add panoramax token
This commit is contained in:
parent
f66e5e3f7b
commit
1a3df2ed75
12 changed files with 1132 additions and 61 deletions
|
|
@ -8,16 +8,13 @@
|
|||
<link href="https://unpkg.com/maplibre-gl@3.0.0/dist/maplibre-gl.css" rel="stylesheet" />
|
||||
<link rel="stylesheet" href="/static/demo_styles.css">
|
||||
<script defer src="https://use.fontawesome.com/releases/v5.15.4/js/all.js"></script>
|
||||
<link rel="stylesheet" href="/static/traffic.css">
|
||||
<script src="/static/demo_auth.js"></script>
|
||||
<script src="/static/traffic.js" defer></script>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<div class="nav-links">
|
||||
<a href="/demo">← Back to Map</a>
|
||||
<a href="/">API Information</a>
|
||||
<a href="/event">View Events</a>
|
||||
<a href="/demo/view-events">View Saved Events</a>
|
||||
</div>
|
||||
{% include 'partials/demo_nav.html' %}
|
||||
|
||||
<h1>Report Road Issue</h1>
|
||||
|
||||
|
|
@ -25,6 +22,9 @@
|
|||
<input type="hidden" id="osmClientId" value="{{ client_id }}">
|
||||
<input type="hidden" id="osmClientSecret" value="{{ client_secret }}">
|
||||
<input type="hidden" id="osmRedirectUri" value="{{ client_redirect }}">
|
||||
<!-- Hidden Panoramax configuration (upload endpoint and token) -->
|
||||
<input type="hidden" id="panoramaxUploadUrl" value="{{ panoramax_upload_url }}">
|
||||
<input type="hidden" id="panoramaxToken" value="{{ panoramax_token }}">
|
||||
|
||||
<!-- Authentication section will be rendered by JavaScript or server-side -->
|
||||
<div id="auth-section">
|
||||
|
|
@ -46,17 +46,15 @@
|
|||
{% endif %}
|
||||
|
||||
<script>
|
||||
// Replace server-side auth section with JavaScript-rendered version if available
|
||||
// Préserve l'affichage serveur si présent, sinon laisse traffic.js/dema_auth gérer
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
if (window.osmAuth) {
|
||||
const hasServerAuth = document.getElementById('osmUsername') && document.getElementById('osmUsername').value;
|
||||
if (hasServerAuth) return;
|
||||
if (window.osmAuth && osmAuth.renderAuthSection) {
|
||||
const clientId = document.getElementById('osmClientId').value;
|
||||
const redirectUri = document.getElementById('osmRedirectUri').value;
|
||||
const authSection = document.getElementById('auth-section');
|
||||
|
||||
// Only replace if osmAuth is loaded and has renderAuthSection method
|
||||
if (osmAuth.renderAuthSection) {
|
||||
authSection.innerHTML = osmAuth.renderAuthSection(clientId, redirectUri);
|
||||
}
|
||||
authSection.innerHTML = osmAuth.renderAuthSection(clientId, redirectUri);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
|
@ -67,16 +65,19 @@
|
|||
<!-- Tab Navigation -->
|
||||
<div class="tabs">
|
||||
<div class="tab-item active" data-tab="road">
|
||||
<i class="fas fa-road"></i> Road
|
||||
<i class="fas fa-road"></i> Route
|
||||
</div>
|
||||
<div class="tab-item" data-tab="rail">
|
||||
<i class="fas fa-train"></i> Rail
|
||||
</div>
|
||||
<div class="tab-item" data-tab="weather">
|
||||
<i class="fas fa-cloud-sun-rain"></i> Weather
|
||||
<i class="fas fa-cloud-sun-rain"></i> Météo
|
||||
</div>
|
||||
<div class="tab-item" data-tab="emergency">
|
||||
<i class="fas fa-exclamation-circle"></i> Emergency
|
||||
<i class="fas fa-exclamation-circle"></i> Urgences
|
||||
</div>
|
||||
<div class="tab-item" data-tab="civic">
|
||||
<i class="fas fa-bicycle"></i> Cycles
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
@ -95,7 +96,7 @@
|
|||
</div>
|
||||
<div class="issue-button road vehicle" onclick="fillForm('vehicle')">
|
||||
<i class="fas fa-car"></i>
|
||||
Vehicle on Side
|
||||
Véhicule sur le bas côté de la route
|
||||
</div>
|
||||
<div class="issue-button road danger" onclick="fillForm('danger')">
|
||||
<i class="fas fa-skull-crossbones"></i>
|
||||
|
|
@ -107,15 +108,15 @@
|
|||
</div>
|
||||
<div class="issue-button road flooded-road" onclick="fillForm('flooded_road')">
|
||||
<i class="fas fa-water"></i>
|
||||
Flooded Road
|
||||
Route inondée
|
||||
</div>
|
||||
<div class="issue-button road roadwork" onclick="fillForm('roadwork')">
|
||||
<i class="fas fa-hard-hat"></i>
|
||||
Roadwork
|
||||
Travaux
|
||||
</div>
|
||||
<div class="issue-button road black-traffic" onclick="fillForm('black_traffic')">
|
||||
<i class="fas fa-traffic-light"></i>
|
||||
Black Traffic
|
||||
journée noire bison futé
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -125,15 +126,15 @@
|
|||
<div class="issue-buttons">
|
||||
<div class="issue-button rail unattended-luggage" onclick="fillForm('unattended_luggage')">
|
||||
<i class="fas fa-suitcase"></i>
|
||||
Unattended Luggage
|
||||
Bagage abandonné
|
||||
</div>
|
||||
<div class="issue-button rail transport-delay" onclick="fillForm('transport_delay')">
|
||||
<i class="fas fa-hourglass-half"></i>
|
||||
Delay
|
||||
Retard
|
||||
</div>
|
||||
<div class="issue-button rail major-delay" onclick="fillForm('major_transport_delay')">
|
||||
<i class="fas fa-hourglass-end"></i>
|
||||
Major Delay
|
||||
Retard important
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -143,15 +144,15 @@
|
|||
<div class="issue-buttons">
|
||||
<div class="issue-button weather flood-danger" onclick="fillForm('flood_danger')">
|
||||
<i class="fas fa-water"></i>
|
||||
Flood Alert
|
||||
Vigilance rouge inondation
|
||||
</div>
|
||||
<div class="issue-button weather thunderstorm" onclick="fillForm('thunderstorm_alert')">
|
||||
<i class="fas fa-bolt"></i>
|
||||
Thunderstorm
|
||||
Vigilance orange orages
|
||||
</div>
|
||||
<div class="issue-button weather fog" onclick="fillForm('fog_warning')">
|
||||
<i class="fas fa-smog"></i>
|
||||
Fog Warning
|
||||
Vigilance jaune brouillard
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -161,11 +162,25 @@
|
|||
<div class="issue-buttons">
|
||||
<div class="issue-button emergency emergency-alert" onclick="fillForm('emergency_alert')">
|
||||
<i class="fas fa-exclamation-circle"></i>
|
||||
Emergency Alert
|
||||
Alerte d'urgence (SAIP)
|
||||
</div>
|
||||
<div class="issue-button emergency daylight-saving" onclick="fillForm('daylight_saving')">
|
||||
<i class="fas fa-clock"></i>
|
||||
Daylight Saving
|
||||
Période d'heure d'été
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Civic Tab -->
|
||||
<div class="tab-pane" id="civic-tab">
|
||||
<div class="issue-buttons">
|
||||
<div class="issue-button civic bike-obstacle" onclick="fillForm('bike_obstacle')">
|
||||
<i class="fas fa-bicycle"></i>
|
||||
Obstacle vélo
|
||||
</div>
|
||||
<div class="issue-button civic illegal-dumping" onclick="fillForm('illegal_dumping')">
|
||||
<i class="fas fa-trash"></i>
|
||||
Décharge sauvage
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -173,12 +188,45 @@
|
|||
|
||||
<button id="geolocateBtn" class="geolocation-btn">
|
||||
<span id="geolocateSpinner" class="loading" style="display: none;"></span>
|
||||
Get My Current Location
|
||||
Obtenir ma position actuelle
|
||||
</button>
|
||||
<span id="gpsStatus" class="gps-status" title="État GPS">GPS: inconnu</span>
|
||||
|
||||
<form id="trafficForm">
|
||||
<div class="form-group">
|
||||
<label for="label" class="required">Issue Description</label>
|
||||
<label for="photo">Photo (optionnelle)</label>
|
||||
<input type="file" id="photo" name="photo" accept="image/*" capture="environment">
|
||||
<div class="note">Prenez une photo géolocalisée de la situation (mobile recommandé)</div>
|
||||
<div id="photoPreviewContainer" style="margin-top:8px; display:none;">
|
||||
<img id="photoPreview" alt="Aperçu photo" style="max-width:100%; border-radius:4px;"/>
|
||||
</div>
|
||||
<div class="form-row" style="margin-top:8px;">
|
||||
<div class="form-group">
|
||||
<label for="panoramaxTokenInput">Token Panoramax</label>
|
||||
<input type="password" id="panoramaxTokenInput" placeholder="Jeton d'API Panoramax">
|
||||
<div class="note">Stocké en local sur cet appareil. Utilisé pour envoyer la photo.</div>
|
||||
</div>
|
||||
<div class="form-group" style="align-self:flex-end;">
|
||||
<button type="button" id="savePanoramaxTokenBtn">Enregistrer le token</button>
|
||||
<button type="button" id="showPanoramaxTokenBtn" style="display:none;">Modifier le token</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="camera-block">
|
||||
<label>Prendre une photo avec la caméra</label>
|
||||
<div class="camera-controls">
|
||||
<button type="button" id="startCameraBtn">Démarrer la caméra</button>
|
||||
<button type="button" id="capturePhotoBtn" disabled>Prendre la photo</button>
|
||||
<button type="button" id="stopCameraBtn" disabled>Arrêter</button>
|
||||
</div>
|
||||
<div class="camera-preview">
|
||||
<video id="cameraVideo" autoplay playsinline muted></video>
|
||||
<canvas id="cameraCanvas" style="display:none;"></canvas>
|
||||
</div>
|
||||
<div class="note">La photo capturée sera ajoutée au champ ci-dessus.</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="label" class="required">Description du problème</label>
|
||||
<input type="text" id="label" name="label" placeholder="e.g., Large pothole on Highway A1" required>
|
||||
</div>
|
||||
|
||||
|
|
@ -186,50 +234,50 @@
|
|||
|
||||
<div class="form-row">
|
||||
<div class="form-group">
|
||||
<label for="severity" class="required">Severity</label>
|
||||
<label for="severity" class="required">Gravité</label>
|
||||
<select id="severity" name="severity" required>
|
||||
<option value="low">Low (Minor issue)</option>
|
||||
<option value="medium" selected>Medium (Moderate issue)</option>
|
||||
<option value="high">High (Severe issue)</option>
|
||||
<option value="low">Faible (Problème mineur)</option>
|
||||
<option value="medium" selected>Moyen (Problème modéré)</option>
|
||||
<option value="high">Élevé (Problème grave)</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="cause">Additional Details</label>
|
||||
<label for="cause">Détails supplémentaires</label>
|
||||
<input type="text" id="cause" name="cause" placeholder="e.g., Size, specific location details">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-row">
|
||||
<div class="form-group">
|
||||
<label for="start" class="required">Report Time</label>
|
||||
<label for="start" class="required">Heure de début</label>
|
||||
<input type="datetime-local" id="start" name="start" required value="">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="stop" class="required">Estimated Clear Time</label>
|
||||
<label for="stop" class="required">Heure estimée de fin</label>
|
||||
<input type="datetime-local" id="stop" name="stop" required value="">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="where">Road/Location Name</label>
|
||||
<label for="where">Route/Nom du lieu</label>
|
||||
<input type="text" id="where" name="where" placeholder="e.g., Highway A1, Main Street">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="required">Location</label>
|
||||
<div id="map"></div>
|
||||
<div class="note">Click on the map to set the issue location or use the "Get My Current Location" button</div>
|
||||
<div class="note">Cliquez sur la carte pour définir la localisation du problème ou utilisez le bouton "Obtenir ma position actuelle"</div>
|
||||
</div>
|
||||
|
||||
<button id="report_issue_button" type="submit" disabled>Report Issue</button>
|
||||
<button id="report_issue_button" type="submit" disabled>Signaler le problème</button>
|
||||
</form>
|
||||
|
||||
<div id="result"></div>
|
||||
|
||||
<a href="/demo/view-events" class="view-saved-events">
|
||||
<i class="fas fa-map-marked-alt"></i> View All Saved Events on Map
|
||||
<i class="fas fa-map-marked-alt"></i> Voir tous les événements enregistrés sur la carte
|
||||
</a>
|
||||
</div>
|
||||
|
||||
|
|
@ -242,9 +290,9 @@
|
|||
// Set start time to current time
|
||||
document.getElementById('start').value = nowISO;
|
||||
|
||||
// Set end time to current time + 1 hour
|
||||
const oneHourLater = new Date(now.getTime() + 60 * 60 * 1000);
|
||||
document.getElementById('stop').value = oneHourLater.toISOString().slice(0, 16);
|
||||
// Set end time to current time + 6 hours (durée par défaut des signalements)
|
||||
const sixHoursLater = new Date(now.getTime() + 6 * 60 * 60 * 1000);
|
||||
document.getElementById('stop').value = sixHoursLater.toISOString().slice(0, 16);
|
||||
}
|
||||
|
||||
// Call function to set default dates
|
||||
|
|
@ -442,6 +490,18 @@
|
|||
let markerColor = '#ff3860'; // Default red color
|
||||
|
||||
switch(issueType) {
|
||||
case 'bike_obstacle':
|
||||
labelInput.value = 'Obstacle vélo';
|
||||
issueTypeInput.value = 'mobility.cycling.obstacle';
|
||||
severitySelect.value = 'medium';
|
||||
markerColor = '#388e3c'; // Green
|
||||
break;
|
||||
case 'illegal_dumping':
|
||||
labelInput.value = 'Décharge sauvage';
|
||||
issueTypeInput.value = 'environment.dumping.illegal';
|
||||
severitySelect.value = 'medium';
|
||||
markerColor = '#795548'; // Brown
|
||||
break;
|
||||
case 'pothole':
|
||||
labelInput.value = 'Nid de poule';
|
||||
issueTypeInput.value = 'traffic.hazard.pothole';
|
||||
|
|
@ -721,8 +781,64 @@
|
|||
setTimeout(validateForm, 100);
|
||||
});
|
||||
|
||||
// Photo preview
|
||||
const photoInput = document.getElementById('photo');
|
||||
if (photoInput) {
|
||||
photoInput.addEventListener('change', function() {
|
||||
const file = this.files && this.files[0];
|
||||
if (!file) {
|
||||
document.getElementById('photoPreviewContainer').style.display = 'none';
|
||||
return;
|
||||
}
|
||||
const url = URL.createObjectURL(file);
|
||||
const img = document.getElementById('photoPreview');
|
||||
img.src = url;
|
||||
document.getElementById('photoPreviewContainer').style.display = 'block';
|
||||
});
|
||||
}
|
||||
|
||||
async function uploadPhotoIfConfigured(file, lng, lat, isoDatetime) {
|
||||
try {
|
||||
const uploadUrl = document.getElementById('panoramaxUploadUrl')?.value || '';
|
||||
const token = document.getElementById('panoramaxToken')?.value || '';
|
||||
if (!uploadUrl || !file) {
|
||||
return null; // pas configuré ou pas de fichier
|
||||
}
|
||||
const form = new FormData();
|
||||
form.append('file', file, file.name || 'photo.jpg');
|
||||
// Métadonnées géo/temps standard
|
||||
if (typeof lng === 'number' && typeof lat === 'number') {
|
||||
form.append('lon', String(lng));
|
||||
form.append('lat', String(lat));
|
||||
}
|
||||
if (isoDatetime) {
|
||||
form.append('datetime', isoDatetime);
|
||||
}
|
||||
const headers = {};
|
||||
if (token) {
|
||||
headers['Authorization'] = `Bearer ${token}`;
|
||||
}
|
||||
const res = await fetch(uploadUrl, { method: 'POST', headers, body: form });
|
||||
if (!res.ok) {
|
||||
const t = await res.text();
|
||||
throw new Error(t || `Upload failed (${res.status})`);
|
||||
}
|
||||
const data = await res.json().catch(() => ({}));
|
||||
// On essaie de normaliser quelques champs courants
|
||||
return {
|
||||
id: data.id || data.uuid || data.photo_id || null,
|
||||
url: data.url || data.permalink || data.link || null,
|
||||
raw: data
|
||||
};
|
||||
} catch (err) {
|
||||
console.error('Panoramax upload error:', err);
|
||||
showResult(`Erreur upload photo: ${err.message}`, 'error');
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// Handle form submission
|
||||
document.getElementById('trafficForm').addEventListener('submit', function(e) {
|
||||
document.getElementById('trafficForm').addEventListener('submit', async function(e) {
|
||||
e.preventDefault();
|
||||
|
||||
// Validate form before submission
|
||||
|
|
@ -795,6 +911,18 @@
|
|||
console.log(`Including OSM username in report: ${osmUsernameValue}`);
|
||||
}
|
||||
|
||||
// Upload photo to Panoramax si configuré
|
||||
let photoInfo = null;
|
||||
const photoFile = (photoInput && photoInput.files && photoInput.files[0]) ? photoInput.files[0] : null;
|
||||
if (photoFile) {
|
||||
photoInfo = await uploadPhotoIfConfigured(photoFile, lngLat.lng, lngLat.lat, start);
|
||||
if (photoInfo) {
|
||||
event.properties['photo:service'] = 'panoramax';
|
||||
if (photoInfo.id) event.properties['photo:id'] = String(photoInfo.id);
|
||||
if (photoInfo.url) event.properties['photo:url'] = photoInfo.url;
|
||||
}
|
||||
}
|
||||
|
||||
// Save event to localStorage
|
||||
saveEventToLocalStorage(event);
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue