Some checks are pending
NordaBiz Tests / Unit & Integration Tests (push) Waiting to run
NordaBiz Tests / E2E Tests (Playwright) (push) Blocked by required conditions
NordaBiz Tests / Smoke Tests (Production) (push) Blocked by required conditions
NordaBiz Tests / Send Failure Notification (push) Blocked by required conditions
After refactoring to blueprints, templates still used bare endpoint names
(e.g., url_for('admin_zopk')) instead of prefixed names (e.g.,
url_for('admin.admin_zopk')). While most worked via backward-compat aliases,
api_zopk_search_news was missing from the alias list causing 500 on /admin/zopk.
Fixed 19 template files and added missing alias for api_zopk_search_news.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1476 lines
57 KiB
HTML
1476 lines
57 KiB
HTML
{% extends "base.html" %}
|
|
|
|
{% block title %}Panel Audytu IT - Norda Biznes Partner{% endblock %}
|
|
|
|
{% block extra_css %}
|
|
<style>
|
|
.admin-header {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
margin-bottom: var(--spacing-xl);
|
|
flex-wrap: wrap;
|
|
gap: var(--spacing-md);
|
|
}
|
|
|
|
.admin-header h1 {
|
|
font-size: var(--font-size-3xl);
|
|
color: var(--text-primary);
|
|
}
|
|
|
|
.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;
|
|
}
|
|
|
|
/* Stats Grid */
|
|
.stats-grid {
|
|
display: grid;
|
|
grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
|
|
gap: var(--spacing-md);
|
|
margin-bottom: var(--spacing-xl);
|
|
}
|
|
|
|
.stat-card {
|
|
background: white;
|
|
padding: var(--spacing-lg);
|
|
border-radius: var(--radius-lg);
|
|
box-shadow: var(--shadow-sm);
|
|
text-align: center;
|
|
}
|
|
|
|
.stat-card.primary { border-top: 4px solid var(--primary); }
|
|
.stat-card.success { border-top: 4px solid var(--success); }
|
|
.stat-card.warning { border-top: 4px solid var(--warning); }
|
|
.stat-card.info { border-top: 4px solid #0284c7; }
|
|
.stat-card.purple { border-top: 4px solid #7c3aed; }
|
|
|
|
.stat-number {
|
|
font-size: var(--font-size-2xl);
|
|
font-weight: 700;
|
|
display: block;
|
|
margin-bottom: var(--spacing-xs);
|
|
}
|
|
|
|
.stat-number.green { color: var(--success); }
|
|
.stat-number.yellow { color: var(--warning); }
|
|
.stat-number.red { color: var(--error); }
|
|
.stat-number.gray { color: var(--secondary); }
|
|
.stat-number.blue { color: #0284c7; }
|
|
.stat-number.purple { color: #7c3aed; }
|
|
|
|
.stat-label {
|
|
color: var(--text-secondary);
|
|
font-size: var(--font-size-sm);
|
|
}
|
|
|
|
.stat-sublabel {
|
|
color: var(--text-secondary);
|
|
font-size: var(--font-size-xs);
|
|
margin-top: var(--spacing-xs);
|
|
}
|
|
|
|
/* Section styling */
|
|
.section {
|
|
background: var(--surface);
|
|
padding: var(--spacing-xl);
|
|
border-radius: var(--radius-lg);
|
|
box-shadow: var(--shadow);
|
|
margin-bottom: var(--spacing-xl);
|
|
}
|
|
|
|
.section h2 {
|
|
font-size: var(--font-size-xl);
|
|
margin-bottom: var(--spacing-lg);
|
|
color: var(--text-primary);
|
|
border-bottom: 2px solid var(--border);
|
|
padding-bottom: var(--spacing-sm);
|
|
}
|
|
|
|
/* Technology Stats Grid */
|
|
.tech-stats-grid {
|
|
display: grid;
|
|
grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
|
|
gap: var(--spacing-lg);
|
|
}
|
|
|
|
.tech-stat-card {
|
|
background: var(--background);
|
|
padding: var(--spacing-md);
|
|
border-radius: var(--radius);
|
|
display: flex;
|
|
align-items: center;
|
|
gap: var(--spacing-md);
|
|
}
|
|
|
|
.tech-stat-icon {
|
|
width: 48px;
|
|
height: 48px;
|
|
border-radius: var(--radius);
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
flex-shrink: 0;
|
|
font-size: var(--font-size-xl);
|
|
}
|
|
|
|
.tech-stat-icon.azure { background: #e8f4fc; color: #0078d4; }
|
|
.tech-stat-icon.m365 { background: #fff3e0; color: #d83b01; }
|
|
.tech-stat-icon.pbs { background: #e8f5e9; color: #2e7d32; }
|
|
.tech-stat-icon.zabbix { background: #fee2e2; color: #dc2626; }
|
|
.tech-stat-icon.security { background: #e0f2fe; color: #0284c7; }
|
|
.tech-stat-icon.backup { background: #f3e8ff; color: #7c3aed; }
|
|
|
|
.tech-stat-content {
|
|
flex: 1;
|
|
}
|
|
|
|
.tech-stat-value {
|
|
font-size: var(--font-size-xl);
|
|
font-weight: 700;
|
|
color: var(--text-primary);
|
|
}
|
|
|
|
.tech-stat-label {
|
|
color: var(--text-secondary);
|
|
font-size: var(--font-size-sm);
|
|
}
|
|
|
|
.tech-stat-bar {
|
|
height: 4px;
|
|
background: var(--border);
|
|
border-radius: 2px;
|
|
margin-top: var(--spacing-xs);
|
|
overflow: hidden;
|
|
}
|
|
|
|
.tech-stat-bar-fill {
|
|
height: 100%;
|
|
border-radius: 2px;
|
|
transition: width 0.5s ease;
|
|
}
|
|
|
|
.tech-stat-bar-fill.azure { background: #0078d4; }
|
|
.tech-stat-bar-fill.m365 { background: #d83b01; }
|
|
.tech-stat-bar-fill.pbs { background: #2e7d32; }
|
|
.tech-stat-bar-fill.zabbix { background: #dc2626; }
|
|
.tech-stat-bar-fill.security { background: #0284c7; }
|
|
.tech-stat-bar-fill.backup { background: #7c3aed; }
|
|
|
|
/* Maturity Distribution */
|
|
.maturity-grid {
|
|
display: grid;
|
|
grid-template-columns: repeat(4, 1fr);
|
|
gap: var(--spacing-md);
|
|
}
|
|
|
|
.maturity-card {
|
|
background: var(--background);
|
|
padding: var(--spacing-md);
|
|
border-radius: var(--radius);
|
|
text-align: center;
|
|
border-left: 4px solid transparent;
|
|
}
|
|
|
|
.maturity-card.basic { border-left-color: var(--error); }
|
|
.maturity-card.developing { border-left-color: var(--warning); }
|
|
.maturity-card.established { border-left-color: #0284c7; }
|
|
.maturity-card.advanced { border-left-color: var(--success); }
|
|
|
|
.maturity-card .stat-number.basic { color: var(--error); }
|
|
.maturity-card .stat-number.developing { color: var(--warning); }
|
|
.maturity-card .stat-number.established { color: #0284c7; }
|
|
.maturity-card .stat-number.advanced { color: var(--success); }
|
|
|
|
.maturity-card .maturity-label {
|
|
font-size: var(--font-size-sm);
|
|
color: var(--text-secondary);
|
|
margin-top: var(--spacing-xs);
|
|
}
|
|
|
|
/* Collaboration Flags */
|
|
.collab-flags-grid {
|
|
display: grid;
|
|
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
|
|
gap: var(--spacing-md);
|
|
}
|
|
|
|
.collab-flag-card {
|
|
background: var(--background);
|
|
padding: var(--spacing-md);
|
|
border-radius: var(--radius);
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
}
|
|
|
|
.collab-flag-info {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: var(--spacing-sm);
|
|
}
|
|
|
|
.collab-flag-icon {
|
|
width: 36px;
|
|
height: 36px;
|
|
border-radius: var(--radius-sm);
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
background: var(--primary-light, #e0f2fe);
|
|
color: var(--primary);
|
|
}
|
|
|
|
.collab-flag-label {
|
|
font-size: var(--font-size-sm);
|
|
color: var(--text-primary);
|
|
}
|
|
|
|
.collab-flag-count {
|
|
font-size: var(--font-size-lg);
|
|
font-weight: 700;
|
|
color: var(--primary);
|
|
}
|
|
|
|
/* Collaboration Matrix */
|
|
.collab-matrix-grid {
|
|
display: grid;
|
|
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
|
|
gap: var(--spacing-lg);
|
|
}
|
|
|
|
.match-type-card {
|
|
background: var(--background);
|
|
border-radius: var(--radius);
|
|
padding: var(--spacing-md);
|
|
}
|
|
|
|
.match-type-card h3 {
|
|
font-size: var(--font-size-base);
|
|
color: var(--text-primary);
|
|
margin-bottom: var(--spacing-sm);
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
}
|
|
|
|
.match-type-card h3 .count {
|
|
background: var(--primary);
|
|
color: white;
|
|
padding: 2px 8px;
|
|
border-radius: var(--radius-sm);
|
|
font-size: var(--font-size-sm);
|
|
}
|
|
|
|
.match-type-icon {
|
|
width: 32px;
|
|
height: 32px;
|
|
border-radius: var(--radius-sm);
|
|
display: inline-flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
margin-right: var(--spacing-xs);
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
.match-type-icon.m365 { background: #fff3e0; color: #d83b01; }
|
|
.match-type-icon.backup { background: #e8f5e9; color: #2e7d32; }
|
|
.match-type-icon.teams { background: #e8f4fc; color: #0078d4; }
|
|
.match-type-icon.monitoring { background: #fee2e2; color: #dc2626; }
|
|
.match-type-icon.purchasing { background: #f3e8ff; color: #7c3aed; }
|
|
.match-type-icon.knowledge { background: #fef3c7; color: #92400e; }
|
|
|
|
.match-pairs-list {
|
|
max-height: 200px;
|
|
overflow-y: auto;
|
|
font-size: var(--font-size-sm);
|
|
}
|
|
|
|
.match-pair {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
padding: var(--spacing-xs) 0;
|
|
border-bottom: 1px solid var(--border);
|
|
}
|
|
|
|
.match-pair:last-child {
|
|
border-bottom: none;
|
|
}
|
|
|
|
.match-pair-companies {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: var(--spacing-xs);
|
|
flex: 1;
|
|
min-width: 0;
|
|
}
|
|
|
|
.match-pair-companies a {
|
|
color: var(--text-secondary);
|
|
text-decoration: none;
|
|
white-space: nowrap;
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
max-width: 120px;
|
|
}
|
|
|
|
.match-pair-companies a:hover {
|
|
color: var(--primary);
|
|
}
|
|
|
|
.match-pair-separator {
|
|
color: var(--text-muted);
|
|
font-size: var(--font-size-xs);
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
.match-status-badge {
|
|
display: inline-block;
|
|
padding: 2px 6px;
|
|
border-radius: var(--radius-sm);
|
|
font-size: var(--font-size-xs);
|
|
font-weight: 500;
|
|
flex-shrink: 0;
|
|
margin-left: var(--spacing-xs);
|
|
}
|
|
|
|
.match-status-badge.suggested {
|
|
background: #e0f2fe;
|
|
color: #0284c7;
|
|
}
|
|
|
|
.match-status-badge.contacted {
|
|
background: #fef3c7;
|
|
color: #92400e;
|
|
}
|
|
|
|
.match-status-badge.active {
|
|
background: #dcfce7;
|
|
color: #166534;
|
|
}
|
|
|
|
.match-status-badge.declined {
|
|
background: #fee2e2;
|
|
color: #991b1b;
|
|
}
|
|
|
|
/* Empty state */
|
|
.empty-state {
|
|
text-align: center;
|
|
padding: var(--spacing-2xl);
|
|
color: var(--text-secondary);
|
|
}
|
|
|
|
.empty-state svg {
|
|
margin-bottom: var(--spacing-md);
|
|
opacity: 0.3;
|
|
}
|
|
|
|
.empty-state h3 {
|
|
color: var(--text-primary);
|
|
margin-bottom: var(--spacing-sm);
|
|
}
|
|
|
|
/* Filters */
|
|
.filters-bar {
|
|
display: flex;
|
|
gap: var(--spacing-md);
|
|
margin-bottom: var(--spacing-lg);
|
|
flex-wrap: wrap;
|
|
align-items: center;
|
|
background: white;
|
|
padding: var(--spacing-md);
|
|
border-radius: var(--radius);
|
|
box-shadow: var(--shadow-sm);
|
|
}
|
|
|
|
.filter-group {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: var(--spacing-sm);
|
|
}
|
|
|
|
.filter-group label {
|
|
font-size: var(--font-size-sm);
|
|
color: var(--text-secondary);
|
|
font-weight: 500;
|
|
}
|
|
|
|
.filter-group select,
|
|
.filter-group input {
|
|
padding: var(--spacing-sm) var(--spacing-md);
|
|
border: 1px solid var(--border);
|
|
border-radius: var(--radius);
|
|
font-size: var(--font-size-sm);
|
|
min-width: 150px;
|
|
}
|
|
|
|
.filter-group select:focus,
|
|
.filter-group input:focus {
|
|
outline: none;
|
|
border-color: var(--primary);
|
|
}
|
|
|
|
/* Table Container */
|
|
.table-container {
|
|
background: var(--surface);
|
|
border-radius: var(--radius-lg);
|
|
box-shadow: var(--shadow);
|
|
overflow: hidden;
|
|
}
|
|
|
|
.audit-table {
|
|
width: 100%;
|
|
border-collapse: collapse;
|
|
}
|
|
|
|
.audit-table th,
|
|
.audit-table td {
|
|
padding: var(--spacing-md);
|
|
text-align: left;
|
|
border-bottom: 1px solid var(--border);
|
|
}
|
|
|
|
.audit-table th {
|
|
background: var(--background);
|
|
font-weight: 600;
|
|
color: var(--text-secondary);
|
|
font-size: var(--font-size-sm);
|
|
text-transform: uppercase;
|
|
cursor: pointer;
|
|
user-select: none;
|
|
white-space: nowrap;
|
|
}
|
|
|
|
.audit-table th:hover {
|
|
background: #e9ecef;
|
|
}
|
|
|
|
.audit-table th .sort-icon {
|
|
display: inline-block;
|
|
margin-left: var(--spacing-xs);
|
|
opacity: 0.3;
|
|
}
|
|
|
|
.audit-table th.sorted .sort-icon {
|
|
opacity: 1;
|
|
}
|
|
|
|
.audit-table th.sorted-asc .sort-icon::after {
|
|
content: '\2191';
|
|
}
|
|
|
|
.audit-table th.sorted-desc .sort-icon::after {
|
|
content: '\2193';
|
|
}
|
|
|
|
.audit-table tbody tr:hover {
|
|
background: var(--background);
|
|
}
|
|
|
|
.company-name-cell {
|
|
font-weight: 500;
|
|
max-width: 250px;
|
|
}
|
|
|
|
.company-name-cell a {
|
|
color: var(--text-primary);
|
|
text-decoration: none;
|
|
}
|
|
|
|
.company-name-cell a:hover {
|
|
color: var(--primary);
|
|
}
|
|
|
|
/* Score Cells */
|
|
.score-cell {
|
|
text-align: center;
|
|
font-weight: 600;
|
|
font-size: var(--font-size-base);
|
|
}
|
|
|
|
.score-badge {
|
|
display: inline-block;
|
|
padding: 4px 12px;
|
|
border-radius: var(--radius-sm);
|
|
min-width: 45px;
|
|
}
|
|
|
|
.score-good {
|
|
background: #dcfce7;
|
|
color: #166534;
|
|
}
|
|
|
|
.score-medium {
|
|
background: #fef3c7;
|
|
color: #92400e;
|
|
}
|
|
|
|
.score-poor {
|
|
background: #fee2e2;
|
|
color: #991b1b;
|
|
}
|
|
|
|
.score-na {
|
|
background: var(--border);
|
|
color: var(--text-secondary);
|
|
font-style: italic;
|
|
font-weight: normal;
|
|
}
|
|
|
|
/* Overall Score - larger */
|
|
.overall-score {
|
|
font-size: var(--font-size-lg);
|
|
font-weight: 700;
|
|
}
|
|
|
|
/* Maturity Badge */
|
|
.maturity-badge {
|
|
display: inline-block;
|
|
padding: 4px 10px;
|
|
border-radius: var(--radius-sm);
|
|
font-size: var(--font-size-sm);
|
|
font-weight: 500;
|
|
}
|
|
|
|
.maturity-badge.basic {
|
|
background: #fee2e2;
|
|
color: #991b1b;
|
|
}
|
|
|
|
.maturity-badge.developing {
|
|
background: #fef3c7;
|
|
color: #92400e;
|
|
}
|
|
|
|
.maturity-badge.established {
|
|
background: #dbeafe;
|
|
color: #1e40af;
|
|
}
|
|
|
|
.maturity-badge.advanced {
|
|
background: #dcfce7;
|
|
color: #166534;
|
|
}
|
|
|
|
/* Tech Icons */
|
|
.tech-icons {
|
|
display: flex;
|
|
gap: var(--spacing-xs);
|
|
flex-wrap: wrap;
|
|
}
|
|
|
|
.tech-icon {
|
|
width: 28px;
|
|
height: 28px;
|
|
border-radius: var(--radius-sm);
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
font-size: 12px;
|
|
font-weight: 600;
|
|
}
|
|
|
|
.tech-icon.azure { background: #e8f4fc; color: #0078d4; }
|
|
.tech-icon.m365 { background: #fff3e0; color: #d83b01; }
|
|
.tech-icon.pbs { background: #e8f5e9; color: #2e7d32; }
|
|
.tech-icon.zabbix { background: #fee2e2; color: #dc2626; }
|
|
.tech-icon.edr { background: #e0f2fe; color: #0284c7; }
|
|
.tech-icon.dr { background: #f3e8ff; color: #7c3aed; }
|
|
.tech-icon.inactive { background: var(--border); color: var(--text-secondary); opacity: 0.4; }
|
|
|
|
/* Action buttons */
|
|
.action-buttons {
|
|
display: flex;
|
|
gap: var(--spacing-xs);
|
|
}
|
|
|
|
.btn-icon {
|
|
width: 32px;
|
|
height: 32px;
|
|
padding: 0;
|
|
display: inline-flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
border-radius: var(--radius);
|
|
border: 1px solid var(--border);
|
|
background: var(--surface);
|
|
cursor: pointer;
|
|
transition: var(--transition);
|
|
text-decoration: none;
|
|
color: var(--text-primary);
|
|
}
|
|
|
|
.btn-icon:hover {
|
|
background: var(--background);
|
|
border-color: var(--primary);
|
|
color: var(--primary);
|
|
}
|
|
|
|
.btn-icon.edit {
|
|
color: var(--primary);
|
|
}
|
|
|
|
.btn-icon.edit:hover {
|
|
background: #e0f2fe;
|
|
border-color: var(--primary);
|
|
}
|
|
|
|
/* Legend */
|
|
.legend {
|
|
display: flex;
|
|
gap: var(--spacing-lg);
|
|
margin-bottom: var(--spacing-md);
|
|
font-size: var(--font-size-sm);
|
|
color: var(--text-secondary);
|
|
flex-wrap: wrap;
|
|
}
|
|
|
|
.legend-item {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: var(--spacing-xs);
|
|
}
|
|
|
|
.legend-dot {
|
|
width: 12px;
|
|
height: 12px;
|
|
border-radius: 2px;
|
|
}
|
|
|
|
.legend-dot.good { background: #dcfce7; border: 1px solid #166534; }
|
|
.legend-dot.medium { background: #fef3c7; border: 1px solid #92400e; }
|
|
.legend-dot.poor { background: #fee2e2; border: 1px solid #991b1b; }
|
|
|
|
/* Responsive */
|
|
@media (max-width: 1200px) {
|
|
.audit-table {
|
|
font-size: var(--font-size-sm);
|
|
}
|
|
|
|
.hide-mobile {
|
|
display: none;
|
|
}
|
|
}
|
|
|
|
@media (max-width: 768px) {
|
|
.maturity-grid {
|
|
grid-template-columns: repeat(2, 1fr);
|
|
}
|
|
|
|
.stats-grid {
|
|
grid-template-columns: 1fr 1fr;
|
|
}
|
|
|
|
.filters-bar {
|
|
flex-direction: column;
|
|
align-items: stretch;
|
|
}
|
|
|
|
.filter-group {
|
|
flex-direction: column;
|
|
align-items: stretch;
|
|
}
|
|
|
|
.filter-group select,
|
|
.filter-group input {
|
|
min-width: 100%;
|
|
}
|
|
}
|
|
|
|
@media (max-width: 480px) {
|
|
.maturity-grid {
|
|
grid-template-columns: 1fr;
|
|
}
|
|
|
|
.stats-grid {
|
|
grid-template-columns: 1fr;
|
|
}
|
|
}
|
|
</style>
|
|
{% endblock %}
|
|
|
|
{% block content %}
|
|
<div class="admin-header">
|
|
<div>
|
|
<h1>Panel Audytu IT</h1>
|
|
<p class="text-muted">Przeglad infrastruktury IT i potencjalu wspolpracy firm czlonkowskich</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="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"/>
|
|
</svg>
|
|
<span>Dane z formularzy Audytu IT wypelnionych przez czlonkow Norda Biznes</span>
|
|
</div>
|
|
</div>
|
|
<div class="header-actions">
|
|
<a href="{{ url_for('it_audit.it_audit_form') }}" 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="M12 4v16m8-8H4"/>
|
|
</svg>
|
|
Nowy audyt
|
|
</a>
|
|
<a href="{{ url_for('it_audit.api_it_audit_export') }}" class="btn btn-outline btn-sm" target="_blank">
|
|
<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="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4"/>
|
|
</svg>
|
|
Eksport
|
|
</a>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Main Stats -->
|
|
<div class="stats-grid">
|
|
<div class="stat-card primary">
|
|
<span class="stat-number">{{ stats.total_audits }}</span>
|
|
<span class="stat-label">Przeprowadzonych audytow</span>
|
|
<span class="stat-sublabel">z {{ stats.total_companies }} firm</span>
|
|
</div>
|
|
<div class="stat-card success">
|
|
<span class="stat-number green">{{ stats.avg_overall_score|default('-', true) }}{% if stats.avg_overall_score %}<small>/100</small>{% endif %}</span>
|
|
<span class="stat-label">Sredni wynik ogolny</span>
|
|
</div>
|
|
<div class="stat-card info">
|
|
<span class="stat-number blue">{{ stats.avg_security_score|default('-', true) }}{% if stats.avg_security_score %}<small>/100</small>{% endif %}</span>
|
|
<span class="stat-label">Sredni wynik bezpieczenstwa</span>
|
|
</div>
|
|
<div class="stat-card purple">
|
|
<span class="stat-number purple">{{ stats.avg_collaboration_score|default('-', true) }}{% if stats.avg_collaboration_score %}<small>/100</small>{% endif %}</span>
|
|
<span class="stat-label">Sredni wynik wspolpracy</span>
|
|
</div>
|
|
<div class="stat-card warning">
|
|
<span class="stat-number yellow">{{ stats.companies_without_audit }}</span>
|
|
<span class="stat-label">Firm bez audytu</span>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Maturity Distribution -->
|
|
<div class="section">
|
|
<h2>Poziom dojrzalosci IT</h2>
|
|
{% if stats.total_audits > 0 %}
|
|
<div class="maturity-grid">
|
|
<div class="maturity-card basic">
|
|
<span class="stat-number basic">{{ stats.maturity_basic }}</span>
|
|
<div class="maturity-label">Podstawowy</div>
|
|
<div class="stat-sublabel">< 40 pkt</div>
|
|
</div>
|
|
<div class="maturity-card developing">
|
|
<span class="stat-number developing">{{ stats.maturity_developing }}</span>
|
|
<div class="maturity-label">Rozwijajacy sie</div>
|
|
<div class="stat-sublabel">40-59 pkt</div>
|
|
</div>
|
|
<div class="maturity-card established">
|
|
<span class="stat-number established">{{ stats.maturity_established }}</span>
|
|
<div class="maturity-label">Ugruntowany</div>
|
|
<div class="stat-sublabel">60-79 pkt</div>
|
|
</div>
|
|
<div class="maturity-card advanced">
|
|
<span class="stat-number advanced">{{ stats.maturity_advanced }}</span>
|
|
<div class="maturity-label">Zaawansowany</div>
|
|
<div class="stat-sublabel">≥ 80 pkt</div>
|
|
</div>
|
|
</div>
|
|
{% else %}
|
|
<div class="empty-state">
|
|
<svg width="64" height="64" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z"/>
|
|
</svg>
|
|
<p>Brak danych o poziomie dojrzalosci. Wypelnij pierwszy audyt IT.</p>
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
|
|
<!-- Technology Stats -->
|
|
<div class="section">
|
|
<h2>Statystyki technologii</h2>
|
|
{% if stats.total_audits > 0 %}
|
|
<div class="tech-stats-grid">
|
|
<div class="tech-stat-card">
|
|
<div class="tech-stat-icon azure">
|
|
<svg width="24" height="24" fill="currentColor" viewBox="0 0 24 24">
|
|
<path d="M5.483 21.3H24L14.025 4.013l-3.038 8.347 5.836 6.938L5.483 21.3zM13.175 2.7L6.388 19.142H0l6.6-10.04 6.575-6.402z"/>
|
|
</svg>
|
|
</div>
|
|
<div class="tech-stat-content">
|
|
<div class="tech-stat-value">{{ stats.has_azure_ad }}</div>
|
|
<div class="tech-stat-label">Azure AD / Entra ID</div>
|
|
<div class="tech-stat-bar">
|
|
<div class="tech-stat-bar-fill azure" style="width: {{ (stats.has_azure_ad / stats.total_audits * 100)|round|int if stats.total_audits > 0 else 0 }}%"></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="tech-stat-card">
|
|
<div class="tech-stat-icon m365">
|
|
<svg width="24" height="24" fill="currentColor" viewBox="0 0 24 24">
|
|
<path d="M11.5 2.25L2.25 6v12l9.25 3.75L21.75 18V6L11.5 2.25zm0 1.5l7.5 3v10.5l-7.5 3-7.5-3V6.75l7.5-3z"/>
|
|
</svg>
|
|
</div>
|
|
<div class="tech-stat-content">
|
|
<div class="tech-stat-value">{{ stats.has_m365 }}</div>
|
|
<div class="tech-stat-label">Microsoft 365</div>
|
|
<div class="tech-stat-bar">
|
|
<div class="tech-stat-bar-fill m365" style="width: {{ (stats.has_m365 / stats.total_audits * 100)|round|int if stats.total_audits > 0 else 0 }}%"></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="tech-stat-card">
|
|
<div class="tech-stat-icon pbs">
|
|
<svg width="24" height="24" fill="currentColor" viewBox="0 0 24 24">
|
|
<path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-2 15l-5-5 1.41-1.41L10 14.17l7.59-7.59L19 8l-9 9z"/>
|
|
</svg>
|
|
</div>
|
|
<div class="tech-stat-content">
|
|
<div class="tech-stat-value">{{ stats.has_proxmox_pbs }}</div>
|
|
<div class="tech-stat-label">Proxmox Backup Server</div>
|
|
<div class="tech-stat-bar">
|
|
<div class="tech-stat-bar-fill pbs" style="width: {{ (stats.has_proxmox_pbs / stats.total_audits * 100)|round|int if stats.total_audits > 0 else 0 }}%"></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="tech-stat-card">
|
|
<div class="tech-stat-icon zabbix">
|
|
<svg width="24" height="24" fill="currentColor" viewBox="0 0 24 24">
|
|
<path d="M3 3v18h18V3H3zm16 16H5V5h14v14zM7 7h2v2H7V7zm4 0h2v2h-2V7zm4 0h2v2h-2V7zm-8 4h2v2H7v-2zm4 0h2v2h-2v-2zm4 0h2v2h-2v-2zm-8 4h2v2H7v-2zm4 0h2v2h-2v-2zm4 0h2v2h-2v-2z"/>
|
|
</svg>
|
|
</div>
|
|
<div class="tech-stat-content">
|
|
<div class="tech-stat-value">{{ stats.has_zabbix }}</div>
|
|
<div class="tech-stat-label">Zabbix Monitoring</div>
|
|
<div class="tech-stat-bar">
|
|
<div class="tech-stat-bar-fill zabbix" style="width: {{ (stats.has_zabbix / stats.total_audits * 100)|round|int if stats.total_audits > 0 else 0 }}%"></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="tech-stat-card">
|
|
<div class="tech-stat-icon security">
|
|
<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="M9 12l2 2 4-4m5.618-4.016A11.955 11.955 0 0112 2.944a11.955 11.955 0 01-8.618 3.04A12.02 12.02 0 003 9c0 5.591 3.824 10.29 9 11.622 5.176-1.332 9-6.03 9-11.622 0-1.042-.133-2.052-.382-3.016z"/>
|
|
</svg>
|
|
</div>
|
|
<div class="tech-stat-content">
|
|
<div class="tech-stat-value">{{ stats.has_edr }}</div>
|
|
<div class="tech-stat-label">EDR / XDR</div>
|
|
<div class="tech-stat-bar">
|
|
<div class="tech-stat-bar-fill security" style="width: {{ (stats.has_edr / stats.total_audits * 100)|round|int if stats.total_audits > 0 else 0 }}%"></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="tech-stat-card">
|
|
<div class="tech-stat-icon backup">
|
|
<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 8h14M5 8a2 2 0 110-4h14a2 2 0 110 4M5 8v10a2 2 0 002 2h10a2 2 0 002-2V8m-9 4h4"/>
|
|
</svg>
|
|
</div>
|
|
<div class="tech-stat-content">
|
|
<div class="tech-stat-value">{{ stats.has_dr_plan }}</div>
|
|
<div class="tech-stat-label">Plan DR</div>
|
|
<div class="tech-stat-bar">
|
|
<div class="tech-stat-bar-fill backup" style="width: {{ (stats.has_dr_plan / stats.total_audits * 100)|round|int if stats.total_audits > 0 else 0 }}%"></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% else %}
|
|
<div class="empty-state">
|
|
<svg width="64" height="64" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" 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>
|
|
<p>Brak danych o technologiach. Wypelnij pierwszy audyt IT.</p>
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
|
|
<!-- Collaboration Flags -->
|
|
<div class="section">
|
|
<h2>Gotowość do wspolpracy</h2>
|
|
{% if stats.total_audits > 0 %}
|
|
<div class="collab-flags-grid">
|
|
<div class="collab-flag-card">
|
|
<div class="collab-flag-info">
|
|
<div class="collab-flag-icon">
|
|
<svg width="18" height="18" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4M7.835 4.697a3.42 3.42 0 001.946-.806 3.42 3.42 0 014.438 0 3.42 3.42 0 001.946.806 3.42 3.42 0 013.138 3.138 3.42 3.42 0 00.806 1.946 3.42 3.42 0 010 4.438 3.42 3.42 0 00-.806 1.946 3.42 3.42 0 01-3.138 3.138 3.42 3.42 0 00-1.946.806 3.42 3.42 0 01-4.438 0 3.42 3.42 0 00-1.946-.806 3.42 3.42 0 01-3.138-3.138 3.42 3.42 0 00-.806-1.946 3.42 3.42 0 010-4.438 3.42 3.42 0 00.806-1.946 3.42 3.42 0 013.138-3.138z"/>
|
|
</svg>
|
|
</div>
|
|
<span class="collab-flag-label">Wspoldzielone licencje</span>
|
|
</div>
|
|
<span class="collab-flag-count">{{ stats.open_to_shared_licensing }}</span>
|
|
</div>
|
|
<div class="collab-flag-card">
|
|
<div class="collab-flag-info">
|
|
<div class="collab-flag-icon">
|
|
<svg width="18" height="18" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 7v10c0 2.21 3.582 4 8 4s8-1.79 8-4V7M4 7c0 2.21 3.582 4 8 4s8-1.79 8-4M4 7c0-2.21 3.582-4 8-4s8 1.79 8 4m0 5c0 2.21-3.582 4-8 4s-8-1.79-8-4"/>
|
|
</svg>
|
|
</div>
|
|
<span class="collab-flag-label">Replikacja backupow</span>
|
|
</div>
|
|
<span class="collab-flag-count">{{ stats.open_to_backup_replication }}</span>
|
|
</div>
|
|
<div class="collab-flag-card">
|
|
<div class="collab-flag-info">
|
|
<div class="collab-flag-icon">
|
|
<svg width="18" height="18" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0zm6 3a2 2 0 11-4 0 2 2 0 014 0zM7 10a2 2 0 11-4 0 2 2 0 014 0z"/>
|
|
</svg>
|
|
</div>
|
|
<span class="collab-flag-label">Federacja Teams</span>
|
|
</div>
|
|
<span class="collab-flag-count">{{ stats.open_to_teams_federation }}</span>
|
|
</div>
|
|
<div class="collab-flag-card">
|
|
<div class="collab-flag-info">
|
|
<div class="collab-flag-icon">
|
|
<svg width="18" height="18" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z"/>
|
|
</svg>
|
|
</div>
|
|
<span class="collab-flag-label">Wspolny monitoring</span>
|
|
</div>
|
|
<span class="collab-flag-count">{{ stats.open_to_shared_monitoring }}</span>
|
|
</div>
|
|
<div class="collab-flag-card">
|
|
<div class="collab-flag-info">
|
|
<div class="collab-flag-icon">
|
|
<svg width="18" height="18" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 3h2l.4 2M7 13h10l4-8H5.4M7 13L5.4 5M7 13l-2.293 2.293c-.63.63-.184 1.707.707 1.707H17m0 0a2 2 0 100 4 2 2 0 000-4zm-8 2a2 2 0 11-4 0 2 2 0 014 0z"/>
|
|
</svg>
|
|
</div>
|
|
<span class="collab-flag-label">Zakupy grupowe</span>
|
|
</div>
|
|
<span class="collab-flag-count">{{ stats.open_to_collective_purchasing }}</span>
|
|
</div>
|
|
<div class="collab-flag-card">
|
|
<div class="collab-flag-info">
|
|
<div class="collab-flag-icon">
|
|
<svg width="18" height="18" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6.253v13m0-13C10.832 5.477 9.246 5 7.5 5S4.168 5.477 3 6.253v13C4.168 18.477 5.754 18 7.5 18s3.332.477 4.5 1.253m0-13C13.168 5.477 14.754 5 16.5 5c1.747 0 3.332.477 4.5 1.253v13C19.832 18.477 18.247 18 16.5 18c-1.746 0-3.332.477-4.5 1.253"/>
|
|
</svg>
|
|
</div>
|
|
<span class="collab-flag-label">Wymiana wiedzy</span>
|
|
</div>
|
|
<span class="collab-flag-count">{{ stats.open_to_knowledge_sharing }}</span>
|
|
</div>
|
|
</div>
|
|
{% else %}
|
|
<div class="empty-state">
|
|
<svg width="64" height="64" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0z"/>
|
|
</svg>
|
|
<p>Brak danych o gotowosci do wspolpracy. Wypelnij pierwszy audyt IT.</p>
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
|
|
<!-- Collaboration Matrix -->
|
|
<div class="section">
|
|
<h2>Macierz wspolpracy</h2>
|
|
{% if collaboration_matches %}
|
|
<div class="collab-matrix-grid">
|
|
<!-- Shared M365 Licensing -->
|
|
{% set m365_matches = collaboration_matches|selectattr('match_type', 'equalto', 'shared_licensing')|list %}
|
|
{% if m365_matches %}
|
|
<div class="match-type-card">
|
|
<h3>
|
|
<span>
|
|
<span class="match-type-icon m365">
|
|
<svg width="16" height="16" fill="currentColor" viewBox="0 0 24 24">
|
|
<path d="M11.5 2.25L2.25 6v12l9.25 3.75L21.75 18V6L11.5 2.25z"/>
|
|
</svg>
|
|
</span>
|
|
Wspoldzielone licencje M365
|
|
</span>
|
|
<span class="count">{{ m365_matches|length }}</span>
|
|
</h3>
|
|
<div class="match-pairs-list">
|
|
{% for match in m365_matches %}
|
|
<div class="match-pair">
|
|
<div class="match-pair-companies">
|
|
<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_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>
|
|
{% endfor %}
|
|
</div>
|
|
</div>
|
|
{% endif %}
|
|
|
|
<!-- Backup Replication -->
|
|
{% set backup_matches = collaboration_matches|selectattr('match_type', 'equalto', 'backup_replication')|list %}
|
|
{% if backup_matches %}
|
|
<div class="match-type-card">
|
|
<h3>
|
|
<span>
|
|
<span class="match-type-icon backup">
|
|
<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="M4 7v10c0 2.21 3.582 4 8 4s8-1.79 8-4V7M4 7c0 2.21 3.582 4 8 4s8-1.79 8-4M4 7c0-2.21 3.582-4 8-4s8 1.79 8 4"/>
|
|
</svg>
|
|
</span>
|
|
Replikacja backupow
|
|
</span>
|
|
<span class="count">{{ backup_matches|length }}</span>
|
|
</h3>
|
|
<div class="match-pairs-list">
|
|
{% for match in backup_matches %}
|
|
<div class="match-pair">
|
|
<div class="match-pair-companies">
|
|
<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_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>
|
|
{% endfor %}
|
|
</div>
|
|
</div>
|
|
{% endif %}
|
|
|
|
<!-- Teams Federation -->
|
|
{% set teams_matches = collaboration_matches|selectattr('match_type', 'equalto', 'teams_federation')|list %}
|
|
{% if teams_matches %}
|
|
<div class="match-type-card">
|
|
<h3>
|
|
<span>
|
|
<span class="match-type-icon teams">
|
|
<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="M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0z"/>
|
|
</svg>
|
|
</span>
|
|
Federacja Teams
|
|
</span>
|
|
<span class="count">{{ teams_matches|length }}</span>
|
|
</h3>
|
|
<div class="match-pairs-list">
|
|
{% for match in teams_matches %}
|
|
<div class="match-pair">
|
|
<div class="match-pair-companies">
|
|
<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_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>
|
|
{% endfor %}
|
|
</div>
|
|
</div>
|
|
{% endif %}
|
|
|
|
<!-- Shared Monitoring -->
|
|
{% set monitoring_matches = collaboration_matches|selectattr('match_type', 'equalto', 'shared_monitoring')|list %}
|
|
{% if monitoring_matches %}
|
|
<div class="match-type-card">
|
|
<h3>
|
|
<span>
|
|
<span class="match-type-icon monitoring">
|
|
<svg width="16" height="16" fill="currentColor" viewBox="0 0 24 24">
|
|
<path d="M3 3v18h18V3H3zm16 16H5V5h14v14zM7 7h2v2H7V7zm4 0h2v2h-2V7zm4 0h2v2h-2V7z"/>
|
|
</svg>
|
|
</span>
|
|
Wspolny monitoring
|
|
</span>
|
|
<span class="count">{{ monitoring_matches|length }}</span>
|
|
</h3>
|
|
<div class="match-pairs-list">
|
|
{% for match in monitoring_matches %}
|
|
<div class="match-pair">
|
|
<div class="match-pair-companies">
|
|
<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_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>
|
|
{% endfor %}
|
|
</div>
|
|
</div>
|
|
{% endif %}
|
|
|
|
<!-- Collective Purchasing -->
|
|
{% set purchasing_matches = collaboration_matches|selectattr('match_type', 'equalto', 'collective_purchasing')|list %}
|
|
{% if purchasing_matches %}
|
|
<div class="match-type-card">
|
|
<h3>
|
|
<span>
|
|
<span class="match-type-icon purchasing">
|
|
<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="M3 3h2l.4 2M7 13h10l4-8H5.4M7 13L5.4 5M7 13l-2.293 2.293c-.63.63-.184 1.707.707 1.707H17m0 0a2 2 0 100 4 2 2 0 000-4zm-8 2a2 2 0 11-4 0 2 2 0 014 0z"/>
|
|
</svg>
|
|
</span>
|
|
Zakupy grupowe
|
|
</span>
|
|
<span class="count">{{ purchasing_matches|length }}</span>
|
|
</h3>
|
|
<div class="match-pairs-list">
|
|
{% for match in purchasing_matches %}
|
|
<div class="match-pair">
|
|
<div class="match-pair-companies">
|
|
<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_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>
|
|
{% endfor %}
|
|
</div>
|
|
</div>
|
|
{% endif %}
|
|
|
|
<!-- Knowledge Sharing -->
|
|
{% set knowledge_matches = collaboration_matches|selectattr('match_type', 'equalto', 'knowledge_sharing')|list %}
|
|
{% if knowledge_matches %}
|
|
<div class="match-type-card">
|
|
<h3>
|
|
<span>
|
|
<span class="match-type-icon knowledge">
|
|
<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="M12 6.253v13m0-13C10.832 5.477 9.246 5 7.5 5S4.168 5.477 3 6.253v13C4.168 18.477 5.754 18 7.5 18s3.332.477 4.5 1.253m0-13C13.168 5.477 14.754 5 16.5 5c1.747 0 3.332.477 4.5 1.253v13C19.832 18.477 18.247 18 16.5 18c-1.746 0-3.332.477-4.5 1.253"/>
|
|
</svg>
|
|
</span>
|
|
Wymiana wiedzy
|
|
</span>
|
|
<span class="count">{{ knowledge_matches|length }}</span>
|
|
</h3>
|
|
<div class="match-pairs-list">
|
|
{% for match in knowledge_matches %}
|
|
<div class="match-pair">
|
|
<div class="match-pair-companies">
|
|
<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_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>
|
|
{% endfor %}
|
|
</div>
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
{% else %}
|
|
<div class="empty-state">
|
|
<svg width="64" height="64" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M13.828 10.172a4 4 0 00-5.656 0l-4 4a4 4 0 105.656 5.656l1.102-1.101m-.758-4.899a4 4 0 005.656 0l4-4a4 4 0 00-5.656-5.656l-1.1 1.1"/>
|
|
</svg>
|
|
<h3>Brak dopasowan wspolpracy</h3>
|
|
<p>Dopasowania pojawia sie automatycznie, gdy firmy z podobna infrastruktura IT wypelnia audyty.</p>
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
|
|
<!-- Company Audit Table -->
|
|
<div class="section">
|
|
<h2>Lista firm</h2>
|
|
|
|
<!-- Filters -->
|
|
<div class="filters-bar">
|
|
<div class="filter-group">
|
|
<label for="filterMaturity">Poziom dojrzalosci:</label>
|
|
<select id="filterMaturity" onchange="applyFilters()">
|
|
<option value="">Wszystkie</option>
|
|
<option value="basic">Podstawowy</option>
|
|
<option value="developing">Rozwijajacy sie</option>
|
|
<option value="established">Ugruntowany</option>
|
|
<option value="advanced">Zaawansowany</option>
|
|
</select>
|
|
</div>
|
|
<div class="filter-group">
|
|
<label for="filterScore">Wynik ogolny:</label>
|
|
<select id="filterScore" onchange="applyFilters()">
|
|
<option value="">Wszystkie</option>
|
|
<option value="good">Dobry (80-100)</option>
|
|
<option value="medium">Sredni (40-79)</option>
|
|
<option value="poor">Slaby (0-39)</option>
|
|
<option value="none">Bez audytu</option>
|
|
</select>
|
|
</div>
|
|
<div class="filter-group">
|
|
<label for="filterSearch">Szukaj:</label>
|
|
<input type="text" id="filterSearch" placeholder="Nazwa firmy..." oninput="applyFilters()">
|
|
</div>
|
|
<div class="filter-group" style="margin-left: auto;">
|
|
<button class="btn btn-sm btn-outline" onclick="resetFilters()">Resetuj filtry</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Legend -->
|
|
<div class="legend">
|
|
<div class="legend-item">
|
|
<div class="legend-dot good"></div>
|
|
<span>80-100 (dobry)</span>
|
|
</div>
|
|
<div class="legend-item">
|
|
<div class="legend-dot medium"></div>
|
|
<span>40-79 (sredni)</span>
|
|
</div>
|
|
<div class="legend-item">
|
|
<div class="legend-dot poor"></div>
|
|
<span>0-39 (slaby)</span>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Table -->
|
|
{% if companies %}
|
|
<div class="table-container">
|
|
<table class="audit-table" id="auditTable">
|
|
<thead>
|
|
<tr>
|
|
<th data-sort="name">
|
|
Firma <span class="sort-icon"></span>
|
|
</th>
|
|
<th data-sort="overall" class="sorted sorted-desc">
|
|
Wynik ogolny <span class="sort-icon"></span>
|
|
</th>
|
|
<th data-sort="security" class="hide-mobile">
|
|
Bezpieczenstwo <span class="sort-icon"></span>
|
|
</th>
|
|
<th data-sort="collaboration" class="hide-mobile">
|
|
Wspolpraca <span class="sort-icon"></span>
|
|
</th>
|
|
<th data-sort="maturity">
|
|
Dojrzalosc <span class="sort-icon"></span>
|
|
</th>
|
|
<th class="hide-mobile">
|
|
Technologie
|
|
</th>
|
|
<th>Akcje</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody id="auditTableBody">
|
|
{% for company in companies %}
|
|
{# 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 has_audit %}
|
|
{% if company.overall_score < 40 %}
|
|
{% set maturity_level = 'basic' %}
|
|
{% elif company.overall_score < 60 %}
|
|
{% set maturity_level = 'developing' %}
|
|
{% elif company.overall_score < 80 %}
|
|
{% set maturity_level = 'established' %}
|
|
{% else %}
|
|
{% set maturity_level = 'advanced' %}
|
|
{% endif %}
|
|
{% endif %}
|
|
<tr data-name="{{ company.name|lower }}"
|
|
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_by_slug', slug=company.slug) }}">{{ company.name }}</a>
|
|
</td>
|
|
<td class="score-cell">
|
|
{% 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 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 has_audit %}
|
|
<span class="maturity-badge {{ maturity_level }}">
|
|
{% if maturity_level == 'basic' %}Podstawowy
|
|
{% 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 audytu</span>
|
|
{% endif %}
|
|
</td>
|
|
<td class="hide-mobile">
|
|
{% if has_audit %}
|
|
<div class="tech-icons">
|
|
<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>
|
|
{% endif %}
|
|
</td>
|
|
<td>
|
|
<div class="action-buttons">
|
|
<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"/>
|
|
</svg>
|
|
</a>
|
|
{% if current_user.can_access_admin_panel() %}
|
|
<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 %}
|
|
</div>
|
|
</td>
|
|
</tr>
|
|
{% endfor %}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
{% else %}
|
|
<div class="empty-state">
|
|
<svg width="80" height="80" viewBox="0 0 80 80" fill="none" opacity="0.3">
|
|
<circle cx="40" cy="40" r="30" stroke="currentColor" stroke-width="3"/>
|
|
<path d="M30 40h20M40 30v20" stroke="currentColor" stroke-width="3" stroke-linecap="round"/>
|
|
</svg>
|
|
<h3>Brak firm do wyswietlenia</h3>
|
|
<p>Nie znaleziono firm z danymi audytu IT.</p>
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
{% endblock %}
|
|
|
|
{% block extra_js %}
|
|
// Sorting state
|
|
let currentSort = { column: 'overall', direction: 'desc' };
|
|
|
|
// Sort table
|
|
function sortTable(column) {
|
|
const tbody = document.getElementById('auditTableBody');
|
|
if (!tbody) return;
|
|
|
|
const rows = Array.from(tbody.querySelectorAll('tr'));
|
|
const headers = document.querySelectorAll('.audit-table th[data-sort]');
|
|
|
|
// Toggle direction if same column
|
|
if (currentSort.column === column) {
|
|
currentSort.direction = currentSort.direction === 'asc' ? 'desc' : 'asc';
|
|
} else {
|
|
currentSort.column = column;
|
|
currentSort.direction = 'desc';
|
|
}
|
|
|
|
// Update header classes
|
|
headers.forEach(h => {
|
|
h.classList.remove('sorted', 'sorted-asc', 'sorted-desc');
|
|
if (h.dataset.sort === column) {
|
|
h.classList.add('sorted', 'sorted-' + currentSort.direction);
|
|
}
|
|
});
|
|
|
|
// Maturity order for sorting
|
|
const maturityOrder = { 'none': 0, 'basic': 1, 'developing': 2, 'established': 3, 'advanced': 4 };
|
|
|
|
// Sort rows
|
|
rows.sort((a, b) => {
|
|
let aVal, bVal;
|
|
|
|
if (column === 'name') {
|
|
aVal = a.dataset.name || '';
|
|
bVal = b.dataset.name || '';
|
|
} else if (column === 'maturity') {
|
|
aVal = maturityOrder[a.dataset.maturity] || 0;
|
|
bVal = maturityOrder[b.dataset.maturity] || 0;
|
|
} else {
|
|
aVal = parseFloat(a.dataset[column]) || -1;
|
|
bVal = parseFloat(b.dataset[column]) || -1;
|
|
}
|
|
|
|
if (aVal < bVal) return currentSort.direction === 'asc' ? -1 : 1;
|
|
if (aVal > bVal) return currentSort.direction === 'asc' ? 1 : -1;
|
|
return 0;
|
|
});
|
|
|
|
// Re-append rows
|
|
rows.forEach(row => tbody.appendChild(row));
|
|
}
|
|
|
|
// Setup sorting click handlers
|
|
document.querySelectorAll('.audit-table th[data-sort]').forEach(th => {
|
|
th.addEventListener('click', () => sortTable(th.dataset.sort));
|
|
});
|
|
|
|
// Filtering
|
|
function applyFilters() {
|
|
const maturity = document.getElementById('filterMaturity').value;
|
|
const score = document.getElementById('filterScore').value;
|
|
const search = document.getElementById('filterSearch').value.toLowerCase();
|
|
|
|
const rows = document.querySelectorAll('#auditTableBody tr');
|
|
|
|
rows.forEach(row => {
|
|
let show = true;
|
|
|
|
// Maturity filter
|
|
if (maturity && row.dataset.maturity !== maturity) {
|
|
show = false;
|
|
}
|
|
|
|
// Score filter
|
|
if (score && show) {
|
|
const overallScore = parseFloat(row.dataset.overall);
|
|
if (score === 'good' && (overallScore < 80 || overallScore < 0)) show = false;
|
|
else if (score === 'medium' && (overallScore < 40 || overallScore >= 80)) show = false;
|
|
else if (score === 'poor' && (overallScore < 0 || overallScore >= 40)) show = false;
|
|
else if (score === 'none' && overallScore >= 0) show = false;
|
|
}
|
|
|
|
// Search filter
|
|
if (search && show) {
|
|
if (!row.dataset.name.includes(search)) {
|
|
show = false;
|
|
}
|
|
}
|
|
|
|
row.style.display = show ? '' : 'none';
|
|
});
|
|
}
|
|
|
|
function resetFilters() {
|
|
document.getElementById('filterMaturity').value = '';
|
|
document.getElementById('filterScore').value = '';
|
|
document.getElementById('filterSearch').value = '';
|
|
applyFilters();
|
|
}
|
|
{% endblock %}
|