feat(cabana): read-only Plugins screen with installed plugins

- GET /plugins (backend.view_plugins, superusers always pass) lists the
  plugins from party.Installed, or party.Activated IDs for older builds,
  with the built framework version and whether the registry is configured
- Settings -> System -> Plugins entry with the new SettingsEntry.path,
  added only for principals allowed to open it
- PluginsView at /system/plugins with an Installed tab (reka-ui Tabs);
  settings panel, rail, breadcrumbs and page title know the route
- OpenAPI and SPA types regenerated; contract, token-class and
  conformance inventories list the route; en/pl strings; docs
This commit is contained in:
Jakub Zych
2026-10-29 19:05:46 +01:00
parent 0418900122
commit c4b1ac4076
27 changed files with 998 additions and 12 deletions

View File

@@ -11,7 +11,7 @@ import ShortcutHelpDialog from './ShortcutHelpDialog.vue'
import { ExternalLink } from '@lucide/vue'
import { controllerIdFromPath } from '../../app/controllerRoutes'
import { activeEntry } from '../../state/useNavigation'
import { settingsEntry, settingsLinkFor, settingsSectionOf } from '../../state/useSettings'
import { settingsEntry, settingsEntryForPath, settingsLinkFor, settingsSectionOf } from '../../state/useSettings'
import { useSidebar } from '../../state/useSidebar'
import { t } from '../../app/i18n'
import { closeShortcutHelp, installHotkeys } from '../../app/hotkeys'
@@ -52,6 +52,9 @@ const settingsSection = computed(() => {
if (route.name === 'settings-form') {
return settingsSectionOf(settingsEntry(String(route.params.code ?? '')))
}
if (route.name === 'plugins') {
return settingsSectionOf(settingsEntryForPath(route.path))
}
return settingsSectionOf(settingsLinkFor(controllerIdFromPath(route.path)))
})

View File

@@ -25,6 +25,9 @@ const crumbs = computed<Crumb[]>(() => {
if (route.name === 'account') {
return [{ label: t('backend::lang.myaccount.menu_label') }]
}
if (route.name === 'plugins') {
return [{ label: t('backend::lang.nav.settings'), to: '/settings' }, { label: t('backend::lang.plugins.title') }]
}
if (route.name === 'settings' || route.name === 'settings-form') {
const out: Crumb[] = [{ label: t('backend::lang.nav.settings'), to: '/settings' }]
const entry = route.name === 'settings-form' ? settingsEntry(String(route.params.code ?? '')) : null

View File

@@ -7,7 +7,7 @@ import { controllerIdFromPath } from '../../app/controllerRoutes'
import { iconFor } from '../../app/icons'
import { activeEntry, firstControllerPath, railEntries } from '../../state/useNavigation'
import { cycleTheme, themeChoice } from '../../state/usePreferences'
import { hasSettings, settingsLinkFor } from '../../state/useSettings'
import { hasSettings, settingsEntryForPath, settingsLinkFor } from '../../state/useSettings'
import { useSidebar } from '../../state/useSidebar'
import SectionFlyout from './SectionFlyout.vue'
@@ -45,6 +45,7 @@ const inSettings = computed(
() =>
route.path === '/settings' ||
route.path.startsWith('/settings/') ||
settingsEntryForPath(route.path) !== null ||
(active.value === null && settingsLinkFor(controllerIdFromPath(route.path)) !== null),
)