@burdenoff/microfe-vibecontrols 2026.710.1 → 2026.714.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/agents/AgentForm.js.map +1 -1
- package/dist/components/security/agentClient.js.map +1 -1
- package/dist/components/security/types.js.map +1 -1
- package/dist/pages/AgentDetailsPage.js.map +1 -1
- package/dist/pages/CatalogDetailPage.js.map +1 -1
- package/dist/pages/ComponentDetailPage.js.map +1 -1
- package/dist/pages/ScheduledJobDetailPage.js.map +1 -1
- package/dist/pages/VibesPage.js.map +1 -1
- package/dist/services/assistantApi.js +34 -248
- package/dist/services/assistantApi.js.map +1 -1
- package/dist/services/createSandboxAssistantTransport.js +11 -114
- package/dist/services/createSandboxAssistantTransport.js.map +1 -1
- package/dist/types/assistant.js.map +1 -1
- package/package.json +2 -2
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ComponentDetailPage.js","names":[],"sources":["../../src/pages/ComponentDetailPage.tsx"],"sourcesContent":["import { useState, useCallback, useMemo } from 'react';\nimport { useParams } from 'react-router-dom';\nimport { usePermissions } from '@burdenoff/fe-libs/shared/providers/shell';\nimport { VIBE_PERMISSIONS } from '../constants/permissions';\nimport { AnimatePresence, motion } from 'framer-motion';\nimport {\n ArrowLeft,\n Box,\n BookOpen,\n BarChart3,\n FileText,\n Settings,\n RefreshCw,\n FileCode,\n Layers,\n Upload,\n Trash2,\n Tag,\n Plus,\n X,\n Share2,\n} from 'lucide-react';\nimport {\n PageHeader,\n Card,\n StatCard,\n EmptyState,\n ErrorState,\n QuotaExhaustedDialog,\n ConfirmDialog,\n} from '@/components/shared';\nimport { EntityTagPicker } from '@/components/shared/EntityTagPicker';\nimport { PageSkeleton } from '@/components/shared/PageSkeleton';\nimport { MotionPage, MotionList, MotionItem } from '@burdenoff/fe-libs/shared/motion';\nimport { PagePurpose, NextSteps } from '@burdenoff/fe-libs/ui';\n\nimport { DocsTabPanel } from '@/components/docs';\nimport { ScorecardPanel } from '@/components/scorecard';\nimport { CatalogCard } from '@/components/catalog';\nimport type { CatalogCardData } from '@/components/catalog';\nimport { ComponentForm, DependencyGraphTab } from '@/components/idp-components';\nimport type { ComponentFormData } from '@/components/idp-components';\nimport { PublishTemplateForm } from '@/components/templates';\nimport type { PublishTemplateFormData } from '@/components/templates';\nimport { EntityShareButton } from '@/components/sharing';\nimport { useVibeNavigate } from '@/providers/VibeControlsProvider';\nimport { useQueryBackedTab } from '@/hooks/useQueryBackedTab';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { isQuotaExhaustedError, markQuotaHandledLocally } from '@/utils/quotaUtils';\nimport {\n useGetComponentQuery,\n useUpdateComponentMutation,\n useDeleteComponentMutation,\n usePublishComponentAsTemplateMutation,\n useAddComponentToCatalogMutation,\n useListCatalogsQuery,\n} from '@/generated/wspace-operations';\nimport type { VibecontrolsTemplateCategory } from '@/generated/wspace-types';\n\ntype ComponentTab =\n | 'overview'\n | 'catalogs'\n | 'scorecard'\n | 'docs'\n | 'dependencies'\n | 'template'\n | 'settings';\nconst COMPONENT_TABS: readonly ComponentTab[] = [\n 'overview',\n 'catalogs',\n 'scorecard',\n 'docs',\n 'dependencies',\n 'template',\n 'settings',\n];\n\nexport function ComponentDetailPage() {\n const { t } = useI18n();\n const { id } = useParams<{ id: string }>();\n const navigate = useVibeNavigate();\n const { hasPermission, isLoading: permissionsLoading } = usePermissions();\n const canReadComponent = hasPermission(VIBE_PERMISSIONS.COMPONENT_READ);\n const canUpdateComponent = hasPermission(VIBE_PERMISSIONS.COMPONENT_UPDATE);\n const canDeleteComponent = hasPermission(VIBE_PERMISSIONS.COMPONENT_DELETE);\n const canReadCatalog = hasPermission(VIBE_PERMISSIONS.CATALOG_READ);\n const canUpdateCatalog = hasPermission(VIBE_PERMISSIONS.CATALOG_UPDATE);\n const canUseCatalogPicker = canReadCatalog && canUpdateCatalog;\n const canCreateTemplate = hasPermission(VIBE_PERMISSIONS.TEMPLATE_CREATE);\n\n const tr = useCallback(\n (key: string, fallback: string): string => {\n const value = t(key);\n return value === key ? fallback : value;\n },\n [t]\n );\n\n const [activeTab, setActiveTab] = useQueryBackedTab<ComponentTab>(COMPONENT_TABS, 'overview');\n const [showEditForm, setShowEditForm] = useState(false);\n const [showDeleteConfirm, setShowDeleteConfirm] = useState(false);\n const [isDeleting, setIsDeleting] = useState(false);\n const [successMessage, setSuccessMessage] = useState<string | null>(null);\n const [showQuotaExhausted, setShowQuotaExhausted] = useState(false);\n const [showPublishForm, setShowPublishForm] = useState(false);\n const [showAddToCatalog, setShowAddToCatalog] = useState(false);\n\n const { data, loading, error, refetch } = useGetComponentQuery({\n variables: { id: id || '' },\n skip: !id || permissionsLoading || !canReadComponent,\n fetchPolicy: 'cache-and-network',\n });\n\n const [updateComponent] = useUpdateComponentMutation({ refetchQueries: ['GetComponent'] });\n const [deleteComponent] = useDeleteComponentMutation({ refetchQueries: ['ListComponents'] });\n const [publishAsTemplate] = usePublishComponentAsTemplateMutation({\n refetchQueries: ['GetComponent'],\n });\n const [addToCatalog] = useAddComponentToCatalogMutation({ refetchQueries: ['GetComponent'] });\n const { data: catalogsData } = useListCatalogsQuery({\n variables: { pagination: { limit: 100, offset: 0 } },\n skip: !showAddToCatalog || !canUseCatalogPicker,\n fetchPolicy: 'cache-and-network',\n });\n\n const component = data?.vibecontrolsComponent ?? null;\n\n const catalogs: CatalogCardData[] = useMemo(() => {\n return (component?.catalogs ?? []).map((entry: Record<string, any>) => {\n const c = entry.catalog || {};\n return {\n id: c.id as string,\n name: c.name as string,\n description: (c.description as string) || null,\n visibility: c.visibility as CatalogCardData['visibility'],\n componentCount: (c.componentCount as number) || 0,\n vibeCount: (c.vibeCount as number) || 0,\n tags: (c.tags as string[]) || [],\n createdAt: c.createdAt as string,\n updatedAt: c.updatedAt as string,\n };\n });\n }, [component?.catalogs]);\n\n if (permissionsLoading)\n return (\n <div className=\"flex items-center justify-center min-h-[400px]\">\n <p className=\"text-text-secondary\">Loading permissions…</p>\n </div>\n );\n if (!canReadComponent)\n return (\n <div className=\"flex items-center justify-center min-h-[400px]\">\n <div className=\"text-center space-y-2\">\n <h2 className=\"text-lg font-semibold text-text-primary\">Access Denied</h2>\n <p className=\"text-sm text-text-secondary\">\n You don't have permission to access this page.\n </p>\n </div>\n </div>\n );\n\n const handleBack = () => {\n navigate('/components');\n };\n\n const handleCatalogClick = (catalog: CatalogCardData) => {\n navigate(`/catalogs/${catalog.id}`);\n };\n\n const handleUpdateComponent = async (formData: ComponentFormData) => {\n if (!id || !canUpdateComponent) return;\n await updateComponent({\n variables: {\n id,\n input: {\n name: formData.name,\n description: formData.description || undefined,\n type: formData.type,\n lifecycle: formData.lifecycle,\n language: formData.language || undefined,\n framework: formData.framework || undefined,\n version: formData.version || undefined,\n ownerName: formData.ownerName || undefined,\n tags: formData.tags.length > 0 ? formData.tags : undefined,\n repositoryUrl: formData.repositoryUrl || undefined,\n documentationUrl: formData.documentationUrl || undefined,\n dependencies: formData.dependencies ?? undefined,\n sbomData: formData.sbomData ?? undefined,\n },\n },\n });\n setSuccessMessage(tr('component.updatedSuccess', 'Component updated successfully'));\n setTimeout(() => setSuccessMessage(null), 3000);\n };\n\n const handleDeleteComponent = async () => {\n if (!id) return;\n setIsDeleting(true);\n try {\n await deleteComponent({ variables: { id } });\n setShowDeleteConfirm(false);\n navigate('/components');\n } finally {\n setIsDeleting(false);\n }\n };\n\n const handlePublishAsTemplate = async (formData: PublishTemplateFormData) => {\n if (!id || !canCreateTemplate) return;\n try {\n const res = await publishAsTemplate({\n variables: {\n componentId: id,\n input: {\n name: formData.name,\n description: formData.description || undefined,\n category: formData.category as VibecontrolsTemplateCategory,\n version: formData.version || undefined,\n setupInstructions: formData.setupInstructions || undefined,\n variables: formData.variables.length > 0 ? formData.variables : undefined,\n sourceRepositoryUrl: formData.sourceRepositoryUrl,\n },\n },\n });\n if (res.error && isQuotaExhaustedError(res.error)) {\n setShowPublishForm(false);\n setShowQuotaExhausted(true);\n return;\n }\n setShowPublishForm(false);\n setSuccessMessage(tr('component.publishedAsTemplate', 'Published as template successfully'));\n setTimeout(() => setSuccessMessage(null), 3000);\n } catch (err) {\n if (isQuotaExhaustedError(err)) {\n setShowPublishForm(false);\n markQuotaHandledLocally();\n setShowQuotaExhausted(true);\n return;\n }\n throw err;\n }\n };\n\n const lifecycleStyles: Record<string, string> = {\n EXPERIMENTAL: 'bg-status-warning-bg text-status-warning-text',\n ACTIVE: 'bg-status-success-bg text-status-success-text',\n DEPRECATED: 'bg-status-error-bg text-status-error-text',\n END_OF_LIFE: 'bg-status-error-bg text-status-error-text',\n };\n\n const deps = Array.isArray(component?.dependencies)\n ? (component.dependencies as Array<{\n name: string;\n version?: string | null;\n type?: string | null;\n }>)\n : [];\n\n const tabs: { id: ComponentTab; label: string; icon: typeof Box; badge?: string }[] = [\n { id: 'overview', label: tr('nav.overview', 'Overview'), icon: Box },\n {\n id: 'catalogs',\n label: tr('component.catalogs', 'Catalogs'),\n icon: BookOpen,\n badge: `${catalogs.length}`,\n },\n { id: 'scorecard', label: tr('component.scorecard', 'Scorecard'), icon: BarChart3 },\n { id: 'docs', label: tr('component.docs', 'Docs'), icon: FileText },\n {\n id: 'dependencies',\n label: tr('component.dependencies', 'Dependencies'),\n icon: Share2,\n badge: deps.length > 0 ? `${deps.length}` : undefined,\n },\n { id: 'template', label: tr('component.template', 'Template'), icon: FileCode },\n { id: 'settings', label: tr('common.settings', 'Settings'), icon: Settings },\n ];\n\n // Guard states\n if (loading && !component) {\n return (\n <MotionPage className=\"space-y-6 px-6 lg:px-8 py-6 bg-bg-canvas min-h-screen\">\n <button\n type=\"button\"\n onClick={handleBack}\n className=\"flex items-center gap-2 text-text-secondary hover:text-text-primary transition-colors\"\n >\n <ArrowLeft className=\"size-4\" />\n {tr('component.backToComponents', 'Back to Components')}\n </button>\n <PageSkeleton variant=\"detail\" />\n </MotionPage>\n );\n }\n\n if (error && !component) {\n return (\n <MotionPage className=\"space-y-6 px-6 lg:px-8 py-6 bg-bg-canvas min-h-screen\">\n <button\n type=\"button\"\n onClick={handleBack}\n className=\"flex items-center gap-2 text-text-secondary hover:text-text-primary transition-colors\"\n >\n <ArrowLeft className=\"size-4\" />\n {tr('component.backToComponents', 'Back to Components')}\n </button>\n <ErrorState\n title={tr('component.failedLoad', 'Failed to load component')}\n message={error.message}\n onRetry={() => refetch()}\n />\n </MotionPage>\n );\n }\n\n if (!component) {\n return (\n <MotionPage className=\"space-y-6 px-6 lg:px-8 py-6 bg-bg-canvas min-h-screen\">\n <button\n type=\"button\"\n onClick={handleBack}\n className=\"flex items-center gap-2 text-text-secondary hover:text-text-primary transition-colors\"\n >\n <ArrowLeft className=\"size-4\" />\n {tr('component.backToComponents', 'Back to Components')}\n </button>\n <EmptyState\n icon={<Box className=\"size-8 text-text-secondary\" />}\n title={tr('component.notFound', 'Component not found')}\n description={`No component with ID \"${id}\" exists`}\n action={\n <button\n type=\"button\"\n onClick={handleBack}\n className=\"px-4 py-2 bg-action-primary-bg text-action-primary-text rounded-md hover:bg-action-primary-bg-hover transition-colors\"\n >\n {tr('component.backToComponents', 'Back to Components')}\n </button>\n }\n />\n </MotionPage>\n );\n }\n\n return (\n <MotionPage className=\"space-y-6 px-6 lg:px-8 py-6 bg-bg-canvas min-h-screen\">\n {/* Success Toast */}\n {successMessage && (\n <div className=\"fixed top-4 left-4 right-4 sm:left-auto z-50 bg-status-success-bg text-status-success-text px-4 py-3 rounded-md shadow-lg\">\n {successMessage}\n </div>\n )}\n\n {/* Back Button */}\n <button\n type=\"button\"\n onClick={handleBack}\n className=\"flex items-center gap-2 text-text-secondary hover:text-text-primary transition-colors\"\n >\n <ArrowLeft className=\"size-4\" />\n {tr('component.backToComponents', 'Back to Components')}\n </button>\n\n {/* Header */}\n <PageHeader\n title={component.name}\n description={component.description || tr('component.noDescription', 'No description')}\n actions={\n <div className=\"flex items-center gap-2 flex-wrap\">\n <EntityShareButton\n entity=\"component\"\n resource={{ id: component.id, name: component.name }}\n label={tr('component.share', 'Share')}\n variant=\"button\"\n />\n <button\n type=\"button\"\n onClick={() => refetch()}\n disabled={loading}\n className=\"flex items-center justify-center p-2 bg-bg-sunken text-text-primary rounded-md hover:bg-action-secondary-bg-hover transition-colors disabled:opacity-50\"\n title={tr('common.refresh', 'Refresh')}\n >\n <RefreshCw className={`size-4 ${loading ? 'animate-spin' : ''}`} />\n </button>\n <span className=\"inline-flex items-center px-2.5 py-1 text-xs font-medium bg-bg-sunken text-text-secondary rounded\">\n {component.type}\n </span>\n <span\n className={`inline-flex items-center px-2.5 py-1 text-xs font-medium rounded ${lifecycleStyles[component.lifecycle] || 'bg-bg-sunken text-text-secondary'}`}\n >\n {component.lifecycle}\n </span>\n </div>\n }\n />\n\n <PagePurpose icon={<Box className=\"size-4\" />}>\n {tr(\n 'componentDetail.purpose',\n 'This is the record for one component — a single service, app, library or API in your system. From here you see its lifecycle and ownership, the catalogs it belongs to, its dependencies, docs and scorecard, and you can publish it as a reusable template. Come here to understand what something is, who owns it, and how healthy it is.'\n )}\n </PagePurpose>\n\n <NextSteps\n storageKey=\"vibecontrols-component-details\"\n steps={[\n {\n id: 'view-dependencies',\n label: tr('componentDetail.next.dependencies', 'Map its dependencies'),\n description: tr(\n 'componentDetail.next.dependenciesDesc',\n 'See what this component depends on and what depends on it.'\n ),\n icon: <Share2 className=\"size-4\" />,\n onClick: () => setActiveTab('dependencies'),\n },\n {\n id: 'check-scorecard',\n label: tr('componentDetail.next.scorecard', 'Review the scorecard'),\n description: tr(\n 'componentDetail.next.scorecardDesc',\n 'Check health and best-practice scores for this component.'\n ),\n icon: <BarChart3 className=\"size-4\" />,\n onClick: () => setActiveTab('scorecard'),\n },\n {\n id: 'publish-template',\n label: tr('componentDetail.next.template', 'Publish as a template'),\n description: tr(\n 'componentDetail.next.templateDesc',\n 'Turn this setup into a reusable blueprint for new components.'\n ),\n icon: <FileCode className=\"size-4\" />,\n onClick: () => setActiveTab('template'),\n },\n ]}\n />\n\n {/* Stat Cards */}\n <MotionList stagger={0.04} className=\"grid grid-cols-2 sm:grid-cols-4 gap-3 sm:gap-4\">\n <MotionItem>\n <StatCard\n title={tr('component.type', 'Type')}\n value={component.type}\n icon={<Box className=\"size-4\" />}\n />\n </MotionItem>\n <MotionItem>\n <StatCard\n title={tr('component.lifecycle', 'Lifecycle')}\n value={component.lifecycle}\n icon={<Layers className=\"size-4\" />}\n variant={\n component.lifecycle === 'ACTIVE'\n ? 'success'\n : component.lifecycle === 'DEPRECATED' || component.lifecycle === 'END_OF_LIFE'\n ? 'error'\n : 'warning'\n }\n />\n </MotionItem>\n <MotionItem>\n <StatCard\n title={tr('component.version', 'Version')}\n value={component.version || '-'}\n icon={<FileCode className=\"size-4\" />}\n />\n </MotionItem>\n <MotionItem>\n <StatCard\n title={tr('component.catalogs', 'Catalogs')}\n value={catalogs.length}\n icon={<BookOpen className=\"size-4\" />}\n />\n </MotionItem>\n </MotionList>\n\n {/* Universal tags */}\n <Card>\n <div className=\"p-4 border-b border-border-default flex items-center gap-2\">\n <Tag className=\"size-5\" />\n <h3 className=\"font-semibold text-text-primary\">{tr('component.tags', 'Tags')}</h3>\n </div>\n <div className=\"p-4\">\n <EntityTagPicker entityType=\"VIBECONTROLS_COMPONENT\" entityId={component.id} />\n </div>\n </Card>\n\n {/* Tab Navigation */}\n <div className=\"border-b border-border-default\">\n <nav\n className=\"flex gap-1 -mb-px overflow-x-auto pb-1\"\n aria-label={tr('component.tabsAriaLabel', 'Component tabs')}\n >\n {tabs.map((tab) => {\n const Icon = tab.icon;\n const isActiveTab = activeTab === tab.id;\n return (\n <button\n type=\"button\"\n key={tab.id}\n onClick={() => setActiveTab(tab.id)}\n className={`flex shrink-0 items-center gap-1.5 px-3 py-2.5 text-xs sm:gap-2 sm:px-4 sm:text-sm font-medium border-b-2 transition-colors whitespace-nowrap ${\n isActiveTab\n ? 'border-action-primary-bg text-action-primary-text'\n : 'border-transparent text-text-secondary hover:text-text-primary hover:border-border-default'\n }`}\n aria-selected={isActiveTab}\n role=\"tab\"\n >\n <Icon className=\"size-4\" />\n {tab.label}\n {tab.badge && (\n <span\n className={`hidden rounded-full px-1.5 py-0.5 text-[10px] sm:inline-flex sm:text-xs ${\n isActiveTab\n ? 'bg-[var(--color-accent-soft)] text-text-primary'\n : 'bg-bg-sunken text-text-muted'\n }`}\n >\n {tab.badge}\n </span>\n )}\n </button>\n );\n })}\n </nav>\n </div>\n\n {/* Tab Panels */}\n <AnimatePresence mode=\"wait\">\n {activeTab === 'overview' && (\n <motion.div\n key=\"overview\"\n initial={{ opacity: 0, y: 8 }}\n animate={{ opacity: 1, y: 0 }}\n exit={{ opacity: 0, y: -8 }}\n transition={{ duration: 0.2 }}\n className=\"space-y-6\"\n >\n {component.description && (\n <Card>\n <div className=\"p-4\">\n <p className=\"text-text-primary\">{component.description}</p>\n </div>\n </Card>\n )}\n\n <Card>\n <div className=\"p-4 border-b border-border-default\">\n <h3 className=\"font-semibold text-text-primary\">\n {tr('component.metadata', 'Metadata')}\n </h3>\n </div>\n <div className=\"p-4\">\n <div className=\"grid grid-cols-1 sm:grid-cols-2 gap-4 text-sm\">\n <div>\n <p className=\"text-text-secondary\">{tr('component.ownerNameLabel', 'Owner')}</p>\n <p className=\"font-medium text-text-primary\">\n {component.ownerName || tr('common.unset', 'Unset')}\n </p>\n </div>\n <div>\n <p className=\"text-text-secondary\">\n {tr('component.languageLabel', 'Language')}\n </p>\n <p className=\"font-medium text-text-primary\">\n {component.language || tr('common.unset', 'Unset')}\n </p>\n </div>\n <div>\n <p className=\"text-text-secondary\">\n {tr('component.frameworkLabel', 'Framework')}\n </p>\n <p className=\"font-medium text-text-primary\">\n {component.framework || tr('common.unset', 'Unset')}\n </p>\n </div>\n <div>\n <p className=\"text-text-secondary\">{tr('component.versionLabel', 'Version')}</p>\n <p className=\"font-medium text-text-primary\">\n {component.version || tr('common.unset', 'Unset')}\n </p>\n </div>\n {component.repositoryUrl && (\n <div className=\"sm:col-span-2\">\n <p className=\"text-text-secondary\">\n {tr('component.repository', 'Repository')}\n </p>\n <a\n href={component.repositoryUrl}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n className=\"font-medium text-action-primary-bg hover:underline break-all\"\n >\n {component.repositoryUrl}\n </a>\n </div>\n )}\n <div>\n <p className=\"text-text-secondary\">{tr('common.created', 'Created')}</p>\n <p className=\"font-medium text-text-primary\">\n {new Date(component.createdAt).toLocaleDateString()}\n </p>\n </div>\n <div>\n <p className=\"text-text-secondary\">{tr('common.updated', 'Updated')}</p>\n <p className=\"font-medium text-text-primary\">\n {new Date(component.updatedAt).toLocaleDateString()}\n </p>\n </div>\n </div>\n </div>\n </Card>\n\n {component.tags?.length > 0 && (\n <Card>\n <div className=\"p-4\">\n <h3 className=\"font-semibold text-text-primary mb-3\">\n {tr('component.tags', 'Tags')}\n </h3>\n <div className=\"flex flex-wrap gap-2\">\n {component.tags.map((tag: string) => (\n <span\n key={tag}\n className=\"px-2.5 py-1 text-sm bg-bg-sunken text-text-secondary rounded-md\"\n >\n {tag}\n </span>\n ))}\n </div>\n </div>\n </Card>\n )}\n </motion.div>\n )}\n\n {activeTab === 'catalogs' && (\n <motion.div\n key=\"catalogs\"\n initial={{ opacity: 0, y: 8 }}\n animate={{ opacity: 1, y: 0 }}\n exit={{ opacity: 0, y: -8 }}\n transition={{ duration: 0.2 }}\n className=\"space-y-4\"\n >\n <div className=\"flex items-center justify-between\">\n <p className=\"text-sm text-text-secondary\">\n {catalogs.length}{' '}\n {tr('component.catalogsContaining', 'catalogs containing this component')}\n </p>\n {canUseCatalogPicker && (\n <button\n type=\"button\"\n onClick={() => setShowAddToCatalog(true)}\n className=\"flex items-center gap-2 px-3 py-2 text-sm bg-action-primary-bg text-action-primary-text rounded-md hover:bg-action-primary-bg-hover transition-colors\"\n >\n <Plus className=\"size-4\" />\n {tr('component.addToCatalog', 'Add to Catalog')}\n </button>\n )}\n </div>\n {catalogs.length > 0 ? (\n <MotionList\n stagger={0.04}\n className=\"grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4\"\n >\n {catalogs.map((catalog) => (\n <MotionItem key={catalog.id}>\n <CatalogCard catalog={catalog} onOpen={handleCatalogClick} />\n </MotionItem>\n ))}\n </MotionList>\n ) : (\n <EmptyState\n icon={<BookOpen className=\"size-8 text-text-secondary\" />}\n title={tr('component.noCatalogs', 'Not in any catalogs')}\n description={tr(\n 'component.noCatalogsDescription',\n 'This component has not been added to any catalogs yet'\n )}\n action={\n canUseCatalogPicker ? (\n <button\n type=\"button\"\n onClick={() => setShowAddToCatalog(true)}\n className=\"flex items-center gap-2 px-4 py-2 bg-action-primary-bg text-action-primary-text rounded-md hover:bg-action-primary-bg-hover transition-colors\"\n >\n <Plus className=\"size-4\" />\n {tr('component.addToCatalog', 'Add to Catalog')}\n </button>\n ) : undefined\n }\n />\n )}\n </motion.div>\n )}\n\n {activeTab === 'scorecard' && (\n <motion.div\n key=\"scorecard\"\n initial={{ opacity: 0, y: 8 }}\n animate={{ opacity: 1, y: 0 }}\n exit={{ opacity: 0, y: -8 }}\n transition={{ duration: 0.2 }}\n className=\"space-y-6\"\n >\n <ScorecardPanel ownerType=\"COMPONENT\" ownerId={id || ''} />\n </motion.div>\n )}\n\n {activeTab === 'docs' && (\n <motion.div\n key=\"docs\"\n initial={{ opacity: 0, y: 8 }}\n animate={{ opacity: 1, y: 0 }}\n exit={{ opacity: 0, y: -8 }}\n transition={{ duration: 0.2 }}\n className=\"space-y-6\"\n >\n <DocsTabPanel ownerType=\"COMPONENT\" ownerId={id || ''} />\n </motion.div>\n )}\n\n {activeTab === 'dependencies' && (\n <motion.div\n key=\"dependencies\"\n initial={{ opacity: 0, y: 8 }}\n animate={{ opacity: 1, y: 0 }}\n exit={{ opacity: 0, y: -8 }}\n transition={{ duration: 0.2 }}\n >\n <DependencyGraphTab\n componentName={component.name}\n componentType={component.type}\n dependencies={deps}\n />\n </motion.div>\n )}\n\n {activeTab === 'template' && (\n <motion.div\n key=\"template\"\n initial={{ opacity: 0, y: 8 }}\n animate={{ opacity: 1, y: 0 }}\n exit={{ opacity: 0, y: -8 }}\n transition={{ duration: 0.2 }}\n className=\"space-y-4\"\n >\n {(component.templates?.length ?? 0) === 0 ? (\n <Card padding=\"lg\">\n <div className=\"flex flex-col items-center justify-center py-12 text-center\">\n <FileCode className=\"size-12 text-text-muted mb-4\" />\n <h3 className=\"text-lg font-semibold text-text-primary mb-2\">\n {tr('component.template', 'Publish as Template')}\n </h3>\n <p className=\"text-sm text-text-secondary max-w-md mb-6\">\n {tr(\n 'component.publishDescription',\n 'Turn this component into a reusable template. Others can clone it and fill in configuration variables to create new components.'\n )}\n </p>\n {canCreateTemplate && (\n <button\n type=\"button\"\n onClick={() => setShowPublishForm(true)}\n className=\"flex items-center gap-2 px-4 py-2 bg-action-primary-bg text-action-primary-text rounded-md hover:bg-action-primary-bg-hover transition-colors\"\n >\n <Upload className=\"size-4\" />\n {tr('component.publishAsTemplate', 'Publish as Template')}\n </button>\n )}\n </div>\n </Card>\n ) : (\n <Card>\n <div className=\"p-4 border-b border-border-default\">\n <h3 className=\"font-semibold text-text-primary\">Published Templates</h3>\n </div>\n <div className=\"p-4 space-y-3\">\n {component.templates.map((tmpl: Record<string, string>) => (\n <div\n key={tmpl.id}\n className=\"flex items-center justify-between p-3 bg-bg-sunken rounded-lg\"\n >\n <div className=\"flex items-center gap-3\">\n <FileCode className=\"size-5 text-action-primary-bg\" />\n <div>\n <p className=\"text-sm font-medium text-text-primary\">{tmpl.name}</p>\n <p className=\"text-xs text-text-muted\">\n {tmpl.category} · v{tmpl.version || '—'}\n </p>\n </div>\n </div>\n <button\n type=\"button\"\n onClick={() => navigate(`/templates/${tmpl.id}`)}\n className=\"text-xs text-action-primary-bg hover:underline\"\n >\n View Template →\n </button>\n </div>\n ))}\n {canCreateTemplate && (\n <button\n type=\"button\"\n onClick={() => setShowPublishForm(true)}\n className=\"flex items-center gap-2 px-3 py-2 text-sm text-text-secondary border border-dashed border-border-default hover:border-border-strong rounded-lg w-full justify-center transition-colors\"\n >\n <Upload className=\"size-4\" />\n Update template\n </button>\n )}\n </div>\n </Card>\n )}\n </motion.div>\n )}\n\n {activeTab === 'settings' && (\n <motion.div\n key=\"settings\"\n initial={{ opacity: 0, y: 8 }}\n animate={{ opacity: 1, y: 0 }}\n exit={{ opacity: 0, y: -8 }}\n transition={{ duration: 0.2 }}\n >\n <div className=\"space-y-4\">\n {canUpdateComponent && (\n <Card padding=\"lg\">\n <h3 className=\"font-semibold text-text-primary mb-4\">\n {tr('component.editComponent', 'Edit Component')}\n </h3>\n <button\n type=\"button\"\n onClick={() => setShowEditForm(true)}\n className=\"px-4 py-2 bg-action-primary-bg text-action-primary-text rounded-md hover:bg-action-primary-bg-hover transition-colors\"\n >\n {tr('component.editDetails', 'Edit Details')}\n </button>\n </Card>\n )}\n {canDeleteComponent && (\n <Card padding=\"lg\">\n <h3 className=\"font-semibold text-status-error-text mb-2\">\n {tr('component.dangerZone', 'Danger Zone')}\n </h3>\n <p className=\"text-sm text-text-secondary mb-4\">\n {tr(\n 'component.deleteWarning',\n 'Deleting this component will remove it from all catalogs and break any templates derived from it. This cannot be undone.'\n )}\n </p>\n <button\n type=\"button\"\n data-testid=\"component-delete-button\"\n onClick={() => setShowDeleteConfirm(true)}\n className=\"inline-flex items-center gap-2 px-4 py-2 bg-status-error-bg text-status-error-text rounded-md hover:opacity-90 transition-opacity\"\n >\n <Trash2 className=\"size-4\" aria-hidden=\"true\" />\n {tr('component.deleteComponent', 'Delete Component')}\n </button>\n </Card>\n )}\n </div>\n </motion.div>\n )}\n </AnimatePresence>\n\n {/* Edit Form */}\n {canUpdateComponent && (\n <ComponentForm\n isOpen={showEditForm}\n onClose={() => setShowEditForm(false)}\n onSubmit={handleUpdateComponent}\n initialData={{\n name: component.name,\n description: component.description || '',\n type: component.type,\n lifecycle: component.lifecycle,\n language: component.language || '',\n framework: component.framework || '',\n version: component.version || '',\n ownerName: component.ownerName || '',\n tags: component.tags || [],\n repositoryUrl: component.repositoryUrl || '',\n documentationUrl: component.documentationUrl || '',\n dependencies: Array.isArray(component.dependencies) ? component.dependencies : null,\n sbomData:\n component.sbomData && typeof component.sbomData === 'object'\n ? (component.sbomData as Record<string, unknown>)\n : null,\n }}\n title={tr('component.editComponent', 'Edit Component')}\n />\n )}\n\n <ConfirmDialog\n isOpen={showDeleteConfirm}\n onClose={() => setShowDeleteConfirm(false)}\n onConfirm={handleDeleteComponent}\n title={tr('component.deleteComponent', 'Delete Component')}\n message={tr(\n 'component.deleteConfirmMessage',\n `Are you sure you want to delete \"${component.name}\"? This action cannot be undone.`\n )}\n confirmLabel={tr('common.delete', 'Delete')}\n variant=\"danger\"\n isLoading={isDeleting}\n />\n\n <QuotaExhaustedDialog\n isOpen={showQuotaExhausted}\n onClose={() => setShowQuotaExhausted(false)}\n onConfirm={() => {\n setShowQuotaExhausted(false);\n navigate('/billing/plans');\n }}\n title=\"Template Quota Reached\"\n message=\"You've reached the maximum number of published templates on your current plan. Upgrade your plan or purchase an add-on to publish more templates.\"\n />\n\n {showPublishForm && canCreateTemplate && (\n <PublishTemplateForm\n componentName={component.name}\n componentRepositoryUrl={component.repositoryUrl}\n onClose={() => setShowPublishForm(false)}\n onSubmit={handlePublishAsTemplate}\n />\n )}\n\n {showAddToCatalog && canUseCatalogPicker && (\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 <motion.div\n initial={{ opacity: 0, scale: 0.95 }}\n animate={{ opacity: 1, scale: 1 }}\n className=\"bg-bg-surface rounded-lg w-full max-w-md shadow-xl\"\n >\n <div className=\"flex items-center justify-between px-4 py-3 border-b border-border-default\">\n <h2 className=\"text-base font-semibold text-text-primary\">\n {tr('component.addToCatalog', 'Add to Catalog')}\n </h2>\n <button\n type=\"button\"\n onClick={() => setShowAddToCatalog(false)}\n className=\"p-1.5 text-text-secondary hover:text-text-primary rounded-md hover:bg-bg-sunken transition-colors\"\n >\n <X className=\"size-4\" />\n </button>\n </div>\n <div className=\"p-2 max-h-80 overflow-y-auto\">\n {(catalogsData?.vibecontrolsCatalogs?.items ?? []).length === 0 ? (\n <p className=\"text-sm text-text-secondary text-center py-6\">\n No catalogs found.{' '}\n <button\n type=\"button\"\n onClick={() => {\n setShowAddToCatalog(false);\n navigate('/catalogs');\n }}\n className=\"text-action-primary-bg hover:underline\"\n >\n Create one first.\n </button>\n </p>\n ) : (\n (catalogsData?.vibecontrolsCatalogs?.items ?? []).map(\n (cat: { id: string; name: string }) => {\n const alreadyIn = catalogs.some((c) => c.id === cat.id);\n return (\n <button\n type=\"button\"\n key={cat.id}\n disabled={alreadyIn}\n onClick={async () => {\n if (!id || alreadyIn) return;\n try {\n await addToCatalog({\n variables: { catalogId: cat.id, componentId: id },\n });\n setShowAddToCatalog(false);\n setSuccessMessage(`Added to ${cat.name}`);\n setTimeout(() => setSuccessMessage(null), 3000);\n } catch {\n // error shown by Apollo\n }\n }}\n className={`flex items-center justify-between w-full px-3 py-2.5 text-left rounded-md transition-colors ${\n alreadyIn\n ? 'opacity-40 cursor-not-allowed'\n : 'hover:bg-bg-sunken cursor-pointer'\n }`}\n >\n <div className=\"flex items-center gap-2\">\n <BookOpen className=\"size-4 text-action-primary-bg flex-shrink-0\" />\n <span className=\"text-sm text-text-primary\">{cat.name}</span>\n </div>\n {alreadyIn && (\n <span className=\"text-xs text-text-muted\">already added</span>\n )}\n </button>\n );\n }\n )\n )}\n </div>\n </motion.div>\n </div>\n )}\n </MotionPage>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"ComponentDetailPage.js","names":[],"sources":["../../src/pages/ComponentDetailPage.tsx"],"sourcesContent":["import { useState, useCallback, useMemo } from 'react';\nimport { useParams } from 'react-router-dom';\nimport { usePermissions } from '@burdenoff/fe-libs/shared/providers/shell';\nimport { VIBE_PERMISSIONS } from '../constants/permissions';\nimport { AnimatePresence, motion } from 'framer-motion';\nimport {\n ArrowLeft,\n Box,\n BookOpen,\n BarChart3,\n FileText,\n Settings,\n RefreshCw,\n FileCode,\n Layers,\n Upload,\n Trash2,\n Tag,\n Plus,\n X,\n Share2,\n} from 'lucide-react';\nimport {\n PageHeader,\n Card,\n StatCard,\n EmptyState,\n ErrorState,\n QuotaExhaustedDialog,\n ConfirmDialog,\n} from '@/components/shared';\nimport { EntityTagPicker } from '@/components/shared/EntityTagPicker';\nimport { PageSkeleton } from '@/components/shared/PageSkeleton';\nimport { MotionPage, MotionList, MotionItem } from '@burdenoff/fe-libs/shared/motion';\nimport { PagePurpose, NextSteps } from '@burdenoff/fe-libs/ui';\n\nimport { DocsTabPanel } from '@/components/docs';\nimport { ScorecardPanel } from '@/components/scorecard';\nimport { CatalogCard } from '@/components/catalog';\nimport type { CatalogCardData } from '@/components/catalog';\nimport { ComponentForm, DependencyGraphTab } from '@/components/idp-components';\nimport type { ComponentFormData } from '@/components/idp-components';\nimport { PublishTemplateForm } from '@/components/templates';\nimport type { PublishTemplateFormData } from '@/components/templates';\nimport { EntityShareButton } from '@/components/sharing';\nimport { useVibeNavigate } from '@/providers/VibeControlsProvider';\nimport { useQueryBackedTab } from '@/hooks/useQueryBackedTab';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { isQuotaExhaustedError, markQuotaHandledLocally } from '@/utils/quotaUtils';\nimport {\n useGetComponentQuery,\n useUpdateComponentMutation,\n useDeleteComponentMutation,\n usePublishComponentAsTemplateMutation,\n useAddComponentToCatalogMutation,\n useListCatalogsQuery,\n} from '@/generated/wspace-operations';\nimport type { VibecontrolsTemplateCategory } from '@/generated/wspace-types';\n\ntype ComponentTab =\n 'overview' | 'catalogs' | 'scorecard' | 'docs' | 'dependencies' | 'template' | 'settings';\nconst COMPONENT_TABS: readonly ComponentTab[] = [\n 'overview',\n 'catalogs',\n 'scorecard',\n 'docs',\n 'dependencies',\n 'template',\n 'settings',\n];\n\nexport function ComponentDetailPage() {\n const { t } = useI18n();\n const { id } = useParams<{ id: string }>();\n const navigate = useVibeNavigate();\n const { hasPermission, isLoading: permissionsLoading } = usePermissions();\n const canReadComponent = hasPermission(VIBE_PERMISSIONS.COMPONENT_READ);\n const canUpdateComponent = hasPermission(VIBE_PERMISSIONS.COMPONENT_UPDATE);\n const canDeleteComponent = hasPermission(VIBE_PERMISSIONS.COMPONENT_DELETE);\n const canReadCatalog = hasPermission(VIBE_PERMISSIONS.CATALOG_READ);\n const canUpdateCatalog = hasPermission(VIBE_PERMISSIONS.CATALOG_UPDATE);\n const canUseCatalogPicker = canReadCatalog && canUpdateCatalog;\n const canCreateTemplate = hasPermission(VIBE_PERMISSIONS.TEMPLATE_CREATE);\n\n const tr = useCallback(\n (key: string, fallback: string): string => {\n const value = t(key);\n return value === key ? fallback : value;\n },\n [t]\n );\n\n const [activeTab, setActiveTab] = useQueryBackedTab<ComponentTab>(COMPONENT_TABS, 'overview');\n const [showEditForm, setShowEditForm] = useState(false);\n const [showDeleteConfirm, setShowDeleteConfirm] = useState(false);\n const [isDeleting, setIsDeleting] = useState(false);\n const [successMessage, setSuccessMessage] = useState<string | null>(null);\n const [showQuotaExhausted, setShowQuotaExhausted] = useState(false);\n const [showPublishForm, setShowPublishForm] = useState(false);\n const [showAddToCatalog, setShowAddToCatalog] = useState(false);\n\n const { data, loading, error, refetch } = useGetComponentQuery({\n variables: { id: id || '' },\n skip: !id || permissionsLoading || !canReadComponent,\n fetchPolicy: 'cache-and-network',\n });\n\n const [updateComponent] = useUpdateComponentMutation({ refetchQueries: ['GetComponent'] });\n const [deleteComponent] = useDeleteComponentMutation({ refetchQueries: ['ListComponents'] });\n const [publishAsTemplate] = usePublishComponentAsTemplateMutation({\n refetchQueries: ['GetComponent'],\n });\n const [addToCatalog] = useAddComponentToCatalogMutation({ refetchQueries: ['GetComponent'] });\n const { data: catalogsData } = useListCatalogsQuery({\n variables: { pagination: { limit: 100, offset: 0 } },\n skip: !showAddToCatalog || !canUseCatalogPicker,\n fetchPolicy: 'cache-and-network',\n });\n\n const component = data?.vibecontrolsComponent ?? null;\n\n const catalogs: CatalogCardData[] = useMemo(() => {\n return (component?.catalogs ?? []).map((entry: Record<string, any>) => {\n const c = entry.catalog || {};\n return {\n id: c.id as string,\n name: c.name as string,\n description: (c.description as string) || null,\n visibility: c.visibility as CatalogCardData['visibility'],\n componentCount: (c.componentCount as number) || 0,\n vibeCount: (c.vibeCount as number) || 0,\n tags: (c.tags as string[]) || [],\n createdAt: c.createdAt as string,\n updatedAt: c.updatedAt as string,\n };\n });\n }, [component?.catalogs]);\n\n if (permissionsLoading)\n return (\n <div className=\"flex items-center justify-center min-h-[400px]\">\n <p className=\"text-text-secondary\">Loading permissions…</p>\n </div>\n );\n if (!canReadComponent)\n return (\n <div className=\"flex items-center justify-center min-h-[400px]\">\n <div className=\"text-center space-y-2\">\n <h2 className=\"text-lg font-semibold text-text-primary\">Access Denied</h2>\n <p className=\"text-sm text-text-secondary\">\n You don't have permission to access this page.\n </p>\n </div>\n </div>\n );\n\n const handleBack = () => {\n navigate('/components');\n };\n\n const handleCatalogClick = (catalog: CatalogCardData) => {\n navigate(`/catalogs/${catalog.id}`);\n };\n\n const handleUpdateComponent = async (formData: ComponentFormData) => {\n if (!id || !canUpdateComponent) return;\n await updateComponent({\n variables: {\n id,\n input: {\n name: formData.name,\n description: formData.description || undefined,\n type: formData.type,\n lifecycle: formData.lifecycle,\n language: formData.language || undefined,\n framework: formData.framework || undefined,\n version: formData.version || undefined,\n ownerName: formData.ownerName || undefined,\n tags: formData.tags.length > 0 ? formData.tags : undefined,\n repositoryUrl: formData.repositoryUrl || undefined,\n documentationUrl: formData.documentationUrl || undefined,\n dependencies: formData.dependencies ?? undefined,\n sbomData: formData.sbomData ?? undefined,\n },\n },\n });\n setSuccessMessage(tr('component.updatedSuccess', 'Component updated successfully'));\n setTimeout(() => setSuccessMessage(null), 3000);\n };\n\n const handleDeleteComponent = async () => {\n if (!id) return;\n setIsDeleting(true);\n try {\n await deleteComponent({ variables: { id } });\n setShowDeleteConfirm(false);\n navigate('/components');\n } finally {\n setIsDeleting(false);\n }\n };\n\n const handlePublishAsTemplate = async (formData: PublishTemplateFormData) => {\n if (!id || !canCreateTemplate) return;\n try {\n const res = await publishAsTemplate({\n variables: {\n componentId: id,\n input: {\n name: formData.name,\n description: formData.description || undefined,\n category: formData.category as VibecontrolsTemplateCategory,\n version: formData.version || undefined,\n setupInstructions: formData.setupInstructions || undefined,\n variables: formData.variables.length > 0 ? formData.variables : undefined,\n sourceRepositoryUrl: formData.sourceRepositoryUrl,\n },\n },\n });\n if (res.error && isQuotaExhaustedError(res.error)) {\n setShowPublishForm(false);\n setShowQuotaExhausted(true);\n return;\n }\n setShowPublishForm(false);\n setSuccessMessage(tr('component.publishedAsTemplate', 'Published as template successfully'));\n setTimeout(() => setSuccessMessage(null), 3000);\n } catch (err) {\n if (isQuotaExhaustedError(err)) {\n setShowPublishForm(false);\n markQuotaHandledLocally();\n setShowQuotaExhausted(true);\n return;\n }\n throw err;\n }\n };\n\n const lifecycleStyles: Record<string, string> = {\n EXPERIMENTAL: 'bg-status-warning-bg text-status-warning-text',\n ACTIVE: 'bg-status-success-bg text-status-success-text',\n DEPRECATED: 'bg-status-error-bg text-status-error-text',\n END_OF_LIFE: 'bg-status-error-bg text-status-error-text',\n };\n\n const deps = Array.isArray(component?.dependencies)\n ? (component.dependencies as Array<{\n name: string;\n version?: string | null;\n type?: string | null;\n }>)\n : [];\n\n const tabs: { id: ComponentTab; label: string; icon: typeof Box; badge?: string }[] = [\n { id: 'overview', label: tr('nav.overview', 'Overview'), icon: Box },\n {\n id: 'catalogs',\n label: tr('component.catalogs', 'Catalogs'),\n icon: BookOpen,\n badge: `${catalogs.length}`,\n },\n { id: 'scorecard', label: tr('component.scorecard', 'Scorecard'), icon: BarChart3 },\n { id: 'docs', label: tr('component.docs', 'Docs'), icon: FileText },\n {\n id: 'dependencies',\n label: tr('component.dependencies', 'Dependencies'),\n icon: Share2,\n badge: deps.length > 0 ? `${deps.length}` : undefined,\n },\n { id: 'template', label: tr('component.template', 'Template'), icon: FileCode },\n { id: 'settings', label: tr('common.settings', 'Settings'), icon: Settings },\n ];\n\n // Guard states\n if (loading && !component) {\n return (\n <MotionPage className=\"space-y-6 px-6 lg:px-8 py-6 bg-bg-canvas min-h-screen\">\n <button\n type=\"button\"\n onClick={handleBack}\n className=\"flex items-center gap-2 text-text-secondary hover:text-text-primary transition-colors\"\n >\n <ArrowLeft className=\"size-4\" />\n {tr('component.backToComponents', 'Back to Components')}\n </button>\n <PageSkeleton variant=\"detail\" />\n </MotionPage>\n );\n }\n\n if (error && !component) {\n return (\n <MotionPage className=\"space-y-6 px-6 lg:px-8 py-6 bg-bg-canvas min-h-screen\">\n <button\n type=\"button\"\n onClick={handleBack}\n className=\"flex items-center gap-2 text-text-secondary hover:text-text-primary transition-colors\"\n >\n <ArrowLeft className=\"size-4\" />\n {tr('component.backToComponents', 'Back to Components')}\n </button>\n <ErrorState\n title={tr('component.failedLoad', 'Failed to load component')}\n message={error.message}\n onRetry={() => refetch()}\n />\n </MotionPage>\n );\n }\n\n if (!component) {\n return (\n <MotionPage className=\"space-y-6 px-6 lg:px-8 py-6 bg-bg-canvas min-h-screen\">\n <button\n type=\"button\"\n onClick={handleBack}\n className=\"flex items-center gap-2 text-text-secondary hover:text-text-primary transition-colors\"\n >\n <ArrowLeft className=\"size-4\" />\n {tr('component.backToComponents', 'Back to Components')}\n </button>\n <EmptyState\n icon={<Box className=\"size-8 text-text-secondary\" />}\n title={tr('component.notFound', 'Component not found')}\n description={`No component with ID \"${id}\" exists`}\n action={\n <button\n type=\"button\"\n onClick={handleBack}\n className=\"px-4 py-2 bg-action-primary-bg text-action-primary-text rounded-md hover:bg-action-primary-bg-hover transition-colors\"\n >\n {tr('component.backToComponents', 'Back to Components')}\n </button>\n }\n />\n </MotionPage>\n );\n }\n\n return (\n <MotionPage className=\"space-y-6 px-6 lg:px-8 py-6 bg-bg-canvas min-h-screen\">\n {/* Success Toast */}\n {successMessage && (\n <div className=\"fixed top-4 left-4 right-4 sm:left-auto z-50 bg-status-success-bg text-status-success-text px-4 py-3 rounded-md shadow-lg\">\n {successMessage}\n </div>\n )}\n\n {/* Back Button */}\n <button\n type=\"button\"\n onClick={handleBack}\n className=\"flex items-center gap-2 text-text-secondary hover:text-text-primary transition-colors\"\n >\n <ArrowLeft className=\"size-4\" />\n {tr('component.backToComponents', 'Back to Components')}\n </button>\n\n {/* Header */}\n <PageHeader\n title={component.name}\n description={component.description || tr('component.noDescription', 'No description')}\n actions={\n <div className=\"flex items-center gap-2 flex-wrap\">\n <EntityShareButton\n entity=\"component\"\n resource={{ id: component.id, name: component.name }}\n label={tr('component.share', 'Share')}\n variant=\"button\"\n />\n <button\n type=\"button\"\n onClick={() => refetch()}\n disabled={loading}\n className=\"flex items-center justify-center p-2 bg-bg-sunken text-text-primary rounded-md hover:bg-action-secondary-bg-hover transition-colors disabled:opacity-50\"\n title={tr('common.refresh', 'Refresh')}\n >\n <RefreshCw className={`size-4 ${loading ? 'animate-spin' : ''}`} />\n </button>\n <span className=\"inline-flex items-center px-2.5 py-1 text-xs font-medium bg-bg-sunken text-text-secondary rounded\">\n {component.type}\n </span>\n <span\n className={`inline-flex items-center px-2.5 py-1 text-xs font-medium rounded ${lifecycleStyles[component.lifecycle] || 'bg-bg-sunken text-text-secondary'}`}\n >\n {component.lifecycle}\n </span>\n </div>\n }\n />\n\n <PagePurpose icon={<Box className=\"size-4\" />}>\n {tr(\n 'componentDetail.purpose',\n 'This is the record for one component — a single service, app, library or API in your system. From here you see its lifecycle and ownership, the catalogs it belongs to, its dependencies, docs and scorecard, and you can publish it as a reusable template. Come here to understand what something is, who owns it, and how healthy it is.'\n )}\n </PagePurpose>\n\n <NextSteps\n storageKey=\"vibecontrols-component-details\"\n steps={[\n {\n id: 'view-dependencies',\n label: tr('componentDetail.next.dependencies', 'Map its dependencies'),\n description: tr(\n 'componentDetail.next.dependenciesDesc',\n 'See what this component depends on and what depends on it.'\n ),\n icon: <Share2 className=\"size-4\" />,\n onClick: () => setActiveTab('dependencies'),\n },\n {\n id: 'check-scorecard',\n label: tr('componentDetail.next.scorecard', 'Review the scorecard'),\n description: tr(\n 'componentDetail.next.scorecardDesc',\n 'Check health and best-practice scores for this component.'\n ),\n icon: <BarChart3 className=\"size-4\" />,\n onClick: () => setActiveTab('scorecard'),\n },\n {\n id: 'publish-template',\n label: tr('componentDetail.next.template', 'Publish as a template'),\n description: tr(\n 'componentDetail.next.templateDesc',\n 'Turn this setup into a reusable blueprint for new components.'\n ),\n icon: <FileCode className=\"size-4\" />,\n onClick: () => setActiveTab('template'),\n },\n ]}\n />\n\n {/* Stat Cards */}\n <MotionList stagger={0.04} className=\"grid grid-cols-2 sm:grid-cols-4 gap-3 sm:gap-4\">\n <MotionItem>\n <StatCard\n title={tr('component.type', 'Type')}\n value={component.type}\n icon={<Box className=\"size-4\" />}\n />\n </MotionItem>\n <MotionItem>\n <StatCard\n title={tr('component.lifecycle', 'Lifecycle')}\n value={component.lifecycle}\n icon={<Layers className=\"size-4\" />}\n variant={\n component.lifecycle === 'ACTIVE'\n ? 'success'\n : component.lifecycle === 'DEPRECATED' || component.lifecycle === 'END_OF_LIFE'\n ? 'error'\n : 'warning'\n }\n />\n </MotionItem>\n <MotionItem>\n <StatCard\n title={tr('component.version', 'Version')}\n value={component.version || '-'}\n icon={<FileCode className=\"size-4\" />}\n />\n </MotionItem>\n <MotionItem>\n <StatCard\n title={tr('component.catalogs', 'Catalogs')}\n value={catalogs.length}\n icon={<BookOpen className=\"size-4\" />}\n />\n </MotionItem>\n </MotionList>\n\n {/* Universal tags */}\n <Card>\n <div className=\"p-4 border-b border-border-default flex items-center gap-2\">\n <Tag className=\"size-5\" />\n <h3 className=\"font-semibold text-text-primary\">{tr('component.tags', 'Tags')}</h3>\n </div>\n <div className=\"p-4\">\n <EntityTagPicker entityType=\"VIBECONTROLS_COMPONENT\" entityId={component.id} />\n </div>\n </Card>\n\n {/* Tab Navigation */}\n <div className=\"border-b border-border-default\">\n <nav\n className=\"flex gap-1 -mb-px overflow-x-auto pb-1\"\n aria-label={tr('component.tabsAriaLabel', 'Component tabs')}\n >\n {tabs.map((tab) => {\n const Icon = tab.icon;\n const isActiveTab = activeTab === tab.id;\n return (\n <button\n type=\"button\"\n key={tab.id}\n onClick={() => setActiveTab(tab.id)}\n className={`flex shrink-0 items-center gap-1.5 px-3 py-2.5 text-xs sm:gap-2 sm:px-4 sm:text-sm font-medium border-b-2 transition-colors whitespace-nowrap ${\n isActiveTab\n ? 'border-action-primary-bg text-action-primary-text'\n : 'border-transparent text-text-secondary hover:text-text-primary hover:border-border-default'\n }`}\n aria-selected={isActiveTab}\n role=\"tab\"\n >\n <Icon className=\"size-4\" />\n {tab.label}\n {tab.badge && (\n <span\n className={`hidden rounded-full px-1.5 py-0.5 text-[10px] sm:inline-flex sm:text-xs ${\n isActiveTab\n ? 'bg-[var(--color-accent-soft)] text-text-primary'\n : 'bg-bg-sunken text-text-muted'\n }`}\n >\n {tab.badge}\n </span>\n )}\n </button>\n );\n })}\n </nav>\n </div>\n\n {/* Tab Panels */}\n <AnimatePresence mode=\"wait\">\n {activeTab === 'overview' && (\n <motion.div\n key=\"overview\"\n initial={{ opacity: 0, y: 8 }}\n animate={{ opacity: 1, y: 0 }}\n exit={{ opacity: 0, y: -8 }}\n transition={{ duration: 0.2 }}\n className=\"space-y-6\"\n >\n {component.description && (\n <Card>\n <div className=\"p-4\">\n <p className=\"text-text-primary\">{component.description}</p>\n </div>\n </Card>\n )}\n\n <Card>\n <div className=\"p-4 border-b border-border-default\">\n <h3 className=\"font-semibold text-text-primary\">\n {tr('component.metadata', 'Metadata')}\n </h3>\n </div>\n <div className=\"p-4\">\n <div className=\"grid grid-cols-1 sm:grid-cols-2 gap-4 text-sm\">\n <div>\n <p className=\"text-text-secondary\">{tr('component.ownerNameLabel', 'Owner')}</p>\n <p className=\"font-medium text-text-primary\">\n {component.ownerName || tr('common.unset', 'Unset')}\n </p>\n </div>\n <div>\n <p className=\"text-text-secondary\">\n {tr('component.languageLabel', 'Language')}\n </p>\n <p className=\"font-medium text-text-primary\">\n {component.language || tr('common.unset', 'Unset')}\n </p>\n </div>\n <div>\n <p className=\"text-text-secondary\">\n {tr('component.frameworkLabel', 'Framework')}\n </p>\n <p className=\"font-medium text-text-primary\">\n {component.framework || tr('common.unset', 'Unset')}\n </p>\n </div>\n <div>\n <p className=\"text-text-secondary\">{tr('component.versionLabel', 'Version')}</p>\n <p className=\"font-medium text-text-primary\">\n {component.version || tr('common.unset', 'Unset')}\n </p>\n </div>\n {component.repositoryUrl && (\n <div className=\"sm:col-span-2\">\n <p className=\"text-text-secondary\">\n {tr('component.repository', 'Repository')}\n </p>\n <a\n href={component.repositoryUrl}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n className=\"font-medium text-action-primary-bg hover:underline break-all\"\n >\n {component.repositoryUrl}\n </a>\n </div>\n )}\n <div>\n <p className=\"text-text-secondary\">{tr('common.created', 'Created')}</p>\n <p className=\"font-medium text-text-primary\">\n {new Date(component.createdAt).toLocaleDateString()}\n </p>\n </div>\n <div>\n <p className=\"text-text-secondary\">{tr('common.updated', 'Updated')}</p>\n <p className=\"font-medium text-text-primary\">\n {new Date(component.updatedAt).toLocaleDateString()}\n </p>\n </div>\n </div>\n </div>\n </Card>\n\n {component.tags?.length > 0 && (\n <Card>\n <div className=\"p-4\">\n <h3 className=\"font-semibold text-text-primary mb-3\">\n {tr('component.tags', 'Tags')}\n </h3>\n <div className=\"flex flex-wrap gap-2\">\n {component.tags.map((tag: string) => (\n <span\n key={tag}\n className=\"px-2.5 py-1 text-sm bg-bg-sunken text-text-secondary rounded-md\"\n >\n {tag}\n </span>\n ))}\n </div>\n </div>\n </Card>\n )}\n </motion.div>\n )}\n\n {activeTab === 'catalogs' && (\n <motion.div\n key=\"catalogs\"\n initial={{ opacity: 0, y: 8 }}\n animate={{ opacity: 1, y: 0 }}\n exit={{ opacity: 0, y: -8 }}\n transition={{ duration: 0.2 }}\n className=\"space-y-4\"\n >\n <div className=\"flex items-center justify-between\">\n <p className=\"text-sm text-text-secondary\">\n {catalogs.length}{' '}\n {tr('component.catalogsContaining', 'catalogs containing this component')}\n </p>\n {canUseCatalogPicker && (\n <button\n type=\"button\"\n onClick={() => setShowAddToCatalog(true)}\n className=\"flex items-center gap-2 px-3 py-2 text-sm bg-action-primary-bg text-action-primary-text rounded-md hover:bg-action-primary-bg-hover transition-colors\"\n >\n <Plus className=\"size-4\" />\n {tr('component.addToCatalog', 'Add to Catalog')}\n </button>\n )}\n </div>\n {catalogs.length > 0 ? (\n <MotionList\n stagger={0.04}\n className=\"grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4\"\n >\n {catalogs.map((catalog) => (\n <MotionItem key={catalog.id}>\n <CatalogCard catalog={catalog} onOpen={handleCatalogClick} />\n </MotionItem>\n ))}\n </MotionList>\n ) : (\n <EmptyState\n icon={<BookOpen className=\"size-8 text-text-secondary\" />}\n title={tr('component.noCatalogs', 'Not in any catalogs')}\n description={tr(\n 'component.noCatalogsDescription',\n 'This component has not been added to any catalogs yet'\n )}\n action={\n canUseCatalogPicker ? (\n <button\n type=\"button\"\n onClick={() => setShowAddToCatalog(true)}\n className=\"flex items-center gap-2 px-4 py-2 bg-action-primary-bg text-action-primary-text rounded-md hover:bg-action-primary-bg-hover transition-colors\"\n >\n <Plus className=\"size-4\" />\n {tr('component.addToCatalog', 'Add to Catalog')}\n </button>\n ) : undefined\n }\n />\n )}\n </motion.div>\n )}\n\n {activeTab === 'scorecard' && (\n <motion.div\n key=\"scorecard\"\n initial={{ opacity: 0, y: 8 }}\n animate={{ opacity: 1, y: 0 }}\n exit={{ opacity: 0, y: -8 }}\n transition={{ duration: 0.2 }}\n className=\"space-y-6\"\n >\n <ScorecardPanel ownerType=\"COMPONENT\" ownerId={id || ''} />\n </motion.div>\n )}\n\n {activeTab === 'docs' && (\n <motion.div\n key=\"docs\"\n initial={{ opacity: 0, y: 8 }}\n animate={{ opacity: 1, y: 0 }}\n exit={{ opacity: 0, y: -8 }}\n transition={{ duration: 0.2 }}\n className=\"space-y-6\"\n >\n <DocsTabPanel ownerType=\"COMPONENT\" ownerId={id || ''} />\n </motion.div>\n )}\n\n {activeTab === 'dependencies' && (\n <motion.div\n key=\"dependencies\"\n initial={{ opacity: 0, y: 8 }}\n animate={{ opacity: 1, y: 0 }}\n exit={{ opacity: 0, y: -8 }}\n transition={{ duration: 0.2 }}\n >\n <DependencyGraphTab\n componentName={component.name}\n componentType={component.type}\n dependencies={deps}\n />\n </motion.div>\n )}\n\n {activeTab === 'template' && (\n <motion.div\n key=\"template\"\n initial={{ opacity: 0, y: 8 }}\n animate={{ opacity: 1, y: 0 }}\n exit={{ opacity: 0, y: -8 }}\n transition={{ duration: 0.2 }}\n className=\"space-y-4\"\n >\n {(component.templates?.length ?? 0) === 0 ? (\n <Card padding=\"lg\">\n <div className=\"flex flex-col items-center justify-center py-12 text-center\">\n <FileCode className=\"size-12 text-text-muted mb-4\" />\n <h3 className=\"text-lg font-semibold text-text-primary mb-2\">\n {tr('component.template', 'Publish as Template')}\n </h3>\n <p className=\"text-sm text-text-secondary max-w-md mb-6\">\n {tr(\n 'component.publishDescription',\n 'Turn this component into a reusable template. Others can clone it and fill in configuration variables to create new components.'\n )}\n </p>\n {canCreateTemplate && (\n <button\n type=\"button\"\n onClick={() => setShowPublishForm(true)}\n className=\"flex items-center gap-2 px-4 py-2 bg-action-primary-bg text-action-primary-text rounded-md hover:bg-action-primary-bg-hover transition-colors\"\n >\n <Upload className=\"size-4\" />\n {tr('component.publishAsTemplate', 'Publish as Template')}\n </button>\n )}\n </div>\n </Card>\n ) : (\n <Card>\n <div className=\"p-4 border-b border-border-default\">\n <h3 className=\"font-semibold text-text-primary\">Published Templates</h3>\n </div>\n <div className=\"p-4 space-y-3\">\n {component.templates.map((tmpl: Record<string, string>) => (\n <div\n key={tmpl.id}\n className=\"flex items-center justify-between p-3 bg-bg-sunken rounded-lg\"\n >\n <div className=\"flex items-center gap-3\">\n <FileCode className=\"size-5 text-action-primary-bg\" />\n <div>\n <p className=\"text-sm font-medium text-text-primary\">{tmpl.name}</p>\n <p className=\"text-xs text-text-muted\">\n {tmpl.category} · v{tmpl.version || '—'}\n </p>\n </div>\n </div>\n <button\n type=\"button\"\n onClick={() => navigate(`/templates/${tmpl.id}`)}\n className=\"text-xs text-action-primary-bg hover:underline\"\n >\n View Template →\n </button>\n </div>\n ))}\n {canCreateTemplate && (\n <button\n type=\"button\"\n onClick={() => setShowPublishForm(true)}\n className=\"flex items-center gap-2 px-3 py-2 text-sm text-text-secondary border border-dashed border-border-default hover:border-border-strong rounded-lg w-full justify-center transition-colors\"\n >\n <Upload className=\"size-4\" />\n Update template\n </button>\n )}\n </div>\n </Card>\n )}\n </motion.div>\n )}\n\n {activeTab === 'settings' && (\n <motion.div\n key=\"settings\"\n initial={{ opacity: 0, y: 8 }}\n animate={{ opacity: 1, y: 0 }}\n exit={{ opacity: 0, y: -8 }}\n transition={{ duration: 0.2 }}\n >\n <div className=\"space-y-4\">\n {canUpdateComponent && (\n <Card padding=\"lg\">\n <h3 className=\"font-semibold text-text-primary mb-4\">\n {tr('component.editComponent', 'Edit Component')}\n </h3>\n <button\n type=\"button\"\n onClick={() => setShowEditForm(true)}\n className=\"px-4 py-2 bg-action-primary-bg text-action-primary-text rounded-md hover:bg-action-primary-bg-hover transition-colors\"\n >\n {tr('component.editDetails', 'Edit Details')}\n </button>\n </Card>\n )}\n {canDeleteComponent && (\n <Card padding=\"lg\">\n <h3 className=\"font-semibold text-status-error-text mb-2\">\n {tr('component.dangerZone', 'Danger Zone')}\n </h3>\n <p className=\"text-sm text-text-secondary mb-4\">\n {tr(\n 'component.deleteWarning',\n 'Deleting this component will remove it from all catalogs and break any templates derived from it. This cannot be undone.'\n )}\n </p>\n <button\n type=\"button\"\n data-testid=\"component-delete-button\"\n onClick={() => setShowDeleteConfirm(true)}\n className=\"inline-flex items-center gap-2 px-4 py-2 bg-status-error-bg text-status-error-text rounded-md hover:opacity-90 transition-opacity\"\n >\n <Trash2 className=\"size-4\" aria-hidden=\"true\" />\n {tr('component.deleteComponent', 'Delete Component')}\n </button>\n </Card>\n )}\n </div>\n </motion.div>\n )}\n </AnimatePresence>\n\n {/* Edit Form */}\n {canUpdateComponent && (\n <ComponentForm\n isOpen={showEditForm}\n onClose={() => setShowEditForm(false)}\n onSubmit={handleUpdateComponent}\n initialData={{\n name: component.name,\n description: component.description || '',\n type: component.type,\n lifecycle: component.lifecycle,\n language: component.language || '',\n framework: component.framework || '',\n version: component.version || '',\n ownerName: component.ownerName || '',\n tags: component.tags || [],\n repositoryUrl: component.repositoryUrl || '',\n documentationUrl: component.documentationUrl || '',\n dependencies: Array.isArray(component.dependencies) ? component.dependencies : null,\n sbomData:\n component.sbomData && typeof component.sbomData === 'object'\n ? (component.sbomData as Record<string, unknown>)\n : null,\n }}\n title={tr('component.editComponent', 'Edit Component')}\n />\n )}\n\n <ConfirmDialog\n isOpen={showDeleteConfirm}\n onClose={() => setShowDeleteConfirm(false)}\n onConfirm={handleDeleteComponent}\n title={tr('component.deleteComponent', 'Delete Component')}\n message={tr(\n 'component.deleteConfirmMessage',\n `Are you sure you want to delete \"${component.name}\"? This action cannot be undone.`\n )}\n confirmLabel={tr('common.delete', 'Delete')}\n variant=\"danger\"\n isLoading={isDeleting}\n />\n\n <QuotaExhaustedDialog\n isOpen={showQuotaExhausted}\n onClose={() => setShowQuotaExhausted(false)}\n onConfirm={() => {\n setShowQuotaExhausted(false);\n navigate('/billing/plans');\n }}\n title=\"Template Quota Reached\"\n message=\"You've reached the maximum number of published templates on your current plan. Upgrade your plan or purchase an add-on to publish more templates.\"\n />\n\n {showPublishForm && canCreateTemplate && (\n <PublishTemplateForm\n componentName={component.name}\n componentRepositoryUrl={component.repositoryUrl}\n onClose={() => setShowPublishForm(false)}\n onSubmit={handlePublishAsTemplate}\n />\n )}\n\n {showAddToCatalog && canUseCatalogPicker && (\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 <motion.div\n initial={{ opacity: 0, scale: 0.95 }}\n animate={{ opacity: 1, scale: 1 }}\n className=\"bg-bg-surface rounded-lg w-full max-w-md shadow-xl\"\n >\n <div className=\"flex items-center justify-between px-4 py-3 border-b border-border-default\">\n <h2 className=\"text-base font-semibold text-text-primary\">\n {tr('component.addToCatalog', 'Add to Catalog')}\n </h2>\n <button\n type=\"button\"\n onClick={() => setShowAddToCatalog(false)}\n className=\"p-1.5 text-text-secondary hover:text-text-primary rounded-md hover:bg-bg-sunken transition-colors\"\n >\n <X className=\"size-4\" />\n </button>\n </div>\n <div className=\"p-2 max-h-80 overflow-y-auto\">\n {(catalogsData?.vibecontrolsCatalogs?.items ?? []).length === 0 ? (\n <p className=\"text-sm text-text-secondary text-center py-6\">\n No catalogs found.{' '}\n <button\n type=\"button\"\n onClick={() => {\n setShowAddToCatalog(false);\n navigate('/catalogs');\n }}\n className=\"text-action-primary-bg hover:underline\"\n >\n Create one first.\n </button>\n </p>\n ) : (\n (catalogsData?.vibecontrolsCatalogs?.items ?? []).map(\n (cat: { id: string; name: string }) => {\n const alreadyIn = catalogs.some((c) => c.id === cat.id);\n return (\n <button\n type=\"button\"\n key={cat.id}\n disabled={alreadyIn}\n onClick={async () => {\n if (!id || alreadyIn) return;\n try {\n await addToCatalog({\n variables: { catalogId: cat.id, componentId: id },\n });\n setShowAddToCatalog(false);\n setSuccessMessage(`Added to ${cat.name}`);\n setTimeout(() => setSuccessMessage(null), 3000);\n } catch {\n // error shown by Apollo\n }\n }}\n className={`flex items-center justify-between w-full px-3 py-2.5 text-left rounded-md transition-colors ${\n alreadyIn\n ? 'opacity-40 cursor-not-allowed'\n : 'hover:bg-bg-sunken cursor-pointer'\n }`}\n >\n <div className=\"flex items-center gap-2\">\n <BookOpen className=\"size-4 text-action-primary-bg flex-shrink-0\" />\n <span className=\"text-sm text-text-primary\">{cat.name}</span>\n </div>\n {alreadyIn && (\n <span className=\"text-xs text-text-muted\">already added</span>\n )}\n </button>\n );\n }\n )\n )}\n </div>\n </motion.div>\n </div>\n )}\n </MotionPage>\n );\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ScheduledJobDetailPage.js","names":[],"sources":["../../src/pages/ScheduledJobDetailPage.tsx"],"sourcesContent":["import { useCallback, useEffect, useRef, useState } from 'react';\nimport { useParams, useNavigate, useSearchParams } from 'react-router-dom';\nimport { useTenantContext } from '@/hooks/useTenantContext';\nimport { useVibeNavigate } from '@/providers/VibeControlsProvider';\nimport { usePermissions } from '@burdenoff/fe-libs/shared/providers/shell';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { ArrowLeft, Play, Loader2, Eye, AlertCircle, X } from 'lucide-react';\nimport { MotionPage, MotionList, MotionItem } from '@burdenoff/fe-libs/shared/motion';\nimport { LogsDrawer } from '../components/vibecalendar/LogsDrawer';\nimport { STATUS_COLORS } from '../components/vibecalendar/types';\nimport type { VibeCalendarExecution } from '../components/vibecalendar/types';\nimport { VIBE_PERMISSIONS } from '../constants/permissions';\nimport {\n useExecuteScheduledJobNowMutation,\n useGetScheduledJobDetailQuery,\n} from '@/generated/wspace-operations';\n\n// INTERNAL_API jobs persist the worker envelope as\n// `{ statusCode, duration, responseData: { executeAction: { output,\n// exitCode, status, errorMessage } } }`. `adaptExecution` unwraps\n// that into the flat shape the LogsDrawer consumes.\ntype JobExecutionShape = {\n id: string;\n status?: string | null;\n startedAt?: string | null;\n completedAt?: string | null;\n output?: unknown;\n errorMessage?: string | null;\n statusCode?: number | null;\n};\nfunction adaptExecution(\n ex: JobExecutionShape,\n canReadExecutionLogs: boolean\n): VibeCalendarExecution {\n let stdout: string | null = null;\n let exitCode: number | null = ex.statusCode ?? null;\n // Nested action errors are the only failure signal on agent-exit\n // failures (top-level `errorMessage` only fires for scheduler-side\n // dispatch failures), so capture both and let the chain below\n // prefer the top-level when present.\n let nestedActionError: string | null = null;\n if (typeof ex.output === 'string') {\n stdout = ex.output;\n } else if (ex.output && typeof ex.output === 'object') {\n const envelope = ex.output as Record<string, unknown>;\n const action = (envelope.responseData as Record<string, unknown> | undefined)?.executeAction as\n | Record<string, unknown>\n | undefined;\n if (action && typeof action.output === 'string') {\n stdout = action.output;\n }\n if (action && typeof action.exitCode === 'number') {\n exitCode = action.exitCode;\n }\n if (action && typeof action.errorMessage === 'string' && action.errorMessage.length > 0) {\n nestedActionError = action.errorMessage;\n }\n if (stdout === null) {\n stdout = JSON.stringify(envelope, null, 2);\n }\n }\n // `??` only falls back for null/undefined; an empty top-level\n // string would otherwise mask a populated nested error.\n const topErrorTrimmed =\n typeof ex.errorMessage === 'string' && ex.errorMessage.trim().length > 0\n ? ex.errorMessage\n : null;\n return {\n id: ex.id,\n taskId: '',\n status: ex.status ?? 'UNKNOWN',\n // Suppress raw envelope fields for callers without `log:read`.\n output: canReadExecutionLogs ? stdout : null,\n exitCode,\n webhookStatusCode: null,\n startedAt: ex.startedAt ?? null,\n completedAt: ex.completedAt ?? null,\n error: canReadExecutionLogs ? (topErrorTrimmed ?? nestedActionError ?? null) : null,\n };\n}\n\n// In-product detail view for a wspace-scheduler-svc ScheduledJob.\n// Lives next to VibeTaskDetailPage but reads/executes through the\n// scheduler subgraph (different id space, different RBAC resource).\nexport function ScheduledJobDetailPage() {\n const { t } = useI18n();\n const tr = useCallback(\n (key: string, fallback: string): string => {\n const v = t(key);\n return v === key ? fallback : v;\n },\n [t]\n );\n const { jobId } = useParams<{ jobId: string }>();\n // Two navigators: `navigate(-1)` only goes through react-router's\n // browser-history API (no basePath rewriting needed), but the\n // absolute-path fallback has to thread the MFE basePath via\n // `useVibeNavigate` so production mounts under `/vibecontrols`\n // land on the right calendar route instead of a host 404.\n const navigate = useNavigate();\n const vibeNavigate = useVibeNavigate();\n const [searchParams] = useSearchParams();\n // Support deep-link flow: carries `?org=…&tenant=…` URL\n // overrides into the Apollo auth-link headers for this page's\n // query + mutation, so they reach the gateway even when the\n // Apollo client's storage refs point at a different tenant.\n const tenantContext = useTenantContext();\n\n // Gateway enforces `schedule:read` on `scheduledJob(id)` and\n // `schedule:execute` on `executeJobNow` — gate the UI on the\n // matching strings so users aren't UI-locked out of server actions\n // they hold or shown buttons that the server will deny.\n const { hasPermission, isLoading: permissionsLoading, permissions } = usePermissions();\n const canReadExecutionLogs = hasPermission(VIBE_PERMISSIONS.LOG_READ);\n const canExecute = hasPermission(VIBE_PERMISSIONS.SCHEDULE_EXECUTE);\n // Skip the network query for users without `schedule:read` so the\n // raw envelope never reaches the browser. The dev/legacy fallback\n // mirrors VibeTaskDetailPage's denial check (no permission strings\n // declared = no enforcement).\n const canFetchScheduledJob =\n !permissionsLoading &&\n (!permissions ||\n (permissions.permissionStrings?.length ?? 0) === 0 ||\n hasPermission(VIBE_PERMISSIONS.SCHEDULE_READ));\n\n const [showLogsDrawer, setShowLogsDrawer] = useState(false);\n const [selectedExecution, setSelectedExecution] = useState<VibeCalendarExecution | null>(null);\n // Inline-banner state — the MFE has no toast provider, so Execute\n // Now failures surface here.\n const [executionError, setExecutionError] = useState<string | null>(null);\n // `executing` flips false the moment `executeJobNow` returns, but\n // the worker still takes 1–6 s to write the new row. Surface a\n // subtle \"waiting for execution\" hint next to the executions list\n // for the duration of the poll window so the user knows the\n // system is still working.\n const [polling, setPolling] = useState(false);\n // Set when the 12 s cap fires without seeing a new row — gives\n // the user a clear cue that the worker didn't land in the\n // expected window without claiming the run actually failed\n // (it may have completed; the row may surface on a manual refresh).\n const [pollingTimedOut, setPollingTimedOut] = useState(false);\n\n const { data, loading, error, refetch } = useGetScheduledJobDetailQuery({\n variables: { id: jobId ?? '', canReadLogs: canReadExecutionLogs },\n skip: !jobId || !canFetchScheduledJob,\n fetchPolicy: 'cache-and-network',\n context: tenantContext,\n });\n const [executeNow, { loading: executing }] = useExecuteScheduledJobNowMutation();\n\n // Latest query result mirrored into a ref so handleExecuteNow can\n // snapshot `executions.total` without taking `data` as a dep\n // (which would re-mint the callback on every cache-and-network\n // refetch and bust downstream memoization).\n const dataRef = useRef(data);\n dataRef.current = data;\n\n // Poll interval owned by a ref + cleared on unmount AND on\n // jobId change so a stale `before` snapshot from a prior job\n // can't keep ticking on the new one's URL.\n const pollIntervalRef = useRef<ReturnType<typeof setInterval> | null>(null);\n useEffect(() => {\n return () => {\n if (pollIntervalRef.current) {\n clearInterval(pollIntervalRef.current);\n pollIntervalRef.current = null;\n }\n };\n }, [jobId]);\n\n // Mount tracking is split into its own effect (no deps) so\n // jobId-change cleanup above doesn't flip the flag false on a\n // still-mounted component. handleExecuteNow awaits a mutation\n // before arming the interval, so it has to verify it's still\n // mounted before starting one that the cleanup would otherwise\n // miss (unmount mid-await with ref=null).\n const mountedRef = useRef(true);\n useEffect(() => {\n return () => {\n mountedRef.current = false;\n };\n }, []);\n\n const job = data?.scheduledJob;\n const executions = job?.executions?.executions ?? [];\n\n // Deep-link / new-tab fallback: when there's no prior history\n // entry (user opened the URL directly), `navigate(-1)` is a no-op.\n // Land them back on the calendar list via the basePath-aware\n // shell navigator so production mounts under `/vibecontrols`\n // resolve to the right route, not a host 404. Carry the current\n // query string through so a `?org=…&tenant=…` deep-link doesn't\n // lose its tenant context and snap the calendar back to the\n // default/stored tenant.\n const handleBack = useCallback(() => {\n if (window.history.length > 1) {\n navigate(-1);\n return;\n }\n const search = searchParams.toString();\n vibeNavigate(search ? `/calendar?${search}` : '/calendar');\n }, [navigate, vibeNavigate, searchParams]);\n\n const handleExecuteNow = useCallback(async () => {\n if (!jobId) return;\n // Clear any prior failure banner before we re-attempt — if the\n // mutation phase below also fails its catch block will set a\n // fresh message.\n setExecutionError(null);\n\n // `executeJobNow` returns once queued; the worker writes the\n // new row 1–6 s later. Snapshot the total now and poll until it\n // increases or the 12 s cap fires. dataRef instead of data so\n // the callback stays referentially stable across refetches.\n const before = dataRef.current?.scheduledJob?.executions?.total ?? 0;\n\n // Mutation failure is the only error path the user must see.\n // A later refetch failure must not surface as \"Execute failed\"\n // because the job already ran server-side.\n //\n // VibeControlsProvider sets `errorPolicy: 'all'`, so RBAC /\n // GraphQL errors resolve into `result.error` (ApolloError)\n // instead of throwing. Inspect both paths or denials silently\n // kick off the poll cycle for a job that never ran.\n const fallbackMsg = tr(\n 'scheduledJob.detail.executeFailedFallback',\n 'Failed to execute job. Try again or contact support.'\n );\n try {\n const result = await executeNow({\n variables: { id: jobId, canReadLogs: canReadExecutionLogs },\n context: tenantContext,\n });\n if (result.error) {\n console.error('executeJobNow returned an Apollo error:', result.error);\n setExecutionError(result.error.message || fallbackMsg);\n return;\n }\n } catch (err) {\n console.error('Failed to execute job:', err);\n const msg = err instanceof Error && err.message ? err.message : fallbackMsg;\n setExecutionError(msg);\n return;\n }\n\n // Bail before any state writes if the component unmounted\n // mid-await — without this we'd arm a new setInterval that the\n // effect cleanup already missed (cleanup ran with ref=null).\n if (!mountedRef.current) return;\n\n try {\n await refetch();\n } catch (err) {\n console.error('Post-execute refetch failed (mutation already succeeded):', err);\n }\n\n if (!mountedRef.current) return;\n\n // Latest click owns the polling window — drop any prior cycle.\n if (pollIntervalRef.current) {\n clearInterval(pollIntervalRef.current);\n pollIntervalRef.current = null;\n }\n setPollingTimedOut(false);\n setPolling(true);\n let attempts = 0;\n // Locals (not the shared ref) own this cycle's bookkeeping so a\n // stale promise from a prior cycle can't cancel this interval\n // or its in-flight flag. `inFlight` skips ticks that arrive\n // before the previous refetch has resolved — a slow gateway\n // (cold start, GC pause) shouldn't pile up concurrent requests\n // against the same Apollo cache key.\n let inFlight = false;\n const intervalId = setInterval(() => {\n if (inFlight) return;\n if (attempts >= 12) {\n if (pollIntervalRef.current === intervalId) {\n clearInterval(intervalId);\n pollIntervalRef.current = null;\n if (mountedRef.current) {\n setPolling(false);\n setPollingTimedOut(true);\n }\n }\n return;\n }\n attempts += 1;\n inFlight = true;\n void refetch()\n .then((r) => {\n const now = r.data?.scheduledJob?.executions?.total ?? before;\n if (now > before) {\n if (pollIntervalRef.current === intervalId) {\n clearInterval(intervalId);\n pollIntervalRef.current = null;\n if (mountedRef.current) setPolling(false);\n }\n }\n })\n .catch(() => {\n // Cap is enforced on the next tick.\n })\n .finally(() => {\n inFlight = false;\n });\n }, 1000);\n pollIntervalRef.current = intervalId;\n }, [jobId, executeNow, refetch, canReadExecutionLogs, tenantContext, tr]);\n\n const handleViewExecution = useCallback(\n (ex: JobExecutionShape) => {\n setSelectedExecution(adaptExecution(ex, canReadExecutionLogs));\n setShowLogsDrawer(true);\n },\n [canReadExecutionLogs]\n );\n\n // Wait for the permissions provider to settle so we don't flash\n // an access-denied screen for users who actually hold the\n // permission (race: query resolves before permissions do).\n if (permissionsLoading) {\n return (\n <MotionPage className=\"flex items-center justify-center h-full p-6\">\n <p className=\"text-sm text-text-secondary\">\n {tr('scheduledJob.detail.loadingPermissions', 'Loading permissions…')}\n </p>\n </MotionPage>\n );\n }\n // Deep-link / bookmark deny — the calendar route gate only\n // protects the page mount, not this specific resource read.\n if (\n permissions &&\n (permissions.permissionStrings?.length ?? 0) > 0 &&\n !hasPermission(VIBE_PERMISSIONS.SCHEDULE_READ)\n ) {\n return (\n <MotionPage className=\"flex flex-col items-center justify-center h-full p-6 gap-3\">\n <h2 className=\"text-base font-semibold text-text-primary\">\n {tr('scheduledJob.detail.accessDenied', 'Access Denied')}\n </h2>\n <p className=\"text-sm text-text-secondary text-center max-w-md\">\n {tr(\n 'scheduledJob.detail.accessDeniedHint',\n 'You do not have permission to view scheduled-job details. Contact your workspace owner if you believe you should.'\n )}\n </p>\n <button\n type=\"button\"\n onClick={handleBack}\n className=\"mt-2 px-3 py-1.5 text-sm rounded-lg border border-border-default hover:bg-bg-sunken transition-colors\"\n >\n {tr('common.back', 'Back')}\n </button>\n </MotionPage>\n );\n }\n\n // ----- loading / error / not-found states -----\n if (loading && !job) {\n return (\n <MotionPage className=\"flex items-center justify-center h-full p-6\">\n <Loader2 size={20} className=\"animate-spin text-text-tertiary\" />\n <p className=\"ml-2 text-sm text-text-secondary\">\n {tr('scheduledJob.detail.loading', 'Loading job…')}\n </p>\n </MotionPage>\n );\n }\n // `cache-and-network` keeps `job` populated from cache while a\n // background refetch is in flight; a transient network error\n // during that refetch sets `error` without nulling `data`, so\n // dropping to the not-found state on `error` alone would discard\n // a perfectly usable cached view. Only treat the page as\n // not-found when there's actually no data to render.\n if (!job && !loading) {\n return (\n <MotionPage className=\"flex flex-col items-center justify-center h-full p-6 gap-3\">\n <p className=\"text-base font-semibold text-text-primary\">\n {tr('scheduledJob.detail.notFound', 'Job not found')}\n </p>\n <p className=\"text-sm text-text-secondary text-center max-w-md\">\n {tr(\n 'scheduledJob.detail.notFoundHint',\n 'It may have been deleted, archived, or moved to a workspace you no longer have access to.'\n )}\n </p>\n <button\n type=\"button\"\n onClick={handleBack}\n className=\"mt-2 px-3 py-1.5 text-sm rounded-lg border border-border-default hover:bg-bg-sunken transition-colors\"\n >\n {tr('common.back', 'Back')}\n </button>\n </MotionPage>\n );\n }\n\n const statusColor = STATUS_COLORS[job.status] || STATUS_COLORS.PENDING;\n const fmtTime = (iso?: string | null) =>\n iso ? new Date(iso).toLocaleString() : tr('common.notAvailable', 'Not available');\n\n return (\n <MotionPage className=\"flex flex-col h-full p-4 gap-6\">\n {/* Header */}\n <div className=\"flex flex-col gap-3 sm:flex-row sm:items-center\">\n <button\n type=\"button\"\n onClick={handleBack}\n className=\"p-1.5 rounded hover:bg-bg-sunken transition-colors w-fit\"\n aria-label={tr('common.back', 'Back')}\n >\n <ArrowLeft size={18} />\n </button>\n <div className=\"flex-1 min-w-0\">\n <div className=\"flex items-center gap-2 flex-wrap\">\n <h1 className=\"text-xl font-bold text-text-primary truncate\">\n {job.name || tr('scheduledJob.detail.untitled', 'Untitled job')}\n </h1>\n <span\n className={`px-2 py-0.5 text-xs font-medium rounded border ${statusColor}`}\n aria-label={`Status ${job.status}`}\n >\n {job.status}\n </span>\n </div>\n {job.description && (\n <p className=\"text-sm text-text-secondary mt-1 line-clamp-2\">{job.description}</p>\n )}\n </div>\n <div className=\"flex w-full flex-wrap items-center gap-2 sm:w-auto sm:justify-end\">\n {canExecute && (\n <button\n type=\"button\"\n onClick={handleExecuteNow}\n disabled={executing}\n className=\"flex items-center gap-1.5 px-3 py-1.5 text-sm bg-status-success-bg-subtle text-status-success-text border border-status-success-border rounded-button hover:bg-bg-sunken transition-colors disabled:opacity-50\"\n >\n <Play size={14} />\n {tr('scheduledJob.detail.executeNow', 'Execute Now')}\n </button>\n )}\n </div>\n </div>\n\n {executionError && (\n <div\n role=\"alert\"\n className=\"flex items-start gap-2 rounded-lg border border-status-error-border bg-status-error-bg-subtle px-3 py-2 text-sm text-status-error-text\"\n >\n <AlertCircle size={16} className=\"mt-0.5 flex-shrink-0\" />\n <p className=\"flex-1 break-words\">{executionError}</p>\n <button\n type=\"button\"\n onClick={() => setExecutionError(null)}\n className=\"p-0.5 rounded hover:bg-bg-sunken transition-colors\"\n aria-label={tr('common.dismiss', 'Dismiss')}\n >\n <X size={14} />\n </button>\n </div>\n )}\n\n {/* Info Cards */}\n <MotionList stagger={0.04} className=\"grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-3\">\n <MotionItem className=\"bg-bg-surface border border-border-default rounded-lg p-3\">\n <p className=\"text-xs text-text-tertiary\">\n {tr('scheduledJob.detail.scheduleType', 'Schedule Type')}\n </p>\n <p className=\"text-sm font-medium text-text-primary mt-0.5\">{job.scheduleType}</p>\n {job.cronExpression && (\n <p className=\"text-xs text-text-secondary font-mono mt-1 truncate\">\n {job.cronExpression}\n </p>\n )}\n </MotionItem>\n <MotionItem className=\"bg-bg-surface border border-border-default rounded-lg p-3\">\n <p className=\"text-xs text-text-tertiary\">\n {tr('scheduledJob.detail.timezone', 'Timezone')}\n </p>\n <p className=\"text-sm font-medium text-text-primary mt-0.5\">{job.timezone || 'UTC'}</p>\n </MotionItem>\n <MotionItem className=\"bg-bg-surface border border-border-default rounded-lg p-3\">\n <p className=\"text-xs text-text-tertiary\">\n {tr('scheduledJob.detail.nextExecution', 'Next Execution')}\n </p>\n <p className=\"text-sm font-medium text-text-primary mt-0.5\">\n {fmtTime(job.nextExecutionAt)}\n </p>\n </MotionItem>\n <MotionItem className=\"bg-bg-surface border border-border-default rounded-lg p-3\">\n <p className=\"text-xs text-text-tertiary\">\n {tr('scheduledJob.detail.lastExecution', 'Last Execution')}\n </p>\n <p className=\"text-sm font-medium text-text-primary mt-0.5\">\n {fmtTime(job.lastExecutionAt)}\n </p>\n </MotionItem>\n <MotionItem className=\"bg-bg-surface border border-border-default rounded-lg p-3\">\n <p className=\"text-xs text-text-tertiary\">\n {tr('scheduledJob.detail.executor', 'Executor')}\n </p>\n <p className=\"text-sm font-medium text-text-primary mt-0.5\">\n {job.executorType || tr('common.notAvailable', 'Not available')}\n </p>\n {job.actionId && (\n <p className=\"text-xs text-text-secondary font-mono mt-1 truncate\">\n {tr('scheduledJob.detail.action', 'Action')}: {job.actionId}\n </p>\n )}\n </MotionItem>\n <MotionItem className=\"bg-bg-surface border border-border-default rounded-lg p-3\">\n <p className=\"text-xs text-text-tertiary\">\n {tr('scheduledJob.detail.priority', 'Priority')}\n </p>\n <p className=\"text-sm font-medium text-text-primary mt-0.5\">{job.priority}</p>\n </MotionItem>\n </MotionList>\n\n {/* Executions List */}\n <div className=\"flex flex-col gap-2 flex-1 min-h-0\">\n <div className=\"flex items-center justify-between flex-wrap gap-2\">\n <div className=\"flex items-center gap-2\">\n <h2 className=\"text-sm font-semibold text-text-primary\">\n {tr('scheduledJob.detail.executions', 'Executions')}\n </h2>\n {polling && (\n <span className=\"flex items-center gap-1 text-xs text-text-tertiary\">\n <Loader2 size={12} className=\"animate-spin\" />\n {tr('scheduledJob.detail.waitingForExecution', 'Waiting for execution…')}\n </span>\n )}\n {!polling && pollingTimedOut && (\n <span className=\"text-xs text-text-tertiary\">\n {tr(\n 'scheduledJob.detail.pollingTimedOut',\n \"Didn't see a new execution yet — refresh to check again.\"\n )}\n </span>\n )}\n </div>\n <p className=\"text-xs text-text-tertiary\">\n {tr('scheduledJob.detail.executionsCount', 'Total')}: {job.executions?.total ?? 0}\n </p>\n </div>\n {executions.length === 0 ? (\n <div className=\"flex items-center justify-center py-12 border border-border-default border-dashed rounded-lg\">\n <p className=\"text-sm text-text-secondary\">\n {tr('scheduledJob.detail.noExecutions', 'No executions yet')}\n </p>\n </div>\n ) : (\n <MotionList\n stagger={0.02}\n className=\"flex flex-col gap-2 overflow-auto pr-1 max-h-[60vh]\"\n >\n {executions.map((ex: JobExecutionShape) => {\n // `??` keeps null/undefined out of the badge text.\n const exStatus = ex.status ?? 'UNKNOWN';\n const exColor = STATUS_COLORS[exStatus] || STATUS_COLORS.PENDING;\n return (\n <MotionItem\n key={ex.id}\n className=\"flex items-center gap-3 bg-bg-surface border border-border-default rounded-lg p-3\"\n >\n <span\n className={`px-2 py-0.5 text-xs font-medium rounded border ${exColor} flex-shrink-0`}\n >\n {exStatus}\n </span>\n <div className=\"flex-1 min-w-0\">\n <p className=\"text-sm text-text-primary truncate\">{fmtTime(ex.startedAt)}</p>\n {ex.completedAt && (\n <p className=\"text-xs text-text-tertiary truncate\">\n {tr('scheduledJob.detail.completedAt', 'Completed')}:{' '}\n {fmtTime(ex.completedAt)}\n </p>\n )}\n </div>\n {ex.statusCode !== null && ex.statusCode !== undefined && (\n <span className=\"text-xs font-mono text-text-secondary flex-shrink-0\">\n {ex.statusCode}\n </span>\n )}\n {canReadExecutionLogs && (\n <button\n type=\"button\"\n onClick={() => handleViewExecution(ex)}\n className=\"p-1 rounded hover:bg-bg-sunken transition-colors flex-shrink-0\"\n aria-label={tr('scheduledJob.detail.viewLogs', 'View logs')}\n title={tr('scheduledJob.detail.viewLogs', 'View logs')}\n >\n <Eye size={14} />\n </button>\n )}\n </MotionItem>\n );\n })}\n </MotionList>\n )}\n </div>\n\n {/* Per-execution Logs Drawer (reused) */}\n <LogsDrawer\n isOpen={showLogsDrawer}\n onClose={() => setShowLogsDrawer(false)}\n execution={selectedExecution}\n taskTitle={job.name || tr('scheduledJob.detail.untitled', 'Untitled job')}\n // ScheduledJob executions resolve the agent through the\n // linked Action at run time, so there's no agentId to\n // associate with the drawer view — LogsDrawer destructures\n // this as `_agentId` and never reads it.\n agentId=\"\"\n />\n </MotionPage>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"ScheduledJobDetailPage.js","names":[],"sources":["../../src/pages/ScheduledJobDetailPage.tsx"],"sourcesContent":["import { useCallback, useEffect, useRef, useState } from 'react';\nimport { useParams, useNavigate, useSearchParams } from 'react-router-dom';\nimport { useTenantContext } from '@/hooks/useTenantContext';\nimport { useVibeNavigate } from '@/providers/VibeControlsProvider';\nimport { usePermissions } from '@burdenoff/fe-libs/shared/providers/shell';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { ArrowLeft, Play, Loader2, Eye, AlertCircle, X } from 'lucide-react';\nimport { MotionPage, MotionList, MotionItem } from '@burdenoff/fe-libs/shared/motion';\nimport { LogsDrawer } from '../components/vibecalendar/LogsDrawer';\nimport { STATUS_COLORS } from '../components/vibecalendar/types';\nimport type { VibeCalendarExecution } from '../components/vibecalendar/types';\nimport { VIBE_PERMISSIONS } from '../constants/permissions';\nimport {\n useExecuteScheduledJobNowMutation,\n useGetScheduledJobDetailQuery,\n} from '@/generated/wspace-operations';\n\n// INTERNAL_API jobs persist the worker envelope as\n// `{ statusCode, duration, responseData: { executeAction: { output,\n// exitCode, status, errorMessage } } }`. `adaptExecution` unwraps\n// that into the flat shape the LogsDrawer consumes.\ntype JobExecutionShape = {\n id: string;\n status?: string | null;\n startedAt?: string | null;\n completedAt?: string | null;\n output?: unknown;\n errorMessage?: string | null;\n statusCode?: number | null;\n};\nfunction adaptExecution(\n ex: JobExecutionShape,\n canReadExecutionLogs: boolean\n): VibeCalendarExecution {\n let stdout: string | null = null;\n let exitCode: number | null = ex.statusCode ?? null;\n // Nested action errors are the only failure signal on agent-exit\n // failures (top-level `errorMessage` only fires for scheduler-side\n // dispatch failures), so capture both and let the chain below\n // prefer the top-level when present.\n let nestedActionError: string | null = null;\n if (typeof ex.output === 'string') {\n stdout = ex.output;\n } else if (ex.output && typeof ex.output === 'object') {\n const envelope = ex.output as Record<string, unknown>;\n const action = (envelope.responseData as Record<string, unknown> | undefined)?.executeAction as\n Record<string, unknown> | undefined;\n if (action && typeof action.output === 'string') {\n stdout = action.output;\n }\n if (action && typeof action.exitCode === 'number') {\n exitCode = action.exitCode;\n }\n if (action && typeof action.errorMessage === 'string' && action.errorMessage.length > 0) {\n nestedActionError = action.errorMessage;\n }\n if (stdout === null) {\n stdout = JSON.stringify(envelope, null, 2);\n }\n }\n // `??` only falls back for null/undefined; an empty top-level\n // string would otherwise mask a populated nested error.\n const topErrorTrimmed =\n typeof ex.errorMessage === 'string' && ex.errorMessage.trim().length > 0\n ? ex.errorMessage\n : null;\n return {\n id: ex.id,\n taskId: '',\n status: ex.status ?? 'UNKNOWN',\n // Suppress raw envelope fields for callers without `log:read`.\n output: canReadExecutionLogs ? stdout : null,\n exitCode,\n webhookStatusCode: null,\n startedAt: ex.startedAt ?? null,\n completedAt: ex.completedAt ?? null,\n error: canReadExecutionLogs ? (topErrorTrimmed ?? nestedActionError ?? null) : null,\n };\n}\n\n// In-product detail view for a wspace-scheduler-svc ScheduledJob.\n// Lives next to VibeTaskDetailPage but reads/executes through the\n// scheduler subgraph (different id space, different RBAC resource).\nexport function ScheduledJobDetailPage() {\n const { t } = useI18n();\n const tr = useCallback(\n (key: string, fallback: string): string => {\n const v = t(key);\n return v === key ? fallback : v;\n },\n [t]\n );\n const { jobId } = useParams<{ jobId: string }>();\n // Two navigators: `navigate(-1)` only goes through react-router's\n // browser-history API (no basePath rewriting needed), but the\n // absolute-path fallback has to thread the MFE basePath via\n // `useVibeNavigate` so production mounts under `/vibecontrols`\n // land on the right calendar route instead of a host 404.\n const navigate = useNavigate();\n const vibeNavigate = useVibeNavigate();\n const [searchParams] = useSearchParams();\n // Support deep-link flow: carries `?org=…&tenant=…` URL\n // overrides into the Apollo auth-link headers for this page's\n // query + mutation, so they reach the gateway even when the\n // Apollo client's storage refs point at a different tenant.\n const tenantContext = useTenantContext();\n\n // Gateway enforces `schedule:read` on `scheduledJob(id)` and\n // `schedule:execute` on `executeJobNow` — gate the UI on the\n // matching strings so users aren't UI-locked out of server actions\n // they hold or shown buttons that the server will deny.\n const { hasPermission, isLoading: permissionsLoading, permissions } = usePermissions();\n const canReadExecutionLogs = hasPermission(VIBE_PERMISSIONS.LOG_READ);\n const canExecute = hasPermission(VIBE_PERMISSIONS.SCHEDULE_EXECUTE);\n // Skip the network query for users without `schedule:read` so the\n // raw envelope never reaches the browser. The dev/legacy fallback\n // mirrors VibeTaskDetailPage's denial check (no permission strings\n // declared = no enforcement).\n const canFetchScheduledJob =\n !permissionsLoading &&\n (!permissions ||\n (permissions.permissionStrings?.length ?? 0) === 0 ||\n hasPermission(VIBE_PERMISSIONS.SCHEDULE_READ));\n\n const [showLogsDrawer, setShowLogsDrawer] = useState(false);\n const [selectedExecution, setSelectedExecution] = useState<VibeCalendarExecution | null>(null);\n // Inline-banner state — the MFE has no toast provider, so Execute\n // Now failures surface here.\n const [executionError, setExecutionError] = useState<string | null>(null);\n // `executing` flips false the moment `executeJobNow` returns, but\n // the worker still takes 1–6 s to write the new row. Surface a\n // subtle \"waiting for execution\" hint next to the executions list\n // for the duration of the poll window so the user knows the\n // system is still working.\n const [polling, setPolling] = useState(false);\n // Set when the 12 s cap fires without seeing a new row — gives\n // the user a clear cue that the worker didn't land in the\n // expected window without claiming the run actually failed\n // (it may have completed; the row may surface on a manual refresh).\n const [pollingTimedOut, setPollingTimedOut] = useState(false);\n\n const { data, loading, error, refetch } = useGetScheduledJobDetailQuery({\n variables: { id: jobId ?? '', canReadLogs: canReadExecutionLogs },\n skip: !jobId || !canFetchScheduledJob,\n fetchPolicy: 'cache-and-network',\n context: tenantContext,\n });\n const [executeNow, { loading: executing }] = useExecuteScheduledJobNowMutation();\n\n // Latest query result mirrored into a ref so handleExecuteNow can\n // snapshot `executions.total` without taking `data` as a dep\n // (which would re-mint the callback on every cache-and-network\n // refetch and bust downstream memoization).\n const dataRef = useRef(data);\n dataRef.current = data;\n\n // Poll interval owned by a ref + cleared on unmount AND on\n // jobId change so a stale `before` snapshot from a prior job\n // can't keep ticking on the new one's URL.\n const pollIntervalRef = useRef<ReturnType<typeof setInterval> | null>(null);\n useEffect(() => {\n return () => {\n if (pollIntervalRef.current) {\n clearInterval(pollIntervalRef.current);\n pollIntervalRef.current = null;\n }\n };\n }, [jobId]);\n\n // Mount tracking is split into its own effect (no deps) so\n // jobId-change cleanup above doesn't flip the flag false on a\n // still-mounted component. handleExecuteNow awaits a mutation\n // before arming the interval, so it has to verify it's still\n // mounted before starting one that the cleanup would otherwise\n // miss (unmount mid-await with ref=null).\n const mountedRef = useRef(true);\n useEffect(() => {\n return () => {\n mountedRef.current = false;\n };\n }, []);\n\n const job = data?.scheduledJob;\n const executions = job?.executions?.executions ?? [];\n\n // Deep-link / new-tab fallback: when there's no prior history\n // entry (user opened the URL directly), `navigate(-1)` is a no-op.\n // Land them back on the calendar list via the basePath-aware\n // shell navigator so production mounts under `/vibecontrols`\n // resolve to the right route, not a host 404. Carry the current\n // query string through so a `?org=…&tenant=…` deep-link doesn't\n // lose its tenant context and snap the calendar back to the\n // default/stored tenant.\n const handleBack = useCallback(() => {\n if (window.history.length > 1) {\n navigate(-1);\n return;\n }\n const search = searchParams.toString();\n vibeNavigate(search ? `/calendar?${search}` : '/calendar');\n }, [navigate, vibeNavigate, searchParams]);\n\n const handleExecuteNow = useCallback(async () => {\n if (!jobId) return;\n // Clear any prior failure banner before we re-attempt — if the\n // mutation phase below also fails its catch block will set a\n // fresh message.\n setExecutionError(null);\n\n // `executeJobNow` returns once queued; the worker writes the\n // new row 1–6 s later. Snapshot the total now and poll until it\n // increases or the 12 s cap fires. dataRef instead of data so\n // the callback stays referentially stable across refetches.\n const before = dataRef.current?.scheduledJob?.executions?.total ?? 0;\n\n // Mutation failure is the only error path the user must see.\n // A later refetch failure must not surface as \"Execute failed\"\n // because the job already ran server-side.\n //\n // VibeControlsProvider sets `errorPolicy: 'all'`, so RBAC /\n // GraphQL errors resolve into `result.error` (ApolloError)\n // instead of throwing. Inspect both paths or denials silently\n // kick off the poll cycle for a job that never ran.\n const fallbackMsg = tr(\n 'scheduledJob.detail.executeFailedFallback',\n 'Failed to execute job. Try again or contact support.'\n );\n try {\n const result = await executeNow({\n variables: { id: jobId, canReadLogs: canReadExecutionLogs },\n context: tenantContext,\n });\n if (result.error) {\n console.error('executeJobNow returned an Apollo error:', result.error);\n setExecutionError(result.error.message || fallbackMsg);\n return;\n }\n } catch (err) {\n console.error('Failed to execute job:', err);\n const msg = err instanceof Error && err.message ? err.message : fallbackMsg;\n setExecutionError(msg);\n return;\n }\n\n // Bail before any state writes if the component unmounted\n // mid-await — without this we'd arm a new setInterval that the\n // effect cleanup already missed (cleanup ran with ref=null).\n if (!mountedRef.current) return;\n\n try {\n await refetch();\n } catch (err) {\n console.error('Post-execute refetch failed (mutation already succeeded):', err);\n }\n\n if (!mountedRef.current) return;\n\n // Latest click owns the polling window — drop any prior cycle.\n if (pollIntervalRef.current) {\n clearInterval(pollIntervalRef.current);\n pollIntervalRef.current = null;\n }\n setPollingTimedOut(false);\n setPolling(true);\n let attempts = 0;\n // Locals (not the shared ref) own this cycle's bookkeeping so a\n // stale promise from a prior cycle can't cancel this interval\n // or its in-flight flag. `inFlight` skips ticks that arrive\n // before the previous refetch has resolved — a slow gateway\n // (cold start, GC pause) shouldn't pile up concurrent requests\n // against the same Apollo cache key.\n let inFlight = false;\n const intervalId = setInterval(() => {\n if (inFlight) return;\n if (attempts >= 12) {\n if (pollIntervalRef.current === intervalId) {\n clearInterval(intervalId);\n pollIntervalRef.current = null;\n if (mountedRef.current) {\n setPolling(false);\n setPollingTimedOut(true);\n }\n }\n return;\n }\n attempts += 1;\n inFlight = true;\n void refetch()\n .then((r) => {\n const now = r.data?.scheduledJob?.executions?.total ?? before;\n if (now > before) {\n if (pollIntervalRef.current === intervalId) {\n clearInterval(intervalId);\n pollIntervalRef.current = null;\n if (mountedRef.current) setPolling(false);\n }\n }\n })\n .catch(() => {\n // Cap is enforced on the next tick.\n })\n .finally(() => {\n inFlight = false;\n });\n }, 1000);\n pollIntervalRef.current = intervalId;\n }, [jobId, executeNow, refetch, canReadExecutionLogs, tenantContext, tr]);\n\n const handleViewExecution = useCallback(\n (ex: JobExecutionShape) => {\n setSelectedExecution(adaptExecution(ex, canReadExecutionLogs));\n setShowLogsDrawer(true);\n },\n [canReadExecutionLogs]\n );\n\n // Wait for the permissions provider to settle so we don't flash\n // an access-denied screen for users who actually hold the\n // permission (race: query resolves before permissions do).\n if (permissionsLoading) {\n return (\n <MotionPage className=\"flex items-center justify-center h-full p-6\">\n <p className=\"text-sm text-text-secondary\">\n {tr('scheduledJob.detail.loadingPermissions', 'Loading permissions…')}\n </p>\n </MotionPage>\n );\n }\n // Deep-link / bookmark deny — the calendar route gate only\n // protects the page mount, not this specific resource read.\n if (\n permissions &&\n (permissions.permissionStrings?.length ?? 0) > 0 &&\n !hasPermission(VIBE_PERMISSIONS.SCHEDULE_READ)\n ) {\n return (\n <MotionPage className=\"flex flex-col items-center justify-center h-full p-6 gap-3\">\n <h2 className=\"text-base font-semibold text-text-primary\">\n {tr('scheduledJob.detail.accessDenied', 'Access Denied')}\n </h2>\n <p className=\"text-sm text-text-secondary text-center max-w-md\">\n {tr(\n 'scheduledJob.detail.accessDeniedHint',\n 'You do not have permission to view scheduled-job details. Contact your workspace owner if you believe you should.'\n )}\n </p>\n <button\n type=\"button\"\n onClick={handleBack}\n className=\"mt-2 px-3 py-1.5 text-sm rounded-lg border border-border-default hover:bg-bg-sunken transition-colors\"\n >\n {tr('common.back', 'Back')}\n </button>\n </MotionPage>\n );\n }\n\n // ----- loading / error / not-found states -----\n if (loading && !job) {\n return (\n <MotionPage className=\"flex items-center justify-center h-full p-6\">\n <Loader2 size={20} className=\"animate-spin text-text-tertiary\" />\n <p className=\"ml-2 text-sm text-text-secondary\">\n {tr('scheduledJob.detail.loading', 'Loading job…')}\n </p>\n </MotionPage>\n );\n }\n // `cache-and-network` keeps `job` populated from cache while a\n // background refetch is in flight; a transient network error\n // during that refetch sets `error` without nulling `data`, so\n // dropping to the not-found state on `error` alone would discard\n // a perfectly usable cached view. Only treat the page as\n // not-found when there's actually no data to render.\n if (!job && !loading) {\n return (\n <MotionPage className=\"flex flex-col items-center justify-center h-full p-6 gap-3\">\n <p className=\"text-base font-semibold text-text-primary\">\n {tr('scheduledJob.detail.notFound', 'Job not found')}\n </p>\n <p className=\"text-sm text-text-secondary text-center max-w-md\">\n {tr(\n 'scheduledJob.detail.notFoundHint',\n 'It may have been deleted, archived, or moved to a workspace you no longer have access to.'\n )}\n </p>\n <button\n type=\"button\"\n onClick={handleBack}\n className=\"mt-2 px-3 py-1.5 text-sm rounded-lg border border-border-default hover:bg-bg-sunken transition-colors\"\n >\n {tr('common.back', 'Back')}\n </button>\n </MotionPage>\n );\n }\n\n const statusColor = STATUS_COLORS[job.status] || STATUS_COLORS.PENDING;\n const fmtTime = (iso?: string | null) =>\n iso ? new Date(iso).toLocaleString() : tr('common.notAvailable', 'Not available');\n\n return (\n <MotionPage className=\"flex flex-col h-full p-4 gap-6\">\n {/* Header */}\n <div className=\"flex flex-col gap-3 sm:flex-row sm:items-center\">\n <button\n type=\"button\"\n onClick={handleBack}\n className=\"p-1.5 rounded hover:bg-bg-sunken transition-colors w-fit\"\n aria-label={tr('common.back', 'Back')}\n >\n <ArrowLeft size={18} />\n </button>\n <div className=\"flex-1 min-w-0\">\n <div className=\"flex items-center gap-2 flex-wrap\">\n <h1 className=\"text-xl font-bold text-text-primary truncate\">\n {job.name || tr('scheduledJob.detail.untitled', 'Untitled job')}\n </h1>\n <span\n className={`px-2 py-0.5 text-xs font-medium rounded border ${statusColor}`}\n aria-label={`Status ${job.status}`}\n >\n {job.status}\n </span>\n </div>\n {job.description && (\n <p className=\"text-sm text-text-secondary mt-1 line-clamp-2\">{job.description}</p>\n )}\n </div>\n <div className=\"flex w-full flex-wrap items-center gap-2 sm:w-auto sm:justify-end\">\n {canExecute && (\n <button\n type=\"button\"\n onClick={handleExecuteNow}\n disabled={executing}\n className=\"flex items-center gap-1.5 px-3 py-1.5 text-sm bg-status-success-bg-subtle text-status-success-text border border-status-success-border rounded-button hover:bg-bg-sunken transition-colors disabled:opacity-50\"\n >\n <Play size={14} />\n {tr('scheduledJob.detail.executeNow', 'Execute Now')}\n </button>\n )}\n </div>\n </div>\n\n {executionError && (\n <div\n role=\"alert\"\n className=\"flex items-start gap-2 rounded-lg border border-status-error-border bg-status-error-bg-subtle px-3 py-2 text-sm text-status-error-text\"\n >\n <AlertCircle size={16} className=\"mt-0.5 flex-shrink-0\" />\n <p className=\"flex-1 break-words\">{executionError}</p>\n <button\n type=\"button\"\n onClick={() => setExecutionError(null)}\n className=\"p-0.5 rounded hover:bg-bg-sunken transition-colors\"\n aria-label={tr('common.dismiss', 'Dismiss')}\n >\n <X size={14} />\n </button>\n </div>\n )}\n\n {/* Info Cards */}\n <MotionList stagger={0.04} className=\"grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-3\">\n <MotionItem className=\"bg-bg-surface border border-border-default rounded-lg p-3\">\n <p className=\"text-xs text-text-tertiary\">\n {tr('scheduledJob.detail.scheduleType', 'Schedule Type')}\n </p>\n <p className=\"text-sm font-medium text-text-primary mt-0.5\">{job.scheduleType}</p>\n {job.cronExpression && (\n <p className=\"text-xs text-text-secondary font-mono mt-1 truncate\">\n {job.cronExpression}\n </p>\n )}\n </MotionItem>\n <MotionItem className=\"bg-bg-surface border border-border-default rounded-lg p-3\">\n <p className=\"text-xs text-text-tertiary\">\n {tr('scheduledJob.detail.timezone', 'Timezone')}\n </p>\n <p className=\"text-sm font-medium text-text-primary mt-0.5\">{job.timezone || 'UTC'}</p>\n </MotionItem>\n <MotionItem className=\"bg-bg-surface border border-border-default rounded-lg p-3\">\n <p className=\"text-xs text-text-tertiary\">\n {tr('scheduledJob.detail.nextExecution', 'Next Execution')}\n </p>\n <p className=\"text-sm font-medium text-text-primary mt-0.5\">\n {fmtTime(job.nextExecutionAt)}\n </p>\n </MotionItem>\n <MotionItem className=\"bg-bg-surface border border-border-default rounded-lg p-3\">\n <p className=\"text-xs text-text-tertiary\">\n {tr('scheduledJob.detail.lastExecution', 'Last Execution')}\n </p>\n <p className=\"text-sm font-medium text-text-primary mt-0.5\">\n {fmtTime(job.lastExecutionAt)}\n </p>\n </MotionItem>\n <MotionItem className=\"bg-bg-surface border border-border-default rounded-lg p-3\">\n <p className=\"text-xs text-text-tertiary\">\n {tr('scheduledJob.detail.executor', 'Executor')}\n </p>\n <p className=\"text-sm font-medium text-text-primary mt-0.5\">\n {job.executorType || tr('common.notAvailable', 'Not available')}\n </p>\n {job.actionId && (\n <p className=\"text-xs text-text-secondary font-mono mt-1 truncate\">\n {tr('scheduledJob.detail.action', 'Action')}: {job.actionId}\n </p>\n )}\n </MotionItem>\n <MotionItem className=\"bg-bg-surface border border-border-default rounded-lg p-3\">\n <p className=\"text-xs text-text-tertiary\">\n {tr('scheduledJob.detail.priority', 'Priority')}\n </p>\n <p className=\"text-sm font-medium text-text-primary mt-0.5\">{job.priority}</p>\n </MotionItem>\n </MotionList>\n\n {/* Executions List */}\n <div className=\"flex flex-col gap-2 flex-1 min-h-0\">\n <div className=\"flex items-center justify-between flex-wrap gap-2\">\n <div className=\"flex items-center gap-2\">\n <h2 className=\"text-sm font-semibold text-text-primary\">\n {tr('scheduledJob.detail.executions', 'Executions')}\n </h2>\n {polling && (\n <span className=\"flex items-center gap-1 text-xs text-text-tertiary\">\n <Loader2 size={12} className=\"animate-spin\" />\n {tr('scheduledJob.detail.waitingForExecution', 'Waiting for execution…')}\n </span>\n )}\n {!polling && pollingTimedOut && (\n <span className=\"text-xs text-text-tertiary\">\n {tr(\n 'scheduledJob.detail.pollingTimedOut',\n \"Didn't see a new execution yet — refresh to check again.\"\n )}\n </span>\n )}\n </div>\n <p className=\"text-xs text-text-tertiary\">\n {tr('scheduledJob.detail.executionsCount', 'Total')}: {job.executions?.total ?? 0}\n </p>\n </div>\n {executions.length === 0 ? (\n <div className=\"flex items-center justify-center py-12 border border-border-default border-dashed rounded-lg\">\n <p className=\"text-sm text-text-secondary\">\n {tr('scheduledJob.detail.noExecutions', 'No executions yet')}\n </p>\n </div>\n ) : (\n <MotionList\n stagger={0.02}\n className=\"flex flex-col gap-2 overflow-auto pr-1 max-h-[60vh]\"\n >\n {executions.map((ex: JobExecutionShape) => {\n // `??` keeps null/undefined out of the badge text.\n const exStatus = ex.status ?? 'UNKNOWN';\n const exColor = STATUS_COLORS[exStatus] || STATUS_COLORS.PENDING;\n return (\n <MotionItem\n key={ex.id}\n className=\"flex items-center gap-3 bg-bg-surface border border-border-default rounded-lg p-3\"\n >\n <span\n className={`px-2 py-0.5 text-xs font-medium rounded border ${exColor} flex-shrink-0`}\n >\n {exStatus}\n </span>\n <div className=\"flex-1 min-w-0\">\n <p className=\"text-sm text-text-primary truncate\">{fmtTime(ex.startedAt)}</p>\n {ex.completedAt && (\n <p className=\"text-xs text-text-tertiary truncate\">\n {tr('scheduledJob.detail.completedAt', 'Completed')}:{' '}\n {fmtTime(ex.completedAt)}\n </p>\n )}\n </div>\n {ex.statusCode !== null && ex.statusCode !== undefined && (\n <span className=\"text-xs font-mono text-text-secondary flex-shrink-0\">\n {ex.statusCode}\n </span>\n )}\n {canReadExecutionLogs && (\n <button\n type=\"button\"\n onClick={() => handleViewExecution(ex)}\n className=\"p-1 rounded hover:bg-bg-sunken transition-colors flex-shrink-0\"\n aria-label={tr('scheduledJob.detail.viewLogs', 'View logs')}\n title={tr('scheduledJob.detail.viewLogs', 'View logs')}\n >\n <Eye size={14} />\n </button>\n )}\n </MotionItem>\n );\n })}\n </MotionList>\n )}\n </div>\n\n {/* Per-execution Logs Drawer (reused) */}\n <LogsDrawer\n isOpen={showLogsDrawer}\n onClose={() => setShowLogsDrawer(false)}\n execution={selectedExecution}\n taskTitle={job.name || tr('scheduledJob.detail.untitled', 'Untitled job')}\n // ScheduledJob executions resolve the agent through the\n // linked Action at run time, so there's no agentId to\n // associate with the drawer view — LogsDrawer destructures\n // this as `_agentId` and never reads it.\n agentId=\"\"\n />\n </MotionPage>\n );\n}\n"],"mappings":"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"}
|