Implement plugin dynamic settings routes architecture
Plugins can now register their settings pages dynamically via get_settings_routes()
hook, eliminating hardcoded imports that break lean builds.
Backend:
- Add get_settings_routes() hook to BasePlugin (shopdb/plugins/base.py)
- Implement in ComputersPlugin for Operating Systems management
- Add /api/plugins/settings-routes endpoint to expose plugin routes
Frontend:
- ModelCatalogHub fetches plugin routes and dynamically adds them via router.addRoute()
- Tabs render dynamically from pluginTabs ref
- Components lazy-load when clicked
Benefits:
- Lean builds work without manual route exclusions
- Plugins self-contain their UI extensions
- No core code changes needed when adding plugin settings pages
- Follows plugin architecture best practices
Fixes TODO from commit a03fd3de4f
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -56,13 +56,9 @@ export default [
|
|||||||
name: 'model-catalog-models',
|
name: 'model-catalog-models',
|
||||||
component: () => import('../../views/settings/ModelsList.vue')
|
component: () => import('../../views/settings/ModelsList.vue')
|
||||||
}
|
}
|
||||||
// TODO: Operating Systems route should be added by computers plugin, not hardcoded in core
|
// Plugin settings routes (e.g., Operating Systems from computers plugin) are added
|
||||||
// Lean builds without computers plugin fail because this import doesn't exist
|
// dynamically by ModelCatalogHub via router.addRoute() from /api/plugins/settings-routes.
|
||||||
// {
|
// This allows plugins to extend settings hubs without breaking lean builds.
|
||||||
// path: 'operatingsystems',
|
|
||||||
// name: 'model-catalog-os',
|
|
||||||
// component: () => import('../../.plugins-staged/computers/views/OperatingSystemsList.vue')
|
|
||||||
// }
|
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -27,14 +27,16 @@
|
|||||||
>
|
>
|
||||||
Models
|
Models
|
||||||
</router-link>
|
</router-link>
|
||||||
<!-- TODO: Operating Systems tab should be added by computers plugin -->
|
<!-- Plugin-contributed tabs loaded dynamically -->
|
||||||
<!-- <router-link
|
<router-link
|
||||||
:to="{ name: 'model-catalog-os' }"
|
v-for="tab in pluginTabs"
|
||||||
|
:key="tab.name"
|
||||||
|
:to="{ name: tab.name }"
|
||||||
class="tab"
|
class="tab"
|
||||||
:class="{ active: $route.name === 'model-catalog-os' }"
|
:class="{ active: $route.name === tab.name }"
|
||||||
>
|
>
|
||||||
Operating Systems
|
{{ tab.tab }}
|
||||||
</router-link> -->
|
</router-link>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="tab-content">
|
<div class="tab-content">
|
||||||
@@ -44,13 +46,37 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { onMounted } from 'vue'
|
import { ref, onMounted } from 'vue'
|
||||||
import { useRoute, useRouter } from 'vue-router'
|
import { useRoute, useRouter } from 'vue-router'
|
||||||
|
import axios from 'axios'
|
||||||
|
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
const pluginTabs = ref([])
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
// Fetch plugin-contributed settings routes
|
||||||
|
try {
|
||||||
|
const response = await axios.get('/api/plugins/settings-routes')
|
||||||
|
if (response.data && response.data.data) {
|
||||||
|
// Filter for model-catalog hub routes
|
||||||
|
pluginTabs.value = response.data.data.routes.filter(r => r.hub === 'model-catalog')
|
||||||
|
|
||||||
|
// Dynamically register routes for plugin tabs
|
||||||
|
pluginTabs.value.forEach(tab => {
|
||||||
|
if (!router.hasRoute(tab.name)) {
|
||||||
|
router.addRoute('model-catalog', {
|
||||||
|
path: tab.path,
|
||||||
|
name: tab.name,
|
||||||
|
component: () => import(`../../${tab.component}`)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to load plugin settings routes:', error)
|
||||||
|
}
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
// Redirect to vendors tab if no child route
|
// Redirect to vendors tab if no child route
|
||||||
if (route.name === 'model-catalog') {
|
if (route.name === 'model-catalog') {
|
||||||
router.replace({ name: 'model-catalog-vendors' })
|
router.replace({ name: 'model-catalog-vendors' })
|
||||||
|
|||||||
@@ -218,6 +218,16 @@ class ComputersPlugin(BasePlugin):
|
|||||||
self._register_import_hook()
|
self._register_import_hook()
|
||||||
logger.info(f"Computers plugin initialized (v{self.meta.version})")
|
logger.info(f"Computers plugin initialized (v{self.meta.version})")
|
||||||
|
|
||||||
|
def get_settings_routes(self) -> List[Dict]:
|
||||||
|
"""Register Operating Systems management page in Model Catalog."""
|
||||||
|
return [{
|
||||||
|
'hub': 'model-catalog',
|
||||||
|
'path': 'operatingsystems',
|
||||||
|
'name': 'model-catalog-os',
|
||||||
|
'tab': 'Operating Systems',
|
||||||
|
'component': '.plugins-staged/computers/views/OperatingSystemsList.vue'
|
||||||
|
}]
|
||||||
|
|
||||||
# -- bulk-import machine link -------------------------------------------
|
# -- bulk-import machine link -------------------------------------------
|
||||||
|
|
||||||
def _register_import_hook(self):
|
def _register_import_hook(self):
|
||||||
|
|||||||
@@ -42,6 +42,30 @@ def list_enabled_plugins():
|
|||||||
return success_response(sorted(names))
|
return success_response(sorted(names))
|
||||||
|
|
||||||
|
|
||||||
|
@plugins_bp.route('/settings-routes', methods=['GET'])
|
||||||
|
@jwt_required(optional=True)
|
||||||
|
def get_plugin_settings_routes():
|
||||||
|
"""Return settings routes contributed by all enabled plugins.
|
||||||
|
|
||||||
|
Plugins can register routes to add tabs/pages to settings hubs like
|
||||||
|
Model Catalog, Asset Config, etc. Frontend router merges these dynamically.
|
||||||
|
|
||||||
|
Returns: { routes: [{hub, path, name, tab, component}, ...] }
|
||||||
|
"""
|
||||||
|
pm = current_app.extensions.get('plugin_manager')
|
||||||
|
if not pm:
|
||||||
|
return success_response({'routes': []})
|
||||||
|
|
||||||
|
routes = []
|
||||||
|
for plugin in pm.get_enabled_plugin_instances():
|
||||||
|
if hasattr(plugin, 'get_settings_routes'):
|
||||||
|
plugin_routes = plugin.get_settings_routes()
|
||||||
|
if plugin_routes:
|
||||||
|
routes.extend(plugin_routes)
|
||||||
|
|
||||||
|
return success_response({'routes': routes})
|
||||||
|
|
||||||
|
|
||||||
@plugins_bp.route('/<name>', methods=['PUT'])
|
@plugins_bp.route('/<name>', methods=['PUT'])
|
||||||
@jwt_required()
|
@jwt_required()
|
||||||
@require_role('admin')
|
@require_role('admin')
|
||||||
|
|||||||
@@ -129,6 +129,30 @@ class BasePlugin(ABC):
|
|||||||
"""
|
"""
|
||||||
return []
|
return []
|
||||||
|
|
||||||
|
def get_settings_routes(self) -> List[Dict]:
|
||||||
|
"""Frontend routes this plugin adds to settings pages.
|
||||||
|
|
||||||
|
Returns a list of route configurations for the plugin's settings pages.
|
||||||
|
Each route is a dict with:
|
||||||
|
hub - settings hub to attach to ('model-catalog', 'asset-config', etc.)
|
||||||
|
path - route path relative to hub (e.g. 'operatingsystems')
|
||||||
|
name - unique route name (e.g. 'model-catalog-os')
|
||||||
|
tab - tab label shown in hub UI (e.g. 'Operating Systems')
|
||||||
|
component- path to Vue component (e.g. 'plugins/<name>/frontend/views/<Component>.vue')
|
||||||
|
|
||||||
|
Example:
|
||||||
|
return [{
|
||||||
|
'hub': 'model-catalog',
|
||||||
|
'path': 'operatingsystems',
|
||||||
|
'name': 'model-catalog-os',
|
||||||
|
'tab': 'Operating Systems',
|
||||||
|
'component': 'plugins/computers/frontend/views/OperatingSystemsList.vue'
|
||||||
|
}]
|
||||||
|
|
||||||
|
Return [] (default) if the plugin adds no settings pages.
|
||||||
|
"""
|
||||||
|
return []
|
||||||
|
|
||||||
def get_settings_defaults(self) -> List[Dict]:
|
def get_settings_defaults(self) -> List[Dict]:
|
||||||
"""Declare the Setting rows this plugin owns.
|
"""Declare the Setting rows this plugin owns.
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user