feat(dashboard): expose repository urls refs NOISSUE
This commit is contained in:
@@ -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]:
|
||||||
|
|||||||
@@ -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']:
|
||||||
|
|||||||
Reference in New Issue
Block a user