|
|
@@ -240,31 +240,59 @@
|
|
|
</div>
|
|
|
|
|
|
{# ── Rétention ── #}
|
|
|
+ {% set gfs_cfg = (job.retention_gfs_config | fromjson) if job and job.retention_gfs_config else {} %}
|
|
|
<div class="bg-white rounded-xl border border-gray-200 p-6 space-y-4">
|
|
|
<h2 class="text-sm font-semibold text-gray-700 uppercase tracking-wide">Rétention</h2>
|
|
|
- <div class="grid grid-cols-2 gap-4">
|
|
|
- <div>
|
|
|
- <label class="block text-sm font-medium text-gray-700 mb-1">Mode</label>
|
|
|
- <select name="retention_mode"
|
|
|
- class="w-full border border-gray-300 rounded-lg px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500">
|
|
|
- {% for val, label in [('count','Garder les N dernières archives'),
|
|
|
- ('daily','1 archive par jour sur N jours glissants'),
|
|
|
- ('gfs','Grand-Père-Fils (non disponible)')] %}
|
|
|
- <option value="{{ val }}"
|
|
|
- {% if job and job.retention_mode == val %}selected{% endif %}
|
|
|
- {% if val == 'gfs' %}disabled class="text-gray-400"{% endif %}>
|
|
|
- {{ label }}
|
|
|
- </option>
|
|
|
- {% endfor %}
|
|
|
- </select>
|
|
|
- </div>
|
|
|
- <div>
|
|
|
- <label class="block text-sm font-medium text-gray-700 mb-1">Valeur (N)</label>
|
|
|
- <input type="number" name="retention_value" min="1" max="365" required
|
|
|
- value="{{ job.retention_value if job else 7 }}"
|
|
|
- class="w-full border border-gray-300 rounded-lg px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500">
|
|
|
+ <div>
|
|
|
+ <label class="block text-sm font-medium text-gray-700 mb-1">Mode</label>
|
|
|
+ <select name="retention_mode" id="retention_mode"
|
|
|
+ class="w-full border border-gray-300 rounded-lg px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500">
|
|
|
+ {% for val, label in [('count','Garder les N dernières archives'),
|
|
|
+ ('daily','1 archive par jour sur N jours glissants'),
|
|
|
+ ('gfs','Grand-Père-Père-Fils (GFS)')] %}
|
|
|
+ <option value="{{ val }}"
|
|
|
+ {% if job and job.retention_mode == val %}selected{% endif %}>
|
|
|
+ {{ label }}
|
|
|
+ </option>
|
|
|
+ {% endfor %}
|
|
|
+ </select>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ {# Mode count / daily : un seul champ N #}
|
|
|
+ <div id="retention-simple">
|
|
|
+ <label class="block text-sm font-medium text-gray-700 mb-1">Valeur (N)</label>
|
|
|
+ <input type="number" name="retention_value" id="retention_value" min="1" max="365"
|
|
|
+ value="{{ job.retention_value if job and job.retention_mode != 'gfs' else 7 }}"
|
|
|
+ class="w-full border border-gray-300 rounded-lg px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500">
|
|
|
+ </div>
|
|
|
+
|
|
|
+ {# Mode GFS : trois champs Fils / Père / Grand-Père #}
|
|
|
+ <div id="retention-gfs" class="hidden space-y-3">
|
|
|
+ <div class="grid grid-cols-3 gap-4">
|
|
|
+ <div>
|
|
|
+ <label class="block text-sm font-medium text-gray-700 mb-1">Fils (journaliers)</label>
|
|
|
+ <input type="number" name="gfs_daily" min="1" max="31"
|
|
|
+ value="{{ gfs_cfg.get('daily', 7) }}"
|
|
|
+ class="w-full border border-gray-300 rounded-lg px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500">
|
|
|
+ <p class="text-xs text-gray-400 mt-1">dernières archives</p>
|
|
|
+ </div>
|
|
|
+ <div>
|
|
|
+ <label class="block text-sm font-medium text-gray-700 mb-1">Père (hebdos)</label>
|
|
|
+ <input type="number" name="gfs_weekly" min="1" max="52"
|
|
|
+ value="{{ gfs_cfg.get('weekly', 4) }}"
|
|
|
+ class="w-full border border-gray-300 rounded-lg px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500">
|
|
|
+ <p class="text-xs text-gray-400 mt-1">semaines glissantes</p>
|
|
|
+ </div>
|
|
|
+ <div>
|
|
|
+ <label class="block text-sm font-medium text-gray-700 mb-1">Grand-Père (mensuels)</label>
|
|
|
+ <input type="number" name="gfs_monthly" min="1" max="60"
|
|
|
+ value="{{ gfs_cfg.get('monthly', 12) }}"
|
|
|
+ class="w-full border border-gray-300 rounded-lg px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-blue-500">
|
|
|
+ <p class="text-xs text-gray-400 mt-1">mois glissants</p>
|
|
|
+ </div>
|
|
|
</div>
|
|
|
</div>
|
|
|
+
|
|
|
<p id="retention-help" class="text-xs text-gray-400"></p>
|
|
|
</div>
|
|
|
|
|
|
@@ -329,7 +357,7 @@
|
|
|
const retentionHelp = {
|
|
|
count: "Ex : N=7 → conserve les 7 dernières archives, supprime les plus anciennes.",
|
|
|
daily: "Ex : N=30 → conserve 1 archive par jour sur les 30 derniers jours. Les doublons du même jour sont supprimés.",
|
|
|
- gfs: "Mode non encore disponible."
|
|
|
+ gfs: "Ex : Fils=7, Père=4, GP=12 → 7 archives récentes + 1 par semaine sur 4 semaines + 1 par mois sur 12 mois. Une archive peut couvrir plusieurs niveaux."
|
|
|
};
|
|
|
|
|
|
const dbCache = {};
|
|
|
@@ -382,8 +410,27 @@
|
|
|
if (type === 'mysql' || type === 'postgresql') loadDatabases(type);
|
|
|
}
|
|
|
|
|
|
- function updateRetentionHelp() {
|
|
|
- const mode = document.querySelector('[name=retention_mode]').value;
|
|
|
+ function updateRetentionMode() {
|
|
|
+ const mode = document.getElementById('retention_mode').value;
|
|
|
+ const simple = document.getElementById('retention-simple');
|
|
|
+ const gfsBlock = document.getElementById('retention-gfs');
|
|
|
+ const simpleInput = document.getElementById('retention_value');
|
|
|
+ const gfsInputs = gfsBlock.querySelectorAll('input');
|
|
|
+
|
|
|
+ if (mode === 'gfs') {
|
|
|
+ simple.classList.add('hidden');
|
|
|
+ simpleInput.disabled = true;
|
|
|
+ simpleInput.required = false;
|
|
|
+ gfsBlock.classList.remove('hidden');
|
|
|
+ gfsInputs.forEach(i => { i.disabled = false; i.required = true; });
|
|
|
+ } else {
|
|
|
+ simple.classList.remove('hidden');
|
|
|
+ simpleInput.disabled = false;
|
|
|
+ simpleInput.required = true;
|
|
|
+ gfsBlock.classList.add('hidden');
|
|
|
+ gfsInputs.forEach(i => { i.disabled = true; i.required = false; });
|
|
|
+ }
|
|
|
+
|
|
|
document.getElementById('retention-help').textContent = retentionHelp[mode] || '';
|
|
|
}
|
|
|
|
|
|
@@ -424,10 +471,10 @@
|
|
|
const srcPath = document.querySelector('[name=source_path]');
|
|
|
if (srcPath) srcPath.addEventListener('input', suggestName);
|
|
|
|
|
|
- document.querySelector('[name=retention_mode]').addEventListener('change', updateRetentionHelp);
|
|
|
+ document.getElementById('retention_mode').addEventListener('change', updateRetentionMode);
|
|
|
showTypeConfig();
|
|
|
suggestName(); // pré-remplit le nom si champ vide à l'ouverture du formulaire
|
|
|
- updateRetentionHelp();
|
|
|
+ updateRetentionMode();
|
|
|
|
|
|
// Toggle planification cron
|
|
|
const scheduledChk = document.getElementById('scheduled');
|