nowe funkcje (zrealizowane) i poprawki w detekcji ip

This commit is contained in:
Mateusz Gruszczyński 2025-03-26 11:04:21 +01:00
parent 60754ec23b
commit b3c9aa1586
8 changed files with 151 additions and 35 deletions

View File

@ -1 +1,3 @@
ALTER TABLE global_settings ADD COLUMN allowed_login_hosts TEXT; ALTER TABLE global_settings ADD COLUMN allowed_login_hosts TEXT;
ALTER TABLE zbiorka ADD COLUMN zrealizowana BOOLEAN DEFAULT 0;

44
app.py
View File

@ -43,6 +43,7 @@ class Zbiorka(db.Model):
ukryta = db.Column(db.Boolean, default=False) ukryta = db.Column(db.Boolean, default=False)
ukryj_kwote = db.Column(db.Boolean, default=False) ukryj_kwote = db.Column(db.Boolean, default=False)
wplaty = db.relationship('Wplata', backref='zbiorka', lazy=True, order_by='Wplata.data.desc()') wplaty = db.relationship('Wplata', backref='zbiorka', lazy=True, order_by='Wplata.data.desc()')
zrealizowana = db.Column(db.Boolean, default=False)
class Wplata(db.Model): class Wplata(db.Model):
id = db.Column(db.Integer, primary_key=True) id = db.Column(db.Integer, primary_key=True)
@ -91,7 +92,12 @@ def markdown_filter(text):
@app.route('/') @app.route('/')
def index(): def index():
zbiorki = Zbiorka.query.filter_by(ukryta=False).all() zbiorki = Zbiorka.query.filter_by(ukryta=False, zrealizowana=False).all()
return render_template('index.html', zbiorki=zbiorki)
@app.route('/zbiorki_zrealizowane')
def zbiorki_zrealizowane():
zbiorki = Zbiorka.query.filter_by(zrealizowana=True).all()
return render_template('index.html', zbiorki=zbiorki) return render_template('index.html', zbiorki=zbiorki)
@app.errorhandler(404) @app.errorhandler(404)
@ -106,6 +112,21 @@ def zbiorka(zbiorka_id):
abort(404) abort(404)
return render_template('zbiorka.html', zbiorka=zb) return render_template('zbiorka.html', zbiorka=zb)
def get_real_ip():
# Sprawdź, czy żądanie pochodzi przez Cloudflare
if "CF-Connecting-IP" in request.headers:
return request.headers.get("CF-Connecting-IP")
# Następnie sprawdź nagłówek X-Real-IP
elif "X-Real-IP" in request.headers:
return request.headers.get("X-Real-IP")
# Jeśli jest nagłówek X-Forwarded-For, pobierz pierwszy adres na liście
elif "X-Forwarded-For" in request.headers:
forwarded_for = request.headers.get("X-Forwarded-For").split(",")
return forwarded_for[0].strip()
# W przeciwnym wypadku użyj standardowego remote_addr
return request.remote_addr
# TRASY LOGOWANIA I REJESTRACJI # TRASY LOGOWANIA I REJESTRACJI
@app.route('/login', methods=['GET', 'POST']) @app.route('/login', methods=['GET', 'POST'])
@ -117,7 +138,8 @@ def login():
allowed_hosts_str = settings.allowed_login_hosts allowed_hosts_str = settings.allowed_login_hosts
# Sprawdzenie, czy adres IP klienta jest dozwolony # Sprawdzenie, czy adres IP klienta jest dozwolony
if not is_allowed_ip(request.remote_addr, allowed_hosts_str): client_ip = get_real_ip()
if not is_allowed_ip(client_ip, allowed_hosts_str):
flash('Dostęp do endpointu /login jest zablokowany dla Twojego adresu IP', 'danger') flash('Dostęp do endpointu /login jest zablokowany dla Twojego adresu IP', 'danger')
return redirect(url_for('index')) return redirect(url_for('index'))
@ -166,12 +188,13 @@ def register():
@app.route('/admin') @app.route('/admin')
@login_required @login_required
def admin_dashboard(): def admin_dashboard():
# Tylko użytkownik z flagą is_admin ma dostęp do pełnych funkcji panelu
if not current_user.is_admin: if not current_user.is_admin:
flash('Brak uprawnień do panelu administracyjnego', 'danger') flash('Brak uprawnień do panelu administracyjnego', 'danger')
return redirect(url_for('index')) return redirect(url_for('index'))
zbiorki = Zbiorka.query.all() active_zbiorki = Zbiorka.query.filter_by(zrealizowana=False).all()
return render_template('admin/dashboard.html', zbiorki=zbiorki) completed_zbiorki = Zbiorka.query.filter_by(zrealizowana=True).all()
return render_template('admin/dashboard.html', active_zbiorki=active_zbiorki,
completed_zbiorki=completed_zbiorki)
@app.route('/admin/zbiorka/dodaj', methods=['GET', 'POST']) @app.route('/admin/zbiorka/dodaj', methods=['GET', 'POST'])
@login_required @login_required
@ -346,6 +369,17 @@ def admin_settings():
return render_template('admin/settings.html', settings=settings) return render_template('admin/settings.html', settings=settings)
@app.route('/admin/zbiorka/oznacz/<int:zbiorka_id>', methods=['POST'])
@login_required
def oznacz_zbiorka(zbiorka_id):
if not current_user.is_admin:
flash('Brak uprawnień do wykonania tej operacji', 'danger')
return redirect(url_for('index'))
zb = Zbiorka.query.get_or_404(zbiorka_id)
zb.zrealizowana = True
db.session.commit()
flash('Zbiórka została oznaczona jako zrealizowana', 'success')
return redirect(url_for('admin_dashboard'))
@app.route('/robots.txt') @app.route('/robots.txt')
def robots(): def robots():

