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:
parent
27ec723256
commit
c65f1605b1
97
app.py
97
app.py
@ -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
|
||||
# ============================================================
|
||||
|
||||
@ -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"/>
|
||||
|
||||
@ -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) -->
|
||||
|
||||
@ -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">
|
||||
|
||||
Loading…
Reference in New Issue
Block a user