changer le look du calendar légèrement et régler problème avec déconnection des test
CI - Security, Lint & Tests / validate (push) Canceled after 0s

This commit is contained in:
cedrick2711
2026-09-10 10:43:47 -04:00
parent a9b33b30f5
commit bd9ed4bdc7
4 changed files with 184 additions and 25 deletions
+18 -4
View File
@@ -324,8 +324,9 @@ def _create_database():
def _stop(*_args):
"""Stop the PostgreSQL server with a fast shutdown.
Registered as ``atexit`` and signal handler so it fires on Ctrl+C,
normal exit, and terminal close.
Registered as an ``atexit`` handler so it fires on normal exit. Signal
handling goes through ``_handle_stop_signal``, which also stops the
server but then re-raises so the process actually exits.
"""
global _started
global _stopped_system_service_name
@@ -349,6 +350,19 @@ def _stop(*_args):
_start_system_service(srv)
def _handle_stop_signal(signum, frame):
"""Stop PostgreSQL, then let the interpreter shut down normally.
``_stop`` alone would swallow Ctrl+C: it stops the server and restores the
system service, then returns without exiting, leaving the dev server
running against a stopped database and the terminal frozen. Re-raising
``KeyboardInterrupt`` hands control back to the framework, which exits;
the ``atexit`` registration then runs ``_stop`` again (idempotent).
"""
_stop()
raise KeyboardInterrupt
# ---------------------------------------------------------------------------
# Public API
# ---------------------------------------------------------------------------
@@ -420,9 +434,9 @@ def start() -> str:
_started = True
atexit.register(_stop)
with contextlib.suppress(ValueError, OSError):
signal.signal(signal.SIGINT, _stop)
signal.signal(signal.SIGINT, _handle_stop_signal)
with contextlib.suppress(AttributeError, ValueError, OSError):
signal.signal(signal.SIGBREAK, _stop) # type: ignore[attr-defined]
signal.signal(signal.SIGBREAK, _handle_stop_signal) # type: ignore[attr-defined]
logger.info('Local PostgreSQL ready — DATABASE_URL=%s', DATABASE_URL)
return DATABASE_URL
-4
View File
@@ -224,7 +224,6 @@ def api_events():
for match in matches:
tryout = tryouts_by_id[match.tryout_id]
match_color = '#10b981' if match.match_type == 'team_vs_team' else '#f59e0b'
# 'description' used to be participants_str + '<br>' + description.
# Building presentation markup inside a JSON field is what carried
# the stored XSS: the browser dropped it straight into innerHTML,
@@ -255,7 +254,6 @@ def api_events():
'id': f'match_{match.id}',
'title': match.title,
'type': 'match',
'color': match_color,
'extendedProps': {
'location': match.location or tryout.location or 'TBD',
'status': match.status,
@@ -301,7 +299,6 @@ def api_events():
'id': f'team_match_{team_match.id}',
'title': team_match.title,
'type': 'team_match',
'color': '#3b82f6',
'extendedProps': {
'location': team_match.location or 'TBD',
'status': team_match.status,
@@ -334,7 +331,6 @@ def api_events():
'id': f'one_on_one_{ooo.id}',
'title': f'1:1 - {ooo.player.full_name} & {ooo.coach.full_name}',
'type': 'one_on_one',
'color': '#8b5cf6',
'extendedProps': {
'location': 'Discord / Voice Chat',
'status': 'approved',
+119 -15
View File
@@ -1043,6 +1043,10 @@ a:hover { color: var(--primary-dark); }
.fc {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
--fc-border-color: var(--gray-200);
--fc-page-bg-color: transparent;
--fc-neutral-bg-color: var(--gray-50);
--fc-today-bg-color: var(--info-light);
}
.fc-toolbar {
@@ -1055,24 +1059,29 @@ a:hover { color: var(--primary-dark); }
color: var(--gray-900);
}
.fc-button {
background: var(--primary) !important;
border: none !important;
.fc .fc-button {
background: var(--gray-50) !important;
border: 1px solid var(--gray-200) !important;
color: var(--gray-700) !important;
border-radius: var(--radius-sm) !important;
padding: 6px 12px !important;
font-size: 0.85rem !important;
}
.fc-button:hover {
background: var(--primary-dark) !important;
.fc .fc-button:hover {
background: var(--gray-100) !important;
color: var(--gray-900) !important;
}
.fc-button-primary:not(:disabled).fc-button-active,
.fc-button-primary:not(:disabled):active {
background: var(--primary-dark) !important;
.fc .fc-button-primary:not(:disabled).fc-button-active,
.fc .fc-button-primary:not(:disabled):active {
background: var(--primary) !important;
border-color: var(--primary) !important;
color: #fff !important;
}
.fc-daygrid-day {
.fc-daygrid-day,
.fc-timegrid-slot {
transition: var(--transition);
}
@@ -1080,16 +1089,111 @@ a:hover { color: var(--primary-dark); }
background: var(--gray-50);
}
.fc-event {
border-radius: var(--radius-sm);
padding: 2px 4px;
font-size: 0.8rem;
/* Event cards (scoped to the main calendar: the tryout mini-calendar keeps
its inline event colors) */
#calendar .fc-event {
border: none !important;
border-radius: var(--radius-sm) !important;
padding: 3px 6px !important;
font-size: 0.8rem !important;
background-color: var(--fc-event-bg, var(--primary)) !important;
color: #fff !important;
box-shadow: var(--shadow);
}
.fc-event-title {
font-weight: 600;
#calendar .fc-event-body {
display: flex;
align-items: center;
gap: 6px;
min-width: 0;
}
#calendar .fc-event-body i {
flex: 0 0 auto;
font-size: 0.7rem;
opacity: 0.9;
}
#calendar .fc-event-text {
display: flex;
flex-wrap: wrap;
align-items: baseline;
gap: 0 4px;
min-width: 0;
}
#calendar .fc-event-time {
font-size: 0.72rem;
font-weight: 700;
}
#calendar .fc-event-title {
font-weight: 600 !important;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
/* Event type colors (semantic, light theme) */
#calendar .fc-event--team { --fc-event-bg: #10b981; }
#calendar .fc-event--scrim { --fc-event-bg: #f59e0b; }
#calendar .fc-event--team-match { --fc-event-bg: #3b82f6; }
#calendar .fc-event--one-on-one { --fc-event-bg: #8b5cf6; }
#calendar .fc-now-indicator {
border-color: var(--danger);
}
/* Dark theme: the calendar chrome reacts to the light/dark switch */
[data-theme="dark"] .fc {
--fc-border-color: var(--border-color);
--fc-neutral-bg-color: var(--bg-secondary);
--fc-today-bg-color: rgba(59, 130, 246, 0.15);
}
[data-theme="dark"] .fc-toolbar-title {
color: var(--text-primary);
}
[data-theme="dark"] .fc .fc-button {
background: var(--bg-tertiary) !important;
border-color: var(--border-color) !important;
color: var(--text-secondary) !important;
}
[data-theme="dark"] .fc .fc-button:hover {
background: var(--bg-secondary) !important;
color: var(--text-primary) !important;
}
[data-theme="dark"] .fc .fc-button-primary:not(:disabled).fc-button-active,
[data-theme="dark"] .fc .fc-button-primary:not(:disabled):active {
background: var(--primary) !important;
border-color: var(--primary) !important;
color: #fff !important;
}
[data-theme="dark"] .fc-daygrid-day:hover {
background: var(--bg-tertiary);
}
[data-theme="dark"] .fc-daygrid-day-number,
[data-theme="dark"] .fc-col-header-cell-cushion,
[data-theme="dark"] .fc-timegrid-axis-cushion,
[data-theme="dark"] .fc-timegrid-slot-label-cushion {
color: var(--text-secondary);
}
/* Pastel event chips with dark text keep contrast on a dark background. */
[data-theme="dark"] #calendar .fc-event {
color: var(--gray-900) !important;
}
[data-theme="dark"] #calendar .fc-event--team { --fc-event-bg: #d1fae5; }
[data-theme="dark"] #calendar .fc-event--scrim { --fc-event-bg: #fef3c7; }
[data-theme="dark"] #calendar .fc-event--team-match { --fc-event-bg: #dbeafe; }
[data-theme="dark"] #calendar .fc-event--one-on-one { --fc-event-bg: #ede9fe; }
.btn-group {
display: flex;
gap: 4px;
+47 -2
View File
@@ -137,12 +137,18 @@ document.addEventListener('DOMContentLoaded', function() {
headerToolbar: {
left: 'prev,next today',
center: 'title',
right: 'dayGridMonth,timeGridWeek,timeGridDay,listMonth'
right: ''
},
events: '/matches/api/events',
eventClick: function(info) {
showEventModal(info.event);
},
eventClassNames: function(arg) {
return [eventClassName(arg.event)];
},
eventContent: function(arg) {
return renderEventContent(arg);
},
dateClick: function(info) {
if (canScheduleMatches) {
var dateStr = info.dateStr;
@@ -158,7 +164,11 @@ document.addEventListener('DOMContentLoaded', function() {
}
},
slotMinTime: '12:00:00',
slotMaxTime: '24:00:00'
slotMaxTime: '24:00:00',
slotEventOverlap: true,
dayMaxEvents: false,
nowIndicator: true,
height: 'auto'
});
calendar.render();
@@ -177,6 +187,41 @@ function changeView(viewName) {
}
}
function eventClassName(event) {
var props = event.extendedProps || {};
if (props.type === 'team_match') { return 'fc-event--team-match'; }
if (props.type === 'one_on_one') { return 'fc-event--one-on-one'; }
return props.match_type === 'team_vs_team' ? 'fc-event--team' : 'fc-event--scrim';
}
function eventIcon(type, matchType) {
if (type === 'team_match') { return 'fa-trophy'; }
if (type === 'one_on_one') { return 'fa-user-friends'; }
if (matchType === 'team_vs_team') { return 'fa-shield-alt'; }
if (matchType === 'player_vs_player') { return 'fa-users'; }
return 'fa-gamepad';
}
function renderEventContent(arg) {
// Built with DOM nodes + textContent: the title comes from a JSON endpoint
// and has never been HTML-escaped, so it must not reach innerHTML.
var props = arg.event.extendedProps || {};
var wrap = makeEl('div', 'fc-event-body');
var icon = makeEl('i', 'fas ' + eventIcon(props.type, props.match_type));
icon.setAttribute('aria-hidden', 'true');
wrap.appendChild(icon);
var text = makeEl('span', 'fc-event-text');
if (!arg.event.allDay && arg.timeText && arg.view.type.indexOf('dayGrid') === 0) {
text.appendChild(makeEl('span', 'fc-event-time', arg.timeText));
}
text.appendChild(makeEl('span', 'fc-event-title', arg.event.title));
wrap.appendChild(text);
return { domNodes: [wrap] };
}
// --- Create Event Modal ---
function showCreateEventModal(dateStr) {
document.getElementById('createEventDate').textContent = dateStr;