3
emergency_access.txt Normal file
View File

@ -0,0 +1,3 @@
Jeśli ten plik istwnieje w katalogu apliakcji, to wylacza zebzpieczenie logowania do panelu admina z ograniczeniem IP.
Musi miec rozszerzenie .txt

View File

@ -2,13 +2,66 @@
{% block title %}Panel Admina{% endblock %} {% block title %}Panel Admina{% endblock %}
{% block content %} {% block content %}
<div class="container my-4"> <div class="container my-4">
<h3 class="mb-4">Panel Admina</h3> <h2 class="mb-4">Panel Admina</h2>
<div class="mb-3"> <div class="mb-3">
<div class="mb-3"> <a href="{{ url_for('dodaj_zbiorka') }}" class="btn btn-success">Dodaj zbiórkę</a>
<a href="{{ url_for('dodaj_zbiorka') }}" class="btn btn-success">Dodaj zbiórkę</a> <a href="{{ url_for('admin_settings') }}" class="btn btn-primary">Ustawienia</a>
<a href="{{ url_for('admin_settings') }}" class="btn btn-primary">Ustawienia globalne</a>
</div>
</div> </div>
<!-- Tabela zbiórek aktywnych -->
<h4>Aktywne zbiórki</h4>
<div class="table-responsive mb-5">
<table class="table table-dark table-striped table-hover">
<thead>
<tr>
<th>ID</th>
<th>Nazwa</th>
<th>Widoczność</th>
<th>Opcje</th>
</tr>
</thead>
<tbody>
{% for z in active_zbiorki %}
<tr>
<td>{{ z.id }}</td>
<td>{{ z.nazwa }}</td>
<td>
{% if z.ukryta %}
<span class="badge bg-secondary">Ukryta</span>
{% else %}
<span class="badge bg-success">Widoczna</span>
{% endif %}
</td>
<td>
<a href="{{ url_for('edytuj_zbiorka', zbiorka_id=z.id) }}" class="btn btn-primary btn-sm">Edytuj</a>
<a href="{{ url_for('admin_dodaj_wplate', zbiorka_id=z.id) }}" class="btn btn-warning btn-sm">Dodaj wpłatę</a>
<a href="{{ url_for('edytuj_stan', zbiorka_id=z.id) }}" class="btn btn-info btn-sm">Edytuj stan</a>
<!-- Przycisk do oznaczenia jako zrealizowana -->
<form action="{{ url_for('oznacz_zbiorka', zbiorka_id=z.id) }}" method="post" style="display: inline;">
<button type="submit" class="btn btn-warning btn-sm">Oznacz jako zrealizowana</button>
</form>
<form action="{{ url_for('toggle_visibility', zbiorka_id=z.id) }}" method="post" style="display: inline;">
<button type="submit" class="btn btn-secondary btn-sm">
{% if z.ukryta %} Pokaż {% else %} Ukryj {% endif %}
</button>
</form>
<form action="{{ url_for('usun_zbiorka', zbiorka_id=z.id) }}" method="post" style="display: inline;">
<button type="submit" class="btn btn-danger btn-sm" onclick="return confirm('Czy na pewno chcesz usunąć tę zbiórkę?');">Usuń</button>
</form>
</td>
</tr>
{% else %}
<tr>
<td colspan="4" class="text-center">Brak aktywnych zbiórek</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
<!-- Tabela zbiórek zrealizowanych -->
<h4>Zrealizowane zbiórki</h4>
<div class="table-responsive"> <div class="table-responsive">
<table class="table table-dark table-striped table-hover"> <table class="table table-dark table-striped table-hover">
<thead> <thead>
@ -20,7 +73,7 @@
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{% for z in zbiorki %} {% for z in completed_zbiorki %}
<tr> <tr>
<td>{{ z.id }}</td> <td>{{ z.id }}</td>
<td>{{ z.nazwa }}</td> <td>{{ z.nazwa }}</td>
@ -47,7 +100,7 @@
</tr> </tr>
{% else %} {% else %}
<tr> <tr>
<td colspan="4" class="text-center">Brak zbiórek</td> <td colspan="4" class="text-center">Brak zbiórek zrealizowanych</td>
</tr> </tr>
{% endfor %} {% endfor %}
</tbody> </tbody>

