261 lines
10 KiB
Python
261 lines
10 KiB
Python
from django.contrib import admin
|
|
from django.utils.html import format_html
|
|
from django.urls import reverse
|
|
from django.utils.safestring import mark_safe
|
|
|
|
from .models import (
|
|
Postmortem, KnowledgeBaseArticle, IncidentRecommendation,
|
|
LearningPattern, KnowledgeBaseUsage, AutomatedPostmortemGeneration
|
|
)
|
|
|
|
|
|
@admin.register(Postmortem)
|
|
class PostmortemAdmin(admin.ModelAdmin):
|
|
list_display = [
|
|
'title', 'incident_link', 'status', 'severity', 'owner',
|
|
'completion_percentage', 'is_overdue', 'created_at'
|
|
]
|
|
list_filter = ['status', 'severity', 'is_automated', 'created_at']
|
|
search_fields = ['title', 'incident__title', 'owner__username']
|
|
readonly_fields = ['id', 'created_at', 'updated_at', 'completion_percentage', 'is_overdue']
|
|
fieldsets = (
|
|
('Basic Information', {
|
|
'fields': ('id', 'title', 'incident', 'status', 'severity')
|
|
}),
|
|
('Content', {
|
|
'fields': ('executive_summary', 'timeline', 'root_cause_analysis',
|
|
'impact_assessment', 'lessons_learned', 'action_items')
|
|
}),
|
|
('Automation', {
|
|
'fields': ('is_automated', 'generation_confidence', 'auto_generated_sections')
|
|
}),
|
|
('Workflow', {
|
|
'fields': ('owner', 'reviewers', 'approver', 'due_date')
|
|
}),
|
|
('Context', {
|
|
'fields': ('related_incidents', 'affected_services', 'affected_teams')
|
|
}),
|
|
('Timestamps', {
|
|
'fields': ('created_at', 'updated_at', 'published_at'),
|
|
'classes': ('collapse',)
|
|
})
|
|
)
|
|
filter_horizontal = ['reviewers', 'related_incidents']
|
|
|
|
def incident_link(self, obj):
|
|
if obj.incident:
|
|
url = reverse('admin:incident_intelligence_incident_change', args=[obj.incident.id])
|
|
return format_html('<a href="{}">{}</a>', url, obj.incident.title)
|
|
return '-'
|
|
incident_link.short_description = 'Incident'
|
|
|
|
def completion_percentage(self, obj):
|
|
percentage = obj.get_completion_percentage()
|
|
color = 'green' if percentage >= 80 else 'orange' if percentage >= 50 else 'red'
|
|
return format_html(
|
|
'<span style="color: {};">{:.1f}%</span>',
|
|
color, percentage
|
|
)
|
|
completion_percentage.short_description = 'Completion'
|
|
|
|
def is_overdue(self, obj):
|
|
if obj.is_overdue:
|
|
return format_html('<span style="color: red;">Yes</span>')
|
|
return format_html('<span style="color: green;">No</span>')
|
|
is_overdue.short_description = 'Overdue'
|
|
|
|
|
|
@admin.register(KnowledgeBaseArticle)
|
|
class KnowledgeBaseArticleAdmin(admin.ModelAdmin):
|
|
list_display = [
|
|
'title', 'article_type', 'category', 'status', 'view_count',
|
|
'author', 'is_due_for_review', 'created_at'
|
|
]
|
|
list_filter = ['article_type', 'category', 'status', 'difficulty_level', 'is_featured', 'created_at']
|
|
search_fields = ['title', 'content', 'summary', 'tags', 'author__username']
|
|
readonly_fields = ['id', 'created_at', 'updated_at', 'view_count', 'is_due_for_review']
|
|
fieldsets = (
|
|
('Basic Information', {
|
|
'fields': ('id', 'title', 'slug', 'article_type', 'category', 'subcategory')
|
|
}),
|
|
('Content', {
|
|
'fields': ('content', 'summary', 'tags', 'search_keywords')
|
|
}),
|
|
('Classification', {
|
|
'fields': ('related_services', 'related_components', 'difficulty_level')
|
|
}),
|
|
('Status & Workflow', {
|
|
'fields': ('status', 'is_featured', 'author', 'last_updated_by', 'maintainer')
|
|
}),
|
|
('Review Schedule', {
|
|
'fields': ('last_reviewed', 'next_review_due', 'is_due_for_review')
|
|
}),
|
|
('External Links', {
|
|
'fields': ('confluence_url', 'wiki_url', 'external_references'),
|
|
'classes': ('collapse',)
|
|
}),
|
|
('Relationships', {
|
|
'fields': ('related_incidents', 'source_postmortems'),
|
|
'classes': ('collapse',)
|
|
}),
|
|
('Statistics', {
|
|
'fields': ('view_count', 'created_at', 'updated_at'),
|
|
'classes': ('collapse',)
|
|
})
|
|
)
|
|
filter_horizontal = ['related_incidents', 'source_postmortems']
|
|
|
|
def is_due_for_review(self, obj):
|
|
if obj.is_due_for_review():
|
|
return format_html('<span style="color: red;">Yes</span>')
|
|
return format_html('<span style="color: green;">No</span>')
|
|
is_due_for_review.short_description = 'Due for Review'
|
|
|
|
|
|
@admin.register(IncidentRecommendation)
|
|
class IncidentRecommendationAdmin(admin.ModelAdmin):
|
|
list_display = [
|
|
'title', 'incident_link', 'recommendation_type', 'confidence_level',
|
|
'similarity_score', 'is_applied', 'created_at'
|
|
]
|
|
list_filter = ['recommendation_type', 'confidence_level', 'is_applied', 'created_at']
|
|
search_fields = ['title', 'description', 'incident__title', 'reasoning']
|
|
readonly_fields = ['id', 'created_at', 'updated_at']
|
|
fieldsets = (
|
|
('Basic Information', {
|
|
'fields': ('id', 'incident', 'recommendation_type', 'title', 'description')
|
|
}),
|
|
('Scores', {
|
|
'fields': ('similarity_score', 'confidence_level', 'confidence_score')
|
|
}),
|
|
('Related Objects', {
|
|
'fields': ('related_incident', 'knowledge_article', 'suggested_expert')
|
|
}),
|
|
('Recommendation Details', {
|
|
'fields': ('suggested_actions', 'expected_outcome', 'time_to_implement')
|
|
}),
|
|
('Usage Tracking', {
|
|
'fields': ('is_applied', 'applied_at', 'applied_by', 'effectiveness_rating')
|
|
}),
|
|
('AI Analysis', {
|
|
'fields': ('reasoning', 'matching_factors', 'model_version'),
|
|
'classes': ('collapse',)
|
|
}),
|
|
('Timestamps', {
|
|
'fields': ('created_at', 'updated_at'),
|
|
'classes': ('collapse',)
|
|
})
|
|
)
|
|
|
|
def incident_link(self, obj):
|
|
if obj.incident:
|
|
url = reverse('admin:incident_intelligence_incident_change', args=[obj.incident.id])
|
|
return format_html('<a href="{}">{}</a>', url, obj.incident.title)
|
|
return '-'
|
|
incident_link.short_description = 'Incident'
|
|
|
|
|
|
@admin.register(LearningPattern)
|
|
class LearningPatternAdmin(admin.ModelAdmin):
|
|
list_display = [
|
|
'name', 'pattern_type', 'frequency', 'success_rate',
|
|
'confidence_score', 'is_validated', 'times_applied'
|
|
]
|
|
list_filter = ['pattern_type', 'is_validated', 'created_at']
|
|
search_fields = ['name', 'description', 'validated_by__username']
|
|
readonly_fields = ['id', 'created_at', 'updated_at']
|
|
fieldsets = (
|
|
('Basic Information', {
|
|
'fields': ('id', 'name', 'pattern_type', 'description')
|
|
}),
|
|
('Pattern Characteristics', {
|
|
'fields': ('frequency', 'success_rate', 'confidence_score')
|
|
}),
|
|
('Pattern Details', {
|
|
'fields': ('triggers', 'actions', 'outcomes')
|
|
}),
|
|
('Source Data', {
|
|
'fields': ('source_incidents', 'source_postmortems'),
|
|
'classes': ('collapse',)
|
|
}),
|
|
('Validation', {
|
|
'fields': ('is_validated', 'validated_by', 'validation_notes')
|
|
}),
|
|
('Usage Tracking', {
|
|
'fields': ('times_applied', 'last_applied')
|
|
}),
|
|
('Timestamps', {
|
|
'fields': ('created_at', 'updated_at'),
|
|
'classes': ('collapse',)
|
|
})
|
|
)
|
|
filter_horizontal = ['source_incidents', 'source_postmortems']
|
|
|
|
|
|
@admin.register(KnowledgeBaseUsage)
|
|
class KnowledgeBaseUsageAdmin(admin.ModelAdmin):
|
|
list_display = [
|
|
'user', 'usage_type', 'article_link', 'recommendation_link',
|
|
'incident_link', 'created_at'
|
|
]
|
|
list_filter = ['usage_type', 'created_at']
|
|
search_fields = ['user__username', 'knowledge_article__title', 'recommendation__title']
|
|
readonly_fields = ['id', 'created_at']
|
|
|
|
def article_link(self, obj):
|
|
if obj.knowledge_article:
|
|
url = reverse('admin:knowledge_learning_knowledgebasearticle_change', args=[obj.knowledge_article.id])
|
|
return format_html('<a href="{}">{}</a>', url, obj.knowledge_article.title)
|
|
return '-'
|
|
article_link.short_description = 'Article'
|
|
|
|
def recommendation_link(self, obj):
|
|
if obj.recommendation:
|
|
url = reverse('admin:knowledge_learning_incidentrecommendation_change', args=[obj.recommendation.id])
|
|
return format_html('<a href="{}">{}</a>', url, obj.recommendation.title)
|
|
return '-'
|
|
recommendation_link.short_description = 'Recommendation'
|
|
|
|
def incident_link(self, obj):
|
|
if obj.incident:
|
|
url = reverse('admin:incident_intelligence_incident_change', args=[obj.incident.id])
|
|
return format_html('<a href="{}">{}</a>', url, obj.incident.title)
|
|
return '-'
|
|
incident_link.short_description = 'Incident'
|
|
|
|
|
|
@admin.register(AutomatedPostmortemGeneration)
|
|
class AutomatedPostmortemGenerationAdmin(admin.ModelAdmin):
|
|
list_display = [
|
|
'incident_link', 'status', 'generation_trigger', 'processing_time',
|
|
'model_version', 'started_at'
|
|
]
|
|
list_filter = ['status', 'generation_trigger', 'model_version', 'started_at']
|
|
search_fields = ['incident__title', 'error_message']
|
|
readonly_fields = ['id', 'started_at', 'completed_at']
|
|
fieldsets = (
|
|
('Basic Information', {
|
|
'fields': ('id', 'incident', 'status', 'generation_trigger')
|
|
}),
|
|
('Input Data', {
|
|
'fields': ('incident_data', 'timeline_data', 'log_data'),
|
|
'classes': ('collapse',)
|
|
}),
|
|
('Generation Results', {
|
|
'fields': ('generated_content', 'confidence_scores', 'quality_metrics', 'generated_postmortem'),
|
|
'classes': ('collapse',)
|
|
}),
|
|
('Processing Details', {
|
|
'fields': ('processing_time', 'model_version', 'error_message')
|
|
}),
|
|
('Timestamps', {
|
|
'fields': ('started_at', 'completed_at')
|
|
})
|
|
)
|
|
|
|
def incident_link(self, obj):
|
|
if obj.incident:
|
|
url = reverse('admin:incident_intelligence_incident_change', args=[obj.incident.id])
|
|
return format_html('<a href="{}">{}</a>', url, obj.incident.title)
|
|
return '-'
|
|
incident_link.short_description = 'Incident' |