auto-claude: subtask-6-1 - Add /admin/it-audit route for admin dashboard
Implemented the IT audit admin dashboard route at /admin/it-audit that: - Checks for admin authentication (is_admin flag) - Queries all active companies with their latest IT audit data - Calculates statistics (audit count, avg scores, maturity distribution) - Provides technology adoption stats (Azure AD, M365, PBS, Zabbix, EDR, DR) - Queries collaboration flags from IT audits - Retrieves and organizes collaboration matches by type - Renders admin/it_audit_dashboard.html template Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
aab2b298a7
commit
7370ce78fa
210
app.py
210
app.py
@ -5033,6 +5033,216 @@ def admin_social_media():
|
||||
db.close()
|
||||
|
||||
|
||||
# ============================================================
|
||||
# IT AUDIT ADMIN DASHBOARD
|
||||
# ============================================================
|
||||
|
||||
@app.route('/admin/it-audit')
|
||||
@login_required
|
||||
def admin_it_audit():
|
||||
"""
|
||||
Admin dashboard for IT audit overview.
|
||||
|
||||
Displays:
|
||||
- Summary stats (audit count, average scores, maturity distribution)
|
||||
- Technology adoption stats (Azure AD, M365, PBS, Zabbix, EDR, DR)
|
||||
- Collaboration flags distribution
|
||||
- Company table with IT audit data
|
||||
- Collaboration matches matrix
|
||||
|
||||
Access: Admin only
|
||||
"""
|
||||
if not current_user.is_admin:
|
||||
flash('Brak uprawnień do tej strony.', 'error')
|
||||
return redirect(url_for('dashboard'))
|
||||
|
||||
db = SessionLocal()
|
||||
try:
|
||||
from sqlalchemy import func, distinct
|
||||
|
||||
# Import IT audit models and service
|
||||
from database import ITAudit, ITCollaborationMatch
|
||||
from it_audit_service import get_maturity_level_label
|
||||
|
||||
# Get all active companies with their latest IT audit
|
||||
# Using subquery to get only the latest audit per company
|
||||
latest_audit_subq = db.query(
|
||||
ITAudit.company_id,
|
||||
func.max(ITAudit.audit_date).label('max_date')
|
||||
).group_by(ITAudit.company_id).subquery()
|
||||
|
||||
companies_query = db.query(
|
||||
Company.id,
|
||||
Company.name,
|
||||
Company.slug,
|
||||
ITAudit.id.label('audit_id'),
|
||||
ITAudit.overall_score,
|
||||
ITAudit.security_score,
|
||||
ITAudit.collaboration_score,
|
||||
ITAudit.completeness_score,
|
||||
ITAudit.maturity_level,
|
||||
ITAudit.audit_date,
|
||||
ITAudit.has_azure_ad,
|
||||
ITAudit.has_m365,
|
||||
ITAudit.has_proxmox_pbs,
|
||||
ITAudit.monitoring_solution,
|
||||
ITAudit.has_edr,
|
||||
ITAudit.has_dr_plan
|
||||
).outerjoin(
|
||||
latest_audit_subq,
|
||||
Company.id == latest_audit_subq.c.company_id
|
||||
).outerjoin(
|
||||
ITAudit,
|
||||
(Company.id == ITAudit.company_id) &
|
||||
(ITAudit.audit_date == latest_audit_subq.c.max_date)
|
||||
).filter(
|
||||
Company.status == 'active'
|
||||
).order_by(
|
||||
Company.name
|
||||
).all()
|
||||
|
||||
# Build companies list with named attributes for template
|
||||
companies = []
|
||||
for row in companies_query:
|
||||
# Detect Zabbix from monitoring_solution field
|
||||
has_zabbix = row.monitoring_solution and 'zabbix' in str(row.monitoring_solution).lower()
|
||||
|
||||
companies.append({
|
||||
'id': row.id,
|
||||
'name': row.name,
|
||||
'slug': row.slug,
|
||||
'audit_id': row.audit_id,
|
||||
'overall_score': row.overall_score,
|
||||
'security_score': row.security_score,
|
||||
'collaboration_score': row.collaboration_score,
|
||||
'completeness_score': row.completeness_score,
|
||||
'maturity_level': row.maturity_level,
|
||||
'maturity_label': get_maturity_level_label(row.maturity_level) if row.maturity_level else None,
|
||||
'audit_date': row.audit_date,
|
||||
'has_azure_ad': row.has_azure_ad,
|
||||
'has_m365': row.has_m365,
|
||||
'has_proxmox_pbs': row.has_proxmox_pbs,
|
||||
'has_zabbix': has_zabbix,
|
||||
'has_edr': row.has_edr,
|
||||
'has_dr_plan': row.has_dr_plan
|
||||
})
|
||||
|
||||
# Calculate statistics
|
||||
audited_companies = [c for c in companies if c['overall_score'] is not None]
|
||||
not_audited = [c for c in companies if c['overall_score'] is None]
|
||||
|
||||
# Maturity distribution
|
||||
maturity_counts = {
|
||||
'basic': 0,
|
||||
'developing': 0,
|
||||
'established': 0,
|
||||
'advanced': 0
|
||||
}
|
||||
for c in audited_companies:
|
||||
level = c['maturity_level']
|
||||
if level in maturity_counts:
|
||||
maturity_counts[level] += 1
|
||||
|
||||
# Calculate average scores
|
||||
if audited_companies:
|
||||
avg_overall = round(sum(c['overall_score'] for c in audited_companies) / len(audited_companies))
|
||||
avg_security = round(sum(c['security_score'] or 0 for c in audited_companies) / len(audited_companies))
|
||||
avg_collaboration = round(sum(c['collaboration_score'] or 0 for c in audited_companies) / len(audited_companies))
|
||||
else:
|
||||
avg_overall = None
|
||||
avg_security = None
|
||||
avg_collaboration = None
|
||||
|
||||
# Technology adoption stats
|
||||
tech_stats = {
|
||||
'azure_ad': len([c for c in audited_companies if c['has_azure_ad']]),
|
||||
'm365': len([c for c in audited_companies if c['has_m365']]),
|
||||
'proxmox_pbs': len([c for c in audited_companies if c['has_proxmox_pbs']]),
|
||||
'zabbix': len([c for c in audited_companies if c['has_zabbix']]),
|
||||
'edr': len([c for c in audited_companies if c['has_edr']]),
|
||||
'dr_plan': len([c for c in audited_companies if c['has_dr_plan']])
|
||||
}
|
||||
|
||||
# Collaboration flags stats from latest audits
|
||||
collab_stats = {}
|
||||
if audited_companies:
|
||||
collab_flags = [
|
||||
'open_to_shared_licensing',
|
||||
'open_to_backup_replication',
|
||||
'open_to_teams_federation',
|
||||
'open_to_shared_monitoring',
|
||||
'open_to_collective_purchasing',
|
||||
'open_to_knowledge_sharing'
|
||||
]
|
||||
for flag in collab_flags:
|
||||
count = db.query(func.count(ITAudit.id)).filter(
|
||||
ITAudit.id.in_([c['audit_id'] for c in audited_companies if c['audit_id']]),
|
||||
getattr(ITAudit, flag) == True
|
||||
).scalar()
|
||||
collab_stats[flag] = count
|
||||
|
||||
# Get collaboration matches
|
||||
matches = db.query(
|
||||
ITCollaborationMatch,
|
||||
Company.name.label('company_a_name'),
|
||||
Company.slug.label('company_a_slug')
|
||||
).join(
|
||||
Company, ITCollaborationMatch.company_a_id == Company.id
|
||||
).order_by(
|
||||
ITCollaborationMatch.match_score.desc()
|
||||
).all()
|
||||
|
||||
# Organize matches by type
|
||||
matches_by_type = {}
|
||||
for match_row in matches:
|
||||
match = match_row[0]
|
||||
match_type = match.match_type
|
||||
if match_type not in matches_by_type:
|
||||
matches_by_type[match_type] = []
|
||||
|
||||
# Get company B info
|
||||
company_b = db.query(Company).filter(Company.id == match.company_b_id).first()
|
||||
|
||||
matches_by_type[match_type].append({
|
||||
'id': match.id,
|
||||
'company_a': {'name': match_row.company_a_name, 'slug': match_row.company_a_slug},
|
||||
'company_b': {'name': company_b.name if company_b else 'Nieznana', 'slug': company_b.slug if company_b else ''},
|
||||
'match_reason': match.match_reason,
|
||||
'match_score': match.match_score,
|
||||
'status': match.status
|
||||
})
|
||||
|
||||
stats = {
|
||||
'total_audits': len(audited_companies),
|
||||
'not_audited_count': len(not_audited),
|
||||
'avg_overall': avg_overall,
|
||||
'avg_security': avg_security,
|
||||
'avg_collaboration': avg_collaboration,
|
||||
'maturity_counts': maturity_counts,
|
||||
'tech_stats': tech_stats,
|
||||
'collab_stats': collab_stats,
|
||||
'total_matches': len(matches)
|
||||
}
|
||||
|
||||
# Convert companies list to objects with attribute access for template
|
||||
class CompanyRow:
|
||||
def __init__(self, data):
|
||||
for key, value in data.items():
|
||||
setattr(self, key, value)
|
||||
|
||||
companies_objects = [CompanyRow(c) for c in companies]
|
||||
|
||||
return render_template('admin/it_audit_dashboard.html',
|
||||
companies=companies_objects,
|
||||
stats=stats,
|
||||
matches_by_type=matches_by_type,
|
||||
now=datetime.now()
|
||||
)
|
||||
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
|
||||
# ============================================================
|
||||
# ERROR HANDLERS
|
||||
# ============================================================
|
||||
|
||||
Loading…
Reference in New Issue
Block a user