@burdenoff/microfe-vibecontrols 2026.623.1 → 2026.623.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/actions/ActionCard.js +2 -2
- package/dist/components/actions/ActionCard.js.map +1 -1
- package/dist/components/actions/ActionTypeBadge.js +1 -1
- package/dist/components/actions/ActionTypeBadge.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 +24 -24
- 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 +4 -4
- 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 +7 -7
- 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 +76 -43
- package/dist/pages/OverviewPage.js.map +1 -1
- package/dist/pages/PluginsCatalogPage.js +7 -4
- 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":"ComponentForm.js","names":[],"sources":["../../../src/components/idp-components/ComponentForm.tsx"],"sourcesContent":["import { useRef, useState } from 'react';\nimport type { FC, ReactNode } from 'react';\nimport { X, ChevronDown, ChevronRight, Github } from 'lucide-react';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\n\nexport interface ComponentFormData {\n name: string;\n description: string;\n type:\n | 'SERVICE'\n | 'FRONTEND'\n | 'LIBRARY'\n | 'API'\n | 'MOBILE'\n | 'DATABASE'\n | 'INFRASTRUCTURE'\n | 'CUSTOM';\n lifecycle: 'EXPERIMENTAL' | 'ACTIVE' | 'DEPRECATED' | 'END_OF_LIFE';\n language: string;\n framework: string;\n version: string;\n ownerName: string;\n tags: string[];\n repositoryUrl?: string;\n documentationUrl?: string;\n dependencies?: Array<{ name: string; version?: string; type?: string }> | null;\n sbomData?: Record<string, unknown> | null;\n}\n\nexport interface ComponentFormProps {\n isOpen: boolean;\n onClose: () => void;\n onSubmit: (data: ComponentFormData) => Promise<void>;\n initialData?: Partial<ComponentFormData>;\n title?: string;\n /** Optional extra content (e.g. a draft EntityTagPicker) rendered above the footer. */\n extraSection?: ReactNode;\n}\n\nexport const ComponentForm: FC<ComponentFormProps> = ({\n isOpen,\n onClose,\n onSubmit,\n initialData,\n title,\n extraSection,\n}) => {\n const { t } = useI18n();\n const tr = (key: string, fallback: string) => {\n const v = t(key);\n return v === key ? fallback : v;\n };\n\n const nameRef = useRef<HTMLInputElement>(null);\n const descriptionRef = useRef<HTMLTextAreaElement>(null);\n const typeRef = useRef<HTMLSelectElement>(null);\n const lifecycleRef = useRef<HTMLSelectElement>(null);\n const languageRef = useRef<HTMLInputElement>(null);\n const frameworkRef = useRef<HTMLInputElement>(null);\n const versionRef = useRef<HTMLInputElement>(null);\n const ownerNameRef = useRef<HTMLInputElement>(null);\n const tagsRef = useRef<HTMLInputElement>(null);\n const repositoryUrlRef = useRef<HTMLInputElement>(null);\n const documentationUrlRef = useRef<HTMLInputElement>(null);\n const dependenciesRef = useRef<HTMLTextAreaElement>(null);\n const sbomDataRef = useRef<HTMLTextAreaElement>(null);\n\n const [loading, setLoading] = useState(false);\n const [error, setError] = useState<string | null>(null);\n const [fieldErrors, setFieldErrors] = useState<{\n name?: string;\n repositoryUrl?: string;\n dependencies?: string;\n sbomData?: string;\n }>({});\n const [showAdvanced, setShowAdvanced] = useState(false);\n\n if (!isOpen) return null;\n\n const handleSubmit = async () => {\n const name = nameRef.current?.value?.trim();\n if (!name) {\n setFieldErrors({ name: tr('component.nameRequired', 'Name is required') });\n setError(tr('component.fillRequired', 'Please fill in all required fields'));\n return;\n }\n\n const repoUrl = repositoryUrlRef.current?.value?.trim();\n if (!repoUrl) {\n setFieldErrors({\n repositoryUrl: tr('component.repositoryUrlRequired', 'Repository URL is required'),\n });\n setError(tr('component.fillRequired', 'Please fill in all required fields'));\n return;\n }\n if (!/^https?:\\/\\/.+\\/.+/.test(repoUrl)) {\n setFieldErrors({\n repositoryUrl: tr(\n 'component.repositoryUrlInvalid',\n 'Enter a valid repository URL (e.g. https://github.com/org/repo)'\n ),\n });\n setError(tr('component.fillRequired', 'Please fix the errors above'));\n return;\n }\n\n const newFieldErrors: {\n name?: string;\n repositoryUrl?: string;\n dependencies?: string;\n sbomData?: string;\n } = {};\n\n let parsedDependencies: ComponentFormData['dependencies'] = null;\n const depsRaw = dependenciesRef.current?.value?.trim();\n if (depsRaw) {\n try {\n parsedDependencies = JSON.parse(depsRaw);\n if (!Array.isArray(parsedDependencies)) throw new Error('Must be a JSON array');\n } catch {\n newFieldErrors.dependencies =\n 'Invalid JSON — must be an array like [{\"name\":\"react\",\"version\":\"19.0.0\"}]';\n }\n }\n\n let parsedSbom: ComponentFormData['sbomData'] = null;\n const sbomRaw = sbomDataRef.current?.value?.trim();\n if (sbomRaw) {\n try {\n parsedSbom = JSON.parse(sbomRaw);\n } catch {\n newFieldErrors.sbomData = 'Invalid JSON — paste a valid CycloneDX or SPDX document';\n }\n }\n\n if (Object.keys(newFieldErrors).length > 0) {\n setFieldErrors(newFieldErrors);\n setError(tr('component.fillRequired', 'Please fix the errors above'));\n return;\n }\n\n setFieldErrors({});\n\n const data: ComponentFormData = {\n name,\n description: descriptionRef.current?.value?.trim() || '',\n type: (typeRef.current?.value as ComponentFormData['type']) || 'SERVICE',\n lifecycle: (lifecycleRef.current?.value as ComponentFormData['lifecycle']) || 'EXPERIMENTAL',\n language: languageRef.current?.value?.trim() || '',\n framework: frameworkRef.current?.value?.trim() || '',\n version: versionRef.current?.value?.trim() || '',\n ownerName: ownerNameRef.current?.value?.trim() || '',\n tags: tagsRef.current?.value\n ? tagsRef.current.value\n .split(',')\n .map((s) => s.trim())\n .filter(Boolean)\n : [],\n repositoryUrl: repositoryUrlRef.current?.value?.trim() || undefined,\n documentationUrl: documentationUrlRef.current?.value?.trim() || undefined,\n dependencies: parsedDependencies,\n sbomData: parsedSbom,\n };\n\n setLoading(true);\n setError(null);\n try {\n await onSubmit(data);\n onClose();\n } catch (err) {\n setError(\n err instanceof Error ? err.message : tr('component.failedSave', 'Failed to save component')\n );\n } finally {\n setLoading(false);\n }\n };\n\n return (\n <div\n className=\"fixed inset-0 flex items-center justify-center z-50 p-4\"\n style={{ backgroundColor: 'rgba(0,0,0,0.55)' }}\n >\n <div className=\"bg-bg-surface rounded-lg p-6 w-full max-w-lg max-h-[90vh] overflow-y-auto\">\n <div className=\"flex items-center justify-between mb-4\">\n <h2 className=\"text-lg font-semibold text-text-primary\">\n {title || tr('component.createComponent', 'Create Component')}\n </h2>\n <button\n type=\"button\"\n onClick={onClose}\n className=\"p-1 rounded hover:bg-bg-sunken transition-colors text-text-secondary\"\n >\n <X className=\"size-5\" />\n </button>\n </div>\n <div className=\"space-y-4\">\n {error && (\n <div className=\"p-3 bg-status-error-bg text-status-error-text text-sm rounded-md\">\n {error}\n </div>\n )}\n <div>\n <label className=\"block text-sm font-medium text-text-primary mb-1\">\n {tr('component.nameLabel', 'Name *')}\n </label>\n <input\n aria-label=\"control\"\n ref={nameRef}\n type=\"text\"\n defaultValue={initialData?.name || ''}\n placeholder={tr('component.namePlaceholder', 'my-service')}\n onChange={() =>\n fieldErrors.name && setFieldErrors((e) => ({ ...e, name: undefined }))\n }\n className={`w-full px-3 py-2 bg-bg-sunken border rounded-md text-text-primary placeholder:text-text-placeholder ${fieldErrors.name ? 'border-status-error-border' : 'border-border-default'}`}\n />\n {fieldErrors.name && (\n <p className=\"mt-1 text-xs text-status-error-text\">{fieldErrors.name}</p>\n )}\n </div>\n <div>\n <label className=\"block text-sm font-medium text-text-primary mb-1\">\n {tr('component.descriptionLabel', 'Description')}\n </label>\n <textarea\n aria-label=\"control\"\n ref={descriptionRef}\n defaultValue={initialData?.description || ''}\n placeholder={tr('component.descriptionPlaceholder', 'Describe this component...')}\n rows={3}\n className=\"w-full px-3 py-2 bg-bg-sunken border border-border-default rounded-md text-text-primary placeholder:text-text-placeholder resize-none\"\n />\n </div>\n <div>\n <label className=\"block text-sm font-medium text-text-primary mb-1\">\n <span className=\"flex items-center gap-1.5\">\n <Github className=\"size-3.5\" />\n {tr('component.repositoryUrl', 'Repository URL')}{' '}\n <span className=\"text-status-error-text\">*</span>\n </span>\n </label>\n <input\n aria-label=\"https://github.com/org/repo\"\n ref={repositoryUrlRef}\n type=\"url\"\n defaultValue={initialData?.repositoryUrl || ''}\n placeholder=\"https://github.com/org/repo\"\n onChange={() =>\n fieldErrors.repositoryUrl &&\n setFieldErrors((e) => ({ ...e, repositoryUrl: undefined }))\n }\n className={`w-full px-3 py-2 bg-bg-sunken border rounded-md text-text-primary placeholder:text-text-placeholder text-sm ${fieldErrors.repositoryUrl ? 'border-status-error-border' : 'border-border-default'}`}\n />\n {fieldErrors.repositoryUrl && (\n <p className=\"mt-1 text-xs text-status-error-text\">{fieldErrors.repositoryUrl}</p>\n )}\n </div>\n <div className=\"grid grid-cols-1 sm:grid-cols-2 gap-4\">\n <div>\n <label className=\"block text-sm font-medium text-text-primary mb-1\">\n {tr('component.typeLabel', 'Type')}\n </label>\n <select\n ref={typeRef}\n defaultValue={initialData?.type || 'SERVICE'}\n className=\"w-full px-3 py-2 bg-bg-sunken border border-border-default rounded-md text-text-primary\"\n >\n <option value=\"SERVICE\">{tr('component.typeService', 'Service')}</option>\n <option value=\"FRONTEND\">{tr('component.typeFrontend', 'Frontend')}</option>\n <option value=\"LIBRARY\">{tr('component.typeLibrary', 'Library')}</option>\n <option value=\"API\">{tr('component.typeApi', 'API')}</option>\n <option value=\"MOBILE\">{tr('component.typeMobile', 'Mobile')}</option>\n <option value=\"DATABASE\">{tr('component.typeDatabase', 'Database')}</option>\n <option value=\"INFRASTRUCTURE\">\n {tr('component.typeInfrastructure', 'Infrastructure')}\n </option>\n <option value=\"CUSTOM\">{tr('component.typeCustom', 'Custom')}</option>\n </select>\n </div>\n <div>\n <label className=\"block text-sm font-medium text-text-primary mb-1\">\n {tr('component.lifecycleLabel', 'Lifecycle')}\n </label>\n <select\n ref={lifecycleRef}\n defaultValue={initialData?.lifecycle || 'EXPERIMENTAL'}\n className=\"w-full px-3 py-2 bg-bg-sunken border border-border-default rounded-md text-text-primary\"\n >\n <option value=\"EXPERIMENTAL\">\n {tr('component.lifecycleExperimental', 'Experimental')}\n </option>\n <option value=\"ACTIVE\">{tr('component.lifecycleActive', 'Active')}</option>\n <option value=\"DEPRECATED\">\n {tr('component.lifecycleDeprecated', 'Deprecated')}\n </option>\n <option value=\"END_OF_LIFE\">\n {tr('component.lifecycleEndOfLife', 'End of Life')}\n </option>\n </select>\n </div>\n </div>\n <div className=\"grid grid-cols-1 sm:grid-cols-2 gap-4\">\n <div>\n <label className=\"block text-sm font-medium text-text-primary mb-1\">\n {tr('component.languageLabel', 'Language')}\n </label>\n <input\n aria-label=\"control\"\n ref={languageRef}\n type=\"text\"\n defaultValue={initialData?.language || ''}\n placeholder={tr('component.languagePlaceholder', 'TypeScript')}\n className=\"w-full px-3 py-2 bg-bg-sunken border border-border-default rounded-md text-text-primary placeholder:text-text-placeholder\"\n />\n </div>\n <div>\n <label className=\"block text-sm font-medium text-text-primary mb-1\">\n {tr('component.frameworkLabel', 'Framework')}\n </label>\n <input\n aria-label=\"control\"\n ref={frameworkRef}\n type=\"text\"\n defaultValue={initialData?.framework || ''}\n placeholder={tr('component.frameworkPlaceholder', 'React')}\n className=\"w-full px-3 py-2 bg-bg-sunken border border-border-default rounded-md text-text-primary placeholder:text-text-placeholder\"\n />\n </div>\n </div>\n <div className=\"grid grid-cols-1 sm:grid-cols-2 gap-4\">\n <div>\n <label className=\"block text-sm font-medium text-text-primary mb-1\">\n {tr('component.versionLabel', 'Version')}\n </label>\n <input\n aria-label=\"control\"\n ref={versionRef}\n type=\"text\"\n defaultValue={initialData?.version || ''}\n placeholder={tr('component.versionPlaceholder', '1.0.0')}\n className=\"w-full px-3 py-2 bg-bg-sunken border border-border-default rounded-md text-text-primary placeholder:text-text-placeholder\"\n />\n </div>\n <div>\n <label className=\"block text-sm font-medium text-text-primary mb-1\">\n {tr('component.ownerLabel', 'Owner')}\n </label>\n <input\n aria-label=\"control\"\n ref={ownerNameRef}\n type=\"text\"\n defaultValue={initialData?.ownerName || ''}\n placeholder={tr('component.ownerPlaceholder', 'team-name')}\n className=\"w-full px-3 py-2 bg-bg-sunken border border-border-default rounded-md text-text-primary placeholder:text-text-placeholder\"\n />\n </div>\n </div>\n <div>\n <label className=\"block text-sm font-medium text-text-primary mb-1\">\n {tr('component.tagsLabel', 'Tags (comma-separated)')}\n </label>\n <input\n aria-label=\"control\"\n ref={tagsRef}\n type=\"text\"\n defaultValue={initialData?.tags?.join(', ') || ''}\n placeholder={tr('component.tagsPlaceholder', 'backend, api, graphql')}\n className=\"w-full px-3 py-2 bg-bg-sunken border border-border-default rounded-md text-text-primary placeholder:text-text-placeholder\"\n />\n </div>\n\n {/* Advanced section */}\n <div className=\"border border-border-default rounded-md overflow-hidden\">\n <button\n type=\"button\"\n onClick={() => setShowAdvanced((v) => !v)}\n className=\"w-full flex items-center justify-between px-3 py-2 bg-bg-sunken hover:bg-bg-sunken/80 transition-colors text-sm font-medium text-text-secondary\"\n >\n <span>{tr('component.advancedSection', 'Advanced (Docs, Dependencies, SBOM)')}</span>\n {showAdvanced ? (\n <ChevronDown className=\"size-4\" />\n ) : (\n <ChevronRight className=\"size-4\" />\n )}\n </button>\n {showAdvanced && (\n <div className=\"p-3 space-y-4 border-t border-border-default\">\n <div>\n <label className=\"block text-sm font-medium text-text-primary mb-1\">\n {tr('component.documentationUrl', 'Documentation URL')}\n </label>\n <input\n aria-label=\"https://docs.example.com\"\n ref={documentationUrlRef}\n type=\"url\"\n defaultValue={initialData?.documentationUrl || ''}\n placeholder=\"https://docs.example.com\"\n className=\"w-full px-3 py-2 bg-bg-sunken border border-border-default rounded-md text-text-primary placeholder:text-text-placeholder text-sm\"\n />\n </div>\n <div>\n <label className=\"block text-sm font-medium text-text-primary mb-1\">\n {tr('component.dependencies', 'Dependencies (JSON array)')}\n </label>\n <textarea\n aria-label=\"control\"\n ref={dependenciesRef}\n rows={4}\n defaultValue={\n initialData?.dependencies\n ? JSON.stringify(initialData.dependencies, null, 2)\n : ''\n }\n placeholder={\n '[\\n {\"name\": \"react\", \"version\": \"19.0.0\", \"type\": \"runtime\"},\\n {\"name\": \"typescript\", \"version\": \"5.9.0\", \"type\": \"dev\"}\\n]'\n }\n className={`w-full px-3 py-2 bg-bg-sunken border rounded-md text-text-primary placeholder:text-text-muted font-mono text-xs resize-y ${fieldErrors.dependencies ? 'border-status-error-border' : 'border-border-default'}`}\n />\n {fieldErrors.dependencies && (\n <p className=\"mt-1 text-xs text-status-error-text\">\n {fieldErrors.dependencies}\n </p>\n )}\n <p className=\"mt-1 text-xs text-text-muted\">\n {tr(\n 'component.dependenciesHint',\n 'Used to build the Dependency Graph in Catalogs. Each entry: {name, version, type}.'\n )}\n </p>\n </div>\n <div>\n <label className=\"block text-sm font-medium text-text-primary mb-1\">\n {tr('component.sbomData', 'SBOM Data (CycloneDX / SPDX JSON)')}\n </label>\n <textarea\n aria-label=\"control\"\n ref={sbomDataRef}\n rows={5}\n defaultValue={\n initialData?.sbomData ? JSON.stringify(initialData.sbomData, null, 2) : ''\n }\n placeholder={\n '{\\n \"bomFormat\": \"CycloneDX\",\\n \"specVersion\": \"1.4\",\\n \"components\": [...]\\n}'\n }\n className={`w-full px-3 py-2 bg-bg-sunken border rounded-md text-text-primary placeholder:text-text-muted font-mono text-xs resize-y ${fieldErrors.sbomData ? 'border-status-error-border' : 'border-border-default'}`}\n />\n {fieldErrors.sbomData && (\n <p className=\"mt-1 text-xs text-status-error-text\">{fieldErrors.sbomData}</p>\n )}\n <p className=\"mt-1 text-xs text-text-muted\">\n {tr(\n 'component.sbomDataHint',\n 'Paste output from syft, trivy, or npm sbom. Aggregated per-catalog in the SBOM tab.'\n )}\n </p>\n </div>\n </div>\n )}\n </div>\n </div>\n {extraSection && (\n <div className=\"mt-4 border-t border-border-default pt-4\">{extraSection}</div>\n )}\n <div className=\"flex flex-col sm:flex-row justify-end gap-3 mt-6\">\n <button\n type=\"button\"\n onClick={onClose}\n disabled={loading}\n className=\"px-4 py-2 text-text-primary hover:bg-bg-sunken rounded-md transition-colors disabled:opacity-50\"\n >\n {tr('common.cancel', 'Cancel')}\n </button>\n <button\n type=\"button\"\n onClick={handleSubmit}\n disabled={loading}\n className=\"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 >\n {loading\n ? tr('common.saving', 'Saving...')\n : initialData?.name\n ? tr('common.save', 'Save')\n : tr('component.createComponent', 'Create Component')}\n </button>\n </div>\n </div>\n </div>\n );\n};\n"],"mappings":";;;;;AAuCA,IAAa,KAAyC,EACpD,WACA,YACA,aACA,gBACA,UACA,sBACI;CACJ,IAAM,EAAE,SAAM,GAAS,EACjB,KAAM,GAAa,MAAqB;EAC5C,IAAM,IAAI,EAAE,EAAI;AAChB,SAAO,MAAM,IAAM,IAAW;IAG1B,IAAU,EAAyB,KAAK,EACxC,IAAiB,EAA4B,KAAK,EAClD,IAAU,EAA0B,KAAK,EACzC,IAAe,EAA0B,KAAK,EAC9C,IAAc,EAAyB,KAAK,EAC5C,IAAe,EAAyB,KAAK,EAC7C,IAAa,EAAyB,KAAK,EAC3C,IAAe,EAAyB,KAAK,EAC7C,IAAU,EAAyB,KAAK,EACxC,IAAmB,EAAyB,KAAK,EACjD,IAAsB,EAAyB,KAAK,EACpD,IAAkB,EAA4B,KAAK,EACnD,IAAc,EAA4B,KAAK,EAE/C,CAAC,GAAS,KAAc,EAAS,GAAM,EACvC,CAAC,GAAO,KAAY,EAAwB,KAAK,EACjD,CAAC,GAAa,KAAkB,EAKnC,EAAE,CAAC,EACA,CAAC,GAAc,KAAmB,EAAS,GAAM;AAuGvD,QArGK,IAsGH,kBAAC,OAAD;EACE,WAAU;EACV,OAAO,EAAE,iBAAiB,oBAAoB;YAE9C,kBAAC,OAAD;GAAK,WAAU;aAAf;IACE,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,MAAD;MAAI,WAAU;gBACX,KAAS,EAAG,6BAA6B,mBAAmB;MAC1D,CAAA,EACL,kBAAC,UAAD;MACE,MAAK;MACL,SAAS;MACT,WAAU;gBAEV,kBAAC,GAAD,EAAG,WAAU,UAAW,CAAA;MACjB,CAAA,CACL;;IACN,kBAAC,OAAD;KAAK,WAAU;eAAf;MACG,KACC,kBAAC,OAAD;OAAK,WAAU;iBACZ;OACG,CAAA;MAER,kBAAC,OAAD,EAAA,UAAA;OACE,kBAAC,SAAD;QAAO,WAAU;kBACd,EAAG,uBAAuB,SAAS;QAC9B,CAAA;OACR,kBAAC,SAAD;QACE,cAAW;QACX,KAAK;QACL,MAAK;QACL,cAAc,GAAa,QAAQ;QACnC,aAAa,EAAG,6BAA6B,aAAa;QAC1D,gBACE,EAAY,QAAQ,GAAgB,OAAO;SAAE,GAAG;SAAG,MAAM,KAAA;SAAW,EAAE;QAExE,WAAW,uGAAuG,EAAY,OAAO,+BAA+B;QACpK,CAAA;OACD,EAAY,QACX,kBAAC,KAAD;QAAG,WAAU;kBAAuC,EAAY;QAAS,CAAA;OAEvE,EAAA,CAAA;MACN,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,SAAD;OAAO,WAAU;iBACd,EAAG,8BAA8B,cAAc;OAC1C,CAAA,EACR,kBAAC,YAAD;OACE,cAAW;OACX,KAAK;OACL,cAAc,GAAa,eAAe;OAC1C,aAAa,EAAG,oCAAoC,6BAA6B;OACjF,MAAM;OACN,WAAU;OACV,CAAA,CACE,EAAA,CAAA;MACN,kBAAC,OAAD,EAAA,UAAA;OACE,kBAAC,SAAD;QAAO,WAAU;kBACf,kBAAC,QAAD;SAAM,WAAU;mBAAhB;UACE,kBAAC,GAAD,EAAQ,WAAU,YAAa,CAAA;UAC9B,EAAG,2BAA2B,iBAAiB;UAAE;UAClD,kBAAC,QAAD;WAAM,WAAU;qBAAyB;WAAQ,CAAA;UAC5C;;QACD,CAAA;OACR,kBAAC,SAAD;QACE,cAAW;QACX,KAAK;QACL,MAAK;QACL,cAAc,GAAa,iBAAiB;QAC5C,aAAY;QACZ,gBACE,EAAY,iBACZ,GAAgB,OAAO;SAAE,GAAG;SAAG,eAAe,KAAA;SAAW,EAAE;QAE7D,WAAW,+GAA+G,EAAY,gBAAgB,+BAA+B;QACrL,CAAA;OACD,EAAY,iBACX,kBAAC,KAAD;QAAG,WAAU;kBAAuC,EAAY;QAAkB,CAAA;OAEhF,EAAA,CAAA;MACN,kBAAC,OAAD;OAAK,WAAU;iBAAf,CACE,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,SAAD;QAAO,WAAU;kBACd,EAAG,uBAAuB,OAAO;QAC5B,CAAA,EACR,kBAAC,UAAD;QACE,KAAK;QACL,cAAc,GAAa,QAAQ;QACnC,WAAU;kBAHZ;SAKE,kBAAC,UAAD;UAAQ,OAAM;oBAAW,EAAG,yBAAyB,UAAU;UAAU,CAAA;SACzE,kBAAC,UAAD;UAAQ,OAAM;oBAAY,EAAG,0BAA0B,WAAW;UAAU,CAAA;SAC5E,kBAAC,UAAD;UAAQ,OAAM;oBAAW,EAAG,yBAAyB,UAAU;UAAU,CAAA;SACzE,kBAAC,UAAD;UAAQ,OAAM;oBAAO,EAAG,qBAAqB,MAAM;UAAU,CAAA;SAC7D,kBAAC,UAAD;UAAQ,OAAM;oBAAU,EAAG,wBAAwB,SAAS;UAAU,CAAA;SACtE,kBAAC,UAAD;UAAQ,OAAM;oBAAY,EAAG,0BAA0B,WAAW;UAAU,CAAA;SAC5E,kBAAC,UAAD;UAAQ,OAAM;oBACX,EAAG,gCAAgC,iBAAiB;UAC9C,CAAA;SACT,kBAAC,UAAD;UAAQ,OAAM;oBAAU,EAAG,wBAAwB,SAAS;UAAU,CAAA;SAC/D;UACL,EAAA,CAAA,EACN,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,SAAD;QAAO,WAAU;kBACd,EAAG,4BAA4B,YAAY;QACtC,CAAA,EACR,kBAAC,UAAD;QACE,KAAK;QACL,cAAc,GAAa,aAAa;QACxC,WAAU;kBAHZ;SAKE,kBAAC,UAAD;UAAQ,OAAM;oBACX,EAAG,mCAAmC,eAAe;UAC/C,CAAA;SACT,kBAAC,UAAD;UAAQ,OAAM;oBAAU,EAAG,6BAA6B,SAAS;UAAU,CAAA;SAC3E,kBAAC,UAAD;UAAQ,OAAM;oBACX,EAAG,iCAAiC,aAAa;UAC3C,CAAA;SACT,kBAAC,UAAD;UAAQ,OAAM;oBACX,EAAG,gCAAgC,cAAc;UAC3C,CAAA;SACF;UACL,EAAA,CAAA,CACF;;MACN,kBAAC,OAAD;OAAK,WAAU;iBAAf,CACE,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,SAAD;QAAO,WAAU;kBACd,EAAG,2BAA2B,WAAW;QACpC,CAAA,EACR,kBAAC,SAAD;QACE,cAAW;QACX,KAAK;QACL,MAAK;QACL,cAAc,GAAa,YAAY;QACvC,aAAa,EAAG,iCAAiC,aAAa;QAC9D,WAAU;QACV,CAAA,CACE,EAAA,CAAA,EACN,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,SAAD;QAAO,WAAU;kBACd,EAAG,4BAA4B,YAAY;QACtC,CAAA,EACR,kBAAC,SAAD;QACE,cAAW;QACX,KAAK;QACL,MAAK;QACL,cAAc,GAAa,aAAa;QACxC,aAAa,EAAG,kCAAkC,QAAQ;QAC1D,WAAU;QACV,CAAA,CACE,EAAA,CAAA,CACF;;MACN,kBAAC,OAAD;OAAK,WAAU;iBAAf,CACE,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,SAAD;QAAO,WAAU;kBACd,EAAG,0BAA0B,UAAU;QAClC,CAAA,EACR,kBAAC,SAAD;QACE,cAAW;QACX,KAAK;QACL,MAAK;QACL,cAAc,GAAa,WAAW;QACtC,aAAa,EAAG,gCAAgC,QAAQ;QACxD,WAAU;QACV,CAAA,CACE,EAAA,CAAA,EACN,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,SAAD;QAAO,WAAU;kBACd,EAAG,wBAAwB,QAAQ;QAC9B,CAAA,EACR,kBAAC,SAAD;QACE,cAAW;QACX,KAAK;QACL,MAAK;QACL,cAAc,GAAa,aAAa;QACxC,aAAa,EAAG,8BAA8B,YAAY;QAC1D,WAAU;QACV,CAAA,CACE,EAAA,CAAA,CACF;;MACN,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,SAAD;OAAO,WAAU;iBACd,EAAG,uBAAuB,yBAAyB;OAC9C,CAAA,EACR,kBAAC,SAAD;OACE,cAAW;OACX,KAAK;OACL,MAAK;OACL,cAAc,GAAa,MAAM,KAAK,KAAK,IAAI;OAC/C,aAAa,EAAG,6BAA6B,wBAAwB;OACrE,WAAU;OACV,CAAA,CACE,EAAA,CAAA;MAGN,kBAAC,OAAD;OAAK,WAAU;iBAAf,CACE,kBAAC,UAAD;QACE,MAAK;QACL,eAAe,GAAiB,MAAM,CAAC,EAAE;QACzC,WAAU;kBAHZ,CAKE,kBAAC,QAAD,EAAA,UAAO,EAAG,6BAA6B,sCAAsC,EAAQ,CAAA,EAEnF,EADD,IACE,IAEA,GAFD,EAAa,WAAU,UAAW,CAEC,CAE9B;WACR,KACC,kBAAC,OAAD;QAAK,WAAU;kBAAf;SACE,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,SAAD;UAAO,WAAU;oBACd,EAAG,8BAA8B,oBAAoB;UAChD,CAAA,EACR,kBAAC,SAAD;UACE,cAAW;UACX,KAAK;UACL,MAAK;UACL,cAAc,GAAa,oBAAoB;UAC/C,aAAY;UACZ,WAAU;UACV,CAAA,CACE,EAAA,CAAA;SACN,kBAAC,OAAD,EAAA,UAAA;UACE,kBAAC,SAAD;WAAO,WAAU;qBACd,EAAG,0BAA0B,4BAA4B;WACpD,CAAA;UACR,kBAAC,YAAD;WACE,cAAW;WACX,KAAK;WACL,MAAM;WACN,cACE,GAAa,eACT,KAAK,UAAU,EAAY,cAAc,MAAM,EAAE,GACjD;WAEN,aACE;WAEF,WAAW,4HAA4H,EAAY,eAAe,+BAA+B;WACjM,CAAA;UACD,EAAY,gBACX,kBAAC,KAAD;WAAG,WAAU;qBACV,EAAY;WACX,CAAA;UAEN,kBAAC,KAAD;WAAG,WAAU;qBACV,EACC,8BACA,qFACD;WACC,CAAA;UACA,EAAA,CAAA;SACN,kBAAC,OAAD,EAAA,UAAA;UACE,kBAAC,SAAD;WAAO,WAAU;qBACd,EAAG,sBAAsB,oCAAoC;WACxD,CAAA;UACR,kBAAC,YAAD;WACE,cAAW;WACX,KAAK;WACL,MAAM;WACN,cACE,GAAa,WAAW,KAAK,UAAU,EAAY,UAAU,MAAM,EAAE,GAAG;WAE1E,aACE;WAEF,WAAW,4HAA4H,EAAY,WAAW,+BAA+B;WAC7L,CAAA;UACD,EAAY,YACX,kBAAC,KAAD;WAAG,WAAU;qBAAuC,EAAY;WAAa,CAAA;UAE/E,kBAAC,KAAD;WAAG,WAAU;qBACV,EACC,0BACA,sFACD;WACC,CAAA;UACA,EAAA,CAAA;SACF;UAEJ;;MACF;;IACL,KACC,kBAAC,OAAD;KAAK,WAAU;eAA4C;KAAmB,CAAA;IAEhF,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,UAAD;MACE,MAAK;MACL,SAAS;MACT,UAAU;MACV,WAAU;gBAET,EAAG,iBAAiB,SAAS;MACvB,CAAA,EACT,kBAAC,UAAD;MACE,MAAK;MACL,SA5YW,YAAY;OAC/B,IAAM,IAAO,EAAQ,SAAS,OAAO,MAAM;AAC3C,WAAI,CAAC,GAAM;AAET,QADA,EAAe,EAAE,MAAM,EAAG,0BAA0B,mBAAmB,EAAE,CAAC,EAC1E,EAAS,EAAG,0BAA0B,qCAAqC,CAAC;AAC5E;;OAGF,IAAM,IAAU,EAAiB,SAAS,OAAO,MAAM;AACvD,WAAI,CAAC,GAAS;AAIZ,QAHA,EAAe,EACb,eAAe,EAAG,mCAAmC,6BAA6B,EACnF,CAAC,EACF,EAAS,EAAG,0BAA0B,qCAAqC,CAAC;AAC5E;;AAEF,WAAI,CAAC,qBAAqB,KAAK,EAAQ,EAAE;AAOvC,QANA,EAAe,EACb,eAAe,EACb,kCACA,kEACD,EACF,CAAC,EACF,EAAS,EAAG,0BAA0B,8BAA8B,CAAC;AACrE;;OAGF,IAAM,IAKF,EAAE,EAEF,IAAwD,MACtD,IAAU,EAAgB,SAAS,OAAO,MAAM;AACtD,WAAI,EACF,KAAI;AAEF,YADA,IAAqB,KAAK,MAAM,EAAQ,EACpC,CAAC,MAAM,QAAQ,EAAmB,CAAE,OAAU,MAAM,uBAAuB;eACzE;AACN,UAAe,eACb;;OAIN,IAAI,IAA4C,MAC1C,IAAU,EAAY,SAAS,OAAO,MAAM;AAClD,WAAI,EACF,KAAI;AACF,YAAa,KAAK,MAAM,EAAQ;eAC1B;AACN,UAAe,WAAW;;AAI9B,WAAI,OAAO,KAAK,EAAe,CAAC,SAAS,GAAG;AAE1C,QADA,EAAe,EAAe,EAC9B,EAAS,EAAG,0BAA0B,8BAA8B,CAAC;AACrE;;AAGF,SAAe,EAAE,CAAC;OAElB,IAAM,IAA0B;QAC9B;QACA,aAAa,EAAe,SAAS,OAAO,MAAM,IAAI;QACtD,MAAO,EAAQ,SAAS,SAAuC;QAC/D,WAAY,EAAa,SAAS,SAA4C;QAC9E,UAAU,EAAY,SAAS,OAAO,MAAM,IAAI;QAChD,WAAW,EAAa,SAAS,OAAO,MAAM,IAAI;QAClD,SAAS,EAAW,SAAS,OAAO,MAAM,IAAI;QAC9C,WAAW,EAAa,SAAS,OAAO,MAAM,IAAI;QAClD,MAAM,EAAQ,SAAS,QACnB,EAAQ,QAAQ,MACb,MAAM,IAAI,CACV,KAAK,MAAM,EAAE,MAAM,CAAC,CACpB,OAAO,QAAQ,GAClB,EAAE;QACN,eAAe,EAAiB,SAAS,OAAO,MAAM,IAAI,KAAA;QAC1D,kBAAkB,EAAoB,SAAS,OAAO,MAAM,IAAI,KAAA;QAChE,cAAc;QACd,UAAU;QACX;AAGD,OADA,EAAW,GAAK,EAChB,EAAS,KAAK;AACd,WAAI;AAEF,QADA,MAAM,EAAS,EAAK,EACpB,GAAS;gBACF,GAAK;AACZ,UACE,aAAe,QAAQ,EAAI,UAAU,EAAG,wBAAwB,2BAA2B,CAC5F;iBACO;AACR,UAAW,GAAM;;;MA8SX,UAAU;MACV,WAAU;gBAET,IACG,EAAG,iBAAiB,YAAY,GAChC,GAAa,OACX,EAAG,eAAe,OAAO,GACzB,EAAG,6BAA6B,mBAAmB;MAClD,CAAA,CACL;;IACF;;EACF,CAAA,GA1ZY"}
|
|
1
|
+
{"version":3,"file":"ComponentForm.js","names":[],"sources":["../../../src/components/idp-components/ComponentForm.tsx"],"sourcesContent":["import { useRef, useState } from 'react';\nimport type { FC, ReactNode } from 'react';\nimport { X, ChevronDown, ChevronRight, Github } from 'lucide-react';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\n\nexport interface ComponentFormData {\n name: string;\n description: string;\n type:\n | 'SERVICE'\n | 'FRONTEND'\n | 'LIBRARY'\n | 'API'\n | 'MOBILE'\n | 'DATABASE'\n | 'INFRASTRUCTURE'\n | 'CUSTOM';\n lifecycle: 'EXPERIMENTAL' | 'ACTIVE' | 'DEPRECATED' | 'END_OF_LIFE';\n language: string;\n framework: string;\n version: string;\n ownerName: string;\n tags: string[];\n repositoryUrl?: string;\n documentationUrl?: string;\n dependencies?: Array<{ name: string; version?: string; type?: string }> | null;\n sbomData?: Record<string, unknown> | null;\n}\n\nexport interface ComponentFormProps {\n isOpen: boolean;\n onClose: () => void;\n onSubmit: (data: ComponentFormData) => Promise<void>;\n initialData?: Partial<ComponentFormData>;\n title?: string;\n /** Optional extra content (e.g. a draft EntityTagPicker) rendered above the footer. */\n extraSection?: ReactNode;\n}\n\nexport const ComponentForm: FC<ComponentFormProps> = ({\n isOpen,\n onClose,\n onSubmit,\n initialData,\n title,\n extraSection,\n}) => {\n const { t } = useI18n();\n const tr = (key: string, fallback: string) => {\n const v = t(key);\n return v === key ? fallback : v;\n };\n\n const nameRef = useRef<HTMLInputElement>(null);\n const descriptionRef = useRef<HTMLTextAreaElement>(null);\n const typeRef = useRef<HTMLSelectElement>(null);\n const lifecycleRef = useRef<HTMLSelectElement>(null);\n const languageRef = useRef<HTMLInputElement>(null);\n const frameworkRef = useRef<HTMLInputElement>(null);\n const versionRef = useRef<HTMLInputElement>(null);\n const ownerNameRef = useRef<HTMLInputElement>(null);\n const tagsRef = useRef<HTMLInputElement>(null);\n const repositoryUrlRef = useRef<HTMLInputElement>(null);\n const documentationUrlRef = useRef<HTMLInputElement>(null);\n const dependenciesRef = useRef<HTMLTextAreaElement>(null);\n const sbomDataRef = useRef<HTMLTextAreaElement>(null);\n\n const [loading, setLoading] = useState(false);\n const [error, setError] = useState<string | null>(null);\n const [fieldErrors, setFieldErrors] = useState<{\n name?: string;\n repositoryUrl?: string;\n dependencies?: string;\n sbomData?: string;\n }>({});\n const [showAdvanced, setShowAdvanced] = useState(false);\n\n if (!isOpen) return null;\n\n const handleSubmit = async () => {\n const name = nameRef.current?.value?.trim();\n if (!name) {\n setFieldErrors({ name: tr('component.nameRequired', 'Name is required') });\n setError(tr('component.fillRequired', 'Please fill in all required fields'));\n return;\n }\n\n const repoUrl = repositoryUrlRef.current?.value?.trim();\n if (!repoUrl) {\n setFieldErrors({\n repositoryUrl: tr('component.repositoryUrlRequired', 'Repository URL is required'),\n });\n setError(tr('component.fillRequired', 'Please fill in all required fields'));\n return;\n }\n if (!/^https?:\\/\\/.+\\/.+/.test(repoUrl)) {\n setFieldErrors({\n repositoryUrl: tr(\n 'component.repositoryUrlInvalid',\n 'Enter a valid repository URL (e.g. https://github.com/org/repo)'\n ),\n });\n setError(tr('component.fillRequired', 'Please fix the errors above'));\n return;\n }\n\n const newFieldErrors: {\n name?: string;\n repositoryUrl?: string;\n dependencies?: string;\n sbomData?: string;\n } = {};\n\n let parsedDependencies: ComponentFormData['dependencies'] = null;\n const depsRaw = dependenciesRef.current?.value?.trim();\n if (depsRaw) {\n try {\n parsedDependencies = JSON.parse(depsRaw);\n if (!Array.isArray(parsedDependencies)) throw new Error('Must be a JSON array');\n } catch {\n newFieldErrors.dependencies =\n 'Invalid JSON — must be an array like [{\"name\":\"react\",\"version\":\"19.0.0\"}]';\n }\n }\n\n let parsedSbom: ComponentFormData['sbomData'] = null;\n const sbomRaw = sbomDataRef.current?.value?.trim();\n if (sbomRaw) {\n try {\n parsedSbom = JSON.parse(sbomRaw);\n } catch {\n newFieldErrors.sbomData = 'Invalid JSON — paste a valid CycloneDX or SPDX document';\n }\n }\n\n if (Object.keys(newFieldErrors).length > 0) {\n setFieldErrors(newFieldErrors);\n setError(tr('component.fillRequired', 'Please fix the errors above'));\n return;\n }\n\n setFieldErrors({});\n\n const data: ComponentFormData = {\n name,\n description: descriptionRef.current?.value?.trim() || '',\n type: (typeRef.current?.value as ComponentFormData['type']) || 'SERVICE',\n lifecycle: (lifecycleRef.current?.value as ComponentFormData['lifecycle']) || 'EXPERIMENTAL',\n language: languageRef.current?.value?.trim() || '',\n framework: frameworkRef.current?.value?.trim() || '',\n version: versionRef.current?.value?.trim() || '',\n ownerName: ownerNameRef.current?.value?.trim() || '',\n tags: tagsRef.current?.value\n ? tagsRef.current.value\n .split(',')\n .map((s) => s.trim())\n .filter(Boolean)\n : [],\n repositoryUrl: repositoryUrlRef.current?.value?.trim() || undefined,\n documentationUrl: documentationUrlRef.current?.value?.trim() || undefined,\n dependencies: parsedDependencies,\n sbomData: parsedSbom,\n };\n\n setLoading(true);\n setError(null);\n try {\n await onSubmit(data);\n onClose();\n } catch (err) {\n setError(\n err instanceof Error ? err.message : tr('component.failedSave', 'Failed to save component')\n );\n } finally {\n setLoading(false);\n }\n };\n\n return (\n <div\n className=\"fixed inset-0 flex items-center justify-center z-50 p-4\"\n style={{ backgroundColor: 'rgba(0,0,0,0.55)' }}\n >\n <div className=\"bg-bg-surface rounded-lg p-6 w-full max-w-lg max-h-[90vh] overflow-y-auto\">\n <div className=\"flex items-center justify-between mb-4\">\n <h2 className=\"text-lg font-semibold text-text-primary\">\n {title || tr('component.createComponent', 'Create Component')}\n </h2>\n <button\n type=\"button\"\n onClick={onClose}\n className=\"p-1 rounded hover:bg-bg-sunken transition-colors text-text-secondary\"\n >\n <X className=\"size-5\" />\n </button>\n </div>\n <div className=\"space-y-4\">\n {error && (\n <div className=\"p-3 bg-status-error-bg-subtle text-status-error-text text-sm rounded-md\">\n {error}\n </div>\n )}\n <div>\n <label className=\"block text-sm font-medium text-text-primary mb-1\">\n {tr('component.nameLabel', 'Name *')}\n </label>\n <input\n aria-label=\"control\"\n ref={nameRef}\n type=\"text\"\n defaultValue={initialData?.name || ''}\n placeholder={tr('component.namePlaceholder', 'my-service')}\n onChange={() =>\n fieldErrors.name && setFieldErrors((e) => ({ ...e, name: undefined }))\n }\n className={`w-full px-3 py-2 bg-bg-sunken border rounded-md text-text-primary placeholder:text-text-placeholder ${fieldErrors.name ? 'border-status-error-border' : 'border-border-default'}`}\n />\n {fieldErrors.name && (\n <p className=\"mt-1 text-xs text-status-error-text\">{fieldErrors.name}</p>\n )}\n </div>\n <div>\n <label className=\"block text-sm font-medium text-text-primary mb-1\">\n {tr('component.descriptionLabel', 'Description')}\n </label>\n <textarea\n aria-label=\"control\"\n ref={descriptionRef}\n defaultValue={initialData?.description || ''}\n placeholder={tr('component.descriptionPlaceholder', 'Describe this component...')}\n rows={3}\n className=\"w-full px-3 py-2 bg-bg-sunken border border-border-default rounded-md text-text-primary placeholder:text-text-placeholder resize-none\"\n />\n </div>\n <div>\n <label className=\"block text-sm font-medium text-text-primary mb-1\">\n <span className=\"flex items-center gap-1.5\">\n <Github className=\"size-3.5\" />\n {tr('component.repositoryUrl', 'Repository URL')}{' '}\n <span className=\"text-status-error-text\">*</span>\n </span>\n </label>\n <input\n aria-label=\"https://github.com/org/repo\"\n ref={repositoryUrlRef}\n type=\"url\"\n defaultValue={initialData?.repositoryUrl || ''}\n placeholder=\"https://github.com/org/repo\"\n onChange={() =>\n fieldErrors.repositoryUrl &&\n setFieldErrors((e) => ({ ...e, repositoryUrl: undefined }))\n }\n className={`w-full px-3 py-2 bg-bg-sunken border rounded-md text-text-primary placeholder:text-text-placeholder text-sm ${fieldErrors.repositoryUrl ? 'border-status-error-border' : 'border-border-default'}`}\n />\n {fieldErrors.repositoryUrl && (\n <p className=\"mt-1 text-xs text-status-error-text\">{fieldErrors.repositoryUrl}</p>\n )}\n </div>\n <div className=\"grid grid-cols-1 sm:grid-cols-2 gap-4\">\n <div>\n <label className=\"block text-sm font-medium text-text-primary mb-1\">\n {tr('component.typeLabel', 'Type')}\n </label>\n <select\n ref={typeRef}\n defaultValue={initialData?.type || 'SERVICE'}\n className=\"w-full px-3 py-2 bg-bg-sunken border border-border-default rounded-md text-text-primary\"\n >\n <option value=\"SERVICE\">{tr('component.typeService', 'Service')}</option>\n <option value=\"FRONTEND\">{tr('component.typeFrontend', 'Frontend')}</option>\n <option value=\"LIBRARY\">{tr('component.typeLibrary', 'Library')}</option>\n <option value=\"API\">{tr('component.typeApi', 'API')}</option>\n <option value=\"MOBILE\">{tr('component.typeMobile', 'Mobile')}</option>\n <option value=\"DATABASE\">{tr('component.typeDatabase', 'Database')}</option>\n <option value=\"INFRASTRUCTURE\">\n {tr('component.typeInfrastructure', 'Infrastructure')}\n </option>\n <option value=\"CUSTOM\">{tr('component.typeCustom', 'Custom')}</option>\n </select>\n </div>\n <div>\n <label className=\"block text-sm font-medium text-text-primary mb-1\">\n {tr('component.lifecycleLabel', 'Lifecycle')}\n </label>\n <select\n ref={lifecycleRef}\n defaultValue={initialData?.lifecycle || 'EXPERIMENTAL'}\n className=\"w-full px-3 py-2 bg-bg-sunken border border-border-default rounded-md text-text-primary\"\n >\n <option value=\"EXPERIMENTAL\">\n {tr('component.lifecycleExperimental', 'Experimental')}\n </option>\n <option value=\"ACTIVE\">{tr('component.lifecycleActive', 'Active')}</option>\n <option value=\"DEPRECATED\">\n {tr('component.lifecycleDeprecated', 'Deprecated')}\n </option>\n <option value=\"END_OF_LIFE\">\n {tr('component.lifecycleEndOfLife', 'End of Life')}\n </option>\n </select>\n </div>\n </div>\n <div className=\"grid grid-cols-1 sm:grid-cols-2 gap-4\">\n <div>\n <label className=\"block text-sm font-medium text-text-primary mb-1\">\n {tr('component.languageLabel', 'Language')}\n </label>\n <input\n aria-label=\"control\"\n ref={languageRef}\n type=\"text\"\n defaultValue={initialData?.language || ''}\n placeholder={tr('component.languagePlaceholder', 'TypeScript')}\n className=\"w-full px-3 py-2 bg-bg-sunken border border-border-default rounded-md text-text-primary placeholder:text-text-placeholder\"\n />\n </div>\n <div>\n <label className=\"block text-sm font-medium text-text-primary mb-1\">\n {tr('component.frameworkLabel', 'Framework')}\n </label>\n <input\n aria-label=\"control\"\n ref={frameworkRef}\n type=\"text\"\n defaultValue={initialData?.framework || ''}\n placeholder={tr('component.frameworkPlaceholder', 'React')}\n className=\"w-full px-3 py-2 bg-bg-sunken border border-border-default rounded-md text-text-primary placeholder:text-text-placeholder\"\n />\n </div>\n </div>\n <div className=\"grid grid-cols-1 sm:grid-cols-2 gap-4\">\n <div>\n <label className=\"block text-sm font-medium text-text-primary mb-1\">\n {tr('component.versionLabel', 'Version')}\n </label>\n <input\n aria-label=\"control\"\n ref={versionRef}\n type=\"text\"\n defaultValue={initialData?.version || ''}\n placeholder={tr('component.versionPlaceholder', '1.0.0')}\n className=\"w-full px-3 py-2 bg-bg-sunken border border-border-default rounded-md text-text-primary placeholder:text-text-placeholder\"\n />\n </div>\n <div>\n <label className=\"block text-sm font-medium text-text-primary mb-1\">\n {tr('component.ownerLabel', 'Owner')}\n </label>\n <input\n aria-label=\"control\"\n ref={ownerNameRef}\n type=\"text\"\n defaultValue={initialData?.ownerName || ''}\n placeholder={tr('component.ownerPlaceholder', 'team-name')}\n className=\"w-full px-3 py-2 bg-bg-sunken border border-border-default rounded-md text-text-primary placeholder:text-text-placeholder\"\n />\n </div>\n </div>\n <div>\n <label className=\"block text-sm font-medium text-text-primary mb-1\">\n {tr('component.tagsLabel', 'Tags (comma-separated)')}\n </label>\n <input\n aria-label=\"control\"\n ref={tagsRef}\n type=\"text\"\n defaultValue={initialData?.tags?.join(', ') || ''}\n placeholder={tr('component.tagsPlaceholder', 'backend, api, graphql')}\n className=\"w-full px-3 py-2 bg-bg-sunken border border-border-default rounded-md text-text-primary placeholder:text-text-placeholder\"\n />\n </div>\n\n {/* Advanced section */}\n <div className=\"border border-border-default rounded-md overflow-hidden\">\n <button\n type=\"button\"\n onClick={() => setShowAdvanced((v) => !v)}\n className=\"w-full flex items-center justify-between px-3 py-2 bg-bg-sunken hover:bg-bg-sunken/80 transition-colors text-sm font-medium text-text-secondary\"\n >\n <span>{tr('component.advancedSection', 'Advanced (Docs, Dependencies, SBOM)')}</span>\n {showAdvanced ? (\n <ChevronDown className=\"size-4\" />\n ) : (\n <ChevronRight className=\"size-4\" />\n )}\n </button>\n {showAdvanced && (\n <div className=\"p-3 space-y-4 border-t border-border-default\">\n <div>\n <label className=\"block text-sm font-medium text-text-primary mb-1\">\n {tr('component.documentationUrl', 'Documentation URL')}\n </label>\n <input\n aria-label=\"https://docs.example.com\"\n ref={documentationUrlRef}\n type=\"url\"\n defaultValue={initialData?.documentationUrl || ''}\n placeholder=\"https://docs.example.com\"\n className=\"w-full px-3 py-2 bg-bg-sunken border border-border-default rounded-md text-text-primary placeholder:text-text-placeholder text-sm\"\n />\n </div>\n <div>\n <label className=\"block text-sm font-medium text-text-primary mb-1\">\n {tr('component.dependencies', 'Dependencies (JSON array)')}\n </label>\n <textarea\n aria-label=\"control\"\n ref={dependenciesRef}\n rows={4}\n defaultValue={\n initialData?.dependencies\n ? JSON.stringify(initialData.dependencies, null, 2)\n : ''\n }\n placeholder={\n '[\\n {\"name\": \"react\", \"version\": \"19.0.0\", \"type\": \"runtime\"},\\n {\"name\": \"typescript\", \"version\": \"5.9.0\", \"type\": \"dev\"}\\n]'\n }\n className={`w-full px-3 py-2 bg-bg-sunken border rounded-md text-text-primary placeholder:text-text-muted font-mono text-xs resize-y ${fieldErrors.dependencies ? 'border-status-error-border' : 'border-border-default'}`}\n />\n {fieldErrors.dependencies && (\n <p className=\"mt-1 text-xs text-status-error-text\">\n {fieldErrors.dependencies}\n </p>\n )}\n <p className=\"mt-1 text-xs text-text-muted\">\n {tr(\n 'component.dependenciesHint',\n 'Used to build the Dependency Graph in Catalogs. Each entry: {name, version, type}.'\n )}\n </p>\n </div>\n <div>\n <label className=\"block text-sm font-medium text-text-primary mb-1\">\n {tr('component.sbomData', 'SBOM Data (CycloneDX / SPDX JSON)')}\n </label>\n <textarea\n aria-label=\"control\"\n ref={sbomDataRef}\n rows={5}\n defaultValue={\n initialData?.sbomData ? JSON.stringify(initialData.sbomData, null, 2) : ''\n }\n placeholder={\n '{\\n \"bomFormat\": \"CycloneDX\",\\n \"specVersion\": \"1.4\",\\n \"components\": [...]\\n}'\n }\n className={`w-full px-3 py-2 bg-bg-sunken border rounded-md text-text-primary placeholder:text-text-muted font-mono text-xs resize-y ${fieldErrors.sbomData ? 'border-status-error-border' : 'border-border-default'}`}\n />\n {fieldErrors.sbomData && (\n <p className=\"mt-1 text-xs text-status-error-text\">{fieldErrors.sbomData}</p>\n )}\n <p className=\"mt-1 text-xs text-text-muted\">\n {tr(\n 'component.sbomDataHint',\n 'Paste output from syft, trivy, or npm sbom. Aggregated per-catalog in the SBOM tab.'\n )}\n </p>\n </div>\n </div>\n )}\n </div>\n </div>\n {extraSection && (\n <div className=\"mt-4 border-t border-border-default pt-4\">{extraSection}</div>\n )}\n <div className=\"flex flex-col sm:flex-row justify-end gap-3 mt-6\">\n <button\n type=\"button\"\n onClick={onClose}\n disabled={loading}\n className=\"px-4 py-2 text-text-primary hover:bg-bg-sunken rounded-md transition-colors disabled:opacity-50\"\n >\n {tr('common.cancel', 'Cancel')}\n </button>\n <button\n type=\"button\"\n onClick={handleSubmit}\n disabled={loading}\n className=\"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 >\n {loading\n ? tr('common.saving', 'Saving...')\n : initialData?.name\n ? tr('common.save', 'Save')\n : tr('component.createComponent', 'Create Component')}\n </button>\n </div>\n </div>\n </div>\n );\n};\n"],"mappings":"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"}
|
|
@@ -45,12 +45,12 @@ function p(e) {
|
|
|
45
45
|
case "peer": return {
|
|
46
46
|
...t,
|
|
47
47
|
backgroundColor: "var(--color-status-info-border)",
|
|
48
|
-
color: "
|
|
48
|
+
color: "var(--color-text-inverse)"
|
|
49
49
|
};
|
|
50
50
|
case "dev": return {
|
|
51
51
|
...t,
|
|
52
52
|
backgroundColor: "var(--color-border-strong)",
|
|
53
|
-
color: "
|
|
53
|
+
color: "var(--color-text-inverse)"
|
|
54
54
|
};
|
|
55
55
|
case "optional": return {
|
|
56
56
|
...t,
|
|
@@ -60,7 +60,7 @@ function p(e) {
|
|
|
60
60
|
default: return {
|
|
61
61
|
...t,
|
|
62
62
|
backgroundColor: "var(--color-action-primary-bg)",
|
|
63
|
-
color: "
|
|
63
|
+
color: "var(--color-action-primary-text)"
|
|
64
64
|
};
|
|
65
65
|
}
|
|
66
66
|
}
|
|
@@ -106,7 +106,7 @@ function h(e, t) {
|
|
|
106
106
|
style: {
|
|
107
107
|
fontSize: 10,
|
|
108
108
|
marginTop: 3,
|
|
109
|
-
backgroundColor: "
|
|
109
|
+
backgroundColor: "var(--color-accent-soft)",
|
|
110
110
|
borderRadius: 4,
|
|
111
111
|
padding: "1px 6px",
|
|
112
112
|
display: "inline-block",
|
|
@@ -128,13 +128,13 @@ function g(e, t, n) {
|
|
|
128
128
|
data: { label: h(e, t) },
|
|
129
129
|
style: {
|
|
130
130
|
backgroundColor: "var(--color-action-primary-bg)",
|
|
131
|
-
color: "
|
|
131
|
+
color: "var(--color-action-primary-text)",
|
|
132
132
|
borderRadius: 10,
|
|
133
133
|
padding: "10px 16px",
|
|
134
134
|
width: 160,
|
|
135
135
|
fontWeight: 700,
|
|
136
136
|
fontSize: 13,
|
|
137
|
-
boxShadow: "
|
|
137
|
+
boxShadow: "var(--shadow-elevation-2)",
|
|
138
138
|
border: "none",
|
|
139
139
|
textAlign: "center"
|
|
140
140
|
}
|
|
@@ -178,7 +178,7 @@ function _({ componentName: t, componentType: s, deps: d, canvasHeight: f }) {
|
|
|
178
178
|
d
|
|
179
179
|
]), [h, , _] = u(p), [v, , y] = l(m);
|
|
180
180
|
return /* @__PURE__ */ n("div", {
|
|
181
|
-
className: "w-full rounded-lg border border-border-default overflow-hidden bg-bg-
|
|
181
|
+
className: "w-full rounded-lg border border-border-default overflow-hidden bg-bg-surface",
|
|
182
182
|
style: { height: f },
|
|
183
183
|
children: /* @__PURE__ */ r(c, {
|
|
184
184
|
nodes: h,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DependencyGraphTab.js","names":[],"sources":["../../../src/components/idp-components/DependencyGraphTab.tsx"],"sourcesContent":["/**\n * DependencyGraphTab — React Flow visualization of a component's dependency graph.\n *\n * Root component node sits on the RIGHT; dependency nodes stack on the LEFT.\n * Edges flow LEFT → RIGHT with type-specific styling.\n * A compact table below the canvas lists all dependencies.\n */\n\nimport { useMemo } from 'react';\nimport { PackageOpen } from 'lucide-react';\nimport '@xyflow/react/dist/style.css';\nimport {\n ReactFlow,\n Background,\n Controls,\n useNodesState,\n useEdgesState,\n type Node,\n type Edge,\n MarkerType,\n BackgroundVariant,\n} from '@xyflow/react';\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\nexport interface DependencyGraphTabProps {\n componentName: string;\n componentType: string;\n dependencies: Array<{ name: string; version?: string | null; type?: string | null }> | null;\n}\n\ntype DepType = 'runtime' | 'dev' | 'peer' | 'optional' | null;\n\nconst MAX_GRAPH_DEPS = 40;\n\n// ---------------------------------------------------------------------------\n// Inline style helpers (React Flow nodes render in a portal — no Tailwind)\n// ---------------------------------------------------------------------------\n\nfunction depNodeStyle(type: DepType): React.CSSProperties {\n switch (type) {\n case 'peer':\n return {\n backgroundColor: 'var(--color-status-info-bg)',\n border: '1px solid var(--color-status-info-border)',\n color: 'var(--color-text-primary)',\n };\n case 'dev':\n return {\n backgroundColor: 'var(--color-bg-sunken)',\n border: '1px dashed var(--color-border-default)',\n color: 'var(--color-text-secondary)',\n };\n case 'optional':\n return {\n backgroundColor: 'var(--color-bg-sunken)',\n border: '1px solid var(--color-border-default)',\n color: 'var(--color-text-primary)',\n opacity: 0.7,\n };\n default:\n // runtime or null\n return {\n backgroundColor: 'var(--color-bg-sunken)',\n border: '1px solid var(--color-border-default)',\n color: 'var(--color-text-primary)',\n };\n }\n}\n\nfunction depTypeBadgeStyle(type: DepType): React.CSSProperties {\n const base: React.CSSProperties = {\n display: 'inline-block',\n fontSize: 10,\n fontWeight: 600,\n borderRadius: 4,\n padding: '1px 5px',\n marginLeft: 4,\n textTransform: 'uppercase',\n letterSpacing: '0.04em',\n };\n switch (type) {\n case 'peer':\n return { ...base, backgroundColor: 'var(--color-status-info-border)', color: '#fff' };\n case 'dev':\n return { ...base, backgroundColor: 'var(--color-border-strong)', color: '#fff' };\n case 'optional':\n return {\n ...base,\n backgroundColor: 'var(--color-border-default)',\n color: 'var(--color-text-secondary)',\n };\n default:\n return { ...base, backgroundColor: 'var(--color-action-primary-bg)', color: '#fff' };\n }\n}\n\n// ---------------------------------------------------------------------------\n// Label renderers (ReactNode — React Flow default node accepts ReactNode label)\n// ---------------------------------------------------------------------------\n\nfunction makeDepLabel(dep: {\n name: string;\n version?: string | null;\n type?: string | null;\n}): React.ReactNode {\n const depType = (dep.type ?? null) as DepType;\n return (\n <div style={{ lineHeight: 1.4 }}>\n <div\n style={{\n fontWeight: 600,\n maxWidth: 180,\n overflow: 'hidden',\n textOverflow: 'ellipsis',\n whiteSpace: 'nowrap',\n }}\n >\n {dep.name}\n {depType && <span style={depTypeBadgeStyle(depType)}>{depType}</span>}\n </div>\n {dep.version && (\n <div style={{ fontSize: 10, color: 'var(--color-text-muted)', marginTop: 2 }}>\n {dep.version}\n </div>\n )}\n </div>\n );\n}\n\nfunction makeRootLabel(componentName: string, componentType: string): React.ReactNode {\n return (\n <div style={{ lineHeight: 1.4, textAlign: 'center' }}>\n <div style={{ fontWeight: 700, fontSize: 13 }}>{componentName}</div>\n <div\n style={{\n fontSize: 10,\n marginTop: 3,\n backgroundColor: 'rgba(255,255,255,0.2)',\n borderRadius: 4,\n padding: '1px 6px',\n display: 'inline-block',\n letterSpacing: '0.05em',\n textTransform: 'uppercase',\n }}\n >\n {componentType}\n </div>\n </div>\n );\n}\n\n// ---------------------------------------------------------------------------\n// Graph element builder\n// ---------------------------------------------------------------------------\n\nfunction buildGraphElements(\n componentName: string,\n componentType: string,\n deps: Array<{ name: string; version?: string | null; type?: string | null }>\n): { nodes: Node[]; edges: Edge[] } {\n const visible = deps.slice(0, MAX_GRAPH_DEPS);\n const totalHeight = Math.max(visible.length * 80, 80);\n const rootY = totalHeight / 2 - 24;\n\n const nodes: Node[] = [];\n const edges: Edge[] = [];\n\n // Root node\n nodes.push({\n id: 'root',\n position: { x: 520, y: rootY },\n data: { label: makeRootLabel(componentName, componentType) },\n style: {\n backgroundColor: 'var(--color-action-primary-bg)',\n color: '#fff',\n borderRadius: 10,\n padding: '10px 16px',\n width: 160,\n fontWeight: 700,\n fontSize: 13,\n boxShadow: '0 2px 8px rgba(0,0,0,0.15)',\n border: 'none',\n textAlign: 'center',\n },\n });\n\n // Dependency nodes and edges\n visible.forEach((dep, i) => {\n const depType = (dep.type ?? null) as DepType;\n const nodeId = `dep-${i}`;\n const isRuntime = !depType || depType === 'runtime';\n const isDev = depType === 'dev';\n\n nodes.push({\n id: nodeId,\n position: { x: 40, y: 20 + i * 80 },\n data: { label: makeDepLabel(dep) },\n style: {\n ...depNodeStyle(depType),\n borderRadius: 8,\n padding: '8px 12px',\n width: 220,\n fontSize: 12,\n },\n });\n\n edges.push({\n id: `edge-${i}`,\n source: nodeId,\n target: 'root',\n animated: isRuntime,\n markerEnd: { type: MarkerType.ArrowClosed },\n style: {\n stroke: 'var(--color-border-strong)',\n strokeWidth: 1.5,\n ...(isDev ? { strokeDasharray: '4 2' } : {}),\n },\n });\n });\n\n return { nodes, edges };\n}\n\n// ---------------------------------------------------------------------------\n// Inner React Flow component (needs its own hook calls — can't be conditional)\n// ---------------------------------------------------------------------------\n\ninterface DependencyFlowProps {\n componentName: string;\n componentType: string;\n deps: Array<{ name: string; version?: string | null; type?: string | null }>;\n canvasHeight: number;\n}\n\nfunction DependencyFlow({ componentName, componentType, deps, canvasHeight }: DependencyFlowProps) {\n const { nodes: initialNodes, edges: initialEdges } = useMemo(\n () => buildGraphElements(componentName, componentType, deps),\n [componentName, componentType, deps]\n );\n\n const [nodes, , onNodesChange] = useNodesState(initialNodes);\n const [edges, , onEdgesChange] = useEdgesState(initialEdges);\n\n return (\n <div\n className=\"w-full rounded-lg border border-border-default overflow-hidden bg-bg-secondary\"\n style={{ height: canvasHeight }}\n >\n <ReactFlow\n nodes={nodes}\n edges={edges}\n onNodesChange={onNodesChange}\n onEdgesChange={onEdgesChange}\n nodesDraggable={false}\n fitView\n fitViewOptions={{ padding: 0.15 }}\n minZoom={0.2}\n maxZoom={2}\n panOnDrag\n proOptions={{ hideAttribution: true }}\n >\n <Background\n variant={BackgroundVariant.Dots}\n gap={20}\n size={1}\n className=\"!fill-text-tertiary/10\"\n />\n <Controls\n showInteractive={false}\n style={{\n background: 'var(--color-bg-primary)',\n border: '1px solid var(--color-border-default)',\n borderRadius: 8,\n boxShadow: '0 1px 4px var(--color-border-default)',\n }}\n className=\"[&_button]:!bg-[var(--color-bg-primary)] [&_button]:!border-[var(--color-border-default)] [&_button]:!fill-[var(--color-text-secondary)] [&_button:hover]:!bg-[var(--color-bg-sunken)]\"\n />\n </ReactFlow>\n </div>\n );\n}\n\n// ---------------------------------------------------------------------------\n// Main export\n// ---------------------------------------------------------------------------\n\nexport function DependencyGraphTab({\n componentName,\n componentType,\n dependencies,\n}: DependencyGraphTabProps) {\n const deps = dependencies ?? [];\n const visibleCount = Math.min(deps.length, MAX_GRAPH_DEPS);\n const hiddenCount = deps.length - visibleCount;\n const canvasHeight = Math.max(400, Math.min(deps.length * 80 + 100, 600));\n\n if (deps.length === 0) {\n return (\n <div className=\"flex flex-col items-center justify-center py-20 text-center gap-4\">\n <PackageOpen className=\"size-10 text-text-muted\" />\n <p className=\"text-sm text-text-secondary max-w-xs\">\n No dependencies recorded. Add them via Settings.\n </p>\n </div>\n );\n }\n\n return (\n <div className=\"space-y-6\">\n {/* Graph canvas */}\n <div>\n {hiddenCount > 0 && (\n <p className=\"text-xs text-text-muted mb-2 text-right\">\n Showing {visibleCount} of {deps.length} dependencies (+{hiddenCount} more in table\n below)\n </p>\n )}\n <DependencyFlow\n componentName={componentName}\n componentType={componentType}\n deps={deps}\n canvasHeight={canvasHeight}\n />\n </div>\n\n {/* All-dependencies table */}\n <div>\n <h3 className=\"text-sm font-semibold text-text-primary mb-3\">\n All Dependencies ({deps.length})\n </h3>\n <div className=\"overflow-x-auto rounded-lg border border-border-default\">\n <table className=\"w-full text-sm\">\n <thead>\n <tr className=\"bg-bg-sunken border-b border-border-default\">\n <th className=\"px-4 py-2.5 text-left font-medium text-text-secondary\">Name</th>\n <th className=\"px-4 py-2.5 text-left font-medium text-text-secondary\">Version</th>\n <th className=\"px-4 py-2.5 text-left font-medium text-text-secondary\">Type</th>\n </tr>\n </thead>\n <tbody>\n {deps.map((dep, i) => (\n <tr\n key={`${dep.name}-${i}`}\n className=\"border-b border-border-default last:border-0 hover:bg-bg-sunken/50 transition-colors\"\n >\n <td className=\"px-4 py-2.5 font-mono text-xs text-text-primary\">{dep.name}</td>\n <td className=\"px-4 py-2.5 text-text-secondary\">{dep.version ?? '—'}</td>\n <td className=\"px-4 py-2.5\">\n {dep.type ? (\n <span className=\"inline-flex items-center px-2 py-0.5 text-xs font-medium rounded bg-bg-sunken text-text-secondary\">\n {dep.type}\n </span>\n ) : (\n <span className=\"text-text-muted\">runtime</span>\n )}\n </td>\n </tr>\n ))}\n </tbody>\n </table>\n </div>\n </div>\n </div>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"DependencyGraphTab.js","names":[],"sources":["../../../src/components/idp-components/DependencyGraphTab.tsx"],"sourcesContent":["/**\n * DependencyGraphTab — React Flow visualization of a component's dependency graph.\n *\n * Root component node sits on the RIGHT; dependency nodes stack on the LEFT.\n * Edges flow LEFT → RIGHT with type-specific styling.\n * A compact table below the canvas lists all dependencies.\n */\n\nimport { useMemo } from 'react';\nimport { PackageOpen } from 'lucide-react';\nimport '@xyflow/react/dist/style.css';\nimport {\n ReactFlow,\n Background,\n Controls,\n useNodesState,\n useEdgesState,\n type Node,\n type Edge,\n MarkerType,\n BackgroundVariant,\n} from '@xyflow/react';\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\nexport interface DependencyGraphTabProps {\n componentName: string;\n componentType: string;\n dependencies: Array<{ name: string; version?: string | null; type?: string | null }> | null;\n}\n\ntype DepType = 'runtime' | 'dev' | 'peer' | 'optional' | null;\n\nconst MAX_GRAPH_DEPS = 40;\n\n// ---------------------------------------------------------------------------\n// Inline style helpers (React Flow nodes render in a portal — no Tailwind)\n// ---------------------------------------------------------------------------\n\nfunction depNodeStyle(type: DepType): React.CSSProperties {\n switch (type) {\n case 'peer':\n return {\n backgroundColor: 'var(--color-status-info-bg)',\n border: '1px solid var(--color-status-info-border)',\n color: 'var(--color-text-primary)',\n };\n case 'dev':\n return {\n backgroundColor: 'var(--color-bg-sunken)',\n border: '1px dashed var(--color-border-default)',\n color: 'var(--color-text-secondary)',\n };\n case 'optional':\n return {\n backgroundColor: 'var(--color-bg-sunken)',\n border: '1px solid var(--color-border-default)',\n color: 'var(--color-text-primary)',\n opacity: 0.7,\n };\n default:\n // runtime or null\n return {\n backgroundColor: 'var(--color-bg-sunken)',\n border: '1px solid var(--color-border-default)',\n color: 'var(--color-text-primary)',\n };\n }\n}\n\nfunction depTypeBadgeStyle(type: DepType): React.CSSProperties {\n const base: React.CSSProperties = {\n display: 'inline-block',\n fontSize: 10,\n fontWeight: 600,\n borderRadius: 4,\n padding: '1px 5px',\n marginLeft: 4,\n textTransform: 'uppercase',\n letterSpacing: '0.04em',\n };\n switch (type) {\n case 'peer':\n return {\n ...base,\n backgroundColor: 'var(--color-status-info-border)',\n color: 'var(--color-text-inverse)',\n };\n case 'dev':\n return {\n ...base,\n backgroundColor: 'var(--color-border-strong)',\n color: 'var(--color-text-inverse)',\n };\n case 'optional':\n return {\n ...base,\n backgroundColor: 'var(--color-border-default)',\n color: 'var(--color-text-secondary)',\n };\n default:\n return {\n ...base,\n backgroundColor: 'var(--color-action-primary-bg)',\n color: 'var(--color-action-primary-text)',\n };\n }\n}\n\n// ---------------------------------------------------------------------------\n// Label renderers (ReactNode — React Flow default node accepts ReactNode label)\n// ---------------------------------------------------------------------------\n\nfunction makeDepLabel(dep: {\n name: string;\n version?: string | null;\n type?: string | null;\n}): React.ReactNode {\n const depType = (dep.type ?? null) as DepType;\n return (\n <div style={{ lineHeight: 1.4 }}>\n <div\n style={{\n fontWeight: 600,\n maxWidth: 180,\n overflow: 'hidden',\n textOverflow: 'ellipsis',\n whiteSpace: 'nowrap',\n }}\n >\n {dep.name}\n {depType && <span style={depTypeBadgeStyle(depType)}>{depType}</span>}\n </div>\n {dep.version && (\n <div style={{ fontSize: 10, color: 'var(--color-text-muted)', marginTop: 2 }}>\n {dep.version}\n </div>\n )}\n </div>\n );\n}\n\nfunction makeRootLabel(componentName: string, componentType: string): React.ReactNode {\n return (\n <div style={{ lineHeight: 1.4, textAlign: 'center' }}>\n <div style={{ fontWeight: 700, fontSize: 13 }}>{componentName}</div>\n <div\n style={{\n fontSize: 10,\n marginTop: 3,\n backgroundColor: 'var(--color-accent-soft)',\n borderRadius: 4,\n padding: '1px 6px',\n display: 'inline-block',\n letterSpacing: '0.05em',\n textTransform: 'uppercase',\n }}\n >\n {componentType}\n </div>\n </div>\n );\n}\n\n// ---------------------------------------------------------------------------\n// Graph element builder\n// ---------------------------------------------------------------------------\n\nfunction buildGraphElements(\n componentName: string,\n componentType: string,\n deps: Array<{ name: string; version?: string | null; type?: string | null }>\n): { nodes: Node[]; edges: Edge[] } {\n const visible = deps.slice(0, MAX_GRAPH_DEPS);\n const totalHeight = Math.max(visible.length * 80, 80);\n const rootY = totalHeight / 2 - 24;\n\n const nodes: Node[] = [];\n const edges: Edge[] = [];\n\n // Root node\n nodes.push({\n id: 'root',\n position: { x: 520, y: rootY },\n data: { label: makeRootLabel(componentName, componentType) },\n style: {\n backgroundColor: 'var(--color-action-primary-bg)',\n color: 'var(--color-action-primary-text)',\n borderRadius: 10,\n padding: '10px 16px',\n width: 160,\n fontWeight: 700,\n fontSize: 13,\n boxShadow: 'var(--shadow-elevation-2)',\n border: 'none',\n textAlign: 'center',\n },\n });\n\n // Dependency nodes and edges\n visible.forEach((dep, i) => {\n const depType = (dep.type ?? null) as DepType;\n const nodeId = `dep-${i}`;\n const isRuntime = !depType || depType === 'runtime';\n const isDev = depType === 'dev';\n\n nodes.push({\n id: nodeId,\n position: { x: 40, y: 20 + i * 80 },\n data: { label: makeDepLabel(dep) },\n style: {\n ...depNodeStyle(depType),\n borderRadius: 8,\n padding: '8px 12px',\n width: 220,\n fontSize: 12,\n },\n });\n\n edges.push({\n id: `edge-${i}`,\n source: nodeId,\n target: 'root',\n animated: isRuntime,\n markerEnd: { type: MarkerType.ArrowClosed },\n style: {\n stroke: 'var(--color-border-strong)',\n strokeWidth: 1.5,\n ...(isDev ? { strokeDasharray: '4 2' } : {}),\n },\n });\n });\n\n return { nodes, edges };\n}\n\n// ---------------------------------------------------------------------------\n// Inner React Flow component (needs its own hook calls — can't be conditional)\n// ---------------------------------------------------------------------------\n\ninterface DependencyFlowProps {\n componentName: string;\n componentType: string;\n deps: Array<{ name: string; version?: string | null; type?: string | null }>;\n canvasHeight: number;\n}\n\nfunction DependencyFlow({ componentName, componentType, deps, canvasHeight }: DependencyFlowProps) {\n const { nodes: initialNodes, edges: initialEdges } = useMemo(\n () => buildGraphElements(componentName, componentType, deps),\n [componentName, componentType, deps]\n );\n\n const [nodes, , onNodesChange] = useNodesState(initialNodes);\n const [edges, , onEdgesChange] = useEdgesState(initialEdges);\n\n return (\n <div\n className=\"w-full rounded-lg border border-border-default overflow-hidden bg-bg-surface\"\n style={{ height: canvasHeight }}\n >\n <ReactFlow\n nodes={nodes}\n edges={edges}\n onNodesChange={onNodesChange}\n onEdgesChange={onEdgesChange}\n nodesDraggable={false}\n fitView\n fitViewOptions={{ padding: 0.15 }}\n minZoom={0.2}\n maxZoom={2}\n panOnDrag\n proOptions={{ hideAttribution: true }}\n >\n <Background\n variant={BackgroundVariant.Dots}\n gap={20}\n size={1}\n className=\"!fill-text-tertiary/10\"\n />\n <Controls\n showInteractive={false}\n style={{\n background: 'var(--color-bg-primary)',\n border: '1px solid var(--color-border-default)',\n borderRadius: 8,\n boxShadow: '0 1px 4px var(--color-border-default)',\n }}\n className=\"[&_button]:!bg-[var(--color-bg-primary)] [&_button]:!border-[var(--color-border-default)] [&_button]:!fill-[var(--color-text-secondary)] [&_button:hover]:!bg-[var(--color-bg-sunken)]\"\n />\n </ReactFlow>\n </div>\n );\n}\n\n// ---------------------------------------------------------------------------\n// Main export\n// ---------------------------------------------------------------------------\n\nexport function DependencyGraphTab({\n componentName,\n componentType,\n dependencies,\n}: DependencyGraphTabProps) {\n const deps = dependencies ?? [];\n const visibleCount = Math.min(deps.length, MAX_GRAPH_DEPS);\n const hiddenCount = deps.length - visibleCount;\n const canvasHeight = Math.max(400, Math.min(deps.length * 80 + 100, 600));\n\n if (deps.length === 0) {\n return (\n <div className=\"flex flex-col items-center justify-center py-20 text-center gap-4\">\n <PackageOpen className=\"size-10 text-text-muted\" />\n <p className=\"text-sm text-text-secondary max-w-xs\">\n No dependencies recorded. Add them via Settings.\n </p>\n </div>\n );\n }\n\n return (\n <div className=\"space-y-6\">\n {/* Graph canvas */}\n <div>\n {hiddenCount > 0 && (\n <p className=\"text-xs text-text-muted mb-2 text-right\">\n Showing {visibleCount} of {deps.length} dependencies (+{hiddenCount} more in table\n below)\n </p>\n )}\n <DependencyFlow\n componentName={componentName}\n componentType={componentType}\n deps={deps}\n canvasHeight={canvasHeight}\n />\n </div>\n\n {/* All-dependencies table */}\n <div>\n <h3 className=\"text-sm font-semibold text-text-primary mb-3\">\n All Dependencies ({deps.length})\n </h3>\n <div className=\"overflow-x-auto rounded-lg border border-border-default\">\n <table className=\"w-full text-sm\">\n <thead>\n <tr className=\"bg-bg-sunken border-b border-border-default\">\n <th className=\"px-4 py-2.5 text-left font-medium text-text-secondary\">Name</th>\n <th className=\"px-4 py-2.5 text-left font-medium text-text-secondary\">Version</th>\n <th className=\"px-4 py-2.5 text-left font-medium text-text-secondary\">Type</th>\n </tr>\n </thead>\n <tbody>\n {deps.map((dep, i) => (\n <tr\n key={`${dep.name}-${i}`}\n className=\"border-b border-border-default last:border-0 hover:bg-bg-sunken/50 transition-colors\"\n >\n <td className=\"px-4 py-2.5 font-mono text-xs text-text-primary\">{dep.name}</td>\n <td className=\"px-4 py-2.5 text-text-secondary\">{dep.version ?? '—'}</td>\n <td className=\"px-4 py-2.5\">\n {dep.type ? (\n <span className=\"inline-flex items-center px-2 py-0.5 text-xs font-medium rounded bg-bg-sunken text-text-secondary\">\n {dep.type}\n </span>\n ) : (\n <span className=\"text-text-muted\">runtime</span>\n )}\n </td>\n </tr>\n ))}\n </tbody>\n </table>\n </div>\n </div>\n </div>\n );\n}\n"],"mappings":"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"}
|
|
@@ -13,10 +13,10 @@ var h = {
|
|
|
13
13
|
warn: "text-status-warning-text",
|
|
14
14
|
error: "text-status-error-text"
|
|
15
15
|
}, g = {
|
|
16
|
-
debug: "bg-bg-
|
|
17
|
-
info: "bg-status-info-bg
|
|
18
|
-
warn: "bg-status-warning-bg
|
|
19
|
-
error: "bg-status-error-bg
|
|
16
|
+
debug: "bg-bg-sunken",
|
|
17
|
+
info: "bg-status-info-bg-subtle",
|
|
18
|
+
warn: "bg-status-warning-bg-subtle",
|
|
19
|
+
error: "bg-status-error-bg-subtle"
|
|
20
20
|
}, _ = ({ agentId: _, profile: v, level: y = "info", source: b, maxEntries: x = 1e3, className: S = "", compact: C = !1 }) => {
|
|
21
21
|
let { t: w } = a(), [T, E] = i([]), [D, O] = i(!1), [k, A] = i(y), [j, M] = i(""), [N, P] = i(null), F = r(null), I = r(!0), { data: L, error: R, loading: z } = e({
|
|
22
22
|
variables: {
|
|
@@ -94,10 +94,10 @@ var h = {
|
|
|
94
94
|
}
|
|
95
95
|
};
|
|
96
96
|
return /* @__PURE__ */ p("div", {
|
|
97
|
-
className: `flex flex-col overflow-hidden rounded-lg border border-border-
|
|
97
|
+
className: `flex flex-col overflow-hidden rounded-lg border border-border-default bg-bg-elevated ${S}`,
|
|
98
98
|
children: [
|
|
99
99
|
/* @__PURE__ */ p("div", {
|
|
100
|
-
className: "flex flex-wrap items-center gap-2 border-b border-border-
|
|
100
|
+
className: "flex flex-wrap items-center gap-2 border-b border-border-default bg-bg-sunken px-3 py-2",
|
|
101
101
|
children: [
|
|
102
102
|
/* @__PURE__ */ p("div", {
|
|
103
103
|
className: "mr-2 flex items-center gap-1.5",
|
|
@@ -109,7 +109,7 @@ var h = {
|
|
|
109
109
|
/* @__PURE__ */ p("select", {
|
|
110
110
|
value: k,
|
|
111
111
|
onChange: (e) => A(e.target.value),
|
|
112
|
-
className: "rounded border border-border-
|
|
112
|
+
className: "rounded border border-border-subtle bg-bg-sunken px-2 py-1 text-xs text-text-primary",
|
|
113
113
|
"aria-label": B("logs.levelFilter", "Log level filter"),
|
|
114
114
|
children: [
|
|
115
115
|
/* @__PURE__ */ f("option", {
|
|
@@ -141,7 +141,7 @@ var h = {
|
|
|
141
141
|
placeholder: B("logs.filterLogs", "Filter logs..."),
|
|
142
142
|
value: j,
|
|
143
143
|
onChange: (e) => M(e.target.value),
|
|
144
|
-
className: "w-full rounded border border-border-
|
|
144
|
+
className: "w-full rounded border border-border-subtle bg-bg-sunken py-1 pl-6 pr-2 text-xs text-text-primary placeholder:text-text-tertiary"
|
|
145
145
|
})]
|
|
146
146
|
}),
|
|
147
147
|
/* @__PURE__ */ p("div", {
|
|
@@ -159,7 +159,7 @@ var h = {
|
|
|
159
159
|
type: "button",
|
|
160
160
|
onClick: () => void W(),
|
|
161
161
|
disabled: U,
|
|
162
|
-
className: "rounded p-1 text-text-tertiary transition-colors hover:bg-bg-
|
|
162
|
+
className: "rounded p-1 text-text-tertiary transition-colors hover:bg-bg-sunken disabled:opacity-50",
|
|
163
163
|
title: B("common.refresh", "Refresh"),
|
|
164
164
|
children: /* @__PURE__ */ f(l, {
|
|
165
165
|
size: 14,
|
|
@@ -169,21 +169,21 @@ var h = {
|
|
|
169
169
|
/* @__PURE__ */ f("button", {
|
|
170
170
|
type: "button",
|
|
171
171
|
onClick: () => O((e) => !e),
|
|
172
|
-
className: `rounded p-1 transition-colors hover:bg-bg-
|
|
172
|
+
className: `rounded p-1 transition-colors hover:bg-bg-sunken ${D ? "text-status-warning-text" : "text-text-tertiary"}`,
|
|
173
173
|
title: D ? B("logs.resume", "Resume") : B("logs.pause", "Pause"),
|
|
174
174
|
children: f(D ? c : s, { size: 14 })
|
|
175
175
|
}),
|
|
176
176
|
/* @__PURE__ */ f("button", {
|
|
177
177
|
type: "button",
|
|
178
178
|
onClick: K,
|
|
179
|
-
className: "rounded p-1 text-text-tertiary transition-colors hover:bg-bg-
|
|
179
|
+
className: "rounded p-1 text-text-tertiary transition-colors hover:bg-bg-sunken",
|
|
180
180
|
title: B("logs.clear", "Clear"),
|
|
181
181
|
children: /* @__PURE__ */ f(d, { size: 14 })
|
|
182
182
|
}),
|
|
183
183
|
/* @__PURE__ */ f("button", {
|
|
184
184
|
type: "button",
|
|
185
185
|
onClick: q,
|
|
186
|
-
className: "rounded p-1 text-text-tertiary transition-colors hover:bg-bg-
|
|
186
|
+
className: "rounded p-1 text-text-tertiary transition-colors hover:bg-bg-sunken",
|
|
187
187
|
title: B("logs.export", "Export"),
|
|
188
188
|
children: /* @__PURE__ */ f(o, { size: 14 })
|
|
189
189
|
})
|
|
@@ -193,7 +193,7 @@ var h = {
|
|
|
193
193
|
}),
|
|
194
194
|
N ? /* @__PURE__ */ f("div", {
|
|
195
195
|
role: "alert",
|
|
196
|
-
className: "border-b border-border-
|
|
196
|
+
className: "border-b border-border-default bg-status-error-bg-subtle px-3 py-2 text-xs text-status-error-text",
|
|
197
197
|
children: N
|
|
198
198
|
}) : null,
|
|
199
199
|
/* @__PURE__ */ f("div", {
|
|
@@ -205,7 +205,7 @@ var h = {
|
|
|
205
205
|
className: "flex h-full min-h-[200px] items-center justify-center text-text-tertiary",
|
|
206
206
|
children: _ ? N ? `${B("logs.subscriptionError", "Log stream error")}: ${N}` : z ? B("logs.connecting", "Connecting to log stream…") : B("logs.waitingForEntries", "Waiting for log entries…") : B("logs.connectToAgent", "Connect to an agent to view logs")
|
|
207
207
|
}) : G.map((e, t) => /* @__PURE__ */ p("div", {
|
|
208
|
-
className: `flex gap-2 border-b border-border-
|
|
208
|
+
className: `flex gap-2 border-b border-border-default/30 px-3 py-0.5 hover:bg-bg-sunken ${g[e.level] ?? ""}`,
|
|
209
209
|
children: [
|
|
210
210
|
/* @__PURE__ */ f("span", {
|
|
211
211
|
className: "w-[85px] shrink-0 text-text-tertiary",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"LogViewer.js","names":[],"sources":["../../../src/components/logs/LogViewer.tsx"],"sourcesContent":["import { useCallback, useEffect, useRef, useState, type FC } from 'react';\nimport { Download, Pause, Play, RefreshCw, Search, Trash2 } from 'lucide-react';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { useVibecontrolsAgentLogStreamSubscription } from '@/generated/wspace-operations';\n\ninterface LogEntry {\n timestamp: string;\n level: 'debug' | 'info' | 'warn' | 'error';\n source: string;\n message: string;\n metadata?: Record<string, unknown>;\n}\n\ninterface LogViewerProps {\n agentId: string;\n /** Canonical CLI profile name on the remote agent. */\n profile: string;\n level?: 'debug' | 'info' | 'warn' | 'error';\n source?: string;\n maxEntries?: number;\n className?: string;\n compact?: boolean;\n}\n\nfunction getEntryKey(entry: LogEntry): string {\n return `${entry.timestamp}:${entry.level}:${entry.source}:${entry.message}`;\n}\n\nconst LEVEL_COLORS: Record<string, string> = {\n debug: 'text-text-tertiary',\n info: 'text-status-info-text',\n warn: 'text-status-warning-text',\n error: 'text-status-error-text',\n};\n\nconst LEVEL_BG: Record<string, string> = {\n debug: 'bg-bg-tertiary/30',\n info: 'bg-status-info-bg/10',\n warn: 'bg-status-warning-bg/10',\n error: 'bg-status-error-bg/10',\n};\n\nexport const LogViewer: FC<LogViewerProps> = ({\n agentId,\n profile,\n level: initialLevel = 'info',\n source: initialSource,\n maxEntries = 1000,\n className = '',\n compact = false,\n}) => {\n const { t } = useI18n();\n const [entries, setEntries] = useState<LogEntry[]>([]);\n const [paused, setPaused] = useState(false);\n const [level, setLevel] = useState(initialLevel);\n const [searchQuery, setSearchQuery] = useState('');\n const [errorMessage, setErrorMessage] = useState<string | null>(null);\n const containerRef = useRef<HTMLDivElement>(null);\n const autoScrollRef = useRef(true);\n\n // GraphQL subscription — the svc polls the agent server-side every\n // ~2s and pushes only new entries here. Replaces the previous client\n // setInterval + fetch, which was firing infinitely because\n // `tr` recreated on every render mutated fetchLogs' identity, which\n // re-ran the useEffect, which re-armed the interval, which called\n // fetchLogs immediately. The subscription has no client polling at\n // all — Apollo opens one WS, the svc owns the cadence.\n const {\n data: streamData,\n error: subscriptionError,\n loading: streamLoading,\n } = useVibecontrolsAgentLogStreamSubscription({\n variables: {\n agentId,\n profile,\n level,\n source: initialSource,\n },\n skip: !agentId || paused,\n shouldResubscribe: true,\n });\n\n const tr = (key: string, fallback: string) => {\n const value = t(key);\n return value === key ? fallback : value;\n };\n\n const handleScroll = useCallback(() => {\n if (!containerRef.current) {\n return;\n }\n\n const element = containerRef.current;\n autoScrollRef.current = element.scrollHeight - element.scrollTop - element.clientHeight < 50;\n }, []);\n\n const scrollToBottom = useCallback(() => {\n if (containerRef.current) {\n containerRef.current.scrollTop = containerRef.current.scrollHeight;\n }\n }, []);\n\n // Drain incoming subscription batches into the entries list. Each\n // payload is the NEW entries since the last server-side tick; merge\n // by key so re-deliveries (e.g. on reconnect backfill) don't dupe.\n useEffect(() => {\n const batch = streamData?.vibecontrolsAgentLogStream;\n if (!batch || batch.length === 0) return;\n const incoming: LogEntry[] = batch.map(\n (row: {\n timestamp: string;\n level: string;\n source?: string | null;\n message: string;\n metadataJson?: string | null;\n }) => {\n let metadata: Record<string, unknown> | undefined;\n if (row.metadataJson) {\n try {\n metadata = JSON.parse(row.metadataJson) as Record<string, unknown>;\n } catch {\n metadata = undefined;\n }\n }\n return {\n timestamp: row.timestamp,\n level: (row.level as LogEntry['level']) ?? 'info',\n source: row.source ?? '',\n message: row.message,\n metadata,\n };\n }\n );\n setEntries((current) => {\n const merged = new Map<string, LogEntry>();\n for (const entry of current) merged.set(getEntryKey(entry), entry);\n for (const entry of incoming) merged.set(getEntryKey(entry), entry);\n return Array.from(merged.values())\n .sort((a, b) => new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime())\n .slice(-maxEntries);\n });\n setErrorMessage(null);\n if (autoScrollRef.current) {\n requestAnimationFrame(() => {\n if (containerRef.current) {\n containerRef.current.scrollTop = containerRef.current.scrollHeight;\n }\n });\n }\n }, [streamData, maxEntries]);\n\n useEffect(() => {\n if (subscriptionError) {\n setErrorMessage(subscriptionError.message);\n }\n }, [subscriptionError]);\n\n const connected = !subscriptionError && !streamLoading;\n const isRefreshing = streamLoading;\n\n // Reset the entries list when the agent / filter set actually changes.\n useEffect(() => {\n setEntries([]);\n setErrorMessage(null);\n }, [agentId, profile, level, initialSource]);\n\n // Manual refresh: no-op since the subscription pushes continuously.\n // Kept as a callback so the existing \"Refresh\" button doesn't have to\n // be conditionally hidden. Wraps in setEntries([]) so the operator's\n // intent of \"give me a fresh window\" is honoured — the next push\n // backfills.\n const fetchLogs = useCallback(() => {\n setEntries([]);\n }, []);\n\n const filteredEntries = searchQuery\n ? entries.filter(\n (entry) =>\n entry.message.toLowerCase().includes(searchQuery.toLowerCase()) ||\n entry.source.toLowerCase().includes(searchQuery.toLowerCase())\n )\n : entries;\n\n const handleClear = () => setEntries([]);\n\n const handleExport = () => {\n const blob = new Blob([filteredEntries.map((entry) => JSON.stringify(entry)).join('\\n')], {\n type: 'application/x-ndjson',\n });\n const url = URL.createObjectURL(blob);\n const anchor = document.createElement('a');\n anchor.href = url;\n anchor.download = `agent-logs-${new Date().toISOString().slice(0, 19).replace(/:/g, '-')}.jsonl`;\n anchor.click();\n URL.revokeObjectURL(url);\n };\n\n const formatTime = (timestamp: string) => {\n try {\n const date = new Date(timestamp);\n return (\n date.toLocaleTimeString('en-US', {\n hour12: false,\n hour: '2-digit',\n minute: '2-digit',\n second: '2-digit',\n }) +\n '.' +\n String(date.getMilliseconds()).padStart(3, '0')\n );\n } catch {\n return timestamp;\n }\n };\n\n return (\n <div\n className={`flex flex-col overflow-hidden rounded-lg border border-border-primary bg-bg-elevated ${className}`}\n >\n <div className=\"flex flex-wrap items-center gap-2 border-b border-border-primary bg-bg-secondary/50 px-3 py-2\">\n <div className=\"mr-2 flex items-center gap-1.5\">\n <div\n className={`size-2 rounded-full ${connected ? 'animate-pulse bg-status-success-bg' : 'bg-status-error-bg'}`}\n />\n <span className=\"text-xs text-text-tertiary\">\n {connected ? tr('logs.live', 'Live') : tr('logs.disconnected', 'Disconnected')}\n </span>\n </div>\n\n <select\n value={level}\n onChange={(event) => setLevel(event.target.value as LogEntry['level'])}\n className=\"rounded border border-border-secondary bg-bg-tertiary px-2 py-1 text-xs text-text-primary\"\n aria-label={tr('logs.levelFilter', 'Log level filter')}\n >\n <option value=\"debug\">DEBUG+</option>\n <option value=\"info\">INFO+</option>\n <option value=\"warn\">WARN+</option>\n <option value=\"error\">ERROR</option>\n </select>\n\n <div className=\"relative min-w-[120px] max-w-xs flex-1\">\n <Search\n size={12}\n className=\"absolute left-2 top-1/2 -translate-y-1/2 text-text-tertiary\"\n />\n <input\n aria-label=\"control\"\n type=\"text\"\n placeholder={tr('logs.filterLogs', 'Filter logs...')}\n value={searchQuery}\n onChange={(event) => setSearchQuery(event.target.value)}\n className=\"w-full rounded border border-border-secondary bg-bg-tertiary py-1 pl-6 pr-2 text-xs text-text-primary placeholder:text-text-tertiary\"\n />\n </div>\n\n <div className=\"ml-auto flex items-center gap-1\">\n <span className=\"text-xs text-text-tertiary\">\n {filteredEntries.length} {tr('logs.entries', 'entries')}\n </span>\n <button\n type=\"button\"\n onClick={() => void fetchLogs()}\n disabled={isRefreshing}\n className=\"rounded p-1 text-text-tertiary transition-colors hover:bg-bg-tertiary disabled:opacity-50\"\n title={tr('common.refresh', 'Refresh')}\n >\n <RefreshCw size={14} className={isRefreshing ? 'animate-spin' : ''} />\n </button>\n <button\n type=\"button\"\n onClick={() => setPaused((current) => !current)}\n className={`rounded p-1 transition-colors hover:bg-bg-tertiary ${paused ? 'text-status-warning-text' : 'text-text-tertiary'}`}\n title={paused ? tr('logs.resume', 'Resume') : tr('logs.pause', 'Pause')}\n >\n {paused ? <Play size={14} /> : <Pause size={14} />}\n </button>\n <button\n type=\"button\"\n onClick={handleClear}\n className=\"rounded p-1 text-text-tertiary transition-colors hover:bg-bg-tertiary\"\n title={tr('logs.clear', 'Clear')}\n >\n <Trash2 size={14} />\n </button>\n <button\n type=\"button\"\n onClick={handleExport}\n className=\"rounded p-1 text-text-tertiary transition-colors hover:bg-bg-tertiary\"\n title={tr('logs.export', 'Export')}\n >\n <Download size={14} />\n </button>\n </div>\n </div>\n\n {errorMessage ? (\n <div\n role=\"alert\"\n className=\"border-b border-border-primary bg-status-error-bg/10 px-3 py-2 text-xs text-status-error-text\"\n >\n {errorMessage}\n </div>\n ) : null}\n\n <div\n ref={containerRef}\n onScroll={handleScroll}\n className={`overflow-y-auto font-mono text-xs leading-relaxed ${compact ? 'max-h-[300px]' : 'min-h-[400px] flex-1'}`}\n style={{ maxHeight: compact ? '300px' : undefined }}\n >\n {filteredEntries.length === 0 ? (\n <div className=\"flex h-full min-h-[200px] items-center justify-center text-text-tertiary\">\n {!agentId\n ? tr('logs.connectToAgent', 'Connect to an agent to view logs')\n : errorMessage\n ? `${tr('logs.subscriptionError', 'Log stream error')}: ${errorMessage}`\n : streamLoading\n ? tr('logs.connecting', 'Connecting to log stream…')\n : tr('logs.waitingForEntries', 'Waiting for log entries…')}\n </div>\n ) : (\n filteredEntries.map((entry, index) => (\n <div\n key={`${entry.timestamp}-${entry.source}-${index}`}\n className={`flex gap-2 border-b border-border-primary/30 px-3 py-0.5 hover:bg-bg-tertiary/50 ${LEVEL_BG[entry.level] ?? ''}`}\n >\n <span className=\"w-[85px] shrink-0 text-text-tertiary\">\n {formatTime(entry.timestamp)}\n </span>\n <span\n className={`w-[42px] shrink-0 uppercase font-semibold ${LEVEL_COLORS[entry.level] ?? 'text-text-primary'}`}\n >\n {entry.level}\n </span>\n <span\n className=\"w-[100px] shrink-0 truncate text-text-secondary\"\n title={entry.source}\n >\n [{entry.source}]\n </span>\n <span className=\"flex-1 break-all text-text-primary\">\n {entry.message}\n {entry.metadata && Object.keys(entry.metadata).length > 0 ? (\n <span className=\"ml-2 text-text-tertiary\">{JSON.stringify(entry.metadata)}</span>\n ) : null}\n </span>\n </div>\n ))\n )}\n </div>\n </div>\n );\n};\n"],"mappings":";;;;;;AAwBA,SAAS,EAAY,GAAyB;AAC5C,QAAO,GAAG,EAAM,UAAU,GAAG,EAAM,MAAM,GAAG,EAAM,OAAO,GAAG,EAAM;;AAGpE,IAAM,IAAuC;CAC3C,OAAO;CACP,MAAM;CACN,MAAM;CACN,OAAO;CACR,EAEK,IAAmC;CACvC,OAAO;CACP,MAAM;CACN,MAAM;CACN,OAAO;CACR,EAEY,KAAiC,EAC5C,YACA,YACA,OAAO,IAAe,QACtB,QAAQ,GACR,gBAAa,KACb,eAAY,IACZ,aAAU,SACN;CACJ,IAAM,EAAE,SAAM,GAAS,EACjB,CAAC,GAAS,KAAc,EAAqB,EAAE,CAAC,EAChD,CAAC,GAAQ,KAAa,EAAS,GAAM,EACrC,CAAC,GAAO,KAAY,EAAS,EAAa,EAC1C,CAAC,GAAa,KAAkB,EAAS,GAAG,EAC5C,CAAC,GAAc,KAAmB,EAAwB,KAAK,EAC/D,IAAe,EAAuB,KAAK,EAC3C,IAAgB,EAAO,GAAK,EAS5B,EACJ,MAAM,GACN,OAAO,GACP,SAAS,MACP,EAA0C;EAC5C,WAAW;GACT;GACA;GACA;GACA,QAAQ;GACT;EACD,MAAM,CAAC,KAAW;EAClB,mBAAmB;EACpB,CAAC,EAEI,KAAM,GAAa,MAAqB;EAC5C,IAAM,IAAQ,EAAE,EAAI;AACpB,SAAO,MAAU,IAAM,IAAW;IAG9B,IAAe,QAAkB;AACrC,MAAI,CAAC,EAAa,QAChB;EAGF,IAAM,IAAU,EAAa;AAC7B,IAAc,UAAU,EAAQ,eAAe,EAAQ,YAAY,EAAQ,eAAe;IACzF,EAAE,CAAC;AAyDN,CAvDuB,QAAkB;AACvC,EAAI,EAAa,YACf,EAAa,QAAQ,YAAY,EAAa,QAAQ;IAEvD,EAAE,CAAC,EAKN,QAAgB;EACd,IAAM,IAAQ,GAAY;AAC1B,MAAI,CAAC,KAAS,EAAM,WAAW,EAAG;EAClC,IAAM,IAAuB,EAAM,KAChC,MAMK;GACJ,IAAI;AACJ,OAAI,EAAI,aACN,KAAI;AACF,QAAW,KAAK,MAAM,EAAI,aAAa;WACjC;AACN,QAAW,KAAA;;AAGf,UAAO;IACL,WAAW,EAAI;IACf,OAAQ,EAAI,SAA+B;IAC3C,QAAQ,EAAI,UAAU;IACtB,SAAS,EAAI;IACb;IACD;IAEJ;AAUD,EATA,GAAY,MAAY;GACtB,IAAM,oBAAS,IAAI,KAAuB;AAC1C,QAAK,IAAM,KAAS,EAAS,GAAO,IAAI,EAAY,EAAM,EAAE,EAAM;AAClE,QAAK,IAAM,KAAS,EAAU,GAAO,IAAI,EAAY,EAAM,EAAE,EAAM;AACnE,UAAO,MAAM,KAAK,EAAO,QAAQ,CAAC,CAC/B,MAAM,GAAG,MAAM,IAAI,KAAK,EAAE,UAAU,CAAC,SAAS,GAAG,IAAI,KAAK,EAAE,UAAU,CAAC,SAAS,CAAC,CACjF,MAAM,CAAC,EAAW;IACrB,EACF,EAAgB,KAAK,EACjB,EAAc,WAChB,4BAA4B;AAC1B,GAAI,EAAa,YACf,EAAa,QAAQ,YAAY,EAAa,QAAQ;IAExD;IAEH,CAAC,GAAY,EAAW,CAAC,EAE5B,QAAgB;AACd,EAAI,KACF,EAAgB,EAAkB,QAAQ;IAE3C,CAAC,EAAkB,CAAC;CAEvB,IAAM,IAAY,CAAC,KAAqB,CAAC,GACnC,IAAe;AAGrB,SAAgB;AAEd,EADA,EAAW,EAAE,CAAC,EACd,EAAgB,KAAK;IACpB;EAAC;EAAS;EAAS;EAAO;EAAc,CAAC;CAO5C,IAAM,IAAY,QAAkB;AAClC,IAAW,EAAE,CAAC;IACb,EAAE,CAAC,EAEA,IAAkB,IACpB,EAAQ,QACL,MACC,EAAM,QAAQ,aAAa,CAAC,SAAS,EAAY,aAAa,CAAC,IAC/D,EAAM,OAAO,aAAa,CAAC,SAAS,EAAY,aAAa,CAAC,CACjE,GACD,GAEE,UAAoB,EAAW,EAAE,CAAC,EAElC,UAAqB;EACzB,IAAM,IAAO,IAAI,KAAK,CAAC,EAAgB,KAAK,MAAU,KAAK,UAAU,EAAM,CAAC,CAAC,KAAK,KAAK,CAAC,EAAE,EACxF,MAAM,wBACP,CAAC,EACI,IAAM,IAAI,gBAAgB,EAAK,EAC/B,IAAS,SAAS,cAAc,IAAI;AAI1C,EAHA,EAAO,OAAO,GACd,EAAO,WAAW,+BAAc,IAAI,MAAM,EAAC,aAAa,CAAC,MAAM,GAAG,GAAG,CAAC,QAAQ,MAAM,IAAI,CAAC,SACzF,EAAO,OAAO,EACd,IAAI,gBAAgB,EAAI;IAGpB,KAAc,MAAsB;AACxC,MAAI;GACF,IAAM,IAAO,IAAI,KAAK,EAAU;AAChC,UACE,EAAK,mBAAmB,SAAS;IAC/B,QAAQ;IACR,MAAM;IACN,QAAQ;IACR,QAAQ;IACT,CAAC,GACF,MACA,OAAO,EAAK,iBAAiB,CAAC,CAAC,SAAS,GAAG,IAAI;UAE3C;AACN,UAAO;;;AAIX,QACE,kBAAC,OAAD;EACE,WAAW,wFAAwF;YADrG;GAGE,kBAAC,OAAD;IAAK,WAAU;cAAf;KACE,kBAAC,OAAD;MAAK,WAAU;gBAAf,CACE,kBAAC,OAAD,EACE,WAAW,uBAAuB,IAAY,uCAAuC,wBACrF,CAAA,EACF,kBAAC,QAAD;OAAM,WAAU;iBACb,IAAY,EAAG,aAAa,OAAO,GAAG,EAAG,qBAAqB,eAAe;OACzE,CAAA,CACH;;KAEN,kBAAC,UAAD;MACE,OAAO;MACP,WAAW,MAAU,EAAS,EAAM,OAAO,MAA2B;MACtE,WAAU;MACV,cAAY,EAAG,oBAAoB,mBAAmB;gBAJxD;OAME,kBAAC,UAAD;QAAQ,OAAM;kBAAQ;QAAe,CAAA;OACrC,kBAAC,UAAD;QAAQ,OAAM;kBAAO;QAAc,CAAA;OACnC,kBAAC,UAAD;QAAQ,OAAM;kBAAO;QAAc,CAAA;OACnC,kBAAC,UAAD;QAAQ,OAAM;kBAAQ;QAAc,CAAA;OAC7B;;KAET,kBAAC,OAAD;MAAK,WAAU;gBAAf,CACE,kBAAC,GAAD;OACE,MAAM;OACN,WAAU;OACV,CAAA,EACF,kBAAC,SAAD;OACE,cAAW;OACX,MAAK;OACL,aAAa,EAAG,mBAAmB,iBAAiB;OACpD,OAAO;OACP,WAAW,MAAU,EAAe,EAAM,OAAO,MAAM;OACvD,WAAU;OACV,CAAA,CACE;;KAEN,kBAAC,OAAD;MAAK,WAAU;gBAAf;OACE,kBAAC,QAAD;QAAM,WAAU;kBAAhB;SACG,EAAgB;SAAO;SAAE,EAAG,gBAAgB,UAAU;SAClD;;OACP,kBAAC,UAAD;QACE,MAAK;QACL,eAAe,KAAK,GAAW;QAC/B,UAAU;QACV,WAAU;QACV,OAAO,EAAG,kBAAkB,UAAU;kBAEtC,kBAAC,GAAD;SAAW,MAAM;SAAI,WAAW,IAAe,iBAAiB;SAAM,CAAA;QAC/D,CAAA;OACT,kBAAC,UAAD;QACE,MAAK;QACL,eAAe,GAAW,MAAY,CAAC,EAAQ;QAC/C,WAAW,sDAAsD,IAAS,6BAA6B;QACvG,OAAO,IAAS,EAAG,eAAe,SAAS,GAAG,EAAG,cAAc,QAAQ;kBAE7D,EAAT,IAAU,IAAqB,GAAtB,EAAM,MAAM,IAAM,CAAsB;QAC3C,CAAA;OACT,kBAAC,UAAD;QACE,MAAK;QACL,SAAS;QACT,WAAU;QACV,OAAO,EAAG,cAAc,QAAQ;kBAEhC,kBAAC,GAAD,EAAQ,MAAM,IAAM,CAAA;QACb,CAAA;OACT,kBAAC,UAAD;QACE,MAAK;QACL,SAAS;QACT,WAAU;QACV,OAAO,EAAG,eAAe,SAAS;kBAElC,kBAAC,GAAD,EAAU,MAAM,IAAM,CAAA;QACf,CAAA;OACL;;KACF;;GAEL,IACC,kBAAC,OAAD;IACE,MAAK;IACL,WAAU;cAET;IACG,CAAA,GACJ;GAEJ,kBAAC,OAAD;IACE,KAAK;IACL,UAAU;IACV,WAAW,qDAAqD,IAAU,kBAAkB;IAC5F,OAAO,EAAE,WAAW,IAAU,UAAU,KAAA,GAAW;cAElD,EAAgB,WAAW,IAC1B,kBAAC,OAAD;KAAK,WAAU;eACX,IAEE,IACE,GAAG,EAAG,0BAA0B,mBAAmB,CAAC,IAAI,MACxD,IACE,EAAG,mBAAmB,4BAA4B,GAClD,EAAG,0BAA0B,2BAA2B,GAL5D,EAAG,uBAAuB,mCAAmC;KAM7D,CAAA,GAEN,EAAgB,KAAK,GAAO,MAC1B,kBAAC,OAAD;KAEE,WAAW,oFAAoF,EAAS,EAAM,UAAU;eAF1H;MAIE,kBAAC,QAAD;OAAM,WAAU;iBACb,EAAW,EAAM,UAAU;OACvB,CAAA;MACP,kBAAC,QAAD;OACE,WAAW,6CAA6C,EAAa,EAAM,UAAU;iBAEpF,EAAM;OACF,CAAA;MACP,kBAAC,QAAD;OACE,WAAU;OACV,OAAO,EAAM;iBAFf;QAGC;QACG,EAAM;QAAO;QACV;;MACP,kBAAC,QAAD;OAAM,WAAU;iBAAhB,CACG,EAAM,SACN,EAAM,YAAY,OAAO,KAAK,EAAM,SAAS,CAAC,SAAS,IACtD,kBAAC,QAAD;QAAM,WAAU;kBAA2B,KAAK,UAAU,EAAM,SAAS;QAAQ,CAAA,GAC/E,KACC;;MACH;OAvBC,GAAG,EAAM,UAAU,GAAG,EAAM,OAAO,GAAG,IAuBvC,CACN;IAEA,CAAA;GACF"}
|
|
1
|
+
{"version":3,"file":"LogViewer.js","names":[],"sources":["../../../src/components/logs/LogViewer.tsx"],"sourcesContent":["import { useCallback, useEffect, useRef, useState, type FC } from 'react';\nimport { Download, Pause, Play, RefreshCw, Search, Trash2 } from 'lucide-react';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { useVibecontrolsAgentLogStreamSubscription } from '@/generated/wspace-operations';\n\ninterface LogEntry {\n timestamp: string;\n level: 'debug' | 'info' | 'warn' | 'error';\n source: string;\n message: string;\n metadata?: Record<string, unknown>;\n}\n\ninterface LogViewerProps {\n agentId: string;\n /** Canonical CLI profile name on the remote agent. */\n profile: string;\n level?: 'debug' | 'info' | 'warn' | 'error';\n source?: string;\n maxEntries?: number;\n className?: string;\n compact?: boolean;\n}\n\nfunction getEntryKey(entry: LogEntry): string {\n return `${entry.timestamp}:${entry.level}:${entry.source}:${entry.message}`;\n}\n\nconst LEVEL_COLORS: Record<string, string> = {\n debug: 'text-text-tertiary',\n info: 'text-status-info-text',\n warn: 'text-status-warning-text',\n error: 'text-status-error-text',\n};\n\nconst LEVEL_BG: Record<string, string> = {\n debug: 'bg-bg-sunken',\n info: 'bg-status-info-bg-subtle',\n warn: 'bg-status-warning-bg-subtle',\n error: 'bg-status-error-bg-subtle',\n};\n\nexport const LogViewer: FC<LogViewerProps> = ({\n agentId,\n profile,\n level: initialLevel = 'info',\n source: initialSource,\n maxEntries = 1000,\n className = '',\n compact = false,\n}) => {\n const { t } = useI18n();\n const [entries, setEntries] = useState<LogEntry[]>([]);\n const [paused, setPaused] = useState(false);\n const [level, setLevel] = useState(initialLevel);\n const [searchQuery, setSearchQuery] = useState('');\n const [errorMessage, setErrorMessage] = useState<string | null>(null);\n const containerRef = useRef<HTMLDivElement>(null);\n const autoScrollRef = useRef(true);\n\n // GraphQL subscription — the svc polls the agent server-side every\n // ~2s and pushes only new entries here. Replaces the previous client\n // setInterval + fetch, which was firing infinitely because\n // `tr` recreated on every render mutated fetchLogs' identity, which\n // re-ran the useEffect, which re-armed the interval, which called\n // fetchLogs immediately. The subscription has no client polling at\n // all — Apollo opens one WS, the svc owns the cadence.\n const {\n data: streamData,\n error: subscriptionError,\n loading: streamLoading,\n } = useVibecontrolsAgentLogStreamSubscription({\n variables: {\n agentId,\n profile,\n level,\n source: initialSource,\n },\n skip: !agentId || paused,\n shouldResubscribe: true,\n });\n\n const tr = (key: string, fallback: string) => {\n const value = t(key);\n return value === key ? fallback : value;\n };\n\n const handleScroll = useCallback(() => {\n if (!containerRef.current) {\n return;\n }\n\n const element = containerRef.current;\n autoScrollRef.current = element.scrollHeight - element.scrollTop - element.clientHeight < 50;\n }, []);\n\n const scrollToBottom = useCallback(() => {\n if (containerRef.current) {\n containerRef.current.scrollTop = containerRef.current.scrollHeight;\n }\n }, []);\n\n // Drain incoming subscription batches into the entries list. Each\n // payload is the NEW entries since the last server-side tick; merge\n // by key so re-deliveries (e.g. on reconnect backfill) don't dupe.\n useEffect(() => {\n const batch = streamData?.vibecontrolsAgentLogStream;\n if (!batch || batch.length === 0) return;\n const incoming: LogEntry[] = batch.map(\n (row: {\n timestamp: string;\n level: string;\n source?: string | null;\n message: string;\n metadataJson?: string | null;\n }) => {\n let metadata: Record<string, unknown> | undefined;\n if (row.metadataJson) {\n try {\n metadata = JSON.parse(row.metadataJson) as Record<string, unknown>;\n } catch {\n metadata = undefined;\n }\n }\n return {\n timestamp: row.timestamp,\n level: (row.level as LogEntry['level']) ?? 'info',\n source: row.source ?? '',\n message: row.message,\n metadata,\n };\n }\n );\n setEntries((current) => {\n const merged = new Map<string, LogEntry>();\n for (const entry of current) merged.set(getEntryKey(entry), entry);\n for (const entry of incoming) merged.set(getEntryKey(entry), entry);\n return Array.from(merged.values())\n .sort((a, b) => new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime())\n .slice(-maxEntries);\n });\n setErrorMessage(null);\n if (autoScrollRef.current) {\n requestAnimationFrame(() => {\n if (containerRef.current) {\n containerRef.current.scrollTop = containerRef.current.scrollHeight;\n }\n });\n }\n }, [streamData, maxEntries]);\n\n useEffect(() => {\n if (subscriptionError) {\n setErrorMessage(subscriptionError.message);\n }\n }, [subscriptionError]);\n\n const connected = !subscriptionError && !streamLoading;\n const isRefreshing = streamLoading;\n\n // Reset the entries list when the agent / filter set actually changes.\n useEffect(() => {\n setEntries([]);\n setErrorMessage(null);\n }, [agentId, profile, level, initialSource]);\n\n // Manual refresh: no-op since the subscription pushes continuously.\n // Kept as a callback so the existing \"Refresh\" button doesn't have to\n // be conditionally hidden. Wraps in setEntries([]) so the operator's\n // intent of \"give me a fresh window\" is honoured — the next push\n // backfills.\n const fetchLogs = useCallback(() => {\n setEntries([]);\n }, []);\n\n const filteredEntries = searchQuery\n ? entries.filter(\n (entry) =>\n entry.message.toLowerCase().includes(searchQuery.toLowerCase()) ||\n entry.source.toLowerCase().includes(searchQuery.toLowerCase())\n )\n : entries;\n\n const handleClear = () => setEntries([]);\n\n const handleExport = () => {\n const blob = new Blob([filteredEntries.map((entry) => JSON.stringify(entry)).join('\\n')], {\n type: 'application/x-ndjson',\n });\n const url = URL.createObjectURL(blob);\n const anchor = document.createElement('a');\n anchor.href = url;\n anchor.download = `agent-logs-${new Date().toISOString().slice(0, 19).replace(/:/g, '-')}.jsonl`;\n anchor.click();\n URL.revokeObjectURL(url);\n };\n\n const formatTime = (timestamp: string) => {\n try {\n const date = new Date(timestamp);\n return (\n date.toLocaleTimeString('en-US', {\n hour12: false,\n hour: '2-digit',\n minute: '2-digit',\n second: '2-digit',\n }) +\n '.' +\n String(date.getMilliseconds()).padStart(3, '0')\n );\n } catch {\n return timestamp;\n }\n };\n\n return (\n <div\n className={`flex flex-col overflow-hidden rounded-lg border border-border-default bg-bg-elevated ${className}`}\n >\n <div className=\"flex flex-wrap items-center gap-2 border-b border-border-default bg-bg-sunken px-3 py-2\">\n <div className=\"mr-2 flex items-center gap-1.5\">\n <div\n className={`size-2 rounded-full ${connected ? 'animate-pulse bg-status-success-bg' : 'bg-status-error-bg'}`}\n />\n <span className=\"text-xs text-text-tertiary\">\n {connected ? tr('logs.live', 'Live') : tr('logs.disconnected', 'Disconnected')}\n </span>\n </div>\n\n <select\n value={level}\n onChange={(event) => setLevel(event.target.value as LogEntry['level'])}\n className=\"rounded border border-border-subtle bg-bg-sunken px-2 py-1 text-xs text-text-primary\"\n aria-label={tr('logs.levelFilter', 'Log level filter')}\n >\n <option value=\"debug\">DEBUG+</option>\n <option value=\"info\">INFO+</option>\n <option value=\"warn\">WARN+</option>\n <option value=\"error\">ERROR</option>\n </select>\n\n <div className=\"relative min-w-[120px] max-w-xs flex-1\">\n <Search\n size={12}\n className=\"absolute left-2 top-1/2 -translate-y-1/2 text-text-tertiary\"\n />\n <input\n aria-label=\"control\"\n type=\"text\"\n placeholder={tr('logs.filterLogs', 'Filter logs...')}\n value={searchQuery}\n onChange={(event) => setSearchQuery(event.target.value)}\n className=\"w-full rounded border border-border-subtle bg-bg-sunken py-1 pl-6 pr-2 text-xs text-text-primary placeholder:text-text-tertiary\"\n />\n </div>\n\n <div className=\"ml-auto flex items-center gap-1\">\n <span className=\"text-xs text-text-tertiary\">\n {filteredEntries.length} {tr('logs.entries', 'entries')}\n </span>\n <button\n type=\"button\"\n onClick={() => void fetchLogs()}\n disabled={isRefreshing}\n className=\"rounded p-1 text-text-tertiary transition-colors hover:bg-bg-sunken disabled:opacity-50\"\n title={tr('common.refresh', 'Refresh')}\n >\n <RefreshCw size={14} className={isRefreshing ? 'animate-spin' : ''} />\n </button>\n <button\n type=\"button\"\n onClick={() => setPaused((current) => !current)}\n className={`rounded p-1 transition-colors hover:bg-bg-sunken ${paused ? 'text-status-warning-text' : 'text-text-tertiary'}`}\n title={paused ? tr('logs.resume', 'Resume') : tr('logs.pause', 'Pause')}\n >\n {paused ? <Play size={14} /> : <Pause size={14} />}\n </button>\n <button\n type=\"button\"\n onClick={handleClear}\n className=\"rounded p-1 text-text-tertiary transition-colors hover:bg-bg-sunken\"\n title={tr('logs.clear', 'Clear')}\n >\n <Trash2 size={14} />\n </button>\n <button\n type=\"button\"\n onClick={handleExport}\n className=\"rounded p-1 text-text-tertiary transition-colors hover:bg-bg-sunken\"\n title={tr('logs.export', 'Export')}\n >\n <Download size={14} />\n </button>\n </div>\n </div>\n\n {errorMessage ? (\n <div\n role=\"alert\"\n className=\"border-b border-border-default bg-status-error-bg-subtle px-3 py-2 text-xs text-status-error-text\"\n >\n {errorMessage}\n </div>\n ) : null}\n\n <div\n ref={containerRef}\n onScroll={handleScroll}\n className={`overflow-y-auto font-mono text-xs leading-relaxed ${compact ? 'max-h-[300px]' : 'min-h-[400px] flex-1'}`}\n style={{ maxHeight: compact ? '300px' : undefined }}\n >\n {filteredEntries.length === 0 ? (\n <div className=\"flex h-full min-h-[200px] items-center justify-center text-text-tertiary\">\n {!agentId\n ? tr('logs.connectToAgent', 'Connect to an agent to view logs')\n : errorMessage\n ? `${tr('logs.subscriptionError', 'Log stream error')}: ${errorMessage}`\n : streamLoading\n ? tr('logs.connecting', 'Connecting to log stream…')\n : tr('logs.waitingForEntries', 'Waiting for log entries…')}\n </div>\n ) : (\n filteredEntries.map((entry, index) => (\n <div\n key={`${entry.timestamp}-${entry.source}-${index}`}\n className={`flex gap-2 border-b border-border-default/30 px-3 py-0.5 hover:bg-bg-sunken ${LEVEL_BG[entry.level] ?? ''}`}\n >\n <span className=\"w-[85px] shrink-0 text-text-tertiary\">\n {formatTime(entry.timestamp)}\n </span>\n <span\n className={`w-[42px] shrink-0 uppercase font-semibold ${LEVEL_COLORS[entry.level] ?? 'text-text-primary'}`}\n >\n {entry.level}\n </span>\n <span\n className=\"w-[100px] shrink-0 truncate text-text-secondary\"\n title={entry.source}\n >\n [{entry.source}]\n </span>\n <span className=\"flex-1 break-all text-text-primary\">\n {entry.message}\n {entry.metadata && Object.keys(entry.metadata).length > 0 ? (\n <span className=\"ml-2 text-text-tertiary\">{JSON.stringify(entry.metadata)}</span>\n ) : null}\n </span>\n </div>\n ))\n )}\n </div>\n </div>\n );\n};\n"],"mappings":"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"}
|
|
@@ -156,7 +156,7 @@ function g({ note: g, onOpen: _, onEdit: v, onDelete: y, onPin: b, onUnpin: x, o
|
|
|
156
156
|
g.tagAssignments && g.tagAssignments.length > 0 ? /* @__PURE__ */ h("div", {
|
|
157
157
|
className: "mt-3 flex flex-wrap gap-1",
|
|
158
158
|
children: [g.tagAssignments.slice(0, 3).map((e) => /* @__PURE__ */ h("span", {
|
|
159
|
-
className: "inline-flex items-center gap-1 px-2 py-0.5 text-xs bg-
|
|
159
|
+
className: "inline-flex items-center gap-1 px-2 py-0.5 text-xs bg-[var(--color-accent-soft)] text-text-primary rounded",
|
|
160
160
|
style: e.tag?.color ? { borderLeft: `3px solid ${e.tag.color}` } : void 0,
|
|
161
161
|
children: [/* @__PURE__ */ m(u, { className: "size-3" }), e.tag?.label ?? e.tagId.slice(0, 8)]
|
|
162
162
|
}, e.id)), g.tagAssignments.length > 3 && /* @__PURE__ */ h("span", {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"NoteCard.js","names":[],"sources":["../../../src/components/notes/NoteCard.tsx"],"sourcesContent":["import { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport {\n FileText,\n Pin,\n Tag,\n MoreVertical,\n Edit,\n Trash2,\n PinOff,\n Lock,\n Globe,\n Share2,\n} from 'lucide-react';\nimport { useState } from 'react';\nimport { motion, AnimatePresence } from 'framer-motion';\nimport type { Note } from './types';\n\ninterface NoteCardProps {\n note: Note;\n onOpen?: (note: Note) => void;\n onEdit?: (note: Note) => void;\n onDelete?: (note: Note) => void;\n onPin?: (note: Note) => void;\n onUnpin?: (note: Note) => void;\n onShare?: (note: Note) => void;\n /**\n * Generic-sharing entry point: opens the full Share dialog (users / link /\n * vibe). Distinct from `onShare`, which only toggles PRIVATE/WORKSPACE\n * visibility. Provided only when the caller has share permission + the\n * `vibecontrols.sharing.note` flag is on.\n */\n onShareAccess?: (note: Note) => void;\n}\n\nexport function NoteCard({\n note,\n onOpen,\n onEdit,\n onDelete,\n onPin,\n onUnpin,\n onShare,\n onShareAccess,\n}: NoteCardProps) {\n const { t } = useI18n();\n const tr = (key: string, fallback: string) => {\n const v = t(key);\n return v === key ? fallback : v;\n };\n const [showMenu, setShowMenu] = useState(false);\n\n const formatDate = (dateStr: string) => {\n return new Date(dateStr).toLocaleDateString('en-US', {\n month: 'short',\n day: 'numeric',\n year: 'numeric',\n });\n };\n\n const getPreview = (content: string) => {\n // Remove markdown headers and get first 100 chars\n const text = content\n .replace(/^#+\\s+/gm, '')\n .replace(/\\n/g, ' ')\n .trim();\n return text.length > 100 ? text.slice(0, 100) + '...' : text;\n };\n\n return (\n <motion.div\n whileHover={{ y: -2, transition: { duration: 0.15, ease: [0.4, 0, 0.2, 1] } }}\n whileTap={{ scale: 0.98 }}\n className=\"bg-bg-surface border border-border-default rounded-lg p-4 hover:border-border-strong hover:shadow-md transition-all cursor-pointer\"\n onClick={() => onOpen?.(note)}\n >\n <div className=\"flex items-start justify-between gap-3\">\n <div className=\"flex items-start gap-3 min-w-0\">\n <div className=\"flex-shrink-0 size-10 rounded-lg bg-status-warning-bg flex items-center justify-center\">\n <FileText className=\"size-5 text-status-warning-text\" />\n </div>\n <div className=\"min-w-0\">\n <div className=\"flex items-center gap-2\">\n <h3 className=\"font-semibold text-text-primary truncate\">{note.title}</h3>\n {note.isPinned && <Pin className=\"size-4 text-status-warning-text flex-shrink-0\" />}\n {note.visibility === 'PRIVATE' ? (\n <span title=\"Private note\">\n <Lock className=\"size-3.5 text-text-muted flex-shrink-0\" />\n </span>\n ) : (\n <span title=\"Workspace note\">\n <Globe className=\"size-3.5 text-text-muted flex-shrink-0\" />\n </span>\n )}\n </div>\n <p className=\"text-xs text-text-secondary\">{formatDate(note.updatedAt)}</p>\n </div>\n </div>\n <div className=\"relative\">\n <button\n type=\"button\"\n onClick={(e) => {\n e.stopPropagation();\n setShowMenu(!showMenu);\n }}\n className=\"p-1 rounded hover:bg-bg-sunken transition-colors\"\n >\n <MoreVertical className=\"size-4 text-text-secondary\" />\n </button>\n <AnimatePresence>\n {showMenu && (\n <motion.div\n initial={{ opacity: 0, scale: 0.95, y: -4 }}\n animate={{ opacity: 1, scale: 1, y: 0 }}\n exit={{ opacity: 0, scale: 0.95, y: -4 }}\n transition={{ duration: 0.15 }}\n className=\"absolute right-0 mt-1 w-36 bg-bg-surface border border-border-default rounded-lg shadow-lg z-10\"\n >\n {onEdit && (\n <motion.button\n whileTap={{ scale: 0.97 }}\n onClick={(e) => {\n e.stopPropagation();\n onEdit(note);\n setShowMenu(false);\n }}\n className=\"flex items-center gap-2 w-full px-3 py-2 text-sm text-text-primary hover:bg-bg-sunken rounded-t-lg\"\n >\n <Edit className=\"size-4\" />\n Edit\n </motion.button>\n )}\n {onShare && note.visibility === 'PRIVATE' && (\n <motion.button\n whileTap={{ scale: 0.97 }}\n onClick={(e) => {\n e.stopPropagation();\n onShare(note);\n setShowMenu(false);\n }}\n className=\"flex items-center gap-2 w-full px-3 py-2 text-sm text-text-primary hover:bg-bg-sunken\"\n >\n <Share2 className=\"size-4\" />\n Share with workspace\n </motion.button>\n )}\n {onShare && note.visibility === 'WORKSPACE' && (\n <motion.button\n whileTap={{ scale: 0.97 }}\n onClick={(e) => {\n e.stopPropagation();\n onShare(note);\n setShowMenu(false);\n }}\n className=\"flex items-center gap-2 w-full px-3 py-2 text-sm text-text-primary hover:bg-bg-sunken\"\n >\n <Lock className=\"size-4\" />\n Make private\n </motion.button>\n )}\n {onShareAccess && (\n <motion.button\n whileTap={{ scale: 0.97 }}\n data-testid=\"note-share-access\"\n onClick={(e) => {\n e.stopPropagation();\n onShareAccess(note);\n setShowMenu(false);\n }}\n className=\"flex items-center gap-2 w-full px-3 py-2 text-sm text-text-primary hover:bg-bg-sunken\"\n >\n <Share2 className=\"size-4\" />\n Share access…\n </motion.button>\n )}\n {note.isPinned && onUnpin && (\n <motion.button\n whileTap={{ scale: 0.97 }}\n onClick={(e) => {\n e.stopPropagation();\n onUnpin(note);\n setShowMenu(false);\n }}\n className=\"flex items-center gap-2 w-full px-3 py-2 text-sm text-text-primary hover:bg-bg-sunken\"\n >\n <PinOff className=\"size-4\" />\n Unpin\n </motion.button>\n )}\n {!note.isPinned && onPin && (\n <motion.button\n whileTap={{ scale: 0.97 }}\n onClick={(e) => {\n e.stopPropagation();\n onPin(note);\n setShowMenu(false);\n }}\n className=\"flex items-center gap-2 w-full px-3 py-2 text-sm text-text-primary hover:bg-bg-sunken\"\n >\n <Pin className=\"size-4\" />\n Pin\n </motion.button>\n )}\n {onDelete && (\n <motion.button\n whileTap={{ scale: 0.97 }}\n onClick={(e) => {\n e.stopPropagation();\n onDelete(note);\n setShowMenu(false);\n }}\n className=\"flex items-center gap-2 w-full px-3 py-2 text-sm text-status-error-text hover:bg-bg-sunken rounded-b-lg\"\n >\n <Trash2 className=\"size-4\" />\n Delete\n </motion.button>\n )}\n </motion.div>\n )}\n </AnimatePresence>\n </div>\n </div>\n\n <p className=\"mt-3 text-sm text-text-secondary line-clamp-2\">{getPreview(note.content)}</p>\n\n {/* Render tagAssignments if available, otherwise fall back to legacy string tags */}\n {note.tagAssignments && note.tagAssignments.length > 0 ? (\n <div className=\"mt-3 flex flex-wrap gap-1\">\n {note.tagAssignments.slice(0, 3).map((assignment) => (\n <span\n key={assignment.id}\n className=\"inline-flex items-center gap-1 px-2 py-0.5 text-xs bg-action-primary-bg/10 text-action-primary-bg rounded\"\n style={\n assignment.tag?.color\n ? { borderLeft: `3px solid ${assignment.tag.color}` }\n : undefined\n }\n >\n <Tag className=\"size-3\" />\n {assignment.tag?.label ?? assignment.tagId.slice(0, 8)}\n </span>\n ))}\n {note.tagAssignments.length > 3 && (\n <span className=\"px-2 py-0.5 text-xs bg-bg-sunken text-text-secondary rounded\">\n +{note.tagAssignments.length - 3}\n </span>\n )}\n </div>\n ) : note.tags && note.tags.length > 0 ? (\n <div className=\"mt-3 flex flex-wrap gap-1\">\n {note.tags.slice(0, 3).map((tag) => (\n <span\n key={tag}\n className=\"inline-flex items-center gap-1 px-2 py-0.5 text-xs bg-bg-sunken text-text-secondary rounded\"\n >\n <Tag className=\"size-3\" />\n {tag}\n </span>\n ))}\n {note.tags.length > 3 && (\n <span className=\"px-2 py-0.5 text-xs bg-bg-sunken text-text-secondary rounded\">\n +{note.tags.length - 3}\n </span>\n )}\n </div>\n ) : null}\n </motion.div>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"NoteCard.js","names":[],"sources":["../../../src/components/notes/NoteCard.tsx"],"sourcesContent":["import { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport {\n FileText,\n Pin,\n Tag,\n MoreVertical,\n Edit,\n Trash2,\n PinOff,\n Lock,\n Globe,\n Share2,\n} from 'lucide-react';\nimport { useState } from 'react';\nimport { motion, AnimatePresence } from 'framer-motion';\nimport type { Note } from './types';\n\ninterface NoteCardProps {\n note: Note;\n onOpen?: (note: Note) => void;\n onEdit?: (note: Note) => void;\n onDelete?: (note: Note) => void;\n onPin?: (note: Note) => void;\n onUnpin?: (note: Note) => void;\n onShare?: (note: Note) => void;\n /**\n * Generic-sharing entry point: opens the full Share dialog (users / link /\n * vibe). Distinct from `onShare`, which only toggles PRIVATE/WORKSPACE\n * visibility. Provided only when the caller has share permission + the\n * `vibecontrols.sharing.note` flag is on.\n */\n onShareAccess?: (note: Note) => void;\n}\n\nexport function NoteCard({\n note,\n onOpen,\n onEdit,\n onDelete,\n onPin,\n onUnpin,\n onShare,\n onShareAccess,\n}: NoteCardProps) {\n const { t } = useI18n();\n const tr = (key: string, fallback: string) => {\n const v = t(key);\n return v === key ? fallback : v;\n };\n const [showMenu, setShowMenu] = useState(false);\n\n const formatDate = (dateStr: string) => {\n return new Date(dateStr).toLocaleDateString('en-US', {\n month: 'short',\n day: 'numeric',\n year: 'numeric',\n });\n };\n\n const getPreview = (content: string) => {\n // Remove markdown headers and get first 100 chars\n const text = content\n .replace(/^#+\\s+/gm, '')\n .replace(/\\n/g, ' ')\n .trim();\n return text.length > 100 ? text.slice(0, 100) + '...' : text;\n };\n\n return (\n <motion.div\n whileHover={{ y: -2, transition: { duration: 0.15, ease: [0.4, 0, 0.2, 1] } }}\n whileTap={{ scale: 0.98 }}\n className=\"bg-bg-surface border border-border-default rounded-lg p-4 hover:border-border-strong hover:shadow-md transition-all cursor-pointer\"\n onClick={() => onOpen?.(note)}\n >\n <div className=\"flex items-start justify-between gap-3\">\n <div className=\"flex items-start gap-3 min-w-0\">\n <div className=\"flex-shrink-0 size-10 rounded-lg bg-status-warning-bg flex items-center justify-center\">\n <FileText className=\"size-5 text-status-warning-text\" />\n </div>\n <div className=\"min-w-0\">\n <div className=\"flex items-center gap-2\">\n <h3 className=\"font-semibold text-text-primary truncate\">{note.title}</h3>\n {note.isPinned && <Pin className=\"size-4 text-status-warning-text flex-shrink-0\" />}\n {note.visibility === 'PRIVATE' ? (\n <span title=\"Private note\">\n <Lock className=\"size-3.5 text-text-muted flex-shrink-0\" />\n </span>\n ) : (\n <span title=\"Workspace note\">\n <Globe className=\"size-3.5 text-text-muted flex-shrink-0\" />\n </span>\n )}\n </div>\n <p className=\"text-xs text-text-secondary\">{formatDate(note.updatedAt)}</p>\n </div>\n </div>\n <div className=\"relative\">\n <button\n type=\"button\"\n onClick={(e) => {\n e.stopPropagation();\n setShowMenu(!showMenu);\n }}\n className=\"p-1 rounded hover:bg-bg-sunken transition-colors\"\n >\n <MoreVertical className=\"size-4 text-text-secondary\" />\n </button>\n <AnimatePresence>\n {showMenu && (\n <motion.div\n initial={{ opacity: 0, scale: 0.95, y: -4 }}\n animate={{ opacity: 1, scale: 1, y: 0 }}\n exit={{ opacity: 0, scale: 0.95, y: -4 }}\n transition={{ duration: 0.15 }}\n className=\"absolute right-0 mt-1 w-36 bg-bg-surface border border-border-default rounded-lg shadow-lg z-10\"\n >\n {onEdit && (\n <motion.button\n whileTap={{ scale: 0.97 }}\n onClick={(e) => {\n e.stopPropagation();\n onEdit(note);\n setShowMenu(false);\n }}\n className=\"flex items-center gap-2 w-full px-3 py-2 text-sm text-text-primary hover:bg-bg-sunken rounded-t-lg\"\n >\n <Edit className=\"size-4\" />\n Edit\n </motion.button>\n )}\n {onShare && note.visibility === 'PRIVATE' && (\n <motion.button\n whileTap={{ scale: 0.97 }}\n onClick={(e) => {\n e.stopPropagation();\n onShare(note);\n setShowMenu(false);\n }}\n className=\"flex items-center gap-2 w-full px-3 py-2 text-sm text-text-primary hover:bg-bg-sunken\"\n >\n <Share2 className=\"size-4\" />\n Share with workspace\n </motion.button>\n )}\n {onShare && note.visibility === 'WORKSPACE' && (\n <motion.button\n whileTap={{ scale: 0.97 }}\n onClick={(e) => {\n e.stopPropagation();\n onShare(note);\n setShowMenu(false);\n }}\n className=\"flex items-center gap-2 w-full px-3 py-2 text-sm text-text-primary hover:bg-bg-sunken\"\n >\n <Lock className=\"size-4\" />\n Make private\n </motion.button>\n )}\n {onShareAccess && (\n <motion.button\n whileTap={{ scale: 0.97 }}\n data-testid=\"note-share-access\"\n onClick={(e) => {\n e.stopPropagation();\n onShareAccess(note);\n setShowMenu(false);\n }}\n className=\"flex items-center gap-2 w-full px-3 py-2 text-sm text-text-primary hover:bg-bg-sunken\"\n >\n <Share2 className=\"size-4\" />\n Share access…\n </motion.button>\n )}\n {note.isPinned && onUnpin && (\n <motion.button\n whileTap={{ scale: 0.97 }}\n onClick={(e) => {\n e.stopPropagation();\n onUnpin(note);\n setShowMenu(false);\n }}\n className=\"flex items-center gap-2 w-full px-3 py-2 text-sm text-text-primary hover:bg-bg-sunken\"\n >\n <PinOff className=\"size-4\" />\n Unpin\n </motion.button>\n )}\n {!note.isPinned && onPin && (\n <motion.button\n whileTap={{ scale: 0.97 }}\n onClick={(e) => {\n e.stopPropagation();\n onPin(note);\n setShowMenu(false);\n }}\n className=\"flex items-center gap-2 w-full px-3 py-2 text-sm text-text-primary hover:bg-bg-sunken\"\n >\n <Pin className=\"size-4\" />\n Pin\n </motion.button>\n )}\n {onDelete && (\n <motion.button\n whileTap={{ scale: 0.97 }}\n onClick={(e) => {\n e.stopPropagation();\n onDelete(note);\n setShowMenu(false);\n }}\n className=\"flex items-center gap-2 w-full px-3 py-2 text-sm text-status-error-text hover:bg-bg-sunken rounded-b-lg\"\n >\n <Trash2 className=\"size-4\" />\n Delete\n </motion.button>\n )}\n </motion.div>\n )}\n </AnimatePresence>\n </div>\n </div>\n\n <p className=\"mt-3 text-sm text-text-secondary line-clamp-2\">{getPreview(note.content)}</p>\n\n {/* Render tagAssignments if available, otherwise fall back to legacy string tags */}\n {note.tagAssignments && note.tagAssignments.length > 0 ? (\n <div className=\"mt-3 flex flex-wrap gap-1\">\n {note.tagAssignments.slice(0, 3).map((assignment) => (\n <span\n key={assignment.id}\n className=\"inline-flex items-center gap-1 px-2 py-0.5 text-xs bg-[var(--color-accent-soft)] text-text-primary rounded\"\n style={\n assignment.tag?.color\n ? { borderLeft: `3px solid ${assignment.tag.color}` }\n : undefined\n }\n >\n <Tag className=\"size-3\" />\n {assignment.tag?.label ?? assignment.tagId.slice(0, 8)}\n </span>\n ))}\n {note.tagAssignments.length > 3 && (\n <span className=\"px-2 py-0.5 text-xs bg-bg-sunken text-text-secondary rounded\">\n +{note.tagAssignments.length - 3}\n </span>\n )}\n </div>\n ) : note.tags && note.tags.length > 0 ? (\n <div className=\"mt-3 flex flex-wrap gap-1\">\n {note.tags.slice(0, 3).map((tag) => (\n <span\n key={tag}\n className=\"inline-flex items-center gap-1 px-2 py-0.5 text-xs bg-bg-sunken text-text-secondary rounded\"\n >\n <Tag className=\"size-3\" />\n {tag}\n </span>\n ))}\n {note.tags.length > 3 && (\n <span className=\"px-2 py-0.5 text-xs bg-bg-sunken text-text-secondary rounded\">\n +{note.tags.length - 3}\n </span>\n )}\n </div>\n ) : null}\n </motion.div>\n );\n}\n"],"mappings":"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"}
|
|
@@ -186,7 +186,7 @@ function v({ note: v, vibeOptions: y = [], defaultVibeId: b, hideVibeSelect: x,
|
|
|
186
186
|
/* @__PURE__ */ g("div", {
|
|
187
187
|
className: "flex flex-wrap gap-1.5 min-h-[38px] px-3 py-2 bg-bg-sunken border border-border-default rounded-md",
|
|
188
188
|
children: [z.filter((e) => j.has(e.id)).map((e) => /* @__PURE__ */ g("span", {
|
|
189
|
-
className: "inline-flex items-center gap-1 px-2 py-0.5 text-xs font-medium bg-
|
|
189
|
+
className: "inline-flex items-center gap-1 px-2 py-0.5 text-xs font-medium bg-[var(--color-accent-soft)] text-text-primary rounded",
|
|
190
190
|
children: [
|
|
191
191
|
/* @__PURE__ */ h(p, { className: "size-3" }),
|
|
192
192
|
e.label,
|
|
@@ -231,7 +231,7 @@ function v({ note: v, vibeOptions: y = [], defaultVibeId: b, hideVibeSelect: x,
|
|
|
231
231
|
type: "button",
|
|
232
232
|
onClick: q,
|
|
233
233
|
disabled: !V.trim() || G,
|
|
234
|
-
className: "p-1 text-action-primary-bg hover:bg-
|
|
234
|
+
className: "p-1 text-action-primary-bg hover:bg-[var(--color-accent-soft)] rounded transition-colors disabled:opacity-30",
|
|
235
235
|
children: /* @__PURE__ */ h(d, { className: "size-4" })
|
|
236
236
|
})
|
|
237
237
|
]
|