auto-claude: subtask-7-3 - Handle edge cases for IT audit
Edge cases handled: 1. Partial submission: - Added is_partial flag to save response - Dynamic success message based on completeness score - Completeness threshold messages (< 30%, 30-70%, > 70%) 2. Company without audit: - Fixed template to show "Brak audytu" for companies without audit - Added "Utwórz audyt" button (+ icon) for companies without audit - Fixed data structure mismatch between route and template 3. Multiple audit history: - Added get_company_audit_history() convenience function - Added has_company_audit() helper function - Added /api/it-audit/history/<company_id> API endpoint - Returns history_count in save response Other fixes: - Fixed stats variable naming in admin_it_audit route - Fixed collaboration_matches data structure for template - Fixed url_for to use slug instead of company_id - Fixed match_type filter (shared_licensing not shared_m365_licensing) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
fa45b4b793
commit
b405fdd810
202
app.py
202
app.py
@ -5293,47 +5293,77 @@ def admin_it_audit():
|
||||
).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(
|
||||
# Get collaboration matches with both companies' info
|
||||
matches = db.query(ITCollaborationMatch).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] = []
|
||||
# Build flat list of collaboration matches with all necessary attributes
|
||||
class CollabMatchRow:
|
||||
"""Helper class for template attribute access"""
|
||||
def __init__(self, **kwargs):
|
||||
for key, value in kwargs.items():
|
||||
setattr(self, key, value)
|
||||
|
||||
# Get company B info
|
||||
collaboration_matches = []
|
||||
for match in matches:
|
||||
# Get company A and B info
|
||||
company_a = db.query(Company).filter(Company.id == match.company_a_id).first()
|
||||
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
|
||||
})
|
||||
collaboration_matches.append(CollabMatchRow(
|
||||
id=match.id,
|
||||
match_type=match.match_type,
|
||||
company_a_id=match.company_a_id,
|
||||
company_a_name=company_a.name if company_a else 'Nieznana',
|
||||
company_a_slug=company_a.slug if company_a else '',
|
||||
company_b_id=match.company_b_id,
|
||||
company_b_name=company_b.name if company_b else 'Nieznana',
|
||||
company_b_slug=company_b.slug if company_b else '',
|
||||
match_reason=match.match_reason,
|
||||
match_score=match.match_score,
|
||||
status=match.status,
|
||||
created_at=match.created_at
|
||||
))
|
||||
|
||||
stats = {
|
||||
# Main stats
|
||||
'total_audits': len(audited_companies),
|
||||
'not_audited_count': len(not_audited),
|
||||
'avg_overall': avg_overall,
|
||||
'avg_security': avg_security,
|
||||
'avg_collaboration': avg_collaboration,
|
||||
'total_companies': len(companies),
|
||||
'companies_without_audit': len(not_audited),
|
||||
|
||||
# Score averages
|
||||
'avg_overall_score': avg_overall,
|
||||
'avg_security_score': avg_security,
|
||||
'avg_collaboration_score': avg_collaboration,
|
||||
|
||||
# Maturity distribution (flattened for template)
|
||||
'maturity_basic': maturity_counts['basic'],
|
||||
'maturity_developing': maturity_counts['developing'],
|
||||
'maturity_established': maturity_counts['established'],
|
||||
'maturity_advanced': maturity_counts['advanced'],
|
||||
|
||||
# Technology adoption stats (matching template naming with has_* prefix)
|
||||
'has_azure_ad': tech_stats['azure_ad'],
|
||||
'has_m365': tech_stats['m365'],
|
||||
'has_proxmox_pbs': tech_stats['proxmox_pbs'],
|
||||
'has_zabbix': tech_stats['zabbix'],
|
||||
'has_edr': tech_stats['edr'],
|
||||
'has_dr_plan': tech_stats['dr_plan'],
|
||||
|
||||
# Collaboration flags
|
||||
'open_to_shared_licensing': collab_stats.get('open_to_shared_licensing', 0),
|
||||
'open_to_backup_replication': collab_stats.get('open_to_backup_replication', 0),
|
||||
'open_to_teams_federation': collab_stats.get('open_to_teams_federation', 0),
|
||||
'open_to_shared_monitoring': collab_stats.get('open_to_shared_monitoring', 0),
|
||||
'open_to_collective_purchasing': collab_stats.get('open_to_collective_purchasing', 0),
|
||||
'open_to_knowledge_sharing': collab_stats.get('open_to_knowledge_sharing', 0),
|
||||
|
||||
# Legacy nested structures (for any templates that still use them)
|
||||
'maturity_counts': maturity_counts,
|
||||
'tech_stats': tech_stats,
|
||||
'collab_stats': collab_stats,
|
||||
'total_matches': len(matches)
|
||||
'total_matches': len(collaboration_matches)
|
||||
}
|
||||
|
||||
# Convert companies list to objects with attribute access for template
|
||||
@ -5347,7 +5377,7 @@ def admin_it_audit():
|
||||
return render_template('admin/it_audit_dashboard.html',
|
||||
companies=companies_objects,
|
||||
stats=stats,
|
||||
matches_by_type=matches_by_type,
|
||||
collaboration_matches=collaboration_matches,
|
||||
now=datetime.now()
|
||||
)
|
||||
|
||||
@ -5529,16 +5559,36 @@ def it_audit_save():
|
||||
service = ITAuditService(db)
|
||||
audit = service.save_audit(company_id, audit_data)
|
||||
|
||||
# Check if this is a partial submission (completeness < 100)
|
||||
is_partial = audit.completeness_score < 100 if audit.completeness_score else True
|
||||
|
||||
# Count previous audits for this company (to indicate if history exists)
|
||||
audit_history_count = db.query(ITAudit).filter(
|
||||
ITAudit.company_id == company_id
|
||||
).count()
|
||||
|
||||
logger.info(
|
||||
f"IT audit saved by {current_user.email} for company {company.name}: "
|
||||
f"overall={audit.overall_score}, security={audit.security_score}, "
|
||||
f"collaboration={audit.collaboration_score}, completeness={audit.completeness_score}"
|
||||
f"{' (partial)' if is_partial else ''}"
|
||||
)
|
||||
|
||||
# Return success response
|
||||
# Build appropriate success message
|
||||
if is_partial:
|
||||
if audit.completeness_score < 30:
|
||||
message = f'Audyt IT został zapisany. Formularz wypełniony w {audit.completeness_score}%. Uzupełnij więcej sekcji, aby uzyskać pełniejszy obraz infrastruktury IT.'
|
||||
elif audit.completeness_score < 70:
|
||||
message = f'Audyt IT został zapisany. Wypełniono {audit.completeness_score}% formularza. Rozważ uzupełnienie pozostałych sekcji.'
|
||||
else:
|
||||
message = f'Audyt IT został zapisany. Formularz prawie kompletny ({audit.completeness_score}%).'
|
||||
else:
|
||||
message = 'Audyt IT został zapisany pomyślnie. Formularz jest kompletny.'
|
||||
|
||||
# Return success response with detailed information
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'message': 'Audyt IT został zapisany pomyślnie.',
|
||||
'message': message,
|
||||
'company_id': company.id,
|
||||
'company_name': company.name,
|
||||
'company_slug': company.slug,
|
||||
@ -5550,7 +5600,9 @@ def it_audit_save():
|
||||
'collaboration_score': audit.collaboration_score,
|
||||
'completeness_score': audit.completeness_score,
|
||||
'maturity_level': audit.maturity_level,
|
||||
'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)
|
||||
}), 200
|
||||
|
||||
@ -5759,6 +5811,92 @@ def api_it_audit_matches(company_id):
|
||||
db.close()
|
||||
|
||||
|
||||
@app.route('/api/it-audit/history/<int:company_id>')
|
||||
@login_required
|
||||
def api_it_audit_history(company_id):
|
||||
"""
|
||||
API: Get IT audit history for a company.
|
||||
|
||||
Returns a list of all IT audits for a company, ordered by date descending.
|
||||
The first item in the list is always the latest (current) audit.
|
||||
|
||||
Access:
|
||||
- Admin: Can view history for any company
|
||||
- User: Can only view history for their own company
|
||||
|
||||
Args:
|
||||
company_id: Company ID to get audit history for
|
||||
|
||||
Query params:
|
||||
limit: Maximum number of audits to return (default: 10)
|
||||
|
||||
Returns:
|
||||
JSON with list of audits including:
|
||||
- audit_id, audit_date, overall_score, scores, maturity_level
|
||||
- is_current flag (True for the most recent audit)
|
||||
"""
|
||||
from it_audit_service import get_company_audit_history
|
||||
|
||||
# Access control: users can only view their own company's history
|
||||
if not current_user.is_admin and current_user.company_id != company_id:
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'error': 'Brak uprawnień do przeglądania historii audytów tej firmy.'
|
||||
}), 403
|
||||
|
||||
# Parse limit from query params
|
||||
limit = request.args.get('limit', 10, type=int)
|
||||
limit = min(max(limit, 1), 50) # Clamp to 1-50
|
||||
|
||||
db = SessionLocal()
|
||||
try:
|
||||
# Verify company exists
|
||||
company = db.query(Company).filter_by(id=company_id).first()
|
||||
if not company:
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'error': 'Firma nie znaleziona'
|
||||
}), 404
|
||||
|
||||
# Get audit history
|
||||
audits = get_company_audit_history(db, company_id, limit)
|
||||
|
||||
# Format response
|
||||
history = []
|
||||
for idx, audit in enumerate(audits):
|
||||
history.append({
|
||||
'id': audit.id,
|
||||
'audit_date': audit.audit_date.isoformat() if audit.audit_date else None,
|
||||
'audit_source': audit.audit_source,
|
||||
'overall_score': audit.overall_score,
|
||||
'security_score': audit.security_score,
|
||||
'collaboration_score': audit.collaboration_score,
|
||||
'completeness_score': audit.completeness_score,
|
||||
'maturity_level': audit.maturity_level,
|
||||
'is_current': idx == 0, # First item is most recent
|
||||
'is_partial': (audit.completeness_score or 0) < 100,
|
||||
})
|
||||
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'company_id': company_id,
|
||||
'company_name': company.name,
|
||||
'company_slug': company.slug,
|
||||
'total_audits': len(history),
|
||||
'history': history
|
||||
}), 200
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error fetching IT audit history for company {company_id}: {e}")
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'error': f'Błąd podczas pobierania historii audytów: {str(e)}'
|
||||
}), 500
|
||||
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
|
||||
# ============================================================
|
||||
# ERROR HANDLERS
|
||||
# ============================================================
|
||||
|
||||
@ -912,6 +912,36 @@ def calculate_scores(audit_data: dict) -> ITAuditResult:
|
||||
return service.calculate_scores(audit_data)
|
||||
|
||||
|
||||
def get_company_audit_history(db: Session, company_id: int, limit: int = 10) -> List[ITAudit]:
|
||||
"""
|
||||
Get audit history for a company.
|
||||
|
||||
Args:
|
||||
db: Database session
|
||||
company_id: Company ID
|
||||
limit: Maximum number of audits to return
|
||||
|
||||
Returns:
|
||||
List of ITAudit records ordered by date descending
|
||||
"""
|
||||
service = ITAuditService(db)
|
||||
return service.get_audit_history(company_id, limit)
|
||||
|
||||
|
||||
def has_company_audit(db: Session, company_id: int) -> bool:
|
||||
"""
|
||||
Check if a company has any IT audit.
|
||||
|
||||
Args:
|
||||
db: Database session
|
||||
company_id: Company ID
|
||||
|
||||
Returns:
|
||||
True if company has at least one audit, False otherwise
|
||||
"""
|
||||
return db.query(ITAudit).filter(ITAudit.company_id == company_id).first() is not None
|
||||
|
||||
|
||||
# === Main for Testing ===
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
@ -985,7 +985,7 @@
|
||||
{% if collaboration_matches %}
|
||||
<div class="collab-matrix-grid">
|
||||
<!-- Shared M365 Licensing -->
|
||||
{% set m365_matches = collaboration_matches|selectattr('match_type', 'equalto', 'shared_m365_licensing')|list %}
|
||||
{% set m365_matches = collaboration_matches|selectattr('match_type', 'equalto', 'shared_licensing')|list %}
|
||||
{% if m365_matches %}
|
||||
<div class="match-type-card">
|
||||
<h3>
|
||||
@ -1003,9 +1003,9 @@
|
||||
{% for match in m365_matches %}
|
||||
<div class="match-pair">
|
||||
<div class="match-pair-companies">
|
||||
<a href="{{ url_for('company_detail', company_id=match.company_a_id) }}" title="{{ match.company_a_name }}">{{ match.company_a_name }}</a>
|
||||
<a href="{{ url_for('company_detail', 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', company_id=match.company_b_id) }}" title="{{ match.company_b_name }}">{{ match.company_b_name }}</a>
|
||||
<a href="{{ url_for('company_detail', 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', company_id=match.company_a_id) }}" title="{{ match.company_a_name }}">{{ match.company_a_name }}</a>
|
||||
<a href="{{ url_for('company_detail', 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', company_id=match.company_b_id) }}" title="{{ match.company_b_name }}">{{ match.company_b_name }}</a>
|
||||
<a href="{{ url_for('company_detail', 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', company_id=match.company_a_id) }}" title="{{ match.company_a_name }}">{{ match.company_a_name }}</a>
|
||||
<a href="{{ url_for('company_detail', 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', company_id=match.company_b_id) }}" title="{{ match.company_b_name }}">{{ match.company_b_name }}</a>
|
||||
<a href="{{ url_for('company_detail', 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', company_id=match.company_a_id) }}" title="{{ match.company_a_name }}">{{ match.company_a_name }}</a>
|
||||
<a href="{{ url_for('company_detail', 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', company_id=match.company_b_id) }}" title="{{ match.company_b_name }}">{{ match.company_b_name }}</a>
|
||||
<a href="{{ url_for('company_detail', 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', company_id=match.company_a_id) }}" title="{{ match.company_a_name }}">{{ match.company_a_name }}</a>
|
||||
<a href="{{ url_for('company_detail', 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', company_id=match.company_b_id) }}" title="{{ match.company_b_name }}">{{ match.company_b_name }}</a>
|
||||
<a href="{{ url_for('company_detail', 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', company_id=match.company_a_id) }}" title="{{ match.company_a_name }}">{{ match.company_a_name }}</a>
|
||||
<a href="{{ url_for('company_detail', 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', company_id=match.company_b_id) }}" title="{{ match.company_b_name }}">{{ match.company_b_name }}</a>
|
||||
<a href="{{ url_for('company_detail', 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>
|
||||
@ -1255,76 +1255,77 @@
|
||||
</thead>
|
||||
<tbody id="auditTableBody">
|
||||
{% for company in companies %}
|
||||
{% set audit = company.it_audit %}
|
||||
{# company object has flat audit data (overall_score, security_score, etc.) directly on it #}
|
||||
{% set has_audit = company.overall_score is not none %}
|
||||
{% set maturity_level = 'none' %}
|
||||
{% if audit %}
|
||||
{% if audit.overall_score < 40 %}
|
||||
{% if has_audit %}
|
||||
{% if company.overall_score < 40 %}
|
||||
{% set maturity_level = 'basic' %}
|
||||
{% elif audit.overall_score < 60 %}
|
||||
{% elif company.overall_score < 60 %}
|
||||
{% set maturity_level = 'developing' %}
|
||||
{% elif audit.overall_score < 80 %}
|
||||
{% elif company.overall_score < 80 %}
|
||||
{% set maturity_level = 'established' %}
|
||||
{% else %}
|
||||
{% set maturity_level = 'advanced' %}
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
<tr data-name="{{ company.name|lower }}"
|
||||
data-overall="{{ audit.overall_score if audit else -1 }}"
|
||||
data-security="{{ audit.security_score if audit else -1 }}"
|
||||
data-collaboration="{{ audit.collaboration_score if audit else -1 }}"
|
||||
data-overall="{{ company.overall_score if has_audit else -1 }}"
|
||||
data-security="{{ company.security_score if has_audit else -1 }}"
|
||||
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', company_id=company.id) }}">{{ company.name }}</a>
|
||||
<a href="{{ url_for('company_detail', slug=company.slug) }}">{{ company.name }}</a>
|
||||
</td>
|
||||
<td class="score-cell">
|
||||
{% if audit %}
|
||||
<span class="score-badge overall-score {{ 'score-good' if audit.overall_score >= 80 else ('score-medium' if audit.overall_score >= 40 else 'score-poor') }}">
|
||||
{{ audit.overall_score }}
|
||||
{% if has_audit %}
|
||||
<span class="score-badge overall-score {{ 'score-good' if company.overall_score >= 80 else ('score-medium' if company.overall_score >= 40 else 'score-poor') }}">
|
||||
{{ company.overall_score }}
|
||||
</span>
|
||||
{% else %}
|
||||
<span class="score-badge score-na">Brak audytu</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td class="score-cell hide-mobile">
|
||||
{% if has_audit %}
|
||||
<span class="score-badge {{ 'score-good' if company.security_score >= 80 else ('score-medium' if company.security_score >= 40 else 'score-poor') }}">
|
||||
{{ company.security_score }}
|
||||
</span>
|
||||
{% else %}
|
||||
<span class="score-badge score-na">-</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td class="score-cell hide-mobile">
|
||||
{% if audit %}
|
||||
<span class="score-badge {{ 'score-good' if audit.security_score >= 80 else ('score-medium' if audit.security_score >= 40 else 'score-poor') }}">
|
||||
{{ audit.security_score }}
|
||||
</span>
|
||||
{% else %}
|
||||
<span class="score-badge score-na">-</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td class="score-cell hide-mobile">
|
||||
{% if audit %}
|
||||
<span class="score-badge {{ 'score-good' if audit.collaboration_score >= 80 else ('score-medium' if audit.collaboration_score >= 40 else 'score-poor') }}">
|
||||
{{ audit.collaboration_score }}
|
||||
{% if has_audit %}
|
||||
<span class="score-badge {{ 'score-good' if company.collaboration_score >= 80 else ('score-medium' if company.collaboration_score >= 40 else 'score-poor') }}">
|
||||
{{ company.collaboration_score }}
|
||||
</span>
|
||||
{% else %}
|
||||
<span class="score-badge score-na">-</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
{% if audit %}
|
||||
{% if has_audit %}
|
||||
<span class="maturity-badge {{ maturity_level }}">
|
||||
{% if maturity_level == 'basic' %}Podstawowy
|
||||
{% elif maturity_level == 'developing' %}Rozwijajacy
|
||||
{% elif maturity_level == 'developing' %}Rozwijający
|
||||
{% elif maturity_level == 'established' %}Ugruntowany
|
||||
{% elif maturity_level == 'advanced' %}Zaawansowany
|
||||
{% endif %}
|
||||
</span>
|
||||
{% else %}
|
||||
<span class="maturity-badge" style="background: var(--border); color: var(--text-secondary);">Brak</span>
|
||||
<span class="maturity-badge" style="background: var(--border); color: var(--text-secondary);">Brak audytu</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td class="hide-mobile">
|
||||
{% if audit %}
|
||||
{% if has_audit %}
|
||||
<div class="tech-icons">
|
||||
<span class="tech-icon {{ 'azure' if audit.has_azure_ad else 'inactive' }}" title="Azure AD / Entra ID">Az</span>
|
||||
<span class="tech-icon {{ 'm365' if audit.has_m365 else 'inactive' }}" title="Microsoft 365">M3</span>
|
||||
<span class="tech-icon {{ 'pbs' if audit.has_proxmox_pbs else 'inactive' }}" title="Proxmox Backup Server">PB</span>
|
||||
<span class="tech-icon {{ 'zabbix' if audit.has_zabbix else 'inactive' }}" title="Zabbix Monitoring">Zb</span>
|
||||
<span class="tech-icon {{ 'edr' if audit.has_edr else 'inactive' }}" title="EDR / XDR">ED</span>
|
||||
<span class="tech-icon {{ 'dr' if audit.has_dr_plan else 'inactive' }}" title="Plan DR">DR</span>
|
||||
<span class="tech-icon {{ 'azure' if company.has_azure_ad else 'inactive' }}" title="Azure AD / Entra ID">Az</span>
|
||||
<span class="tech-icon {{ 'm365' if company.has_m365 else 'inactive' }}" title="Microsoft 365">M3</span>
|
||||
<span class="tech-icon {{ 'pbs' if company.has_proxmox_pbs else 'inactive' }}" title="Proxmox Backup Server">PB</span>
|
||||
<span class="tech-icon {{ 'zabbix' if company.has_zabbix else 'inactive' }}" title="Zabbix Monitoring">Zb</span>
|
||||
<span class="tech-icon {{ 'edr' if company.has_edr else 'inactive' }}" title="EDR / XDR">ED</span>
|
||||
<span class="tech-icon {{ 'dr' if company.has_dr_plan else 'inactive' }}" title="Plan DR">DR</span>
|
||||
</div>
|
||||
{% else %}
|
||||
<span class="text-muted">-</span>
|
||||
@ -1332,16 +1333,20 @@
|
||||
</td>
|
||||
<td>
|
||||
<div class="action-buttons">
|
||||
<a href="{{ url_for('company_detail', company_id=company.id) }}" class="btn-icon" title="Zobacz profil">
|
||||
<a href="{{ url_for('company_detail', 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"/>
|
||||
</svg>
|
||||
</a>
|
||||
{% if current_user.is_admin %}
|
||||
<a href="{{ url_for('it_audit_form', company_id=company.id) }}" class="btn-icon edit" title="Edytuj audyt">
|
||||
<a href="{{ url_for('it_audit_form', company_id=company.id) }}" class="btn-icon edit" title="{{ 'Edytuj audyt' if has_audit else 'Utwórz audyt' }}">
|
||||
<svg width="16" height="16" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
{% if has_audit %}
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z"/>
|
||||
{% else %}
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4"/>
|
||||
{% endif %}
|
||||
</svg>
|
||||
</a>
|
||||
{% endif %}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user