régler problème de dispos des coachs
This commit is contained in:
@@ -128,14 +128,13 @@ def configure_logging(app):
|
||||
app.logger.addHandler(app_handler)
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
# 4. Console Handler (for development)
|
||||
# 4. Console Handler (always enabled for debugging in both dev and production)
|
||||
# -------------------------------------------------------------------------
|
||||
if os.getenv('FLASK_DEBUG', 'false').lower() == 'true':
|
||||
console_handler = logging.StreamHandler()
|
||||
console_handler.setLevel(logging.DEBUG)
|
||||
console_handler.setFormatter(formatter)
|
||||
console_handler.addFilter(sensitive_filter)
|
||||
app.logger.addHandler(console_handler)
|
||||
console_handler = logging.StreamHandler()
|
||||
console_handler.setLevel(log_level)
|
||||
console_handler.setFormatter(formatter)
|
||||
console_handler.addFilter(sensitive_filter)
|
||||
app.logger.addHandler(console_handler)
|
||||
|
||||
# Log startup information
|
||||
app.logger.info('Logging configured - Level: %s, Log directory: %s', log_level_name, log_dir)
|
||||
|
||||
@@ -1,129 +0,0 @@
|
||||
"""Development HTTPS server for testing production settings locally.
|
||||
|
||||
Generates a self-signed certificate (if not present) and runs the
|
||||
application via Waitress wrapped in a TLS socket. This simulates the
|
||||
production environment where Nginx handles TLS termination.
|
||||
|
||||
Usage:
|
||||
python run_https.py
|
||||
|
||||
The server will listen on https://localhost:8443
|
||||
Accept the self-signed certificate warning in your browser to proceed.
|
||||
"""
|
||||
|
||||
import os
|
||||
import socket
|
||||
import subprocess
|
||||
import ssl
|
||||
import sys
|
||||
from waitress.server import create_server
|
||||
from app.app import create_app
|
||||
|
||||
CERT_FILE = 'certs/localhost.pem'
|
||||
KEY_FILE = 'certs/localhost-key.pem'
|
||||
|
||||
|
||||
def generate_self_signed_cert():
|
||||
"""Generate a self-signed certificate for local HTTPS testing.
|
||||
|
||||
Uses OpenSSL to create a key and certificate valid for 365 days.
|
||||
Skips generation if certificate files already exist.
|
||||
"""
|
||||
if os.path.exists(CERT_FILE) and os.path.exists(KEY_FILE):
|
||||
print('[OK] Self-signed certificate already exists.')
|
||||
return
|
||||
|
||||
os.makedirs('certs', exist_ok=True)
|
||||
|
||||
print('[INFO] Generating self-signed certificate for localhost...')
|
||||
try:
|
||||
subprocess.run([
|
||||
'openssl', 'req', '-x509', '-newkey', 'rsa:2048',
|
||||
'-keyout', KEY_FILE,
|
||||
'-out', CERT_FILE,
|
||||
'-days', '365',
|
||||
'-nodes',
|
||||
'-subj', '/CN=localhost'
|
||||
], check=True, capture_output=True)
|
||||
print('[OK] Certificate generated: certs/localhost.pem')
|
||||
except FileNotFoundError:
|
||||
print('[ERROR] OpenSSL not found. Install OpenSSL or use:')
|
||||
print(' winget install OpenSSL.OpenSSL')
|
||||
print(' OR download from https://slproweb.com/products/Win32OpenSSL.html')
|
||||
sys.exit(1)
|
||||
except subprocess.CalledProcessError as e:
|
||||
print(f'[ERROR] Certificate generation failed: {e}')
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def main():
|
||||
"""Start the HTTPS development server."""
|
||||
port = int(os.getenv('HTTPS_PORT', 8443))
|
||||
host = os.getenv('HOST', '127.0.0.1')
|
||||
|
||||
# Ensure certificate exists
|
||||
generate_self_signed_cert()
|
||||
|
||||
# Create the Flask application
|
||||
app = create_app()
|
||||
|
||||
# Create SSL context
|
||||
context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
|
||||
context.load_cert_chain(CERT_FILE, KEY_FILE)
|
||||
|
||||
# Create a TCP socket, wrap it with TLS, then pass to Waitress
|
||||
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0)
|
||||
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||
sock.bind((host, port))
|
||||
sock.listen(5)
|
||||
|
||||
# Wrap the socket with TLS
|
||||
ssl_sock = context.wrap_socket(sock, server_side=True)
|
||||
|
||||
# =====================================================================
|
||||
# WSGI Middleware: Tell Flask the connection is HTTPS
|
||||
#
|
||||
# Waitress doesn't know the underlying socket is TLS, so Flask sees
|
||||
# wsgi.url_scheme = "http". Without this middleware, force_https()
|
||||
# would cause an infinite redirect loop (ERR_TOO_MANY_REDIRECTS).
|
||||
# =====================================================================
|
||||
class ForceHTTPSMiddleware:
|
||||
"""WSGI middleware that sets url_scheme to 'https'.
|
||||
|
||||
Since we're wrapping the TCP socket with SSL before passing it
|
||||
to Waitress, Flask's request.is_secure returns False because
|
||||
Waitress reports wsgi.url_scheme='http'. This middleware fixes
|
||||
that so Flask correctly identifies the connection as HTTPS.
|
||||
"""
|
||||
|
||||
def __init__(self, wsgi_app):
|
||||
self.wsgi_app = wsgi_app
|
||||
|
||||
def __call__(self, environ, start_response):
|
||||
environ['wsgi.url_scheme'] = 'https'
|
||||
environ['HTTPS'] = 'on'
|
||||
return self.wsgi_app(environ, start_response)
|
||||
|
||||
# Wrap the Flask app with the HTTPS middleware
|
||||
app.wsgi_app = ForceHTTPSMiddleware(app.wsgi_app)
|
||||
|
||||
print(f'\n╔══════════════════════════════════════════════════════╗')
|
||||
print(f'║ TEAM TRYOUTS - Development HTTPS Server ║')
|
||||
print(f'╠══════════════════════════════════════════════════════╣')
|
||||
print(f'║ URL: https://{host}:{port} ║')
|
||||
print(f'║ Cert: self-signed (accept browser warning) ║')
|
||||
print(f'║ Press Ctrl+C to stop ║')
|
||||
print(f'╚══════════════════════════════════════════════════════╝\n')
|
||||
|
||||
# Create Waitress server with the SSL-wrapped socket
|
||||
server = create_server(
|
||||
app,
|
||||
sockets=[ssl_sock],
|
||||
threads=4,
|
||||
channel_timeout=30,
|
||||
)
|
||||
server.run()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -85,12 +85,6 @@
|
||||
</li>
|
||||
{% endif %}
|
||||
{% if current_user.role == 'coach' %}
|
||||
<li>
|
||||
<a href="{{ url_for('users.manage_coach_availability') }}" class="{% if request.endpoint == 'users.manage_coach_availability' %}active{% endif %}">
|
||||
<i class="fas fa-clock"></i>
|
||||
<span>Availability</span>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="{{ url_for('users.notes_dashboard') }}" class="{% if request.endpoint == 'users.notes_dashboard' or request.endpoint == 'users.manage_team_notes' or request.endpoint == 'users.manage_personal_notes' %}active{% endif %}">
|
||||
<i class="fas fa-sticky-note"></i>
|
||||
|
||||
@@ -15,6 +15,9 @@
|
||||
</div>
|
||||
|
||||
<div class="form-actions mt-4">
|
||||
<button type="button" class="btn btn-primary" onclick="saveAvailability()">
|
||||
<i class="fas fa-save"></i> Save Availability
|
||||
</button>
|
||||
<button type="button" class="btn btn-secondary" onclick="clearAllAvailability()">
|
||||
<i class="fas fa-trash"></i> Clear All
|
||||
</button>
|
||||
@@ -168,13 +171,18 @@ function saveAvailability() {
|
||||
|
||||
fetch('{{ url_for("users.manage_coach_availability") }}', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-CSRFToken': '{{ csrf_token() }}'
|
||||
},
|
||||
body: JSON.stringify({ slots: slots })
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
flash('Availability saved!', 'success');
|
||||
} else {
|
||||
flash(data.error || 'Error saving availability.', 'danger');
|
||||
}
|
||||
})
|
||||
.catch(function(error) {
|
||||
@@ -188,7 +196,10 @@ function clearAllAvailability() {
|
||||
return;
|
||||
}
|
||||
|
||||
fetch('{{ url_for("users.clear_coach_availability") }}', { method: 'POST' })
|
||||
fetch('{{ url_for("users.clear_coach_availability") }}', {
|
||||
method: 'POST',
|
||||
headers: { 'X-CSRFToken': '{{ csrf_token() }}' }
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
|
||||
@@ -357,7 +357,10 @@ function saveCoachAvailability() {
|
||||
}
|
||||
fetch('{{ url_for("users.manage_coach_availability") }}', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-CSRFToken': '{{ csrf_token() }}'
|
||||
},
|
||||
body: JSON.stringify({ slots: slots })
|
||||
})
|
||||
.then(r => r.json())
|
||||
@@ -376,7 +379,10 @@ function saveCoachAvailability() {
|
||||
|
||||
function clearAllAvailability() {
|
||||
if (!confirm('Are you sure you want to clear all your availability slots?')) return;
|
||||
fetch('{{ url_for("users.clear_coach_availability") }}', { method: 'POST' })
|
||||
fetch('{{ url_for("users.clear_coach_availability") }}', {
|
||||
method: 'POST',
|
||||
headers: { 'X-CSRFToken': '{{ csrf_token() }}' }
|
||||
})
|
||||
.then(r => r.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
|
||||
Reference in New Issue
Block a user