nordabiz/templates/it_audit_form.html
Maciej Pienczyn b9a7f4640c auto-claude: subtask-4-1 - Create it_audit_form.html template with base structure
- Created IT audit form template with 9 sections (1-3 fully implemented)
- Section 1: IT Contact (IT manager, outsourcing, provider, contact info)
- Section 2: Cloud & Identity (Azure AD, M365, Google Workspace, local AD)
- Section 3: Server Infrastructure (server count, types, virtualization, OS)
- Sections 4-9: Placeholder structure ready for implementation
- Added progress bar with section navigation dots
- Implemented toggle switches, chip selects for multi-value fields
- Added conditional field visibility logic
- Included loading overlay and info modal patterns
- Responsive design following existing audit templates (gbp_audit.html, seo_audit.html)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-09 08:46:23 +01:00

1420 lines
52 KiB
HTML

{% extends "base.html" %}
{% block title %}Audyt IT - {{ company.name }} - Norda Biznes Hub{% endblock %}
{% block extra_css %}
<style>
.audit-header {
display: flex;
justify-content: space-between;
align-items: flex-start;
margin-bottom: var(--spacing-xl);
flex-wrap: wrap;
gap: var(--spacing-md);
}
.audit-header-info h1 {
font-size: var(--font-size-2xl);
color: var(--text-primary);
margin-bottom: var(--spacing-xs);
}
.audit-header-info p {
color: var(--text-secondary);
font-size: var(--font-size-sm);
}
.data-source-info {
display: inline-flex;
align-items: center;
gap: var(--spacing-xs);
margin-top: var(--spacing-sm);
padding: var(--spacing-xs) var(--spacing-sm);
background: var(--info-light, #e0f2fe);
border-radius: var(--radius);
font-size: var(--font-size-sm);
color: var(--info, #0284c7);
}
.data-source-info svg {
flex-shrink: 0;
}
.header-actions {
display: flex;
gap: var(--spacing-sm);
align-items: center;
}
/* Progress Bar */
.progress-container {
background: var(--surface);
padding: var(--spacing-md);
border-radius: var(--radius-lg);
box-shadow: var(--shadow-sm);
margin-bottom: var(--spacing-xl);
}
.progress-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: var(--spacing-sm);
}
.progress-title {
font-weight: 600;
color: var(--text-primary);
}
.progress-percentage {
font-size: var(--font-size-lg);
font-weight: 700;
color: var(--primary);
}
.progress-bar {
height: 8px;
background: var(--border);
border-radius: 4px;
overflow: hidden;
}
.progress-fill {
height: 100%;
background: var(--primary);
border-radius: 4px;
transition: width 0.3s ease;
}
.progress-sections {
display: flex;
gap: var(--spacing-xs);
margin-top: var(--spacing-sm);
flex-wrap: wrap;
}
.progress-section-dot {
width: 24px;
height: 24px;
border-radius: 50%;
background: var(--border);
display: flex;
align-items: center;
justify-content: center;
font-size: var(--font-size-xs);
font-weight: 600;
color: var(--text-tertiary);
cursor: pointer;
transition: var(--transition);
}
.progress-section-dot.active {
background: var(--primary);
color: white;
}
.progress-section-dot.complete {
background: var(--success);
color: white;
}
.progress-section-dot:hover {
transform: scale(1.1);
}
/* Form Sections */
.form-section {
background: var(--surface);
border-radius: var(--radius-lg);
box-shadow: var(--shadow);
margin-bottom: var(--spacing-xl);
overflow: hidden;
}
.section-header {
background: var(--bg-secondary);
padding: var(--spacing-md) var(--spacing-lg);
border-bottom: 1px solid var(--border);
display: flex;
align-items: center;
gap: var(--spacing-md);
cursor: pointer;
transition: var(--transition);
}
.section-header:hover {
background: var(--bg-tertiary);
}
.section-number {
width: 32px;
height: 32px;
border-radius: 50%;
background: var(--primary);
color: white;
display: flex;
align-items: center;
justify-content: center;
font-weight: 700;
font-size: var(--font-size-sm);
flex-shrink: 0;
}
.section-number.complete {
background: var(--success);
}
.section-title-group {
flex: 1;
}
.section-title {
font-size: var(--font-size-lg);
font-weight: 600;
color: var(--text-primary);
margin-bottom: var(--spacing-xs);
}
.section-subtitle {
font-size: var(--font-size-sm);
color: var(--text-secondary);
}
.section-toggle {
width: 24px;
height: 24px;
color: var(--text-secondary);
transition: transform 0.2s ease;
}
.section-header.collapsed .section-toggle {
transform: rotate(-90deg);
}
.section-content {
padding: var(--spacing-lg);
}
.section-content.collapsed {
display: none;
}
/* Form Fields */
.form-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
gap: var(--spacing-lg);
}
.form-group {
display: flex;
flex-direction: column;
gap: var(--spacing-xs);
}
.form-group.full-width {
grid-column: 1 / -1;
}
.form-label {
font-weight: 500;
color: var(--text-primary);
font-size: var(--font-size-sm);
display: flex;
align-items: center;
gap: var(--spacing-xs);
}
.form-label .required {
color: var(--error);
}
.form-label .help-icon {
width: 16px;
height: 16px;
color: var(--text-tertiary);
cursor: help;
}
.form-input,
.form-select,
.form-textarea {
padding: var(--spacing-sm) var(--spacing-md);
border: 1px solid var(--border);
border-radius: var(--radius);
font-size: var(--font-size-sm);
color: var(--text-primary);
background: var(--surface);
transition: var(--transition);
}
.form-input:focus,
.form-select:focus,
.form-textarea:focus {
outline: none;
border-color: var(--primary);
box-shadow: 0 0 0 3px rgba(37, 99, 235, 0.1);
}
.form-select {
cursor: pointer;
appearance: none;
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 20 20'%3e%3cpath stroke='%236b7280' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' d='M6 8l4 4 4-4'/%3e%3c/svg%3e");
background-position: right 0.5rem center;
background-repeat: no-repeat;
background-size: 1.5em 1.5em;
padding-right: 2.5rem;
}
.form-hint {
font-size: var(--font-size-xs);
color: var(--text-tertiary);
}
/* Checkbox and Toggle */
.form-checkbox-group {
display: flex;
flex-direction: column;
gap: var(--spacing-sm);
}
.form-checkbox {
display: flex;
align-items: center;
gap: var(--spacing-sm);
cursor: pointer;
}
.form-checkbox input[type="checkbox"] {
width: 18px;
height: 18px;
accent-color: var(--primary);
cursor: pointer;
}
.form-checkbox-label {
font-size: var(--font-size-sm);
color: var(--text-primary);
}
.form-toggle {
display: flex;
align-items: center;
gap: var(--spacing-md);
padding: var(--spacing-sm);
background: var(--bg-secondary);
border-radius: var(--radius);
}
.toggle-switch {
position: relative;
width: 48px;
height: 24px;
background: var(--border);
border-radius: 12px;
cursor: pointer;
transition: var(--transition);
}
.toggle-switch.active {
background: var(--success);
}
.toggle-switch::after {
content: '';
position: absolute;
width: 20px;
height: 20px;
background: white;
border-radius: 50%;
top: 2px;
left: 2px;
transition: var(--transition);
box-shadow: var(--shadow-sm);
}
.toggle-switch.active::after {
left: 26px;
}
.toggle-label {
font-size: var(--font-size-sm);
color: var(--text-primary);
}
/* Conditional Fields */
.conditional-fields {
margin-top: var(--spacing-md);
padding: var(--spacing-md);
background: var(--bg-secondary);
border-radius: var(--radius);
border-left: 3px solid var(--primary);
}
.conditional-fields.hidden {
display: none;
}
/* Multi-select chips */
.chip-select {
display: flex;
flex-wrap: wrap;
gap: var(--spacing-sm);
}
.chip-option {
padding: var(--spacing-xs) var(--spacing-md);
background: var(--bg-secondary);
border: 1px solid var(--border);
border-radius: var(--radius-full);
font-size: var(--font-size-sm);
color: var(--text-secondary);
cursor: pointer;
transition: var(--transition);
}
.chip-option:hover {
border-color: var(--primary);
color: var(--primary);
}
.chip-option.selected {
background: var(--primary);
border-color: var(--primary);
color: white;
}
/* Form Actions */
.form-actions {
display: flex;
justify-content: space-between;
align-items: center;
padding: var(--spacing-lg);
background: var(--surface);
border-radius: var(--radius-lg);
box-shadow: var(--shadow);
margin-top: var(--spacing-xl);
flex-wrap: wrap;
gap: var(--spacing-md);
}
.form-actions-left {
display: flex;
align-items: center;
gap: var(--spacing-sm);
color: var(--text-secondary);
font-size: var(--font-size-sm);
}
.form-actions-right {
display: flex;
gap: var(--spacing-sm);
}
/* Breadcrumb */
.breadcrumb {
display: flex;
align-items: center;
gap: var(--spacing-sm);
font-size: var(--font-size-sm);
color: var(--text-secondary);
margin-bottom: var(--spacing-md);
}
.breadcrumb a {
color: var(--primary);
text-decoration: none;
}
.breadcrumb a:hover {
text-decoration: underline;
}
.breadcrumb-separator {
color: var(--border);
}
/* Loading Overlay */
.loading-overlay {
display: none;
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(255, 255, 255, 0.95);
z-index: 1000;
align-items: center;
justify-content: center;
flex-direction: column;
gap: var(--spacing-lg);
}
.loading-overlay.active {
display: flex;
}
.loading-content {
background: var(--surface);
padding: var(--spacing-xl);
border-radius: var(--radius-lg);
box-shadow: var(--shadow-lg);
max-width: 400px;
width: 90%;
text-align: center;
}
.loading-spinner {
width: 48px;
height: 48px;
border: 4px solid var(--border);
border-top-color: var(--primary);
border-radius: 50%;
animation: spin 1s linear infinite;
margin: 0 auto var(--spacing-md);
}
@keyframes spin {
to { transform: rotate(360deg); }
}
/* Modal */
.modal {
display: none;
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.5);
z-index: 1000;
align-items: center;
justify-content: center;
}
.modal.active {
display: flex;
}
.modal-content {
background: var(--surface);
padding: var(--spacing-xl);
border-radius: var(--radius-lg);
max-width: 480px;
width: 90%;
box-shadow: var(--shadow-lg);
animation: modalSlideIn 0.2s ease-out;
}
@keyframes modalSlideIn {
from {
opacity: 0;
transform: translateY(-20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.modal-header {
display: flex;
align-items: center;
gap: var(--spacing-md);
margin-bottom: var(--spacing-md);
}
.modal-icon {
width: 48px;
height: 48px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
}
.modal-icon.success { background: #dcfce7; color: #16a34a; }
.modal-icon.info { background: #dbeafe; color: #2563eb; }
.modal-icon.warning { background: #fef3c7; color: #d97706; }
.modal-icon.error { background: #fee2e2; color: #dc2626; }
.modal-title {
font-size: var(--font-size-xl);
font-weight: 600;
}
.modal-body {
color: var(--text-secondary);
margin-bottom: var(--spacing-lg);
line-height: 1.6;
}
.modal-footer {
display: flex;
justify-content: flex-end;
gap: var(--spacing-sm);
}
/* Section icons */
.section-icon {
width: 20px;
height: 20px;
color: var(--text-secondary);
}
/* Responsive */
@media (max-width: 768px) {
.audit-header {
flex-direction: column;
}
.header-actions {
width: 100%;
justify-content: center;
}
.form-grid {
grid-template-columns: 1fr;
}
.form-actions {
flex-direction: column;
text-align: center;
}
.form-actions-left,
.form-actions-right {
width: 100%;
justify-content: center;
}
}
</style>
{% endblock %}
{% block content %}
<!-- Breadcrumb -->
<div class="breadcrumb">
<a href="{{ url_for('index') }}">Firmy</a>
<span class="breadcrumb-separator">/</span>
<a href="{{ url_for('company_detail', company_id=company.id) }}">{{ company.name }}</a>
<span class="breadcrumb-separator">/</span>
<span>Audyt IT</span>
</div>
<div class="audit-header">
<div class="audit-header-info">
<h1>Audyt Infrastruktury IT</h1>
<p>{{ company.name }}</p>
<div class="data-source-info">
<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="M9 3v2m6-2v2M9 19v2m6-2v2M5 9H3m2 6H3m18-6h-2m2 6h-2M7 19h10a2 2 0 002-2V7a2 2 0 00-2-2H7a2 2 0 00-2 2v10a2 2 0 002 2zM9 9h6v6H9V9z"/>
</svg>
<span>Formularz oceny infrastruktury IT i gotowosci do wspolpracy</span>
</div>
</div>
<div class="header-actions">
<a href="{{ url_for('company_detail', company_id=company.id) }}" class="btn btn-outline btn-sm">
<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="M10 19l-7-7m0 0l7-7m-7 7h18"/>
</svg>
Profil firmy
</a>
</div>
</div>
<!-- Progress Bar -->
<div class="progress-container">
<div class="progress-header">
<span class="progress-title">Postep wypelniania formularza</span>
<span class="progress-percentage" id="progressPercentage">0%</span>
</div>
<div class="progress-bar">
<div class="progress-fill" id="progressFill" style="width: 0%;"></div>
</div>
<div class="progress-sections">
<div class="progress-section-dot active" data-section="1" title="Kontakt IT">1</div>
<div class="progress-section-dot" data-section="2" title="Chmura i Tozsamosc">2</div>
<div class="progress-section-dot" data-section="3" title="Infrastruktura Serwerowa">3</div>
<div class="progress-section-dot" data-section="4" title="Stacje Robocze">4</div>
<div class="progress-section-dot" data-section="5" title="Bezpieczenstwo">5</div>
<div class="progress-section-dot" data-section="6" title="Backup i DR">6</div>
<div class="progress-section-dot" data-section="7" title="Monitoring">7</div>
<div class="progress-section-dot" data-section="8" title="Aplikacje Biznesowe">8</div>
<div class="progress-section-dot" data-section="9" title="Wspolpraca">9</div>
</div>
</div>
<form id="itAuditForm" method="POST" action="{{ url_for('save_it_audit', company_id=company.id) }}">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
<!-- Section 1: IT Contact -->
<div class="form-section" data-section="1">
<div class="section-header" onclick="toggleSection(1)">
<div class="section-number" id="sectionNumber1">1</div>
<div class="section-title-group">
<div class="section-title">
<svg class="section-icon" style="display: inline; vertical-align: middle; margin-right: 8px;" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z"/>
</svg>
Kontakt IT
</div>
<div class="section-subtitle">Informacje o osobie odpowiedzialnej za IT w firmie</div>
</div>
<svg class="section-toggle" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"/>
</svg>
</div>
<div class="section-content" id="sectionContent1">
<div class="form-grid">
<div class="form-group">
<label class="form-label">
Czy firma ma wewnetrznego menedzera IT?
</label>
<div class="form-toggle">
<div class="toggle-switch" id="toggleHasItManager" onclick="toggleSwitch('hasItManager')"></div>
<span class="toggle-label" id="labelHasItManager">Nie</span>
<input type="hidden" name="has_it_manager" id="hasItManager" value="false">
</div>
</div>
<div class="form-group">
<label class="form-label">
Czy IT jest outsourcowane?
</label>
<div class="form-toggle">
<div class="toggle-switch" id="toggleItOutsourced" onclick="toggleSwitch('itOutsourced')"></div>
<span class="toggle-label" id="labelItOutsourced">Nie</span>
<input type="hidden" name="it_outsourced" id="itOutsourced" value="false">
</div>
</div>
<div class="form-group conditional-fields" id="itProviderFields">
<label class="form-label">
Nazwa dostawcy uslug IT
</label>
<input type="text" class="form-input" name="it_provider_name" id="itProviderName" placeholder="np. INPI, Comarch, IT Partner">
<span class="form-hint">Nazwa firmy zewnetrznej obslugujaca IT</span>
</div>
<div class="form-group">
<label class="form-label">
Imie i nazwisko osoby kontaktowej ds. IT
</label>
<input type="text" class="form-input" name="it_contact_name" id="itContactName" placeholder="np. Jan Kowalski">
</div>
<div class="form-group">
<label class="form-label">
Email kontaktowy IT
</label>
<input type="email" class="form-input" name="it_contact_email" id="itContactEmail" placeholder="it@firma.pl">
<span class="form-hint">Adres email do spraw technicznych</span>
</div>
</div>
</div>
</div>
<!-- Section 2: Cloud & Identity -->
<div class="form-section" data-section="2">
<div class="section-header" onclick="toggleSection(2)">
<div class="section-number" id="sectionNumber2">2</div>
<div class="section-title-group">
<div class="section-title">
<svg class="section-icon" style="display: inline; vertical-align: middle; margin-right: 8px;" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 15a4 4 0 004 4h9a5 5 0 10-.1-9.999 5.002 5.002 0 10-9.78 2.096A4.001 4.001 0 003 15z"/>
</svg>
Chmura i Tozsamosc
</div>
<div class="section-subtitle">Uslugi chmurowe, Azure AD, Microsoft 365, Google Workspace</div>
</div>
<svg class="section-toggle" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"/>
</svg>
</div>
<div class="section-content collapsed" id="sectionContent2">
<div class="form-grid">
<div class="form-group">
<label class="form-label">
Czy firma korzysta z Azure Active Directory (Entra ID)?
</label>
<div class="form-toggle">
<div class="toggle-switch" id="toggleHasAzureAd" onclick="toggleSwitch('hasAzureAd')"></div>
<span class="toggle-label" id="labelHasAzureAd">Nie</span>
<input type="hidden" name="has_azure_ad" id="hasAzureAd" value="false">
</div>
</div>
<div class="form-group conditional-fields hidden" id="azureAdFields">
<label class="form-label">
Nazwa tenanta Azure (domena)
</label>
<input type="text" class="form-input" name="azure_tenant_name" id="azureTenantName" placeholder="np. firma.onmicrosoft.com">
<span class="form-hint">Domena glowna lub .onmicrosoft.com</span>
</div>
<div class="form-group conditional-fields hidden" id="azureUserCountField">
<label class="form-label">
Liczba uzytkownikow w Azure AD
</label>
<select class="form-select" name="azure_user_count" id="azureUserCount">
<option value="">Wybierz przedzial</option>
<option value="1-10">1-10 uzytkownikow</option>
<option value="11-25">11-25 uzytkownikow</option>
<option value="26-50">26-50 uzytkownikow</option>
<option value="51-100">51-100 uzytkownikow</option>
<option value="101-250">101-250 uzytkownikow</option>
<option value="250+">Ponad 250 uzytkownikow</option>
</select>
</div>
<div class="form-group">
<label class="form-label">
Czy firma korzysta z Microsoft 365?
</label>
<div class="form-toggle">
<div class="toggle-switch" id="toggleHasM365" onclick="toggleSwitch('hasM365')"></div>
<span class="toggle-label" id="labelHasM365">Nie</span>
<input type="hidden" name="has_m365" id="hasM365" value="false">
</div>
</div>
<div class="form-group full-width conditional-fields hidden" id="m365PlansField">
<label class="form-label">
Posiadane plany Microsoft 365 (wybierz wszystkie)
</label>
<div class="chip-select" id="m365PlansChips">
<div class="chip-option" data-value="basic" onclick="toggleChip(this, 'm365Plans')">Microsoft 365 Basic</div>
<div class="chip-option" data-value="standard" onclick="toggleChip(this, 'm365Plans')">Microsoft 365 Business Standard</div>
<div class="chip-option" data-value="premium" onclick="toggleChip(this, 'm365Plans')">Microsoft 365 Business Premium</div>
<div class="chip-option" data-value="e3" onclick="toggleChip(this, 'm365Plans')">Microsoft 365 E3</div>
<div class="chip-option" data-value="e5" onclick="toggleChip(this, 'm365Plans')">Microsoft 365 E5</div>
<div class="chip-option" data-value="exchange" onclick="toggleChip(this, 'm365Plans')">Exchange Online</div>
<div class="chip-option" data-value="sharepoint" onclick="toggleChip(this, 'm365Plans')">SharePoint Online</div>
</div>
<input type="hidden" name="m365_plans" id="m365Plans" value="">
</div>
<div class="form-group full-width conditional-fields hidden" id="teamsUsageField">
<label class="form-label">
Wykorzystanie Microsoft Teams (wybierz wszystkie)
</label>
<div class="chip-select" id="teamsUsageChips">
<div class="chip-option" data-value="chat" onclick="toggleChip(this, 'teamsUsage')">Chat</div>
<div class="chip-option" data-value="calls" onclick="toggleChip(this, 'teamsUsage')">Polaczenia glosowe</div>
<div class="chip-option" data-value="meetings" onclick="toggleChip(this, 'teamsUsage')">Spotkania video</div>
<div class="chip-option" data-value="channels" onclick="toggleChip(this, 'teamsUsage')">Kanaly zespolow</div>
<div class="chip-option" data-value="files" onclick="toggleChip(this, 'teamsUsage')">Udostepnianie plikow</div>
<div class="chip-option" data-value="apps" onclick="toggleChip(this, 'teamsUsage')">Aplikacje i integracje</div>
</div>
<input type="hidden" name="teams_usage" id="teamsUsage" value="">
</div>
<div class="form-group">
<label class="form-label">
Czy firma korzysta z Google Workspace?
</label>
<div class="form-toggle">
<div class="toggle-switch" id="toggleHasGoogleWorkspace" onclick="toggleSwitch('hasGoogleWorkspace')"></div>
<span class="toggle-label" id="labelHasGoogleWorkspace">Nie</span>
<input type="hidden" name="has_google_workspace" id="hasGoogleWorkspace" value="false">
</div>
</div>
<div class="form-group">
<label class="form-label">
Czy firma ma lokalna Active Directory (on-premises)?
</label>
<div class="form-toggle">
<div class="toggle-switch" id="toggleHasLocalAd" onclick="toggleSwitch('hasLocalAd')"></div>
<span class="toggle-label" id="labelHasLocalAd">Nie</span>
<input type="hidden" name="has_local_ad" id="hasLocalAd" value="false">
</div>
</div>
<div class="form-group conditional-fields hidden" id="localAdFields">
<label class="form-label">
Nazwa domeny AD
</label>
<input type="text" class="form-input" name="ad_domain_name" id="adDomainName" placeholder="np. firma.local">
</div>
<div class="form-group conditional-fields hidden" id="adSyncField">
<label class="form-label">
Czy AD jest zsynchronizowane z Azure AD?
</label>
<div class="form-toggle">
<div class="toggle-switch" id="toggleHasAdAzureSync" onclick="toggleSwitch('hasAdAzureSync')"></div>
<span class="toggle-label" id="labelHasAdAzureSync">Nie</span>
<input type="hidden" name="has_ad_azure_sync" id="hasAdAzureSync" value="false">
</div>
<span class="form-hint">Azure AD Connect / Cloud Sync</span>
</div>
</div>
</div>
</div>
<!-- Section 3: Server Infrastructure -->
<div class="form-section" data-section="3">
<div class="section-header" onclick="toggleSection(3)">
<div class="section-number" id="sectionNumber3">3</div>
<div class="section-title-group">
<div class="section-title">
<svg class="section-icon" style="display: inline; vertical-align: middle; margin-right: 8px;" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" 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>
Infrastruktura Serwerowa
</div>
<div class="section-subtitle">Serwery fizyczne i wirtualne, platformy wirtualizacji, systemy operacyjne</div>
</div>
<svg class="section-toggle" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"/>
</svg>
</div>
<div class="section-content collapsed" id="sectionContent3">
<div class="form-grid">
<div class="form-group">
<label class="form-label">
Liczba serwerow (fizycznych + wirtualnych)
</label>
<select class="form-select" name="server_count" id="serverCount">
<option value="">Wybierz przedzial</option>
<option value="0">Brak serwerow</option>
<option value="1-3">1-3 serwerow</option>
<option value="4-10">4-10 serwerow</option>
<option value="11-25">11-25 serwerow</option>
<option value="26-50">26-50 serwerow</option>
<option value="50+">Ponad 50 serwerow</option>
</select>
</div>
<div class="form-group full-width">
<label class="form-label">
Typy serwerow (wybierz wszystkie)
</label>
<div class="chip-select" id="serverTypesChips">
<div class="chip-option" data-value="physical_onprem" onclick="toggleChip(this, 'serverTypes')">Fizyczne (on-premises)</div>
<div class="chip-option" data-value="virtual_onprem" onclick="toggleChip(this, 'serverTypes')">Wirtualne (on-premises)</div>
<div class="chip-option" data-value="vps" onclick="toggleChip(this, 'serverTypes')">VPS (hosting zewnetrzny)</div>
<div class="chip-option" data-value="azure_vm" onclick="toggleChip(this, 'serverTypes')">Azure VM</div>
<div class="chip-option" data-value="aws_ec2" onclick="toggleChip(this, 'serverTypes')">AWS EC2</div>
<div class="chip-option" data-value="gcp_compute" onclick="toggleChip(this, 'serverTypes')">Google Cloud Compute</div>
<div class="chip-option" data-value="dedicated" onclick="toggleChip(this, 'serverTypes')">Serwery dedykowane (kolokacja)</div>
</div>
<input type="hidden" name="server_types" id="serverTypes" value="">
</div>
<div class="form-group">
<label class="form-label">
Platforma wirtualizacji (glowna)
</label>
<select class="form-select" name="virtualization_platform" id="virtualizationPlatform">
<option value="">Wybierz platforme</option>
<option value="none">Brak wirtualizacji</option>
<option value="proxmox">Proxmox VE</option>
<option value="vmware_esxi">VMware ESXi / vSphere</option>
<option value="hyperv">Microsoft Hyper-V</option>
<option value="xcp-ng">XCP-ng</option>
<option value="kvm">KVM / QEMU</option>
<option value="nutanix">Nutanix AHV</option>
<option value="other">Inna</option>
</select>
</div>
<div class="form-group full-width">
<label class="form-label">
Systemy operacyjne serwerow (wybierz wszystkie)
</label>
<div class="chip-select" id="serverOsChips">
<div class="chip-option" data-value="windows_2022" onclick="toggleChip(this, 'serverOs')">Windows Server 2022</div>
<div class="chip-option" data-value="windows_2019" onclick="toggleChip(this, 'serverOs')">Windows Server 2019</div>
<div class="chip-option" data-value="windows_2016" onclick="toggleChip(this, 'serverOs')">Windows Server 2016</div>
<div class="chip-option" data-value="windows_older" onclick="toggleChip(this, 'serverOs')">Windows Server (starszy)</div>
<div class="chip-option" data-value="ubuntu" onclick="toggleChip(this, 'serverOs')">Ubuntu Server</div>
<div class="chip-option" data-value="debian" onclick="toggleChip(this, 'serverOs')">Debian</div>
<div class="chip-option" data-value="rhel" onclick="toggleChip(this, 'serverOs')">RHEL / CentOS / Rocky</div>
<div class="chip-option" data-value="suse" onclick="toggleChip(this, 'serverOs')">SUSE Linux</div>
<div class="chip-option" data-value="freebsd" onclick="toggleChip(this, 'serverOs')">FreeBSD / TrueNAS</div>
</div>
<input type="hidden" name="server_os" id="serverOs" value="">
</div>
<div class="form-group">
<label class="form-label">
Marka firewalla / UTM sieciowego
</label>
<select class="form-select" name="network_firewall_brand" id="networkFirewallBrand">
<option value="">Wybierz marke</option>
<option value="none">Brak dedykowanego firewalla</option>
<option value="fortigate">Fortinet FortiGate</option>
<option value="pfsense">pfSense / OPNsense</option>
<option value="sophos">Sophos XG / XGS</option>
<option value="cisco">Cisco ASA / Meraki</option>
<option value="paloalto">Palo Alto Networks</option>
<option value="watchguard">WatchGuard</option>
<option value="mikrotik">MikroTik</option>
<option value="ubiquiti">Ubiquiti UDM / USG</option>
<option value="checkpoint">Check Point</option>
<option value="other">Inny</option>
</select>
</div>
</div>
</div>
</div>
<!-- Placeholder sections 4-9 (to be implemented in next subtask) -->
<div class="form-section" data-section="4">
<div class="section-header collapsed" onclick="toggleSection(4)">
<div class="section-number" id="sectionNumber4">4</div>
<div class="section-title-group">
<div class="section-title">Stacje Robocze</div>
<div class="section-subtitle">Komputery pracownikow, systemy operacyjne, MDM</div>
</div>
<svg class="section-toggle" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"/>
</svg>
</div>
<div class="section-content collapsed" id="sectionContent4">
<p style="color: var(--text-secondary); padding: var(--spacing-lg);">Ta sekcja zostanie dodana wkrotce...</p>
</div>
</div>
<div class="form-section" data-section="5">
<div class="section-header collapsed" onclick="toggleSection(5)">
<div class="section-number" id="sectionNumber5">5</div>
<div class="section-title-group">
<div class="section-title">Bezpieczenstwo</div>
<div class="section-subtitle">Antywirus, EDR, VPN, MFA, firewall</div>
</div>
<svg class="section-toggle" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"/>
</svg>
</div>
<div class="section-content collapsed" id="sectionContent5">
<p style="color: var(--text-secondary); padding: var(--spacing-lg);">Ta sekcja zostanie dodana wkrotce...</p>
</div>
</div>
<div class="form-section" data-section="6">
<div class="section-header collapsed" onclick="toggleSection(6)">
<div class="section-number" id="sectionNumber6">6</div>
<div class="section-title-group">
<div class="section-title">Backup i Disaster Recovery</div>
<div class="section-subtitle">Rozwiazania backupowe, cele, czestotliwosc, plan DR</div>
</div>
<svg class="section-toggle" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"/>
</svg>
</div>
<div class="section-content collapsed" id="sectionContent6">
<p style="color: var(--text-secondary); padding: var(--spacing-lg);">Ta sekcja zostanie dodana wkrotce...</p>
</div>
</div>
<div class="form-section" data-section="7">
<div class="section-header collapsed" onclick="toggleSection(7)">
<div class="section-number" id="sectionNumber7">7</div>
<div class="section-title-group">
<div class="section-title">Monitoring</div>
<div class="section-subtitle">Rozwiazania monitoringu, Zabbix, logi</div>
</div>
<svg class="section-toggle" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"/>
</svg>
</div>
<div class="section-content collapsed" id="sectionContent7">
<p style="color: var(--text-secondary); padding: var(--spacing-lg);">Ta sekcja zostanie dodana wkrotce...</p>
</div>
</div>
<div class="form-section" data-section="8">
<div class="section-header collapsed" onclick="toggleSection(8)">
<div class="section-number" id="sectionNumber8">8</div>
<div class="section-title-group">
<div class="section-title">Aplikacje Biznesowe</div>
<div class="section-subtitle">System ticketowy, ERP, CRM, dokumenty</div>
</div>
<svg class="section-toggle" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"/>
</svg>
</div>
<div class="section-content collapsed" id="sectionContent8">
<p style="color: var(--text-secondary); padding: var(--spacing-lg);">Ta sekcja zostanie dodana wkrotce...</p>
</div>
</div>
<div class="form-section" data-section="9">
<div class="section-header collapsed" onclick="toggleSection(9)">
<div class="section-number" id="sectionNumber9">9</div>
<div class="section-title-group">
<div class="section-title">Gotowosc do Wspolpracy</div>
<div class="section-subtitle">Mozliwosci wspolpracy z innymi firmami Norda Biznes</div>
</div>
<svg class="section-toggle" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"/>
</svg>
</div>
<div class="section-content collapsed" id="sectionContent9">
<p style="color: var(--text-secondary); padding: var(--spacing-lg);">Ta sekcja zostanie dodana wkrotce...</p>
</div>
</div>
<!-- Form Actions -->
<div class="form-actions">
<div class="form-actions-left">
<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="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"/>
</svg>
<span>Formularz mozna zapisac w dowolnym momencie. Postep zostanie zachowany.</span>
</div>
<div class="form-actions-right">
<button type="button" class="btn btn-outline" onclick="saveDraft()">
<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="M8 7H5a2 2 0 00-2 2v9a2 2 0 002 2h14a2 2 0 002-2V9a2 2 0 00-2-2h-3m-1 4l-3 3m0 0l-3-3m3 3V4"/>
</svg>
Zapisz wersje robocza
</button>
<button type="submit" class="btn btn-primary" id="submitBtn">
<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="M5 13l4 4L19 7"/>
</svg>
Zapisz audyt
</button>
</div>
</div>
</form>
<!-- Loading Overlay -->
<div class="loading-overlay" id="loadingOverlay">
<div class="loading-content">
<div class="loading-spinner"></div>
<h3>Zapisywanie audytu...</h3>
<p>Prosze czekac, dane sa przetwarzane.</p>
</div>
</div>
<!-- Info Modal -->
<div class="modal" id="infoModal">
<div class="modal-content">
<div class="modal-header">
<div class="modal-icon success" id="modalIcon">
<svg width="24" height="24" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"/>
</svg>
</div>
<div class="modal-title" id="modalTitle">Informacja</div>
</div>
<div class="modal-body" id="modalBody">
Tresc informacji.
</div>
<div class="modal-footer">
<button class="btn btn-primary" onclick="closeInfoModal()">OK</button>
</div>
</div>
</div>
{% endblock %}
{% block extra_js %}
const csrfToken = '{{ csrf_token() }}';
const companyId = {{ company.id }};
// Track selected values for chip selects
const chipSelections = {
m365Plans: [],
teamsUsage: [],
serverTypes: [],
serverOs: []
};
// Toggle switch functionality
function toggleSwitch(fieldId) {
const toggle = document.getElementById('toggle' + fieldId.charAt(0).toUpperCase() + fieldId.slice(1));
const input = document.getElementById(fieldId);
const label = document.getElementById('label' + fieldId.charAt(0).toUpperCase() + fieldId.slice(1));
const isActive = toggle.classList.toggle('active');
input.value = isActive ? 'true' : 'false';
label.textContent = isActive ? 'Tak' : 'Nie';
// Handle conditional fields visibility
handleConditionalFields(fieldId, isActive);
updateProgress();
}
// Handle conditional fields visibility
function handleConditionalFields(fieldId, isActive) {
const conditionalMappings = {
'itOutsourced': ['itProviderFields'],
'hasAzureAd': ['azureAdFields', 'azureUserCountField'],
'hasM365': ['m365PlansField', 'teamsUsageField'],
'hasLocalAd': ['localAdFields', 'adSyncField']
};
const fieldsToToggle = conditionalMappings[fieldId];
if (fieldsToToggle) {
fieldsToToggle.forEach(fieldGroupId => {
const fieldGroup = document.getElementById(fieldGroupId);
if (fieldGroup) {
if (isActive) {
fieldGroup.classList.remove('hidden');
} else {
fieldGroup.classList.add('hidden');
}
}
});
}
}
// Toggle chip selection
function toggleChip(element, fieldId) {
const value = element.dataset.value;
const isSelected = element.classList.toggle('selected');
if (isSelected) {
if (!chipSelections[fieldId].includes(value)) {
chipSelections[fieldId].push(value);
}
} else {
chipSelections[fieldId] = chipSelections[fieldId].filter(v => v !== value);
}
// Update hidden input
document.getElementById(fieldId).value = chipSelections[fieldId].join(',');
updateProgress();
}
// Toggle section visibility
function toggleSection(sectionNum) {
const header = document.querySelector(`.form-section[data-section="${sectionNum}"] .section-header`);
const content = document.getElementById('sectionContent' + sectionNum);
if (content) {
header.classList.toggle('collapsed');
content.classList.toggle('collapsed');
}
// Update progress dots
updateProgressDots(sectionNum);
}
// Update progress dots when navigating sections
function updateProgressDots(activeSectionNum) {
document.querySelectorAll('.progress-section-dot').forEach(dot => {
const section = parseInt(dot.dataset.section);
if (section === activeSectionNum) {
dot.classList.add('active');
}
});
}
// Calculate and update form progress
function updateProgress() {
const form = document.getElementById('itAuditForm');
const inputs = form.querySelectorAll('input:not([type="hidden"]), select');
let filledCount = 0;
let totalCount = 0;
inputs.forEach(input => {
if (input.offsetParent !== null) { // Check if visible
totalCount++;
if (input.value && input.value.trim() !== '') {
filledCount++;
}
}
});
// Also count toggle switches
document.querySelectorAll('.toggle-switch.active').forEach(() => {
filledCount++;
});
// Count chip selections
Object.values(chipSelections).forEach(selection => {
if (selection.length > 0) {
filledCount++;
}
});
const percentage = totalCount > 0 ? Math.round((filledCount / Math.max(totalCount, 1)) * 100) : 0;
document.getElementById('progressPercentage').textContent = percentage + '%';
document.getElementById('progressFill').style.width = percentage + '%';
}
// Save draft
function saveDraft() {
const form = document.getElementById('itAuditForm');
const formData = new FormData(form);
formData.append('is_draft', 'true');
showLoading();
fetch(form.action, {
method: 'POST',
body: formData
})
.then(response => response.json())
.then(data => {
hideLoading();
if (data.success) {
showInfoModal('Zapisano', 'Wersja robocza audytu zostala zapisana.', true);
} else {
showInfoModal('Blad', data.error || 'Nie udalo sie zapisac wersji roboczej.', false);
}
})
.catch(error => {
hideLoading();
showInfoModal('Blad polaczenia', 'Nie udalo sie polaczyc z serwerem: ' + error.message, false);
});
}
// Form submission
document.getElementById('itAuditForm').addEventListener('submit', function(e) {
e.preventDefault();
const formData = new FormData(this);
showLoading();
fetch(this.action, {
method: 'POST',
body: formData
})
.then(response => response.json())
.then(data => {
hideLoading();
if (data.success) {
showInfoModal('Audyt zapisany', 'Audyt IT zostal zapisany pomyslnie. Za chwile zostaniesz przekierowany.', true);
setTimeout(() => {
window.location.href = data.redirect_url || '/company/' + companyId;
}, 1500);
} else {
showInfoModal('Blad', data.error || 'Nie udalo sie zapisac audytu.', false);
}
})
.catch(error => {
hideLoading();
showInfoModal('Blad polaczenia', 'Nie udalo sie polaczyc z serwerem: ' + error.message, false);
});
});
// Loading overlay
function showLoading() {
document.getElementById('loadingOverlay').classList.add('active');
}
function hideLoading() {
document.getElementById('loadingOverlay').classList.remove('active');
}
// Info modal
function showInfoModal(title, body, isSuccess) {
document.getElementById('modalTitle').textContent = title;
document.getElementById('modalBody').textContent = body;
const icon = document.getElementById('modalIcon');
icon.className = 'modal-icon ' + (isSuccess ? 'success' : 'error');
document.getElementById('infoModal').classList.add('active');
}
function closeInfoModal() {
document.getElementById('infoModal').classList.remove('active');
}
// Close modal on outside click
document.getElementById('infoModal')?.addEventListener('click', (e) => {
if (e.target.id === 'infoModal') closeInfoModal();
});
// Initialize form with existing data if editing
{% if audit %}
function initializeForm() {
// Initialize toggle switches
{% if audit.has_it_manager %}
toggleSwitch('hasItManager');
{% endif %}
{% if audit.it_outsourced %}
toggleSwitch('itOutsourced');
{% endif %}
{% if audit.has_azure_ad %}
toggleSwitch('hasAzureAd');
{% endif %}
{% if audit.has_m365 %}
toggleSwitch('hasM365');
{% endif %}
{% if audit.has_google_workspace %}
toggleSwitch('hasGoogleWorkspace');
{% endif %}
{% if audit.has_local_ad %}
toggleSwitch('hasLocalAd');
{% endif %}
{% if audit.has_ad_azure_sync %}
toggleSwitch('hasAdAzureSync');
{% endif %}
// Initialize chip selections
{% if audit.m365_plans %}
'{{ audit.m365_plans | join(",") }}'.split(',').forEach(value => {
const chip = document.querySelector('#m365PlansChips .chip-option[data-value="' + value + '"]');
if (chip) toggleChip(chip, 'm365Plans');
});
{% endif %}
{% if audit.teams_usage %}
'{{ audit.teams_usage | join(",") }}'.split(',').forEach(value => {
const chip = document.querySelector('#teamsUsageChips .chip-option[data-value="' + value + '"]');
if (chip) toggleChip(chip, 'teamsUsage');
});
{% endif %}
{% if audit.server_types %}
'{{ audit.server_types | join(",") }}'.split(',').forEach(value => {
const chip = document.querySelector('#serverTypesChips .chip-option[data-value="' + value + '"]');
if (chip) toggleChip(chip, 'serverTypes');
});
{% endif %}
{% if audit.server_os %}
'{{ audit.server_os | join(",") }}'.split(',').forEach(value => {
const chip = document.querySelector('#serverOsChips .chip-option[data-value="' + value + '"]');
if (chip) toggleChip(chip, 'serverOs');
});
{% endif %}
updateProgress();
}
document.addEventListener('DOMContentLoaded', initializeForm);
{% else %}
document.addEventListener('DOMContentLoaded', updateProgress);
{% endif %}
// Navigation via progress dots
document.querySelectorAll('.progress-section-dot').forEach(dot => {
dot.addEventListener('click', () => {
const sectionNum = parseInt(dot.dataset.section);
// Close all sections
document.querySelectorAll('.section-content').forEach(content => {
content.classList.add('collapsed');
});
document.querySelectorAll('.section-header').forEach(header => {
header.classList.add('collapsed');
});
// Open target section
const targetContent = document.getElementById('sectionContent' + sectionNum);
const targetHeader = document.querySelector(`.form-section[data-section="${sectionNum}"] .section-header`);
if (targetContent) {
targetContent.classList.remove('collapsed');
targetHeader?.classList.remove('collapsed');
// Scroll to section
document.querySelector(`.form-section[data-section="${sectionNum}"]`).scrollIntoView({
behavior: 'smooth',
block: 'start'
});
}
// Update active dot
document.querySelectorAll('.progress-section-dot').forEach(d => d.classList.remove('active'));
dot.classList.add('active');
});
});
{% endblock %}