feat: better dashboard reloading mechanism, refs NOISSUE

This commit is contained in:
2026-04-11 10:30:56 +02:00
parent f6681a0f85
commit 3d77ac3104

View File

@@ -627,15 +627,15 @@ def create_dashboard():
def _store_llm_stage(event) -> None:
app.storage.user[llm_stage_filter_key] = event.value or ''
dashboard_body.refresh()
_refresh_llm_filtered_sections()
def _store_llm_model(event) -> None:
app.storage.user[llm_model_filter_key] = event.value or ''
dashboard_body.refresh()
_refresh_llm_filtered_sections()
def _store_llm_search(event) -> None:
app.storage.user[llm_search_filter_key] = event.value or ''
dashboard_body.refresh()
_refresh_llm_filtered_sections()
def _selected_commit_lookup() -> str:
return app.storage.user.get(commit_lookup_key, '')
@@ -648,7 +648,7 @@ def create_dashboard():
def _store_branch_scope(event) -> None:
app.storage.user[branch_scope_filter_key] = event.value or ''
dashboard_body.refresh()
_refresh_timeline_sections()
def _selected_repo_owner() -> str:
return app.storage.user.get(repo_owner_key, settings.gitea_owner or '')
@@ -698,7 +698,7 @@ def create_dashboard():
)
_set_discovered_repositories(resolved)
ui.notify(f'Discovered {len(resolved)} repositories in {owner}', color='positive')
dashboard_body.refresh()
_refresh_system_sections()
async def onboard_repository_action(owner: str, repo_name: str) -> None:
if not settings.gitea_url or not settings.gitea_token:
@@ -728,7 +728,7 @@ def create_dashboard():
)
await discover_gitea_repositories_action()
ui.notify(f'Onboarded {owner}/{repo_name}', color='positive')
dashboard_body.refresh()
_refresh_all_dashboard_sections()
def sync_project_repository_action(project_id: str) -> None:
if not settings.gitea_url or not settings.gitea_token:
@@ -760,7 +760,7 @@ def create_dashboard():
)
manager.sync_repository_issues(project_id=project_id, gitea_api=gitea_api, state='open')
ui.notify(result.get('message', 'Repository sync finished'), color='positive' if result.get('status') == 'success' else 'negative')
dashboard_body.refresh()
_refresh_all_dashboard_sections()
async def setup_n8n_workflow_action() -> None:
api_url = _resolve_n8n_api_url()
@@ -787,7 +787,7 @@ def create_dashboard():
if result.get('status') == 'error':
_render_n8n_error_dialog(result)
ui.notify(result.get('message', 'n8n setup finished'), color='positive' if result.get('status') == 'success' else 'negative')
dashboard_body.refresh()
_refresh_all_dashboard_sections()
async def send_telegram_prompt_guide_action() -> None:
if not settings.telegram_bot_token:
@@ -815,12 +815,12 @@ def create_dashboard():
)
ui.notify(result.get('message', 'Telegram message sent'), color='positive' if result.get('status') == 'success' else 'negative')
dashboard_body.refresh()
_refresh_health_sections()
def init_db_action() -> None:
result = init_db()
ui.notify(result.get('message', 'Database initialized'), color='positive' if result.get('status') == 'success' else 'negative')
dashboard_body.refresh()
_refresh_all_dashboard_sections()
async def undo_prompt_action(project_id: str, prompt_id: int) -> None:
db = get_db_sync()
@@ -830,7 +830,7 @@ def create_dashboard():
with closing(db):
result = await PromptWorkflowManager(db).undo_prompt(project_id=project_id, prompt_id=prompt_id)
ui.notify(result.get('message', 'Prompt reverted') if result.get('status') != 'success' else 'Prompt changes reverted', color='positive' if result.get('status') == 'success' else 'negative')
dashboard_body.refresh()
_refresh_all_dashboard_sections()
def archive_project_action(project_id: str) -> None:
db = get_db_sync()
@@ -840,7 +840,7 @@ def create_dashboard():
with closing(db):
result = DatabaseManager(db).archive_project(project_id)
ui.notify(result.get('message', 'Project archived'), color='positive' if result.get('status') == 'success' else 'negative')
dashboard_body.refresh()
_refresh_all_dashboard_sections()
def unarchive_project_action(project_id: str) -> None:
db = get_db_sync()
@@ -850,7 +850,7 @@ def create_dashboard():
with closing(db):
result = DatabaseManager(db).unarchive_project(project_id)
ui.notify(result.get('message', 'Project restored'), color='positive' if result.get('status') == 'success' else 'negative')
dashboard_body.refresh()
_refresh_all_dashboard_sections()
def delete_project_action(project_id: str) -> None:
db = get_db_sync()
@@ -876,35 +876,50 @@ def create_dashboard():
if remote_delete and not remote_delete.get('error'):
message = f"{message}; remote repository deleted"
ui.notify(message, color='positive' if result.get('status') == 'success' else 'negative')
dashboard_body.refresh()
_refresh_all_dashboard_sections()
@ui.refreshable
def dashboard_body() -> None:
dashboard_state: dict = {}
def _load_dashboard_view_model() -> dict:
snapshot = _load_dashboard_snapshot()
if snapshot.get('error'):
with ui.card().classes('factory-panel w-full max-w-4xl mx-auto q-pa-xl'):
ui.label('Dashboard unavailable').style('font-size: 1.5rem; font-weight: 700; color: #5c2d1f;')
ui.label(snapshot['error']).classes('factory-muted')
ui.button('Initialize Database', on_click=init_db_action).props('unelevated')
return
summary = snapshot['summary']
projects = snapshot['projects']
archived_projects = snapshot.get('archived_projects', [])
correlations = snapshot['correlations']
system_logs = snapshot['system_logs']
llm_runtime = LLMServiceClient().get_runtime_configuration()
llm_stage_filter = _selected_llm_stage()
llm_model_filter = _selected_llm_model()
llm_search_filter = _selected_llm_search()
branch_scope_filter = _selected_branch_scope()
commit_lookup_query = _selected_commit_lookup()
commit_context = _load_commit_context(commit_lookup_query, branch_scope_filter) if commit_lookup_query else None
discovered_repositories = _get_discovered_repositories()
if snapshot.get('error'):
return {
'error': snapshot['error'],
'llm_runtime': llm_runtime,
'llm_stage_filter': llm_stage_filter,
'llm_model_filter': llm_model_filter,
'llm_search_filter': llm_search_filter,
'branch_scope_filter': branch_scope_filter,
'commit_lookup_query': commit_lookup_query,
'discovered_repositories': discovered_repositories,
}
projects = snapshot['projects']
all_llm_traces = [trace for project_bundle in projects for trace in project_bundle.get('llm_traces', [])]
llm_stage_options = [''] + sorted({trace.get('stage') for trace in all_llm_traces if trace.get('stage')})
llm_model_options = [''] + sorted({trace.get('model') for trace in all_llm_traces if trace.get('model')})
project_repository_map = {
return {
'snapshot': snapshot,
'summary': snapshot['summary'],
'projects': projects,
'archived_projects': snapshot.get('archived_projects', []),
'correlations': snapshot['correlations'],
'system_logs': snapshot['system_logs'],
'llm_runtime': llm_runtime,
'llm_stage_filter': llm_stage_filter,
'llm_model_filter': llm_model_filter,
'llm_search_filter': llm_search_filter,
'branch_scope_filter': branch_scope_filter,
'commit_lookup_query': commit_lookup_query,
'commit_context': _load_commit_context(commit_lookup_query, branch_scope_filter) if commit_lookup_query else None,
'discovered_repositories': discovered_repositories,
'llm_stage_options': [''] + sorted({trace.get('stage') for trace in all_llm_traces if trace.get('stage')}),
'llm_model_options': [''] + sorted({trace.get('model') for trace in all_llm_traces if trace.get('model')}),
'project_repository_map': {
project_bundle['project']['project_id']: {
'project_name': project_bundle['project']['project_name'],
'repository': project_bundle.get('repository') or project_bundle['project'].get('repository'),
@@ -913,20 +928,44 @@ def create_dashboard():
}
for project_bundle in projects
if project_bundle.get('project')
},
}
with ui.column().classes('factory-shell w-full gap-4 q-pa-lg'):
def _update_dashboard_state() -> None:
dashboard_state.clear()
dashboard_state.update(_load_dashboard_view_model())
def _view_model() -> dict:
if not dashboard_state:
_update_dashboard_state()
return dashboard_state
def _render_dashboard_unavailable(message: str) -> None:
with ui.card().classes('factory-panel w-full max-w-4xl mx-auto q-pa-xl'):
ui.label('Dashboard unavailable').style('font-size: 1.5rem; font-weight: 700; color: #5c2d1f;')
ui.label(message).classes('factory-muted')
ui.button('Initialize Database', on_click=init_db_action).props('unelevated')
@ui.refreshable
def render_header() -> None:
with ui.card().classes('factory-panel w-full q-pa-lg'):
with ui.row().classes('items-center justify-between w-full'):
with ui.column().classes('gap-1'):
ui.label('AI Software Factory').style('font-size: 2.3rem; font-weight: 800; color: #302116;')
ui.label('Operational dashboard with project audit, prompt traces, and n8n controls.').classes('factory-muted')
with ui.row().classes('items-center gap-2'):
ui.button('Refresh', on_click=dashboard_body.refresh).props('outline')
ui.button('Refresh', on_click=_refresh_current_dashboard_sections).props('outline')
ui.button('Initialize DB', on_click=init_db_action).props('unelevated color=dark')
ui.button('Provision n8n Workflow', on_click=setup_n8n_workflow_action).props('unelevated color=accent')
ui.button('Message Prompt Channel', on_click=send_telegram_prompt_guide_action).props('outline color=secondary')
@ui.refreshable
def render_metrics() -> None:
view_model = _view_model()
if view_model.get('error'):
_render_dashboard_unavailable(view_model['error'])
return
summary = view_model['summary']
with ui.grid(columns=4).classes('w-full gap-4'):
metrics = [
('Projects', summary['total_projects'], 'Tracked generation requests'),
@@ -941,19 +980,14 @@ def create_dashboard():
ui.label(str(value)).style('font-size: 2.1rem; font-weight: 800; margin-top: 6px;')
ui.label(subtitle).style('font-size: 0.9rem; opacity: 0.78; margin-top: 8px;')
selected_tab = _selected_tab_name()
with ui.tabs(value=selected_tab, on_change=_store_selected_tab).classes('w-full') as tabs:
ui.tab('Overview').props('name=overview')
ui.tab('Projects').props('name=projects')
ui.tab('Archived').props('name=archived')
ui.tab('Prompt Trace').props('name=trace')
ui.tab('Compare').props('name=compare')
ui.tab('Timeline').props('name=timeline')
ui.tab('System').props('name=system')
ui.tab('Health').props('name=health')
with ui.tab_panels(tabs, value=selected_tab).classes('w-full'):
with ui.tab_panel('overview'):
@ui.refreshable
def render_overview_panel() -> None:
view_model = _view_model()
if view_model.get('error'):
_render_dashboard_unavailable(view_model['error'])
return
projects = view_model['projects']
summary = view_model['summary']
with ui.grid(columns=2).classes('w-full gap-4'):
with ui.card().classes('factory-panel q-pa-lg'):
ui.label('Project Pipeline').style('font-size: 1.25rem; font-weight: 700; color: #3a281a;')
@@ -983,7 +1017,13 @@ def create_dashboard():
ui.label(label).classes('factory-muted')
ui.label(value).style('font-weight: 600; color: #3a281a;')
with ui.tab_panel('projects'):
@ui.refreshable
def render_projects_panel() -> None:
view_model = _view_model()
if view_model.get('error'):
_render_dashboard_unavailable(view_model['error'])
return
projects = view_model['projects']
if not projects:
with ui.card().classes('factory-panel q-pa-lg'):
ui.label('No project data available yet.').classes('factory-muted')
@@ -1019,7 +1059,16 @@ def create_dashboard():
on_click=lambda _=None, project_id=project['project_id']: sync_project_repository_action(project_id),
).props('outline color=secondary').classes('q-mt-md')
with ui.tab_panel('archived'):
@ui.refreshable
def render_archived_panel() -> None:
view_model = _view_model()
if view_model.get('error'):
_render_dashboard_unavailable(view_model['error'])
return
archived_projects = view_model['archived_projects']
llm_stage_filter = view_model['llm_stage_filter']
llm_model_filter = view_model['llm_model_filter']
llm_search_filter = view_model['llm_search_filter']
if not archived_projects:
with ui.card().classes('factory-panel q-pa-lg'):
ui.label('No archived projects yet.').classes('factory-muted')
@@ -1064,16 +1113,13 @@ def create_dashboard():
with ui.card().classes('q-pa-md'):
ui.label('Tracked Issues').style('font-weight: 700; color: #3a281a;')
_render_issue_list(project_bundle.get('issues', []))
with ui.card().classes('q-pa-md'):
ui.label('Repository Sync').style('font-weight: 700; color: #3a281a;')
_render_repository_sync_block(project_bundle.get('repository_sync') or project.get('repository_sync'))
with ui.card().classes('q-pa-md'):
ui.label('Pull Request').style('font-weight: 700; color: #3a281a;')
open_pr = next((pr for pr in project_bundle.get('pull_requests', []) if pr.get('pr_state') == 'open' and not pr.get('merged')), None)
_render_pull_request_block(open_pr)
with ui.card().classes('q-pa-md'):
ui.label('Prompt').style('font-weight: 700; color: #3a281a;')
prompts = project_bundle.get('prompts', [])
@@ -1086,25 +1132,20 @@ def create_dashboard():
ui.label(prompt['prompt_text']).classes('factory-code')
else:
ui.label('No prompt recorded.').classes('factory-muted')
with ui.grid(columns=1).classes('w-full gap-4 q-pa-md'):
with ui.card().classes('q-pa-md'):
ui.label('Generated Changes').style('font-weight: 700; color: #3a281a;')
_render_change_list(project_bundle.get('code_changes', []))
with ui.card().classes('q-pa-md'):
ui.label('Tracked Issues').style('font-weight: 700; color: #3a281a;')
_render_issue_list(project_bundle.get('issues', []))
with ui.grid(columns=2).classes('w-full gap-4 q-pa-md'):
with ui.card().classes('q-pa-md'):
ui.label('Git Commits').style('font-weight: 700; color: #3a281a;')
_render_commit_list(project_bundle.get('commits', []))
with ui.card().classes('q-pa-md'):
ui.label('LLM Trace').style('font-weight: 700; color: #3a281a;')
_render_llm_traces(_filter_llm_traces(project_bundle.get('llm_traces', []), llm_stage_filter, llm_model_filter, llm_search_filter))
with ui.card().classes('q-pa-md'):
ui.label('Recent Logs').style('font-weight: 700; color: #3a281a;')
logs = project_bundle.get('logs', [])[:6]
@@ -1113,12 +1154,10 @@ def create_dashboard():
ui.markdown(f"- {log['timestamp'] or 'n/a'} · {log['level']} · {log['message']}")
else:
ui.label('No project logs yet.').classes('factory-muted')
with ui.grid(columns=1).classes('w-full gap-4 q-pa-md'):
with ui.card().classes('q-pa-md'):
ui.label('Issue Work').style('font-weight: 700; color: #3a281a;')
_render_issue_work_events(project_bundle.get('issue_work', []))
with ui.card().classes('q-pa-md'):
ui.label('Audit Trail').style('font-weight: 700; color: #3a281a;')
audits = project_bundle.get('audit_trail', [])[:6]
@@ -1128,28 +1167,26 @@ def create_dashboard():
else:
ui.label('No audit events yet.').classes('factory-muted')
with ui.tab_panel('trace'):
@ui.refreshable
def render_trace_panel() -> None:
view_model = _view_model()
if view_model.get('error'):
_render_dashboard_unavailable(view_model['error'])
return
correlations = view_model['correlations']
project_repository_map = view_model['project_repository_map']
llm_stage_options = view_model['llm_stage_options']
llm_model_options = view_model['llm_model_options']
llm_stage_filter = view_model['llm_stage_filter']
llm_model_filter = view_model['llm_model_filter']
llm_search_filter = view_model['llm_search_filter']
with ui.card().classes('factory-panel q-pa-lg'):
ui.label('Prompt to Code Correlation').style('font-size: 1.25rem; font-weight: 700; color: #3a281a;')
ui.label('Each prompt entry is linked to the generated files recorded after that prompt for the same project.').classes('factory-muted')
with ui.row().classes('items-center gap-3 q-mt-md w-full'):
ui.select(
options=llm_stage_options,
value=llm_stage_filter,
on_change=_store_llm_stage,
label='LLM stage',
).classes('min-w-[12rem]')
ui.select(
options=llm_model_options,
value=llm_model_filter,
on_change=_store_llm_model,
label='LLM model',
).classes('min-w-[12rem]')
ui.input(
label='Search trace text',
value=llm_search_filter,
on_change=_store_llm_search,
).classes('min-w-[18rem]')
ui.select(options=llm_stage_options, value=llm_stage_filter, on_change=_store_llm_stage, label='LLM stage').classes('min-w-[12rem]')
ui.select(options=llm_model_options, value=llm_model_filter, on_change=_store_llm_model, label='LLM model').classes('min-w-[12rem]')
ui.input(label='Search trace text', value=llm_search_filter, on_change=_store_llm_search).classes('min-w-[18rem]')
if correlations:
for correlation in correlations:
correlation_project = project_repository_map.get(correlation['project_id'], {})
@@ -1172,35 +1209,30 @@ def create_dashboard():
else:
ui.label('No prompt traces recorded yet.').classes('factory-muted')
with ui.tab_panel('compare'):
@ui.refreshable
def render_compare_panel() -> None:
view_model = _view_model()
if view_model.get('error'):
_render_dashboard_unavailable(view_model['error'])
return
correlations = view_model['correlations']
project_repository_map = view_model['project_repository_map']
llm_stage_options = view_model['llm_stage_options']
llm_model_options = view_model['llm_model_options']
llm_stage_filter = view_model['llm_stage_filter']
llm_model_filter = view_model['llm_model_filter']
llm_search_filter = view_model['llm_search_filter']
with ui.card().classes('factory-panel q-pa-lg'):
ui.label('Prompt Compare View').style('font-size: 1.25rem; font-weight: 700; color: #3a281a;')
ui.label('Review one prompt at a time as a complete change set: repo diagnostics, commit links, and file-level diffs in one place.').classes('factory-muted')
with ui.row().classes('items-center gap-3 q-mt-md w-full'):
ui.select(
options=llm_stage_options,
value=llm_stage_filter,
on_change=_store_llm_stage,
label='LLM stage',
).classes('min-w-[12rem]')
ui.select(
options=llm_model_options,
value=llm_model_filter,
on_change=_store_llm_model,
label='LLM model',
).classes('min-w-[12rem]')
ui.input(
label='Search trace text',
value=llm_search_filter,
on_change=_store_llm_search,
).classes('min-w-[18rem]')
ui.select(options=llm_stage_options, value=llm_stage_filter, on_change=_store_llm_stage, label='LLM stage').classes('min-w-[12rem]')
ui.select(options=llm_model_options, value=llm_model_filter, on_change=_store_llm_model, label='LLM model').classes('min-w-[12rem]')
ui.input(label='Search trace text', value=llm_search_filter, on_change=_store_llm_search).classes('min-w-[18rem]')
if correlations:
for correlation in correlations:
correlation_project = project_repository_map.get(correlation['project_id'], {})
correlation = {
**correlation,
'llm_traces': _filter_llm_traces(correlation.get('llm_traces', []), llm_stage_filter, llm_model_filter, llm_search_filter),
}
filtered_correlation = {**correlation, 'llm_traces': _filter_llm_traces(correlation.get('llm_traces', []), llm_stage_filter, llm_model_filter, llm_search_filter)}
with ui.card().classes('q-pa-md q-mt-md'):
ui.label(correlation_project.get('project_name') or correlation['project_id']).style('font-size: 1rem; font-weight: 700; color: #2f241d;')
_render_repository_block(correlation_project.get('repository'))
@@ -1215,28 +1247,27 @@ def create_dashboard():
'Undo This Prompt',
on_click=lambda _=None, project_id=correlation['project_id'], prompt_id=correlation['prompt_id']: undo_prompt_action(project_id, prompt_id),
).props('outline color=negative')
_render_prompt_compare(correlation)
_render_prompt_compare(filtered_correlation)
else:
ui.label('No prompt compare data recorded yet.').classes('factory-muted')
with ui.tab_panel('timeline'):
@ui.refreshable
def render_timeline_panel() -> None:
view_model = _view_model()
if view_model.get('error'):
_render_dashboard_unavailable(view_model['error'])
return
projects = view_model['projects']
branch_scope_filter = view_model['branch_scope_filter']
commit_lookup_query = view_model['commit_lookup_query']
commit_context = view_model['commit_context']
with ui.card().classes('factory-panel q-pa-lg q-mb-md'):
ui.label('Commit Lookup').style('font-size: 1.25rem; font-weight: 700; color: #3a281a;')
ui.label('Submit a commit id to reconstruct the prompt, traces, repository state, and surrounding timeline that produced it.').classes('factory-muted')
with ui.row().classes('items-center gap-3 q-mt-md w-full'):
ui.select(
options=['', 'main', 'pr', 'manual'],
value=branch_scope_filter,
on_change=_store_branch_scope,
label='Branch scope',
).classes('min-w-[10rem]')
ui.input(
label='Commit hash',
value=commit_lookup_query,
on_change=_store_commit_lookup,
placeholder='deadbeef',
).classes('min-w-[18rem]')
ui.button('Lookup', on_click=dashboard_body.refresh).props('unelevated color=dark')
ui.select(options=['', 'main', 'pr', 'manual'], value=branch_scope_filter, on_change=_store_branch_scope, label='Branch scope').classes('min-w-[10rem]')
ui.input(label='Commit hash', value=commit_lookup_query, on_change=_store_commit_lookup, placeholder='deadbeef').classes('min-w-[18rem]')
ui.button('Lookup', on_click=_refresh_timeline_sections).props('unelevated color=dark')
if commit_lookup_query and commit_context is None:
ui.label('No recorded context found for that commit hash.').classes('factory-muted q-mt-md')
elif commit_context is not None:
@@ -1246,12 +1277,7 @@ def create_dashboard():
ui.label('Project Timelines').style('font-size: 1.25rem; font-weight: 700; color: #3a281a;')
ui.label('Chronological view of prompts, LLM traces, commits, PR updates, repository sync events, and prompt reverts.').classes('factory-muted')
with ui.row().classes('items-center gap-3 q-mt-md w-full'):
ui.select(
options=['', 'main', 'pr', 'manual'],
value=branch_scope_filter,
on_change=_store_branch_scope,
label='Branch scope',
).classes('min-w-[10rem]')
ui.select(options=['', 'main', 'pr', 'manual'], value=branch_scope_filter, on_change=_store_branch_scope, label='Branch scope').classes('min-w-[10rem]')
if projects:
for project_bundle in projects:
project = project_bundle['project']
@@ -1260,7 +1286,15 @@ def create_dashboard():
else:
ui.label('No project timelines recorded yet.').classes('factory-muted')
with ui.tab_panel('system'):
@ui.refreshable
def render_system_panel() -> None:
view_model = _view_model()
if view_model.get('error'):
_render_dashboard_unavailable(view_model['error'])
return
system_logs = view_model['system_logs']
llm_runtime = view_model['llm_runtime']
discovered_repositories = view_model['discovered_repositories']
with ui.grid(columns=2).classes('w-full gap-4'):
with ui.card().classes('factory-panel q-pa-lg'):
ui.label('System Logs').style('font-size: 1.25rem; font-weight: 700; color: #3a281a;')
@@ -1269,7 +1303,6 @@ def create_dashboard():
ui.markdown(f"- {log['timestamp'] or 'n/a'} · **{log['component']}** · {log['level']} · {log['message']}")
else:
ui.label('No system logs yet.').classes('factory-muted')
with ui.card().classes('factory-panel q-pa-lg'):
ui.label('LLM Runtime').style('font-size: 1.25rem; font-weight: 700; color: #3a281a;')
rows = [
@@ -1303,8 +1336,7 @@ def create_dashboard():
ui.label('Stage Tool Overrides').style('font-weight: 700; color: #3a281a; margin-top: 12px;')
ui.label(json.dumps(llm_runtime.get('live_tool_stage_tool_map'), indent=2, sort_keys=True)).classes('factory-code q-mt-sm')
ui.label('Guardrails').style('font-weight: 700; color: #3a281a; margin-top: 12px;')
guardrails = llm_runtime.get('guardrails', {})
for label, text in guardrails.items():
for label, text in (llm_runtime.get('guardrails') or {}).items():
ui.label(label.replace('_', ' ').title()).classes('factory-muted q-mt-sm')
ui.label(text or 'Not configured').classes('factory-code')
system_prompts = llm_runtime.get('system_prompts', {})
@@ -1313,26 +1345,14 @@ def create_dashboard():
for label, text in system_prompts.items():
ui.label(label.replace('_', ' ').title()).classes('factory-muted q-mt-sm')
ui.label(text or 'Not configured').classes('factory-code')
with ui.card().classes('factory-panel q-pa-lg'):
ui.label('Repository Onboarding').style('font-size: 1.25rem; font-weight: 700; color: #3a281a;')
ui.label('Discover repositories in the Gitea organization, onboard manually created repos, and import their recent commits into the dashboard.').classes('factory-muted')
with ui.row().classes('items-center gap-3 q-mt-md w-full'):
ui.input(
label='Owner / org',
value=_selected_repo_owner(),
on_change=_store_repo_owner,
).classes('min-w-[12rem]')
ui.input(
label='Repository name',
value=_selected_repo_name(),
on_change=_store_repo_name,
).classes('min-w-[14rem]')
ui.input(label='Owner / org', value=_selected_repo_owner(), on_change=_store_repo_owner).classes('min-w-[12rem]')
ui.input(label='Repository name', value=_selected_repo_name(), on_change=_store_repo_name).classes('min-w-[14rem]')
ui.button('Discover Repos', on_click=discover_gitea_repositories_action).props('outline color=secondary')
ui.button(
'Onboard Repo',
on_click=lambda: onboard_repository_action(_selected_repo_owner(), _selected_repo_name()),
).props('unelevated color=dark')
ui.button('Onboard Repo', on_click=lambda: onboard_repository_action(_selected_repo_owner(), _selected_repo_name())).props('unelevated color=dark')
if discovered_repositories:
for repo in discovered_repositories:
with ui.card().classes('q-pa-sm q-mt-md'):
@@ -1354,32 +1374,18 @@ def create_dashboard():
ui.link(repo['html_url'], repo['html_url'], new_tab=True).classes('factory-code')
else:
ui.label('No discovered repositories loaded yet.').classes('factory-muted q-mt-md')
with ui.card().classes('factory-panel q-pa-lg'):
ui.label('Important Endpoints').style('font-size: 1.25rem; font-weight: 700; color: #3a281a;')
endpoints = [
'/health',
'/llm/runtime',
'/generate',
'/projects',
'/audit/projects',
'/audit/prompts',
'/audit/changes',
'/audit/issues',
'/audit/commit-context',
'/audit/timeline',
'/audit/llm-traces',
'/audit/correlations',
'/projects/{project_id}/sync-repository',
'/gitea/repos',
'/gitea/repos/onboard',
'/n8n/health',
'/n8n/setup',
'/health', '/llm/runtime', '/generate', '/projects', '/audit/projects', '/audit/prompts', '/audit/changes', '/audit/issues',
'/audit/commit-context', '/audit/timeline', '/audit/llm-traces', '/audit/correlations', '/projects/{project_id}/sync-repository',
'/gitea/repos', '/gitea/repos/onboard', '/n8n/health', '/n8n/setup',
]
for endpoint in endpoints:
ui.label(endpoint).classes('factory-code q-mt-sm')
with ui.tab_panel('health'):
@ui.refreshable
def render_health_panel() -> None:
with ui.card().classes('factory-panel q-pa-lg q-mb-md'):
ui.label('Health and Diagnostics').style('font-size: 1.25rem; font-weight: 700; color: #3a281a;')
ui.label('Use this page to verify runtime configuration, n8n API connectivity, and likely causes of provisioning failures.').classes('factory-muted')
@@ -1394,9 +1400,87 @@ def create_dashboard():
ui.button('Send Prompt Guide', on_click=send_telegram_prompt_guide_action).props('unelevated color=secondary')
_render_health_panels()
dashboard_body()
panel_refreshers: dict[str, callable] = {}
def _refresh_current_dashboard_sections() -> None:
_update_dashboard_state()
panel_refreshers['metrics']()
active_tab = _selected_tab_name()
if active_tab in panel_refreshers:
panel_refreshers[active_tab]()
def _refresh_all_dashboard_sections() -> None:
_update_dashboard_state()
panel_refreshers['metrics']()
for name in ('overview', 'projects', 'archived', 'trace', 'compare', 'timeline', 'system', 'health'):
panel_refreshers[name]()
def _refresh_llm_filtered_sections() -> None:
_update_dashboard_state()
for name in ('archived', 'trace', 'compare'):
panel_refreshers[name]()
def _refresh_timeline_sections() -> None:
_update_dashboard_state()
panel_refreshers['timeline']()
def _refresh_system_sections() -> None:
_update_dashboard_state()
panel_refreshers['system']()
def _refresh_health_sections() -> None:
panel_refreshers['health']()
_update_dashboard_state()
with ui.column().classes('factory-shell w-full gap-4 q-pa-lg'):
render_header()
render_metrics()
selected_tab = _selected_tab_name()
with ui.tabs(value=selected_tab, on_change=_store_selected_tab).classes('w-full') as tabs:
ui.tab('Overview').props('name=overview')
ui.tab('Projects').props('name=projects')
ui.tab('Archived').props('name=archived')
ui.tab('Prompt Trace').props('name=trace')
ui.tab('Compare').props('name=compare')
ui.tab('Timeline').props('name=timeline')
ui.tab('System').props('name=system')
ui.tab('Health').props('name=health')
with ui.tab_panels(tabs, value=selected_tab).classes('w-full'):
with ui.tab_panel('overview'):
render_overview_panel()
with ui.tab_panel('projects'):
render_projects_panel()
with ui.tab_panel('archived'):
render_archived_panel()
with ui.tab_panel('trace'):
render_trace_panel()
with ui.tab_panel('compare'):
render_compare_panel()
with ui.tab_panel('timeline'):
render_timeline_panel()
with ui.tab_panel('system'):
render_system_panel()
with ui.tab_panel('health'):
render_health_panel()
panel_refreshers.update({
'header': render_header.refresh,
'metrics': render_metrics.refresh,
'overview': render_overview_panel.refresh,
'projects': render_projects_panel.refresh,
'archived': render_archived_panel.refresh,
'trace': render_trace_panel.refresh,
'compare': render_compare_panel.refresh,
'timeline': render_timeline_panel.refresh,
'system': render_system_panel.refresh,
'health': render_health_panel.refresh,
})
ui.timer(15.0, _run_background_repository_sync)
ui.timer(10.0, dashboard_body.refresh)
ui.timer(10.0, _refresh_current_dashboard_sections)
def run_app(port=None, reload=False, browser=True, storage_secret=None):