feat(dashboard): expose repository urls refs NOISSUE

This commit is contained in:
2026-04-10 20:27:08 +02:00
parent 227ad1ad6f
commit af4247e657
2 changed files with 69 additions and 1 deletions

View File

@@ -4,6 +4,7 @@ from sqlalchemy.orm import Session
from sqlalchemy import text from sqlalchemy import text
try: try:
from ..config import settings
from ..models import ( from ..models import (
AuditTrail, AuditTrail,
ProjectHistory, ProjectHistory,
@@ -17,6 +18,7 @@ try:
UserAction, UserAction,
) )
except ImportError: except ImportError:
from config import settings
from models import ( from models import (
AuditTrail, AuditTrail,
ProjectHistory, ProjectHistory,
@@ -307,6 +309,31 @@ class DatabaseManager:
self.db.refresh(pr) self.db.refresh(pr)
return pr return pr
def _get_latest_ui_snapshot_data(self, history_id: int) -> dict:
"""Return the latest stored UI snapshot payload for a project."""
snapshot = self.db.query(UISnapshot).filter(
UISnapshot.history_id == history_id
).order_by(UISnapshot.created_at.desc(), UISnapshot.id.desc()).first()
if not snapshot:
return {}
return self._normalize_metadata(snapshot.snapshot_data)
def _get_project_repository(self, history: ProjectHistory) -> dict | None:
"""Resolve repository metadata for a project."""
snapshot_data = self._get_latest_ui_snapshot_data(history.id)
repository = snapshot_data.get("repository")
if isinstance(repository, dict) and any(repository.values()):
return repository
if settings.gitea_owner and settings.gitea_repo and settings.gitea_url:
return {
"owner": settings.gitea_owner,
"name": settings.gitea_repo,
"url": f"{settings.gitea_url.rstrip('/')}/{settings.gitea_owner}/{settings.gitea_repo}",
"mode": "shared",
}
return None
def get_project_by_id(self, project_id: str) -> ProjectHistory | None: def get_project_by_id(self, project_id: str) -> ProjectHistory | None:
"""Get project by ID.""" """Get project by ID."""
return self.db.query(ProjectHistory).filter(ProjectHistory.project_id == project_id).first() return self.db.query(ProjectHistory).filter(ProjectHistory.project_id == project_id).first()
@@ -708,6 +735,7 @@ class DatabaseManager:
prompts = self.get_prompt_events(project_id=project_id) prompts = self.get_prompt_events(project_id=project_id)
code_changes = self.get_code_changes(project_id=project_id) code_changes = self.get_code_changes(project_id=project_id)
correlations = self.get_prompt_change_correlations(project_id=project_id) correlations = self.get_prompt_change_correlations(project_id=project_id)
repository = self._get_project_repository(history)
return { return {
"project": { "project": {
@@ -720,6 +748,7 @@ class DatabaseManager:
"message": history.message, "message": history.message,
"error_message": history.error_message, "error_message": history.error_message,
"current_step": history.current_step, "current_step": history.current_step,
"repository": repository,
"completed_at": history.completed_at.isoformat() if history.completed_at else None, "completed_at": history.completed_at.isoformat() if history.completed_at else None,
"created_at": history.started_at.isoformat() if history.started_at else None "created_at": history.started_at.isoformat() if history.started_at else None
}, },
@@ -759,6 +788,7 @@ class DatabaseManager:
"prompts": prompts, "prompts": prompts,
"code_changes": code_changes, "code_changes": code_changes,
"prompt_change_correlations": correlations, "prompt_change_correlations": correlations,
"repository": repository,
} }
def get_prompt_events(self, project_id: str | None = None, limit: int = 100) -> list[dict]: def get_prompt_events(self, project_id: str | None = None, limit: int = 100) -> list[dict]:

View File

@@ -27,6 +27,30 @@ def _resolve_n8n_api_url() -> str:
return '' return ''
def _render_repository_block(repository: dict | None) -> None:
"""Render repository details and URL when available."""
if not repository:
ui.label('Repository URL not available yet.').classes('factory-muted')
return
owner = repository.get('owner') or 'unknown-owner'
name = repository.get('name') or 'unknown-repo'
mode = repository.get('mode') or 'project'
status = repository.get('status')
repo_url = repository.get('url')
with ui.column().classes('gap-1'):
with ui.row().classes('items-center gap-2'):
ui.label(f'{owner}/{name}').style('font-weight: 700; color: #2f241d;')
ui.label(mode).classes('factory-chip')
if status:
ui.label(status).classes('factory-chip')
if repo_url:
ui.link(repo_url, repo_url, new_tab=True).classes('factory-code')
else:
ui.label('Repository URL not available yet.').classes('factory-muted')
def _load_dashboard_snapshot() -> dict: def _load_dashboard_snapshot() -> dict:
"""Load dashboard data from the database.""" """Load dashboard data from the database."""
db = get_db_sync() db = get_db_sync()
@@ -101,6 +125,14 @@ def create_dashboard():
projects = snapshot['projects'] projects = snapshot['projects']
correlations = snapshot['correlations'] correlations = snapshot['correlations']
system_logs = snapshot['system_logs'] system_logs = snapshot['system_logs']
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'),
}
for project_bundle in projects
if project_bundle.get('project')
}
with ui.column().classes('factory-shell w-full gap-4 q-pa-lg'): with ui.column().classes('factory-shell w-full gap-4 q-pa-lg'):
with ui.card().classes('factory-panel w-full q-pa-lg'): with ui.card().classes('factory-panel w-full q-pa-lg'):
@@ -171,6 +203,10 @@ def create_dashboard():
project = project_bundle['project'] project = project_bundle['project']
with ui.expansion(f"{project['project_name']} · {project['status']}", icon='folder').classes('factory-panel w-full q-mb-md'): with ui.expansion(f"{project['project_name']} · {project['status']}", icon='folder').classes('factory-panel w-full q-mb-md'):
with ui.grid(columns=2).classes('w-full gap-4 q-pa-md'): with ui.grid(columns=2).classes('w-full gap-4 q-pa-md'):
with ui.card().classes('q-pa-md'):
ui.label('Repository').style('font-weight: 700; color: #3a281a;')
_render_repository_block(project_bundle.get('repository') or project.get('repository'))
with ui.card().classes('q-pa-md'): with ui.card().classes('q-pa-md'):
ui.label('Prompt').style('font-weight: 700; color: #3a281a;') ui.label('Prompt').style('font-weight: 700; color: #3a281a;')
prompts = project_bundle.get('prompts', []) prompts = project_bundle.get('prompts', [])
@@ -219,8 +255,10 @@ def create_dashboard():
ui.label('Each prompt entry is linked to the generated files recorded after that prompt for the same project.').classes('factory-muted') ui.label('Each prompt entry is linked to the generated files recorded after that prompt for the same project.').classes('factory-muted')
if correlations: if correlations:
for correlation in correlations: for correlation in correlations:
correlation_project = project_repository_map.get(correlation['project_id'], {})
with ui.card().classes('q-pa-md q-mt-md'): with ui.card().classes('q-pa-md q-mt-md'):
ui.label(correlation['project_id']).style('font-size: 1rem; font-weight: 700; color: #2f241d;') 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'))
ui.label(correlation['prompt_text']).classes('factory-code q-mt-sm') ui.label(correlation['prompt_text']).classes('factory-code q-mt-sm')
if correlation['changes']: if correlation['changes']:
for change in correlation['changes']: for change in correlation['changes']: