fix: IT Audit fixes and improvements

- Add api_it_audit_export endpoint for CSV export
- Fix url_for references (company_detail -> company_detail_by_slug)
- Fix form action (save_it_audit -> it_audit_save)
- Add "Audyt IT" button to company profile contact bar

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Maciej Pienczyn 2026-01-09 13:19:53 +01:00
parent 27ec723256
commit c65f1605b1
4 changed files with 128 additions and 16 deletions

97
app.py
View File

@ -5603,7 +5603,7 @@ def it_audit_save():
'is_partial': is_partial,
},
'history_count': audit_history_count, # Number of audits for this company (including current)
'redirect_url': url_for('company_detail', slug=company.slug)
'redirect_url': url_for('company_detail_by_slug', slug=company.slug)
}), 200
except Exception as e:
@ -5897,6 +5897,101 @@ def api_it_audit_history(company_id):
db.close()
@app.route('/api/it-audit/export')
@login_required
def api_it_audit_export():
"""
API: Export IT audit data as CSV.
Exports all IT audits with company information and scores.
Admin-only endpoint.
Returns:
CSV file with IT audit data
"""
if not current_user.is_admin:
return jsonify({
'success': False,
'error': 'Tylko administrator może eksportować dane audytów.'
}), 403
db = SessionLocal()
try:
from database import ITAudit
import csv
from io import StringIO
# Get all latest audits per company
audits = db.query(ITAudit, Company).join(
Company, ITAudit.company_id == Company.id
).order_by(
ITAudit.company_id,
ITAudit.audit_date.desc()
).all()
# Deduplicate to get only latest audit per company
seen_companies = set()
latest_audits = []
for audit, company in audits:
if company.id not in seen_companies:
seen_companies.add(company.id)
latest_audits.append((audit, company))
# Create CSV
output = StringIO()
writer = csv.writer(output)
# Header
writer.writerow([
'Firma', 'NIP', 'Kategoria', 'Data audytu',
'Wynik ogólny', 'Bezpieczeństwo', 'Współpraca', 'Kompletność',
'Poziom dojrzałości', 'Azure AD', 'M365', 'EDR', 'MFA',
'Proxmox PBS', 'Monitoring'
])
# Data rows
for audit, company in latest_audits:
writer.writerow([
company.name,
company.nip or '',
company.category.name if company.category else '',
audit.audit_date.strftime('%Y-%m-%d') if audit.audit_date else '',
audit.overall_score or '',
audit.security_score or '',
audit.collaboration_score or '',
audit.completeness_score or '',
audit.maturity_level or '',
'Tak' if audit.has_azure_ad else 'Nie',
'Tak' if audit.has_m365 else 'Nie',
'Tak' if audit.has_edr else 'Nie',
'Tak' if audit.has_mfa else 'Nie',
'Tak' if audit.has_proxmox_pbs else 'Nie',
audit.monitoring_solution or 'Brak'
])
# Create response
output.seek(0)
from flask import Response
return Response(
output.getvalue(),
mimetype='text/csv',
headers={
'Content-Disposition': 'attachment; filename=it_audit_export.csv',
'Content-Type': 'text/csv; charset=utf-8'
}
)
except Exception as e:
logger.error(f"Error exporting IT audits: {e}")
return jsonify({
'success': False,
'error': f'Błąd podczas eksportu: {str(e)}'
}), 500
finally:
db.close()
# ============================================================
# ERROR HANDLERS
# ============================================================

View File

@ -1003,9 +1003,9 @@
{% for match in m365_matches %}
<div class="match-pair">
<div class="match-pair-companies">
<a href="{{ url_for('company_detail', slug=match.company_a_slug) }}" title="{{ match.company_a_name }}">{{ match.company_a_name }}</a>
<a href="{{ url_for('company_detail_by_slug', slug=match.company_a_slug) }}" title="{{ match.company_a_name }}">{{ match.company_a_name }}</a>
<span class="match-pair-separator"></span>
<a href="{{ url_for('company_detail', slug=match.company_b_slug) }}" title="{{ match.company_b_name }}">{{ match.company_b_name }}</a>
<a href="{{ url_for('company_detail_by_slug', slug=match.company_b_slug) }}" title="{{ match.company_b_name }}">{{ match.company_b_name }}</a>
</div>
<span class="match-status-badge {{ match.status }}">{{ match.status }}</span>
</div>
@ -1033,9 +1033,9 @@
{% for match in backup_matches %}
<div class="match-pair">
<div class="match-pair-companies">
<a href="{{ url_for('company_detail', slug=match.company_a_slug) }}" title="{{ match.company_a_name }}">{{ match.company_a_name }}</a>
<a href="{{ url_for('company_detail_by_slug', slug=match.company_a_slug) }}" title="{{ match.company_a_name }}">{{ match.company_a_name }}</a>
<span class="match-pair-separator"></span>
<a href="{{ url_for('company_detail', slug=match.company_b_slug) }}" title="{{ match.company_b_name }}">{{ match.company_b_name }}</a>
<a href="{{ url_for('company_detail_by_slug', slug=match.company_b_slug) }}" title="{{ match.company_b_name }}">{{ match.company_b_name }}</a>
</div>
<span class="match-status-badge {{ match.status }}">{{ match.status }}</span>
</div>
@ -1063,9 +1063,9 @@
{% for match in teams_matches %}
<div class="match-pair">
<div class="match-pair-companies">
<a href="{{ url_for('company_detail', slug=match.company_a_slug) }}" title="{{ match.company_a_name }}">{{ match.company_a_name }}</a>
<a href="{{ url_for('company_detail_by_slug', slug=match.company_a_slug) }}" title="{{ match.company_a_name }}">{{ match.company_a_name }}</a>
<span class="match-pair-separator"></span>
<a href="{{ url_for('company_detail', slug=match.company_b_slug) }}" title="{{ match.company_b_name }}">{{ match.company_b_name }}</a>
<a href="{{ url_for('company_detail_by_slug', slug=match.company_b_slug) }}" title="{{ match.company_b_name }}">{{ match.company_b_name }}</a>
</div>
<span class="match-status-badge {{ match.status }}">{{ match.status }}</span>
</div>
@ -1093,9 +1093,9 @@
{% for match in monitoring_matches %}
<div class="match-pair">
<div class="match-pair-companies">
<a href="{{ url_for('company_detail', slug=match.company_a_slug) }}" title="{{ match.company_a_name }}">{{ match.company_a_name }}</a>
<a href="{{ url_for('company_detail_by_slug', slug=match.company_a_slug) }}" title="{{ match.company_a_name }}">{{ match.company_a_name }}</a>
<span class="match-pair-separator"></span>
<a href="{{ url_for('company_detail', slug=match.company_b_slug) }}" title="{{ match.company_b_name }}">{{ match.company_b_name }}</a>
<a href="{{ url_for('company_detail_by_slug', slug=match.company_b_slug) }}" title="{{ match.company_b_name }}">{{ match.company_b_name }}</a>
</div>
<span class="match-status-badge {{ match.status }}">{{ match.status }}</span>
</div>
@ -1123,9 +1123,9 @@
{% for match in purchasing_matches %}
<div class="match-pair">
<div class="match-pair-companies">
<a href="{{ url_for('company_detail', slug=match.company_a_slug) }}" title="{{ match.company_a_name }}">{{ match.company_a_name }}</a>
<a href="{{ url_for('company_detail_by_slug', slug=match.company_a_slug) }}" title="{{ match.company_a_name }}">{{ match.company_a_name }}</a>
<span class="match-pair-separator"></span>
<a href="{{ url_for('company_detail', slug=match.company_b_slug) }}" title="{{ match.company_b_name }}">{{ match.company_b_name }}</a>
<a href="{{ url_for('company_detail_by_slug', slug=match.company_b_slug) }}" title="{{ match.company_b_name }}">{{ match.company_b_name }}</a>
</div>
<span class="match-status-badge {{ match.status }}">{{ match.status }}</span>
</div>
@ -1153,9 +1153,9 @@
{% for match in knowledge_matches %}
<div class="match-pair">
<div class="match-pair-companies">
<a href="{{ url_for('company_detail', slug=match.company_a_slug) }}" title="{{ match.company_a_name }}">{{ match.company_a_name }}</a>
<a href="{{ url_for('company_detail_by_slug', slug=match.company_a_slug) }}" title="{{ match.company_a_name }}">{{ match.company_a_name }}</a>
<span class="match-pair-separator"></span>
<a href="{{ url_for('company_detail', slug=match.company_b_slug) }}" title="{{ match.company_b_name }}">{{ match.company_b_name }}</a>
<a href="{{ url_for('company_detail_by_slug', slug=match.company_b_slug) }}" title="{{ match.company_b_name }}">{{ match.company_b_name }}</a>
</div>
<span class="match-status-badge {{ match.status }}">{{ match.status }}</span>
</div>
@ -1275,7 +1275,7 @@
data-collaboration="{{ company.collaboration_score if has_audit else -1 }}"
data-maturity="{{ maturity_level }}">
<td class="company-name-cell">
<a href="{{ url_for('company_detail', slug=company.slug) }}">{{ company.name }}</a>
<a href="{{ url_for('company_detail_by_slug', slug=company.slug) }}">{{ company.name }}</a>
</td>
<td class="score-cell">
{% if has_audit %}
@ -1333,7 +1333,7 @@
</td>
<td>
<div class="action-buttons">
<a href="{{ url_for('company_detail', slug=company.slug) }}" class="btn-icon" title="Zobacz profil">
<a href="{{ url_for('company_detail_by_slug', slug=company.slug) }}" class="btn-icon" title="Zobacz profil">
<svg width="16" height="16" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"/>
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z"/>

