@conduction/nextcloud-vue 2.56.0-beta.4 → 2.56.0-beta.5
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/esm/components/CnActionsBar/CnActionsBar.vue2.js +12 -2
- package/dist/esm/components/CnActionsBar/CnActionsBar.vue2.js.map +1 -1
- package/dist/esm/components/CnActionsBar/CnActionsBar.vue3.js +1 -2
- package/dist/esm/components/CnActionsBar/CnActionsBar.vue3.js.map +1 -1
- package/dist/esm/components/CnActionsMenu/CnActionsMenu.vue2.js.map +1 -1
- package/dist/esm/components/CnActionsMenu/CnActionsMenu.vue3.js +0 -3
- package/dist/esm/components/CnActionsMenu/CnActionsMenu.vue3.js.map +1 -1
- package/dist/esm/components/CnAdminSettingsShell/CnAdminSettingsShell.vue2.js.map +1 -1
- package/dist/esm/components/CnAdminSettingsShell/CnAdminSettingsShell.vue3.js +1 -2
- package/dist/esm/components/CnAdminSettingsShell/CnAdminSettingsShell.vue3.js.map +1 -1
- package/dist/esm/components/CnBoardView/CnBoardView.vue2.js +12 -5
- package/dist/esm/components/CnBoardView/CnBoardView.vue2.js.map +1 -1
- package/dist/esm/components/CnBoardView/CnBoardView.vue3.js +2 -1
- package/dist/esm/components/CnBoardView/CnBoardView.vue3.js.map +1 -1
- package/dist/esm/components/CnCardGrid/CnCardGrid.vue2.js +9 -1
- package/dist/esm/components/CnCardGrid/CnCardGrid.vue2.js.map +1 -1
- package/dist/esm/components/CnCardGrid/CnCardGrid.vue3.js.map +1 -1
- package/dist/esm/components/CnContextMenu/CnContextMenu.vue2.js +6 -1
- package/dist/esm/components/CnContextMenu/CnContextMenu.vue2.js.map +1 -1
- package/dist/esm/components/CnContextMenu/CnContextMenu.vue3.js.map +1 -1
- package/dist/esm/components/CnDataTable/CnDataTable.vue2.js +116 -15
- package/dist/esm/components/CnDataTable/CnDataTable.vue2.js.map +1 -1
- package/dist/esm/components/CnDataTable/CnDataTable.vue3.js +27 -14
- package/dist/esm/components/CnDataTable/CnDataTable.vue3.js.map +1 -1
- package/dist/esm/components/CnDateAxisView/CnDateAxisView.vue2.js +13 -6
- package/dist/esm/components/CnDateAxisView/CnDateAxisView.vue2.js.map +1 -1
- package/dist/esm/components/CnDateAxisView/CnDateAxisView.vue3.js +2 -0
- package/dist/esm/components/CnDateAxisView/CnDateAxisView.vue3.js.map +1 -1
- package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js +29 -1
- package/dist/esm/components/CnDetailPage/CnDetailPage.vue2.js.map +1 -1
- package/dist/esm/components/CnDetailPage/CnDetailPage.vue3.js.map +1 -1
- package/dist/esm/components/CnFeaturesAndRoadmapView/CnFeaturesAndRoadmapView.vue2.js.map +1 -1
- package/dist/esm/components/CnFeaturesAndRoadmapView/CnFeaturesAndRoadmapView.vue3.js +1 -2
- package/dist/esm/components/CnFeaturesAndRoadmapView/CnFeaturesAndRoadmapView.vue3.js.map +1 -1
- package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue2.js +3 -1
- package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue2.js.map +1 -1
- package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue3.js +17 -17
- package/dist/esm/components/CnFilesBrowser/CnFilesBrowser.vue3.js.map +1 -1
- package/dist/esm/components/CnFilesWidget/CnFilesWidget.vue2.js +4 -21
- package/dist/esm/components/CnFilesWidget/CnFilesWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnFilesWidget/CnFilesWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnFlowRunsWidget/CnFlowRunsWidget.vue2.js +0 -13
- package/dist/esm/components/CnFlowRunsWidget/CnFlowRunsWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnFlowRunsWidget/CnFlowRunsWidget.vue3.js +8 -8
- package/dist/esm/components/CnFlowRunsWidget/CnFlowRunsWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnFlowsPage/CnFlowsPage.vue2.js.map +1 -1
- package/dist/esm/components/CnFlowsPage/CnFlowsPage.vue3.js +3 -2
- package/dist/esm/components/CnFlowsPage/CnFlowsPage.vue3.js.map +1 -1
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js +67 -13
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue2.js.map +1 -1
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js +15 -8
- package/dist/esm/components/CnIndexPage/CnIndexPage.vue3.js.map +1 -1
- package/dist/esm/components/CnIndexPage/manifestActionDispatch.js +31 -11
- package/dist/esm/components/CnIndexPage/manifestActionDispatch.js.map +1 -1
- package/dist/esm/components/CnIntegrationWidget/CnIntegrationWidgetEmpty.vue2.js.map +1 -1
- package/dist/esm/components/CnIntegrationWidget/CnIntegrationWidgetEmpty.vue3.js +1 -2
- package/dist/esm/components/CnIntegrationWidget/CnIntegrationWidgetEmpty.vue3.js.map +1 -1
- package/dist/esm/components/CnInteractionFormWidget/CnInteractionFormWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnInteractionFormWidget/CnInteractionFormWidget.vue3.js +0 -1
- package/dist/esm/components/CnInteractionFormWidget/CnInteractionFormWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnLinkButtonWidget/CnLinkButtonWidget.vue2.js +2 -17
- package/dist/esm/components/CnLinkButtonWidget/CnLinkButtonWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnLinkButtonWidget/CnLinkButtonWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnLogsPage/CnLogsPage.vue2.js +35 -11
- package/dist/esm/components/CnLogsPage/CnLogsPage.vue2.js.map +1 -1
- package/dist/esm/components/CnLogsPage/CnLogsPage.vue3.js +3 -2
- package/dist/esm/components/CnLogsPage/CnLogsPage.vue3.js.map +1 -1
- package/dist/esm/components/CnObjectCard/CnObjectCard.vue2.js +23 -5
- package/dist/esm/components/CnObjectCard/CnObjectCard.vue2.js.map +1 -1
- package/dist/esm/components/CnObjectCard/CnObjectCard.vue3.js +1 -1
- package/dist/esm/components/CnObjectCard/CnObjectCard.vue3.js.map +1 -1
- package/dist/esm/components/CnObjectList/CnObjectList.vue2.js +19 -3
- package/dist/esm/components/CnObjectList/CnObjectList.vue2.js.map +1 -1
- package/dist/esm/components/CnObjectList/CnObjectList.vue3.js.map +1 -1
- package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue2.js +45 -15
- package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue3.js +4 -2
- package/dist/esm/components/CnObjectListWidget/CnObjectListWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnObjectRow/CnObjectRow.vue2.js +24 -6
- package/dist/esm/components/CnObjectRow/CnObjectRow.vue2.js.map +1 -1
- package/dist/esm/components/CnObjectRow/CnObjectRow.vue3.js +1 -1
- package/dist/esm/components/CnObjectRow/CnObjectRow.vue3.js.map +1 -1
- package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue2.js.map +1 -1
- package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue3.js +0 -1
- package/dist/esm/components/CnObjectSidebar/CnFilesTab.vue3.js.map +1 -1
- package/dist/esm/components/CnPageRenderer/CnPageRenderer.vue2.js.map +1 -1
- package/dist/esm/components/CnPageRenderer/CnPageRenderer.vue3.js +4 -2
- package/dist/esm/components/CnPageRenderer/CnPageRenderer.vue3.js.map +1 -1
- package/dist/esm/components/CnRowActions/CnRowActions.vue2.js +7 -2
- package/dist/esm/components/CnRowActions/CnRowActions.vue2.js.map +1 -1
- package/dist/esm/components/CnRowActions/CnRowActions.vue3.js.map +1 -1
- package/dist/esm/components/CnSpendAnalyticsWidget/CnSpendAnalyticsWidget.vue2.js +5 -3
- package/dist/esm/components/CnSpendAnalyticsWidget/CnSpendAnalyticsWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnSpendAnalyticsWidget/CnSpendAnalyticsWidget.vue3.js.map +1 -1
- package/dist/esm/components/CnTabs/CnTabs.vue2.js.map +1 -1
- package/dist/esm/components/CnTabs/CnTabs.vue3.js.map +1 -1
- package/dist/esm/components/CnTasksWidget/CnTasksWidget.vue2.js +3 -1
- package/dist/esm/components/CnTasksWidget/CnTasksWidget.vue2.js.map +1 -1
- package/dist/esm/components/CnTasksWidget/CnTasksWidget.vue3.js +1 -0
- package/dist/esm/components/CnTasksWidget/CnTasksWidget.vue3.js.map +1 -1
- package/dist/esm/integrations/builtin/analytics/CnAnalyticsCard.vue2.js +3 -1
- package/dist/esm/integrations/builtin/analytics/CnAnalyticsCard.vue2.js.map +1 -1
- package/dist/esm/integrations/builtin/analytics/CnAnalyticsCard.vue3.js.map +1 -1
- package/dist/esm/integrations/builtin/analytics/CnAnalyticsTab.vue2.js +3 -1
- package/dist/esm/integrations/builtin/analytics/CnAnalyticsTab.vue2.js.map +1 -1
- package/dist/esm/integrations/builtin/analytics/CnAnalyticsTab.vue3.js +2 -4
- package/dist/esm/integrations/builtin/analytics/CnAnalyticsTab.vue3.js.map +1 -1
- package/dist/esm/integrations/builtin/bookmarks/CnBookmarksCard.vue2.js +6 -0
- package/dist/esm/integrations/builtin/bookmarks/CnBookmarksCard.vue2.js.map +1 -1
- package/dist/esm/integrations/builtin/bookmarks/CnBookmarksCard.vue3.js +68 -53
- package/dist/esm/integrations/builtin/bookmarks/CnBookmarksCard.vue3.js.map +1 -1
- package/dist/esm/integrations/builtin/bookmarks/CnBookmarksTab.vue2.js +17 -0
- package/dist/esm/integrations/builtin/bookmarks/CnBookmarksTab.vue2.js.map +1 -1
- package/dist/esm/integrations/builtin/bookmarks/CnBookmarksTab.vue3.js +21 -19
- package/dist/esm/integrations/builtin/bookmarks/CnBookmarksTab.vue3.js.map +1 -1
- package/dist/esm/integrations/builtin/collectives/CnCollectivesCard.vue2.js +3 -1
- package/dist/esm/integrations/builtin/collectives/CnCollectivesCard.vue2.js.map +1 -1
- package/dist/esm/integrations/builtin/collectives/CnCollectivesCard.vue3.js.map +1 -1
- package/dist/esm/integrations/builtin/collectives/CnCollectivesTab.vue2.js +3 -1
- package/dist/esm/integrations/builtin/collectives/CnCollectivesTab.vue2.js.map +1 -1
- package/dist/esm/integrations/builtin/collectives/CnCollectivesTab.vue3.js +2 -4
- package/dist/esm/integrations/builtin/collectives/CnCollectivesTab.vue3.js.map +1 -1
- package/dist/esm/integrations/builtin/cospend/CnCospendCard.vue2.js +2 -1
- package/dist/esm/integrations/builtin/cospend/CnCospendCard.vue2.js.map +1 -1
- package/dist/esm/integrations/builtin/cospend/CnCospendCard.vue3.js.map +1 -1
- package/dist/esm/integrations/builtin/cospend/CnCospendTab.vue2.js +2 -1
- package/dist/esm/integrations/builtin/cospend/CnCospendTab.vue2.js.map +1 -1
- package/dist/esm/integrations/builtin/cospend/CnCospendTab.vue3.js +1 -2
- package/dist/esm/integrations/builtin/cospend/CnCospendTab.vue3.js.map +1 -1
- package/dist/esm/integrations/builtin/deck/CnDeckCard.vue2.js +2 -1
- package/dist/esm/integrations/builtin/deck/CnDeckCard.vue2.js.map +1 -1
- package/dist/esm/integrations/builtin/deck/CnDeckCard.vue3.js.map +1 -1
- package/dist/esm/integrations/builtin/deck/CnDeckTab.vue2.js +2 -1
- package/dist/esm/integrations/builtin/deck/CnDeckTab.vue2.js.map +1 -1
- package/dist/esm/integrations/builtin/deck/CnDeckTab.vue3.js +1 -2
- package/dist/esm/integrations/builtin/deck/CnDeckTab.vue3.js.map +1 -1
- package/dist/esm/integrations/builtin/email/CnEmailCard.vue2.js.map +1 -1
- package/dist/esm/integrations/builtin/email/CnEmailCard.vue3.js.map +1 -1
- package/dist/esm/integrations/builtin/email/CnEmailTab.vue2.js.map +1 -1
- package/dist/esm/integrations/builtin/email/CnEmailTab.vue3.js +1 -2
- package/dist/esm/integrations/builtin/email/CnEmailTab.vue3.js.map +1 -1
- package/dist/esm/integrations/builtin/flow/CnFlowCard.vue2.js +2 -1
- package/dist/esm/integrations/builtin/flow/CnFlowCard.vue2.js.map +1 -1
- package/dist/esm/integrations/builtin/flow/CnFlowCard.vue3.js.map +1 -1
- package/dist/esm/integrations/builtin/flow/CnFlowTab.vue2.js +2 -1
- package/dist/esm/integrations/builtin/flow/CnFlowTab.vue2.js.map +1 -1
- package/dist/esm/integrations/builtin/flow/CnFlowTab.vue3.js +1 -2
- package/dist/esm/integrations/builtin/flow/CnFlowTab.vue3.js.map +1 -1
- package/dist/esm/integrations/builtin/forms/CnFormsCard.vue2.js +3 -1
- package/dist/esm/integrations/builtin/forms/CnFormsCard.vue2.js.map +1 -1
- package/dist/esm/integrations/builtin/forms/CnFormsCard.vue3.js.map +1 -1
- package/dist/esm/integrations/builtin/forms/CnFormsTab.vue2.js +3 -1
- package/dist/esm/integrations/builtin/forms/CnFormsTab.vue2.js.map +1 -1
- package/dist/esm/integrations/builtin/forms/CnFormsTab.vue3.js +2 -4
- package/dist/esm/integrations/builtin/forms/CnFormsTab.vue3.js.map +1 -1
- package/dist/esm/integrations/builtin/maps/CnMapsCard.vue2.js +3 -1
- package/dist/esm/integrations/builtin/maps/CnMapsCard.vue2.js.map +1 -1
- package/dist/esm/integrations/builtin/maps/CnMapsCard.vue3.js.map +1 -1
- package/dist/esm/integrations/builtin/maps/CnMapsTab.vue2.js +3 -1
- package/dist/esm/integrations/builtin/maps/CnMapsTab.vue2.js.map +1 -1
- package/dist/esm/integrations/builtin/maps/CnMapsTab.vue3.js +2 -4
- package/dist/esm/integrations/builtin/maps/CnMapsTab.vue3.js.map +1 -1
- package/dist/esm/integrations/builtin/openproject/CnOpenprojectCard.vue2.js +3 -1
- package/dist/esm/integrations/builtin/openproject/CnOpenprojectCard.vue2.js.map +1 -1
- package/dist/esm/integrations/builtin/openproject/CnOpenprojectCard.vue3.js.map +1 -1
- package/dist/esm/integrations/builtin/openproject/CnOpenprojectTab.vue2.js +3 -1
- package/dist/esm/integrations/builtin/openproject/CnOpenprojectTab.vue2.js.map +1 -1
- package/dist/esm/integrations/builtin/openproject/CnOpenprojectTab.vue3.js +2 -4
- package/dist/esm/integrations/builtin/openproject/CnOpenprojectTab.vue3.js.map +1 -1
- package/dist/esm/integrations/builtin/photos/CnPhotosCard.vue2.js +2 -1
- package/dist/esm/integrations/builtin/photos/CnPhotosCard.vue2.js.map +1 -1
- package/dist/esm/integrations/builtin/photos/CnPhotosCard.vue3.js.map +1 -1
- package/dist/esm/integrations/builtin/photos/CnPhotosTab.vue2.js +2 -1
- package/dist/esm/integrations/builtin/photos/CnPhotosTab.vue2.js.map +1 -1
- package/dist/esm/integrations/builtin/photos/CnPhotosTab.vue3.js +1 -2
- package/dist/esm/integrations/builtin/photos/CnPhotosTab.vue3.js.map +1 -1
- package/dist/esm/integrations/builtin/polls/CnPollsCard.vue2.js +2 -1
- package/dist/esm/integrations/builtin/polls/CnPollsCard.vue2.js.map +1 -1
- package/dist/esm/integrations/builtin/polls/CnPollsCard.vue3.js.map +1 -1
- package/dist/esm/integrations/builtin/polls/CnPollsTab.vue2.js +2 -1
- package/dist/esm/integrations/builtin/polls/CnPollsTab.vue2.js.map +1 -1
- package/dist/esm/integrations/builtin/polls/CnPollsTab.vue3.js +1 -2
- package/dist/esm/integrations/builtin/polls/CnPollsTab.vue3.js.map +1 -1
- package/dist/esm/integrations/builtin/shares/CnSharesTab.vue2.js.map +1 -1
- package/dist/esm/integrations/builtin/shares/CnSharesTab.vue3.js +3 -6
- package/dist/esm/integrations/builtin/shares/CnSharesTab.vue3.js.map +1 -1
- package/dist/esm/integrations/builtin/talk/CnTalkCard.vue2.js +3 -1
- package/dist/esm/integrations/builtin/talk/CnTalkCard.vue2.js.map +1 -1
- package/dist/esm/integrations/builtin/talk/CnTalkCard.vue3.js.map +1 -1
- package/dist/esm/integrations/builtin/talk/CnTalkTab.vue2.js +3 -1
- package/dist/esm/integrations/builtin/talk/CnTalkTab.vue2.js.map +1 -1
- package/dist/esm/integrations/builtin/talk/CnTalkTab.vue3.js +2 -4
- package/dist/esm/integrations/builtin/talk/CnTalkTab.vue3.js.map +1 -1
- package/dist/esm/integrations/builtin/time-tracker/CnTimeTrackerCard.vue2.js +2 -1
- package/dist/esm/integrations/builtin/time-tracker/CnTimeTrackerCard.vue2.js.map +1 -1
- package/dist/esm/integrations/builtin/time-tracker/CnTimeTrackerCard.vue3.js.map +1 -1
- package/dist/esm/integrations/builtin/time-tracker/CnTimeTrackerTab.vue2.js +2 -1
- package/dist/esm/integrations/builtin/time-tracker/CnTimeTrackerTab.vue2.js.map +1 -1
- package/dist/esm/integrations/builtin/time-tracker/CnTimeTrackerTab.vue3.js +1 -2
- package/dist/esm/integrations/builtin/time-tracker/CnTimeTrackerTab.vue3.js.map +1 -1
- package/dist/esm/integrations/builtin/xwiki/CnXwikiCard.vue2.js +4 -1
- package/dist/esm/integrations/builtin/xwiki/CnXwikiCard.vue2.js.map +1 -1
- package/dist/esm/integrations/builtin/xwiki/CnXwikiCard.vue3.js.map +1 -1
- package/dist/esm/integrations/builtin/xwiki/CnXwikiTab.vue2.js +6 -3
- package/dist/esm/integrations/builtin/xwiki/CnXwikiTab.vue2.js.map +1 -1
- package/dist/esm/integrations/builtin/xwiki/CnXwikiTab.vue3.js +2 -4
- package/dist/esm/integrations/builtin/xwiki/CnXwikiTab.vue3.js.map +1 -1
- package/dist/esm/l10n/en.json.js +1 -0
- package/dist/esm/l10n/en.json.js.map +1 -1
- package/dist/esm/l10n/nl.json.js +1 -0
- package/dist/esm/l10n/nl.json.js.map +1 -1
- package/dist/esm/nextcloud-vue.css +71 -7
- package/dist/esm/utils/actionLink.js +5 -0
- package/dist/esm/utils/actionLink.js.map +1 -1
- package/dist/esm/utils/actionsDispatcher.js +16 -1
- package/dist/esm/utils/actionsDispatcher.js.map +1 -1
- package/dist/esm/utils/linkNavigation.js +6 -3
- package/dist/esm/utils/linkNavigation.js.map +1 -1
- package/dist/esm/utils/rowAuxClick.js +30 -5
- package/dist/esm/utils/rowAuxClick.js.map +1 -1
- package/dist/esm/utils/safeHref.js +18 -3
- package/dist/esm/utils/safeHref.js.map +1 -1
- package/dist/nextcloud-vue.cjs.js +809 -383
- package/dist/nextcloud-vue.cjs.js.map +2 -2
- package/dist/nextcloud-vue.css +71 -7
- package/l10n/en.json +1 -0
- package/l10n/nl.json +1 -0
- package/package.json +1 -1
- package/src/components/CnActionsBar/CnActionsBar.vue +14 -5
- package/src/components/CnActionsMenu/CnActionsMenu.vue +0 -3
- package/src/components/CnAdminSettingsShell/CnAdminSettingsShell.vue +1 -2
- package/src/components/CnBoardView/CnBoardView.vue +13 -5
- package/src/components/CnCardGrid/CnCardGrid.vue +9 -1
- package/src/components/CnContextMenu/CnContextMenu.vue +6 -1
- package/src/components/CnDataTable/CnDataTable.md +1 -1
- package/src/components/CnDataTable/CnDataTable.vue +131 -17
- package/src/components/CnDateAxisView/CnDateAxisView.vue +15 -6
- package/src/components/CnDetailPage/CnDetailPage.md +1 -1
- package/src/components/CnDetailPage/CnDetailPage.vue +29 -1
- package/src/components/CnFeaturesAndRoadmapView/CnFeaturesAndRoadmapView.vue +1 -2
- package/src/components/CnFilesBrowser/CnFilesBrowser.vue +5 -3
- package/src/components/CnFilesWidget/CnFilesWidget.vue +4 -21
- package/src/components/CnFlowRunsWidget/CnFlowRunsWidget.vue +8 -21
- package/src/components/CnFlowsPage/CnFlowsPage.vue +2 -1
- package/src/components/CnIndexPage/CnIndexPage.vue +75 -14
- package/src/components/CnIndexPage/manifestActionDispatch.js +31 -11
- package/src/components/CnIntegrationWidget/CnIntegrationWidgetEmpty.vue +1 -2
- package/src/components/CnInteractionFormWidget/CnInteractionFormWidget.vue +0 -1
- package/src/components/CnLinkButtonWidget/CnLinkButtonWidget.vue +2 -17
- package/src/components/CnLogsPage/CnLogsPage.vue +37 -12
- package/src/components/CnObjectCard/CnObjectCard.vue +24 -6
- package/src/components/CnObjectList/CnObjectList.vue +19 -3
- package/src/components/CnObjectListWidget/CnObjectListWidget.vue +47 -15
- package/src/components/CnObjectRow/CnObjectRow.vue +25 -7
- package/src/components/CnObjectSidebar/CnFilesTab.vue +0 -1
- package/src/components/CnPageRenderer/CnPageRenderer.vue +2 -0
- package/src/components/CnRowActions/CnRowActions.vue +7 -2
- package/src/components/CnSpendAnalyticsWidget/CnSpendAnalyticsWidget.vue +5 -3
- package/src/components/CnTabs/CnTabs.vue +7 -3
- package/src/components/CnTasksWidget/CnTasksWidget.vue +4 -1
- package/src/css/table.css +26 -0
- package/src/integrations/builtin/analytics/CnAnalyticsCard.vue +3 -2
- package/src/integrations/builtin/analytics/CnAnalyticsTab.vue +5 -6
- package/src/integrations/builtin/bookmarks/CnBookmarksCard.vue +12 -1
- package/src/integrations/builtin/bookmarks/CnBookmarksTab.vue +23 -6
- package/src/integrations/builtin/bookmarks/__tests__/CnBookmarksTab.spec.js +7 -2
- package/src/integrations/builtin/collectives/CnCollectivesCard.vue +3 -2
- package/src/integrations/builtin/collectives/CnCollectivesTab.vue +5 -6
- package/src/integrations/builtin/cospend/CnCospendCard.vue +2 -2
- package/src/integrations/builtin/cospend/CnCospendTab.vue +3 -4
- package/src/integrations/builtin/deck/CnDeckCard.vue +2 -2
- package/src/integrations/builtin/deck/CnDeckTab.vue +3 -4
- package/src/integrations/builtin/email/CnEmailCard.vue +6 -2
- package/src/integrations/builtin/email/CnEmailTab.vue +7 -4
- package/src/integrations/builtin/flow/CnFlowCard.vue +2 -2
- package/src/integrations/builtin/flow/CnFlowTab.vue +3 -4
- package/src/integrations/builtin/forms/CnFormsCard.vue +3 -2
- package/src/integrations/builtin/forms/CnFormsTab.vue +5 -6
- package/src/integrations/builtin/maps/CnMapsCard.vue +3 -2
- package/src/integrations/builtin/maps/CnMapsTab.vue +5 -6
- package/src/integrations/builtin/maps/__tests__/CnMapsTab.spec.js +9 -0
- package/src/integrations/builtin/openproject/CnOpenprojectCard.vue +3 -2
- package/src/integrations/builtin/openproject/CnOpenprojectTab.vue +5 -6
- package/src/integrations/builtin/photos/CnPhotosCard.vue +2 -2
- package/src/integrations/builtin/photos/CnPhotosTab.vue +3 -4
- package/src/integrations/builtin/polls/CnPollsCard.vue +2 -2
- package/src/integrations/builtin/polls/CnPollsTab.vue +3 -4
- package/src/integrations/builtin/shares/CnSharesTab.vue +3 -6
- package/src/integrations/builtin/talk/CnTalkCard.vue +3 -2
- package/src/integrations/builtin/talk/CnTalkTab.vue +5 -6
- package/src/integrations/builtin/time-tracker/CnTimeTrackerCard.vue +2 -2
- package/src/integrations/builtin/time-tracker/CnTimeTrackerTab.vue +3 -4
- package/src/integrations/builtin/xwiki/CnXwikiCard.vue +4 -2
- package/src/integrations/builtin/xwiki/CnXwikiTab.vue +7 -7
- package/src/integrations/builtin/xwiki/__tests__/CnXwikiTab.spec.js +9 -0
- package/src/utils/actionLink.js +5 -0
- package/src/utils/actionsDispatcher.js +15 -0
- package/src/utils/linkNavigation.js +6 -3
- package/src/utils/rowAuxClick.js +29 -4
- package/src/utils/safeHref.js +18 -3
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CnFlowRunsWidget.vue2.js","sources":["../../../../src/components/CnFlowRunsWidget/CnFlowRunsWidget.vue"],"sourcesContent":["<!--\n - SPDX-FileCopyrightText: 2026 Conduction B.V.\n - SPDX-License-Identifier: EUPL-1.2\n-->\n<template>\n\t<div class=\"cn-flow-runs-widget\">\n\t\t<!-- One quiet line on failure, never a leaked axios status string\n\t\t (ADR-062). The real error goes to the console. -->\n\t\t<p v-if=\"error\" class=\"cn-flow-runs-widget__error\">\n\t\t\t{{ tr('Could not load the running flows') }}\n\t\t</p>\n\t\t<!-- A configured-but-unresolved subject token also shows the loading\n\t\t state: \"no runs\" would be a claim about a case the widget has not\n\t\t identified yet. -->\n\t\t<div v-else-if=\"(loading || subjectPending) && rows.length === 0\" class=\"cn-flow-runs-widget__loading\">\n\t\t\t<NcLoadingIcon :size=\"24\" />\n\t\t</div>\n\t\t<!-- A subject where nothing EVER ran gets its own line: distinct from\n\t\t a case that is merely quiet right now. -->\n\t\t<p v-else-if=\"neverRan\" class=\"cn-flow-runs-widget__empty\">\n\t\t\t{{ tr('No flows have run yet') }}\n\t\t</p>\n\t\t<!-- Nothing running is the NORMAL state for this widget, not an\n\t\t error and not a void: one muted line, no illustration. -->\n\t\t<p v-else-if=\"rows.length === 0 && showHistory === false\" class=\"cn-flow-runs-widget__empty\">\n\t\t\t{{ emptyLabel }}\n\t\t</p>\n\t\t<template v-else>\n\t\t\t<p v-if=\"rows.length === 0\" class=\"cn-flow-runs-widget__empty\">\n\t\t\t\t{{ emptyLabel }}\n\t\t\t</p>\n\t\t\t<ul v-else class=\"cn-flow-runs-widget__list\">\n\t\t\t\t<li\n\t\t\t\t\tv-for=\"run in rows\"\n\t\t\t\t\t:key=\"run.uuid\"\n\t\t\t\t\tclass=\"cn-flow-runs-widget__row\"\n\t\t\t\t\t:class=\"{ 'cn-flow-runs-widget__row--linked': isLinked }\"\n\t\t\t\t\t:data-status=\"run.status\">\n\t\t\t\t\t<component\n\t\t\t\t\t\t:is=\"runHrefs[run.uuid] ? 'a' : 'div'\"\n\t\t\t\t\t\tclass=\"cn-flow-runs-widget__row-content\"\n\t\t\t\t\t\t:href=\"runHrefs[run.uuid] || undefined\"\n\t\t\t\t\t\t@click=\"onRowClick($event, run)\">\n\t\t\t\t\t\t<span\n\t\t\t\t\t\t\tclass=\"cn-flow-runs-widget__dot\"\n\t\t\t\t\t\t\t:class=\"`cn-flow-runs-widget__dot--${run.status}`\"\n\t\t\t\t\t\t\t:title=\"statusLabel(run.status)\" />\n\t\t\t\t\t\t<span class=\"cn-flow-runs-widget__body\">\n\t\t\t\t\t\t\t<span class=\"cn-flow-runs-widget__name\">{{ run.flowName }}</span>\n\t\t\t\t\t\t\t<span class=\"cn-flow-runs-widget__meta\">{{ metaLine(run) }}</span>\n\t\t\t\t\t\t</span>\n\t\t\t\t\t\t<span class=\"cn-flow-runs-widget__age\">{{ ageLabel(run) }}</span>\n\t\t\t\t\t</component>\n\t\t\t\t</li>\n\t\t\t</ul>\n\t\t\t<!-- The remainder is a count, never a scrollbar: the widget shows\n\t\t\t what fits its cell and states the honest total (ADR-062). -->\n\t\t\t<p v-if=\"hiddenCount > 0\" class=\"cn-flow-runs-widget__more\">\n\t\t\t\t{{ moreLabel }}\n\t\t\t</p>\n\t\t\t<!-- The subject's run history. Terminal rows sit under their own\n\t\t\t labelled section with a hollow dot and a muted name, so live\n\t\t\t and finished never rely on colour alone to tell apart. -->\n\t\t\t<template v-if=\"showHistory\">\n\t\t\t\t<p class=\"cn-flow-runs-widget__history-title\">\n\t\t\t\t\t{{ tr('Earlier runs') }}\n\t\t\t\t</p>\n\t\t\t\t<p v-if=\"completedError\" class=\"cn-flow-runs-widget__error\">\n\t\t\t\t\t{{ tr('Could not load the run history') }}\n\t\t\t\t</p>\n\t\t\t\t<ul v-else class=\"cn-flow-runs-widget__list\">\n\t\t\t\t\t<li\n\t\t\t\t\t\tv-for=\"run in completedRows\"\n\t\t\t\t\t\t:key=\"run.uuid\"\n\t\t\t\t\t\tclass=\"cn-flow-runs-widget__row cn-flow-runs-widget__row--terminal\"\n\t\t\t\t\t\t:class=\"{ 'cn-flow-runs-widget__row--linked': isLinked }\"\n\t\t\t\t\t\t:data-status=\"run.status\">\n\t\t\t\t\t\t<component\n\t\t\t\t\t\t\t:is=\"runHrefs[run.uuid] ? 'a' : 'div'\"\n\t\t\t\t\t\t\tclass=\"cn-flow-runs-widget__row-content\"\n\t\t\t\t\t\t\t:href=\"runHrefs[run.uuid] || undefined\"\n\t\t\t\t\t\t\t@click=\"onRowClick($event, run)\">\n\t\t\t\t\t\t\t<span\n\t\t\t\t\t\t\t\tclass=\"cn-flow-runs-widget__dot cn-flow-runs-widget__dot--terminal\"\n\t\t\t\t\t\t\t\t:class=\"`cn-flow-runs-widget__dot--${run.status}`\"\n\t\t\t\t\t\t\t\t:title=\"statusLabel(run.status)\" />\n\t\t\t\t\t\t\t<span class=\"cn-flow-runs-widget__body\">\n\t\t\t\t\t\t\t\t<span class=\"cn-flow-runs-widget__name\">{{ run.flowName }}</span>\n\t\t\t\t\t\t\t\t<span class=\"cn-flow-runs-widget__meta\">{{ metaLine(run) }}</span>\n\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t<span class=\"cn-flow-runs-widget__age\">{{ ageLabel(run) }}</span>\n\t\t\t\t\t\t</component>\n\t\t\t\t\t</li>\n\t\t\t\t</ul>\n\t\t\t\t<p v-if=\"completedError === '' && completedHiddenCount > 0\" class=\"cn-flow-runs-widget__more\">\n\t\t\t\t\t{{ completedMoreLabel }}\n\t\t\t\t</p>\n\t\t\t</template>\n\t\t</template>\n\t</div>\n</template>\n\n<script>\nimport { translate as t } from '@nextcloud/l10n'\nimport { NcLoadingIcon } from '@nextcloud/vue'\nimport { computed, inject, ref } from 'vue'\nimport { useEndpointSource } from '../../composables/useEndpointSource.js'\nimport { resolveObjectTokenContext } from '../../utils/detailObjectContext.js'\nimport { followLinkClick, resolveHref } from '../../utils/linkNavigation.js'\nimport { resolveFilterValue } from '../../utils/resolveFilterTokens.js'\n\n/**\n * The OpenRegister endpoint that lists the caller's live flow runs.\n *\n * Every Conduction app runs its flows on OpenRegister's ONE flow engine\n * (ADR-065), so \"what is running right now\" has a single answer for the whole\n * instance and this widget can be app-agnostic: an app places it and gets its\n * users' live runs without shipping a controller, a store or a view.\n *\n * @type {string}\n */\nconst ACTIVE_RUNS_URL = '/apps/openregister/api/flow-runs/active'\n\n/**\n * The OpenRegister endpoint that lists the finished runs for ONE subject\n * object. Subject-required by contract (openregister change\n * `flow-runs-subject-scope`): there is no org-wide \"all finished runs\" here,\n * so the widget only ever calls it with a resolved subject uuid.\n *\n * @type {string}\n */\nconst COMPLETED_RUNS_URL = '/apps/openregister/api/flow-runs/completed'\n\n/**\n * Statuses, in the order a person reads them: what is executing, what is\n * queued behind it, what is parked waiting on a timer or a child run, and\n * then the terminal set a subject's run history renders.\n *\n * @type {Record<string, string>}\n */\nconst STATUS_LABELS = {\n\trunning: 'Running',\n\tqueued: 'Queued',\n\tsuspended: 'Waiting',\n\tcompleted: 'Completed',\n\tstopped: 'Stopped',\n\tfailed: 'Failed',\n\tdead_letter: 'Failed after retries',\n}\n\n/**\n * The terminal statuses (FlowRun::TERMINAL on the OpenRegister side). Rows\n * with one of these are HISTORY: rendered in the earlier-runs section with a\n * hollow dot and a muted name, never mixed into the live list.\n *\n * @type {string[]}\n */\nconst TERMINAL_STATUSES = ['completed', 'stopped', 'failed', 'dead_letter']\n\n/**\n * CnFlowRunsWidget — the live flow runs for the viewer's organisation.\n *\n * Reads OpenRegister's `flow-runs/active` surface (non-terminal runs, scoped\n * server-side to the caller's organisation) and renders one row per run: its\n * flow's NAME, what triggered it, the step it currently sits on, and how long\n * it has been going. Placed by its registry type key `flow-runs`, so any app\n * gets the widget from a single manifest placement:\n *\n * ```js\n * { id: 'running-flows', type: 'flow-runs', title: 'Running flows',\n * content: { limit: 6, pollSeconds: 15, rowRoute: 'GraphDetail' } }\n * ```\n *\n * Why it polls: a run is a moving thing. A widget titled \"running flows\" that\n * only reflects the moment the dashboard mounted would be wrong within\n * seconds and would look identical to being right — so it refetches on an\n * interval, and stops while the tab is hidden so an idle dashboard is not a\n * background request loop.\n *\n * Statuses shown are ALL the non-terminal ones (queued / running / suspended),\n * because filtering to literally `running` would show an empty widget almost\n * always: a run holds that status only during a worker pass, while queued and\n * suspended are where live runs actually wait. See FlowRun::ACTIVE.\n *\n * Subject mode: a `content.subject` (a subject object uuid, or the\n * `@objectId` / `@object.<field>` token a detail placement authors) narrows\n * the live list to one case AND adds that case's finished runs below it, so\n * a flow that completed does not look like nothing ever happened. Tokens\n * resolve against the detail surface's injected object context, the same\n * route CnStatWidget and CnChartWidget take.\n *\n * @spec openspec/changes/cn-flow-runs-widget/specs/cn-flow-runs-widget/spec.md\n * @spec openspec/changes/cn-flow-runs-widget-subject/specs/cn-flow-runs-widget-subject/spec.md\n */\nexport default {\n\tname: 'CnFlowRunsWidget',\n\n\tcomponents: {\n\t\tNcLoadingIcon,\n\t},\n\n\tinject: {\n\t\t/**\n\t\t * Host translate function, provided by CnAppRoot (identity by default)\n\t\t * so a library string localises through the consuming app's l10n.\n\t\t */\n\t\tcnTranslate: { default: () => (key) => key },\n\t},\n\n\tprops: {\n\t\t/**\n\t\t * The widget's persisted configuration blob.\n\t\t *\n\t\t * - `limit` — rows to request AND show (1–50, default 6). The total is\n\t\t * always reported honestly regardless of this cap.\n\t\t * - `pollSeconds` — refetch interval (default 15; `0` disables polling).\n\t\t * - `rowRoute` — vue-router route NAME to open on a row click, receiving\n\t\t * the run's flow id as `:id` and the clicked run's uuid as `?run=`, so\n\t\t * the flow opens with that run inspected rather than merely open.\n\t\t * Omitted = rows are not clickable, which is correct for an app that\n\t\t * has no flow-detail page.\n\t\t * - `runRoute` — vue-router route NAME to open on a row click, receiving\n\t\t * the RUN's uuid as `:id`. Takes precedence over `rowRoute`; a row\n\t\t * without a run uuid falls back to the `rowRoute` flow-id behaviour.\n\t\t * - `subject` — a subject object uuid, or an object-context token\n\t\t * (`@objectId`, `@object.<field>`) a detail placement authors. When\n\t\t * set, the live list is filtered to that subject server-side and the\n\t\t * subject's finished runs render below it. Absent = today's org-wide\n\t\t * widget, unchanged.\n\t\t * - `emptyText` — override for the \"nothing running\" line.\n\t\t *\n\t\t * @type {{limit?: number, pollSeconds?: number, rowRoute?: string, runRoute?: string, subject?: string, emptyText?: string, title?: string}}\n\t\t */\n\t\tcontent: {\n\t\t\ttype: Object,\n\t\t\tdefault: () => ({}),\n\t\t},\n\n\t\t/**\n\t\t * Widget placement id, so the per-widget Refresh action\n\t\t * (`cn:widget:refresh`) reaches this instance's fetch.\n\t\t */\n\t\twidgetId: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * Translate function. Falls back to the injected `cnTranslate`.\n\t\t * Provide explicitly when mounting outside a CnAppRoot ancestor.\n\t\t *\n\t\t * @type {((key: string) => string)|null}\n\t\t */\n\t\ttranslate: {\n\t\t\ttype: Function,\n\t\t\tdefault: null,\n\t\t},\n\t},\n\n\tsetup(props) {\n\t\t// The shared endpoint engine owns request dedup, the short-TTL cache\n\t\t// and the cn:page:refresh / cn:widget:refresh subscriptions — the same\n\t\t// binding CnStatWidget's endpoint mode uses, so a page-level Refresh\n\t\t// updates this widget with everything else on the page.\n\t\tconst widgetIdRef = ref(props.widgetId)\n\n\t\t// The detail surface's object context, injected the same way the other\n\t\t// endpoint-bound widgets (stat / chart / audit-trail) receive it, so a\n\t\t// `subject: '@objectId'` placement binds the CURRENT object without the\n\t\t// manifest hardcoding a uuid. Null on plain dashboards — the token then\n\t\t// stays unresolved and the endpoint engine's `blocked` semantics hold\n\t\t// the fetch instead of sending a literal `@objectId` to the server.\n\t\tconst objectCtxRaw = inject('cnObjectContext', null)\n\t\tconst detailCtxRaw = inject('cnDetailObjectContext', null)\n\t\tconst tokenCtx = () => ({\n\t\t\tworkspace: {},\n\t\t\tconfig: {},\n\t\t\t...(resolveObjectTokenContext(objectCtxRaw, detailCtxRaw) || {}),\n\t\t})\n\n\t\tconst configuredSubject = () => {\n\t\t\tconst subject = props.content && props.content.subject\n\t\t\treturn (typeof subject === 'string' && subject !== '') ? subject : ''\n\t\t}\n\n\t\t/**\n\t\t * The subject uuid after token resolution: `''` when no subject is\n\t\t * configured, `null` while a token still waits on the object context,\n\t\t * else the concrete uuid.\n\t\t */\n\t\tconst resolvedSubject = computed(() => {\n\t\t\tconst subject = configuredSubject()\n\t\t\tif (subject === '') {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\tconst value = resolveFilterValue(subject, tokenCtx())\n\t\t\tif (typeof value === 'string' && value.charAt(0) === '@') {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\treturn (value === undefined || value === null) ? null : String(value)\n\t\t})\n\n\t\tconst source = () => {\n\t\t\tconst params = { limit: normaliseLimit(props.content && props.content.limit) }\n\t\t\tconst subject = configuredSubject()\n\t\t\tif (subject !== '') {\n\t\t\t\tparams.subject = subject\n\t\t\t}\n\t\t\treturn { url: ACTIVE_RUNS_URL, method: 'GET', params }\n\t\t}\n\t\tconst { data, loading, error, refetch } = useEndpointSource(source, {\n\t\t\twidgetId: widgetIdRef,\n\t\t\tctx: tokenCtx,\n\t\t})\n\n\t\t// The history half of the subject view. A null config (no subject)\n\t\t// means the composable never queries: the org-wide widget stays a\n\t\t// single-request surface, bit-identical to before.\n\t\tconst completedSource = () => {\n\t\t\tconst subject = configuredSubject()\n\t\t\tif (subject === '') {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\treturn {\n\t\t\t\turl: COMPLETED_RUNS_URL,\n\t\t\t\tmethod: 'GET',\n\t\t\t\tparams: { subject, limit: normaliseLimit(props.content && props.content.limit) },\n\t\t\t}\n\t\t}\n\t\tconst {\n\t\t\tdata: completedData,\n\t\t\tloading: completedLoading,\n\t\t\terror: completedError,\n\t\t\trefetch: refetchCompleted,\n\t\t} = useEndpointSource(completedSource, {\n\t\t\twidgetId: widgetIdRef,\n\t\t\tctx: tokenCtx,\n\t\t})\n\n\t\t// `inject` is re-read in setup so Vue 2.7 and Vue 3 resolve it\n\t\t// identically to the Options `inject` block (CnStatWidget precedent).\n\t\tconst injectedTranslate = inject('cnTranslate', null)\n\n\t\treturn {\n\t\t\tpayload: data,\n\t\t\tloading,\n\t\t\terror,\n\t\t\trefetch,\n\t\t\tcompletedPayload: completedData,\n\t\t\tcompletedLoading,\n\t\t\tcompletedError,\n\t\t\trefetchCompleted,\n\t\t\tresolvedSubject,\n\t\t\tinjectedTranslate,\n\t\t}\n\t},\n\n\tdata() {\n\t\treturn {\n\t\t\tpollTimer: null,\n\t\t}\n\t},\n\n\tcomputed: {\n\t\t/**\n\t\t * Each run's link href by uuid, resolved once per render.\n\t\t *\n\t\t * @return {{[uuid: string]: string}}\n\t\t */\n\t\trunHrefs() {\n\t\t\tconst out = {}\n\t\t\tfor (const run of [...this.rows, ...this.completedRows]) {\n\t\t\t\tout[run.uuid] = this.runHref(run)\n\t\t\t}\n\t\t\treturn out\n\t\t},\n\n\t\t/**\n\t\t * Effective translate function: the explicit prop, then the injected\n\t\t * host one, then `@nextcloud/l10n` under the library's own app id.\n\t\t *\n\t\t * @return {(key: string) => string}\n\t\t */\n\t\ttr() {\n\t\t\tconst fn = this.translate ?? this.injectedTranslate ?? this.cnTranslate\n\t\t\tif (typeof fn === 'function') {\n\t\t\t\treturn (key) => fn(key)\n\t\t\t}\n\t\t\treturn (key) => t('nextcloud-vue', key)\n\t\t},\n\n\t\t/**\n\t\t * The rows to render — the endpoint's bounded page, defensively\n\t\t * re-capped so a wider server default can never overflow the cell.\n\t\t *\n\t\t * @return {Array<object>} The run rows.\n\t\t */\n\t\trows() {\n\t\t\tconst results = (this.payload && this.payload.results) || []\n\t\t\tif (Array.isArray(results) === false) {\n\t\t\t\treturn []\n\t\t\t}\n\t\t\treturn results.slice(0, normaliseLimit(this.content.limit))\n\t\t},\n\n\t\t/**\n\t\t * How many live runs exist beyond the rendered rows.\n\t\t *\n\t\t * Read off the server's `total`, NOT the array length — the point of\n\t\t * the total is to be honest about what the cell could not fit.\n\t\t *\n\t\t * @return {number} The hidden run count (never negative).\n\t\t */\n\t\thiddenCount() {\n\t\t\tconst total = Number((this.payload && this.payload.total) ?? this.rows.length)\n\t\t\tif (Number.isFinite(total) === false) {\n\t\t\t\treturn 0\n\t\t\t}\n\t\t\treturn Math.max(0, total - this.rows.length)\n\t\t},\n\n\t\t/** The \"+N more\" line. */\n\t\tmoreLabel() {\n\t\t\treturn this.tr('+{count} more').replace('{count}', String(this.hiddenCount))\n\t\t},\n\n\t\t/** The nothing-running line. */\n\t\temptyLabel() {\n\t\t\tconst override = this.content.emptyText\n\t\t\tif (typeof override === 'string' && override !== '') {\n\t\t\t\treturn this.tr(override)\n\t\t\t}\n\t\t\treturn this.tr('No flows are running')\n\t\t},\n\n\t\t/** Whether this placement is scoped to one subject object. */\n\t\thasSubject() {\n\t\t\treturn typeof this.content.subject === 'string' && this.content.subject !== ''\n\t\t},\n\n\t\t/**\n\t\t * Whether a configured subject token still waits on the object\n\t\t * context. The widget shows its loading state instead of an empty\n\t\t * line then: \"no runs\" would be a claim about a case it has not\n\t\t * identified yet.\n\t\t *\n\t\t * @return {boolean} True while the subject is configured but unresolved.\n\t\t */\n\t\tsubjectPending() {\n\t\t\treturn this.hasSubject && this.resolvedSubject === null\n\t\t},\n\n\t\t/**\n\t\t * The subject's finished runs — the completed-runs read's bounded\n\t\t * page, re-capped like the live rows.\n\t\t *\n\t\t * @return {Array<object>} The terminal run rows (empty without a subject).\n\t\t */\n\t\tcompletedRows() {\n\t\t\tif (this.hasSubject === false) {\n\t\t\t\treturn []\n\t\t\t}\n\t\t\tconst results = (this.completedPayload && this.completedPayload.results) || []\n\t\t\tif (Array.isArray(results) === false) {\n\t\t\t\treturn []\n\t\t\t}\n\t\t\treturn results.slice(0, normaliseLimit(this.content.limit))\n\t\t},\n\n\t\t/**\n\t\t * How many finished runs exist beyond the rendered history rows, read\n\t\t * off the completed read's honest total.\n\t\t *\n\t\t * @return {number} The hidden finished-run count (never negative).\n\t\t */\n\t\tcompletedHiddenCount() {\n\t\t\tconst total = Number((this.completedPayload && this.completedPayload.total) ?? this.completedRows.length)\n\t\t\tif (Number.isFinite(total) === false) {\n\t\t\t\treturn 0\n\t\t\t}\n\t\t\treturn Math.max(0, total - this.completedRows.length)\n\t\t},\n\n\t\t/** The \"+N earlier\" line under the history section. */\n\t\tcompletedMoreLabel() {\n\t\t\treturn this.tr('+{count} earlier').replace('{count}', String(this.completedHiddenCount))\n\t\t},\n\n\t\t/**\n\t\t * Whether this subject has NO runs at all, live or finished. Rendered\n\t\t * as its own line: a case where nothing ever ran reads differently\n\t\t * from a case that is merely quiet right now.\n\t\t *\n\t\t * @return {boolean} True when both reads settled empty for a subject.\n\t\t */\n\t\tneverRan() {\n\t\t\treturn this.hasSubject\n\t\t\t\t&& this.subjectPending === false\n\t\t\t\t&& this.loading === false\n\t\t\t\t&& this.completedLoading === false\n\t\t\t\t&& this.completedError === ''\n\t\t\t\t&& this.rows.length === 0\n\t\t\t\t&& this.completedRows.length === 0\n\t\t},\n\n\t\t/** Whether the history section renders (rows or a failed history read). */\n\t\tshowHistory() {\n\t\t\treturn this.hasSubject\n\t\t\t\t&& this.subjectPending === false\n\t\t\t\t&& (this.completedRows.length > 0 || this.completedError !== '')\n\t\t},\n\n\t\t/** Whether a row click navigates (a `runRoute` or `rowRoute` is configured). */\n\t\tisLinked() {\n\t\t\treturn (typeof this.content.rowRoute === 'string' && this.content.rowRoute !== '')\n\t\t\t\t|| (typeof this.content.runRoute === 'string' && this.content.runRoute !== '')\n\t\t},\n\n\t\t/**\n\t\t * Poll interval in ms, or 0 when polling is off.\n\t\t *\n\t\t * Floored at 5s so a mis-authored `pollSeconds: 0.1` cannot turn a\n\t\t * dashboard into a request storm; `0` remains an explicit opt-out.\n\t\t *\n\t\t * @return {number} The interval in milliseconds.\n\t\t */\n\t\tpollMs() {\n\t\t\tconst raw = this.content.pollSeconds\n\t\t\tconst seconds = (raw === undefined || raw === null) ? 15 : Number(raw)\n\t\t\tif (Number.isFinite(seconds) === false || seconds <= 0) {\n\t\t\t\treturn 0\n\t\t\t}\n\t\t\treturn Math.max(5, seconds) * 1000\n\t\t},\n\t},\n\n\tmounted() {\n\t\tthis.startPolling()\n\t\tdocument.addEventListener('visibilitychange', this.onVisibilityChange)\n\t},\n\n\tbeforeUnmount() {\n\t\tthis.teardown()\n\t},\n\n\tmethods: {\n\t\t/**\n\t\t * Start (or restart) the refetch interval, unless polling is disabled.\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\tstartPolling() {\n\t\t\tthis.stopPolling()\n\t\t\tif (this.pollMs === 0 || document.hidden === true) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.pollTimer = setInterval(() => {\n\t\t\t\tthis.refetchAll()\n\t\t\t}, this.pollMs)\n\t\t},\n\n\t\t/**\n\t\t * Refetch the live read, and the history read when a subject is\n\t\t * configured — a run that finishes between polls must MOVE to the\n\t\t * history section, not vanish from the widget.\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\trefetchAll() {\n\t\t\tthis.refetch(true)\n\t\t\tif (this.hasSubject) {\n\t\t\t\tthis.refetchCompleted(true)\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Clear the refetch interval.\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\tstopPolling() {\n\t\t\tif (this.pollTimer !== null) {\n\t\t\t\tclearInterval(this.pollTimer)\n\t\t\t\tthis.pollTimer = null\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Remove the timer and the visibility listener.\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\tteardown() {\n\t\t\tthis.stopPolling()\n\t\t\tdocument.removeEventListener('visibilitychange', this.onVisibilityChange)\n\t\t},\n\n\t\t/**\n\t\t * Pause polling while the tab is hidden; refetch once and resume when\n\t\t * it comes back, so returning to the dashboard shows current state\n\t\t * rather than whatever was true when the tab was backgrounded.\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\tonVisibilityChange() {\n\t\t\tif (document.hidden === true) {\n\t\t\t\tthis.stopPolling()\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.refetchAll()\n\t\t\tthis.startPolling()\n\t\t},\n\n\t\t/**\n\t\t * Whether a run's status is terminal — the history half of the\n\t\t * subject view. Drives the hollow-dot / muted-name row treatment.\n\t\t *\n\t\t * @param {object} run The run row.\n\t\t * @return {boolean} True for a finished run.\n\t\t */\n\t\tisTerminal(run) {\n\t\t\treturn TERMINAL_STATUSES.includes(run && run.status)\n\t\t},\n\n\t\t/**\n\t\t * The human label for a status, falling back to the raw value so an\n\t\t * engine that gains a status still renders something truthful.\n\t\t *\n\t\t * @param {string} status The run status.\n\t\t * @return {string} The label.\n\t\t */\n\t\tstatusLabel(status) {\n\t\t\tconst key = STATUS_LABELS[status]\n\t\t\treturn key ? this.tr(key) : String(status || '')\n\t\t},\n\n\t\t/**\n\t\t * The secondary line: status, the step the run sits on, and what\n\t\t * triggered it — whichever of those the run actually carries.\n\t\t *\n\t\t * @param {object} run The run row.\n\t\t * @return {string} The meta line.\n\t\t */\n\t\tmetaLine(run) {\n\t\t\tconst parts = [this.statusLabel(run.status)]\n\t\t\tif (run.step) {\n\t\t\t\tparts.push(String(run.step))\n\t\t\t}\n\t\t\tif (run.trigger) {\n\t\t\t\tparts.push(String(run.trigger))\n\t\t\t}\n\t\t\treturn parts.join(' · ')\n\t\t},\n\n\t\t/**\n\t\t * How long the run has been going, as a coarse relative label.\n\t\t *\n\t\t * Coarse on purpose: a dashboard reader wants \"8m\", not a timestamp,\n\t\t * and a second-precision label on a 15s poll would read as stale.\n\t\t *\n\t\t * @param {object} run The run row.\n\t\t * @return {string} The age label, or '' when the run has no start time.\n\t\t */\n\t\tageLabel(run) {\n\t\t\tconst started = Date.parse(run.created || '')\n\t\t\tif (Number.isFinite(started) === false) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\tconst seconds = Math.max(0, Math.round((Date.now() - started) / 1000))\n\t\t\tif (seconds < 60) {\n\t\t\t\treturn this.tr('now')\n\t\t\t}\n\t\t\tconst minutes = Math.round(seconds / 60)\n\t\t\tif (minutes < 60) {\n\t\t\t\treturn `${minutes}m`\n\t\t\t}\n\t\t\tconst hours = Math.round(minutes / 60)\n\t\t\tif (hours < 24) {\n\t\t\t\treturn `${hours}h`\n\t\t\t}\n\t\t\treturn `${Math.round(hours / 24)}d`\n\t\t},\n\n\t\t/**\n\t\t * The route a run row links to, or null when it links nowhere.\n\t\t *\n\t\t * `runRoute` wins when the row carries a run uuid: on a case page a\n\t\t * click means \"show me THIS run\", and the run uuid is the deep link\n\t\t * the row contract carries. Without a `runRoute` (or on a row with no\n\t\t * uuid) `rowRoute` receives the FLOW id, the surface every\n\t\t * flow-authoring app has.\n\t\t *\n\t\t * The flow id alone answers the wrong question, so the run travels as\n\t\t * `?run=`, the address CnFlowDetail documents its `run` prop as\n\t\t * reading: a `rowRoute` click and a pasted run link open the same\n\t\t * screen.\n\t\t *\n\t\t * @param {object} run The run row.\n\t\t * @return {object|null} A vue-router location, or null.\n\t\t */\n\t\trunTarget(run) {\n\t\t\tif (this.isLinked === false || !this.$router) {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\tconst hasUuid = run.uuid !== undefined && run.uuid !== null && run.uuid !== ''\n\t\t\tconst runRoute = this.content.runRoute\n\t\t\tif (typeof runRoute === 'string' && runRoute !== '' && hasUuid) {\n\t\t\t\treturn { name: runRoute, params: { id: String(run.uuid) } }\n\t\t\t}\n\t\t\tif (typeof this.content.rowRoute !== 'string' || this.content.rowRoute === '') {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\tconst id = run.flowId\n\t\t\tif (id === undefined || id === null || id === '') {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\tconst target = { name: this.content.rowRoute, params: { id: String(id) } }\n\t\t\t// An empty `?run=` is not a deep link; CnFlowDetail's `run` prop\n\t\t\t// defaults to '' so \"no run named\" stays one value.\n\t\t\tif (hasUuid) {\n\t\t\t\ttarget.query = { run: String(run.uuid) }\n\t\t\t}\n\t\t\treturn target\n\t\t},\n\n\t\t/**\n\t\t * The href of a run row's link, or '' when the row links nowhere.\n\t\t *\n\t\t * @param {object} run The run row.\n\t\t * @return {string} The resolved href.\n\t\t */\n\t\trunHref(run) {\n\t\t\treturn resolveHref(this.runTarget(run), this.$router)\n\t\t},\n\n\t\t/**\n\t\t * Route a plain click on a run row's link in-app; modified clicks\n\t\t * are left to the browser.\n\t\t *\n\t\t * @param {MouseEvent} event The click event.\n\t\t * @param {object} run The clicked run row.\n\t\t * @return {void}\n\t\t */\n\t\tonRowClick(event, run) {\n\t\t\tfollowLinkClick(event, this.runTarget(run), this.$router)\n\t\t},\n\t},\n}\n\n/**\n * Clamp a configured row limit into the range the endpoint accepts.\n *\n * @param {number|string|undefined} raw The configured limit.\n * @return {number} A limit between 1 and 50 (default 6).\n */\nfunction normaliseLimit(raw) {\n\tconst n = Number(raw)\n\tif (Number.isFinite(n) === false || n <= 0) {\n\t\treturn 6\n\t}\n\treturn Math.min(50, Math.round(n))\n}\n</script>\n\n<style scoped>\n.cn-flow-runs-widget {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 2px;\n\tmin-width: 0;\n}\n\n.cn-flow-runs-widget__loading {\n\tdisplay: flex;\n\tjustify-content: center;\n\tpadding: 8px 0;\n}\n\n.cn-flow-runs-widget__empty,\n.cn-flow-runs-widget__error,\n.cn-flow-runs-widget__more {\n\tmargin: 0;\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 13px;\n}\n\n.cn-flow-runs-widget__more {\n\tpadding-top: 4px;\n}\n\n.cn-flow-runs-widget__list {\n\tlist-style: none;\n\tmargin: 0;\n\tpadding: 0;\n\tmin-width: 0;\n}\n\n.cn-flow-runs-widget__row {\n\tborder-bottom: 1px solid var(--color-border);\n\tmin-width: 0;\n}\n\n.cn-flow-runs-widget__row:last-child {\n\tborder-bottom: none;\n}\n\n.cn-flow-runs-widget__row-content {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 10px;\n\tpadding: 6px 0;\n\tmin-width: 0;\n\tcolor: inherit;\n\ttext-decoration: none;\n}\n\na.cn-flow-runs-widget__row-content {\n\tcursor: pointer;\n}\n\n.cn-flow-runs-widget__dot {\n\tflex: 0 0 auto;\n\twidth: 8px;\n\theight: 8px;\n\tborder-radius: 50%;\n\tbackground: var(--color-text-maxcontrast);\n}\n\n/* Executing now — the primary colour, and the only row that animates. */\n.cn-flow-runs-widget__dot--running {\n\tbackground: var(--color-primary-element, #0082c9);\n\tanimation: cn-flow-runs-pulse 1.6s ease-in-out infinite;\n}\n\n.cn-flow-runs-widget__dot--queued {\n\tbackground: var(--color-warning, #c28900);\n}\n\n.cn-flow-runs-widget__dot--suspended {\n\tbackground: var(--color-text-maxcontrast);\n}\n\n/* Terminal rows: the dot is a hollow RING, so live (filled) and finished\n (hollow) never differ by colour alone. The per-status colour still rides\n on the ring's border. */\n.cn-flow-runs-widget__dot--terminal {\n\twidth: 6px;\n\theight: 6px;\n\tbackground: transparent;\n\tborder: 2px solid var(--color-text-maxcontrast);\n}\n\n.cn-flow-runs-widget__dot--terminal.cn-flow-runs-widget__dot--completed {\n\tborder-color: var(--color-success, #2d7b41);\n}\n\n.cn-flow-runs-widget__dot--terminal.cn-flow-runs-widget__dot--failed,\n.cn-flow-runs-widget__dot--terminal.cn-flow-runs-widget__dot--dead_letter {\n\tborder-color: var(--color-error, #d91f2d);\n}\n\n.cn-flow-runs-widget__body {\n\tdisplay: flex;\n\tflex-direction: column;\n\tmin-width: 0;\n\tflex: 1 1 auto;\n}\n\n.cn-flow-runs-widget__name {\n\tfont-weight: 600;\n\twhite-space: nowrap;\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n}\n\n/* Finished runs read as history: regular weight, muted colour. The hollow\n dot and the section label carry the distinction for anyone who cannot\n rely on colour. */\n.cn-flow-runs-widget__row--terminal .cn-flow-runs-widget__name {\n\tfont-weight: 400;\n\tcolor: var(--color-text-maxcontrast);\n}\n\n/* Must sit AFTER the plain `__name` rule: the compound selector is more\n specific, and stylelint's no-descending-specificity flags the reverse\n order because the cascade then depends on specificity rather than\n source order — the shape that quietly stops working when someone adds\n a competing rule. */\na.cn-flow-runs-widget__row-content:hover .cn-flow-runs-widget__name {\n\ttext-decoration: underline;\n}\n\n.cn-flow-runs-widget__meta {\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 12px;\n\twhite-space: nowrap;\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n}\n\n.cn-flow-runs-widget__age {\n\tflex: 0 0 auto;\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 12px;\n\tfont-variant-numeric: tabular-nums;\n}\n\n/* The history section label. Small caps text, not a colour: the section\n boundary must survive monochrome. */\n.cn-flow-runs-widget__history-title {\n\tmargin: 0;\n\tpadding-top: 8px;\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 12px;\n\tfont-weight: 600;\n\ttext-transform: uppercase;\n\tletter-spacing: 0.04em;\n}\n\n@keyframes cn-flow-runs-pulse {\n\t0%, 100% { opacity: 1; }\n\t50% { opacity: 0.35; }\n}\n\n/* Respect a reduced-motion preference: the pulse is decoration, and the dot\n colour already carries the status. */\n@media (prefers-reduced-motion: reduce) {\n\t.cn-flow-runs-widget__dot--running {\n\t\tanimation: none;\n\t}\n}\n</style>\n"],"names":["t"],"mappings":";;;;;;;;AA+GA;;AAEC;;AAEA;AACA;;AAEA;;;AAGD,MAAM,eAAc,GAAI,0CAAwC;;AAEhE;;;AAGC;;AAEA;;;AAGD,MAAM,kBAAmB,GAAE,6CAA2C;;AAEtE;;;;AAIC;;;AAGD,MAAM,gBAAgB;AACrB,CAAA,OAAO,EAAE,SAAS;AAClB,CAAA,MAAM,EAAE,QAAQ;AAChB,CAAA,SAAS,EAAE,SAAS;AACpB,CAAA,SAAS,EAAE,WAAW;AACtB,CAAA,OAAO,EAAE,SAAS;AAClB,CAAA,MAAM,EAAE,QAAQ;AAChB,CAAA,WAAW,EAAE,sBAAsB;AACpC,EAAA;;AAEA;AACC;AACA;;AAEA;AACA;;AAED,MAAM,iBAAgB,GAAI,CAAC,WAAW,EAAE,SAAS,EAAE,QAAQ,EAAE,aAAa,EAAA;;AAE1E;;AAEC;AACA;AACA;AACA;;AAEA;AACA;;;;AAIA;AACA;;;;;;AAMA;AACA;AACA;AACA;;AAEA;;AAEA;;;;AAIA;AACA;AACA;AACA;;AAED,aAAe;CACd,IAAI,EAAE,kBAAkB;;CAExB,UAAU,EAAE;EACX,aAAa;EACb;;AAED,CAAA,MAAM,EAAE;AACP;;;;AAIA,EAAA,WAAW,EAAE,EAAE,OAAO,EAAE,MAAM,CAAC,GAAG,KAAK,KAAK;EAC5C;;AAED,CAAA,KAAK,EAAE;AACN;AACC;AACA;;AAEA;;;;;;AAMA;;;;;;;;AAQA;;AAEA;;;AAGD,EAAA,OAAO,EAAE;GACR,IAAI,EAAE,MAAM;AACZ,GAAA,OAAO,EAAE,OAAO,EAAE,CAAC;GACnB;;AAED;;;;AAIA,EAAA,QAAQ,EAAE;GACT,IAAI,EAAE,MAAM;GACZ,OAAO,EAAE,EAAE;GACX;;AAED;;;AAGC;AACA;;EAED,SAAS,EAAE;GACV,IAAI,EAAE,QAAQ;GACd,OAAO,EAAE,IAAI;GACb;EACD;;CAED,KAAK,CAAC,KAAK,EAAE;;;;;AAKZ,EAAA,MAAM,WAAY,GAAE,GAAG,CAAC,KAAK,CAAC,QAAQ,EAAA;;;;AAItC;;;;EAIA,MAAM,YAAW,GAAI,MAAM,CAAC,iBAAiB,EAAE,IAAI,EAAA;EACnD,MAAM,YAAW,GAAI,MAAM,CAAC,uBAAuB,EAAE,IAAI,EAAA;AACzD,EAAA,MAAM,QAAS,GAAE,OAAO;GACvB,SAAS,EAAE,EAAE;AACb,GAAA,MAAM,EAAE,EAAE;GACV,IAAI,yBAAyB,CAAC,YAAY,EAAE,YAAY,CAAA,IAAK,EAAE,CAAC;GAChE,EAAA;;EAED,MAAM,iBAAgB,GAAI,MAAM;GAC/B,MAAM,UAAU,KAAK,CAAC,OAAQ,IAAG,KAAK,CAAC,OAAO,CAAC,QAAM;GACrD,OAAO,CAAC,OAAO,OAAM,KAAM,QAAO,IAAK,OAAM,KAAM,EAAE,IAAI,OAAQ,GAAE,EAAC;AACrE,IAAA;;AAEA;;;AAGC;;AAED,EAAA,MAAM,eAAgB,GAAE,QAAQ,CAAC,MAAM;AACtC,GAAA,MAAM,OAAQ,GAAE,iBAAiB,GAAC;AAClC,GAAA,IAAI,OAAM,KAAM,EAAE,EAAE;AACnB,IAAA,OAAO,EAAC;AACT,IAAA;GACA,MAAM,KAAM,GAAE,kBAAkB,CAAC,OAAO,EAAE,QAAQ,EAAE,EAAA;GACpD,IAAI,OAAO,KAAI,KAAM,YAAY,KAAK,CAAC,MAAM,CAAC,CAAC,CAAE,KAAI,GAAG,EAAE;AACzD,IAAA,OAAO,IAAG;AACX,IAAA;GACA,OAAO,CAAC,KAAM,KAAI,SAAQ,IAAK,KAAI,KAAM,IAAI,IAAI,OAAO,MAAM,CAAC,KAAK,CAAA;GACpE,EAAA;;AAED,EAAA,MAAM,MAAO,GAAE,MAAM;AACpB,GAAA,MAAM,MAAK,GAAI,EAAE,KAAK,EAAE,cAAc,CAAC,KAAK,CAAC,OAAM,IAAK,KAAK,CAAC,OAAO,CAAC,KAAK,CAAE,GAAA;AAC7E,GAAA,MAAM,OAAQ,GAAE,iBAAiB,GAAC;AAClC,GAAA,IAAI,OAAM,KAAM,EAAE,EAAE;AACnB,IAAA,MAAM,CAAC,UAAU,QAAM;AACxB,IAAA;GACA,OAAO,EAAE,GAAG,EAAE,eAAe,EAAE,MAAM,EAAE,KAAK,EAAE,MAAO,EAAA;AACtD,IAAA;EACA,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,OAAM,EAAI,GAAE,iBAAiB,CAAC,MAAM,EAAE;AACnE,GAAA,QAAQ,EAAE,WAAW;GACrB,GAAG,EAAE,QAAQ;GACb,EAAA;;;;AAID;EACA,MAAM,kBAAkB,MAAM;AAC7B,GAAA,MAAM,OAAQ,GAAE,iBAAiB,GAAC;AAClC,GAAA,IAAI,OAAM,KAAM,EAAE,EAAE;AACnB,IAAA,OAAO,IAAG;AACX,IAAA;AACA,GAAA,OAAO;IACN,GAAG,EAAE,kBAAkB;IACvB,MAAM,EAAE,KAAK;AACb,IAAA,MAAM,EAAE,EAAE,OAAO,EAAE,KAAK,EAAE,cAAc,CAAC,KAAK,CAAC,OAAM,IAAK,KAAK,CAAC,OAAO,CAAC,KAAK,GAAG;AACjF,IAAA;AACD,IAAA;EACA,MAAM;AACL,GAAA,IAAI,EAAE,aAAa;GACnB,OAAO,EAAE,gBAAgB;AACzB,GAAA,KAAK,EAAE,cAAc;GACrB,OAAO,EAAE,gBAAgB;AAC1B,GAAA,GAAI,iBAAiB,CAAC,eAAe,EAAE;AACtC,GAAA,QAAQ,EAAE,WAAW;GACrB,GAAG,EAAE,QAAQ;GACb,EAAA;;;;EAID,MAAM,iBAAgB,GAAI,MAAM,CAAC,aAAa,EAAE,IAAI,EAAA;;AAEpD,EAAA,OAAO;GACN,OAAO,EAAE,IAAI;AACb,GAAA,OAAO;GACP,KAAK;AACL,GAAA,OAAO;AACP,GAAA,gBAAgB,EAAE,aAAa;AAC/B,GAAA,gBAAgB;AAChB,GAAA,cAAc;AACd,GAAA,gBAAgB;AAChB,GAAA,eAAe;AACf,GAAA,iBAAiB;AAClB,GAAA;EACA;;AAED,CAAA,IAAI,GAAG;AACN,EAAA,OAAO;AACN,GAAA,SAAS,EAAE,IAAI;AAChB,GAAA;EACA;;AAED,CAAA,QAAQ,EAAE;AACT;;AAEC;AACA;;EAED,QAAQ,GAAG;GACV,MAAM,GAAE,GAAI,GAAC;GACb,KAAK,MAAM,GAAE,IAAK,CAAC,GAAG,IAAI,CAAC,IAAI,EAAE,GAAG,IAAI,CAAC,aAAa,CAAC,EAAE;AACxD,IAAA,GAAG,CAAC,GAAG,CAAC,IAAI,CAAA,GAAI,IAAI,CAAC,OAAO,CAAC,GAAG,EAAA;AACjC,IAAA;AACA,GAAA,OAAO,GAAE;GACT;;AAED;;;AAGC;;;EAGD,EAAE,GAAG;AACJ,GAAA,MAAM,EAAC,GAAI,IAAI,CAAC,SAAU,IAAG,IAAI,CAAC,iBAAkB,IAAG,IAAI,CAAC,YAAU;GACtE,IAAI,OAAO,EAAG,KAAI,UAAU,EAAE;AAC7B,IAAA,OAAO,CAAC,GAAG,KAAK,EAAE,CAAC,GAAG,CAAA;AACvB,IAAA;AACA,GAAA,OAAO,CAAC,GAAG,KAAKA,SAAC,CAAC,eAAe,EAAE,GAAG,CAAA;GACtC;;AAED;;;AAGC;AACA;;AAED,EAAA,IAAI,GAAG;GACN,MAAM,OAAM,GAAI,CAAC,IAAI,CAAC,OAAM,IAAK,IAAI,CAAC,OAAO,CAAC,OAAO,KAAK,GAAC;AAC3D,GAAA,IAAI,KAAK,CAAC,OAAO,CAAC,OAAO,CAAE,KAAI,KAAK,EAAE;AACrC,IAAA,OAAO,EAAC;AACT,IAAA;GACA,OAAO,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,cAAc,CAAC,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,CAAA;GAC1D;;AAED;AACC;AACA;;;AAGA;;;EAGD,WAAW,GAAG;AACb,GAAA,MAAM,KAAI,GAAI,MAAM,CAAC,CAAC,IAAI,CAAC,OAAQ,IAAG,IAAI,CAAC,OAAO,CAAC,KAAK,KAAK,IAAI,CAAC,IAAI,CAAC,MAAM,EAAA;AAC7E,GAAA,IAAI,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAE,KAAI,KAAK,EAAE;AACrC,IAAA,OAAO,CAAA;AACR,IAAA;AACA,GAAA,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,QAAQ,IAAI,CAAC,IAAI,CAAC,MAAM,CAAA;GAC3C;;AAED;EACA,SAAS,GAAG;AACX,GAAA,OAAO,IAAI,CAAC,EAAE,CAAC,eAAe,CAAC,CAAC,OAAO,CAAC,SAAS,EAAE,MAAM,CAAC,IAAI,CAAC,WAAW,CAAC,CAAA;GAC3E;;;EAGD,UAAU,GAAG;AACZ,GAAA,MAAM,QAAS,GAAE,IAAI,CAAC,OAAO,CAAC,UAAQ;AACtC,GAAA,IAAI,OAAO,aAAa,YAAY,QAAS,KAAI,EAAE,EAAE;IACpD,OAAO,IAAI,CAAC,EAAE,CAAC,QAAQ,CAAA;AACxB,IAAA;AACA,GAAA,OAAO,IAAI,CAAC,EAAE,CAAC,sBAAsB,CAAA;GACrC;;;EAGD,UAAU,GAAG;AACZ,GAAA,OAAO,OAAO,IAAI,CAAC,OAAO,CAAC,YAAY,QAAO,IAAK,IAAI,CAAC,OAAO,CAAC,OAAM,KAAM,EAAC;GAC7E;;AAED;;;;AAIC;AACA;;;AAGD,EAAA,cAAc,GAAG;GAChB,OAAO,IAAI,CAAC,UAAW,IAAG,IAAI,CAAC,eAAc,KAAM,IAAG;GACtD;;AAED;;AAEC;AACA;AACA;;AAED,EAAA,aAAa,GAAG;GACf,IAAI,IAAI,CAAC,UAAS,KAAM,KAAK,EAAE;AAC9B,IAAA,OAAO,EAAC;AACT,IAAA;AACA,GAAA,MAAM,OAAM,GAAI,CAAC,IAAI,CAAC,gBAAe,IAAK,IAAI,CAAC,gBAAgB,CAAC,OAAO,KAAK,GAAC;AAC7E,GAAA,IAAI,KAAK,CAAC,OAAO,CAAC,OAAO,CAAE,KAAI,KAAK,EAAE;AACrC,IAAA,OAAO,EAAC;AACT,IAAA;GACA,OAAO,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,cAAc,CAAC,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,CAAA;GAC1D;;AAED;;AAEC;AACA;;;EAGD,oBAAoB,GAAG;GACtB,MAAM,KAAM,GAAE,MAAM,CAAC,CAAC,IAAI,CAAC,gBAAiB,IAAG,IAAI,CAAC,gBAAgB,CAAC,KAAK,KAAK,IAAI,CAAC,aAAa,CAAC,MAAM,EAAA;AACxG,GAAA,IAAI,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAE,KAAI,KAAK,EAAE;AACrC,IAAA,OAAO,CAAA;AACR,IAAA;GACA,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,KAAI,GAAI,IAAI,CAAC,aAAa,CAAC,MAAM,CAAA;GACpD;;AAED;AACA,EAAA,kBAAkB,GAAG;AACpB,GAAA,OAAO,IAAI,CAAC,EAAE,CAAC,kBAAkB,CAAC,CAAC,OAAO,CAAC,SAAS,EAAE,MAAM,CAAC,IAAI,CAAC,oBAAoB,CAAC,CAAA;GACvF;;AAED;;;AAGC;AACA;;;EAGD,QAAQ,GAAG;AACV,GAAA,OAAO,IAAI,CAAC,UAAS;OACjB,IAAI,CAAC,cAAa,KAAM,KAAI;AAC/B,OAAG,IAAI,CAAC,YAAY,KAAI;AACxB,OAAG,IAAI,CAAC,gBAAe,KAAM,KAAI;OAC9B,IAAI,CAAC,cAAe,KAAI,EAAC;AAC5B,OAAG,IAAI,CAAC,IAAI,CAAC,MAAO,KAAI,CAAA;AACxB,OAAG,IAAI,CAAC,aAAa,CAAC,MAAK,KAAM,CAAA;GAClC;;AAED;EACA,WAAW,GAAG;AACb,GAAA,OAAO,IAAI,CAAC,UAAS;OACjB,IAAI,CAAC,cAAa,KAAM,KAAI;QAC3B,IAAI,CAAC,aAAa,CAAC,MAAK,GAAI,CAAE,IAAG,IAAI,CAAC,cAAa,KAAM,EAAE,CAAA;GAChE;;AAED;EACA,QAAQ,GAAG;AACV,GAAA,OAAO,CAAC,OAAO,IAAI,CAAC,OAAO,CAAC,aAAa,QAAO,IAAK,IAAI,CAAC,OAAO,CAAC,QAAS,KAAI,EAAE;AAChF,QAAI,OAAO,IAAI,CAAC,OAAO,CAAC,QAAS,KAAI,YAAY,IAAI,CAAC,OAAO,CAAC,QAAS,KAAI,EAAE,CAAA;GAC9E;;AAED;AACC;AACA;;;AAGA;AACA;;AAED,EAAA,MAAM,GAAG;AACR,GAAA,MAAM,GAAI,GAAE,IAAI,CAAC,OAAO,CAAC,YAAU;GACnC,MAAM,OAAQ,GAAE,CAAC,GAAI,KAAI,aAAa,GAAI,KAAI,IAAI,IAAI,EAAC,GAAI,MAAM,CAAC,GAAG,EAAA;GACrE,IAAI,MAAM,CAAC,QAAQ,CAAC,OAAO,CAAA,KAAM,KAAM,IAAG,OAAQ,IAAG,CAAC,EAAE;AACvD,IAAA,OAAO,CAAA;AACR,IAAA;GACA,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,OAAO,CAAE,GAAE,IAAG;GACjC;EACD;;AAED,CAAA,OAAO,GAAG;AACT,EAAA,IAAI,CAAC,YAAY,GAAC;AAClB,EAAA,QAAQ,CAAC,gBAAgB,CAAC,kBAAkB,EAAE,IAAI,CAAC,kBAAkB,EAAA;EACrE;;AAED,CAAA,aAAa,GAAG;AACf,EAAA,IAAI,CAAC,QAAQ,GAAC;EACd;;AAED,CAAA,OAAO,EAAE;AACR;;AAEC;;;AAGD,EAAA,YAAY,GAAG;AACd,GAAA,IAAI,CAAC,WAAW,GAAC;AACjB,GAAA,IAAI,IAAI,CAAC,MAAO,KAAI,CAAA,IAAK,QAAQ,CAAC,MAAK,KAAM,IAAI,EAAE;IAClD,MAAK;AACN,IAAA;AACA,GAAA,IAAI,CAAC,SAAU,GAAE,WAAW,CAAC,MAAM;AAClC,IAAA,IAAI,CAAC,UAAU,GAAC;IAChB,EAAE,IAAI,CAAC,MAAM,EAAA;GACd;;AAED;;;AAGC;AACA;;;EAGD,UAAU,GAAG;AACZ,GAAA,IAAI,CAAC,OAAO,CAAC,IAAI,EAAA;AACjB,GAAA,IAAI,IAAI,CAAC,UAAU,EAAE;IACpB,IAAI,CAAC,gBAAgB,CAAC,IAAI,EAAA;AAC3B,IAAA;GACA;;AAED;;AAEC;;;EAGD,WAAW,GAAG;GACb,IAAI,IAAI,CAAC,SAAQ,KAAM,IAAI,EAAE;IAC5B,aAAa,CAAC,IAAI,CAAC,SAAS,EAAA;AAC5B,IAAA,IAAI,CAAC,SAAQ,GAAI,KAAG;AACrB,IAAA;GACA;;AAED;AACC;AACA;;;EAGD,QAAQ,GAAG;AACV,GAAA,IAAI,CAAC,WAAW,GAAC;AACjB,GAAA,QAAQ,CAAC,mBAAmB,CAAC,kBAAkB,EAAE,IAAI,CAAC,kBAAkB,EAAA;GACxE;;AAED;;;;AAIC;;;AAGD,EAAA,kBAAkB,GAAG;GACpB,IAAI,QAAQ,CAAC,MAAK,KAAM,IAAI,EAAE;AAC7B,IAAA,IAAI,CAAC,WAAW,GAAC;IACjB,MAAK;AACN,IAAA;AACA,GAAA,IAAI,CAAC,UAAU,GAAC;AAChB,GAAA,IAAI,CAAC,YAAY,GAAC;GAClB;;AAED;;;AAGC;;AAEA;;AAED,EAAA,UAAU,CAAC,GAAG,EAAE;GACf,OAAO,iBAAiB,CAAC,QAAQ,CAAC,GAAE,IAAK,GAAG,CAAC,MAAM,CAAA;GACnD;;AAED;;;AAGC;AACA;;;AAGD,EAAA,WAAW,CAAC,MAAM,EAAE;GACnB,MAAM,GAAE,GAAI,aAAa,CAAC,MAAM,EAAA;AAChC,GAAA,OAAO,GAAE,GAAI,IAAI,CAAC,EAAE,CAAC,GAAG,CAAA,GAAI,MAAM,CAAC,UAAU,EAAE,CAAA;GAC/C;;AAED;;;AAGC;;;;EAID,QAAQ,CAAC,GAAG,EAAE;AACb,GAAA,MAAM,KAAM,GAAE,CAAC,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,MAAM,CAAC,EAAA;GAC3C,IAAI,GAAG,CAAC,IAAI,EAAE;IACb,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,EAAA;AAC5B,IAAA;AACA,GAAA,IAAI,GAAG,CAAC,OAAO,EAAE;AAChB,IAAA,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC,EAAA;AAC/B,IAAA;AACA,GAAA,OAAO,KAAK,CAAC,IAAI,CAAC,KAAK,CAAA;GACvB;;AAED;;AAEC;;;AAGA;;;;EAID,QAAQ,CAAC,GAAG,EAAE;AACb,GAAA,MAAM,UAAU,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,WAAW,EAAE,EAAA;AAC5C,GAAA,IAAI,MAAM,CAAC,QAAQ,CAAC,OAAO,CAAA,KAAM,KAAK,EAAE;AACvC,IAAA,OAAO,EAAC;AACT,IAAA;GACA,MAAM,OAAM,GAAI,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,GAAG,EAAC,GAAI,OAAO,IAAI,IAAI,CAAC,EAAA;AACrE,GAAA,IAAI,OAAQ,GAAE,EAAE,EAAE;AACjB,IAAA,OAAO,IAAI,CAAC,EAAE,CAAC,KAAK,CAAA;AACrB,IAAA;AACA,GAAA,MAAM,OAAM,GAAI,IAAI,CAAC,KAAK,CAAC,OAAQ,GAAE,EAAE,EAAA;AACvC,GAAA,IAAI,OAAQ,GAAE,EAAE,EAAE;AACjB,IAAA,OAAO,CAAC,EAAE,OAAO,CAAC,CAAC,CAAA;AACpB,IAAA;AACA,GAAA,MAAM,KAAM,GAAE,IAAI,CAAC,KAAK,CAAC,OAAQ,GAAE,EAAE,EAAA;GACrC,IAAI,KAAM,GAAE,EAAE,EAAE;AACf,IAAA,OAAO,CAAC,EAAE,KAAK,CAAC,CAAC,CAAA;AAClB,IAAA;AACA,GAAA,OAAO,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,KAAI,GAAI,EAAE,CAAC,CAAC,CAAC,CAAA;GAClC;;AAED;;AAEC;;;;;AAKA;AACA;;;;AAIA;AACA;;;;AAID,EAAA,SAAS,CAAC,GAAG,EAAE;AACd,GAAA,IAAI,IAAI,CAAC,QAAS,KAAI,KAAM,IAAG,CAAC,IAAI,CAAC,OAAO,EAAE;AAC7C,IAAA,OAAO,IAAG;AACX,IAAA;AACA,GAAA,MAAM,OAAQ,GAAE,GAAG,CAAC,IAAG,KAAM,SAAU,IAAG,GAAG,CAAC,IAAK,KAAI,IAAG,IAAK,GAAG,CAAC,IAAG,KAAM,GAAC;AAC7E,GAAA,MAAM,QAAS,GAAE,IAAI,CAAC,OAAO,CAAC,SAAO;GACrC,IAAI,OAAO,QAAO,KAAM,QAAS,IAAG,QAAO,KAAM,EAAC,IAAK,OAAO,EAAE;IAC/D,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,MAAM,CAAC,GAAG,CAAC,IAAI,CAAA,EAAI,EAAA;AAC3D,IAAA;AACA,GAAA,IAAI,OAAO,IAAI,CAAC,OAAO,CAAC,QAAO,KAAM,QAAO,IAAK,IAAI,CAAC,OAAO,CAAC,QAAO,KAAM,EAAE,EAAE;AAC9E,IAAA,OAAO,IAAG;AACX,IAAA;AACA,GAAA,MAAM,EAAG,GAAE,GAAG,CAAC,OAAK;AACpB,GAAA,IAAI,EAAG,KAAI,aAAa,EAAC,KAAM,IAAK,IAAG,OAAO,EAAE,EAAE;AACjD,IAAA,OAAO,IAAG;AACX,IAAA;AACA,GAAA,MAAM,MAAK,GAAI,EAAE,IAAI,EAAE,IAAI,CAAC,OAAO,CAAC,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,MAAM,CAAC,EAAE,CAAA,EAAI,GAAA;;AAEzE;GACA,IAAI,OAAO,EAAE;AACZ,IAAA,MAAM,CAAC,KAAI,GAAI,EAAE,GAAG,EAAE,MAAM,CAAC,GAAG,CAAC,IAAI,CAAE,GAAA;AACxC,IAAA;GACA,OAAO,MAAK;GACZ;;AAED;;AAEC;;AAEA;;EAED,OAAO,CAAC,GAAG,EAAE;GACZ,OAAO,WAAW,CAAC,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,EAAE,IAAI,CAAC,OAAO,CAAA;GACpD;;AAED;;AAEC;AACA;AACA;AACA;;;AAGD,EAAA,UAAU,CAAC,KAAK,EAAE,GAAG,EAAE;GACtB,eAAe,CAAC,KAAK,EAAE,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,EAAE,IAAI,CAAC,OAAO,EAAA;GACxD;EACD;AACF,EAAA;;AAEA;;AAEC;;AAEA;;AAED,SAAS,cAAc,CAAC,GAAG,EAAE;AAC5B,CAAA,MAAM,IAAI,MAAM,CAAC,GAAG,EAAA;AACpB,CAAA,IAAI,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAE,KAAI,KAAI,IAAK,CAAA,IAAK,CAAC,EAAE;AAC3C,EAAA,OAAO,CAAA;AACR,EAAA;AACA,CAAA,OAAO,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAA;AAClC;;;;"}
|
|
1
|
+
{"version":3,"file":"CnFlowRunsWidget.vue2.js","sources":["../../../../src/components/CnFlowRunsWidget/CnFlowRunsWidget.vue"],"sourcesContent":["<!--\n - SPDX-FileCopyrightText: 2026 Conduction B.V.\n - SPDX-License-Identifier: EUPL-1.2\n-->\n<template>\n\t<div class=\"cn-flow-runs-widget\">\n\t\t<!-- One quiet line on failure, never a leaked axios status string\n\t\t (ADR-062). The real error goes to the console. -->\n\t\t<p v-if=\"error\" class=\"cn-flow-runs-widget__error\">\n\t\t\t{{ tr('Could not load the running flows') }}\n\t\t</p>\n\t\t<!-- A configured-but-unresolved subject token also shows the loading\n\t\t state: \"no runs\" would be a claim about a case the widget has not\n\t\t identified yet. -->\n\t\t<div v-else-if=\"(loading || subjectPending) && rows.length === 0\" class=\"cn-flow-runs-widget__loading\">\n\t\t\t<NcLoadingIcon :size=\"24\" />\n\t\t</div>\n\t\t<!-- A subject where nothing EVER ran gets its own line: distinct from\n\t\t a case that is merely quiet right now. -->\n\t\t<p v-else-if=\"neverRan\" class=\"cn-flow-runs-widget__empty\">\n\t\t\t{{ tr('No flows have run yet') }}\n\t\t</p>\n\t\t<!-- Nothing running is the NORMAL state for this widget, not an\n\t\t error and not a void: one muted line, no illustration. -->\n\t\t<p v-else-if=\"rows.length === 0 && showHistory === false\" class=\"cn-flow-runs-widget__empty\">\n\t\t\t{{ emptyLabel }}\n\t\t</p>\n\t\t<template v-else>\n\t\t\t<p v-if=\"rows.length === 0\" class=\"cn-flow-runs-widget__empty\">\n\t\t\t\t{{ emptyLabel }}\n\t\t\t</p>\n\t\t\t<ul v-else class=\"cn-flow-runs-widget__list\">\n\t\t\t\t<li\n\t\t\t\t\tv-for=\"(run, index) in rows\"\n\t\t\t\t\t:key=\"run.uuid || `row-${index}`\"\n\t\t\t\t\tclass=\"cn-flow-runs-widget__row\"\n\t\t\t\t\t:class=\"{ 'cn-flow-runs-widget__row--linked': isLinked }\"\n\t\t\t\t\t:data-status=\"run.status\">\n\t\t\t\t\t<component\n\t\t\t\t\t\t:is=\"runHref(run) ? 'a' : 'div'\"\n\t\t\t\t\t\tclass=\"cn-flow-runs-widget__row-content\"\n\t\t\t\t\t\t:href=\"runHref(run) || undefined\"\n\t\t\t\t\t\t@click=\"onRowClick($event, run)\">\n\t\t\t\t\t\t<span\n\t\t\t\t\t\t\tclass=\"cn-flow-runs-widget__dot\"\n\t\t\t\t\t\t\t:class=\"`cn-flow-runs-widget__dot--${run.status}`\"\n\t\t\t\t\t\t\t:title=\"statusLabel(run.status)\" />\n\t\t\t\t\t\t<span class=\"cn-flow-runs-widget__body\">\n\t\t\t\t\t\t\t<span class=\"cn-flow-runs-widget__name\">{{ run.flowName }}</span>\n\t\t\t\t\t\t\t<span class=\"cn-flow-runs-widget__meta\">{{ metaLine(run) }}</span>\n\t\t\t\t\t\t</span>\n\t\t\t\t\t\t<span class=\"cn-flow-runs-widget__age\">{{ ageLabel(run) }}</span>\n\t\t\t\t\t</component>\n\t\t\t\t</li>\n\t\t\t</ul>\n\t\t\t<!-- The remainder is a count, never a scrollbar: the widget shows\n\t\t\t what fits its cell and states the honest total (ADR-062). -->\n\t\t\t<p v-if=\"hiddenCount > 0\" class=\"cn-flow-runs-widget__more\">\n\t\t\t\t{{ moreLabel }}\n\t\t\t</p>\n\t\t\t<!-- The subject's run history. Terminal rows sit under their own\n\t\t\t labelled section with a hollow dot and a muted name, so live\n\t\t\t and finished never rely on colour alone to tell apart. -->\n\t\t\t<template v-if=\"showHistory\">\n\t\t\t\t<p class=\"cn-flow-runs-widget__history-title\">\n\t\t\t\t\t{{ tr('Earlier runs') }}\n\t\t\t\t</p>\n\t\t\t\t<p v-if=\"completedError\" class=\"cn-flow-runs-widget__error\">\n\t\t\t\t\t{{ tr('Could not load the run history') }}\n\t\t\t\t</p>\n\t\t\t\t<ul v-else class=\"cn-flow-runs-widget__list\">\n\t\t\t\t\t<li\n\t\t\t\t\t\tv-for=\"(run, index) in completedRows\"\n\t\t\t\t\t\t:key=\"run.uuid || `completed-${index}`\"\n\t\t\t\t\t\tclass=\"cn-flow-runs-widget__row cn-flow-runs-widget__row--terminal\"\n\t\t\t\t\t\t:class=\"{ 'cn-flow-runs-widget__row--linked': isLinked }\"\n\t\t\t\t\t\t:data-status=\"run.status\">\n\t\t\t\t\t\t<component\n\t\t\t\t\t\t\t:is=\"runHref(run) ? 'a' : 'div'\"\n\t\t\t\t\t\t\tclass=\"cn-flow-runs-widget__row-content\"\n\t\t\t\t\t\t\t:href=\"runHref(run) || undefined\"\n\t\t\t\t\t\t\t@click=\"onRowClick($event, run)\">\n\t\t\t\t\t\t\t<span\n\t\t\t\t\t\t\t\tclass=\"cn-flow-runs-widget__dot cn-flow-runs-widget__dot--terminal\"\n\t\t\t\t\t\t\t\t:class=\"`cn-flow-runs-widget__dot--${run.status}`\"\n\t\t\t\t\t\t\t\t:title=\"statusLabel(run.status)\" />\n\t\t\t\t\t\t\t<span class=\"cn-flow-runs-widget__body\">\n\t\t\t\t\t\t\t\t<span class=\"cn-flow-runs-widget__name\">{{ run.flowName }}</span>\n\t\t\t\t\t\t\t\t<span class=\"cn-flow-runs-widget__meta\">{{ metaLine(run) }}</span>\n\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t<span class=\"cn-flow-runs-widget__age\">{{ ageLabel(run) }}</span>\n\t\t\t\t\t\t</component>\n\t\t\t\t\t</li>\n\t\t\t\t</ul>\n\t\t\t\t<p v-if=\"completedError === '' && completedHiddenCount > 0\" class=\"cn-flow-runs-widget__more\">\n\t\t\t\t\t{{ completedMoreLabel }}\n\t\t\t\t</p>\n\t\t\t</template>\n\t\t</template>\n\t</div>\n</template>\n\n<script>\nimport { translate as t } from '@nextcloud/l10n'\nimport { NcLoadingIcon } from '@nextcloud/vue'\nimport { computed, inject, ref } from 'vue'\nimport { useEndpointSource } from '../../composables/useEndpointSource.js'\nimport { resolveObjectTokenContext } from '../../utils/detailObjectContext.js'\nimport { followLinkClick, resolveHref } from '../../utils/linkNavigation.js'\nimport { resolveFilterValue } from '../../utils/resolveFilterTokens.js'\n\n/**\n * The OpenRegister endpoint that lists the caller's live flow runs.\n *\n * Every Conduction app runs its flows on OpenRegister's ONE flow engine\n * (ADR-065), so \"what is running right now\" has a single answer for the whole\n * instance and this widget can be app-agnostic: an app places it and gets its\n * users' live runs without shipping a controller, a store or a view.\n *\n * @type {string}\n */\nconst ACTIVE_RUNS_URL = '/apps/openregister/api/flow-runs/active'\n\n/**\n * The OpenRegister endpoint that lists the finished runs for ONE subject\n * object. Subject-required by contract (openregister change\n * `flow-runs-subject-scope`): there is no org-wide \"all finished runs\" here,\n * so the widget only ever calls it with a resolved subject uuid.\n *\n * @type {string}\n */\nconst COMPLETED_RUNS_URL = '/apps/openregister/api/flow-runs/completed'\n\n/**\n * Statuses, in the order a person reads them: what is executing, what is\n * queued behind it, what is parked waiting on a timer or a child run, and\n * then the terminal set a subject's run history renders.\n *\n * @type {Record<string, string>}\n */\nconst STATUS_LABELS = {\n\trunning: 'Running',\n\tqueued: 'Queued',\n\tsuspended: 'Waiting',\n\tcompleted: 'Completed',\n\tstopped: 'Stopped',\n\tfailed: 'Failed',\n\tdead_letter: 'Failed after retries',\n}\n\n/**\n * The terminal statuses (FlowRun::TERMINAL on the OpenRegister side). Rows\n * with one of these are HISTORY: rendered in the earlier-runs section with a\n * hollow dot and a muted name, never mixed into the live list.\n *\n * @type {string[]}\n */\nconst TERMINAL_STATUSES = ['completed', 'stopped', 'failed', 'dead_letter']\n\n/**\n * CnFlowRunsWidget — the live flow runs for the viewer's organisation.\n *\n * Reads OpenRegister's `flow-runs/active` surface (non-terminal runs, scoped\n * server-side to the caller's organisation) and renders one row per run: its\n * flow's NAME, what triggered it, the step it currently sits on, and how long\n * it has been going. Placed by its registry type key `flow-runs`, so any app\n * gets the widget from a single manifest placement:\n *\n * ```js\n * { id: 'running-flows', type: 'flow-runs', title: 'Running flows',\n * content: { limit: 6, pollSeconds: 15, rowRoute: 'GraphDetail' } }\n * ```\n *\n * Why it polls: a run is a moving thing. A widget titled \"running flows\" that\n * only reflects the moment the dashboard mounted would be wrong within\n * seconds and would look identical to being right — so it refetches on an\n * interval, and stops while the tab is hidden so an idle dashboard is not a\n * background request loop.\n *\n * Statuses shown are ALL the non-terminal ones (queued / running / suspended),\n * because filtering to literally `running` would show an empty widget almost\n * always: a run holds that status only during a worker pass, while queued and\n * suspended are where live runs actually wait. See FlowRun::ACTIVE.\n *\n * Subject mode: a `content.subject` (a subject object uuid, or the\n * `@objectId` / `@object.<field>` token a detail placement authors) narrows\n * the live list to one case AND adds that case's finished runs below it, so\n * a flow that completed does not look like nothing ever happened. Tokens\n * resolve against the detail surface's injected object context, the same\n * route CnStatWidget and CnChartWidget take.\n *\n * @spec openspec/changes/cn-flow-runs-widget/specs/cn-flow-runs-widget/spec.md\n * @spec openspec/changes/cn-flow-runs-widget-subject/specs/cn-flow-runs-widget-subject/spec.md\n */\nexport default {\n\tname: 'CnFlowRunsWidget',\n\n\tcomponents: {\n\t\tNcLoadingIcon,\n\t},\n\n\tinject: {\n\t\t/**\n\t\t * Host translate function, provided by CnAppRoot (identity by default)\n\t\t * so a library string localises through the consuming app's l10n.\n\t\t */\n\t\tcnTranslate: { default: () => (key) => key },\n\t},\n\n\tprops: {\n\t\t/**\n\t\t * The widget's persisted configuration blob.\n\t\t *\n\t\t * - `limit` — rows to request AND show (1–50, default 6). The total is\n\t\t * always reported honestly regardless of this cap.\n\t\t * - `pollSeconds` — refetch interval (default 15; `0` disables polling).\n\t\t * - `rowRoute` — vue-router route NAME to open on a row click, receiving\n\t\t * the run's flow id as `:id` and the clicked run's uuid as `?run=`, so\n\t\t * the flow opens with that run inspected rather than merely open.\n\t\t * Omitted = rows are not clickable, which is correct for an app that\n\t\t * has no flow-detail page.\n\t\t * - `runRoute` — vue-router route NAME to open on a row click, receiving\n\t\t * the RUN's uuid as `:id`. Takes precedence over `rowRoute`; a row\n\t\t * without a run uuid falls back to the `rowRoute` flow-id behaviour.\n\t\t * - `subject` — a subject object uuid, or an object-context token\n\t\t * (`@objectId`, `@object.<field>`) a detail placement authors. When\n\t\t * set, the live list is filtered to that subject server-side and the\n\t\t * subject's finished runs render below it. Absent = today's org-wide\n\t\t * widget, unchanged.\n\t\t * - `emptyText` — override for the \"nothing running\" line.\n\t\t *\n\t\t * @type {{limit?: number, pollSeconds?: number, rowRoute?: string, runRoute?: string, subject?: string, emptyText?: string, title?: string}}\n\t\t */\n\t\tcontent: {\n\t\t\ttype: Object,\n\t\t\tdefault: () => ({}),\n\t\t},\n\n\t\t/**\n\t\t * Widget placement id, so the per-widget Refresh action\n\t\t * (`cn:widget:refresh`) reaches this instance's fetch.\n\t\t */\n\t\twidgetId: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * Translate function. Falls back to the injected `cnTranslate`.\n\t\t * Provide explicitly when mounting outside a CnAppRoot ancestor.\n\t\t *\n\t\t * @type {((key: string) => string)|null}\n\t\t */\n\t\ttranslate: {\n\t\t\ttype: Function,\n\t\t\tdefault: null,\n\t\t},\n\t},\n\n\tsetup(props) {\n\t\t// The shared endpoint engine owns request dedup, the short-TTL cache\n\t\t// and the cn:page:refresh / cn:widget:refresh subscriptions — the same\n\t\t// binding CnStatWidget's endpoint mode uses, so a page-level Refresh\n\t\t// updates this widget with everything else on the page.\n\t\tconst widgetIdRef = ref(props.widgetId)\n\n\t\t// The detail surface's object context, injected the same way the other\n\t\t// endpoint-bound widgets (stat / chart / audit-trail) receive it, so a\n\t\t// `subject: '@objectId'` placement binds the CURRENT object without the\n\t\t// manifest hardcoding a uuid. Null on plain dashboards — the token then\n\t\t// stays unresolved and the endpoint engine's `blocked` semantics hold\n\t\t// the fetch instead of sending a literal `@objectId` to the server.\n\t\tconst objectCtxRaw = inject('cnObjectContext', null)\n\t\tconst detailCtxRaw = inject('cnDetailObjectContext', null)\n\t\tconst tokenCtx = () => ({\n\t\t\tworkspace: {},\n\t\t\tconfig: {},\n\t\t\t...(resolveObjectTokenContext(objectCtxRaw, detailCtxRaw) || {}),\n\t\t})\n\n\t\tconst configuredSubject = () => {\n\t\t\tconst subject = props.content && props.content.subject\n\t\t\treturn (typeof subject === 'string' && subject !== '') ? subject : ''\n\t\t}\n\n\t\t/**\n\t\t * The subject uuid after token resolution: `''` when no subject is\n\t\t * configured, `null` while a token still waits on the object context,\n\t\t * else the concrete uuid.\n\t\t */\n\t\tconst resolvedSubject = computed(() => {\n\t\t\tconst subject = configuredSubject()\n\t\t\tif (subject === '') {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\tconst value = resolveFilterValue(subject, tokenCtx())\n\t\t\tif (typeof value === 'string' && value.charAt(0) === '@') {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\treturn (value === undefined || value === null) ? null : String(value)\n\t\t})\n\n\t\tconst source = () => {\n\t\t\tconst params = { limit: normaliseLimit(props.content && props.content.limit) }\n\t\t\tconst subject = configuredSubject()\n\t\t\tif (subject !== '') {\n\t\t\t\tparams.subject = subject\n\t\t\t}\n\t\t\treturn { url: ACTIVE_RUNS_URL, method: 'GET', params }\n\t\t}\n\t\tconst { data, loading, error, refetch } = useEndpointSource(source, {\n\t\t\twidgetId: widgetIdRef,\n\t\t\tctx: tokenCtx,\n\t\t})\n\n\t\t// The history half of the subject view. A null config (no subject)\n\t\t// means the composable never queries: the org-wide widget stays a\n\t\t// single-request surface, bit-identical to before.\n\t\tconst completedSource = () => {\n\t\t\tconst subject = configuredSubject()\n\t\t\tif (subject === '') {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\treturn {\n\t\t\t\turl: COMPLETED_RUNS_URL,\n\t\t\t\tmethod: 'GET',\n\t\t\t\tparams: { subject, limit: normaliseLimit(props.content && props.content.limit) },\n\t\t\t}\n\t\t}\n\t\tconst {\n\t\t\tdata: completedData,\n\t\t\tloading: completedLoading,\n\t\t\terror: completedError,\n\t\t\trefetch: refetchCompleted,\n\t\t} = useEndpointSource(completedSource, {\n\t\t\twidgetId: widgetIdRef,\n\t\t\tctx: tokenCtx,\n\t\t})\n\n\t\t// `inject` is re-read in setup so Vue 2.7 and Vue 3 resolve it\n\t\t// identically to the Options `inject` block (CnStatWidget precedent).\n\t\tconst injectedTranslate = inject('cnTranslate', null)\n\n\t\treturn {\n\t\t\tpayload: data,\n\t\t\tloading,\n\t\t\terror,\n\t\t\trefetch,\n\t\t\tcompletedPayload: completedData,\n\t\t\tcompletedLoading,\n\t\t\tcompletedError,\n\t\t\trefetchCompleted,\n\t\t\tresolvedSubject,\n\t\t\tinjectedTranslate,\n\t\t}\n\t},\n\n\tdata() {\n\t\treturn {\n\t\t\tpollTimer: null,\n\t\t}\n\t},\n\n\tcomputed: {\n\t\t/**\n\t\t * Effective translate function: the explicit prop, then the injected\n\t\t * host one, then `@nextcloud/l10n` under the library's own app id.\n\t\t *\n\t\t * @return {(key: string) => string}\n\t\t */\n\t\ttr() {\n\t\t\tconst fn = this.translate ?? this.injectedTranslate ?? this.cnTranslate\n\t\t\tif (typeof fn === 'function') {\n\t\t\t\treturn (key) => fn(key)\n\t\t\t}\n\t\t\treturn (key) => t('nextcloud-vue', key)\n\t\t},\n\n\t\t/**\n\t\t * The rows to render — the endpoint's bounded page, defensively\n\t\t * re-capped so a wider server default can never overflow the cell.\n\t\t *\n\t\t * @return {Array<object>} The run rows.\n\t\t */\n\t\trows() {\n\t\t\tconst results = (this.payload && this.payload.results) || []\n\t\t\tif (Array.isArray(results) === false) {\n\t\t\t\treturn []\n\t\t\t}\n\t\t\treturn results.slice(0, normaliseLimit(this.content.limit))\n\t\t},\n\n\t\t/**\n\t\t * How many live runs exist beyond the rendered rows.\n\t\t *\n\t\t * Read off the server's `total`, NOT the array length — the point of\n\t\t * the total is to be honest about what the cell could not fit.\n\t\t *\n\t\t * @return {number} The hidden run count (never negative).\n\t\t */\n\t\thiddenCount() {\n\t\t\tconst total = Number((this.payload && this.payload.total) ?? this.rows.length)\n\t\t\tif (Number.isFinite(total) === false) {\n\t\t\t\treturn 0\n\t\t\t}\n\t\t\treturn Math.max(0, total - this.rows.length)\n\t\t},\n\n\t\t/** The \"+N more\" line. */\n\t\tmoreLabel() {\n\t\t\treturn this.tr('+{count} more').replace('{count}', String(this.hiddenCount))\n\t\t},\n\n\t\t/** The nothing-running line. */\n\t\temptyLabel() {\n\t\t\tconst override = this.content.emptyText\n\t\t\tif (typeof override === 'string' && override !== '') {\n\t\t\t\treturn this.tr(override)\n\t\t\t}\n\t\t\treturn this.tr('No flows are running')\n\t\t},\n\n\t\t/** Whether this placement is scoped to one subject object. */\n\t\thasSubject() {\n\t\t\treturn typeof this.content.subject === 'string' && this.content.subject !== ''\n\t\t},\n\n\t\t/**\n\t\t * Whether a configured subject token still waits on the object\n\t\t * context. The widget shows its loading state instead of an empty\n\t\t * line then: \"no runs\" would be a claim about a case it has not\n\t\t * identified yet.\n\t\t *\n\t\t * @return {boolean} True while the subject is configured but unresolved.\n\t\t */\n\t\tsubjectPending() {\n\t\t\treturn this.hasSubject && this.resolvedSubject === null\n\t\t},\n\n\t\t/**\n\t\t * The subject's finished runs — the completed-runs read's bounded\n\t\t * page, re-capped like the live rows.\n\t\t *\n\t\t * @return {Array<object>} The terminal run rows (empty without a subject).\n\t\t */\n\t\tcompletedRows() {\n\t\t\tif (this.hasSubject === false) {\n\t\t\t\treturn []\n\t\t\t}\n\t\t\tconst results = (this.completedPayload && this.completedPayload.results) || []\n\t\t\tif (Array.isArray(results) === false) {\n\t\t\t\treturn []\n\t\t\t}\n\t\t\treturn results.slice(0, normaliseLimit(this.content.limit))\n\t\t},\n\n\t\t/**\n\t\t * How many finished runs exist beyond the rendered history rows, read\n\t\t * off the completed read's honest total.\n\t\t *\n\t\t * @return {number} The hidden finished-run count (never negative).\n\t\t */\n\t\tcompletedHiddenCount() {\n\t\t\tconst total = Number((this.completedPayload && this.completedPayload.total) ?? this.completedRows.length)\n\t\t\tif (Number.isFinite(total) === false) {\n\t\t\t\treturn 0\n\t\t\t}\n\t\t\treturn Math.max(0, total - this.completedRows.length)\n\t\t},\n\n\t\t/** The \"+N earlier\" line under the history section. */\n\t\tcompletedMoreLabel() {\n\t\t\treturn this.tr('+{count} earlier').replace('{count}', String(this.completedHiddenCount))\n\t\t},\n\n\t\t/**\n\t\t * Whether this subject has NO runs at all, live or finished. Rendered\n\t\t * as its own line: a case where nothing ever ran reads differently\n\t\t * from a case that is merely quiet right now.\n\t\t *\n\t\t * @return {boolean} True when both reads settled empty for a subject.\n\t\t */\n\t\tneverRan() {\n\t\t\treturn this.hasSubject\n\t\t\t\t&& this.subjectPending === false\n\t\t\t\t&& this.loading === false\n\t\t\t\t&& this.completedLoading === false\n\t\t\t\t&& this.completedError === ''\n\t\t\t\t&& this.rows.length === 0\n\t\t\t\t&& this.completedRows.length === 0\n\t\t},\n\n\t\t/** Whether the history section renders (rows or a failed history read). */\n\t\tshowHistory() {\n\t\t\treturn this.hasSubject\n\t\t\t\t&& this.subjectPending === false\n\t\t\t\t&& (this.completedRows.length > 0 || this.completedError !== '')\n\t\t},\n\n\t\t/** Whether a row click navigates (a `runRoute` or `rowRoute` is configured). */\n\t\tisLinked() {\n\t\t\treturn (typeof this.content.rowRoute === 'string' && this.content.rowRoute !== '')\n\t\t\t\t|| (typeof this.content.runRoute === 'string' && this.content.runRoute !== '')\n\t\t},\n\n\t\t/**\n\t\t * Poll interval in ms, or 0 when polling is off.\n\t\t *\n\t\t * Floored at 5s so a mis-authored `pollSeconds: 0.1` cannot turn a\n\t\t * dashboard into a request storm; `0` remains an explicit opt-out.\n\t\t *\n\t\t * @return {number} The interval in milliseconds.\n\t\t */\n\t\tpollMs() {\n\t\t\tconst raw = this.content.pollSeconds\n\t\t\tconst seconds = (raw === undefined || raw === null) ? 15 : Number(raw)\n\t\t\tif (Number.isFinite(seconds) === false || seconds <= 0) {\n\t\t\t\treturn 0\n\t\t\t}\n\t\t\treturn Math.max(5, seconds) * 1000\n\t\t},\n\t},\n\n\tmounted() {\n\t\tthis.startPolling()\n\t\tdocument.addEventListener('visibilitychange', this.onVisibilityChange)\n\t},\n\n\tbeforeUnmount() {\n\t\tthis.teardown()\n\t},\n\n\tmethods: {\n\t\t/**\n\t\t * Start (or restart) the refetch interval, unless polling is disabled.\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\tstartPolling() {\n\t\t\tthis.stopPolling()\n\t\t\tif (this.pollMs === 0 || document.hidden === true) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.pollTimer = setInterval(() => {\n\t\t\t\tthis.refetchAll()\n\t\t\t}, this.pollMs)\n\t\t},\n\n\t\t/**\n\t\t * Refetch the live read, and the history read when a subject is\n\t\t * configured — a run that finishes between polls must MOVE to the\n\t\t * history section, not vanish from the widget.\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\trefetchAll() {\n\t\t\tthis.refetch(true)\n\t\t\tif (this.hasSubject) {\n\t\t\t\tthis.refetchCompleted(true)\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Clear the refetch interval.\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\tstopPolling() {\n\t\t\tif (this.pollTimer !== null) {\n\t\t\t\tclearInterval(this.pollTimer)\n\t\t\t\tthis.pollTimer = null\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Remove the timer and the visibility listener.\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\tteardown() {\n\t\t\tthis.stopPolling()\n\t\t\tdocument.removeEventListener('visibilitychange', this.onVisibilityChange)\n\t\t},\n\n\t\t/**\n\t\t * Pause polling while the tab is hidden; refetch once and resume when\n\t\t * it comes back, so returning to the dashboard shows current state\n\t\t * rather than whatever was true when the tab was backgrounded.\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\tonVisibilityChange() {\n\t\t\tif (document.hidden === true) {\n\t\t\t\tthis.stopPolling()\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.refetchAll()\n\t\t\tthis.startPolling()\n\t\t},\n\n\t\t/**\n\t\t * Whether a run's status is terminal — the history half of the\n\t\t * subject view. Drives the hollow-dot / muted-name row treatment.\n\t\t *\n\t\t * @param {object} run The run row.\n\t\t * @return {boolean} True for a finished run.\n\t\t */\n\t\tisTerminal(run) {\n\t\t\treturn TERMINAL_STATUSES.includes(run && run.status)\n\t\t},\n\n\t\t/**\n\t\t * The human label for a status, falling back to the raw value so an\n\t\t * engine that gains a status still renders something truthful.\n\t\t *\n\t\t * @param {string} status The run status.\n\t\t * @return {string} The label.\n\t\t */\n\t\tstatusLabel(status) {\n\t\t\tconst key = STATUS_LABELS[status]\n\t\t\treturn key ? this.tr(key) : String(status || '')\n\t\t},\n\n\t\t/**\n\t\t * The secondary line: status, the step the run sits on, and what\n\t\t * triggered it — whichever of those the run actually carries.\n\t\t *\n\t\t * @param {object} run The run row.\n\t\t * @return {string} The meta line.\n\t\t */\n\t\tmetaLine(run) {\n\t\t\tconst parts = [this.statusLabel(run.status)]\n\t\t\tif (run.step) {\n\t\t\t\tparts.push(String(run.step))\n\t\t\t}\n\t\t\tif (run.trigger) {\n\t\t\t\tparts.push(String(run.trigger))\n\t\t\t}\n\t\t\treturn parts.join(' · ')\n\t\t},\n\n\t\t/**\n\t\t * How long the run has been going, as a coarse relative label.\n\t\t *\n\t\t * Coarse on purpose: a dashboard reader wants \"8m\", not a timestamp,\n\t\t * and a second-precision label on a 15s poll would read as stale.\n\t\t *\n\t\t * @param {object} run The run row.\n\t\t * @return {string} The age label, or '' when the run has no start time.\n\t\t */\n\t\tageLabel(run) {\n\t\t\tconst started = Date.parse(run.created || '')\n\t\t\tif (Number.isFinite(started) === false) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\tconst seconds = Math.max(0, Math.round((Date.now() - started) / 1000))\n\t\t\tif (seconds < 60) {\n\t\t\t\treturn this.tr('now')\n\t\t\t}\n\t\t\tconst minutes = Math.round(seconds / 60)\n\t\t\tif (minutes < 60) {\n\t\t\t\treturn `${minutes}m`\n\t\t\t}\n\t\t\tconst hours = Math.round(minutes / 60)\n\t\t\tif (hours < 24) {\n\t\t\t\treturn `${hours}h`\n\t\t\t}\n\t\t\treturn `${Math.round(hours / 24)}d`\n\t\t},\n\n\t\t/**\n\t\t * The route a run row links to, or null when it links nowhere.\n\t\t *\n\t\t * `runRoute` wins when the row carries a run uuid: on a case page a\n\t\t * click means \"show me THIS run\", and the run uuid is the deep link\n\t\t * the row contract carries. Without a `runRoute` (or on a row with no\n\t\t * uuid) `rowRoute` receives the FLOW id, the surface every\n\t\t * flow-authoring app has.\n\t\t *\n\t\t * The flow id alone answers the wrong question, so the run travels as\n\t\t * `?run=`, the address CnFlowDetail documents its `run` prop as\n\t\t * reading: a `rowRoute` click and a pasted run link open the same\n\t\t * screen.\n\t\t *\n\t\t * @param {object} run The run row.\n\t\t * @return {object|null} A vue-router location, or null.\n\t\t */\n\t\trunTarget(run) {\n\t\t\tif (this.isLinked === false || !this.$router) {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\tconst hasUuid = run.uuid !== undefined && run.uuid !== null && run.uuid !== ''\n\t\t\tconst runRoute = this.content.runRoute\n\t\t\tif (typeof runRoute === 'string' && runRoute !== '' && hasUuid) {\n\t\t\t\treturn { name: runRoute, params: { id: String(run.uuid) } }\n\t\t\t}\n\t\t\tif (typeof this.content.rowRoute !== 'string' || this.content.rowRoute === '') {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\tconst id = run.flowId\n\t\t\tif (id === undefined || id === null || id === '') {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\tconst target = { name: this.content.rowRoute, params: { id: String(id) } }\n\t\t\t// An empty `?run=` is not a deep link; CnFlowDetail's `run` prop\n\t\t\t// defaults to '' so \"no run named\" stays one value.\n\t\t\tif (hasUuid) {\n\t\t\t\ttarget.query = { run: String(run.uuid) }\n\t\t\t}\n\t\t\treturn target\n\t\t},\n\n\t\t/**\n\t\t * The href of a run row's link, or '' when the row links nowhere.\n\t\t *\n\t\t * @param {object} run The run row.\n\t\t * @return {string} The resolved href.\n\t\t */\n\t\trunHref(run) {\n\t\t\treturn resolveHref(this.runTarget(run), this.$router)\n\t\t},\n\n\t\t/**\n\t\t * Route a plain click on a run row's link in-app; modified clicks\n\t\t * are left to the browser.\n\t\t *\n\t\t * @param {MouseEvent} event The click event.\n\t\t * @param {object} run The clicked run row.\n\t\t * @return {void}\n\t\t */\n\t\tonRowClick(event, run) {\n\t\t\tfollowLinkClick(event, this.runTarget(run), this.$router)\n\t\t},\n\t},\n}\n\n/**\n * Clamp a configured row limit into the range the endpoint accepts.\n *\n * @param {number|string|undefined} raw The configured limit.\n * @return {number} A limit between 1 and 50 (default 6).\n */\nfunction normaliseLimit(raw) {\n\tconst n = Number(raw)\n\tif (Number.isFinite(n) === false || n <= 0) {\n\t\treturn 6\n\t}\n\treturn Math.min(50, Math.round(n))\n}\n</script>\n\n<style scoped>\n.cn-flow-runs-widget {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 2px;\n\tmin-width: 0;\n}\n\n.cn-flow-runs-widget__loading {\n\tdisplay: flex;\n\tjustify-content: center;\n\tpadding: 8px 0;\n}\n\n.cn-flow-runs-widget__empty,\n.cn-flow-runs-widget__error,\n.cn-flow-runs-widget__more {\n\tmargin: 0;\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 13px;\n}\n\n.cn-flow-runs-widget__more {\n\tpadding-top: 4px;\n}\n\n.cn-flow-runs-widget__list {\n\tlist-style: none;\n\tmargin: 0;\n\tpadding: 0;\n\tmin-width: 0;\n}\n\n.cn-flow-runs-widget__row {\n\tborder-bottom: 1px solid var(--color-border);\n\tmin-width: 0;\n}\n\n.cn-flow-runs-widget__row:last-child {\n\tborder-bottom: none;\n}\n\n.cn-flow-runs-widget__row-content {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 10px;\n\tpadding: 6px 0;\n\tmin-width: 0;\n\tcolor: inherit;\n\ttext-decoration: none;\n}\n\na.cn-flow-runs-widget__row-content {\n\tcursor: pointer;\n}\n\n.cn-flow-runs-widget__dot {\n\tflex: 0 0 auto;\n\twidth: 8px;\n\theight: 8px;\n\tborder-radius: 50%;\n\tbackground: var(--color-text-maxcontrast);\n}\n\n/* Executing now — the primary colour, and the only row that animates. */\n.cn-flow-runs-widget__dot--running {\n\tbackground: var(--color-primary-element, #0082c9);\n\tanimation: cn-flow-runs-pulse 1.6s ease-in-out infinite;\n}\n\n.cn-flow-runs-widget__dot--queued {\n\tbackground: var(--color-warning, #c28900);\n}\n\n.cn-flow-runs-widget__dot--suspended {\n\tbackground: var(--color-text-maxcontrast);\n}\n\n/* Terminal rows: the dot is a hollow RING, so live (filled) and finished\n (hollow) never differ by colour alone. The per-status colour still rides\n on the ring's border. */\n.cn-flow-runs-widget__dot--terminal {\n\twidth: 6px;\n\theight: 6px;\n\tbackground: transparent;\n\tborder: 2px solid var(--color-text-maxcontrast);\n}\n\n.cn-flow-runs-widget__dot--terminal.cn-flow-runs-widget__dot--completed {\n\tborder-color: var(--color-success, #2d7b41);\n}\n\n.cn-flow-runs-widget__dot--terminal.cn-flow-runs-widget__dot--failed,\n.cn-flow-runs-widget__dot--terminal.cn-flow-runs-widget__dot--dead_letter {\n\tborder-color: var(--color-error, #d91f2d);\n}\n\n.cn-flow-runs-widget__body {\n\tdisplay: flex;\n\tflex-direction: column;\n\tmin-width: 0;\n\tflex: 1 1 auto;\n}\n\n.cn-flow-runs-widget__name {\n\tfont-weight: 600;\n\twhite-space: nowrap;\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n}\n\n/* Finished runs read as history: regular weight, muted colour. The hollow\n dot and the section label carry the distinction for anyone who cannot\n rely on colour. */\n.cn-flow-runs-widget__row--terminal .cn-flow-runs-widget__name {\n\tfont-weight: 400;\n\tcolor: var(--color-text-maxcontrast);\n}\n\n/* Must sit AFTER the plain `__name` rule: the compound selector is more\n specific, and stylelint's no-descending-specificity flags the reverse\n order because the cascade then depends on specificity rather than\n source order — the shape that quietly stops working when someone adds\n a competing rule. */\na.cn-flow-runs-widget__row-content:hover .cn-flow-runs-widget__name {\n\ttext-decoration: underline;\n}\n\n.cn-flow-runs-widget__meta {\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 12px;\n\twhite-space: nowrap;\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n}\n\n.cn-flow-runs-widget__age {\n\tflex: 0 0 auto;\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 12px;\n\tfont-variant-numeric: tabular-nums;\n}\n\n/* The history section label. Small caps text, not a colour: the section\n boundary must survive monochrome. */\n.cn-flow-runs-widget__history-title {\n\tmargin: 0;\n\tpadding-top: 8px;\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 12px;\n\tfont-weight: 600;\n\ttext-transform: uppercase;\n\tletter-spacing: 0.04em;\n}\n\n@keyframes cn-flow-runs-pulse {\n\t0%, 100% { opacity: 1; }\n\t50% { opacity: 0.35; }\n}\n\n/* Respect a reduced-motion preference: the pulse is decoration, and the dot\n colour already carries the status. */\n@media (prefers-reduced-motion: reduce) {\n\t.cn-flow-runs-widget__dot--running {\n\t\tanimation: none;\n\t}\n}\n</style>\n"],"names":["t"],"mappings":"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"}
|
|
@@ -73,15 +73,15 @@ function render(_ctx, _cache, $props, $setup, $data, $options) {
|
|
|
73
73
|
($options.rows.length === 0)
|
|
74
74
|
? (openBlock(), createElementBlock("p", _hoisted_6, toDisplayString($options.emptyLabel), 1 /* TEXT */))
|
|
75
75
|
: (openBlock(), createElementBlock("ul", _hoisted_7, [
|
|
76
|
-
(openBlock(true), createElementBlock(Fragment, null, renderList($options.rows, (run) => {
|
|
76
|
+
(openBlock(true), createElementBlock(Fragment, null, renderList($options.rows, (run, index) => {
|
|
77
77
|
return (openBlock(), createElementBlock("li", {
|
|
78
|
-
key: run.uuid
|
|
78
|
+
key: run.uuid || `row-${index}`,
|
|
79
79
|
class: normalizeClass(["cn-flow-runs-widget__row", { 'cn-flow-runs-widget__row--linked': $options.isLinked }]),
|
|
80
80
|
"data-status": run.status
|
|
81
81
|
}, [
|
|
82
|
-
(openBlock(), createBlock(resolveDynamicComponent($options.
|
|
82
|
+
(openBlock(), createBlock(resolveDynamicComponent($options.runHref(run) ? 'a' : 'div'), {
|
|
83
83
|
class: "cn-flow-runs-widget__row-content",
|
|
84
|
-
href: $options.
|
|
84
|
+
href: $options.runHref(run) || undefined,
|
|
85
85
|
onClick: $event => ($options.onRowClick($event, run))
|
|
86
86
|
}, {
|
|
87
87
|
default: withCtx(() => [
|
|
@@ -109,15 +109,15 @@ function render(_ctx, _cache, $props, $setup, $data, $options) {
|
|
|
109
109
|
($setup.completedError)
|
|
110
110
|
? (openBlock(), createElementBlock("p", _hoisted_16, toDisplayString($options.tr('Could not load the run history')), 1 /* TEXT */))
|
|
111
111
|
: (openBlock(), createElementBlock("ul", _hoisted_17, [
|
|
112
|
-
(openBlock(true), createElementBlock(Fragment, null, renderList($options.completedRows, (run) => {
|
|
112
|
+
(openBlock(true), createElementBlock(Fragment, null, renderList($options.completedRows, (run, index) => {
|
|
113
113
|
return (openBlock(), createElementBlock("li", {
|
|
114
|
-
key: run.uuid
|
|
114
|
+
key: run.uuid || `completed-${index}`,
|
|
115
115
|
class: normalizeClass(["cn-flow-runs-widget__row cn-flow-runs-widget__row--terminal", { 'cn-flow-runs-widget__row--linked': $options.isLinked }]),
|
|
116
116
|
"data-status": run.status
|
|
117
117
|
}, [
|
|
118
|
-
(openBlock(), createBlock(resolveDynamicComponent($options.
|
|
118
|
+
(openBlock(), createBlock(resolveDynamicComponent($options.runHref(run) ? 'a' : 'div'), {
|
|
119
119
|
class: "cn-flow-runs-widget__row-content",
|
|
120
|
-
href: $options.
|
|
120
|
+
href: $options.runHref(run) || undefined,
|
|
121
121
|
onClick: $event => ($options.onRowClick($event, run))
|
|
122
122
|
}, {
|
|
123
123
|
default: withCtx(() => [
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CnFlowRunsWidget.vue3.js","sources":["../../../../src/components/CnFlowRunsWidget/CnFlowRunsWidget.vue?vue&type=template&id=75628d22&lang.js"],"sourcesContent":["<!--\n - SPDX-FileCopyrightText: 2026 Conduction B.V.\n - SPDX-License-Identifier: EUPL-1.2\n-->\n<template>\n\t<div class=\"cn-flow-runs-widget\">\n\t\t<!-- One quiet line on failure, never a leaked axios status string\n\t\t (ADR-062). The real error goes to the console. -->\n\t\t<p v-if=\"error\" class=\"cn-flow-runs-widget__error\">\n\t\t\t{{ tr('Could not load the running flows') }}\n\t\t</p>\n\t\t<!-- A configured-but-unresolved subject token also shows the loading\n\t\t state: \"no runs\" would be a claim about a case the widget has not\n\t\t identified yet. -->\n\t\t<div v-else-if=\"(loading || subjectPending) && rows.length === 0\" class=\"cn-flow-runs-widget__loading\">\n\t\t\t<NcLoadingIcon :size=\"24\" />\n\t\t</div>\n\t\t<!-- A subject where nothing EVER ran gets its own line: distinct from\n\t\t a case that is merely quiet right now. -->\n\t\t<p v-else-if=\"neverRan\" class=\"cn-flow-runs-widget__empty\">\n\t\t\t{{ tr('No flows have run yet') }}\n\t\t</p>\n\t\t<!-- Nothing running is the NORMAL state for this widget, not an\n\t\t error and not a void: one muted line, no illustration. -->\n\t\t<p v-else-if=\"rows.length === 0 && showHistory === false\" class=\"cn-flow-runs-widget__empty\">\n\t\t\t{{ emptyLabel }}\n\t\t</p>\n\t\t<template v-else>\n\t\t\t<p v-if=\"rows.length === 0\" class=\"cn-flow-runs-widget__empty\">\n\t\t\t\t{{ emptyLabel }}\n\t\t\t</p>\n\t\t\t<ul v-else class=\"cn-flow-runs-widget__list\">\n\t\t\t\t<li\n\t\t\t\t\tv-for=\"run in rows\"\n\t\t\t\t\t:key=\"run.uuid\"\n\t\t\t\t\tclass=\"cn-flow-runs-widget__row\"\n\t\t\t\t\t:class=\"{ 'cn-flow-runs-widget__row--linked': isLinked }\"\n\t\t\t\t\t:data-status=\"run.status\">\n\t\t\t\t\t<component\n\t\t\t\t\t\t:is=\"runHrefs[run.uuid] ? 'a' : 'div'\"\n\t\t\t\t\t\tclass=\"cn-flow-runs-widget__row-content\"\n\t\t\t\t\t\t:href=\"runHrefs[run.uuid] || undefined\"\n\t\t\t\t\t\t@click=\"onRowClick($event, run)\">\n\t\t\t\t\t\t<span\n\t\t\t\t\t\t\tclass=\"cn-flow-runs-widget__dot\"\n\t\t\t\t\t\t\t:class=\"`cn-flow-runs-widget__dot--${run.status}`\"\n\t\t\t\t\t\t\t:title=\"statusLabel(run.status)\" />\n\t\t\t\t\t\t<span class=\"cn-flow-runs-widget__body\">\n\t\t\t\t\t\t\t<span class=\"cn-flow-runs-widget__name\">{{ run.flowName }}</span>\n\t\t\t\t\t\t\t<span class=\"cn-flow-runs-widget__meta\">{{ metaLine(run) }}</span>\n\t\t\t\t\t\t</span>\n\t\t\t\t\t\t<span class=\"cn-flow-runs-widget__age\">{{ ageLabel(run) }}</span>\n\t\t\t\t\t</component>\n\t\t\t\t</li>\n\t\t\t</ul>\n\t\t\t<!-- The remainder is a count, never a scrollbar: the widget shows\n\t\t\t what fits its cell and states the honest total (ADR-062). -->\n\t\t\t<p v-if=\"hiddenCount > 0\" class=\"cn-flow-runs-widget__more\">\n\t\t\t\t{{ moreLabel }}\n\t\t\t</p>\n\t\t\t<!-- The subject's run history. Terminal rows sit under their own\n\t\t\t labelled section with a hollow dot and a muted name, so live\n\t\t\t and finished never rely on colour alone to tell apart. -->\n\t\t\t<template v-if=\"showHistory\">\n\t\t\t\t<p class=\"cn-flow-runs-widget__history-title\">\n\t\t\t\t\t{{ tr('Earlier runs') }}\n\t\t\t\t</p>\n\t\t\t\t<p v-if=\"completedError\" class=\"cn-flow-runs-widget__error\">\n\t\t\t\t\t{{ tr('Could not load the run history') }}\n\t\t\t\t</p>\n\t\t\t\t<ul v-else class=\"cn-flow-runs-widget__list\">\n\t\t\t\t\t<li\n\t\t\t\t\t\tv-for=\"run in completedRows\"\n\t\t\t\t\t\t:key=\"run.uuid\"\n\t\t\t\t\t\tclass=\"cn-flow-runs-widget__row cn-flow-runs-widget__row--terminal\"\n\t\t\t\t\t\t:class=\"{ 'cn-flow-runs-widget__row--linked': isLinked }\"\n\t\t\t\t\t\t:data-status=\"run.status\">\n\t\t\t\t\t\t<component\n\t\t\t\t\t\t\t:is=\"runHrefs[run.uuid] ? 'a' : 'div'\"\n\t\t\t\t\t\t\tclass=\"cn-flow-runs-widget__row-content\"\n\t\t\t\t\t\t\t:href=\"runHrefs[run.uuid] || undefined\"\n\t\t\t\t\t\t\t@click=\"onRowClick($event, run)\">\n\t\t\t\t\t\t\t<span\n\t\t\t\t\t\t\t\tclass=\"cn-flow-runs-widget__dot cn-flow-runs-widget__dot--terminal\"\n\t\t\t\t\t\t\t\t:class=\"`cn-flow-runs-widget__dot--${run.status}`\"\n\t\t\t\t\t\t\t\t:title=\"statusLabel(run.status)\" />\n\t\t\t\t\t\t\t<span class=\"cn-flow-runs-widget__body\">\n\t\t\t\t\t\t\t\t<span class=\"cn-flow-runs-widget__name\">{{ run.flowName }}</span>\n\t\t\t\t\t\t\t\t<span class=\"cn-flow-runs-widget__meta\">{{ metaLine(run) }}</span>\n\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t<span class=\"cn-flow-runs-widget__age\">{{ ageLabel(run) }}</span>\n\t\t\t\t\t\t</component>\n\t\t\t\t\t</li>\n\t\t\t\t</ul>\n\t\t\t\t<p v-if=\"completedError === '' && completedHiddenCount > 0\" class=\"cn-flow-runs-widget__more\">\n\t\t\t\t\t{{ completedMoreLabel }}\n\t\t\t\t</p>\n\t\t\t</template>\n\t\t</template>\n\t</div>\n</template>\n\n<script>\nimport { translate as t } from '@nextcloud/l10n'\nimport { NcLoadingIcon } from '@nextcloud/vue'\nimport { computed, inject, ref } from 'vue'\nimport { useEndpointSource } from '../../composables/useEndpointSource.js'\nimport { resolveObjectTokenContext } from '../../utils/detailObjectContext.js'\nimport { followLinkClick, resolveHref } from '../../utils/linkNavigation.js'\nimport { resolveFilterValue } from '../../utils/resolveFilterTokens.js'\n\n/**\n * The OpenRegister endpoint that lists the caller's live flow runs.\n *\n * Every Conduction app runs its flows on OpenRegister's ONE flow engine\n * (ADR-065), so \"what is running right now\" has a single answer for the whole\n * instance and this widget can be app-agnostic: an app places it and gets its\n * users' live runs without shipping a controller, a store or a view.\n *\n * @type {string}\n */\nconst ACTIVE_RUNS_URL = '/apps/openregister/api/flow-runs/active'\n\n/**\n * The OpenRegister endpoint that lists the finished runs for ONE subject\n * object. Subject-required by contract (openregister change\n * `flow-runs-subject-scope`): there is no org-wide \"all finished runs\" here,\n * so the widget only ever calls it with a resolved subject uuid.\n *\n * @type {string}\n */\nconst COMPLETED_RUNS_URL = '/apps/openregister/api/flow-runs/completed'\n\n/**\n * Statuses, in the order a person reads them: what is executing, what is\n * queued behind it, what is parked waiting on a timer or a child run, and\n * then the terminal set a subject's run history renders.\n *\n * @type {Record<string, string>}\n */\nconst STATUS_LABELS = {\n\trunning: 'Running',\n\tqueued: 'Queued',\n\tsuspended: 'Waiting',\n\tcompleted: 'Completed',\n\tstopped: 'Stopped',\n\tfailed: 'Failed',\n\tdead_letter: 'Failed after retries',\n}\n\n/**\n * The terminal statuses (FlowRun::TERMINAL on the OpenRegister side). Rows\n * with one of these are HISTORY: rendered in the earlier-runs section with a\n * hollow dot and a muted name, never mixed into the live list.\n *\n * @type {string[]}\n */\nconst TERMINAL_STATUSES = ['completed', 'stopped', 'failed', 'dead_letter']\n\n/**\n * CnFlowRunsWidget — the live flow runs for the viewer's organisation.\n *\n * Reads OpenRegister's `flow-runs/active` surface (non-terminal runs, scoped\n * server-side to the caller's organisation) and renders one row per run: its\n * flow's NAME, what triggered it, the step it currently sits on, and how long\n * it has been going. Placed by its registry type key `flow-runs`, so any app\n * gets the widget from a single manifest placement:\n *\n * ```js\n * { id: 'running-flows', type: 'flow-runs', title: 'Running flows',\n * content: { limit: 6, pollSeconds: 15, rowRoute: 'GraphDetail' } }\n * ```\n *\n * Why it polls: a run is a moving thing. A widget titled \"running flows\" that\n * only reflects the moment the dashboard mounted would be wrong within\n * seconds and would look identical to being right — so it refetches on an\n * interval, and stops while the tab is hidden so an idle dashboard is not a\n * background request loop.\n *\n * Statuses shown are ALL the non-terminal ones (queued / running / suspended),\n * because filtering to literally `running` would show an empty widget almost\n * always: a run holds that status only during a worker pass, while queued and\n * suspended are where live runs actually wait. See FlowRun::ACTIVE.\n *\n * Subject mode: a `content.subject` (a subject object uuid, or the\n * `@objectId` / `@object.<field>` token a detail placement authors) narrows\n * the live list to one case AND adds that case's finished runs below it, so\n * a flow that completed does not look like nothing ever happened. Tokens\n * resolve against the detail surface's injected object context, the same\n * route CnStatWidget and CnChartWidget take.\n *\n * @spec openspec/changes/cn-flow-runs-widget/specs/cn-flow-runs-widget/spec.md\n * @spec openspec/changes/cn-flow-runs-widget-subject/specs/cn-flow-runs-widget-subject/spec.md\n */\nexport default {\n\tname: 'CnFlowRunsWidget',\n\n\tcomponents: {\n\t\tNcLoadingIcon,\n\t},\n\n\tinject: {\n\t\t/**\n\t\t * Host translate function, provided by CnAppRoot (identity by default)\n\t\t * so a library string localises through the consuming app's l10n.\n\t\t */\n\t\tcnTranslate: { default: () => (key) => key },\n\t},\n\n\tprops: {\n\t\t/**\n\t\t * The widget's persisted configuration blob.\n\t\t *\n\t\t * - `limit` — rows to request AND show (1–50, default 6). The total is\n\t\t * always reported honestly regardless of this cap.\n\t\t * - `pollSeconds` — refetch interval (default 15; `0` disables polling).\n\t\t * - `rowRoute` — vue-router route NAME to open on a row click, receiving\n\t\t * the run's flow id as `:id` and the clicked run's uuid as `?run=`, so\n\t\t * the flow opens with that run inspected rather than merely open.\n\t\t * Omitted = rows are not clickable, which is correct for an app that\n\t\t * has no flow-detail page.\n\t\t * - `runRoute` — vue-router route NAME to open on a row click, receiving\n\t\t * the RUN's uuid as `:id`. Takes precedence over `rowRoute`; a row\n\t\t * without a run uuid falls back to the `rowRoute` flow-id behaviour.\n\t\t * - `subject` — a subject object uuid, or an object-context token\n\t\t * (`@objectId`, `@object.<field>`) a detail placement authors. When\n\t\t * set, the live list is filtered to that subject server-side and the\n\t\t * subject's finished runs render below it. Absent = today's org-wide\n\t\t * widget, unchanged.\n\t\t * - `emptyText` — override for the \"nothing running\" line.\n\t\t *\n\t\t * @type {{limit?: number, pollSeconds?: number, rowRoute?: string, runRoute?: string, subject?: string, emptyText?: string, title?: string}}\n\t\t */\n\t\tcontent: {\n\t\t\ttype: Object,\n\t\t\tdefault: () => ({}),\n\t\t},\n\n\t\t/**\n\t\t * Widget placement id, so the per-widget Refresh action\n\t\t * (`cn:widget:refresh`) reaches this instance's fetch.\n\t\t */\n\t\twidgetId: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * Translate function. Falls back to the injected `cnTranslate`.\n\t\t * Provide explicitly when mounting outside a CnAppRoot ancestor.\n\t\t *\n\t\t * @type {((key: string) => string)|null}\n\t\t */\n\t\ttranslate: {\n\t\t\ttype: Function,\n\t\t\tdefault: null,\n\t\t},\n\t},\n\n\tsetup(props) {\n\t\t// The shared endpoint engine owns request dedup, the short-TTL cache\n\t\t// and the cn:page:refresh / cn:widget:refresh subscriptions — the same\n\t\t// binding CnStatWidget's endpoint mode uses, so a page-level Refresh\n\t\t// updates this widget with everything else on the page.\n\t\tconst widgetIdRef = ref(props.widgetId)\n\n\t\t// The detail surface's object context, injected the same way the other\n\t\t// endpoint-bound widgets (stat / chart / audit-trail) receive it, so a\n\t\t// `subject: '@objectId'` placement binds the CURRENT object without the\n\t\t// manifest hardcoding a uuid. Null on plain dashboards — the token then\n\t\t// stays unresolved and the endpoint engine's `blocked` semantics hold\n\t\t// the fetch instead of sending a literal `@objectId` to the server.\n\t\tconst objectCtxRaw = inject('cnObjectContext', null)\n\t\tconst detailCtxRaw = inject('cnDetailObjectContext', null)\n\t\tconst tokenCtx = () => ({\n\t\t\tworkspace: {},\n\t\t\tconfig: {},\n\t\t\t...(resolveObjectTokenContext(objectCtxRaw, detailCtxRaw) || {}),\n\t\t})\n\n\t\tconst configuredSubject = () => {\n\t\t\tconst subject = props.content && props.content.subject\n\t\t\treturn (typeof subject === 'string' && subject !== '') ? subject : ''\n\t\t}\n\n\t\t/**\n\t\t * The subject uuid after token resolution: `''` when no subject is\n\t\t * configured, `null` while a token still waits on the object context,\n\t\t * else the concrete uuid.\n\t\t */\n\t\tconst resolvedSubject = computed(() => {\n\t\t\tconst subject = configuredSubject()\n\t\t\tif (subject === '') {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\tconst value = resolveFilterValue(subject, tokenCtx())\n\t\t\tif (typeof value === 'string' && value.charAt(0) === '@') {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\treturn (value === undefined || value === null) ? null : String(value)\n\t\t})\n\n\t\tconst source = () => {\n\t\t\tconst params = { limit: normaliseLimit(props.content && props.content.limit) }\n\t\t\tconst subject = configuredSubject()\n\t\t\tif (subject !== '') {\n\t\t\t\tparams.subject = subject\n\t\t\t}\n\t\t\treturn { url: ACTIVE_RUNS_URL, method: 'GET', params }\n\t\t}\n\t\tconst { data, loading, error, refetch } = useEndpointSource(source, {\n\t\t\twidgetId: widgetIdRef,\n\t\t\tctx: tokenCtx,\n\t\t})\n\n\t\t// The history half of the subject view. A null config (no subject)\n\t\t// means the composable never queries: the org-wide widget stays a\n\t\t// single-request surface, bit-identical to before.\n\t\tconst completedSource = () => {\n\t\t\tconst subject = configuredSubject()\n\t\t\tif (subject === '') {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\treturn {\n\t\t\t\turl: COMPLETED_RUNS_URL,\n\t\t\t\tmethod: 'GET',\n\t\t\t\tparams: { subject, limit: normaliseLimit(props.content && props.content.limit) },\n\t\t\t}\n\t\t}\n\t\tconst {\n\t\t\tdata: completedData,\n\t\t\tloading: completedLoading,\n\t\t\terror: completedError,\n\t\t\trefetch: refetchCompleted,\n\t\t} = useEndpointSource(completedSource, {\n\t\t\twidgetId: widgetIdRef,\n\t\t\tctx: tokenCtx,\n\t\t})\n\n\t\t// `inject` is re-read in setup so Vue 2.7 and Vue 3 resolve it\n\t\t// identically to the Options `inject` block (CnStatWidget precedent).\n\t\tconst injectedTranslate = inject('cnTranslate', null)\n\n\t\treturn {\n\t\t\tpayload: data,\n\t\t\tloading,\n\t\t\terror,\n\t\t\trefetch,\n\t\t\tcompletedPayload: completedData,\n\t\t\tcompletedLoading,\n\t\t\tcompletedError,\n\t\t\trefetchCompleted,\n\t\t\tresolvedSubject,\n\t\t\tinjectedTranslate,\n\t\t}\n\t},\n\n\tdata() {\n\t\treturn {\n\t\t\tpollTimer: null,\n\t\t}\n\t},\n\n\tcomputed: {\n\t\t/**\n\t\t * Each run's link href by uuid, resolved once per render.\n\t\t *\n\t\t * @return {{[uuid: string]: string}}\n\t\t */\n\t\trunHrefs() {\n\t\t\tconst out = {}\n\t\t\tfor (const run of [...this.rows, ...this.completedRows]) {\n\t\t\t\tout[run.uuid] = this.runHref(run)\n\t\t\t}\n\t\t\treturn out\n\t\t},\n\n\t\t/**\n\t\t * Effective translate function: the explicit prop, then the injected\n\t\t * host one, then `@nextcloud/l10n` under the library's own app id.\n\t\t *\n\t\t * @return {(key: string) => string}\n\t\t */\n\t\ttr() {\n\t\t\tconst fn = this.translate ?? this.injectedTranslate ?? this.cnTranslate\n\t\t\tif (typeof fn === 'function') {\n\t\t\t\treturn (key) => fn(key)\n\t\t\t}\n\t\t\treturn (key) => t('nextcloud-vue', key)\n\t\t},\n\n\t\t/**\n\t\t * The rows to render — the endpoint's bounded page, defensively\n\t\t * re-capped so a wider server default can never overflow the cell.\n\t\t *\n\t\t * @return {Array<object>} The run rows.\n\t\t */\n\t\trows() {\n\t\t\tconst results = (this.payload && this.payload.results) || []\n\t\t\tif (Array.isArray(results) === false) {\n\t\t\t\treturn []\n\t\t\t}\n\t\t\treturn results.slice(0, normaliseLimit(this.content.limit))\n\t\t},\n\n\t\t/**\n\t\t * How many live runs exist beyond the rendered rows.\n\t\t *\n\t\t * Read off the server's `total`, NOT the array length — the point of\n\t\t * the total is to be honest about what the cell could not fit.\n\t\t *\n\t\t * @return {number} The hidden run count (never negative).\n\t\t */\n\t\thiddenCount() {\n\t\t\tconst total = Number((this.payload && this.payload.total) ?? this.rows.length)\n\t\t\tif (Number.isFinite(total) === false) {\n\t\t\t\treturn 0\n\t\t\t}\n\t\t\treturn Math.max(0, total - this.rows.length)\n\t\t},\n\n\t\t/** The \"+N more\" line. */\n\t\tmoreLabel() {\n\t\t\treturn this.tr('+{count} more').replace('{count}', String(this.hiddenCount))\n\t\t},\n\n\t\t/** The nothing-running line. */\n\t\temptyLabel() {\n\t\t\tconst override = this.content.emptyText\n\t\t\tif (typeof override === 'string' && override !== '') {\n\t\t\t\treturn this.tr(override)\n\t\t\t}\n\t\t\treturn this.tr('No flows are running')\n\t\t},\n\n\t\t/** Whether this placement is scoped to one subject object. */\n\t\thasSubject() {\n\t\t\treturn typeof this.content.subject === 'string' && this.content.subject !== ''\n\t\t},\n\n\t\t/**\n\t\t * Whether a configured subject token still waits on the object\n\t\t * context. The widget shows its loading state instead of an empty\n\t\t * line then: \"no runs\" would be a claim about a case it has not\n\t\t * identified yet.\n\t\t *\n\t\t * @return {boolean} True while the subject is configured but unresolved.\n\t\t */\n\t\tsubjectPending() {\n\t\t\treturn this.hasSubject && this.resolvedSubject === null\n\t\t},\n\n\t\t/**\n\t\t * The subject's finished runs — the completed-runs read's bounded\n\t\t * page, re-capped like the live rows.\n\t\t *\n\t\t * @return {Array<object>} The terminal run rows (empty without a subject).\n\t\t */\n\t\tcompletedRows() {\n\t\t\tif (this.hasSubject === false) {\n\t\t\t\treturn []\n\t\t\t}\n\t\t\tconst results = (this.completedPayload && this.completedPayload.results) || []\n\t\t\tif (Array.isArray(results) === false) {\n\t\t\t\treturn []\n\t\t\t}\n\t\t\treturn results.slice(0, normaliseLimit(this.content.limit))\n\t\t},\n\n\t\t/**\n\t\t * How many finished runs exist beyond the rendered history rows, read\n\t\t * off the completed read's honest total.\n\t\t *\n\t\t * @return {number} The hidden finished-run count (never negative).\n\t\t */\n\t\tcompletedHiddenCount() {\n\t\t\tconst total = Number((this.completedPayload && this.completedPayload.total) ?? this.completedRows.length)\n\t\t\tif (Number.isFinite(total) === false) {\n\t\t\t\treturn 0\n\t\t\t}\n\t\t\treturn Math.max(0, total - this.completedRows.length)\n\t\t},\n\n\t\t/** The \"+N earlier\" line under the history section. */\n\t\tcompletedMoreLabel() {\n\t\t\treturn this.tr('+{count} earlier').replace('{count}', String(this.completedHiddenCount))\n\t\t},\n\n\t\t/**\n\t\t * Whether this subject has NO runs at all, live or finished. Rendered\n\t\t * as its own line: a case where nothing ever ran reads differently\n\t\t * from a case that is merely quiet right now.\n\t\t *\n\t\t * @return {boolean} True when both reads settled empty for a subject.\n\t\t */\n\t\tneverRan() {\n\t\t\treturn this.hasSubject\n\t\t\t\t&& this.subjectPending === false\n\t\t\t\t&& this.loading === false\n\t\t\t\t&& this.completedLoading === false\n\t\t\t\t&& this.completedError === ''\n\t\t\t\t&& this.rows.length === 0\n\t\t\t\t&& this.completedRows.length === 0\n\t\t},\n\n\t\t/** Whether the history section renders (rows or a failed history read). */\n\t\tshowHistory() {\n\t\t\treturn this.hasSubject\n\t\t\t\t&& this.subjectPending === false\n\t\t\t\t&& (this.completedRows.length > 0 || this.completedError !== '')\n\t\t},\n\n\t\t/** Whether a row click navigates (a `runRoute` or `rowRoute` is configured). */\n\t\tisLinked() {\n\t\t\treturn (typeof this.content.rowRoute === 'string' && this.content.rowRoute !== '')\n\t\t\t\t|| (typeof this.content.runRoute === 'string' && this.content.runRoute !== '')\n\t\t},\n\n\t\t/**\n\t\t * Poll interval in ms, or 0 when polling is off.\n\t\t *\n\t\t * Floored at 5s so a mis-authored `pollSeconds: 0.1` cannot turn a\n\t\t * dashboard into a request storm; `0` remains an explicit opt-out.\n\t\t *\n\t\t * @return {number} The interval in milliseconds.\n\t\t */\n\t\tpollMs() {\n\t\t\tconst raw = this.content.pollSeconds\n\t\t\tconst seconds = (raw === undefined || raw === null) ? 15 : Number(raw)\n\t\t\tif (Number.isFinite(seconds) === false || seconds <= 0) {\n\t\t\t\treturn 0\n\t\t\t}\n\t\t\treturn Math.max(5, seconds) * 1000\n\t\t},\n\t},\n\n\tmounted() {\n\t\tthis.startPolling()\n\t\tdocument.addEventListener('visibilitychange', this.onVisibilityChange)\n\t},\n\n\tbeforeUnmount() {\n\t\tthis.teardown()\n\t},\n\n\tmethods: {\n\t\t/**\n\t\t * Start (or restart) the refetch interval, unless polling is disabled.\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\tstartPolling() {\n\t\t\tthis.stopPolling()\n\t\t\tif (this.pollMs === 0 || document.hidden === true) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.pollTimer = setInterval(() => {\n\t\t\t\tthis.refetchAll()\n\t\t\t}, this.pollMs)\n\t\t},\n\n\t\t/**\n\t\t * Refetch the live read, and the history read when a subject is\n\t\t * configured — a run that finishes between polls must MOVE to the\n\t\t * history section, not vanish from the widget.\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\trefetchAll() {\n\t\t\tthis.refetch(true)\n\t\t\tif (this.hasSubject) {\n\t\t\t\tthis.refetchCompleted(true)\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Clear the refetch interval.\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\tstopPolling() {\n\t\t\tif (this.pollTimer !== null) {\n\t\t\t\tclearInterval(this.pollTimer)\n\t\t\t\tthis.pollTimer = null\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Remove the timer and the visibility listener.\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\tteardown() {\n\t\t\tthis.stopPolling()\n\t\t\tdocument.removeEventListener('visibilitychange', this.onVisibilityChange)\n\t\t},\n\n\t\t/**\n\t\t * Pause polling while the tab is hidden; refetch once and resume when\n\t\t * it comes back, so returning to the dashboard shows current state\n\t\t * rather than whatever was true when the tab was backgrounded.\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\tonVisibilityChange() {\n\t\t\tif (document.hidden === true) {\n\t\t\t\tthis.stopPolling()\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.refetchAll()\n\t\t\tthis.startPolling()\n\t\t},\n\n\t\t/**\n\t\t * Whether a run's status is terminal — the history half of the\n\t\t * subject view. Drives the hollow-dot / muted-name row treatment.\n\t\t *\n\t\t * @param {object} run The run row.\n\t\t * @return {boolean} True for a finished run.\n\t\t */\n\t\tisTerminal(run) {\n\t\t\treturn TERMINAL_STATUSES.includes(run && run.status)\n\t\t},\n\n\t\t/**\n\t\t * The human label for a status, falling back to the raw value so an\n\t\t * engine that gains a status still renders something truthful.\n\t\t *\n\t\t * @param {string} status The run status.\n\t\t * @return {string} The label.\n\t\t */\n\t\tstatusLabel(status) {\n\t\t\tconst key = STATUS_LABELS[status]\n\t\t\treturn key ? this.tr(key) : String(status || '')\n\t\t},\n\n\t\t/**\n\t\t * The secondary line: status, the step the run sits on, and what\n\t\t * triggered it — whichever of those the run actually carries.\n\t\t *\n\t\t * @param {object} run The run row.\n\t\t * @return {string} The meta line.\n\t\t */\n\t\tmetaLine(run) {\n\t\t\tconst parts = [this.statusLabel(run.status)]\n\t\t\tif (run.step) {\n\t\t\t\tparts.push(String(run.step))\n\t\t\t}\n\t\t\tif (run.trigger) {\n\t\t\t\tparts.push(String(run.trigger))\n\t\t\t}\n\t\t\treturn parts.join(' · ')\n\t\t},\n\n\t\t/**\n\t\t * How long the run has been going, as a coarse relative label.\n\t\t *\n\t\t * Coarse on purpose: a dashboard reader wants \"8m\", not a timestamp,\n\t\t * and a second-precision label on a 15s poll would read as stale.\n\t\t *\n\t\t * @param {object} run The run row.\n\t\t * @return {string} The age label, or '' when the run has no start time.\n\t\t */\n\t\tageLabel(run) {\n\t\t\tconst started = Date.parse(run.created || '')\n\t\t\tif (Number.isFinite(started) === false) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\tconst seconds = Math.max(0, Math.round((Date.now() - started) / 1000))\n\t\t\tif (seconds < 60) {\n\t\t\t\treturn this.tr('now')\n\t\t\t}\n\t\t\tconst minutes = Math.round(seconds / 60)\n\t\t\tif (minutes < 60) {\n\t\t\t\treturn `${minutes}m`\n\t\t\t}\n\t\t\tconst hours = Math.round(minutes / 60)\n\t\t\tif (hours < 24) {\n\t\t\t\treturn `${hours}h`\n\t\t\t}\n\t\t\treturn `${Math.round(hours / 24)}d`\n\t\t},\n\n\t\t/**\n\t\t * The route a run row links to, or null when it links nowhere.\n\t\t *\n\t\t * `runRoute` wins when the row carries a run uuid: on a case page a\n\t\t * click means \"show me THIS run\", and the run uuid is the deep link\n\t\t * the row contract carries. Without a `runRoute` (or on a row with no\n\t\t * uuid) `rowRoute` receives the FLOW id, the surface every\n\t\t * flow-authoring app has.\n\t\t *\n\t\t * The flow id alone answers the wrong question, so the run travels as\n\t\t * `?run=`, the address CnFlowDetail documents its `run` prop as\n\t\t * reading: a `rowRoute` click and a pasted run link open the same\n\t\t * screen.\n\t\t *\n\t\t * @param {object} run The run row.\n\t\t * @return {object|null} A vue-router location, or null.\n\t\t */\n\t\trunTarget(run) {\n\t\t\tif (this.isLinked === false || !this.$router) {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\tconst hasUuid = run.uuid !== undefined && run.uuid !== null && run.uuid !== ''\n\t\t\tconst runRoute = this.content.runRoute\n\t\t\tif (typeof runRoute === 'string' && runRoute !== '' && hasUuid) {\n\t\t\t\treturn { name: runRoute, params: { id: String(run.uuid) } }\n\t\t\t}\n\t\t\tif (typeof this.content.rowRoute !== 'string' || this.content.rowRoute === '') {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\tconst id = run.flowId\n\t\t\tif (id === undefined || id === null || id === '') {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\tconst target = { name: this.content.rowRoute, params: { id: String(id) } }\n\t\t\t// An empty `?run=` is not a deep link; CnFlowDetail's `run` prop\n\t\t\t// defaults to '' so \"no run named\" stays one value.\n\t\t\tif (hasUuid) {\n\t\t\t\ttarget.query = { run: String(run.uuid) }\n\t\t\t}\n\t\t\treturn target\n\t\t},\n\n\t\t/**\n\t\t * The href of a run row's link, or '' when the row links nowhere.\n\t\t *\n\t\t * @param {object} run The run row.\n\t\t * @return {string} The resolved href.\n\t\t */\n\t\trunHref(run) {\n\t\t\treturn resolveHref(this.runTarget(run), this.$router)\n\t\t},\n\n\t\t/**\n\t\t * Route a plain click on a run row's link in-app; modified clicks\n\t\t * are left to the browser.\n\t\t *\n\t\t * @param {MouseEvent} event The click event.\n\t\t * @param {object} run The clicked run row.\n\t\t * @return {void}\n\t\t */\n\t\tonRowClick(event, run) {\n\t\t\tfollowLinkClick(event, this.runTarget(run), this.$router)\n\t\t},\n\t},\n}\n\n/**\n * Clamp a configured row limit into the range the endpoint accepts.\n *\n * @param {number|string|undefined} raw The configured limit.\n * @return {number} A limit between 1 and 50 (default 6).\n */\nfunction normaliseLimit(raw) {\n\tconst n = Number(raw)\n\tif (Number.isFinite(n) === false || n <= 0) {\n\t\treturn 6\n\t}\n\treturn Math.min(50, Math.round(n))\n}\n</script>\n\n<style scoped>\n.cn-flow-runs-widget {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 2px;\n\tmin-width: 0;\n}\n\n.cn-flow-runs-widget__loading {\n\tdisplay: flex;\n\tjustify-content: center;\n\tpadding: 8px 0;\n}\n\n.cn-flow-runs-widget__empty,\n.cn-flow-runs-widget__error,\n.cn-flow-runs-widget__more {\n\tmargin: 0;\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 13px;\n}\n\n.cn-flow-runs-widget__more {\n\tpadding-top: 4px;\n}\n\n.cn-flow-runs-widget__list {\n\tlist-style: none;\n\tmargin: 0;\n\tpadding: 0;\n\tmin-width: 0;\n}\n\n.cn-flow-runs-widget__row {\n\tborder-bottom: 1px solid var(--color-border);\n\tmin-width: 0;\n}\n\n.cn-flow-runs-widget__row:last-child {\n\tborder-bottom: none;\n}\n\n.cn-flow-runs-widget__row-content {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 10px;\n\tpadding: 6px 0;\n\tmin-width: 0;\n\tcolor: inherit;\n\ttext-decoration: none;\n}\n\na.cn-flow-runs-widget__row-content {\n\tcursor: pointer;\n}\n\n.cn-flow-runs-widget__dot {\n\tflex: 0 0 auto;\n\twidth: 8px;\n\theight: 8px;\n\tborder-radius: 50%;\n\tbackground: var(--color-text-maxcontrast);\n}\n\n/* Executing now — the primary colour, and the only row that animates. */\n.cn-flow-runs-widget__dot--running {\n\tbackground: var(--color-primary-element, #0082c9);\n\tanimation: cn-flow-runs-pulse 1.6s ease-in-out infinite;\n}\n\n.cn-flow-runs-widget__dot--queued {\n\tbackground: var(--color-warning, #c28900);\n}\n\n.cn-flow-runs-widget__dot--suspended {\n\tbackground: var(--color-text-maxcontrast);\n}\n\n/* Terminal rows: the dot is a hollow RING, so live (filled) and finished\n (hollow) never differ by colour alone. The per-status colour still rides\n on the ring's border. */\n.cn-flow-runs-widget__dot--terminal {\n\twidth: 6px;\n\theight: 6px;\n\tbackground: transparent;\n\tborder: 2px solid var(--color-text-maxcontrast);\n}\n\n.cn-flow-runs-widget__dot--terminal.cn-flow-runs-widget__dot--completed {\n\tborder-color: var(--color-success, #2d7b41);\n}\n\n.cn-flow-runs-widget__dot--terminal.cn-flow-runs-widget__dot--failed,\n.cn-flow-runs-widget__dot--terminal.cn-flow-runs-widget__dot--dead_letter {\n\tborder-color: var(--color-error, #d91f2d);\n}\n\n.cn-flow-runs-widget__body {\n\tdisplay: flex;\n\tflex-direction: column;\n\tmin-width: 0;\n\tflex: 1 1 auto;\n}\n\n.cn-flow-runs-widget__name {\n\tfont-weight: 600;\n\twhite-space: nowrap;\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n}\n\n/* Finished runs read as history: regular weight, muted colour. The hollow\n dot and the section label carry the distinction for anyone who cannot\n rely on colour. */\n.cn-flow-runs-widget__row--terminal .cn-flow-runs-widget__name {\n\tfont-weight: 400;\n\tcolor: var(--color-text-maxcontrast);\n}\n\n/* Must sit AFTER the plain `__name` rule: the compound selector is more\n specific, and stylelint's no-descending-specificity flags the reverse\n order because the cascade then depends on specificity rather than\n source order — the shape that quietly stops working when someone adds\n a competing rule. */\na.cn-flow-runs-widget__row-content:hover .cn-flow-runs-widget__name {\n\ttext-decoration: underline;\n}\n\n.cn-flow-runs-widget__meta {\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 12px;\n\twhite-space: nowrap;\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n}\n\n.cn-flow-runs-widget__age {\n\tflex: 0 0 auto;\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 12px;\n\tfont-variant-numeric: tabular-nums;\n}\n\n/* The history section label. Small caps text, not a colour: the section\n boundary must survive monochrome. */\n.cn-flow-runs-widget__history-title {\n\tmargin: 0;\n\tpadding-top: 8px;\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 12px;\n\tfont-weight: 600;\n\ttext-transform: uppercase;\n\tletter-spacing: 0.04em;\n}\n\n@keyframes cn-flow-runs-pulse {\n\t0%, 100% { opacity: 1; }\n\t50% { opacity: 0.35; }\n}\n\n/* Respect a reduced-motion preference: the pulse is decoration, and the dot\n colour already carries the status. */\n@media (prefers-reduced-motion: reduce) {\n\t.cn-flow-runs-widget__dot--running {\n\t\tanimation: none;\n\t}\n}\n</style>\n"],"names":["_openBlock","_createElementBlock","_toDisplayString","_createVNode","_Fragment","_renderList","_normalizeClass","_createBlock","_resolveDynamicComponent","_createElementVNode"],"mappings":";;AASM,MAAA,UAAA,GAAA,EAAA,KAAK,EAAC,qBAAqB,GAAA;;;AAGf,EAAA,KAAM,EAAA,4BAA4B;;;;AAMJ,EAAA,KAAA,EAAA,8BAAA;;;;AAKtB,EAAA,KAAK,EAAA,4BAA6B;;;;AAKA,EAAA,KAAK,EAAA,4BAAA;;;;AAI3D,EAAA,KAAA,EAAA,4BAAA;;;;AAGQ,EAAA,KAAK,EAAC,2BAAgB;;;;AAgBxB,MAAA,WAAA,GAAA,EAAA,KAAK,EAAC,2BAA2B,GAAA;AAChC,MAAA,WAAA,GAAA,EAAA,KAAG,EAAA,2BAAA,GAAA;AACR,MAAA,WAAA,GAAA,EAAA,KAAA,EAAA,2BAAA,GAAA;AAEI,MAAA,WAAA,GAAA,EAAA,KAAK,EAAC,0BAA0B,GAAA;;;AAMd,EAAA,KAAI,EAAC,2BAA2B;;AAOtD,MAAA,WAAA,GAAA,EAAA,KAAI,EAAC,oCAAiC,GAAA;;;AAGvC,EAAA,KAAA,EAAA,4BAAA;;;;AAGQ,EAAA,KAAK,EAAC,2BAA2B;;;;AAgBnC,MAAA,WAAA,GAAA,EAAA,KAAK,EAAC,2BAA2B,GAAA;AAChC,MAAA,WAAA,GAAA,EAAA,KAAG,EAAA,2BAAA,GAAA;AACR,MAAA,WAAA,GAAA,EAAA,KAAA,EAAA,2BAAA,GAAA;AAEI,MAAA,WAAA,GAAA,EAAA,KAAK,EAAC,0BAA0B,GAAA;;;AAIjC,EAAA,KAAA,EAAA,2BAAA;;;;;;AAzFT,EAAA,QAAAA,SAAA,EAAA,EAAAC,kBAAA,QAAA;KAGQ,MAAK,CAAA,KAAA;wCAET,CAAA,GAAA,YAAA,EAAAC,eAAA,CADA,QAAC,CAAA,EAAA,CAAA,kCAAA,CAAA,CAAA,EAAA,CAAA,YAAA;AAKW,QAAA,CAAA,CAAA,MAAA,CAAA,OAAO,IAAI,QAAA,CAAA,cAAc,KAAI,QAAA,CAAA,IAAA,CAAA,MAAA,KAAA,CAAA;AAAzC,WAAAF,SAAA,EAAA,EAAAC,kBAAA,CAEC,OAFD,UAEC,EAAA;YADLE,WAA4B,CAAA,wBAAA,EAAA,EAAZ,IAAI,EAAC,EAAG,EAAA,CAAA;;WAIZ,QAAS,CAAA,QAAA;4CAElB,CAAA,GAAA,YAAA,EAAAD,eAAA,CADA,QAAE,CAAA,EAAA,CAAA,uBAAA,CAAA,CAAA,EAAA,CAAA,YAAA;aAIQ,QAAI,CAAA,IAAA,CAAC,MAAO,KAAA,CAAA,IAAS,QAAW,CAAA,WAAA,KAAA,KAAA;8CAE1C,CAAA,GAAA,YAAA,EAAAA,eAAA,CADD,QAAW,CAAA,UAAA,CAAA,EAAA,CAAA,YAAA;4BAEbD;AACG,gBAAA,CAAA,QAAA,CAAA,IAAA,CAAA,MAAA,KAAA,CAAA;kCAAFA,kBAEG,CAAA,GAAA,EAFH,UAEG,EAAAC,eAAA,CADA,QAAU,CAAA,UAAA,CAAA,EAAA,CAAA,YAAA;AAEZ,qBAAAF,SAAA,EAAA,EAAAC,kBAAA,CAuBG,MAvBH,UAuBG,EAAA;0DADC,CAAAG,QAAA,EAAA,IAAA,EAAAC,UAAA,CApBU,QAAI,CAAA,IAAA,EAAA,CAAX,GAAG,KAAA;+DAoBN,CAAA,IAAA,EAAA;0BAnBH,GAAG,EAAE,GAAG,CAAA,IAAA;0BACR,KAAI,EAAAC,cAAA,CAAA,CAAC,0BAA2B,EAAA,EAAA,kCAAA,EACO,QAAA,CAAA,QAAA,EAAA,CAAA,CAAA;0BACvC,aAAW,EAAE,GAAG,CAAC,MAAM;;AACvB,2BAAAN,SAAA,EAAA,EAAAO,WAAA,CAcUC,uBAbN,CAAA,QAAA,CAAA,QAAA,CAAA,GAAA,CAAA,IAAA,CAAA,GAAA,GAAA,GAAA,KAAA,CAAA,EAAA;AACH,4BAAA,KAAI,EAAC,kCAA0B;AAC/B,4BAAA,IAAI,EAAE,QAAQ,CAAA,QAAA,CAAC,GAAG,CAAC,IAAI,KAAK,SAAS;AACrC,4BAAA,OAAK,EAAE,MAAA,KAAA,QAAA,CAAA,UAAU,CAAC,MAAM,EAAE,GAAG,CAAA,CAAA;;6CAC9B,MAGM;8BAHNC,kBAGM,CAAA,MAAA,EAAA;AAFL,gCAAA,KAAM,EAAAH,cAAA,CAAA,CAAA,0BAA0B,EACK,CAAA,0BAAA,EAAA,GAAG,CAAC,MAAM,CAAA,CAAA,CAAA,CAAA;AAC9C,gCAAA,KAAI,EAAA,QAAA,CAAA,WAAA,CAAA,GAAA,CAAA,MAAA,CAAA;;AACN,8BAAAG,kBAAA,CAGC,QAHD,WAGC,EAAA;AAFA,gCAAAA,kBAAA,CAAS,MAAT,EAAA,WAAS,EAAAP,eAAA,CAAA,GAAA,CAAA,QAAA,CAAA,EAAA,CAAA,YAAA;AACT,gCAAAO,kBAAA,CAAC,MAAD,EAAA,WAAC,EAAAP,eAAA,CAAA,QAAA,CAAA,QAAA,CAAA,GAAA,CAAA,CAAA,EAAA,CAAA,YAAA;;AAEF,8BAAAO,kBAAA,CAA8D,MAA9D,EAAA,WAA8D,EAApBP,eAAA,CAAA,QAAA,CAAA,QAAO,CAAE,GAAG,CAAA,CAAA,EAAA,CAAA,YAAA;;;;;;;iBAMhD,QAAW,CAAA,WAAA,GAAA,CAAA;oDAEhB,CAAA,GAAA,aAAA,EAAAA,eAAA,SADS,CAAA,SAAA,CAAA,EAAA,CAAA,YAAA;;iBAKG,QAAW,CAAA,WAAA;;sBACzBO,kBAEG,CAAA,GAAA,EAFH,WAEG,EAAAP,eAAA,CADD,QAAA,CAAA,EAAA,CAAA,cAAA,CAAA,CAAA,EAAA,CAAA,YAAA;uBAEA,MAAA,CAAA,cAAA;wCAAFD,kBAEG,CAAA,GAAA,EAFH,WAEG,EAAAC,eAAA,CADA,QAAE,CAAA,EAAA,CAAA,gCAAA,CAAA,CAAA,EAAA,CAAA,YAAA;AAEJ,2BAAAF,SAAA,EAAA,EAAAC,kBAAA,CAuBG,MAvBH,WAuBG,EAAA;gEADF,CAAAG,QAAA,EAAA,IAAA,EAAAC,UAAA,CApBa,QAAW,CAAA,aAAA,EAAA,CAAlB,GAAG,KAAA;qEAoBT,CAAA,IAAA,EAAA;gCAnBA,GAAG,EAAE,GAAG,CAAA,IAAA;gCACR,KAAI,EAAAC,cAAA,CAAA,CAAC,6DAAgC,EAAA,EAAA,kCAAA,EACE,QAAA,CAAA,QAAA,EAAA,CAAA,CAAA;gCACvC,aAAW,EAAE,GAAG,CAAC,MAAM;;AACvB,iCAAAN,SAAA,EAAA,EAAAO,WAAA,CAcWC,uBAbP,CAAA,QAAA,CAAA,QAAA,CAAA,GAAA,CAAA,IAAA,CAAA,GAAA,GAAA,GAAA,KAAA,CAAA,EAAA;AACH,kCAAA,KAAI,EAAC,kCAAkC;AACvC,kCAAA,IAAI,EAAE,QAAQ,CAAA,QAAA,CAAC,GAAG,CAAC,IAAI,KAAK,SAAS;AACrC,kCAAA,OAAK,EAAE,MAAA,KAAA,QAAA,CAAA,UAAU,CAAC,MAAM,EAAE,GAAG,CAAA,CAAA;;mDAC9B,MAGM;oCAHNC,kBAGM,CAAA,MAAA,EAAA;AAFL,sCAAA,KAAM,EAAAH,cAAA,CAAA,CAAA,6DAA0D,EAC3B,CAAA,0BAAA,EAAA,GAAG,CAAC,MAAM,CAAA,CAAA,CAAA,CAAA;AAC9C,sCAAA,KAAI,EAAA,QAAA,CAAA,WAAA,CAAA,GAAA,CAAA,MAAA,CAAA;;AACN,oCAAAG,kBAAA,CAGC,QAHD,WAGC,EAAA;AAFA,sCAAAA,kBAAA,CAAS,MAAT,EAAA,WAAS,EAAAP,eAAA,CAAA,GAAA,CAAA,QAAA,CAAA,EAAA,CAAA,YAAA;AACT,sCAAAO,kBAAA,CAAC,MAAD,EAAA,WAAC,EAAAP,eAAA,CAAA,QAAA,CAAA,QAAA,CAAA,GAAA,CAAA,CAAA,EAAA,CAAA,YAAA;;AAEF,oCAAAO,kBAAA,CAAiE,MAAjE,EAAA,WAAiE,EAAvBP,eAAA,CAAA,QAAA,CAAA,QAAQ,CAAE,GAAE,CAAA,CAAA,EAAA,CAAA,YAAA;;;;;;;AAIjD,sBAAA,CAAA,MAAA,CAAA,cAAA,WAAA,QAAA,CAAA,oBAAA,GAAA,CAAA;wCAARD,wBAAA,6BACE,QAAA,CAAA,kBAAA,CAAA,EAAA,CAAA,YAAA;;;;;;;;;;"}
|
|
1
|
+
{"version":3,"file":"CnFlowRunsWidget.vue3.js","sources":["../../../../src/components/CnFlowRunsWidget/CnFlowRunsWidget.vue?vue&type=template&id=75628d22&lang.js"],"sourcesContent":["<!--\n - SPDX-FileCopyrightText: 2026 Conduction B.V.\n - SPDX-License-Identifier: EUPL-1.2\n-->\n<template>\n\t<div class=\"cn-flow-runs-widget\">\n\t\t<!-- One quiet line on failure, never a leaked axios status string\n\t\t (ADR-062). The real error goes to the console. -->\n\t\t<p v-if=\"error\" class=\"cn-flow-runs-widget__error\">\n\t\t\t{{ tr('Could not load the running flows') }}\n\t\t</p>\n\t\t<!-- A configured-but-unresolved subject token also shows the loading\n\t\t state: \"no runs\" would be a claim about a case the widget has not\n\t\t identified yet. -->\n\t\t<div v-else-if=\"(loading || subjectPending) && rows.length === 0\" class=\"cn-flow-runs-widget__loading\">\n\t\t\t<NcLoadingIcon :size=\"24\" />\n\t\t</div>\n\t\t<!-- A subject where nothing EVER ran gets its own line: distinct from\n\t\t a case that is merely quiet right now. -->\n\t\t<p v-else-if=\"neverRan\" class=\"cn-flow-runs-widget__empty\">\n\t\t\t{{ tr('No flows have run yet') }}\n\t\t</p>\n\t\t<!-- Nothing running is the NORMAL state for this widget, not an\n\t\t error and not a void: one muted line, no illustration. -->\n\t\t<p v-else-if=\"rows.length === 0 && showHistory === false\" class=\"cn-flow-runs-widget__empty\">\n\t\t\t{{ emptyLabel }}\n\t\t</p>\n\t\t<template v-else>\n\t\t\t<p v-if=\"rows.length === 0\" class=\"cn-flow-runs-widget__empty\">\n\t\t\t\t{{ emptyLabel }}\n\t\t\t</p>\n\t\t\t<ul v-else class=\"cn-flow-runs-widget__list\">\n\t\t\t\t<li\n\t\t\t\t\tv-for=\"(run, index) in rows\"\n\t\t\t\t\t:key=\"run.uuid || `row-${index}`\"\n\t\t\t\t\tclass=\"cn-flow-runs-widget__row\"\n\t\t\t\t\t:class=\"{ 'cn-flow-runs-widget__row--linked': isLinked }\"\n\t\t\t\t\t:data-status=\"run.status\">\n\t\t\t\t\t<component\n\t\t\t\t\t\t:is=\"runHref(run) ? 'a' : 'div'\"\n\t\t\t\t\t\tclass=\"cn-flow-runs-widget__row-content\"\n\t\t\t\t\t\t:href=\"runHref(run) || undefined\"\n\t\t\t\t\t\t@click=\"onRowClick($event, run)\">\n\t\t\t\t\t\t<span\n\t\t\t\t\t\t\tclass=\"cn-flow-runs-widget__dot\"\n\t\t\t\t\t\t\t:class=\"`cn-flow-runs-widget__dot--${run.status}`\"\n\t\t\t\t\t\t\t:title=\"statusLabel(run.status)\" />\n\t\t\t\t\t\t<span class=\"cn-flow-runs-widget__body\">\n\t\t\t\t\t\t\t<span class=\"cn-flow-runs-widget__name\">{{ run.flowName }}</span>\n\t\t\t\t\t\t\t<span class=\"cn-flow-runs-widget__meta\">{{ metaLine(run) }}</span>\n\t\t\t\t\t\t</span>\n\t\t\t\t\t\t<span class=\"cn-flow-runs-widget__age\">{{ ageLabel(run) }}</span>\n\t\t\t\t\t</component>\n\t\t\t\t</li>\n\t\t\t</ul>\n\t\t\t<!-- The remainder is a count, never a scrollbar: the widget shows\n\t\t\t what fits its cell and states the honest total (ADR-062). -->\n\t\t\t<p v-if=\"hiddenCount > 0\" class=\"cn-flow-runs-widget__more\">\n\t\t\t\t{{ moreLabel }}\n\t\t\t</p>\n\t\t\t<!-- The subject's run history. Terminal rows sit under their own\n\t\t\t labelled section with a hollow dot and a muted name, so live\n\t\t\t and finished never rely on colour alone to tell apart. -->\n\t\t\t<template v-if=\"showHistory\">\n\t\t\t\t<p class=\"cn-flow-runs-widget__history-title\">\n\t\t\t\t\t{{ tr('Earlier runs') }}\n\t\t\t\t</p>\n\t\t\t\t<p v-if=\"completedError\" class=\"cn-flow-runs-widget__error\">\n\t\t\t\t\t{{ tr('Could not load the run history') }}\n\t\t\t\t</p>\n\t\t\t\t<ul v-else class=\"cn-flow-runs-widget__list\">\n\t\t\t\t\t<li\n\t\t\t\t\t\tv-for=\"(run, index) in completedRows\"\n\t\t\t\t\t\t:key=\"run.uuid || `completed-${index}`\"\n\t\t\t\t\t\tclass=\"cn-flow-runs-widget__row cn-flow-runs-widget__row--terminal\"\n\t\t\t\t\t\t:class=\"{ 'cn-flow-runs-widget__row--linked': isLinked }\"\n\t\t\t\t\t\t:data-status=\"run.status\">\n\t\t\t\t\t\t<component\n\t\t\t\t\t\t\t:is=\"runHref(run) ? 'a' : 'div'\"\n\t\t\t\t\t\t\tclass=\"cn-flow-runs-widget__row-content\"\n\t\t\t\t\t\t\t:href=\"runHref(run) || undefined\"\n\t\t\t\t\t\t\t@click=\"onRowClick($event, run)\">\n\t\t\t\t\t\t\t<span\n\t\t\t\t\t\t\t\tclass=\"cn-flow-runs-widget__dot cn-flow-runs-widget__dot--terminal\"\n\t\t\t\t\t\t\t\t:class=\"`cn-flow-runs-widget__dot--${run.status}`\"\n\t\t\t\t\t\t\t\t:title=\"statusLabel(run.status)\" />\n\t\t\t\t\t\t\t<span class=\"cn-flow-runs-widget__body\">\n\t\t\t\t\t\t\t\t<span class=\"cn-flow-runs-widget__name\">{{ run.flowName }}</span>\n\t\t\t\t\t\t\t\t<span class=\"cn-flow-runs-widget__meta\">{{ metaLine(run) }}</span>\n\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t<span class=\"cn-flow-runs-widget__age\">{{ ageLabel(run) }}</span>\n\t\t\t\t\t\t</component>\n\t\t\t\t\t</li>\n\t\t\t\t</ul>\n\t\t\t\t<p v-if=\"completedError === '' && completedHiddenCount > 0\" class=\"cn-flow-runs-widget__more\">\n\t\t\t\t\t{{ completedMoreLabel }}\n\t\t\t\t</p>\n\t\t\t</template>\n\t\t</template>\n\t</div>\n</template>\n\n<script>\nimport { translate as t } from '@nextcloud/l10n'\nimport { NcLoadingIcon } from '@nextcloud/vue'\nimport { computed, inject, ref } from 'vue'\nimport { useEndpointSource } from '../../composables/useEndpointSource.js'\nimport { resolveObjectTokenContext } from '../../utils/detailObjectContext.js'\nimport { followLinkClick, resolveHref } from '../../utils/linkNavigation.js'\nimport { resolveFilterValue } from '../../utils/resolveFilterTokens.js'\n\n/**\n * The OpenRegister endpoint that lists the caller's live flow runs.\n *\n * Every Conduction app runs its flows on OpenRegister's ONE flow engine\n * (ADR-065), so \"what is running right now\" has a single answer for the whole\n * instance and this widget can be app-agnostic: an app places it and gets its\n * users' live runs without shipping a controller, a store or a view.\n *\n * @type {string}\n */\nconst ACTIVE_RUNS_URL = '/apps/openregister/api/flow-runs/active'\n\n/**\n * The OpenRegister endpoint that lists the finished runs for ONE subject\n * object. Subject-required by contract (openregister change\n * `flow-runs-subject-scope`): there is no org-wide \"all finished runs\" here,\n * so the widget only ever calls it with a resolved subject uuid.\n *\n * @type {string}\n */\nconst COMPLETED_RUNS_URL = '/apps/openregister/api/flow-runs/completed'\n\n/**\n * Statuses, in the order a person reads them: what is executing, what is\n * queued behind it, what is parked waiting on a timer or a child run, and\n * then the terminal set a subject's run history renders.\n *\n * @type {Record<string, string>}\n */\nconst STATUS_LABELS = {\n\trunning: 'Running',\n\tqueued: 'Queued',\n\tsuspended: 'Waiting',\n\tcompleted: 'Completed',\n\tstopped: 'Stopped',\n\tfailed: 'Failed',\n\tdead_letter: 'Failed after retries',\n}\n\n/**\n * The terminal statuses (FlowRun::TERMINAL on the OpenRegister side). Rows\n * with one of these are HISTORY: rendered in the earlier-runs section with a\n * hollow dot and a muted name, never mixed into the live list.\n *\n * @type {string[]}\n */\nconst TERMINAL_STATUSES = ['completed', 'stopped', 'failed', 'dead_letter']\n\n/**\n * CnFlowRunsWidget — the live flow runs for the viewer's organisation.\n *\n * Reads OpenRegister's `flow-runs/active` surface (non-terminal runs, scoped\n * server-side to the caller's organisation) and renders one row per run: its\n * flow's NAME, what triggered it, the step it currently sits on, and how long\n * it has been going. Placed by its registry type key `flow-runs`, so any app\n * gets the widget from a single manifest placement:\n *\n * ```js\n * { id: 'running-flows', type: 'flow-runs', title: 'Running flows',\n * content: { limit: 6, pollSeconds: 15, rowRoute: 'GraphDetail' } }\n * ```\n *\n * Why it polls: a run is a moving thing. A widget titled \"running flows\" that\n * only reflects the moment the dashboard mounted would be wrong within\n * seconds and would look identical to being right — so it refetches on an\n * interval, and stops while the tab is hidden so an idle dashboard is not a\n * background request loop.\n *\n * Statuses shown are ALL the non-terminal ones (queued / running / suspended),\n * because filtering to literally `running` would show an empty widget almost\n * always: a run holds that status only during a worker pass, while queued and\n * suspended are where live runs actually wait. See FlowRun::ACTIVE.\n *\n * Subject mode: a `content.subject` (a subject object uuid, or the\n * `@objectId` / `@object.<field>` token a detail placement authors) narrows\n * the live list to one case AND adds that case's finished runs below it, so\n * a flow that completed does not look like nothing ever happened. Tokens\n * resolve against the detail surface's injected object context, the same\n * route CnStatWidget and CnChartWidget take.\n *\n * @spec openspec/changes/cn-flow-runs-widget/specs/cn-flow-runs-widget/spec.md\n * @spec openspec/changes/cn-flow-runs-widget-subject/specs/cn-flow-runs-widget-subject/spec.md\n */\nexport default {\n\tname: 'CnFlowRunsWidget',\n\n\tcomponents: {\n\t\tNcLoadingIcon,\n\t},\n\n\tinject: {\n\t\t/**\n\t\t * Host translate function, provided by CnAppRoot (identity by default)\n\t\t * so a library string localises through the consuming app's l10n.\n\t\t */\n\t\tcnTranslate: { default: () => (key) => key },\n\t},\n\n\tprops: {\n\t\t/**\n\t\t * The widget's persisted configuration blob.\n\t\t *\n\t\t * - `limit` — rows to request AND show (1–50, default 6). The total is\n\t\t * always reported honestly regardless of this cap.\n\t\t * - `pollSeconds` — refetch interval (default 15; `0` disables polling).\n\t\t * - `rowRoute` — vue-router route NAME to open on a row click, receiving\n\t\t * the run's flow id as `:id` and the clicked run's uuid as `?run=`, so\n\t\t * the flow opens with that run inspected rather than merely open.\n\t\t * Omitted = rows are not clickable, which is correct for an app that\n\t\t * has no flow-detail page.\n\t\t * - `runRoute` — vue-router route NAME to open on a row click, receiving\n\t\t * the RUN's uuid as `:id`. Takes precedence over `rowRoute`; a row\n\t\t * without a run uuid falls back to the `rowRoute` flow-id behaviour.\n\t\t * - `subject` — a subject object uuid, or an object-context token\n\t\t * (`@objectId`, `@object.<field>`) a detail placement authors. When\n\t\t * set, the live list is filtered to that subject server-side and the\n\t\t * subject's finished runs render below it. Absent = today's org-wide\n\t\t * widget, unchanged.\n\t\t * - `emptyText` — override for the \"nothing running\" line.\n\t\t *\n\t\t * @type {{limit?: number, pollSeconds?: number, rowRoute?: string, runRoute?: string, subject?: string, emptyText?: string, title?: string}}\n\t\t */\n\t\tcontent: {\n\t\t\ttype: Object,\n\t\t\tdefault: () => ({}),\n\t\t},\n\n\t\t/**\n\t\t * Widget placement id, so the per-widget Refresh action\n\t\t * (`cn:widget:refresh`) reaches this instance's fetch.\n\t\t */\n\t\twidgetId: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * Translate function. Falls back to the injected `cnTranslate`.\n\t\t * Provide explicitly when mounting outside a CnAppRoot ancestor.\n\t\t *\n\t\t * @type {((key: string) => string)|null}\n\t\t */\n\t\ttranslate: {\n\t\t\ttype: Function,\n\t\t\tdefault: null,\n\t\t},\n\t},\n\n\tsetup(props) {\n\t\t// The shared endpoint engine owns request dedup, the short-TTL cache\n\t\t// and the cn:page:refresh / cn:widget:refresh subscriptions — the same\n\t\t// binding CnStatWidget's endpoint mode uses, so a page-level Refresh\n\t\t// updates this widget with everything else on the page.\n\t\tconst widgetIdRef = ref(props.widgetId)\n\n\t\t// The detail surface's object context, injected the same way the other\n\t\t// endpoint-bound widgets (stat / chart / audit-trail) receive it, so a\n\t\t// `subject: '@objectId'` placement binds the CURRENT object without the\n\t\t// manifest hardcoding a uuid. Null on plain dashboards — the token then\n\t\t// stays unresolved and the endpoint engine's `blocked` semantics hold\n\t\t// the fetch instead of sending a literal `@objectId` to the server.\n\t\tconst objectCtxRaw = inject('cnObjectContext', null)\n\t\tconst detailCtxRaw = inject('cnDetailObjectContext', null)\n\t\tconst tokenCtx = () => ({\n\t\t\tworkspace: {},\n\t\t\tconfig: {},\n\t\t\t...(resolveObjectTokenContext(objectCtxRaw, detailCtxRaw) || {}),\n\t\t})\n\n\t\tconst configuredSubject = () => {\n\t\t\tconst subject = props.content && props.content.subject\n\t\t\treturn (typeof subject === 'string' && subject !== '') ? subject : ''\n\t\t}\n\n\t\t/**\n\t\t * The subject uuid after token resolution: `''` when no subject is\n\t\t * configured, `null` while a token still waits on the object context,\n\t\t * else the concrete uuid.\n\t\t */\n\t\tconst resolvedSubject = computed(() => {\n\t\t\tconst subject = configuredSubject()\n\t\t\tif (subject === '') {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\tconst value = resolveFilterValue(subject, tokenCtx())\n\t\t\tif (typeof value === 'string' && value.charAt(0) === '@') {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\treturn (value === undefined || value === null) ? null : String(value)\n\t\t})\n\n\t\tconst source = () => {\n\t\t\tconst params = { limit: normaliseLimit(props.content && props.content.limit) }\n\t\t\tconst subject = configuredSubject()\n\t\t\tif (subject !== '') {\n\t\t\t\tparams.subject = subject\n\t\t\t}\n\t\t\treturn { url: ACTIVE_RUNS_URL, method: 'GET', params }\n\t\t}\n\t\tconst { data, loading, error, refetch } = useEndpointSource(source, {\n\t\t\twidgetId: widgetIdRef,\n\t\t\tctx: tokenCtx,\n\t\t})\n\n\t\t// The history half of the subject view. A null config (no subject)\n\t\t// means the composable never queries: the org-wide widget stays a\n\t\t// single-request surface, bit-identical to before.\n\t\tconst completedSource = () => {\n\t\t\tconst subject = configuredSubject()\n\t\t\tif (subject === '') {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\treturn {\n\t\t\t\turl: COMPLETED_RUNS_URL,\n\t\t\t\tmethod: 'GET',\n\t\t\t\tparams: { subject, limit: normaliseLimit(props.content && props.content.limit) },\n\t\t\t}\n\t\t}\n\t\tconst {\n\t\t\tdata: completedData,\n\t\t\tloading: completedLoading,\n\t\t\terror: completedError,\n\t\t\trefetch: refetchCompleted,\n\t\t} = useEndpointSource(completedSource, {\n\t\t\twidgetId: widgetIdRef,\n\t\t\tctx: tokenCtx,\n\t\t})\n\n\t\t// `inject` is re-read in setup so Vue 2.7 and Vue 3 resolve it\n\t\t// identically to the Options `inject` block (CnStatWidget precedent).\n\t\tconst injectedTranslate = inject('cnTranslate', null)\n\n\t\treturn {\n\t\t\tpayload: data,\n\t\t\tloading,\n\t\t\terror,\n\t\t\trefetch,\n\t\t\tcompletedPayload: completedData,\n\t\t\tcompletedLoading,\n\t\t\tcompletedError,\n\t\t\trefetchCompleted,\n\t\t\tresolvedSubject,\n\t\t\tinjectedTranslate,\n\t\t}\n\t},\n\n\tdata() {\n\t\treturn {\n\t\t\tpollTimer: null,\n\t\t}\n\t},\n\n\tcomputed: {\n\t\t/**\n\t\t * Effective translate function: the explicit prop, then the injected\n\t\t * host one, then `@nextcloud/l10n` under the library's own app id.\n\t\t *\n\t\t * @return {(key: string) => string}\n\t\t */\n\t\ttr() {\n\t\t\tconst fn = this.translate ?? this.injectedTranslate ?? this.cnTranslate\n\t\t\tif (typeof fn === 'function') {\n\t\t\t\treturn (key) => fn(key)\n\t\t\t}\n\t\t\treturn (key) => t('nextcloud-vue', key)\n\t\t},\n\n\t\t/**\n\t\t * The rows to render — the endpoint's bounded page, defensively\n\t\t * re-capped so a wider server default can never overflow the cell.\n\t\t *\n\t\t * @return {Array<object>} The run rows.\n\t\t */\n\t\trows() {\n\t\t\tconst results = (this.payload && this.payload.results) || []\n\t\t\tif (Array.isArray(results) === false) {\n\t\t\t\treturn []\n\t\t\t}\n\t\t\treturn results.slice(0, normaliseLimit(this.content.limit))\n\t\t},\n\n\t\t/**\n\t\t * How many live runs exist beyond the rendered rows.\n\t\t *\n\t\t * Read off the server's `total`, NOT the array length — the point of\n\t\t * the total is to be honest about what the cell could not fit.\n\t\t *\n\t\t * @return {number} The hidden run count (never negative).\n\t\t */\n\t\thiddenCount() {\n\t\t\tconst total = Number((this.payload && this.payload.total) ?? this.rows.length)\n\t\t\tif (Number.isFinite(total) === false) {\n\t\t\t\treturn 0\n\t\t\t}\n\t\t\treturn Math.max(0, total - this.rows.length)\n\t\t},\n\n\t\t/** The \"+N more\" line. */\n\t\tmoreLabel() {\n\t\t\treturn this.tr('+{count} more').replace('{count}', String(this.hiddenCount))\n\t\t},\n\n\t\t/** The nothing-running line. */\n\t\temptyLabel() {\n\t\t\tconst override = this.content.emptyText\n\t\t\tif (typeof override === 'string' && override !== '') {\n\t\t\t\treturn this.tr(override)\n\t\t\t}\n\t\t\treturn this.tr('No flows are running')\n\t\t},\n\n\t\t/** Whether this placement is scoped to one subject object. */\n\t\thasSubject() {\n\t\t\treturn typeof this.content.subject === 'string' && this.content.subject !== ''\n\t\t},\n\n\t\t/**\n\t\t * Whether a configured subject token still waits on the object\n\t\t * context. The widget shows its loading state instead of an empty\n\t\t * line then: \"no runs\" would be a claim about a case it has not\n\t\t * identified yet.\n\t\t *\n\t\t * @return {boolean} True while the subject is configured but unresolved.\n\t\t */\n\t\tsubjectPending() {\n\t\t\treturn this.hasSubject && this.resolvedSubject === null\n\t\t},\n\n\t\t/**\n\t\t * The subject's finished runs — the completed-runs read's bounded\n\t\t * page, re-capped like the live rows.\n\t\t *\n\t\t * @return {Array<object>} The terminal run rows (empty without a subject).\n\t\t */\n\t\tcompletedRows() {\n\t\t\tif (this.hasSubject === false) {\n\t\t\t\treturn []\n\t\t\t}\n\t\t\tconst results = (this.completedPayload && this.completedPayload.results) || []\n\t\t\tif (Array.isArray(results) === false) {\n\t\t\t\treturn []\n\t\t\t}\n\t\t\treturn results.slice(0, normaliseLimit(this.content.limit))\n\t\t},\n\n\t\t/**\n\t\t * How many finished runs exist beyond the rendered history rows, read\n\t\t * off the completed read's honest total.\n\t\t *\n\t\t * @return {number} The hidden finished-run count (never negative).\n\t\t */\n\t\tcompletedHiddenCount() {\n\t\t\tconst total = Number((this.completedPayload && this.completedPayload.total) ?? this.completedRows.length)\n\t\t\tif (Number.isFinite(total) === false) {\n\t\t\t\treturn 0\n\t\t\t}\n\t\t\treturn Math.max(0, total - this.completedRows.length)\n\t\t},\n\n\t\t/** The \"+N earlier\" line under the history section. */\n\t\tcompletedMoreLabel() {\n\t\t\treturn this.tr('+{count} earlier').replace('{count}', String(this.completedHiddenCount))\n\t\t},\n\n\t\t/**\n\t\t * Whether this subject has NO runs at all, live or finished. Rendered\n\t\t * as its own line: a case where nothing ever ran reads differently\n\t\t * from a case that is merely quiet right now.\n\t\t *\n\t\t * @return {boolean} True when both reads settled empty for a subject.\n\t\t */\n\t\tneverRan() {\n\t\t\treturn this.hasSubject\n\t\t\t\t&& this.subjectPending === false\n\t\t\t\t&& this.loading === false\n\t\t\t\t&& this.completedLoading === false\n\t\t\t\t&& this.completedError === ''\n\t\t\t\t&& this.rows.length === 0\n\t\t\t\t&& this.completedRows.length === 0\n\t\t},\n\n\t\t/** Whether the history section renders (rows or a failed history read). */\n\t\tshowHistory() {\n\t\t\treturn this.hasSubject\n\t\t\t\t&& this.subjectPending === false\n\t\t\t\t&& (this.completedRows.length > 0 || this.completedError !== '')\n\t\t},\n\n\t\t/** Whether a row click navigates (a `runRoute` or `rowRoute` is configured). */\n\t\tisLinked() {\n\t\t\treturn (typeof this.content.rowRoute === 'string' && this.content.rowRoute !== '')\n\t\t\t\t|| (typeof this.content.runRoute === 'string' && this.content.runRoute !== '')\n\t\t},\n\n\t\t/**\n\t\t * Poll interval in ms, or 0 when polling is off.\n\t\t *\n\t\t * Floored at 5s so a mis-authored `pollSeconds: 0.1` cannot turn a\n\t\t * dashboard into a request storm; `0` remains an explicit opt-out.\n\t\t *\n\t\t * @return {number} The interval in milliseconds.\n\t\t */\n\t\tpollMs() {\n\t\t\tconst raw = this.content.pollSeconds\n\t\t\tconst seconds = (raw === undefined || raw === null) ? 15 : Number(raw)\n\t\t\tif (Number.isFinite(seconds) === false || seconds <= 0) {\n\t\t\t\treturn 0\n\t\t\t}\n\t\t\treturn Math.max(5, seconds) * 1000\n\t\t},\n\t},\n\n\tmounted() {\n\t\tthis.startPolling()\n\t\tdocument.addEventListener('visibilitychange', this.onVisibilityChange)\n\t},\n\n\tbeforeUnmount() {\n\t\tthis.teardown()\n\t},\n\n\tmethods: {\n\t\t/**\n\t\t * Start (or restart) the refetch interval, unless polling is disabled.\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\tstartPolling() {\n\t\t\tthis.stopPolling()\n\t\t\tif (this.pollMs === 0 || document.hidden === true) {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.pollTimer = setInterval(() => {\n\t\t\t\tthis.refetchAll()\n\t\t\t}, this.pollMs)\n\t\t},\n\n\t\t/**\n\t\t * Refetch the live read, and the history read when a subject is\n\t\t * configured — a run that finishes between polls must MOVE to the\n\t\t * history section, not vanish from the widget.\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\trefetchAll() {\n\t\t\tthis.refetch(true)\n\t\t\tif (this.hasSubject) {\n\t\t\t\tthis.refetchCompleted(true)\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Clear the refetch interval.\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\tstopPolling() {\n\t\t\tif (this.pollTimer !== null) {\n\t\t\t\tclearInterval(this.pollTimer)\n\t\t\t\tthis.pollTimer = null\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Remove the timer and the visibility listener.\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\tteardown() {\n\t\t\tthis.stopPolling()\n\t\t\tdocument.removeEventListener('visibilitychange', this.onVisibilityChange)\n\t\t},\n\n\t\t/**\n\t\t * Pause polling while the tab is hidden; refetch once and resume when\n\t\t * it comes back, so returning to the dashboard shows current state\n\t\t * rather than whatever was true when the tab was backgrounded.\n\t\t *\n\t\t * @return {void}\n\t\t */\n\t\tonVisibilityChange() {\n\t\t\tif (document.hidden === true) {\n\t\t\t\tthis.stopPolling()\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.refetchAll()\n\t\t\tthis.startPolling()\n\t\t},\n\n\t\t/**\n\t\t * Whether a run's status is terminal — the history half of the\n\t\t * subject view. Drives the hollow-dot / muted-name row treatment.\n\t\t *\n\t\t * @param {object} run The run row.\n\t\t * @return {boolean} True for a finished run.\n\t\t */\n\t\tisTerminal(run) {\n\t\t\treturn TERMINAL_STATUSES.includes(run && run.status)\n\t\t},\n\n\t\t/**\n\t\t * The human label for a status, falling back to the raw value so an\n\t\t * engine that gains a status still renders something truthful.\n\t\t *\n\t\t * @param {string} status The run status.\n\t\t * @return {string} The label.\n\t\t */\n\t\tstatusLabel(status) {\n\t\t\tconst key = STATUS_LABELS[status]\n\t\t\treturn key ? this.tr(key) : String(status || '')\n\t\t},\n\n\t\t/**\n\t\t * The secondary line: status, the step the run sits on, and what\n\t\t * triggered it — whichever of those the run actually carries.\n\t\t *\n\t\t * @param {object} run The run row.\n\t\t * @return {string} The meta line.\n\t\t */\n\t\tmetaLine(run) {\n\t\t\tconst parts = [this.statusLabel(run.status)]\n\t\t\tif (run.step) {\n\t\t\t\tparts.push(String(run.step))\n\t\t\t}\n\t\t\tif (run.trigger) {\n\t\t\t\tparts.push(String(run.trigger))\n\t\t\t}\n\t\t\treturn parts.join(' · ')\n\t\t},\n\n\t\t/**\n\t\t * How long the run has been going, as a coarse relative label.\n\t\t *\n\t\t * Coarse on purpose: a dashboard reader wants \"8m\", not a timestamp,\n\t\t * and a second-precision label on a 15s poll would read as stale.\n\t\t *\n\t\t * @param {object} run The run row.\n\t\t * @return {string} The age label, or '' when the run has no start time.\n\t\t */\n\t\tageLabel(run) {\n\t\t\tconst started = Date.parse(run.created || '')\n\t\t\tif (Number.isFinite(started) === false) {\n\t\t\t\treturn ''\n\t\t\t}\n\t\t\tconst seconds = Math.max(0, Math.round((Date.now() - started) / 1000))\n\t\t\tif (seconds < 60) {\n\t\t\t\treturn this.tr('now')\n\t\t\t}\n\t\t\tconst minutes = Math.round(seconds / 60)\n\t\t\tif (minutes < 60) {\n\t\t\t\treturn `${minutes}m`\n\t\t\t}\n\t\t\tconst hours = Math.round(minutes / 60)\n\t\t\tif (hours < 24) {\n\t\t\t\treturn `${hours}h`\n\t\t\t}\n\t\t\treturn `${Math.round(hours / 24)}d`\n\t\t},\n\n\t\t/**\n\t\t * The route a run row links to, or null when it links nowhere.\n\t\t *\n\t\t * `runRoute` wins when the row carries a run uuid: on a case page a\n\t\t * click means \"show me THIS run\", and the run uuid is the deep link\n\t\t * the row contract carries. Without a `runRoute` (or on a row with no\n\t\t * uuid) `rowRoute` receives the FLOW id, the surface every\n\t\t * flow-authoring app has.\n\t\t *\n\t\t * The flow id alone answers the wrong question, so the run travels as\n\t\t * `?run=`, the address CnFlowDetail documents its `run` prop as\n\t\t * reading: a `rowRoute` click and a pasted run link open the same\n\t\t * screen.\n\t\t *\n\t\t * @param {object} run The run row.\n\t\t * @return {object|null} A vue-router location, or null.\n\t\t */\n\t\trunTarget(run) {\n\t\t\tif (this.isLinked === false || !this.$router) {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\tconst hasUuid = run.uuid !== undefined && run.uuid !== null && run.uuid !== ''\n\t\t\tconst runRoute = this.content.runRoute\n\t\t\tif (typeof runRoute === 'string' && runRoute !== '' && hasUuid) {\n\t\t\t\treturn { name: runRoute, params: { id: String(run.uuid) } }\n\t\t\t}\n\t\t\tif (typeof this.content.rowRoute !== 'string' || this.content.rowRoute === '') {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\tconst id = run.flowId\n\t\t\tif (id === undefined || id === null || id === '') {\n\t\t\t\treturn null\n\t\t\t}\n\t\t\tconst target = { name: this.content.rowRoute, params: { id: String(id) } }\n\t\t\t// An empty `?run=` is not a deep link; CnFlowDetail's `run` prop\n\t\t\t// defaults to '' so \"no run named\" stays one value.\n\t\t\tif (hasUuid) {\n\t\t\t\ttarget.query = { run: String(run.uuid) }\n\t\t\t}\n\t\t\treturn target\n\t\t},\n\n\t\t/**\n\t\t * The href of a run row's link, or '' when the row links nowhere.\n\t\t *\n\t\t * @param {object} run The run row.\n\t\t * @return {string} The resolved href.\n\t\t */\n\t\trunHref(run) {\n\t\t\treturn resolveHref(this.runTarget(run), this.$router)\n\t\t},\n\n\t\t/**\n\t\t * Route a plain click on a run row's link in-app; modified clicks\n\t\t * are left to the browser.\n\t\t *\n\t\t * @param {MouseEvent} event The click event.\n\t\t * @param {object} run The clicked run row.\n\t\t * @return {void}\n\t\t */\n\t\tonRowClick(event, run) {\n\t\t\tfollowLinkClick(event, this.runTarget(run), this.$router)\n\t\t},\n\t},\n}\n\n/**\n * Clamp a configured row limit into the range the endpoint accepts.\n *\n * @param {number|string|undefined} raw The configured limit.\n * @return {number} A limit between 1 and 50 (default 6).\n */\nfunction normaliseLimit(raw) {\n\tconst n = Number(raw)\n\tif (Number.isFinite(n) === false || n <= 0) {\n\t\treturn 6\n\t}\n\treturn Math.min(50, Math.round(n))\n}\n</script>\n\n<style scoped>\n.cn-flow-runs-widget {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: 2px;\n\tmin-width: 0;\n}\n\n.cn-flow-runs-widget__loading {\n\tdisplay: flex;\n\tjustify-content: center;\n\tpadding: 8px 0;\n}\n\n.cn-flow-runs-widget__empty,\n.cn-flow-runs-widget__error,\n.cn-flow-runs-widget__more {\n\tmargin: 0;\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 13px;\n}\n\n.cn-flow-runs-widget__more {\n\tpadding-top: 4px;\n}\n\n.cn-flow-runs-widget__list {\n\tlist-style: none;\n\tmargin: 0;\n\tpadding: 0;\n\tmin-width: 0;\n}\n\n.cn-flow-runs-widget__row {\n\tborder-bottom: 1px solid var(--color-border);\n\tmin-width: 0;\n}\n\n.cn-flow-runs-widget__row:last-child {\n\tborder-bottom: none;\n}\n\n.cn-flow-runs-widget__row-content {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: 10px;\n\tpadding: 6px 0;\n\tmin-width: 0;\n\tcolor: inherit;\n\ttext-decoration: none;\n}\n\na.cn-flow-runs-widget__row-content {\n\tcursor: pointer;\n}\n\n.cn-flow-runs-widget__dot {\n\tflex: 0 0 auto;\n\twidth: 8px;\n\theight: 8px;\n\tborder-radius: 50%;\n\tbackground: var(--color-text-maxcontrast);\n}\n\n/* Executing now — the primary colour, and the only row that animates. */\n.cn-flow-runs-widget__dot--running {\n\tbackground: var(--color-primary-element, #0082c9);\n\tanimation: cn-flow-runs-pulse 1.6s ease-in-out infinite;\n}\n\n.cn-flow-runs-widget__dot--queued {\n\tbackground: var(--color-warning, #c28900);\n}\n\n.cn-flow-runs-widget__dot--suspended {\n\tbackground: var(--color-text-maxcontrast);\n}\n\n/* Terminal rows: the dot is a hollow RING, so live (filled) and finished\n (hollow) never differ by colour alone. The per-status colour still rides\n on the ring's border. */\n.cn-flow-runs-widget__dot--terminal {\n\twidth: 6px;\n\theight: 6px;\n\tbackground: transparent;\n\tborder: 2px solid var(--color-text-maxcontrast);\n}\n\n.cn-flow-runs-widget__dot--terminal.cn-flow-runs-widget__dot--completed {\n\tborder-color: var(--color-success, #2d7b41);\n}\n\n.cn-flow-runs-widget__dot--terminal.cn-flow-runs-widget__dot--failed,\n.cn-flow-runs-widget__dot--terminal.cn-flow-runs-widget__dot--dead_letter {\n\tborder-color: var(--color-error, #d91f2d);\n}\n\n.cn-flow-runs-widget__body {\n\tdisplay: flex;\n\tflex-direction: column;\n\tmin-width: 0;\n\tflex: 1 1 auto;\n}\n\n.cn-flow-runs-widget__name {\n\tfont-weight: 600;\n\twhite-space: nowrap;\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n}\n\n/* Finished runs read as history: regular weight, muted colour. The hollow\n dot and the section label carry the distinction for anyone who cannot\n rely on colour. */\n.cn-flow-runs-widget__row--terminal .cn-flow-runs-widget__name {\n\tfont-weight: 400;\n\tcolor: var(--color-text-maxcontrast);\n}\n\n/* Must sit AFTER the plain `__name` rule: the compound selector is more\n specific, and stylelint's no-descending-specificity flags the reverse\n order because the cascade then depends on specificity rather than\n source order — the shape that quietly stops working when someone adds\n a competing rule. */\na.cn-flow-runs-widget__row-content:hover .cn-flow-runs-widget__name {\n\ttext-decoration: underline;\n}\n\n.cn-flow-runs-widget__meta {\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 12px;\n\twhite-space: nowrap;\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n}\n\n.cn-flow-runs-widget__age {\n\tflex: 0 0 auto;\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 12px;\n\tfont-variant-numeric: tabular-nums;\n}\n\n/* The history section label. Small caps text, not a colour: the section\n boundary must survive monochrome. */\n.cn-flow-runs-widget__history-title {\n\tmargin: 0;\n\tpadding-top: 8px;\n\tcolor: var(--color-text-maxcontrast);\n\tfont-size: 12px;\n\tfont-weight: 600;\n\ttext-transform: uppercase;\n\tletter-spacing: 0.04em;\n}\n\n@keyframes cn-flow-runs-pulse {\n\t0%, 100% { opacity: 1; }\n\t50% { opacity: 0.35; }\n}\n\n/* Respect a reduced-motion preference: the pulse is decoration, and the dot\n colour already carries the status. */\n@media (prefers-reduced-motion: reduce) {\n\t.cn-flow-runs-widget__dot--running {\n\t\tanimation: none;\n\t}\n}\n</style>\n"],"names":["_openBlock","_createElementBlock","_toDisplayString","_createVNode","_Fragment","_renderList","_normalizeClass","_createBlock","_resolveDynamicComponent","_createElementVNode"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CnFlowsPage.vue2.js","sources":["../../../../src/components/CnFlowsPage/CnFlowsPage.vue"],"sourcesContent":["<!-- SPDX-License-Identifier: EUPL-1.2 -->\n<!-- Copyright (C) 2026 Conduction B.V. -->\n<template>\n\t<CnIndexPage\n\t\t:title=\"resolvedTitle\"\n\t\t:description=\"resolvedDescription\"\n\t\t:columns=\"columns\"\n\t\t:objects=\"rows\"\n\t\t:loading=\"store.loading\"\n\t\t:selectable=\"false\"\n\t\t:showAdd=\"false\"\n\t\t:showViewAction=\"false\"\n\t\t:showEditAction=\"false\"\n\t\t:actions=\"rowActions\"\n\t\trowClickToView\n\t\t@rowClick=\"openFlow\">\n\t\t<template #header-actions>\n\t\t\t<NcButton variant=\"primary\" :to=\"newFlowRoute\">\n\t\t\t\t<template #icon>\n\t\t\t\t\t<Plus :size=\"20\" />\n\t\t\t\t</template>\n\t\t\t\t{{ t(\"nextcloud-vue\", \"New flow\") }}\n\t\t\t</NcButton>\n\t\t</template>\n\t</CnIndexPage>\n</template>\n\n<script>\nimport { translate as t } from '@nextcloud/l10n'\nimport { NcButton } from '@nextcloud/vue'\nimport Pencil from 'vue-material-design-icons/Pencil.vue'\nimport Plus from 'vue-material-design-icons/Plus.vue'\nimport CnIndexPage from '../CnIndexPage/CnIndexPage.vue'\nimport { useFlowStore } from '../../composables/useFlowStore.js'\nimport { openRowTarget } from '../../utils/linkNavigation.js'\nimport { isNewTabClick } from '../../utils/rowAuxClick.js'\n\n/**\n * The app-scoped flow list — manifest page type `flows`.\n *\n * Every app authors its own flows (ADR-110 Decision 4): a dossiq flow operates\n * on cases, a shillinq flow on budget lines, so the authoring surface belongs\n * in the app whose objects it drives rather than behind a deep link to another\n * app's list. The ENGINE stays single (ADR-065) — this is a scoped view onto\n * OpenRegister's one native flow store, not a per-app store.\n *\n * Built on `CnIndexPage` per ADR-096, and NOT on the deprecated\n * `CnFlowIndexPage`: a flow list is an ordinary index surface. The source is\n * external (`:objects` from `useFlowStore`) because a flow is not an\n * OpenRegister object, so there is no register/schema pair for a `type: \"index\"`\n * page to bind to — which is exactly why this needs its own page type instead\n * of being expressible in the manifest as an index.\n *\n * This component exists so an app declares two manifest pages and no component\n * files. The three apps that adopted flows before it each copied ~270 lines of\n * identical wrapper differing only in an app-id string; a fix to any of it had\n * to be applied once per app.\n *\n * Manifest declaration:\n *\n * { \"id\": \"Flows\", \"route\": \"/flows\", \"type\": \"flows\", \"title\": \"Flows\",\n * \"config\": { \"app\": \"dossiq\" } }\n */\nexport default {\n\tname: 'CnFlowsPage',\n\n\tcomponents: {\n\t\tCnIndexPage,\n\t\tNcButton,\n\t\t// Pencil is deliberately NOT registered: it is passed as an icon\n\t\t// COMPONENT in `rowActions`, never used as a tag in this template.\n\t\tPlus,\n\t},\n\n\tprops: {\n\t\t/**\n\t\t * The owning app id this list is scoped to. Omit to list every app's\n\t\t * flows — the fleet-wide view OpenRegister uses; a leaf app should\n\t\t * always set it, or it shows other apps' automations as its own.\n\t\t */\n\t\tapp: {\n\t\t\ttype: String,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/**\n\t\t * Page heading. Defaults to a translated \"Flows\".\n\t\t */\n\t\ttitle: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * Page description. Defaults to a translated one-line explanation of\n\t\t * what a flow is.\n\t\t */\n\t\tdescription: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * Route path the list navigates to for a flow, with `/<id>` appended.\n\t\t * Change it only if the detail page is mounted somewhere other than\n\t\t * the conventional `/flows/:id`.\n\t\t */\n\t\tdetailRoute: {\n\t\t\ttype: String,\n\t\t\tdefault: '/flows',\n\t\t},\n\t},\n\n\t/**\n\t * Share the one flow store with the editor page.\n\t *\n\t * @return {object} The setup bindings.\n\t */\n\tsetup() {\n\t\treturn { store: useFlowStore() }\n\t},\n\n\tcomputed: {\n\t\t/**\n\t\t * @return {string} The heading.\n\t\t */\n\t\tresolvedTitle() {\n\t\t\treturn this.title || t('nextcloud-vue', 'Flows')\n\t\t},\n\n\t\t/**\n\t\t * @return {string} The description.\n\t\t */\n\t\tresolvedDescription() {\n\t\t\treturn this.description || t('nextcloud-vue', 'A flow runs a series of steps when something happens — an object changes, a schedule fires, or you run it yourself.')\n\t\t},\n\n\t\t/**\n\t\t * @return {string} Where \"New flow\" links to.\n\t\t */\n\t\tnewFlowRoute() {\n\t\t\treturn `${this.detailRoute}/new`\n\t\t},\n\n\t\t/**\n\t\t * @return {Array<object>} The row-action menu: Edit, and only Edit.\n\t\t */\n\t\trowActions() {\n\t\t\treturn [\n\t\t\t\t{\n\t\t\t\t\tlabel: t('nextcloud-vue', 'Edit'),\n\t\t\t\t\ticon: Pencil,\n\t\t\t\t\thandler: (row) => this.openFlow(row),\n\t\t\t\t},\n\t\t\t]\n\t\t},\n\n\t\t/**\n\t\t * @return {Array<object>} The column definitions.\n\t\t */\n\t\tcolumns() {\n\t\t\treturn [\n\t\t\t\t{ key: 'name', label: t('nextcloud-vue', 'Name') },\n\t\t\t\t{ key: 'description', label: t('nextcloud-vue', 'Description') },\n\t\t\t\t{ key: 'trigger', label: t('nextcloud-vue', 'Trigger') },\n\t\t\t\t{ key: 'cron', label: t('nextcloud-vue', 'Schedule') },\n\t\t\t\t{ key: 'statusLabel', label: t('nextcloud-vue', 'Status') },\n\t\t\t]\n\t\t},\n\n\t\t/**\n\t\t * The flows with the status rendered for display.\n\t\t *\n\t\t * @return {Array<object>} The rows.\n\t\t */\n\t\trows() {\n\t\t\treturn (this.store.flows || []).map((flow) => ({\n\t\t\t\t...flow,\n\t\t\t\tstatusLabel: this.statusLabel(flow),\n\t\t\t}))\n\t\t},\n\t},\n\n\tasync mounted() {\n\t\tawait this.store.load({ app: this.app })\n\t},\n\n\tmethods: {\n\t\tt,\n\n\t\t/**\n\t\t * Enabled and dispatchable are NOT the same thing: a trigger fires with\n\t\t * no acting user, so a flow with no owner has no identity to run as and\n\t\t * will not start however enabled it looks. Saying so in the list is the\n\t\t * only place a user finds out before waiting for a run that never comes.\n\t\t *\n\t\t * @param {object} flow The flow.\n\t\t * @return {string} The label.\n\t\t */\n\t\tstatusLabel(flow) {\n\t\t\tif (!flow.enabled) {\n\t\t\t\treturn t('nextcloud-vue', 'Disabled')\n\t\t\t}\n\t\t\tif (!flow.owner) {\n\t\t\t\treturn t(\n\t\t\t\t\t'nextcloud-vue',\n\t\t\t\t\t'Enabled, but has no owner — it will not start',\n\t\t\t\t)\n\t\t\t}\n\n\t\t\treturn t('nextcloud-vue', 'Enabled')\n\t\t},\n\n\t\t/**\n\t\t * Open a flow; a ctrl/cmd/shift or middle click opens it in a new tab.\n\t\t *\n\t\t * @param {object} flow The activated flow.\n\t\t * @param {MouseEvent} [event] The originating click/auxclick event.\n\t\t * @return {void}\n\t\t */\n\t\topenFlow(flow, event) {\n\t\t\tconst id = flow?.id || flow?.uuid\n\t\t\tif (!id) {\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tconst path = `${this.detailRoute}/${id}`\n\t\t\tif (isNewTabClick(event)) {\n\t\t\t\topenRowTarget(event, { path }, this.$router)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.$router.push(path)\n\t\t},\n\t},\n}\n</script>\n"],"names":["CnIndexPage","t"],"mappings":";;;;;;;;;
|
|
1
|
+
{"version":3,"file":"CnFlowsPage.vue2.js","sources":["../../../../src/components/CnFlowsPage/CnFlowsPage.vue"],"sourcesContent":["<!-- SPDX-License-Identifier: EUPL-1.2 -->\n<!-- Copyright (C) 2026 Conduction B.V. -->\n<template>\n\t<CnIndexPage\n\t\t:title=\"resolvedTitle\"\n\t\t:description=\"resolvedDescription\"\n\t\t:columns=\"columns\"\n\t\t:objects=\"rows\"\n\t\t:loading=\"store.loading\"\n\t\t:selectable=\"false\"\n\t\t:showAdd=\"false\"\n\t\t:showViewAction=\"false\"\n\t\t:showEditAction=\"false\"\n\t\t:actions=\"rowActions\"\n\t\trowClickToView\n\t\t@rowClick=\"openFlow\"\n\t\t@rowAuxClick=\"openFlow\">\n\t\t<template #header-actions>\n\t\t\t<NcButton variant=\"primary\" :to=\"newFlowRoute\">\n\t\t\t\t<template #icon>\n\t\t\t\t\t<Plus :size=\"20\" />\n\t\t\t\t</template>\n\t\t\t\t{{ t(\"nextcloud-vue\", \"New flow\") }}\n\t\t\t</NcButton>\n\t\t</template>\n\t</CnIndexPage>\n</template>\n\n<script>\nimport { translate as t } from '@nextcloud/l10n'\nimport { NcButton } from '@nextcloud/vue'\nimport Pencil from 'vue-material-design-icons/Pencil.vue'\nimport Plus from 'vue-material-design-icons/Plus.vue'\nimport CnIndexPage from '../CnIndexPage/CnIndexPage.vue'\nimport { useFlowStore } from '../../composables/useFlowStore.js'\nimport { openRowTarget } from '../../utils/linkNavigation.js'\nimport { isNewTabClick } from '../../utils/rowAuxClick.js'\n\n/**\n * The app-scoped flow list — manifest page type `flows`.\n *\n * Every app authors its own flows (ADR-110 Decision 4): a dossiq flow operates\n * on cases, a shillinq flow on budget lines, so the authoring surface belongs\n * in the app whose objects it drives rather than behind a deep link to another\n * app's list. The ENGINE stays single (ADR-065) — this is a scoped view onto\n * OpenRegister's one native flow store, not a per-app store.\n *\n * Built on `CnIndexPage` per ADR-096, and NOT on the deprecated\n * `CnFlowIndexPage`: a flow list is an ordinary index surface. The source is\n * external (`:objects` from `useFlowStore`) because a flow is not an\n * OpenRegister object, so there is no register/schema pair for a `type: \"index\"`\n * page to bind to — which is exactly why this needs its own page type instead\n * of being expressible in the manifest as an index.\n *\n * This component exists so an app declares two manifest pages and no component\n * files. The three apps that adopted flows before it each copied ~270 lines of\n * identical wrapper differing only in an app-id string; a fix to any of it had\n * to be applied once per app.\n *\n * Manifest declaration:\n *\n * { \"id\": \"Flows\", \"route\": \"/flows\", \"type\": \"flows\", \"title\": \"Flows\",\n * \"config\": { \"app\": \"dossiq\" } }\n */\nexport default {\n\tname: 'CnFlowsPage',\n\n\tcomponents: {\n\t\tCnIndexPage,\n\t\tNcButton,\n\t\t// Pencil is deliberately NOT registered: it is passed as an icon\n\t\t// COMPONENT in `rowActions`, never used as a tag in this template.\n\t\tPlus,\n\t},\n\n\tprops: {\n\t\t/**\n\t\t * The owning app id this list is scoped to. Omit to list every app's\n\t\t * flows — the fleet-wide view OpenRegister uses; a leaf app should\n\t\t * always set it, or it shows other apps' automations as its own.\n\t\t */\n\t\tapp: {\n\t\t\ttype: String,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/**\n\t\t * Page heading. Defaults to a translated \"Flows\".\n\t\t */\n\t\ttitle: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * Page description. Defaults to a translated one-line explanation of\n\t\t * what a flow is.\n\t\t */\n\t\tdescription: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * Route path the list navigates to for a flow, with `/<id>` appended.\n\t\t * Change it only if the detail page is mounted somewhere other than\n\t\t * the conventional `/flows/:id`.\n\t\t */\n\t\tdetailRoute: {\n\t\t\ttype: String,\n\t\t\tdefault: '/flows',\n\t\t},\n\t},\n\n\t/**\n\t * Share the one flow store with the editor page.\n\t *\n\t * @return {object} The setup bindings.\n\t */\n\tsetup() {\n\t\treturn { store: useFlowStore() }\n\t},\n\n\tcomputed: {\n\t\t/**\n\t\t * @return {string} The heading.\n\t\t */\n\t\tresolvedTitle() {\n\t\t\treturn this.title || t('nextcloud-vue', 'Flows')\n\t\t},\n\n\t\t/**\n\t\t * @return {string} The description.\n\t\t */\n\t\tresolvedDescription() {\n\t\t\treturn this.description || t('nextcloud-vue', 'A flow runs a series of steps when something happens — an object changes, a schedule fires, or you run it yourself.')\n\t\t},\n\n\t\t/**\n\t\t * @return {string} Where \"New flow\" links to.\n\t\t */\n\t\tnewFlowRoute() {\n\t\t\treturn `${this.detailRoute}/new`\n\t\t},\n\n\t\t/**\n\t\t * @return {Array<object>} The row-action menu: Edit, and only Edit.\n\t\t */\n\t\trowActions() {\n\t\t\treturn [\n\t\t\t\t{\n\t\t\t\t\tlabel: t('nextcloud-vue', 'Edit'),\n\t\t\t\t\ticon: Pencil,\n\t\t\t\t\thandler: (row) => this.openFlow(row),\n\t\t\t\t},\n\t\t\t]\n\t\t},\n\n\t\t/**\n\t\t * @return {Array<object>} The column definitions.\n\t\t */\n\t\tcolumns() {\n\t\t\treturn [\n\t\t\t\t{ key: 'name', label: t('nextcloud-vue', 'Name') },\n\t\t\t\t{ key: 'description', label: t('nextcloud-vue', 'Description') },\n\t\t\t\t{ key: 'trigger', label: t('nextcloud-vue', 'Trigger') },\n\t\t\t\t{ key: 'cron', label: t('nextcloud-vue', 'Schedule') },\n\t\t\t\t{ key: 'statusLabel', label: t('nextcloud-vue', 'Status') },\n\t\t\t]\n\t\t},\n\n\t\t/**\n\t\t * The flows with the status rendered for display.\n\t\t *\n\t\t * @return {Array<object>} The rows.\n\t\t */\n\t\trows() {\n\t\t\treturn (this.store.flows || []).map((flow) => ({\n\t\t\t\t...flow,\n\t\t\t\tstatusLabel: this.statusLabel(flow),\n\t\t\t}))\n\t\t},\n\t},\n\n\tasync mounted() {\n\t\tawait this.store.load({ app: this.app })\n\t},\n\n\tmethods: {\n\t\tt,\n\n\t\t/**\n\t\t * Enabled and dispatchable are NOT the same thing: a trigger fires with\n\t\t * no acting user, so a flow with no owner has no identity to run as and\n\t\t * will not start however enabled it looks. Saying so in the list is the\n\t\t * only place a user finds out before waiting for a run that never comes.\n\t\t *\n\t\t * @param {object} flow The flow.\n\t\t * @return {string} The label.\n\t\t */\n\t\tstatusLabel(flow) {\n\t\t\tif (!flow.enabled) {\n\t\t\t\treturn t('nextcloud-vue', 'Disabled')\n\t\t\t}\n\t\t\tif (!flow.owner) {\n\t\t\t\treturn t(\n\t\t\t\t\t'nextcloud-vue',\n\t\t\t\t\t'Enabled, but has no owner — it will not start',\n\t\t\t\t)\n\t\t\t}\n\n\t\t\treturn t('nextcloud-vue', 'Enabled')\n\t\t},\n\n\t\t/**\n\t\t * Open a flow; a ctrl/cmd/shift or middle click opens it in a new tab.\n\t\t *\n\t\t * @param {object} flow The activated flow.\n\t\t * @param {MouseEvent} [event] The originating click/auxclick event.\n\t\t * @return {void}\n\t\t */\n\t\topenFlow(flow, event) {\n\t\t\tconst id = flow?.id || flow?.uuid\n\t\t\tif (!id) {\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tconst path = `${this.detailRoute}/${id}`\n\t\t\tif (isNewTabClick(event)) {\n\t\t\t\topenRowTarget(event, { path }, this.$router)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.$router.push(path)\n\t\t},\n\t},\n}\n</script>\n"],"names":["CnIndexPage","t"],"mappings":"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"}
|
|
@@ -17,7 +17,8 @@ function render(_ctx, _cache, $props, $setup, $data, $options) {
|
|
|
17
17
|
showEditAction: false,
|
|
18
18
|
actions: $options.rowActions,
|
|
19
19
|
rowClickToView: "",
|
|
20
|
-
onRowClick: $options.openFlow
|
|
20
|
+
onRowClick: $options.openFlow,
|
|
21
|
+
onRowAuxClick: $options.openFlow
|
|
21
22
|
}, {
|
|
22
23
|
"header-actions": withCtx(() => [
|
|
23
24
|
createVNode(_component_NcButton, {
|
|
@@ -34,7 +35,7 @@ function render(_ctx, _cache, $props, $setup, $data, $options) {
|
|
|
34
35
|
}, 8 /* PROPS */, ["to"])
|
|
35
36
|
]),
|
|
36
37
|
_: 1 /* STABLE */
|
|
37
|
-
}, 8 /* PROPS */, ["title", "description", "columns", "objects", "loading", "actions", "onRowClick"]))
|
|
38
|
+
}, 8 /* PROPS */, ["title", "description", "columns", "objects", "loading", "actions", "onRowClick", "onRowAuxClick"]))
|
|
38
39
|
}
|
|
39
40
|
|
|
40
41
|
export { render };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CnFlowsPage.vue3.js","sources":["../../../../src/components/CnFlowsPage/CnFlowsPage.vue?vue&type=template&id=673a9163&lang.js"],"sourcesContent":["<!-- SPDX-License-Identifier: EUPL-1.2 -->\n<!-- Copyright (C) 2026 Conduction B.V. -->\n<template>\n\t<CnIndexPage\n\t\t:title=\"resolvedTitle\"\n\t\t:description=\"resolvedDescription\"\n\t\t:columns=\"columns\"\n\t\t:objects=\"rows\"\n\t\t:loading=\"store.loading\"\n\t\t:selectable=\"false\"\n\t\t:showAdd=\"false\"\n\t\t:showViewAction=\"false\"\n\t\t:showEditAction=\"false\"\n\t\t:actions=\"rowActions\"\n\t\trowClickToView\n\t\t@rowClick=\"openFlow\">\n\t\t<template #header-actions>\n\t\t\t<NcButton variant=\"primary\" :to=\"newFlowRoute\">\n\t\t\t\t<template #icon>\n\t\t\t\t\t<Plus :size=\"20\" />\n\t\t\t\t</template>\n\t\t\t\t{{ t(\"nextcloud-vue\", \"New flow\") }}\n\t\t\t</NcButton>\n\t\t</template>\n\t</CnIndexPage>\n</template>\n\n<script>\nimport { translate as t } from '@nextcloud/l10n'\nimport { NcButton } from '@nextcloud/vue'\nimport Pencil from 'vue-material-design-icons/Pencil.vue'\nimport Plus from 'vue-material-design-icons/Plus.vue'\nimport CnIndexPage from '../CnIndexPage/CnIndexPage.vue'\nimport { useFlowStore } from '../../composables/useFlowStore.js'\nimport { openRowTarget } from '../../utils/linkNavigation.js'\nimport { isNewTabClick } from '../../utils/rowAuxClick.js'\n\n/**\n * The app-scoped flow list — manifest page type `flows`.\n *\n * Every app authors its own flows (ADR-110 Decision 4): a dossiq flow operates\n * on cases, a shillinq flow on budget lines, so the authoring surface belongs\n * in the app whose objects it drives rather than behind a deep link to another\n * app's list. The ENGINE stays single (ADR-065) — this is a scoped view onto\n * OpenRegister's one native flow store, not a per-app store.\n *\n * Built on `CnIndexPage` per ADR-096, and NOT on the deprecated\n * `CnFlowIndexPage`: a flow list is an ordinary index surface. The source is\n * external (`:objects` from `useFlowStore`) because a flow is not an\n * OpenRegister object, so there is no register/schema pair for a `type: \"index\"`\n * page to bind to — which is exactly why this needs its own page type instead\n * of being expressible in the manifest as an index.\n *\n * This component exists so an app declares two manifest pages and no component\n * files. The three apps that adopted flows before it each copied ~270 lines of\n * identical wrapper differing only in an app-id string; a fix to any of it had\n * to be applied once per app.\n *\n * Manifest declaration:\n *\n * { \"id\": \"Flows\", \"route\": \"/flows\", \"type\": \"flows\", \"title\": \"Flows\",\n * \"config\": { \"app\": \"dossiq\" } }\n */\nexport default {\n\tname: 'CnFlowsPage',\n\n\tcomponents: {\n\t\tCnIndexPage,\n\t\tNcButton,\n\t\t// Pencil is deliberately NOT registered: it is passed as an icon\n\t\t// COMPONENT in `rowActions`, never used as a tag in this template.\n\t\tPlus,\n\t},\n\n\tprops: {\n\t\t/**\n\t\t * The owning app id this list is scoped to. Omit to list every app's\n\t\t * flows — the fleet-wide view OpenRegister uses; a leaf app should\n\t\t * always set it, or it shows other apps' automations as its own.\n\t\t */\n\t\tapp: {\n\t\t\ttype: String,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/**\n\t\t * Page heading. Defaults to a translated \"Flows\".\n\t\t */\n\t\ttitle: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * Page description. Defaults to a translated one-line explanation of\n\t\t * what a flow is.\n\t\t */\n\t\tdescription: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * Route path the list navigates to for a flow, with `/<id>` appended.\n\t\t * Change it only if the detail page is mounted somewhere other than\n\t\t * the conventional `/flows/:id`.\n\t\t */\n\t\tdetailRoute: {\n\t\t\ttype: String,\n\t\t\tdefault: '/flows',\n\t\t},\n\t},\n\n\t/**\n\t * Share the one flow store with the editor page.\n\t *\n\t * @return {object} The setup bindings.\n\t */\n\tsetup() {\n\t\treturn { store: useFlowStore() }\n\t},\n\n\tcomputed: {\n\t\t/**\n\t\t * @return {string} The heading.\n\t\t */\n\t\tresolvedTitle() {\n\t\t\treturn this.title || t('nextcloud-vue', 'Flows')\n\t\t},\n\n\t\t/**\n\t\t * @return {string} The description.\n\t\t */\n\t\tresolvedDescription() {\n\t\t\treturn this.description || t('nextcloud-vue', 'A flow runs a series of steps when something happens — an object changes, a schedule fires, or you run it yourself.')\n\t\t},\n\n\t\t/**\n\t\t * @return {string} Where \"New flow\" links to.\n\t\t */\n\t\tnewFlowRoute() {\n\t\t\treturn `${this.detailRoute}/new`\n\t\t},\n\n\t\t/**\n\t\t * @return {Array<object>} The row-action menu: Edit, and only Edit.\n\t\t */\n\t\trowActions() {\n\t\t\treturn [\n\t\t\t\t{\n\t\t\t\t\tlabel: t('nextcloud-vue', 'Edit'),\n\t\t\t\t\ticon: Pencil,\n\t\t\t\t\thandler: (row) => this.openFlow(row),\n\t\t\t\t},\n\t\t\t]\n\t\t},\n\n\t\t/**\n\t\t * @return {Array<object>} The column definitions.\n\t\t */\n\t\tcolumns() {\n\t\t\treturn [\n\t\t\t\t{ key: 'name', label: t('nextcloud-vue', 'Name') },\n\t\t\t\t{ key: 'description', label: t('nextcloud-vue', 'Description') },\n\t\t\t\t{ key: 'trigger', label: t('nextcloud-vue', 'Trigger') },\n\t\t\t\t{ key: 'cron', label: t('nextcloud-vue', 'Schedule') },\n\t\t\t\t{ key: 'statusLabel', label: t('nextcloud-vue', 'Status') },\n\t\t\t]\n\t\t},\n\n\t\t/**\n\t\t * The flows with the status rendered for display.\n\t\t *\n\t\t * @return {Array<object>} The rows.\n\t\t */\n\t\trows() {\n\t\t\treturn (this.store.flows || []).map((flow) => ({\n\t\t\t\t...flow,\n\t\t\t\tstatusLabel: this.statusLabel(flow),\n\t\t\t}))\n\t\t},\n\t},\n\n\tasync mounted() {\n\t\tawait this.store.load({ app: this.app })\n\t},\n\n\tmethods: {\n\t\tt,\n\n\t\t/**\n\t\t * Enabled and dispatchable are NOT the same thing: a trigger fires with\n\t\t * no acting user, so a flow with no owner has no identity to run as and\n\t\t * will not start however enabled it looks. Saying so in the list is the\n\t\t * only place a user finds out before waiting for a run that never comes.\n\t\t *\n\t\t * @param {object} flow The flow.\n\t\t * @return {string} The label.\n\t\t */\n\t\tstatusLabel(flow) {\n\t\t\tif (!flow.enabled) {\n\t\t\t\treturn t('nextcloud-vue', 'Disabled')\n\t\t\t}\n\t\t\tif (!flow.owner) {\n\t\t\t\treturn t(\n\t\t\t\t\t'nextcloud-vue',\n\t\t\t\t\t'Enabled, but has no owner — it will not start',\n\t\t\t\t)\n\t\t\t}\n\n\t\t\treturn t('nextcloud-vue', 'Enabled')\n\t\t},\n\n\t\t/**\n\t\t * Open a flow; a ctrl/cmd/shift or middle click opens it in a new tab.\n\t\t *\n\t\t * @param {object} flow The activated flow.\n\t\t * @param {MouseEvent} [event] The originating click/auxclick event.\n\t\t * @return {void}\n\t\t */\n\t\topenFlow(flow, event) {\n\t\t\tconst id = flow?.id || flow?.uuid\n\t\t\tif (!id) {\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tconst path = `${this.detailRoute}/${id}`\n\t\t\tif (isNewTabClick(event)) {\n\t\t\t\topenRowTarget(event, { path }, this.$router)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.$router.push(path)\n\t\t},\n\t},\n}\n</script>\n"],"names":["_createBlock","_createVNode","_createTextVNode"],"mappings":";;;;;;;uBAKEA;AACC,IAAA,KAAK,EAAE,QAAS,CAAA,aAAA;AAChB,IAAA,WAAW,EAAE,QAAA,CAAA,mBAAA;AACb,IAAA,OAAO,EAAE,QAAO,CAAA,OAAA;AAChB,IAAA,OAAO,EAAE,QAAI,CAAA,IAAA;IACb,OAAO,EAAE,MAAK,CAAA,KAAA,CAAA,OAAA;AACd,IAAA,UAAU,EAAE,KAAK;AACjB,IAAA,OAAO,EAAE,KAAK;AACd,IAAA,cAAc,EAAE,KAAG;AACnB,IAAA,cAAY,EAAA,KAAA;AACZ,IAAA,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"CnFlowsPage.vue3.js","sources":["../../../../src/components/CnFlowsPage/CnFlowsPage.vue?vue&type=template&id=673a9163&lang.js"],"sourcesContent":["<!-- SPDX-License-Identifier: EUPL-1.2 -->\n<!-- Copyright (C) 2026 Conduction B.V. -->\n<template>\n\t<CnIndexPage\n\t\t:title=\"resolvedTitle\"\n\t\t:description=\"resolvedDescription\"\n\t\t:columns=\"columns\"\n\t\t:objects=\"rows\"\n\t\t:loading=\"store.loading\"\n\t\t:selectable=\"false\"\n\t\t:showAdd=\"false\"\n\t\t:showViewAction=\"false\"\n\t\t:showEditAction=\"false\"\n\t\t:actions=\"rowActions\"\n\t\trowClickToView\n\t\t@rowClick=\"openFlow\"\n\t\t@rowAuxClick=\"openFlow\">\n\t\t<template #header-actions>\n\t\t\t<NcButton variant=\"primary\" :to=\"newFlowRoute\">\n\t\t\t\t<template #icon>\n\t\t\t\t\t<Plus :size=\"20\" />\n\t\t\t\t</template>\n\t\t\t\t{{ t(\"nextcloud-vue\", \"New flow\") }}\n\t\t\t</NcButton>\n\t\t</template>\n\t</CnIndexPage>\n</template>\n\n<script>\nimport { translate as t } from '@nextcloud/l10n'\nimport { NcButton } from '@nextcloud/vue'\nimport Pencil from 'vue-material-design-icons/Pencil.vue'\nimport Plus from 'vue-material-design-icons/Plus.vue'\nimport CnIndexPage from '../CnIndexPage/CnIndexPage.vue'\nimport { useFlowStore } from '../../composables/useFlowStore.js'\nimport { openRowTarget } from '../../utils/linkNavigation.js'\nimport { isNewTabClick } from '../../utils/rowAuxClick.js'\n\n/**\n * The app-scoped flow list — manifest page type `flows`.\n *\n * Every app authors its own flows (ADR-110 Decision 4): a dossiq flow operates\n * on cases, a shillinq flow on budget lines, so the authoring surface belongs\n * in the app whose objects it drives rather than behind a deep link to another\n * app's list. The ENGINE stays single (ADR-065) — this is a scoped view onto\n * OpenRegister's one native flow store, not a per-app store.\n *\n * Built on `CnIndexPage` per ADR-096, and NOT on the deprecated\n * `CnFlowIndexPage`: a flow list is an ordinary index surface. The source is\n * external (`:objects` from `useFlowStore`) because a flow is not an\n * OpenRegister object, so there is no register/schema pair for a `type: \"index\"`\n * page to bind to — which is exactly why this needs its own page type instead\n * of being expressible in the manifest as an index.\n *\n * This component exists so an app declares two manifest pages and no component\n * files. The three apps that adopted flows before it each copied ~270 lines of\n * identical wrapper differing only in an app-id string; a fix to any of it had\n * to be applied once per app.\n *\n * Manifest declaration:\n *\n * { \"id\": \"Flows\", \"route\": \"/flows\", \"type\": \"flows\", \"title\": \"Flows\",\n * \"config\": { \"app\": \"dossiq\" } }\n */\nexport default {\n\tname: 'CnFlowsPage',\n\n\tcomponents: {\n\t\tCnIndexPage,\n\t\tNcButton,\n\t\t// Pencil is deliberately NOT registered: it is passed as an icon\n\t\t// COMPONENT in `rowActions`, never used as a tag in this template.\n\t\tPlus,\n\t},\n\n\tprops: {\n\t\t/**\n\t\t * The owning app id this list is scoped to. Omit to list every app's\n\t\t * flows — the fleet-wide view OpenRegister uses; a leaf app should\n\t\t * always set it, or it shows other apps' automations as its own.\n\t\t */\n\t\tapp: {\n\t\t\ttype: String,\n\t\t\tdefault: null,\n\t\t},\n\n\t\t/**\n\t\t * Page heading. Defaults to a translated \"Flows\".\n\t\t */\n\t\ttitle: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * Page description. Defaults to a translated one-line explanation of\n\t\t * what a flow is.\n\t\t */\n\t\tdescription: {\n\t\t\ttype: String,\n\t\t\tdefault: '',\n\t\t},\n\n\t\t/**\n\t\t * Route path the list navigates to for a flow, with `/<id>` appended.\n\t\t * Change it only if the detail page is mounted somewhere other than\n\t\t * the conventional `/flows/:id`.\n\t\t */\n\t\tdetailRoute: {\n\t\t\ttype: String,\n\t\t\tdefault: '/flows',\n\t\t},\n\t},\n\n\t/**\n\t * Share the one flow store with the editor page.\n\t *\n\t * @return {object} The setup bindings.\n\t */\n\tsetup() {\n\t\treturn { store: useFlowStore() }\n\t},\n\n\tcomputed: {\n\t\t/**\n\t\t * @return {string} The heading.\n\t\t */\n\t\tresolvedTitle() {\n\t\t\treturn this.title || t('nextcloud-vue', 'Flows')\n\t\t},\n\n\t\t/**\n\t\t * @return {string} The description.\n\t\t */\n\t\tresolvedDescription() {\n\t\t\treturn this.description || t('nextcloud-vue', 'A flow runs a series of steps when something happens — an object changes, a schedule fires, or you run it yourself.')\n\t\t},\n\n\t\t/**\n\t\t * @return {string} Where \"New flow\" links to.\n\t\t */\n\t\tnewFlowRoute() {\n\t\t\treturn `${this.detailRoute}/new`\n\t\t},\n\n\t\t/**\n\t\t * @return {Array<object>} The row-action menu: Edit, and only Edit.\n\t\t */\n\t\trowActions() {\n\t\t\treturn [\n\t\t\t\t{\n\t\t\t\t\tlabel: t('nextcloud-vue', 'Edit'),\n\t\t\t\t\ticon: Pencil,\n\t\t\t\t\thandler: (row) => this.openFlow(row),\n\t\t\t\t},\n\t\t\t]\n\t\t},\n\n\t\t/**\n\t\t * @return {Array<object>} The column definitions.\n\t\t */\n\t\tcolumns() {\n\t\t\treturn [\n\t\t\t\t{ key: 'name', label: t('nextcloud-vue', 'Name') },\n\t\t\t\t{ key: 'description', label: t('nextcloud-vue', 'Description') },\n\t\t\t\t{ key: 'trigger', label: t('nextcloud-vue', 'Trigger') },\n\t\t\t\t{ key: 'cron', label: t('nextcloud-vue', 'Schedule') },\n\t\t\t\t{ key: 'statusLabel', label: t('nextcloud-vue', 'Status') },\n\t\t\t]\n\t\t},\n\n\t\t/**\n\t\t * The flows with the status rendered for display.\n\t\t *\n\t\t * @return {Array<object>} The rows.\n\t\t */\n\t\trows() {\n\t\t\treturn (this.store.flows || []).map((flow) => ({\n\t\t\t\t...flow,\n\t\t\t\tstatusLabel: this.statusLabel(flow),\n\t\t\t}))\n\t\t},\n\t},\n\n\tasync mounted() {\n\t\tawait this.store.load({ app: this.app })\n\t},\n\n\tmethods: {\n\t\tt,\n\n\t\t/**\n\t\t * Enabled and dispatchable are NOT the same thing: a trigger fires with\n\t\t * no acting user, so a flow with no owner has no identity to run as and\n\t\t * will not start however enabled it looks. Saying so in the list is the\n\t\t * only place a user finds out before waiting for a run that never comes.\n\t\t *\n\t\t * @param {object} flow The flow.\n\t\t * @return {string} The label.\n\t\t */\n\t\tstatusLabel(flow) {\n\t\t\tif (!flow.enabled) {\n\t\t\t\treturn t('nextcloud-vue', 'Disabled')\n\t\t\t}\n\t\t\tif (!flow.owner) {\n\t\t\t\treturn t(\n\t\t\t\t\t'nextcloud-vue',\n\t\t\t\t\t'Enabled, but has no owner — it will not start',\n\t\t\t\t)\n\t\t\t}\n\n\t\t\treturn t('nextcloud-vue', 'Enabled')\n\t\t},\n\n\t\t/**\n\t\t * Open a flow; a ctrl/cmd/shift or middle click opens it in a new tab.\n\t\t *\n\t\t * @param {object} flow The activated flow.\n\t\t * @param {MouseEvent} [event] The originating click/auxclick event.\n\t\t * @return {void}\n\t\t */\n\t\topenFlow(flow, event) {\n\t\t\tconst id = flow?.id || flow?.uuid\n\t\t\tif (!id) {\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tconst path = `${this.detailRoute}/${id}`\n\t\t\tif (isNewTabClick(event)) {\n\t\t\t\topenRowTarget(event, { path }, this.$router)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tthis.$router.push(path)\n\t\t},\n\t},\n}\n</script>\n"],"names":["_createBlock","_createVNode","_createTextVNode"],"mappings":";;;;;;;uBAKEA;AACC,IAAA,KAAK,EAAE,QAAS,CAAA,aAAA;AAChB,IAAA,WAAW,EAAE,QAAA,CAAA,mBAAA;AACb,IAAA,OAAO,EAAE,QAAO,CAAA,OAAA;AAChB,IAAA,OAAO,EAAE,QAAI,CAAA,IAAA;IACb,OAAO,EAAE,MAAK,CAAA,KAAA,CAAA,OAAA;AACd,IAAA,UAAU,EAAE,KAAK;AACjB,IAAA,OAAO,EAAE,KAAK;AACd,IAAA,cAAc,EAAE,KAAG;AACnB,IAAA,cAAY,EAAA,KAAA;AACZ,IAAA,OAAO,EAAE,QAAS,CAAA,UAAA;AACnB,IAAA,cAAc,EAAd,EAAc;AACb,IAAA,UAAS,EAAC,QAAQ,CAAA,QAAA;AAClB,IAAA,aAAW,EAAE,QAAQ,CAAA,QAAA;;AACZ,IAAA,gBAAO,UACd,MAKS;MALTC,WAKS,CAAA,mBAAA,EAAA;AALD,QAAA,OAAO,WAAG;AAAA,QAAA,EAAA,EAAA,QAAA,CAAA,YAAA;;AACT,QAAA,IAAA,UACT,MAAmB;UAAnBA,WAAmB,CAAA,eAAA,EAAA,EAAZ,IAAI,EAAE,EAAE,EAAA,CAAA;;yBACP,MACT;AADS,UAAAC,eAAA,CAAA,GACT,mBAAG,QAAC,CAAA,CAAA,CAAA,eAAA,EAAA,UAAA,CAAA,CAAA,EAAA,CAAA,YAAA;;;;;;;;;;;"}
|