style agenda
This commit is contained in:
parent
737781e9aa
commit
7dd38624a4
7 changed files with 471 additions and 161 deletions
11
cleanup_test_files.sh
Executable file
11
cleanup_test_files.sh
Executable file
|
@ -0,0 +1,11 @@
|
|||
#!/bin/bash
|
||||
# Script de nettoyage des fichiers de test CORS
|
||||
|
||||
echo "🧹 Nettoyage des fichiers de test CORS..."
|
||||
|
||||
# Supprimer les fichiers de test
|
||||
rm -f test_cors_embed.py
|
||||
rm -f test_embed_cors.html
|
||||
rm -f cleanup_test_files.sh
|
||||
|
||||
echo "✅ Fichiers de test supprimés"
|
|
@ -1,10 +1,6 @@
|
|||
<div class="agenda-page">
|
||||
<div class="layout">
|
||||
<div class="aside">
|
||||
<app-menu></app-menu>
|
||||
</div>
|
||||
|
||||
<div class="main">
|
||||
@if (isLoading) {
|
||||
<div class="loading">
|
||||
<div class="loading-spinner"></div>
|
||||
|
@ -12,31 +8,31 @@
|
|||
</div>
|
||||
} @else {
|
||||
<div class="agenda-layout">
|
||||
<aside class="agenda-sidebar">
|
||||
<div class="sidebar-header">
|
||||
<h3>Agenda</h3>
|
||||
<small>{{filteredCalendarEvents.length}} évènements</small>
|
||||
</div>
|
||||
<div class="sidebar-filters">
|
||||
<app-what-filter
|
||||
[label]="'Filtrer par type d\'événement'"
|
||||
[available]="availableWhatTypes"
|
||||
[selected]="selectedWhatFilter"
|
||||
(selectedChange)="onWhatFilterChange($event)"></app-what-filter>
|
||||
@if (selectedDate) {
|
||||
<div class="date-filter-info">
|
||||
<small>Filtré par date: {{formatDayHeader(selectedDate)}}</small>
|
||||
<button class="btn-reset-date" (click)="clearDateFilter()">Afficher tous les jours</button>
|
||||
<aside class="agenda-sidebar">
|
||||
<div class="sidebar-header">
|
||||
<h3>Agenda</h3>
|
||||
<small>{{filteredCalendarEvents.length}} évènements</small>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
<div class="day-groups">
|
||||
@for (group of groupedEvents; track group.dateKey) {
|
||||
<div class="day-group" [attr.data-date-key]="group.dateKey">
|
||||
<div class="day-title">{{formatDayHeader(group.date)}}</div>
|
||||
<ul class="event-list">
|
||||
@for (ev of group.items; track ev.id) {
|
||||
<li class="event-item" (click)="selectFromSidebar(ev)" [class.active]="selectedEvent?.id === ev.id">
|
||||
<div class="sidebar-filters">
|
||||
<app-what-filter
|
||||
[label]="'Filtrer par type d\'événement'"
|
||||
[available]="availableWhatTypes"
|
||||
[selected]="selectedWhatFilter"
|
||||
(selectedChange)="onWhatFilterChange($event)"></app-what-filter>
|
||||
@if (selectedDate) {
|
||||
<div class="date-filter-info">
|
||||
<small>Filtré par date: {{formatDayHeader(selectedDate)}}</small>
|
||||
<button class="btn-reset-date" (click)="clearDateFilter()">Afficher tous les jours</button>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
<div class="day-groups">
|
||||
@for (group of groupedEvents; track group.dateKey) {
|
||||
<div class="day-group" [attr.data-date-key]="group.dateKey">
|
||||
<div class="day-title">{{formatDayHeader(group.date)}}</div>
|
||||
<ul class="event-list">
|
||||
@for (ev of group.items; track ev.id) {
|
||||
<li class="event-item" (click)="selectFromSidebar(ev)" [class.active]="selectedEvent?.id === ev.id">
|
||||
<span class="event-icon">
|
||||
@if (getImageForWhat(ev.properties.what)) {
|
||||
<img [src]="getImageForWhat(ev.properties.what)" alt="" />
|
||||
|
@ -46,44 +42,49 @@
|
|||
📌
|
||||
}
|
||||
</span>
|
||||
<div class="event-meta">
|
||||
<div class="event-title">{{ev.properties.label || ev.properties.name || 'Événement'}}</div>
|
||||
<div class="event-when">{{(ev.properties.start || ev.properties.when) || '—'}}</div>
|
||||
</div>
|
||||
</li>
|
||||
}
|
||||
</ul>
|
||||
<div class="event-meta">
|
||||
<div class="event-title">{{ev.properties.label || ev.properties.name || 'Événement'}}</div>
|
||||
<div class="event-when">{{(ev.properties.start || ev.properties.when) || '—'}}</div>
|
||||
</div>
|
||||
</li>
|
||||
}
|
||||
</ul>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
|
||||
@if (selectedEvent) {
|
||||
<div class="event-edit-panel">
|
||||
<div class="panel-header">
|
||||
<h3>Modifier l'événement</h3>
|
||||
<button class="btn-close" (click)="selectedEvent = null">×</button>
|
||||
</div>
|
||||
<div class="panel-content">
|
||||
<app-edit-form
|
||||
[selected]="selectedEvent"
|
||||
(saved)="onEventSaved()"
|
||||
(created)="onEventCreated()"
|
||||
(deleted)="onEventDeleted()">
|
||||
</app-edit-form>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</aside>
|
||||
}
|
||||
|
||||
</div>
|
||||
|
||||
<div class="main">
|
||||
|
||||
<main class="agenda-main">
|
||||
<app-calendar
|
||||
<app-calendar
|
||||
[events]="filteredCalendarEvents"
|
||||
(eventClick)="onEventClick($event)"
|
||||
(dateClick)="onDateClick($event)">
|
||||
</app-calendar>
|
||||
</main>
|
||||
|
||||
@if (selectedEvent) {
|
||||
<div class="event-edit-panel">
|
||||
<div class="panel-header">
|
||||
<h3>Modifier l'événement</h3>
|
||||
<button class="btn-close" (click)="selectedEvent = null">×</button>
|
||||
</div>
|
||||
<div class="panel-content">
|
||||
<app-edit-form
|
||||
[selected]="selectedEvent"
|
||||
(saved)="onEventSaved()"
|
||||
(created)="onEventCreated()"
|
||||
(deleted)="onEventDeleted()">
|
||||
</app-edit-form>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
grid-template-rows: minmax(100vh, auto);
|
||||
gap: 0;
|
||||
// min-height: 100vh;
|
||||
|
||||
|
||||
&.is-small {
|
||||
grid-template-columns: 100px 1fr;
|
||||
}
|
||||
|
@ -220,4 +220,4 @@
|
|||
.event-edit-panel {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
const oedb = {
|
||||
presets : {
|
||||
what : {
|
||||
presets: {
|
||||
what: {
|
||||
'community': {
|
||||
label: 'Événement de base',
|
||||
description: 'Événement communautaire',
|
||||
|
@ -22,11 +22,11 @@ const oedb = {
|
|||
description: 'Événement de type Culture ouvert au public',
|
||||
durationHours: 24,
|
||||
properties: {
|
||||
createdate: { label: 'Createdate', writable: true },
|
||||
lastupdate: { label: 'Lastupdate', writable: true },
|
||||
start: { label: 'Start', writable: true },
|
||||
stop: { label: 'Stop', writable: true },
|
||||
type: { label: 'Type', writable: true }
|
||||
createdate: {label: 'Createdate', writable: true},
|
||||
lastupdate: {label: 'Lastupdate', writable: true},
|
||||
start: {label: 'Start', writable: true},
|
||||
stop: {label: 'Stop', writable: true},
|
||||
type: {label: 'Type', writable: true}
|
||||
}
|
||||
},
|
||||
'culture.floss': {
|
||||
|
@ -35,15 +35,15 @@ const oedb = {
|
|||
category: 'Autre',
|
||||
description: 'Événement de type Culture Floss',
|
||||
durationHours: 24,
|
||||
properties: {
|
||||
}}, 'culture.viparis': {
|
||||
properties: {}
|
||||
}, 'culture.viparis': {
|
||||
emoji: '📅',
|
||||
label: 'Évènements organisés par Viparis',
|
||||
category: 'Autre',
|
||||
description: 'Événement culturel par Viparis, une entreprise qui gère plusieurs grandes salles en Île de France.',
|
||||
durationHours: 24,
|
||||
properties: {
|
||||
}},
|
||||
properties: {}
|
||||
},
|
||||
|
||||
|
||||
// Culture / Arts
|
||||
|
@ -109,8 +109,8 @@ const oedb = {
|
|||
description: 'Infrastructure de recharge disponible',
|
||||
durationHours: 300,
|
||||
properties: {
|
||||
"capacity:vehicles": { label: 'Nombre de véhicules qui peuvent actuellement se brancher', writable: true },
|
||||
"capacity:vehicles:max": { label: 'Nombre de véhicules maximum à pouvoir se brancher', writable: true }
|
||||
"capacity:vehicles": {label: 'Nombre de véhicules qui peuvent actuellement se brancher', writable: true},
|
||||
"capacity:vehicles:max": {label: 'Nombre de véhicules maximum à pouvoir se brancher', writable: true}
|
||||
}
|
||||
},
|
||||
'traffic.counter.bicycle': {
|
||||
|
@ -162,9 +162,9 @@ const oedb = {
|
|||
description: 'Accident de la circulation',
|
||||
durationHours: 6,
|
||||
properties: {
|
||||
severity: { label: 'Gravité', writable: true },
|
||||
lanes_closed: { label: 'Voies fermées', writable: true },
|
||||
vehicles: { label: 'Nombre de véhicules', writable: true }
|
||||
severity: {label: 'Gravité', writable: true},
|
||||
lanes_closed: {label: 'Voies fermées', writable: true},
|
||||
vehicles: {label: 'Nombre de véhicules', writable: true}
|
||||
}
|
||||
},
|
||||
'traffic.incident': {
|
||||
|
@ -193,9 +193,9 @@ const oedb = {
|
|||
description: 'Travaux sur la chaussée',
|
||||
durationHours: 72,
|
||||
properties: {
|
||||
contractor: { label: 'Entreprise', writable: true },
|
||||
reason: { label: 'Raison', writable: true },
|
||||
lanes_affected: { label: 'Voies impactées', writable: true }
|
||||
contractor: {label: 'Entreprise', writable: true},
|
||||
reason: {label: 'Raison', writable: true},
|
||||
lanes_affected: {label: 'Voies impactées', writable: true}
|
||||
}
|
||||
},
|
||||
'traffic.OperatorAction.NetworkManagement.RoadOrCarriagewayOrLaneManagement': {
|
||||
|
@ -206,7 +206,7 @@ const oedb = {
|
|||
description: 'Événement de type Traffic OperatorAction NetworkManagement RoadOrCarriagewayOrLaneManagement',
|
||||
durationHours: 24,
|
||||
properties: {
|
||||
source: { label: 'Source', writable: true },
|
||||
source: {label: 'Source', writable: true},
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -218,7 +218,7 @@ const oedb = {
|
|||
description: 'Événement de type Traffic OperatorAction NetworkManagement RoadOrCarriagewayOrLaneManagement',
|
||||
durationHours: 200,
|
||||
properties: {
|
||||
source: { label: 'Source', writable: true },
|
||||
source: {label: 'Source', writable: true},
|
||||
}
|
||||
},
|
||||
'traffic.OperatorAction.NetworkManagement.ReroutingManagement': {
|
||||
|
@ -229,7 +229,7 @@ const oedb = {
|
|||
description: 'Événement de type Traffic OperatorAction NetworkManagement ReroutingManagement',
|
||||
durationHours: 200,
|
||||
properties: {
|
||||
source: { label: 'Source', writable: true },
|
||||
source: {label: 'Source', writable: true},
|
||||
}
|
||||
},
|
||||
'traffic.OperatorAction.ConstructionWorks': {
|
||||
|
@ -240,7 +240,7 @@ const oedb = {
|
|||
description: 'Événement de type Traffic OperatorAction ConstructionWorks',
|
||||
durationHours: 200,
|
||||
properties: {
|
||||
source: { label: 'Source', writable: true },
|
||||
source: {label: 'Source', writable: true},
|
||||
}
|
||||
},
|
||||
'traffic.OperatorAction.NetworkManagement.SpeedManagement': {
|
||||
|
@ -251,75 +251,75 @@ const oedb = {
|
|||
description: 'Événement de type Traffic OperatorAction SpeedManagement',
|
||||
durationHours: 200,
|
||||
properties: {
|
||||
source: { label: 'Source', writable: true },
|
||||
source: {label: 'Source', writable: true},
|
||||
}
|
||||
},
|
||||
// Gestion générale du réseau
|
||||
'traffic.OperatorAction.NetworkManagement.GeneralNetworkManagement': {
|
||||
emoji: '',
|
||||
'traffic.OperatorAction.NetworkManagement.GeneralNetworkManagement': {
|
||||
emoji: '',
|
||||
image: 'static/cone.png',
|
||||
label: 'Gestion générale du réseau',
|
||||
category: 'Circulation',
|
||||
description: 'Événement de type Traffic OperatorAction GeneralNetworkManagement',
|
||||
durationHours: 200,
|
||||
properties: {
|
||||
source: { label: 'Source', writable: true },
|
||||
}
|
||||
},
|
||||
'traffic.closed': {
|
||||
emoji: '⛔',
|
||||
label: 'Chaussée fermée',
|
||||
category: 'Circulation',
|
||||
description: 'Événement de type Traffic Closed',
|
||||
durationHours: 200,
|
||||
properties: {
|
||||
source: { label: 'Source', writable: true },
|
||||
}
|
||||
},
|
||||
'traffic.TrafficElement.GeneralInstructionOrMessageToRoadUsers': {
|
||||
emoji: '',
|
||||
image: 'static/cone.png',
|
||||
label: 'Instruction ou message aux usagers',
|
||||
category: 'Circulation',
|
||||
description: 'Événement de type Traffic TrafficElement GeneralInstructionOrMessageToRoadUsers',
|
||||
durationHours: 200,
|
||||
properties: {
|
||||
source: { label: 'Source', writable: true },
|
||||
}
|
||||
},
|
||||
'traffic.TrafficElement.Obstruction.GeneralObstruction': {
|
||||
emoji: '',
|
||||
image: 'static/cone.png',
|
||||
label: 'Obstruction générale',
|
||||
category: 'Circulation',
|
||||
description: 'Événement de type Traffic TrafficElement Obstruction GeneralObstruction',
|
||||
durationHours: 200,
|
||||
properties: {
|
||||
source: { label: 'Source', writable: true },
|
||||
}
|
||||
},
|
||||
'traffic.TrafficElement.Obstruction.InfrastructureDamageObstruction': {
|
||||
emoji: '⛔',
|
||||
label: 'Gestion générale du réseau',
|
||||
category: 'Circulation',
|
||||
description: 'Événement de type Traffic OperatorAction GeneralNetworkManagement',
|
||||
durationHours: 200,
|
||||
properties: {
|
||||
source: {label: 'Source', writable: true},
|
||||
}
|
||||
},
|
||||
'traffic.closed': {
|
||||
emoji: '⛔',
|
||||
label: 'Chaussée fermée',
|
||||
category: 'Circulation',
|
||||
description: 'Événement de type Traffic Closed',
|
||||
durationHours: 200,
|
||||
properties: {
|
||||
source: {label: 'Source', writable: true},
|
||||
}
|
||||
},
|
||||
'traffic.TrafficElement.GeneralInstructionOrMessageToRoadUsers': {
|
||||
emoji: '',
|
||||
image: 'static/cone.png',
|
||||
label: 'Instruction ou message aux usagers',
|
||||
category: 'Circulation',
|
||||
description: 'Événement de type Traffic TrafficElement GeneralInstructionOrMessageToRoadUsers',
|
||||
durationHours: 200,
|
||||
properties: {
|
||||
source: {label: 'Source', writable: true},
|
||||
}
|
||||
},
|
||||
'traffic.TrafficElement.Obstruction.GeneralObstruction': {
|
||||
emoji: '',
|
||||
image: 'static/cone.png',
|
||||
label: 'Obstruction générale',
|
||||
category: 'Circulation',
|
||||
description: 'Événement de type Traffic TrafficElement Obstruction GeneralObstruction',
|
||||
durationHours: 200,
|
||||
properties: {
|
||||
source: {label: 'Source', writable: true},
|
||||
}
|
||||
},
|
||||
'traffic.TrafficElement.Obstruction.InfrastructureDamageObstruction': {
|
||||
emoji: '⛔',
|
||||
|
||||
label: 'Obstruction d\'infrastructure',
|
||||
category: 'Circulation',
|
||||
description: 'Événement de type Traffic TrafficElement Obstruction InfrastructureDamageObstruction',
|
||||
durationHours: 200,
|
||||
properties: {
|
||||
source: { label: 'Source', writable: true },
|
||||
}
|
||||
},
|
||||
'traffic.TrafficElement.Obstruction.EnvironmentalObstruction': {
|
||||
emoji: '⛔',
|
||||
label: 'Obstruction d\'infrastructure',
|
||||
category: 'Circulation',
|
||||
description: 'Événement de type Traffic TrafficElement Obstruction InfrastructureDamageObstruction',
|
||||
durationHours: 200,
|
||||
properties: {
|
||||
source: {label: 'Source', writable: true},
|
||||
}
|
||||
},
|
||||
'traffic.TrafficElement.Obstruction.EnvironmentalObstruction': {
|
||||
emoji: '⛔',
|
||||
|
||||
label: 'Obstruction environnementale',
|
||||
category: 'Circulation',
|
||||
description: 'Événement de type Traffic TrafficElement Obstruction EnvironmentalObstruction',
|
||||
durationHours: 200,
|
||||
properties: {
|
||||
source: { label: 'Source', writable: true },
|
||||
}
|
||||
},
|
||||
label: 'Obstruction environnementale',
|
||||
category: 'Circulation',
|
||||
description: 'Événement de type Traffic TrafficElement Obstruction EnvironmentalObstruction',
|
||||
durationHours: 200,
|
||||
properties: {
|
||||
source: {label: 'Source', writable: true},
|
||||
}
|
||||
},
|
||||
'traffic.obstacle.flood': {
|
||||
emoji: '🌊',
|
||||
label: 'Chaussée inondée',
|
||||
|
@ -379,9 +379,9 @@ const oedb = {
|
|||
description: 'Tempête (vent fort)',
|
||||
durationHours: 48,
|
||||
properties: {
|
||||
wind_speed: { label: 'Vent moyen (km/h)', writable: true },
|
||||
wind_gust: { label: 'Rafales (km/h)', writable: true },
|
||||
severity: { label: 'Sévérité', writable: true }
|
||||
wind_speed: {label: 'Vent moyen (km/h)', writable: true},
|
||||
wind_gust: {label: 'Rafales (km/h)', writable: true},
|
||||
severity: {label: 'Sévérité', writable: true}
|
||||
}
|
||||
},
|
||||
'weather.thunder': {
|
||||
|
@ -391,7 +391,7 @@ const oedb = {
|
|||
description: 'Activité orageuse',
|
||||
durationHours: 12,
|
||||
properties: {
|
||||
lightning_count: { label: 'Nombre d’éclairs', writable: true }
|
||||
lightning_count: {label: 'Nombre d’éclairs', writable: true}
|
||||
}
|
||||
}, 'weather.flood': {
|
||||
emoji: '🌊',
|
||||
|
@ -400,7 +400,7 @@ const oedb = {
|
|||
description: 'Inondation',
|
||||
durationHours: 24,
|
||||
properties: {
|
||||
flood_level: { label: 'Niveau d\'inondation', writable: true }
|
||||
flood_level: {label: 'Niveau d\'inondation', writable: true}
|
||||
}
|
||||
},
|
||||
'weather.snow': {
|
||||
|
@ -410,7 +410,7 @@ const oedb = {
|
|||
description: 'Neige',
|
||||
durationHours: 12,
|
||||
properties: {
|
||||
snow_level: { label: 'Niveau de neige', writable: true }
|
||||
snow_level: {label: 'Niveau de neige', writable: true}
|
||||
}
|
||||
},
|
||||
'weather.earthquake': {
|
||||
|
@ -420,8 +420,8 @@ const oedb = {
|
|||
description: 'Séisme',
|
||||
durationHours: 6,
|
||||
properties: {
|
||||
magnitude: { label: 'Magnitude (Mw)', writable: true },
|
||||
depth_km: { label: 'Profondeur (km)', writable: true }
|
||||
magnitude: {label: 'Magnitude (Mw)', writable: true},
|
||||
depth_km: {label: 'Profondeur (km)', writable: true}
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -433,8 +433,8 @@ const oedb = {
|
|||
description: 'Interruption d\'itinéraire',
|
||||
durationHours: 200,
|
||||
properties: {
|
||||
reason: { label: 'Raison', writable: true },
|
||||
route: { label: 'Itinéraire', writable: true },
|
||||
reason: {label: 'Raison', writable: true},
|
||||
route: {label: 'Itinéraire', writable: true},
|
||||
|
||||
}
|
||||
},
|
||||
|
@ -445,8 +445,8 @@ const oedb = {
|
|||
description: 'Mauvais sens de circulation',
|
||||
durationHours: 200,
|
||||
properties: {
|
||||
reason: { label: 'Raison', writable: true },
|
||||
route: { label: 'Itinéraire', writable: true },
|
||||
reason: {label: 'Raison', writable: true},
|
||||
route: {label: 'Itinéraire', writable: true},
|
||||
|
||||
}
|
||||
},
|
||||
|
@ -457,7 +457,7 @@ const oedb = {
|
|||
description: 'Contestation d\'itinéraire',
|
||||
durationHours: 200,
|
||||
properties: {
|
||||
route: { label: 'Itinéraire', writable: true },
|
||||
route: {label: 'Itinéraire', writable: true},
|
||||
}
|
||||
}
|
||||
// ici ajouter d'autres catégories d'évènements à suggérer
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
Middleware components for the OpenEventDatabase.
|
||||
"""
|
||||
|
||||
import falcon
|
||||
from oedb.utils.logging import logger
|
||||
|
||||
class HeaderMiddleware:
|
||||
|
@ -9,6 +10,21 @@ class HeaderMiddleware:
|
|||
Middleware that adds standard headers to all responses.
|
||||
"""
|
||||
|
||||
def process_request(self, req, resp, resource, params):
|
||||
"""
|
||||
Handle preflight OPTIONS requests for CORS.
|
||||
|
||||
Args:
|
||||
req: The request object.
|
||||
resp: The response object.
|
||||
resource: The resource object.
|
||||
params: The request parameters.
|
||||
"""
|
||||
if req.method == 'OPTIONS':
|
||||
logger.debug("Handling CORS preflight request")
|
||||
resp.status = falcon.HTTP_200
|
||||
return True # Skip further processing
|
||||
|
||||
def process_response(self, req, resp, resource, params):
|
||||
"""
|
||||
Add standard headers to the response.
|
||||
|
@ -21,7 +37,15 @@ class HeaderMiddleware:
|
|||
"""
|
||||
logger.debug("Adding standard headers to response")
|
||||
resp.set_header('X-Powered-By', 'OpenEventDatabase')
|
||||
|
||||
# CORS headers - Configuration optimisée pour embed.js
|
||||
resp.set_header('Access-Control-Allow-Origin', '*')
|
||||
resp.set_header('Access-Control-Allow-Headers', 'X-Requested-With')
|
||||
resp.set_header('Access-Control-Allow-Headers', 'Content-Type')
|
||||
resp.set_header('Access-Control-Allow-Methods','GET, POST, PUT, DELETE, OPTIONS')
|
||||
resp.set_header('Access-Control-Allow-Headers', 'X-Requested-With, Content-Type, Authorization, Accept, Origin, User-Agent, Referer')
|
||||
resp.set_header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS, HEAD')
|
||||
resp.set_header('Access-Control-Allow-Credentials', 'false')
|
||||
resp.set_header('Access-Control-Max-Age', '86400') # 24 hours
|
||||
resp.set_header('Access-Control-Expose-Headers', 'Content-Length, Content-Type, Date, Server, X-Powered-By')
|
||||
|
||||
# Headers supplémentaires pour embed.js
|
||||
resp.set_header('Vary', 'Origin')
|
||||
resp.set_header('Cache-Control', 'public, max-age=300') # Cache de 5 minutes pour les requêtes embed
|
94
test_cors_embed.py
Normal file
94
test_cors_embed.py
Normal file
|
@ -0,0 +1,94 @@
|
|||
#!/usr/bin/env python3
|
||||
"""
|
||||
Script de test pour vérifier la configuration CORS avec embed.js
|
||||
"""
|
||||
|
||||
import requests
|
||||
import json
|
||||
|
||||
def test_cors_headers():
|
||||
"""Test des headers CORS pour l'API OEDB"""
|
||||
|
||||
# URL de l'API (à adapter selon votre configuration)
|
||||
api_url = "http://localhost:8080/events"
|
||||
|
||||
print("🔍 Test de la configuration CORS pour embed.js")
|
||||
print("=" * 50)
|
||||
|
||||
# Test 1: Requête GET simple
|
||||
print("\n1. Test requête GET simple...")
|
||||
try:
|
||||
response = requests.get(api_url, timeout=10)
|
||||
print(f" Status: {response.status_code}")
|
||||
|
||||
# Vérifier les headers CORS
|
||||
cors_headers = {
|
||||
'Access-Control-Allow-Origin': response.headers.get('Access-Control-Allow-Origin'),
|
||||
'Access-Control-Allow-Headers': response.headers.get('Access-Control-Allow-Headers'),
|
||||
'Access-Control-Allow-Methods': response.headers.get('Access-Control-Allow-Methods'),
|
||||
'Access-Control-Max-Age': response.headers.get('Access-Control-Max-Age'),
|
||||
'Access-Control-Expose-Headers': response.headers.get('Access-Control-Expose-Headers')
|
||||
}
|
||||
|
||||
print(" Headers CORS:")
|
||||
for header, value in cors_headers.items():
|
||||
status = "✅" if value else "❌"
|
||||
print(f" {status} {header}: {value}")
|
||||
|
||||
except requests.exceptions.RequestException as e:
|
||||
print(f" ❌ Erreur de connexion: {e}")
|
||||
return False
|
||||
|
||||
# Test 2: Requête OPTIONS (preflight)
|
||||
print("\n2. Test requête OPTIONS (preflight)...")
|
||||
try:
|
||||
headers = {
|
||||
'Origin': 'https://example.com',
|
||||
'Access-Control-Request-Method': 'GET',
|
||||
'Access-Control-Request-Headers': 'Content-Type'
|
||||
}
|
||||
|
||||
response = requests.options(api_url, headers=headers, timeout=10)
|
||||
print(f" Status: {response.status_code}")
|
||||
|
||||
if response.status_code == 200:
|
||||
print(" ✅ Requête preflight réussie")
|
||||
else:
|
||||
print(" ❌ Requête preflight échouée")
|
||||
|
||||
except requests.exceptions.RequestException as e:
|
||||
print(f" ❌ Erreur de connexion: {e}")
|
||||
return False
|
||||
|
||||
# Test 3: Simulation d'une requête depuis embed.js
|
||||
print("\n3. Test simulation embed.js...")
|
||||
try:
|
||||
headers = {
|
||||
'Origin': 'https://example.com',
|
||||
'User-Agent': 'Mozilla/5.0 (compatible; OEDB-Embed/1.0)',
|
||||
'Referer': 'https://example.com/page-with-embed'
|
||||
}
|
||||
|
||||
response = requests.get(api_url, headers=headers, timeout=10)
|
||||
print(f" Status: {response.status_code}")
|
||||
|
||||
# Vérifier que la réponse contient des données
|
||||
if response.status_code == 200:
|
||||
try:
|
||||
data = response.json()
|
||||
print(f" ✅ Données reçues: {len(data.get('features', []))} événements")
|
||||
except json.JSONDecodeError:
|
||||
print(" ⚠️ Réponse reçue mais pas de JSON valide")
|
||||
else:
|
||||
print(f" ❌ Erreur HTTP: {response.status_code}")
|
||||
|
||||
except requests.exceptions.RequestException as e:
|
||||
print(f" ❌ Erreur de connexion: {e}")
|
||||
return False
|
||||
|
||||
print("\n" + "=" * 50)
|
||||
print("✅ Tests CORS terminés")
|
||||
return True
|
||||
|
||||
if __name__ == "__main__":
|
||||
test_cors_headers()
|
180
test_embed_cors.html
Normal file
180
test_embed_cors.html
Normal file
|
@ -0,0 +1,180 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="fr">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Test OEDB Embed avec CORS</title>
|
||||
<style>
|
||||
body {
|
||||
font-family: Arial, sans-serif;
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
padding: 20px;
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
.test-section {
|
||||
background: white;
|
||||
padding: 20px;
|
||||
margin: 20px 0;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
||||
}
|
||||
.test-title {
|
||||
color: #2c3e50;
|
||||
border-bottom: 2px solid #3498db;
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
.embed-container {
|
||||
border: 2px dashed #3498db;
|
||||
min-height: 400px;
|
||||
margin: 20px 0;
|
||||
}
|
||||
.config-display {
|
||||
background: #f8f9fa;
|
||||
padding: 15px;
|
||||
border-radius: 4px;
|
||||
font-family: monospace;
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
.status {
|
||||
padding: 10px;
|
||||
border-radius: 4px;
|
||||
margin: 10px 0;
|
||||
}
|
||||
.status.success {
|
||||
background: #d4edda;
|
||||
color: #155724;
|
||||
border: 1px solid #c3e6cb;
|
||||
}
|
||||
.status.error {
|
||||
background: #f8d7da;
|
||||
color: #721c24;
|
||||
border: 1px solid #f5c6cb;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>🧪 Test OEDB Embed avec Configuration CORS</h1>
|
||||
|
||||
<div class="test-section">
|
||||
<h2 class="test-title">Configuration du Test</h2>
|
||||
<div class="config-display" id="config-display">
|
||||
Chargement de la configuration...
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="test-section">
|
||||
<h2 class="test-title">Test de Connexion CORS</h2>
|
||||
<div id="cors-status" class="status">
|
||||
Test en cours...
|
||||
</div>
|
||||
<button onclick="testCorsConnection()">🔄 Tester la Connexion CORS</button>
|
||||
</div>
|
||||
|
||||
<div class="test-section">
|
||||
<h2 class="test-title">Intégration OEDB Embed</h2>
|
||||
<p>Configuration utilisée :</p>
|
||||
<div class="config-display" id="embed-config"></div>
|
||||
|
||||
<div class="embed-container" id="oedb-embed-container">
|
||||
<div style="text-align: center; padding: 50px; color: #666;">
|
||||
Chargement du widget OEDB...
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="test-section">
|
||||
<h2 class="test-title">Logs de Debug</h2>
|
||||
<div id="debug-logs" style="background: #2c3e50; color: #ecf0f1; padding: 15px; border-radius: 4px; font-family: monospace; max-height: 300px; overflow-y: auto;">
|
||||
<div>🚀 Initialisation du test...</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Script OEDB Embed -->
|
||||
<script src="frontend/public/embed.js"></script>
|
||||
|
||||
<script>
|
||||
// Configuration de test
|
||||
const testConfig = {
|
||||
apiUrl: 'http://localhost:8080', // Ajustez selon votre configuration
|
||||
theme: 'light',
|
||||
limit: 10,
|
||||
width: '100%',
|
||||
height: '400px',
|
||||
showMap: true,
|
||||
showList: true,
|
||||
autoRefresh: false
|
||||
};
|
||||
|
||||
// Fonction de logging
|
||||
function log(message, type = 'info') {
|
||||
const timestamp = new Date().toLocaleTimeString();
|
||||
const logElement = document.getElementById('debug-logs');
|
||||
const icon = type === 'error' ? '❌' : type === 'success' ? '✅' : 'ℹ️';
|
||||
logElement.innerHTML += `<div>[${timestamp}] ${icon} ${message}</div>`;
|
||||
logElement.scrollTop = logElement.scrollHeight;
|
||||
}
|
||||
|
||||
// Test de connexion CORS
|
||||
async function testCorsConnection() {
|
||||
const statusElement = document.getElementById('cors-status');
|
||||
statusElement.innerHTML = 'Test en cours...';
|
||||
statusElement.className = 'status';
|
||||
|
||||
log('Début du test CORS...');
|
||||
|
||||
try {
|
||||
const response = await fetch(`${testConfig.apiUrl}/events?limit=1`);
|
||||
|
||||
if (response.ok) {
|
||||
const data = await response.json();
|
||||
statusElement.innerHTML = `✅ Connexion CORS réussie ! (${data.features?.length || 0} événements)`;
|
||||
statusElement.className = 'status success';
|
||||
log('Connexion CORS réussie', 'success');
|
||||
} else {
|
||||
statusElement.innerHTML = `❌ Erreur HTTP: ${response.status}`;
|
||||
statusElement.className = 'status error';
|
||||
log(`Erreur HTTP: ${response.status}`, 'error');
|
||||
}
|
||||
} catch (error) {
|
||||
statusElement.innerHTML = `❌ Erreur de connexion: ${error.message}`;
|
||||
statusElement.className = 'status error';
|
||||
log(`Erreur de connexion: ${error.message}`, 'error');
|
||||
}
|
||||
}
|
||||
|
||||
// Initialisation
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
log('Page chargée, initialisation...');
|
||||
|
||||
// Afficher la configuration
|
||||
document.getElementById('config-display').textContent = JSON.stringify(testConfig, null, 2);
|
||||
document.getElementById('embed-config').textContent = JSON.stringify(testConfig, null, 2);
|
||||
|
||||
// Test automatique de connexion
|
||||
setTimeout(testCorsConnection, 1000);
|
||||
|
||||
// Initialiser le widget OEDB
|
||||
setTimeout(() => {
|
||||
try {
|
||||
log('Initialisation du widget OEDB...');
|
||||
|
||||
if (window.OEDBEmbed) {
|
||||
const embed = new OEDBEmbed(document.getElementById('oedb-embed-container'), testConfig);
|
||||
log('Widget OEDB initialisé avec succès', 'success');
|
||||
|
||||
// Écouter les événements du widget
|
||||
document.getElementById('oedb-embed-container').addEventListener('oedb-event-click', function(event) {
|
||||
log(`Événement cliqué: ${event.detail.eventId}`);
|
||||
});
|
||||
} else {
|
||||
log('Erreur: OEDBEmbed non disponible', 'error');
|
||||
}
|
||||
} catch (error) {
|
||||
log(`Erreur lors de l'initialisation: ${error.message}`, 'error');
|
||||
}
|
||||
}, 2000);
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
Loading…
Add table
Add a link
Reference in a new issue