View File

@ -293,6 +293,10 @@
.contact-bar-item.social-audit { color: #a855f7; border-color: #a855f7; background: rgba(168, 85, 247, 0.05); }
.contact-bar-item.social-audit:hover { background: #a855f7; color: white; }
/* IT Infrastructure Audit link - styled as action button (teal/cyan) */
.contact-bar-item.it-audit { color: #0891b2; border-color: #0891b2; background: rgba(8, 145, 178, 0.05); }
.contact-bar-item.it-audit:hover { background: #0891b2; color: white; }
@media (max-width: 768px) {
.contact-bar {
justify-content: center;
@ -531,6 +535,18 @@
</a>
{% endif %}
{% endif %}
{# IT Infrastructure Audit link - visible to admins (all profiles) or regular users (own company only) #}
{% if current_user.is_authenticated %}
{% if current_user.is_admin or (current_user.company_id and current_user.company_id == company.id) %}
<a href="{{ url_for('it_audit_form', company_id=company.id) }}" class="contact-bar-item it-audit" title="Audyt Infrastruktury IT">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path stroke-linecap="round" stroke-linejoin="round" d="M5 12h14M5 12a2 2 0 01-2-2V6a2 2 0 012-2h14a2 2 0 012 2v4a2 2 0 01-2 2M5 12a2 2 0 00-2 2v4a2 2 0 002 2h14a2 2 0 002-2v-4a2 2 0 00-2-2m-2-4h.01M17 16h.01"/>
</svg>
<span>Audyt IT</span>
</a>
{% endif %}
{% endif %}
</div>
<!-- O firmie - Single Description (prioritized sources) -->

View File

@ -645,8 +645,9 @@
</div>
</div>
<form id="itAuditForm" method="POST" action="{{ url_for('save_it_audit', company_id=company.id) }}">
<form id="itAuditForm" method="POST" action="{{ url_for('it_audit_save') }}">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
<input type="hidden" name="company_id" value="{{ company.id }}">
<!-- Section 1: IT Contact -->
<div class="form-section" data-section="1">