Merge remote-tracking branch 'origin/main'
# Conflicts: # app/calcul.py
This commit is contained in:
@ -18,10 +18,10 @@
|
||||
<td class="c-matin">
|
||||
<div id="cg-matin-{{ j.date }}" class="td-fill{% if matin_cg %} cg{% elif matin_warn %} warn{% endif %}">
|
||||
<div class="time-pair">
|
||||
<input class="time-input" type="time" name="matin_entree"
|
||||
<input class="time-input" type="text" name="matin_entree" inputmode="numeric" pattern="[0-9]{2}:[0-9]{2}" maxlength="5" placeholder="--:--"
|
||||
data-date="{{ j.date }}" value="{{ j.matin_entree or '' }}">
|
||||
<span class="time-sep">→</span>
|
||||
<input class="time-input" type="time" name="matin_sortie"
|
||||
<input class="time-input" type="text" name="matin_sortie" inputmode="numeric" pattern="[0-9]{2}:[0-9]{2}" maxlength="5" placeholder="--:--"
|
||||
data-date="{{ j.date }}" value="{{ j.matin_sortie or '' }}">
|
||||
</div>
|
||||
</div>
|
||||
@ -30,10 +30,10 @@
|
||||
<td class="c-aprem">
|
||||
<div id="cg-aprem-{{ j.date }}" class="td-fill{% if aprem_cg %} cg{% elif aprem_warn %} warn{% endif %}">
|
||||
<div class="time-pair">
|
||||
<input class="time-input" type="time" name="aprem_entree"
|
||||
<input class="time-input" type="text" name="aprem_entree" inputmode="numeric" pattern="[0-9]{2}:[0-9]{2}" maxlength="5" placeholder="--:--"
|
||||
data-date="{{ j.date }}" value="{{ j.aprem_entree or '' }}">
|
||||
<span class="time-sep">→</span>
|
||||
<input class="time-input" type="time" name="aprem_sortie"
|
||||
<input class="time-input" type="text" name="aprem_sortie" inputmode="numeric" pattern="[0-9]{2}:[0-9]{2}" maxlength="5" placeholder="--:--"
|
||||
data-date="{{ j.date }}" value="{{ j.aprem_sortie or '' }}">
|
||||
</div>
|
||||
</div>
|
||||
@ -70,9 +70,9 @@
|
||||
<td class="col-cible">
|
||||
<span id="calc-{{ j.date }}-cible">
|
||||
{%- if j.cible_matin or j.cible_aprem -%}
|
||||
{%- if j.cible_matin -%}<span class="cible-num">M {{ j.cible_matin }}</span>{%- endif -%}
|
||||
{%- if j.cible_matin -%}<span class="cible-num">M {{ j.cible_matin_entree }} → {{ j.cible_matin }}</span>{%- endif -%}
|
||||
{%- if j.cible_matin and j.cible_aprem %} {% endif -%}
|
||||
{%- if j.cible_aprem -%}<span class="cible-num">A {{ j.cible_aprem }}</span>{%- endif -%}
|
||||
{%- if j.cible_aprem -%}<span class="cible-num">A {{ j.cible_aprem_entree }} → {{ j.cible_aprem }}</span>{%- endif -%}
|
||||
{%- else -%}
|
||||
<span class="delta-zero">—</span>
|
||||
{%- endif %}
|
||||
|
||||
@ -4,6 +4,10 @@
|
||||
<div class="solde-eyebrow">Travaillé</div>
|
||||
<div class="solde-num neutral">{{ result.total_travaille }}</div>
|
||||
</div>
|
||||
<div class="solde-item">
|
||||
<div class="solde-eyebrow">Dû par jour</div>
|
||||
<div class="solde-num neutral">{{ result.heures_jour }}</div>
|
||||
</div>
|
||||
<div class="solde-item">
|
||||
<div class="solde-eyebrow">Dû cette semaine</div>
|
||||
<div class="solde-num neutral">{{ result.total_du }}</div>
|
||||
|
||||
229
app/templates/aide.html
Normal file
229
app/templates/aide.html
Normal file
@ -0,0 +1,229 @@
|
||||
{% extends "base.html" %}
|
||||
{% block content %}
|
||||
<div class="stats-head">
|
||||
<div>
|
||||
<span class="stats-title">Aide</span>
|
||||
<div class="stats-meta">Configurer les notifications ntfy et la détection de présence (Automation)</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="settings-card">
|
||||
<span class="hist-label">Principe</span>
|
||||
<p class="settings-p">
|
||||
L'app peut te relancer automatiquement si tu es détecté présent au bureau sans avoir pointé
|
||||
le matin, ou parti sans avoir pointé la sortie. Ça repose sur deux briques :
|
||||
</p>
|
||||
<ul class="aide-list">
|
||||
<li>
|
||||
<b>Réception des notifications</b> : <a href="https://ntfy.sh" target="_blank" rel="noopener">ntfy</a>,
|
||||
un service gratuit de notifications push par topic.
|
||||
</li>
|
||||
<li>
|
||||
<b>Détection de présence</b> : ton téléphone appelle une URL de l'app quand il entre/sort
|
||||
du bureau. L'app fait le reste (vérifie toutes les 5 min, dans la plage horaire réglée
|
||||
sur <a href="/settings">/settings</a>, en semaine, si un pointage manque).
|
||||
</li>
|
||||
</ul>
|
||||
<p class="settings-p">
|
||||
Avant de commencer : va sur <a href="/settings">/settings</a>, choisis un nom de topic ntfy
|
||||
secret (ex: <code>prenom-pointeuse-a1b2</code>, pas devinable), enregistre-le. La page affiche
|
||||
ensuite tes deux URLs personnelles <b>Arrivée</b> et <b>Départ</b> — elles contiennent un token
|
||||
secret, ne les partage pas.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="settings-card">
|
||||
<span class="hist-label">1. Recevoir les notifications (ntfy)</span>
|
||||
<p class="settings-p">
|
||||
Installer l'app <b>ntfy</b> sur le téléphone, puis s'abonner au topic choisi dans /settings.
|
||||
</p>
|
||||
<ol class="aide-steps">
|
||||
<li>
|
||||
Installe l'app <b>ntfy</b> depuis le
|
||||
<a href="https://play.google.com/store/apps/details?id=io.heckel.ntfy" target="_blank" rel="noopener">Play Store</a>
|
||||
ou <a href="https://f-droid.org/en/packages/io.heckel.ntfy/" target="_blank" rel="noopener">F-Droid</a>.
|
||||
</li>
|
||||
<li>
|
||||
Ouvre l'app, bouton <b>+</b> → entre le nom de topic choisi sur
|
||||
<a href="/settings">/settings</a> → valide. Laisse le serveur par défaut (<code>ntfy.sh</code>).
|
||||
</li>
|
||||
<li>
|
||||
Teste l'envoi depuis un navigateur : ouvre
|
||||
<code>https://ntfy.sh/<ton-topic></code> et publie un message. La notif doit
|
||||
arriver sur le téléphone en quelques secondes.
|
||||
</li>
|
||||
<li>
|
||||
<b>Important</b> : sur Android, désactive l'optimisation de la batterie pour ntfy
|
||||
(Réglages → Applications → ntfy → Batterie → "Aucune restriction"). Sinon Android
|
||||
peut couper les notifications en arrière-plan.
|
||||
</li>
|
||||
<li>
|
||||
<b>Serveur protégé par jeton</b> : si ton instance ntfy renvoie <code>403 Forbidden</code>,
|
||||
elle exige un jeton d'accès. Crée-le sur le serveur (panneau d'admin ntfy, onglet
|
||||
"Access tokens"), puis colle-le dans le champ <b>Jeton d'accès</b> sur
|
||||
<a href="/settings">/settings</a>. Il est envoyé comme
|
||||
<code>Authorization: Bearer ...</code> à chaque publication.
|
||||
</li>
|
||||
</ol>
|
||||
<p class="settings-hint">
|
||||
Astuce : si tu veux ton propre serveur ntfy (auto-hébergé, pour éviter le topic public),
|
||||
installe-le (<a href="https://docs.ntfy.sh/install/" target="_blank" rel="noopener">doc officielle</a>)
|
||||
et renseigne son URL dans le champ <b>Serveur ntfy</b> sur /settings.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="settings-card">
|
||||
<span class="hist-label">2. Détecter la présence (Automation)</span>
|
||||
<p class="settings-p">
|
||||
Créer une automatisation <a href="https://f-droid.org/en/packages/com.jens.automation2/" target="_blank" rel="noopener">Automation</a>
|
||||
(app FOSS, F-Droid) déclenchée par le Wi-Fi du bureau ou par une géofence :
|
||||
à l'entrée, appeler l'URL « Arrivée » ; à la sortie, l'URL « Départ ».
|
||||
</p>
|
||||
<ol class="aide-steps">
|
||||
<li>
|
||||
Installe <a href="https://f-droid.org/en/packages/com.jens.automation2/" target="_blank" rel="noopener">Automation</a>
|
||||
depuis F-Droid (évite la version Play Store, amputée de la localisation).
|
||||
</li>
|
||||
<li>
|
||||
Autorise la <b>localisation en arrière-plan</b> : Réglages → Applications →
|
||||
Automation → Autorisations → Position → <b>Toujours</b>. Sans ça, Android
|
||||
coupe la détection quand l'app n'est pas au premier plan.
|
||||
</li>
|
||||
<li>
|
||||
Désactive l'<b>optimisation batterie</b> pour Automation (même menu →
|
||||
Batterie → « Aucune restriction »).
|
||||
</li>
|
||||
<li>
|
||||
Crée la règle <b>Arrivée bureau</b> — Automation → <b>Rules</b> → <b>+</b> :
|
||||
<ul class="aide-substeps">
|
||||
<li><b>Trigger</b> : <b>Wifi connection</b> → entre le SSID du Wi-Fi du bureau (méthode recommandée),
|
||||
<i>ou</i> <b>Location</b> → adresse du bureau, rayon ~100–150 m, événement <b>Enter</b> (géofence).</li>
|
||||
<li><b>Action</b> : <b>Trigger URL</b>, method <code>POST</code>,
|
||||
URL = ton lien <b>Arrivée</b> copié depuis <a href="/settings">/settings</a>.</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
Crée la règle symétrique <b>Sortie bureau</b> : même SSID en sens <b>déconnexion</b>
|
||||
(ou même géofence, événement <b>Exit</b>), action <b>Trigger URL</b> <code>POST</code>
|
||||
vers ton lien <b>Départ</b>.
|
||||
</li>
|
||||
</ol>
|
||||
<p class="settings-hint">
|
||||
Au bureau, la méthode Wi-Fi est généralement plus réactive et plus économe en
|
||||
batterie que la géofence GPS.
|
||||
</p>
|
||||
<p class="settings-hint">
|
||||
Si tu changes de téléphone ou réinstalles, les tokens restent valides — il suffit de
|
||||
recréer les règles Automation et l'abonnement ntfy.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="settings-card">
|
||||
<span class="hist-label">3. Vérifier</span>
|
||||
<ol class="aide-steps">
|
||||
<li>
|
||||
Force une entrée/sortie de zone ou de Wi-Fi (ou, dans Automation, ouvre une règle
|
||||
et utilise le bouton « Test », icône lecture). La réponse doit être <code>{"ok":true}</code>.
|
||||
</li>
|
||||
<li>
|
||||
Va sur <a href="/logs">/logs</a> : tu dois voir une entrée "Présence / arrivée" ou
|
||||
"Présence / départ" correspondante.
|
||||
</li>
|
||||
<li>
|
||||
Simule une journée sans pointage matin pendant que tu es marqué "présent" : une notif
|
||||
ntfy doit arriver dans les 5 minutes. Elle apparaîtra aussi dans
|
||||
<a href="/logs">/logs</a> sous "Notification / envoyée".
|
||||
</li>
|
||||
</ol>
|
||||
</div>
|
||||
|
||||
<div class="settings-card">
|
||||
<span class="hist-label">Dépannage</span>
|
||||
<ul class="aide-list">
|
||||
<li>
|
||||
<b>Pas de notif ntfy</b> : vérifie l'optimisation batterie sur l'app ntfy, et que le topic
|
||||
est bien le même côté serveur et côté téléphone (sensible à la casse).
|
||||
</li>
|
||||
<li>
|
||||
<b>Pas de déclenchement Automation</b> : dans Automation, vérifie que la règle est
|
||||
bien activée et utilise le bouton « Test » pour isoler le problème (réseau vs
|
||||
géoloc vs Wi-Fi). Vérifie aussi que l'optimisation batterie est bien désactivée
|
||||
pour l'app.
|
||||
</li>
|
||||
<li>
|
||||
<b>Logs vides sur /logs</b> : les entrées ne s'écrivent que quand un événement réel se
|
||||
produit (notif envoyée/échouée, arrivée/départ détecté). Force un test (étape 3).
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.aide-list {
|
||||
margin: .4rem 0 .4rem 1.2rem;
|
||||
padding: 0;
|
||||
font-size: 13px;
|
||||
line-height: 1.65;
|
||||
color: var(--text);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: .35rem;
|
||||
list-style: disc;
|
||||
}
|
||||
.aide-list li::marker { color: var(--muted); }
|
||||
.aide-list a, .aide-steps a { color: var(--accent); }
|
||||
.aide-list code, .aide-steps code, .aide-substeps code {
|
||||
font-family: var(--mono);
|
||||
font-size: 12px;
|
||||
background: var(--ground);
|
||||
border: 1px solid var(--rule);
|
||||
padding: .05rem .3rem;
|
||||
}
|
||||
.aide-steps {
|
||||
counter-reset: step;
|
||||
margin: .5rem 0 .4rem;
|
||||
padding: 0;
|
||||
font-size: 13px;
|
||||
line-height: 1.65;
|
||||
color: var(--text);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: .55rem;
|
||||
list-style: none;
|
||||
}
|
||||
.aide-steps > li {
|
||||
position: relative;
|
||||
padding-left: 2rem;
|
||||
}
|
||||
.aide-steps > li::before {
|
||||
counter-increment: step;
|
||||
content: counter(step);
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: .05rem;
|
||||
width: 1.4rem;
|
||||
height: 1.4rem;
|
||||
border-radius: 50%;
|
||||
background: var(--accent);
|
||||
color: #fff;
|
||||
font-family: var(--mono);
|
||||
font-size: 10px;
|
||||
font-weight: 500;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
.aide-substeps {
|
||||
margin: .35rem 0 .1rem 1.2rem;
|
||||
padding: 0;
|
||||
font-size: 12px;
|
||||
color: var(--muted);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: .2rem;
|
||||
list-style: disc;
|
||||
}
|
||||
.aide-substeps li::marker { color: var(--rule); }
|
||||
.settings-card .settings-p { margin: 0; }
|
||||
.settings-card .settings-hint { margin-top: .5rem; }
|
||||
</style>
|
||||
{% endblock %}
|
||||
@ -144,7 +144,7 @@
|
||||
/* ── Soldes ── */
|
||||
.soldes {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
border: 1.5px solid var(--rule);
|
||||
background: var(--surface);
|
||||
}
|
||||
@ -166,7 +166,7 @@
|
||||
}
|
||||
.solde-num {
|
||||
font-family: var(--mono);
|
||||
font-size: 1.9rem;
|
||||
font-size: 1.5rem;
|
||||
font-weight: 500;
|
||||
letter-spacing: -.03em;
|
||||
line-height: 1;
|
||||
@ -188,6 +188,7 @@
|
||||
/* ── Table ── */
|
||||
.table-section { overflow-x: auto; -webkit-overflow-scrolling: touch; }
|
||||
table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
border: 1.5px solid var(--rule);
|
||||
background: var(--surface);
|
||||
@ -195,7 +196,7 @@
|
||||
}
|
||||
|
||||
.th-group th {
|
||||
padding: .45rem .85rem .25rem;
|
||||
padding: .45rem .65rem .25rem;
|
||||
font-family: var(--mono);
|
||||
font-size: 9px;
|
||||
letter-spacing: .12em;
|
||||
@ -222,7 +223,7 @@
|
||||
.th-group .g-conge { text-align: center; }
|
||||
|
||||
.th-sub th {
|
||||
padding: .25rem .85rem .5rem;
|
||||
padding: .25rem .65rem .5rem;
|
||||
font-size: 10px;
|
||||
color: rgba(107,97,82,.65);
|
||||
border-bottom: 2px solid var(--text);
|
||||
@ -258,7 +259,7 @@
|
||||
tbody tr:not(:last-child) td { border-bottom: 1px solid var(--rule-l); }
|
||||
|
||||
td {
|
||||
padding: .6rem .85rem;
|
||||
padding: .5rem .65rem;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
@ -267,7 +268,7 @@
|
||||
.td-fill {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: .6rem .85rem;
|
||||
padding: .5rem .65rem;
|
||||
}
|
||||
td.c-matin .td-fill { border-left: 3px solid rgba(47,95,160,.2); }
|
||||
td.c-aprem .td-fill { border-left: 3px solid rgba(184,106,8,.2); }
|
||||
@ -284,11 +285,11 @@
|
||||
.jour-nom { display: block; font-weight: 500; font-size: 13px; }
|
||||
.jour-date { display: block; font-family: var(--mono); font-size: 11px; color: var(--muted); }
|
||||
|
||||
.time-pair { display: flex; align-items: center; gap: 4px; justify-content: center; }
|
||||
.time-pair { display: flex; align-items: center; gap: 3px; justify-content: center; }
|
||||
.time-sep { color: var(--rule); font-size: 11px; flex-shrink: 0; }
|
||||
.time-input {
|
||||
width: 80px;
|
||||
padding: .28rem .4rem;
|
||||
width: 70px;
|
||||
padding: .28rem .35rem;
|
||||
border: 1px solid var(--rule);
|
||||
border-radius: 0;
|
||||
font-family: var(--mono);
|
||||
@ -316,10 +317,10 @@
|
||||
|
||||
/* Cumulative delta and sortie cible columns */
|
||||
.col-cumul { text-align: right; padding-left: .5rem; }
|
||||
.col-cible { text-align: right; padding-left: .5rem; }
|
||||
.col-cible { text-align: right; padding-left: .5rem; white-space: normal; min-width: 110px; }
|
||||
.g-cumul { text-align: right; }
|
||||
.g-cible { text-align: right; }
|
||||
.cible-num { font-family: var(--mono); font-size: 12px; color: var(--accent); font-weight: 500; }
|
||||
.cible-num { font-family: var(--mono); font-size: 12px; color: var(--accent); font-weight: 500; display: inline-block; }
|
||||
|
||||
.col-save { text-align: center; padding: .4rem .3rem; }
|
||||
.g-save { background: var(--ground); }
|
||||
@ -1032,7 +1033,9 @@
|
||||
{% if current_user is defined and current_user %}
|
||||
<a href="/" class="nav-link">Semaine courante</a>
|
||||
<a href="/stats" class="nav-link">Statistiques</a>
|
||||
<a href="/logs" class="nav-link">Journaux</a>
|
||||
<a href="/settings" class="nav-link">Réglages</a>
|
||||
<a href="/aide" class="nav-link">Aide</a>
|
||||
<span class="nav-user">{{ current_user }}</span>
|
||||
<a href="/logout" class="nav-link nav-logout">Déconnexion</a>
|
||||
{% endif %}
|
||||
|
||||
145
app/templates/logs.html
Normal file
145
app/templates/logs.html
Normal file
@ -0,0 +1,145 @@
|
||||
{% extends "base.html" %}
|
||||
{% block content %}
|
||||
<div class="stats-head">
|
||||
<div>
|
||||
<span class="stats-title">Journaux</span>
|
||||
<div class="stats-meta">
|
||||
Notifications ntfy et détections de présence (Automation) — {{ current_user }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="logs-tabs" role="tablist">
|
||||
{% set counts = {'all': logs|length, 'notif': logs | selectattr('type', 'equalto', 'notif') | list | length, 'presence': logs | selectattr('type', 'equalto', 'presence') | list | length, 'settings': logs | selectattr('type', 'equalto', 'settings') | list | length} %}
|
||||
<a class="ltab{% if filter == 'all' %} active{% endif %}" href="/logs" role="tab">
|
||||
Tout <span class="ltab-count">{{ counts.all }}</span>
|
||||
</a>
|
||||
<a class="ltab{% if filter == 'notif' %} active{% endif %}" href="/logs?filter=notif" role="tab">
|
||||
Notifications <span class="ltab-count">{{ counts.notif }}</span>
|
||||
</a>
|
||||
<a class="ltab{% if filter == 'presence' %} active{% endif %}" href="/logs?filter=presence" role="tab">
|
||||
Présence <span class="ltab-count">{{ counts.presence }}</span>
|
||||
</a>
|
||||
<a class="ltab{% if filter == 'settings' %} active{% endif %}" href="/logs?filter=settings" role="tab">
|
||||
Réglages <span class="ltab-count">{{ counts.settings }}</span>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
{% set visible = logs if filter == 'all' else logs | selectattr('type', 'equalto', filter) | list %}
|
||||
|
||||
{% if visible %}
|
||||
<div class="logs-list">
|
||||
{% for entry in visible %}
|
||||
<div class="log-row log-{{ entry.type }}{% if entry.event == 'échec' %} log-failed{% endif %}">
|
||||
<div class="log-ts">{{ entry.ts }}</div>
|
||||
<div class="log-type">{{ {'notif': 'Notification', 'presence': 'Présence', 'settings': 'Réglages'}[entry.type] }}</div>
|
||||
<div class="log-body">
|
||||
<span class="log-event">{{ entry.event }}</span>
|
||||
{% if entry.message %}<span class="log-msg">{{ entry.message }}</span>{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="logs-empty">Aucun événement enregistré pour le moment.</div>
|
||||
{% endif %}
|
||||
|
||||
<style>
|
||||
.logs-tabs {
|
||||
display: flex;
|
||||
gap: .4rem;
|
||||
border-bottom: 1.5px solid var(--rule);
|
||||
padding-bottom: .6rem;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.ltab {
|
||||
font-family: var(--mono);
|
||||
font-size: 11px;
|
||||
letter-spacing: .04em;
|
||||
color: var(--muted);
|
||||
text-decoration: none;
|
||||
border: 1.5px solid var(--rule);
|
||||
padding: .35rem .7rem;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: .45rem;
|
||||
transition: border-color .15s, color .15s, background .15s;
|
||||
}
|
||||
.ltab:hover { border-color: var(--text); color: var(--text); }
|
||||
.ltab.active { border-color: var(--text); color: var(--text); background: var(--surface); }
|
||||
.ltab-count {
|
||||
font-size: 10px;
|
||||
background: var(--ground);
|
||||
border: 1px solid var(--rule);
|
||||
padding: .05rem .35rem;
|
||||
color: var(--muted);
|
||||
}
|
||||
|
||||
.logs-list {
|
||||
border: 1.5px solid var(--rule);
|
||||
background: var(--surface);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
.log-row {
|
||||
display: grid;
|
||||
grid-template-columns: 180px 130px 1fr;
|
||||
gap: 1rem;
|
||||
padding: .55rem 1rem;
|
||||
border-bottom: 1px solid var(--rule-l);
|
||||
font-size: 13px;
|
||||
align-items: baseline;
|
||||
}
|
||||
.log-row:last-child { border-bottom: none; }
|
||||
.log-row:hover { background: var(--ground); }
|
||||
.log-ts {
|
||||
font-family: var(--mono);
|
||||
font-size: 11px;
|
||||
color: var(--muted);
|
||||
white-space: nowrap;
|
||||
}
|
||||
.log-type {
|
||||
font-family: var(--mono);
|
||||
font-size: 10px;
|
||||
letter-spacing: .06em;
|
||||
text-transform: uppercase;
|
||||
color: var(--muted);
|
||||
white-space: nowrap;
|
||||
}
|
||||
.log-notif .log-type { color: var(--accent); }
|
||||
.log-presence .log-type { color: var(--accent-2); }
|
||||
.log-settings .log-type { color: var(--conge-txt); }
|
||||
.log-body { display: flex; flex-direction: column; gap: .15rem; }
|
||||
.log-event {
|
||||
font-weight: 500;
|
||||
color: var(--text);
|
||||
}
|
||||
.log-msg {
|
||||
font-size: 12px;
|
||||
color: var(--muted);
|
||||
line-height: 1.45;
|
||||
}
|
||||
.log-failed {
|
||||
background: var(--neg-bg);
|
||||
}
|
||||
.log-failed .log-event { color: var(--neg); }
|
||||
.log-failed:hover { background: var(--neg-bg); }
|
||||
|
||||
.logs-empty {
|
||||
border: 1.5px solid var(--rule);
|
||||
background: var(--surface);
|
||||
padding: 2rem;
|
||||
text-align: center;
|
||||
color: var(--muted);
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
@media (max-width: 640px) {
|
||||
.log-row {
|
||||
grid-template-columns: 1fr;
|
||||
gap: .2rem;
|
||||
}
|
||||
.log-ts { font-size: 10px; }
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
@ -53,7 +53,7 @@
|
||||
<th class="g-calc" rowspan="2">Travaillé</th>
|
||||
<th class="g-calc" rowspan="2">Δ jour</th>
|
||||
<th class="g-cumul" rowspan="2">Δ sem.</th>
|
||||
<th class="g-cible" rowspan="2">Fin cible</th>
|
||||
<th class="g-cible" rowspan="2">Cible</th>
|
||||
<th class="g-save" rowspan="2"></th>
|
||||
<th class="g-conge" rowspan="2">Congé</th>
|
||||
</tr>
|
||||
|
||||
@ -51,11 +51,12 @@
|
||||
</div>
|
||||
|
||||
<div class="settings-card">
|
||||
<span class="hist-label">Notifications (ntfy)</span>
|
||||
<span class="hist-label">Notifications (ntfy) <a href="/aide" class="settings-help-link">Aide</a></span>
|
||||
<p class="settings-p">
|
||||
Crée un topic sur ton serveur <a href="https://ntfy.sh" target="_blank" rel="noopener">ntfy</a>
|
||||
(public ou auto-hébergé), un identifiant secret que toi seul connais, abonne-toi dessus avec
|
||||
l'app ntfy sur ton téléphone, puis renseigne les deux champs ci-dessous.
|
||||
l'app ntfy sur ton téléphone, puis renseigne les deux champs ci-dessous. Guide complet sur
|
||||
la page <a href="/aide">Aide</a>.
|
||||
</p>
|
||||
|
||||
{% if saved %}
|
||||
@ -83,6 +84,16 @@
|
||||
placeholder="ex: antoine-pointeuse-x7f2"
|
||||
autocomplete="off"
|
||||
>
|
||||
<label class="login-label" for="ntfy_token">Jeton d'accès (optionnel, serveur protégé)</label>
|
||||
<input
|
||||
id="ntfy_token"
|
||||
class="login-input"
|
||||
type="password"
|
||||
name="ntfy_token"
|
||||
value="{{ ntfy_token }}"
|
||||
placeholder="ex: tk_xxxxxxxxxxxxxxxx"
|
||||
autocomplete="off"
|
||||
>
|
||||
<label class="login-label" for="rappel_debut_h">Plage horaire des rappels</label>
|
||||
<div class="settings-range">
|
||||
<input
|
||||
@ -102,27 +113,51 @@
|
||||
</div>
|
||||
<button class="login-btn" type="submit">Enregistrer</button>
|
||||
</form>
|
||||
<div class="settings-test-row">
|
||||
<button
|
||||
class="login-btn settings-test-btn"
|
||||
hx-post="/settings/test-ntfy"
|
||||
hx-target="#ntfy-test-result"
|
||||
hx-swap="innerHTML"
|
||||
hx-disabled-elt="this"
|
||||
>Tester la notification</button>
|
||||
<span id="ntfy-test-result"></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="settings-card">
|
||||
<span class="hist-label">Détection de présence (Tasker)</span>
|
||||
<span class="hist-label">Détection de présence (Automation) <a href="/aide" class="settings-help-link">Aide</a></span>
|
||||
<p class="settings-p">
|
||||
Crée une automatisation Tasker déclenchée par une géofence autour du bureau : à l'<b>entrée</b> de la
|
||||
zone, appelle l'URL "arrivée" ; à la <b>sortie</b>, l'URL "départ". Tant que tu es détecté présent sans
|
||||
avoir pointé l'entrée du matin, ou parti sans avoir pointé la sortie, tu reçois un rappel toutes les
|
||||
5 minutes, dans la plage horaire réglée ci-dessus (jours ouvrés).
|
||||
Crée une automatisation <a href="https://f-droid.org/en/packages/com.jens.automation2/" target="_blank" rel="noopener">Automation</a>
|
||||
déclenchée par le Wi-Fi du bureau (ou une géofence) : à la <b>connexion</b>/entrée, appelle l'URL « arrivée » ;
|
||||
à la <b>déconnexion</b>/sortie, l'URL « départ ». Tant que tu es détecté présent sans
|
||||
avoir pointé l'entrée du matin, ou parti sans avoir pointé la sortie, tu reçois un rappel
|
||||
selon l'intervalle réglé ci-dessus, dans la plage horaire configurée (jours ouvrés).
|
||||
Étapes détaillées sur la page <a href="/aide">Aide</a>.
|
||||
</p>
|
||||
<div class="settings-urlrow">
|
||||
<span class="settings-urllabel">Arrivée</span>
|
||||
<code class="settings-url">{{ arrivee_url }}</code>
|
||||
</div>
|
||||
<div class="settings-urlrow">
|
||||
<span class="settings-urllabel">Départ</span>
|
||||
<code class="settings-url">{{ depart_url }}</code>
|
||||
<div id="presence-urls">
|
||||
<div class="settings-urlrow">
|
||||
<span class="settings-urllabel">Arrivée</span>
|
||||
<code class="settings-url">{{ arrivee_url }}</code>
|
||||
</div>
|
||||
<div class="settings-urlrow">
|
||||
<span class="settings-urllabel">Départ</span>
|
||||
<code class="settings-url">{{ depart_url }}</code>
|
||||
</div>
|
||||
</div>
|
||||
<p class="settings-hint">
|
||||
Ces URLs sont secrètes (elles contiennent ton token) — ne les partage pas.
|
||||
En cas de compromission, régénère le token : les anciennes URLs cesseront de fonctionner
|
||||
et tu devras mettre à jour Tasker avec les nouvelles.
|
||||
</p>
|
||||
<button
|
||||
class="login-btn settings-regen-btn"
|
||||
hx-post="/settings/regenerate-presence-token"
|
||||
hx-target="#presence-urls"
|
||||
hx-swap="outerHTML"
|
||||
hx-confirm="Régénérer le token ? Les URLs actuelles cesseront immédiatement de fonctionner, tu devras mettre à jour Tasker."
|
||||
hx-disabled-elt="this"
|
||||
>Régénérer le token</button>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
@ -184,5 +219,51 @@
|
||||
.time-range-input::-webkit-calendar-picker-indicator {
|
||||
margin-left: .5rem;
|
||||
}
|
||||
.settings-help-link {
|
||||
font-family: var(--mono);
|
||||
font-size: 10px;
|
||||
letter-spacing: .04em;
|
||||
color: var(--accent);
|
||||
text-decoration: none;
|
||||
border: 1px solid var(--rule);
|
||||
padding: .1rem .4rem;
|
||||
margin-left: .5rem;
|
||||
vertical-align: middle;
|
||||
transition: border-color .15s, background .15s;
|
||||
}
|
||||
.settings-help-link:hover {
|
||||
border-color: var(--accent);
|
||||
background: rgba(47,95,160,.06);
|
||||
}
|
||||
.settings-test-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: .75rem;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.settings-test-btn {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.settings-regen-btn {
|
||||
align-self: flex-start;
|
||||
border-color: var(--neg);
|
||||
color: var(--neg);
|
||||
}
|
||||
.settings-regen-btn:hover:not([disabled]) {
|
||||
background: var(--neg-bg);
|
||||
}
|
||||
#ntfy-test-result {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
#ntfy-test-result:empty { display: none; }
|
||||
#ntfy-test-result:not(:empty) {
|
||||
font-family: var(--mono);
|
||||
font-size: 12px;
|
||||
padding: .5rem .8rem;
|
||||
border-left: 3px solid var(--muted);
|
||||
background: var(--ground);
|
||||
line-height: 1.45;
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
|
||||
Reference in New Issue
Block a user