feat: viewer 3D + parser 3MF par plaque + échecs filament + portail client
Deploy via Portainer / deploy (push) Successful in 0s

This commit is contained in:
Jo
2026-07-07 08:58:12 +02:00
parent e410bfbc16
commit 7b65c4b2e2
11 changed files with 1508 additions and 69 deletions
+105
View File
@@ -194,6 +194,83 @@
</div>
</div>
</div>
<div class="col-12">
<div class="card">
<div class="card-header py-3"><i class="bi bi-cloud me-2"></i>Nextcloud — Intégration fichiers 3D</div>
<div class="card-body">
<div class="row g-3">
<div class="col-md-6">
<label class="form-label fw-semibold">URL Nextcloud</label>
<input type="url" name="nc_url" class="form-control"
placeholder="https://nextcloud.tondomain.com"
value="{{ settings.get('nc_url','') }}">
<div class="form-text">Sans slash final.</div>
</div>
<div class="col-md-3">
<label class="form-label fw-semibold">Identifiant</label>
<input type="text" name="nc_username" class="form-control"
placeholder="ton_login"
value="{{ settings.get('nc_username','') }}">
</div>
<div class="col-md-3">
<label class="form-label fw-semibold">Mot de passe d'application</label>
<input type="password" name="nc_app_password" class="form-control"
placeholder="••••••••••••"
value="{{ settings.get('nc_app_password','') }}">
<div class="form-text">Générer dans Nextcloud → Paramètres → Sécurité.</div>
</div>
<div class="col-md-4">
<label class="form-label fw-semibold">Dossier racine 3D</label>
<input type="text" name="nc_root_path" class="form-control"
placeholder="/Impressions3D"
value="{{ settings.get('nc_root_path','/3D') }}">
<div class="form-text">Chemin de base pour l'explorateur de fichiers.</div>
</div>
<div class="col-md-8 d-flex align-items-end">
<button type="button" class="btn btn-sm btn-outline-secondary" id="testNcBtn">
<i class="bi bi-plug me-1"></i>Tester la connexion
</button>
<span id="testNcResult" class="ms-3 small"></span>
</div>
</div>
</div>
</div>
</div>
<!-- iCal -->
<div class="col-12">
<div class="card">
<div class="card-header py-3"><i class="bi bi-calendar-check me-2"></i>Calendrier iCal — Abonnement Android / iOS</div>
<div class="card-body">
<div class="row g-3 align-items-end">
<div class="col-md-4">
<label class="form-label fw-semibold">Token secret (optionnel)</label>
<input type="text" name="ical_token" class="form-control font-monospace"
placeholder="Laisser vide = pas de protection"
value="{{ settings.get('ical_token','') }}">
<div class="form-text">Si renseigné, l'URL contiendra <code>?token=xxx</code>.</div>
</div>
<div class="col-md-8">
<label class="form-label fw-semibold">URL d'abonnement</label>
<div class="input-group">
<input type="text" id="icalUrl" class="form-control font-monospace small" readonly
value="{{ request.host_url }}calendar.ics{% if settings.get('ical_token','') %}?token={{ settings.get('ical_token','') }}{% endif %}">
<button class="btn btn-outline-secondary" type="button" id="copyIcal" title="Copier">
<i class="bi bi-clipboard"></i>
</button>
</div>
<div class="form-text">
Dans Google Calendar → Autres agendas → Depuis une URL.
Samsung / iOS : Paramètres → Comptes → Ajouter → Calendrier.
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="mt-4">
@@ -203,3 +280,31 @@
</div>
</form>
{% endblock %}
{% block scripts %}
<script>
document.getElementById('copyIcal').addEventListener('click', function () {
const url = document.getElementById('icalUrl').value;
navigator.clipboard.writeText(url).then(() => {
this.innerHTML = '<i class="bi bi-check text-success"></i>';
setTimeout(() => { this.innerHTML = '<i class="bi bi-clipboard"></i>'; }, 2000);
});
});
document.getElementById('testNcBtn').addEventListener('click', async function () {
const res = document.getElementById('testNcResult');
res.textContent = 'Test en cours…';
try {
const r = await fetch('/api/nextcloud/browse');
const d = await r.json();
if (d.error) {
res.innerHTML = `<span class="text-danger"><i class="bi bi-x-circle me-1"></i>${d.error}</span>`;
} else {
res.innerHTML = `<span class="text-success"><i class="bi bi-check-circle me-1"></i>Connexion OK — ${d.items.length} élément(s) dans ${d.path}</span>`;
}
} catch (e) {
res.innerHTML = `<span class="text-danger"><i class="bi bi-x-circle me-1"></i>${e.message}</span>`;
}
});
</script>
{% endblock %}