View File

@ -34,8 +34,15 @@
<input type="checkbox" class="form-check-input" id="ukryj_kwote" name="ukryj_kwote" {% if zbiorka.ukryj_kwote %}checked{% endif %}> <input type="checkbox" class="form-check-input" id="ukryj_kwote" name="ukryj_kwote" {% if zbiorka.ukryj_kwote %}checked{% endif %}>
<label class="form-check-label" for="ukryj_kwote">Ukryj kwoty (cel i stan)</label> <label class="form-check-label" for="ukryj_kwote">Ukryj kwoty (cel i stan)</label>
</div> </div>
<button type="submit" class="btn btn-primary">Zaktualizuj zbiórkę</button> <button type="submit" class="btn btn-primary">Zaktualizuj zbiórkę</button>
<button type="button" class="btn btn-primary" id="ustaw-globalne">Ustaw globalne</button> <button type="button" class="btn btn-primary" id="ustaw-globalne">Ustaw globalne</button>
<form action="{{ url_for('oznacz_zbiorka', zbiorka_id=zbiorka.id) }}" method="post" style="display:inline;">
<button type="submit" class="btn btn-warning">Oznacz jako zrealizowana</button>
</form>
</form> </form>
</div> </div>
</div> </div>

View File

@ -2,28 +2,39 @@
{% block title %}Ustawienia globalne{% endblock %} {% block title %}Ustawienia globalne{% endblock %}
{% block content %} {% block content %}
<div class="container my-4"> <div class="container my-4">
<div class="card shadow-sm"> <form method="post">
<div class="card-header bg-primary text-white"> <!-- Blok ustawień konta -->
<h3 class="card-title mb-0">Ustawienia globalne</h3> <div class="card shadow-sm mb-4">
<div class="card-header bg-primary text-white">
<h3 class="card-title mb-0">Ustawienia konta</h3>
</div>
<div class="card-body">
<div class="mb-3">
<label for="numer_konta" class="form-label">Globalny numer konta</label>
<input type="text" class="form-control" id="numer_konta" name="numer_konta" value="{{ settings.numer_konta if settings else '' }}" required>
</div>
<div class="mb-3">
<label for="numer_telefonu_blik" class="form-label">Globalny numer telefonu BLIK</label>
<input type="text" class="form-control" id="numer_telefonu_blik" name="numer_telefonu_blik" value="{{ settings.numer_telefonu_blik if settings else '' }}" required>
</div>
</div>
</div> </div>
<div class="card-body"> <!-- Blok dozwolonych adresów IP -->
<form method="post"> <div class="card shadow-sm mb-4">
<div class="mb-3"> <div class="card-header bg-secondary text-white">
<label for="numer_konta" class="form-label">Globalny numer konta</label> <h3 class="card-title mb-0">Dozwolone adresy IP</h3>
<input type="text" class="form-control" id="numer_konta" name="numer_konta" value="{{ settings.numer_konta if settings else '' }}" required> </div>
</div> <div class="card-body">
<div class="mb-3"> <div class="mb-3">
<label for="numer_telefonu_blik" class="form-label">Globalny numer telefonu BLIK</label> <label for="allowed_login_hosts" class="form-label">Dozwolone hosty logowania</label>
<input type="text" class="form-control" id="numer_telefonu_blik" name="numer_telefonu_blik" value="{{ settings.numer_telefonu_blik if settings else '' }}" required> <textarea class="form-control" id="allowed_login_hosts" name="allowed_login_hosts" rows="4" placeholder="Podaj adresy IP lub nazwy domen oddzielone przecinkami lub nowymi liniami">{{ settings.allowed_login_hosts if settings and settings.allowed_login_hosts else '' }}</textarea>
</div> </div>
<div class="mb-3"> </div>
<label for="allowed_login_hosts" class="form-label">Dozwolone hosty logowania</label>
<textarea class="form-control" id="allowed_login_hosts" name="allowed_login_hosts" rows="4" placeholder="Podaj adresy IP lub nazwy domen oddzielone przecinkami lub nowymi liniami">{{ settings.allowed_login_hosts if settings and settings.allowed_login_hosts else '' }}</textarea>
</div>
<button type="submit" class="btn btn-primary">Zapisz ustawienia</button>
<a href="{{ url_for('admin_dashboard') }}" class="btn btn-secondary">Powrót</a>
</form>
</div> </div>
</div> <div class="d-flex justify-content-between">
<button type="submit" class="btn btn-primary">Zapisz ustawienia</button>
<a href="{{ url_for('admin_dashboard') }}" class="btn btn-secondary">Powrót</a>
</div>
</form>
</div> </div>
{% endblock %} {% endblock %}

