feat(portal): implement dynamic drawer/page navigation management

Replace hardcoded sidebar drawer configuration with admin-manageable
dynamic system. Extend page_status.json with drawer definitions and
page assignments, add drawer CRUD API endpoints, render portal sidebar
via Jinja2 loops, and extend /admin/pages UI with drawer management.
Fix multi-worker cache invalidation via mtime-based staleness detection.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
egg
2026-02-09 11:34:04 +08:00
parent 706c8ba52c
commit 9b1d2edc52
20 changed files with 2269 additions and 735 deletions

View File

@@ -100,6 +100,108 @@ class TestTemplateIntegration(unittest.TestCase):
self.assertIn('mes-toast-container', html)
class TestPortalDynamicDrawerRendering(unittest.TestCase):
"""Test dynamic portal drawer rendering."""
def setUp(self):
db._ENGINE = None
self.app = create_app('testing')
self.app.config['TESTING'] = True
self.client = self.app.test_client()
_login_as_admin(self.client)
def test_portal_uses_navigation_config_for_sidebar_and_iframes(self):
drawers = [
{
"id": "custom",
"name": "自訂分類",
"order": 1,
"admin_only": False,
"pages": [
{
"route": "/wip-overview",
"name": "自訂首頁",
"status": "released",
"order": 1,
"frame_id": "customFrame",
"tool_src": None,
}
],
},
{
"id": "dev-tools",
"name": "開發工具",
"order": 2,
"admin_only": True,
"pages": [
{
"route": "/admin/pages",
"name": "頁面管理",
"status": "dev",
"order": 1,
"frame_id": "toolFrame",
"tool_src": "/admin/pages",
}
],
},
]
with patch("mes_dashboard.app.get_navigation_config", return_value=drawers):
response = self.client.get("/")
self.assertEqual(response.status_code, 200)
html = response.data.decode("utf-8")
self.assertIn("自訂分類", html)
self.assertIn('data-target="customFrame"', html)
self.assertIn('id="customFrame"', html)
self.assertIn('data-tool-src="/admin/pages"', html)
self.assertIn('id="toolFrame"', html)
def test_portal_hides_admin_only_drawer_for_non_admin(self):
client = self.app.test_client()
drawers = [
{
"id": "custom",
"name": "自訂分類",
"order": 1,
"admin_only": False,
"pages": [
{
"route": "/wip-overview",
"name": "自訂首頁",
"status": "released",
"order": 1,
"frame_id": "customFrame",
"tool_src": None,
}
],
},
{
"id": "dev-tools",
"name": "開發工具",
"order": 2,
"admin_only": True,
"pages": [
{
"route": "/admin/pages",
"name": "頁面管理",
"status": "dev",
"order": 1,
"frame_id": "toolFrame",
"tool_src": "/admin/pages",
}
],
},
]
with patch("mes_dashboard.app.get_navigation_config", return_value=drawers):
response = client.get("/")
self.assertEqual(response.status_code, 200)
html = response.data.decode("utf-8")
self.assertIn("自訂分類", html)
self.assertNotIn("開發工具", html)
self.assertNotIn('data-tool-src="/admin/pages"', html)
class TestToastCSSIntegration(unittest.TestCase):
"""Test that Toast CSS styles are included in pages."""