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
- HIGH: Fix SQL injection in ZOPK knowledge service (3 functions) — replace f-strings with parameterized queries - MEDIUM: Sanitize tsquery/LIKE input in SearchService to prevent injection - MEDIUM: Add @login_required + @role_required(ADMIN) to /health/full endpoint - MEDIUM: Add @role_required(ADMIN) to ZOPK knowledge search API - MEDIUM: Add bleach HTML sanitization on write for announcements, events, board proceedings (stored XSS via |safe) - MEDIUM: Remove partial API key from Gemini service logs - MEDIUM: Remove @csrf.exempt from chat endpoints, add X-CSRFToken headers in JS - MEDIUM: Add missing CSRF tokens to 3 POST forms (data_request, benefits_form, benefits_list) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
330 lines
10 KiB
HTML
330 lines
10 KiB
HTML
{% extends "base.html" %}
|
|
|
|
{% block title %}Korzyści Członkowskie - Admin{% endblock %}
|
|
|
|
{% block extra_css %}
|
|
<style>
|
|
.admin-header {
|
|
margin-bottom: var(--spacing-xl);
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
flex-wrap: wrap;
|
|
gap: var(--spacing-md);
|
|
}
|
|
|
|
.admin-header h1 {
|
|
font-size: var(--font-size-3xl);
|
|
color: var(--text-primary);
|
|
}
|
|
|
|
.stats-row {
|
|
display: flex;
|
|
gap: var(--spacing-lg);
|
|
margin-bottom: var(--spacing-xl);
|
|
}
|
|
|
|
.stat-card {
|
|
background: var(--surface);
|
|
padding: var(--spacing-lg);
|
|
border-radius: var(--radius-lg);
|
|
box-shadow: var(--shadow);
|
|
min-width: 150px;
|
|
}
|
|
|
|
.stat-value {
|
|
font-size: var(--font-size-2xl);
|
|
font-weight: 700;
|
|
color: var(--text-primary);
|
|
}
|
|
|
|
.stat-label {
|
|
font-size: var(--font-size-sm);
|
|
color: var(--text-secondary);
|
|
}
|
|
|
|
.section {
|
|
background: var(--surface);
|
|
padding: var(--spacing-xl);
|
|
border-radius: var(--radius-lg);
|
|
box-shadow: var(--shadow);
|
|
}
|
|
|
|
.benefits-table {
|
|
width: 100%;
|
|
border-collapse: collapse;
|
|
}
|
|
|
|
.benefits-table th,
|
|
.benefits-table td {
|
|
padding: var(--spacing-md);
|
|
text-align: left;
|
|
border-bottom: 1px solid var(--border);
|
|
}
|
|
|
|
.benefits-table th {
|
|
font-weight: 600;
|
|
color: var(--text-secondary);
|
|
font-size: var(--font-size-sm);
|
|
text-transform: uppercase;
|
|
background: var(--background);
|
|
}
|
|
|
|
.benefits-table tr:hover {
|
|
background: var(--background);
|
|
}
|
|
|
|
.benefit-name-cell {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: var(--spacing-sm);
|
|
}
|
|
|
|
.benefit-logo-small {
|
|
width: 32px;
|
|
height: 32px;
|
|
border-radius: var(--radius-sm);
|
|
object-fit: contain;
|
|
background: #f8fafc;
|
|
}
|
|
|
|
.benefit-logo-placeholder-small {
|
|
width: 32px;
|
|
height: 32px;
|
|
border-radius: var(--radius-sm);
|
|
background: linear-gradient(135deg, var(--primary-light) 0%, var(--primary) 100%);
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
color: white;
|
|
font-weight: 700;
|
|
font-size: var(--font-size-sm);
|
|
}
|
|
|
|
.status-badge {
|
|
display: inline-block;
|
|
padding: var(--spacing-xs) var(--spacing-sm);
|
|
border-radius: var(--radius-full);
|
|
font-size: var(--font-size-xs);
|
|
font-weight: 600;
|
|
}
|
|
|
|
.status-active { background: #dcfce7; color: #16a34a; }
|
|
.status-inactive { background: #f3f4f6; color: #6b7280; }
|
|
.status-featured { background: #dbeafe; color: #2563eb; }
|
|
|
|
.category-badge {
|
|
display: inline-block;
|
|
padding: var(--spacing-xs) var(--spacing-sm);
|
|
border-radius: var(--radius-sm);
|
|
font-size: var(--font-size-xs);
|
|
font-weight: 500;
|
|
background: var(--primary-bg);
|
|
color: var(--primary);
|
|
}
|
|
|
|
.actions-cell {
|
|
display: flex;
|
|
gap: var(--spacing-xs);
|
|
}
|
|
|
|
.btn-small {
|
|
padding: var(--spacing-xs) var(--spacing-sm);
|
|
font-size: var(--font-size-xs);
|
|
border-radius: var(--radius-sm);
|
|
cursor: pointer;
|
|
text-decoration: none;
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 4px;
|
|
}
|
|
|
|
.btn-edit {
|
|
background: #f0f9ff;
|
|
color: #0284c7;
|
|
border: 1px solid #bae6fd;
|
|
}
|
|
|
|
.btn-edit:hover {
|
|
background: #e0f2fe;
|
|
}
|
|
|
|
.btn-toggle {
|
|
background: #fef3c7;
|
|
color: #d97706;
|
|
border: 1px solid #fde68a;
|
|
}
|
|
|
|
.btn-toggle:hover {
|
|
background: #fef08a;
|
|
}
|
|
|
|
.btn-delete {
|
|
background: #fee2e2;
|
|
color: #dc2626;
|
|
border: 1px solid #fecaca;
|
|
}
|
|
|
|
.btn-delete:hover {
|
|
background: #fecaca;
|
|
}
|
|
|
|
.btn-primary {
|
|
background: var(--primary);
|
|
color: white;
|
|
border: none;
|
|
padding: var(--spacing-sm) var(--spacing-md);
|
|
border-radius: var(--radius-md);
|
|
font-weight: 600;
|
|
cursor: pointer;
|
|
text-decoration: none;
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: var(--spacing-xs);
|
|
}
|
|
|
|
.btn-primary:hover {
|
|
background: var(--primary-dark);
|
|
color: white;
|
|
}
|
|
|
|
.empty-state {
|
|
text-align: center;
|
|
padding: var(--spacing-3xl);
|
|
color: var(--text-secondary);
|
|
}
|
|
|
|
.private-column {
|
|
background: #fef3c7;
|
|
}
|
|
|
|
.private-indicator {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 4px;
|
|
font-size: var(--font-size-xs);
|
|
color: #92400e;
|
|
margin-left: 4px;
|
|
}
|
|
|
|
.private-indicator svg {
|
|
width: 12px;
|
|
height: 12px;
|
|
}
|
|
</style>
|
|
{% endblock %}
|
|
|
|
{% block content %}
|
|
<div class="container">
|
|
<div class="admin-header">
|
|
<h1>Korzyści Członkowskie</h1>
|
|
<a href="{{ url_for('admin.admin_benefits_new') }}" class="btn-primary">
|
|
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
<line x1="12" y1="5" x2="12" y2="19"></line>
|
|
<line x1="5" y1="12" x2="19" y2="12"></line>
|
|
</svg>
|
|
Dodaj korzyść
|
|
</a>
|
|
</div>
|
|
|
|
<div class="stats-row">
|
|
<div class="stat-card">
|
|
<div class="stat-value">{{ benefits|length }}</div>
|
|
<div class="stat-label">Wszystkich</div>
|
|
</div>
|
|
<div class="stat-card">
|
|
<div class="stat-value">{{ active_count }}</div>
|
|
<div class="stat-label">Aktywnych</div>
|
|
</div>
|
|
<div class="stat-card">
|
|
<div class="stat-value">{{ total_clicks }}</div>
|
|
<div class="stat-label">Kliknięć</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="section">
|
|
{% if benefits %}
|
|
<table class="benefits-table">
|
|
<thead>
|
|
<tr>
|
|
<th>Nazwa</th>
|
|
<th>Kategoria</th>
|
|
{% if current_user.email == 'maciej.pienczyn@inpi.pl' %}
|
|
<th class="private-column">
|
|
Prowizja
|
|
<span class="private-indicator" title="Widoczne tylko dla Ciebie">
|
|
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z"/>
|
|
</svg>
|
|
</span>
|
|
</th>
|
|
{% endif %}
|
|
<th>Kliknięcia</th>
|
|
<th>Status</th>
|
|
<th>Akcje</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{% for benefit in benefits %}
|
|
<tr>
|
|
<td>
|
|
<div class="benefit-name-cell">
|
|
{% if benefit.logo_url %}
|
|
<img src="{{ benefit.logo_url }}" alt="" class="benefit-logo-small">
|
|
{% else %}
|
|
<div class="benefit-logo-placeholder-small">{{ benefit.name[0] }}</div>
|
|
{% endif %}
|
|
<div>
|
|
<strong>{{ benefit.name }}</strong>
|
|
{% if benefit.is_featured %}
|
|
<span class="status-badge status-featured">Polecane</span>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
</td>
|
|
<td>
|
|
<span class="category-badge">{{ benefit.category_label }}</span>
|
|
</td>
|
|
{% if current_user.email == 'maciej.pienczyn@inpi.pl' %}
|
|
<td class="private-column">
|
|
{% if benefit.commission_rate %}
|
|
{{ benefit.commission_rate }}
|
|
{% if benefit.commission_duration %}<br><small>przez {{ benefit.commission_duration }}</small>{% endif %}
|
|
{% else %}
|
|
-
|
|
{% endif %}
|
|
</td>
|
|
{% endif %}
|
|
<td>{{ benefit.click_count or 0 }}</td>
|
|
<td>
|
|
{% if benefit.is_active %}
|
|
<span class="status-badge status-active">Aktywna</span>
|
|
{% else %}
|
|
<span class="status-badge status-inactive">Nieaktywna</span>
|
|
{% endif %}
|
|
</td>
|
|
<td>
|
|
<div class="actions-cell">
|
|
<a href="{{ url_for('admin.admin_benefits_edit', benefit_id=benefit.id) }}" class="btn-small btn-edit">Edytuj</a>
|
|
<form action="{{ url_for('admin.admin_benefits_toggle', benefit_id=benefit.id) }}" method="POST" style="display:inline;">
|
|
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
|
|
<button type="submit" class="btn-small btn-toggle">
|
|
{% if benefit.is_active %}Wyłącz{% else %}Włącz{% endif %}
|
|
</button>
|
|
</form>
|
|
<a href="{{ url_for('admin.admin_benefits_clicks', benefit_id=benefit.id) }}" class="btn-small btn-edit">Kliknięcia</a>
|
|
</div>
|
|
</td>
|
|
</tr>
|
|
{% endfor %}
|
|
</tbody>
|
|
</table>
|
|
{% else %}
|
|
<div class="empty-state">
|
|
<p>Brak korzyści. <a href="{{ url_for('admin.admin_benefits_new') }}">Dodaj pierwszą</a></p>
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
{% endblock %}
|