mirror of
https://github.com/MarekWo/mc-webui.git
synced 2026-03-28 17:42:45 +01:00
feat: Add configurable hour for scheduled contact cleanup
Allow users to select the hour (0-23 UTC) when automatic contact cleanup runs: - Add hour selector dropdown in Advanced Filters (disabled until enabled) - Hour field saved to .webui_settings.json with cleanup_settings - API validates hour (0-23), scheduler uses CronTrigger with hour param - Status text shows configured hour (e.g., "Enabled (runs daily at 03:00 UTC)") - Documentation updated in user-guide.md Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -335,12 +335,13 @@ def _cleanup_job():
|
||||
logger.error(f"Cleanup job failed: {e}", exc_info=True)
|
||||
|
||||
|
||||
def schedule_cleanup(enabled: bool) -> bool:
|
||||
def schedule_cleanup(enabled: bool, hour: int = 1) -> bool:
|
||||
"""
|
||||
Add or remove the cleanup job from the scheduler.
|
||||
|
||||
Args:
|
||||
enabled: True to enable cleanup job, False to disable
|
||||
hour: Hour (0-23 UTC) at which to run cleanup job
|
||||
|
||||
Returns:
|
||||
True if successful, False otherwise
|
||||
@@ -353,8 +354,12 @@ def schedule_cleanup(enabled: bool) -> bool:
|
||||
|
||||
try:
|
||||
if enabled:
|
||||
# Add cleanup job at 01:00 UTC
|
||||
trigger = CronTrigger(hour=1, minute=0)
|
||||
# Validate hour
|
||||
if not isinstance(hour, int) or hour < 0 or hour > 23:
|
||||
hour = 1
|
||||
|
||||
# Add cleanup job at specified hour UTC
|
||||
trigger = CronTrigger(hour=hour, minute=0)
|
||||
|
||||
_scheduler.add_job(
|
||||
func=_cleanup_job,
|
||||
@@ -364,7 +369,7 @@ def schedule_cleanup(enabled: bool) -> bool:
|
||||
replace_existing=True
|
||||
)
|
||||
|
||||
logger.info("Cleanup job scheduled - will run daily at 01:00 UTC")
|
||||
logger.info(f"Cleanup job scheduled - will run daily at {hour:02d}:00 UTC")
|
||||
else:
|
||||
# Remove cleanup job if it exists
|
||||
try:
|
||||
@@ -393,8 +398,9 @@ def init_cleanup_schedule():
|
||||
settings = get_cleanup_settings()
|
||||
|
||||
if settings.get('enabled'):
|
||||
schedule_cleanup(enabled=True)
|
||||
logger.info("Auto-cleanup enabled from saved settings")
|
||||
hour = settings.get('hour', 1)
|
||||
schedule_cleanup(enabled=True, hour=hour)
|
||||
logger.info(f"Auto-cleanup enabled from saved settings (hour={hour:02d}:00 UTC)")
|
||||
else:
|
||||
logger.info("Auto-cleanup is disabled in saved settings")
|
||||
|
||||
|
||||
@@ -192,7 +192,8 @@ def get_cleanup_settings() -> dict:
|
||||
'types': list[int],
|
||||
'date_field': str,
|
||||
'days': int,
|
||||
'name_filter': str
|
||||
'name_filter': str,
|
||||
'hour': int (0-23, UTC)
|
||||
}
|
||||
"""
|
||||
from pathlib import Path
|
||||
@@ -201,7 +202,8 @@ def get_cleanup_settings() -> dict:
|
||||
'types': [1, 2, 3, 4],
|
||||
'date_field': 'last_advert',
|
||||
'days': 30,
|
||||
'name_filter': ''
|
||||
'name_filter': '',
|
||||
'hour': 1
|
||||
}
|
||||
|
||||
settings_path = Path(config.MC_CONFIG_DIR) / ".webui_settings.json"
|
||||
@@ -2023,7 +2025,8 @@ def update_cleanup_settings_api():
|
||||
"types": [1, 2],
|
||||
"date_field": "last_advert",
|
||||
"days": 30,
|
||||
"name_filter": ""
|
||||
"name_filter": "",
|
||||
"hour": 1
|
||||
}
|
||||
|
||||
Returns:
|
||||
@@ -2066,6 +2069,13 @@ def update_cleanup_settings_api():
|
||||
'error': 'Invalid enabled (must be boolean)'
|
||||
}), 400
|
||||
|
||||
if 'hour' in data:
|
||||
if not isinstance(data['hour'], int) or data['hour'] < 0 or data['hour'] > 23:
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'error': 'Invalid hour (must be integer 0-23)'
|
||||
}), 400
|
||||
|
||||
# Get current settings and merge with new values
|
||||
current = get_cleanup_settings()
|
||||
updated = {**current, **data}
|
||||
@@ -2077,9 +2087,9 @@ def update_cleanup_settings_api():
|
||||
'error': 'Failed to save cleanup settings'
|
||||
}), 500
|
||||
|
||||
# Update scheduler based on enabled state
|
||||
# Update scheduler based on enabled state and hour
|
||||
from app.archiver.manager import schedule_cleanup
|
||||
schedule_cleanup(enabled=updated.get('enabled', False))
|
||||
schedule_cleanup(enabled=updated.get('enabled', False), hour=updated.get('hour', 1))
|
||||
|
||||
return jsonify({
|
||||
'success': True,
|
||||
|
||||
@@ -224,6 +224,17 @@ function attachManageEventListeners() {
|
||||
document.querySelectorAll('input[name="cleanupDateField"]').forEach(radio => {
|
||||
radio.addEventListener('change', debouncedSaveCleanupCriteria);
|
||||
});
|
||||
|
||||
// Cleanup hour selector (only saves when auto-cleanup is enabled)
|
||||
const cleanupHour = document.getElementById('cleanupHour');
|
||||
if (cleanupHour) {
|
||||
cleanupHour.addEventListener('change', () => {
|
||||
// Only save if auto-cleanup is enabled
|
||||
if (autoCleanupSettings && autoCleanupSettings.enabled) {
|
||||
saveCleanupSettings(true);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async function loadContactCounts() {
|
||||
@@ -334,14 +345,23 @@ function applyCleanupSettingsToUI(settings) {
|
||||
// Auto-cleanup switch and status
|
||||
const autoCleanupSwitch = document.getElementById('autoCleanupSwitch');
|
||||
const statusText = document.getElementById('autoCleanupStatusText');
|
||||
const hourSelect = document.getElementById('cleanupHour');
|
||||
|
||||
if (autoCleanupSwitch) {
|
||||
autoCleanupSwitch.checked = settings.enabled || false;
|
||||
}
|
||||
|
||||
// Hour selector
|
||||
const hour = settings.hour !== undefined ? settings.hour : 1;
|
||||
if (hourSelect) {
|
||||
hourSelect.value = hour;
|
||||
hourSelect.disabled = !settings.enabled;
|
||||
}
|
||||
|
||||
if (statusText) {
|
||||
if (settings.enabled) {
|
||||
statusText.textContent = 'Enabled (runs daily at 01:00 UTC)';
|
||||
const hourStr = hour.toString().padStart(2, '0');
|
||||
statusText.textContent = `Enabled (runs daily at ${hourStr}:00 UTC)`;
|
||||
statusText.classList.remove('text-muted');
|
||||
statusText.classList.add('text-success');
|
||||
} else {
|
||||
@@ -359,6 +379,7 @@ function applyCleanupSettingsToUI(settings) {
|
||||
async function handleAutoCleanupToggle(event) {
|
||||
const enabled = event.target.checked;
|
||||
const statusText = document.getElementById('autoCleanupStatusText');
|
||||
const hourSelect = document.getElementById('cleanupHour');
|
||||
|
||||
// Validate before enabling
|
||||
if (enabled) {
|
||||
@@ -379,6 +400,11 @@ async function handleAutoCleanupToggle(event) {
|
||||
}
|
||||
}
|
||||
|
||||
// Enable/disable hour selector
|
||||
if (hourSelect) {
|
||||
hourSelect.disabled = !enabled;
|
||||
}
|
||||
|
||||
// Update status text while saving
|
||||
if (statusText) {
|
||||
statusText.textContent = 'Saving...';
|
||||
@@ -389,8 +415,11 @@ async function handleAutoCleanupToggle(event) {
|
||||
const success = await saveCleanupSettings(enabled);
|
||||
|
||||
if (!success) {
|
||||
// Revert switch on failure
|
||||
// Revert switch and hour selector on failure
|
||||
event.target.checked = !enabled;
|
||||
if (hourSelect) {
|
||||
hourSelect.disabled = enabled;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -422,13 +451,16 @@ function debouncedSaveCleanupCriteria() {
|
||||
async function saveCleanupSettings(enabled) {
|
||||
const criteria = collectCleanupCriteria();
|
||||
const statusText = document.getElementById('autoCleanupStatusText');
|
||||
const hourSelect = document.getElementById('cleanupHour');
|
||||
const hour = hourSelect ? parseInt(hourSelect.value) : 1;
|
||||
|
||||
const settings = {
|
||||
enabled: enabled,
|
||||
types: criteria.types,
|
||||
date_field: criteria.date_field,
|
||||
days: criteria.days,
|
||||
name_filter: criteria.name_filter
|
||||
name_filter: criteria.name_filter,
|
||||
hour: hour
|
||||
};
|
||||
|
||||
try {
|
||||
@@ -448,7 +480,9 @@ async function saveCleanupSettings(enabled) {
|
||||
// Update status text
|
||||
if (statusText) {
|
||||
if (data.settings.enabled) {
|
||||
statusText.textContent = 'Enabled (runs daily at 01:00 UTC)';
|
||||
const savedHour = data.settings.hour !== undefined ? data.settings.hour : 1;
|
||||
const hourStr = savedHour.toString().padStart(2, '0');
|
||||
statusText.textContent = `Enabled (runs daily at ${hourStr}:00 UTC)`;
|
||||
statusText.classList.remove('text-muted');
|
||||
statusText.classList.add('text-success');
|
||||
} else {
|
||||
@@ -467,7 +501,9 @@ async function saveCleanupSettings(enabled) {
|
||||
// Restore previous status
|
||||
if (statusText && autoCleanupSettings) {
|
||||
if (autoCleanupSettings.enabled) {
|
||||
statusText.textContent = 'Enabled (runs daily at 01:00 UTC)';
|
||||
const prevHour = autoCleanupSettings.hour !== undefined ? autoCleanupSettings.hour : 1;
|
||||
const hourStr = prevHour.toString().padStart(2, '0');
|
||||
statusText.textContent = `Enabled (runs daily at ${hourStr}:00 UTC)`;
|
||||
} else {
|
||||
statusText.textContent = 'Disabled';
|
||||
}
|
||||
|
||||
@@ -135,9 +135,40 @@
|
||||
<i class="bi bi-info-circle info-icon"
|
||||
data-bs-toggle="tooltip"
|
||||
data-bs-placement="top"
|
||||
title="When enabled, contacts matching the above criteria will be automatically deleted daily at 01:00 UTC"></i>
|
||||
title="When enabled, contacts matching the above criteria will be automatically deleted daily at the specified hour (UTC)"></i>
|
||||
</div>
|
||||
<small class="text-muted">Status: <span id="autoCleanupStatusText">Loading...</span></small>
|
||||
|
||||
<!-- Cleanup Hour Selector -->
|
||||
<div class="mt-2 d-flex align-items-center gap-2">
|
||||
<label for="cleanupHour" class="form-label mb-0 small">Run at:</label>
|
||||
<select class="form-select form-select-sm" id="cleanupHour" style="width: auto;" disabled>
|
||||
<option value="0">00:00 UTC</option>
|
||||
<option value="1" selected>01:00 UTC</option>
|
||||
<option value="2">02:00 UTC</option>
|
||||
<option value="3">03:00 UTC</option>
|
||||
<option value="4">04:00 UTC</option>
|
||||
<option value="5">05:00 UTC</option>
|
||||
<option value="6">06:00 UTC</option>
|
||||
<option value="7">07:00 UTC</option>
|
||||
<option value="8">08:00 UTC</option>
|
||||
<option value="9">09:00 UTC</option>
|
||||
<option value="10">10:00 UTC</option>
|
||||
<option value="11">11:00 UTC</option>
|
||||
<option value="12">12:00 UTC</option>
|
||||
<option value="13">13:00 UTC</option>
|
||||
<option value="14">14:00 UTC</option>
|
||||
<option value="15">15:00 UTC</option>
|
||||
<option value="16">16:00 UTC</option>
|
||||
<option value="17">17:00 UTC</option>
|
||||
<option value="18">18:00 UTC</option>
|
||||
<option value="19">19:00 UTC</option>
|
||||
<option value="20">20:00 UTC</option>
|
||||
<option value="21">21:00 UTC</option>
|
||||
<option value="22">22:00 UTC</option>
|
||||
<option value="23">23:00 UTC</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -282,6 +282,25 @@ The advanced cleanup tool allows you to filter and remove contacts based on mult
|
||||
- Remove all REP contacts inactive for 30+ days: Select REP, set days to 30
|
||||
- Clean specific contact names: Enter partial name (e.g., "test")
|
||||
|
||||
### Automatic Contact Cleanup
|
||||
|
||||
You can schedule automatic cleanup to run daily at a specified hour (UTC):
|
||||
|
||||
1. Navigate to **Contact Management** page
|
||||
2. Expand **Advanced Filters** section
|
||||
3. Configure your filter criteria (types, date field, days of inactivity)
|
||||
4. Toggle **Enable Auto-Cleanup** switch
|
||||
5. Select the hour (UTC) when cleanup should run
|
||||
|
||||
**Requirements for enabling auto-cleanup:**
|
||||
- "Days of Inactivity" must be set to a value greater than 0
|
||||
- At least one contact type must be selected
|
||||
|
||||
**Notes:**
|
||||
- Protected contacts are never deleted by auto-cleanup
|
||||
- Filter criteria changes are auto-saved when auto-cleanup is enabled
|
||||
- The scheduler runs in UTC timezone
|
||||
|
||||
---
|
||||
|
||||
## Network Commands
|
||||
|
||||
Reference in New Issue
Block a user