Files
Lucas/mkdocs-smarteye/upload.php
Debian 24dbc7cd6a Initial commit — Serveur Lucas SmartEye
API réception alertes chute (SmartEye/YOLO), analyse IA (Gemini 2.5 Flash),
gestion alertes avec escalade (watchdog), notifications Firebase,
dashboard web, documentation MkDocs.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-14 21:26:06 +01:00

416 lines
14 KiB
PHP

<?php
/**
* Upload d'images pour la documentation MkDocs SmartEye
* Supporte : drag & drop, sélection de fichier, collage (Cmd+V / Ctrl+V)
*
* Accès : https://lucas.unigest.fr/docs-upload/upload.php
*/
// Sécurité basique — à adapter si besoin
$UPLOAD_DIR = __DIR__ . '/docs/assets/images/';
$ALLOWED_EXTENSIONS = ['jpg', 'jpeg', 'png', 'gif', 'webp', 'svg'];
$MAX_SIZE = 10 * 1024 * 1024; // 10 MB
// Sous-dossiers disponibles
$SUBDIRS = ['general', 'architecture', 'guide', 'deploiement', 'api', 'app'];
// Traitement de l'upload
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_FILES['image'])) {
header('Content-Type: application/json');
$file = $_FILES['image'];
$subdir = isset($_POST['subdir']) && in_array($_POST['subdir'], $SUBDIRS) ? $_POST['subdir'] : 'general';
$targetDir = $UPLOAD_DIR . $subdir . '/';
// Vérifications
if ($file['error'] !== UPLOAD_ERR_OK) {
echo json_encode(['success' => false, 'error' => 'Erreur upload: ' . $file['error']]);
exit;
}
if ($file['size'] > $MAX_SIZE) {
echo json_encode(['success' => false, 'error' => 'Fichier trop volumineux (max 10 MB)']);
exit;
}
// Déterminer l'extension
$originalName = $file['name'];
$ext = strtolower(pathinfo($originalName, PATHINFO_EXTENSION));
// Si c'est un collage (blob), détecter le type MIME
if (empty($ext) || $ext === 'blob') {
$mime = mime_content_type($file['tmp_name']);
$mimeMap = [
'image/png' => 'png',
'image/jpeg' => 'jpg',
'image/gif' => 'gif',
'image/webp' => 'webp',
'image/svg+xml' => 'svg',
];
$ext = $mimeMap[$mime] ?? '';
}
if (!in_array($ext, $ALLOWED_EXTENSIONS)) {
echo json_encode(['success' => false, 'error' => "Extension non autorisée: $ext"]);
exit;
}
// Nom du fichier : soit le nom custom, soit le nom original, soit un timestamp
$customName = isset($_POST['filename']) ? trim($_POST['filename']) : '';
if ($customName) {
// Nettoyer le nom
$customName = preg_replace('/[^a-zA-Z0-9_\-]/', '-', $customName);
$filename = $customName . '.' . $ext;
} elseif ($originalName && $originalName !== 'blob' && $originalName !== 'image.png') {
$filename = preg_replace('/[^a-zA-Z0-9_\-\.]/', '-', $originalName);
} else {
$filename = date('Y-m-d_H-i-s') . '.' . $ext;
}
$targetPath = $targetDir . $filename;
// Éviter l'écrasement
if (file_exists($targetPath)) {
$base = pathinfo($filename, PATHINFO_FILENAME);
$filename = $base . '_' . time() . '.' . $ext;
$targetPath = $targetDir . $filename;
}
if (move_uploaded_file($file['tmp_name'], $targetPath)) {
$mdPath = "assets/images/$subdir/$filename";
echo json_encode([
'success' => true,
'filename' => $filename,
'subdir' => $subdir,
'markdown' => "![Description]($mdPath)",
'path' => $mdPath,
]);
} else {
echo json_encode(['success' => false, 'error' => 'Impossible de déplacer le fichier']);
}
exit;
}
// Lister les images existantes
$existingImages = [];
foreach ($SUBDIRS as $sd) {
$dir = $UPLOAD_DIR . $sd . '/';
if (is_dir($dir)) {
$files = scandir($dir);
foreach ($files as $f) {
if ($f === '.' || $f === '..') continue;
$ext = strtolower(pathinfo($f, PATHINFO_EXTENSION));
if (in_array($ext, $ALLOWED_EXTENSIONS)) {
$existingImages[] = [
'name' => $f,
'subdir' => $sd,
'path' => "assets/images/$sd/$f",
'url' => "docs/assets/images/$sd/$f",
'size' => filesize($dir . $f),
];
}
}
}
}
// Image à la racine de images/
$rootDir = $UPLOAD_DIR;
if (is_dir($rootDir)) {
$files = scandir($rootDir);
foreach ($files as $f) {
if ($f === '.' || $f === '..' || is_dir($rootDir . $f)) continue;
$ext = strtolower(pathinfo($f, PATHINFO_EXTENSION));
if (in_array($ext, $ALLOWED_EXTENSIONS)) {
$existingImages[] = [
'name' => $f,
'subdir' => '(racine)',
'path' => "assets/images/$f",
'url' => "docs/assets/images/$f",
'size' => filesize($rootDir . $f),
];
}
}
}
?>
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Upload Images — Documentation SmartEye</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
font-family: 'Inter', -apple-system, sans-serif;
background: #1a1a2e;
color: #e0e0e0;
min-height: 100vh;
padding: 2rem;
}
h1 { color: #bb86fc; margin-bottom: 0.5rem; }
.subtitle { color: #888; margin-bottom: 2rem; }
.upload-zone {
border: 3px dashed #bb86fc44;
border-radius: 16px;
padding: 3rem 2rem;
text-align: center;
transition: all 0.3s;
cursor: pointer;
margin-bottom: 2rem;
background: #16213e;
}
.upload-zone:hover, .upload-zone.dragover {
border-color: #bb86fc;
background: #1a1a3e;
transform: scale(1.01);
}
.upload-zone .icon { font-size: 3rem; margin-bottom: 1rem; }
.upload-zone p { color: #aaa; }
.upload-zone .hint { font-size: 0.85rem; color: #666; margin-top: 0.5rem; }
.controls {
display: flex;
gap: 1rem;
margin-bottom: 2rem;
flex-wrap: wrap;
align-items: center;
}
select, input[type="text"] {
padding: 0.6rem 1rem;
border-radius: 8px;
border: 1px solid #333;
background: #16213e;
color: #e0e0e0;
font-size: 0.95rem;
}
select:focus, input:focus { outline: none; border-color: #bb86fc; }
input[type="text"] { flex: 1; min-width: 200px; }
.result {
display: none;
background: #0f3460;
border-radius: 12px;
padding: 1.5rem;
margin-bottom: 2rem;
}
.result.show { display: block; }
.result.error { background: #3d0000; }
.result .md-code {
background: #1a1a2e;
padding: 0.8rem 1rem;
border-radius: 8px;
font-family: 'JetBrains Mono', monospace;
font-size: 0.9rem;
cursor: pointer;
display: flex;
justify-content: space-between;
align-items: center;
margin-top: 0.5rem;
}
.result .md-code:hover { background: #222244; }
.result .md-code .copy-btn {
background: #bb86fc;
color: #000;
border: none;
padding: 0.3rem 0.8rem;
border-radius: 6px;
cursor: pointer;
font-size: 0.8rem;
}
.preview-img {
max-width: 300px;
max-height: 200px;
border-radius: 8px;
margin-top: 1rem;
}
h2 { color: #bb86fc; margin: 2rem 0 1rem; }
.gallery {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
gap: 1rem;
}
.gallery-item {
background: #16213e;
border-radius: 12px;
padding: 0.8rem;
text-align: center;
}
.gallery-item img {
max-width: 100%;
max-height: 150px;
border-radius: 8px;
object-fit: contain;
}
.gallery-item .name {
font-size: 0.8rem;
color: #aaa;
margin-top: 0.5rem;
word-break: break-all;
}
.gallery-item .badge {
display: inline-block;
background: #bb86fc22;
color: #bb86fc;
padding: 0.15rem 0.5rem;
border-radius: 4px;
font-size: 0.7rem;
margin-top: 0.3rem;
}
.gallery-item .md-snippet {
font-size: 0.7rem;
color: #666;
cursor: pointer;
margin-top: 0.3rem;
}
.gallery-item .md-snippet:hover { color: #bb86fc; }
.empty { color: #555; text-align: center; padding: 2rem; }
.toast {
position: fixed;
bottom: 2rem;
right: 2rem;
background: #bb86fc;
color: #000;
padding: 0.8rem 1.5rem;
border-radius: 8px;
font-weight: 600;
transform: translateY(100px);
opacity: 0;
transition: all 0.3s;
}
.toast.show { transform: translateY(0); opacity: 1; }
</style>
</head>
<body>
<h1>Upload Images</h1>
<p class="subtitle">Documentation SmartEye — Glissez, cliquez ou collez (Cmd+V) une image</p>
<div class="controls">
<select id="subdir">
<?php foreach ($SUBDIRS as $sd): ?>
<option value="<?= $sd ?>"><?= ucfirst($sd) ?></option>
<?php endforeach; ?>
</select>
<input type="text" id="filename" placeholder="Nom du fichier (optionnel, sans extension)">
</div>
<div class="upload-zone" id="dropzone">
<div class="icon">📸</div>
<p><strong>Glissez une image ici</strong> ou cliquez pour sélectionner</p>
<p class="hint">Cmd+V pour coller depuis le presse-papiers &bull; JPG, PNG, GIF, WebP, SVG &bull; Max 10 MB</p>
<input type="file" id="fileInput" accept="image/*" style="display:none">
</div>
<div class="result" id="result"></div>
<h2>Images existantes (<?= count($existingImages) ?>)</h2>
<?php if (empty($existingImages)): ?>
<p class="empty">Aucune image pour l'instant.</p>
<?php else: ?>
<div class="gallery">
<?php foreach ($existingImages as $img): ?>
<div class="gallery-item">
<?php if (in_array(pathinfo($img['name'], PATHINFO_EXTENSION), ['svg'])): ?>
<img src="<?= htmlspecialchars($img['url']) ?>" alt="<?= htmlspecialchars($img['name']) ?>">
<?php else: ?>
<img src="<?= htmlspecialchars($img['url']) ?>" alt="<?= htmlspecialchars($img['name']) ?>" loading="lazy">
<?php endif; ?>
<div class="name"><?= htmlspecialchars($img['name']) ?></div>
<span class="badge"><?= htmlspecialchars($img['subdir']) ?></span>
<div class="md-snippet" onclick="copyText('![](<?= htmlspecialchars($img['path']) ?>)')">
Copier le markdown
</div>
</div>
<?php endforeach; ?>
</div>
<?php endif; ?>
<div class="toast" id="toast">Copié !</div>
<script>
const dropzone = document.getElementById('dropzone');
const fileInput = document.getElementById('fileInput');
const resultDiv = document.getElementById('result');
// Click to select
dropzone.addEventListener('click', () => fileInput.click());
fileInput.addEventListener('change', () => {
if (fileInput.files[0]) uploadFile(fileInput.files[0]);
});
// Drag & drop
dropzone.addEventListener('dragover', (e) => {
e.preventDefault();
dropzone.classList.add('dragover');
});
dropzone.addEventListener('dragleave', () => dropzone.classList.remove('dragover'));
dropzone.addEventListener('drop', (e) => {
e.preventDefault();
dropzone.classList.remove('dragover');
const file = e.dataTransfer.files[0];
if (file) uploadFile(file);
});
// Paste (Cmd+V)
document.addEventListener('paste', (e) => {
const items = e.clipboardData.items;
for (let item of items) {
if (item.type.startsWith('image/')) {
const file = item.getAsFile();
if (file) uploadFile(file);
return;
}
}
});
function uploadFile(file) {
const formData = new FormData();
formData.append('image', file);
formData.append('subdir', document.getElementById('subdir').value);
const customName = document.getElementById('filename').value.trim();
if (customName) formData.append('filename', customName);
resultDiv.className = 'result show';
resultDiv.innerHTML = '<p>Upload en cours...</p>';
fetch('upload.php', { method: 'POST', body: formData })
.then(r => r.json())
.then(data => {
if (data.success) {
resultDiv.className = 'result show';
resultDiv.innerHTML = `
<p><strong>Upload réussi !</strong> — ${data.filename} dans <em>${data.subdir}/</em></p>
<div class="md-code">
<code>${data.markdown}</code>
<button class="copy-btn" onclick="copyText('${data.markdown}')">Copier</button>
</div>
<img src="docs/assets/images/${data.subdir}/${data.filename}" class="preview-img" alt="Preview">
`;
// Reset le nom custom
document.getElementById('filename').value = '';
} else {
resultDiv.className = 'result show error';
resultDiv.innerHTML = `<p><strong>Erreur :</strong> ${data.error}</p>`;
}
})
.catch(err => {
resultDiv.className = 'result show error';
resultDiv.innerHTML = `<p><strong>Erreur réseau :</strong> ${err.message}</p>`;
});
}
function copyText(text) {
navigator.clipboard.writeText(text).then(() => {
const toast = document.getElementById('toast');
toast.classList.add('show');
setTimeout(() => toast.classList.remove('show'), 1500);
});
}
</script>
</body>
</html>