View File

@ -15,6 +15,8 @@
<a class="navbar-brand" href="{{ url_for('index') }}">Zbiórki unitraklub.pl</a> <a class="navbar-brand" href="{{ url_for('index') }}">Zbiórki unitraklub.pl</a>
<div class="collapse navbar-collapse"> <div class="collapse navbar-collapse">
<ul class="navbar-nav ms-auto"> <ul class="navbar-nav ms-auto">
<li class="nav-item"><a class="nav-link" href="{{ url_for('index') }}">Aktualne zbiórki</a></li>
<li class="nav-item"><a class="nav-link" href="{{ url_for('zbiorki_zrealizowane') }}">Zrealizowane zbiórki</a></li>
{% if current_user.is_authenticated %} {% if current_user.is_authenticated %}
<li class="nav-item"><a class="nav-link" href="{{ url_for('admin_dashboard') }}">Panel Admina</a></li> <li class="nav-item"><a class="nav-link" href="{{ url_for('admin_dashboard') }}">Panel Admina</a></li>
<li class="nav-item"><a class="nav-link" href="{{ url_for('logout') }}">Wyloguj</a></li> <li class="nav-item"><a class="nav-link" href="{{ url_for('logout') }}">Wyloguj</a></li>

View File

@ -1,7 +1,11 @@
{% extends 'base.html' %} {% extends 'base.html' %}
{% block title %}Aktywne zbiórki{% endblock %} {% block title %}{% if request.path == url_for('zbiorki_zrealizowane') %}Zrealizowane zbiórki{% else %}Aktualnie aktywne zbiórki{% endif %}{% endblock %}
{% block content %} {% block content %}
<h2 class="mb-4">Aktualnie aktywne zbiórki</h2> {% if request.path == url_for('zbiorki_zrealizowane') %}
<h2 class="mb-4">Zrealizowane zbiórki</h2>
{% else %}
<h2 class="mb-4">Aktualnie aktywne zbiórki</h2>
{% endif %}
<div class="row"> <div class="row">
{% for z in zbiorki %} {% for z in zbiorki %}
<div class="col-sm-12 col-md-6 col-lg-4 mb-4"> <div class="col-sm-12 col-md-6 col-lg-4 mb-4">