mirror of
https://github.com/inventree/InvenTree.git
synced 2026-09-27 14:16:02 +00:00
[PUI] Dashboard refactor (#8278)
* Refactor plugin components into <RemoteComponent /> * Clean up footer * Allow BuildOrder list to be sorted by 'outstanding' * Fix model name * Update BuildOrderTable filter * Add StockItemTable column * Working towards new dashboard * Cleanup unused imports * Updates: Now rendering some custom widgets * Define icons for model types * Add icon * Cleanup / refactor / delete - Complete transfer of files into new structure * Follow link for query count widgets * Add some more widgets to the library * Remove old dashboard link in header * Remove feedback widget * Bump API version * Remove test widget * Rename "Home" -> "Dashboard" * Add some more widgets * Pass 'editable' property through to widgets * Cleanup * Add drawer for selecting new widgets * Allow different layouts per user on the same machine * Fixes * Add ability to *remove* widgets * Add helpful button * Add a keyboard shortcut * Refactoring * Add backend code for serving custom dashboard items * Load dashboard items from plugins * Tweak for dashboard item API query - Refetch if user changes - Tweak "loaded" value - Prevent refetchOnMount * Add message if no dashboard widgets are displayed * Refactoring main navigation menu - Group into sections - Cleanup / consolidation - General refactoring * Remove playground * Add backend field for storing dashboard layout * Add extra type definitions for UseInstance * Manual labels for builtin dashboard items - Otherwise they will change with translation locale * Shorten labels for more plugins * Adjust DashboardMenu * Reduce stored data * Add widget filter by text * Remove back-end settings * Update playwright tests for dashboard * Updated tests * Refactor backend API for fetching plugin features * Further fixes for back-end code * More back-end fixes * Refactor frontend: - Custom panels - Custom dashboard items * Further backend fixes * Yet more backend fixes - Improve error handling * Fix for custom plugin settings rendering * Enable plugin panels for part index and stock index pages * Cleanup * Fix nav menu * Update typing * Helper func to return all plugin settings as a dict * Update API version date * Fix for UseInstancea * typing fix * Tweak layout callbacks * Pass query parameters through to navigation functions * Improve custom query display * Add "news" widget * Ensure links are prepended with base URL on receipt * Update NewsWidget * Bug fix * Refactor template editor tests * Refactor unit testing for test_ui_panels * Unit test for dashboard item API endpoint * Update comment * Adjust playwright tests * More playwright fixes * Hide barcode scanning options if disabled * Tweak dashboard widget * Fix custom panel title * Update documentation around UIMixin class * Cleanup * Additional docs * Add icon def for 'error' ModelType * Add error boundary to TemplateEditor component * Fix so that it works with template editors and previews again * Tweak error messages * API unit test fixes * Unit test fix * More unit test fixes * Playwright test tweaks * Adjust error messages
This commit is contained in:
@@ -8,7 +8,6 @@ from django.utils.translation import gettext_lazy as _
|
||||
from InvenTree.version import INVENTREE_SW_VERSION
|
||||
from part.models import Part
|
||||
from plugin import InvenTreePlugin
|
||||
from plugin.helpers import render_template, render_text
|
||||
from plugin.mixins import SettingsMixin, UserInterfaceMixin
|
||||
|
||||
|
||||
@@ -19,7 +18,7 @@ class SampleUserInterfacePlugin(SettingsMixin, UserInterfaceMixin, InvenTreePlug
|
||||
SLUG = 'sampleui'
|
||||
TITLE = 'Sample User Interface Plugin'
|
||||
DESCRIPTION = 'A sample plugin which demonstrates user interface integrations'
|
||||
VERSION = '1.1'
|
||||
VERSION = '2.0'
|
||||
|
||||
ADMIN_SOURCE = 'ui_settings.js'
|
||||
|
||||
@@ -50,39 +49,22 @@ class SampleUserInterfacePlugin(SettingsMixin, UserInterfaceMixin, InvenTreePlug
|
||||
},
|
||||
}
|
||||
|
||||
def get_ui_panels(self, instance_type: str, instance_id: int, request, **kwargs):
|
||||
def get_ui_panels(self, request, context, **kwargs):
|
||||
"""Return a list of custom panels to be injected into the UI."""
|
||||
panels = []
|
||||
context = context or {}
|
||||
|
||||
# First, add a custom panel which will appear on every type of page
|
||||
# This panel will contain a simple message
|
||||
|
||||
content = render_text(
|
||||
"""
|
||||
This is a <i>sample panel</i> which appears on every page.
|
||||
It renders a simple string of <b>HTML</b> content.
|
||||
|
||||
<br>
|
||||
<h5>Instance Details:</h5>
|
||||
<ul>
|
||||
<li>Instance Type: {{ instance_type }}</li>
|
||||
<li>Instance ID: {{ instance_id }}</li>
|
||||
</ul>
|
||||
""",
|
||||
context={'instance_type': instance_type, 'instance_id': instance_id},
|
||||
)
|
||||
|
||||
panels.append({
|
||||
'name': 'sample_panel',
|
||||
'label': 'Sample Panel',
|
||||
'content': content,
|
||||
})
|
||||
target_model = context.get('target_model', None)
|
||||
target_id = context.get('target_id', None)
|
||||
|
||||
# A broken panel which tries to load a non-existent JS file
|
||||
if instance_id is not None and self.get_setting('ENABLE_BROKEN_PANElS'):
|
||||
if target_id is not None and self.get_setting('ENABLE_BROKEN_PANElS'):
|
||||
panels.append({
|
||||
'name': 'broken_panel',
|
||||
'label': 'Broken Panel',
|
||||
'key': 'broken-panel',
|
||||
'title': 'Broken Panel',
|
||||
'source': '/this/does/not/exist.js',
|
||||
})
|
||||
|
||||
@@ -90,85 +72,131 @@ class SampleUserInterfacePlugin(SettingsMixin, UserInterfaceMixin, InvenTreePlug
|
||||
# Note that we additionally provide some "context" data to the front-end render function
|
||||
if self.get_setting('ENABLE_DYNAMIC_PANEL'):
|
||||
panels.append({
|
||||
'name': 'dynamic_panel',
|
||||
'label': 'Dynamic Part Panel',
|
||||
'key': 'dynamic-panel',
|
||||
'title': 'Dynamic Panel',
|
||||
'source': self.plugin_static_file('sample_panel.js'),
|
||||
'icon': 'part',
|
||||
'context': {
|
||||
'version': INVENTREE_SW_VERSION,
|
||||
'plugin_version': self.VERSION,
|
||||
'random': random.randint(1, 100),
|
||||
'time': time.time(),
|
||||
},
|
||||
'icon': 'part',
|
||||
})
|
||||
|
||||
# Next, add a custom panel which will appear on the 'part' page
|
||||
# Note that this content is rendered from a template file,
|
||||
# using the django templating system
|
||||
if self.get_setting('ENABLE_PART_PANELS') and instance_type == 'part':
|
||||
if self.get_setting('ENABLE_PART_PANELS') and target_model == 'part':
|
||||
try:
|
||||
part = Part.objects.get(pk=instance_id)
|
||||
part = Part.objects.get(pk=target_id)
|
||||
except (Part.DoesNotExist, ValueError):
|
||||
part = None
|
||||
|
||||
# Note: This panel will *only* be available if the part is active
|
||||
if part and part.active:
|
||||
content = render_template(
|
||||
self, 'uidemo/custom_part_panel.html', context={'part': part}
|
||||
)
|
||||
|
||||
panels.append({
|
||||
'name': 'part_panel',
|
||||
'label': 'Part Panel',
|
||||
'content': content,
|
||||
})
|
||||
panels.append({
|
||||
'key': 'part-panel',
|
||||
'title': _('Part Panel'),
|
||||
'source': self.plugin_static_file('sample_panel.js:renderPartPanel'),
|
||||
'icon': 'part',
|
||||
'context': {'part_name': part.name if part else ''},
|
||||
})
|
||||
|
||||
# Next, add a custom panel which will appear on the 'purchaseorder' page
|
||||
if (
|
||||
self.get_setting('ENABLE_PURCHASE_ORDER_PANELS')
|
||||
and instance_type == 'purchaseorder'
|
||||
if target_model == 'purchaseorder' and self.get_setting(
|
||||
'ENABLE_PURCHASE_ORDER_PANELS'
|
||||
):
|
||||
panels.append({
|
||||
'name': 'purchase_order_panel',
|
||||
'label': 'Purchase Order Panel',
|
||||
'content': 'This is a custom panel which appears on the <b>Purchase Order</b> view page.',
|
||||
'key': 'purchase_order_panel',
|
||||
'title': 'Purchase Order Panel',
|
||||
'source': self.plugin_static_file('sample_panel.js:renderPoPanel'),
|
||||
})
|
||||
|
||||
# Admin panel - only visible to admin users
|
||||
if request.user.is_superuser:
|
||||
panels.append({
|
||||
'key': 'admin-panel',
|
||||
'title': 'Admin Panel',
|
||||
'source': self.plugin_static_file(
|
||||
'sample_panel.js:renderAdminOnlyPanel'
|
||||
),
|
||||
})
|
||||
|
||||
return panels
|
||||
|
||||
def get_ui_features(self, feature_type, context, request):
|
||||
"""Return a list of custom features to be injected into the UI."""
|
||||
if (
|
||||
feature_type == 'template_editor'
|
||||
and context.get('template_type') == 'labeltemplate'
|
||||
):
|
||||
return [
|
||||
{
|
||||
'feature_type': 'template_editor',
|
||||
'options': {
|
||||
'key': 'sample-template-editor',
|
||||
'title': 'Sample Template Editor',
|
||||
'icon': 'keywords',
|
||||
},
|
||||
'source': '/static/plugin/sample_template.js:getTemplateEditor',
|
||||
}
|
||||
]
|
||||
def get_ui_dashboard_items(self, request, context, **kwargs):
|
||||
"""Return a list of custom dashboard items."""
|
||||
items = [
|
||||
{
|
||||
'key': 'broken-dashboard-item',
|
||||
'title': _('Broken Dashboard Item'),
|
||||
'description': _(
|
||||
'This is a broken dashboard item - it will not render!'
|
||||
),
|
||||
'source': '/this/does/not/exist.js',
|
||||
},
|
||||
{
|
||||
'key': 'sample-dashboard-item',
|
||||
'title': _('Sample Dashboard Item'),
|
||||
'description': _(
|
||||
'This is a sample dashboard item. It renders a simple string of HTML content.'
|
||||
),
|
||||
'source': self.plugin_static_file('sample_dashboard_item.js'),
|
||||
},
|
||||
{
|
||||
'key': 'dynamic-dashboard-item',
|
||||
'title': _('Context Dashboard Item'),
|
||||
'description': 'A dashboard item which passes context data from the server',
|
||||
'source': self.plugin_static_file(
|
||||
'sample_dashboard_item.js:renderContextItem'
|
||||
),
|
||||
'context': {'foo': 'bar', 'hello': 'world'},
|
||||
'options': {'width': 3, 'height': 2},
|
||||
},
|
||||
]
|
||||
|
||||
if feature_type == 'template_preview':
|
||||
# Admin item - only visible to users with superuser access
|
||||
if request.user.is_superuser:
|
||||
items.append({
|
||||
'key': 'admin-dashboard-item',
|
||||
'title': _('Admin Dashboard Item'),
|
||||
'description': _('This is an admin-only dashboard item.'),
|
||||
'source': self.plugin_static_file('admin_dashboard_item.js'),
|
||||
'options': {'width': 4, 'height': 2},
|
||||
'context': {'secret-key': 'this-is-a-secret'},
|
||||
})
|
||||
|
||||
return items
|
||||
|
||||
def get_ui_template_editors(self, request, context, **kwargs):
|
||||
"""Return a list of custom template editors."""
|
||||
# If the context is a label template, return a custom template editor
|
||||
if context.get('template_type') == 'labeltemplate':
|
||||
return [
|
||||
{
|
||||
'feature_type': 'template_preview',
|
||||
'options': {
|
||||
'key': 'sample-template-preview',
|
||||
'title': 'Sample Template Preview',
|
||||
'icon': 'category',
|
||||
},
|
||||
'source': '/static/plugin/sample_template.js:getTemplatePreview',
|
||||
'key': 'sample-template-editor',
|
||||
'title': 'Sample Template Editor',
|
||||
'icon': 'keywords',
|
||||
'source': self.plugin_static_file(
|
||||
'sample_template.js:getTemplateEditor'
|
||||
),
|
||||
}
|
||||
]
|
||||
|
||||
return []
|
||||
|
||||
def get_ui_template_previews(self, request, context, **kwargs):
|
||||
"""Return a list of custom template previews."""
|
||||
return [
|
||||
{
|
||||
'key': 'sample-template-preview',
|
||||
'title': 'Sample Template Preview',
|
||||
'icon': 'category',
|
||||
'source': self.plugin_static_file(
|
||||
'sample_preview.js:getTemplatePreview'
|
||||
),
|
||||
}
|
||||
]
|
||||
|
||||
def get_admin_context(self) -> dict:
|
||||
"""Return custom context data which can be rendered in the admin panel."""
|
||||
return {'apple': 'banana', 'foo': 'bar', 'hello': 'world'}
|
||||
|
||||
Reference in New Issue
Block a user