mirror of
https://github.com/xpltdco/media-rip.git
synced 2026-04-03 02:53:58 -06:00
Purge intervals: hours→minutes, default ON at 1440min (24h)
- PurgeConfig: max_age_hours→max_age_minutes (default 1440) - PurgeConfig: privacy_retention_hours→privacy_retention_minutes (default 1440) - PurgeConfig: enabled default False→True - PurgeConfig: cron default every minute (was daily 3am) - Purge scheduler runs every minute for minute-granularity testing - All API fields renamed: purge_max_age_minutes, privacy_retention_minutes - Frontend admin panel inputs show minutes with updated labels - Updated test assertions for new defaults
This commit is contained in:
parent
0a67cb45bc
commit
43ddf43951
12 changed files with 90 additions and 56 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
|
@ -38,3 +38,6 @@ vendor/
|
||||||
coverage/
|
coverage/
|
||||||
.cache/
|
.cache/
|
||||||
tmp/
|
tmp/
|
||||||
|
|
||||||
|
# ── GSD baseline (auto-generated) ──
|
||||||
|
.gsd
|
||||||
|
|
|
||||||
|
|
@ -65,11 +65,11 @@ class SessionConfig(BaseModel):
|
||||||
class PurgeConfig(BaseModel):
|
class PurgeConfig(BaseModel):
|
||||||
"""Automatic purge / cleanup settings."""
|
"""Automatic purge / cleanup settings."""
|
||||||
|
|
||||||
enabled: bool = False
|
enabled: bool = True
|
||||||
max_age_hours: int = 168 # 7 days
|
max_age_minutes: int = 1440 # 24 hours
|
||||||
cron: str = "0 3 * * *" # 3 AM daily
|
cron: str = "* * * * *" # every minute
|
||||||
privacy_mode: bool = False
|
privacy_mode: bool = False
|
||||||
privacy_retention_hours: int = 24 # default when privacy mode enabled
|
privacy_retention_minutes: int = 1440 # default when privacy mode enabled
|
||||||
|
|
||||||
|
|
||||||
class UIConfig(BaseModel):
|
class UIConfig(BaseModel):
|
||||||
|
|
|
||||||
|
|
@ -288,13 +288,13 @@ async def get_settings(
|
||||||
"default_video_format": getattr(request.app.state, "_default_video_format", "auto"),
|
"default_video_format": getattr(request.app.state, "_default_video_format", "auto"),
|
||||||
"default_audio_format": getattr(request.app.state, "_default_audio_format", "auto"),
|
"default_audio_format": getattr(request.app.state, "_default_audio_format", "auto"),
|
||||||
"privacy_mode": config.purge.privacy_mode,
|
"privacy_mode": config.purge.privacy_mode,
|
||||||
"privacy_retention_hours": config.purge.privacy_retention_hours,
|
"privacy_retention_minutes": config.purge.privacy_retention_minutes,
|
||||||
"max_concurrent": config.downloads.max_concurrent,
|
"max_concurrent": config.downloads.max_concurrent,
|
||||||
"session_mode": config.session.mode,
|
"session_mode": config.session.mode,
|
||||||
"session_timeout_hours": config.session.timeout_hours,
|
"session_timeout_hours": config.session.timeout_hours,
|
||||||
"admin_username": config.admin.username,
|
"admin_username": config.admin.username,
|
||||||
"purge_enabled": config.purge.enabled,
|
"purge_enabled": config.purge.enabled,
|
||||||
"purge_max_age_hours": config.purge.max_age_hours,
|
"purge_max_age_minutes": config.purge.max_age_minutes,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -310,13 +310,13 @@ async def update_settings(
|
||||||
- default_video_format: str (auto, mp4, webm)
|
- default_video_format: str (auto, mp4, webm)
|
||||||
- default_audio_format: str (auto, mp3, m4a, flac, wav, opus)
|
- default_audio_format: str (auto, mp3, m4a, flac, wav, opus)
|
||||||
- privacy_mode: bool
|
- privacy_mode: bool
|
||||||
- privacy_retention_hours: int (1-8760)
|
- privacy_retention_minutes: int (1-525600)
|
||||||
- max_concurrent: int (1-10)
|
- max_concurrent: int (1-10)
|
||||||
- session_mode: str (isolated, shared, open)
|
- session_mode: str (isolated, shared, open)
|
||||||
- session_timeout_hours: int (1-8760)
|
- session_timeout_hours: int (1-8760)
|
||||||
- admin_username: str
|
- admin_username: str
|
||||||
- purge_enabled: bool
|
- purge_enabled: bool
|
||||||
- purge_max_age_hours: int (1-87600)
|
- purge_max_age_minutes: int (1-5256000)
|
||||||
"""
|
"""
|
||||||
from app.services.settings import save_settings
|
from app.services.settings import save_settings
|
||||||
|
|
||||||
|
|
@ -364,12 +364,12 @@ async def update_settings(
|
||||||
if val and not getattr(request.app.state, "scheduler", None):
|
if val and not getattr(request.app.state, "scheduler", None):
|
||||||
_start_purge_scheduler(request.app.state, config, db)
|
_start_purge_scheduler(request.app.state, config, db)
|
||||||
|
|
||||||
if "privacy_retention_hours" in body:
|
if "privacy_retention_minutes" in body:
|
||||||
val = body["privacy_retention_hours"]
|
val = body["privacy_retention_minutes"]
|
||||||
if isinstance(val, (int, float)) and 1 <= val <= 8760:
|
if isinstance(val, (int, float)) and 1 <= val <= 525600:
|
||||||
config.purge.privacy_retention_hours = int(val)
|
config.purge.privacy_retention_minutes = int(val)
|
||||||
to_persist["privacy_retention_hours"] = int(val)
|
to_persist["privacy_retention_minutes"] = int(val)
|
||||||
updated.append("privacy_retention_hours")
|
updated.append("privacy_retention_minutes")
|
||||||
|
|
||||||
if "max_concurrent" in body:
|
if "max_concurrent" in body:
|
||||||
val = body["max_concurrent"]
|
val = body["max_concurrent"]
|
||||||
|
|
@ -411,12 +411,12 @@ async def update_settings(
|
||||||
if val and not getattr(request.app.state, "scheduler", None):
|
if val and not getattr(request.app.state, "scheduler", None):
|
||||||
_start_purge_scheduler(request.app.state, config, db)
|
_start_purge_scheduler(request.app.state, config, db)
|
||||||
|
|
||||||
if "purge_max_age_hours" in body:
|
if "purge_max_age_minutes" in body:
|
||||||
val = body["purge_max_age_hours"]
|
val = body["purge_max_age_minutes"]
|
||||||
if isinstance(val, int) and 1 <= val <= 87600:
|
if isinstance(val, int) and 1 <= val <= 5256000:
|
||||||
config.purge.max_age_hours = val
|
config.purge.max_age_minutes = val
|
||||||
to_persist["purge_max_age_hours"] = val
|
to_persist["purge_max_age_minutes"] = val
|
||||||
updated.append("purge_max_age_hours")
|
updated.append("purge_max_age_minutes")
|
||||||
|
|
||||||
# --- Persist to DB ---
|
# --- Persist to DB ---
|
||||||
if to_persist:
|
if to_persist:
|
||||||
|
|
@ -485,7 +485,7 @@ def _start_purge_scheduler(state, config, db) -> None:
|
||||||
scheduler = AsyncIOScheduler()
|
scheduler = AsyncIOScheduler()
|
||||||
scheduler.add_job(
|
scheduler.add_job(
|
||||||
run_purge,
|
run_purge,
|
||||||
CronTrigger(minute="*/30"),
|
CronTrigger(minute="*"),
|
||||||
args=[db, config],
|
args=[db, config],
|
||||||
id="purge_job",
|
id="purge_job",
|
||||||
name="Scheduled purge",
|
name="Scheduled purge",
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,7 @@ async def public_config(request: Request) -> dict:
|
||||||
"default_video_format": getattr(request.app.state, "_default_video_format", "auto"),
|
"default_video_format": getattr(request.app.state, "_default_video_format", "auto"),
|
||||||
"default_audio_format": getattr(request.app.state, "_default_audio_format", "auto"),
|
"default_audio_format": getattr(request.app.state, "_default_audio_format", "auto"),
|
||||||
"privacy_mode": config.purge.privacy_mode,
|
"privacy_mode": config.purge.privacy_mode,
|
||||||
"privacy_retention_hours": config.purge.privacy_retention_hours,
|
"privacy_retention_minutes": config.purge.privacy_retention_minutes,
|
||||||
"admin_enabled": config.admin.enabled,
|
"admin_enabled": config.admin.enabled,
|
||||||
"admin_setup_complete": bool(config.admin.password_hash),
|
"admin_setup_complete": bool(config.admin.password_hash),
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -42,12 +42,12 @@ async def run_purge(
|
||||||
privacy_on = overrides.get("privacy_mode", config.purge.privacy_mode)
|
privacy_on = overrides.get("privacy_mode", config.purge.privacy_mode)
|
||||||
if privacy_on:
|
if privacy_on:
|
||||||
retention = overrides.get(
|
retention = overrides.get(
|
||||||
"privacy_retention_hours", config.purge.privacy_retention_hours
|
"privacy_retention_minutes", config.purge.privacy_retention_minutes
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
retention = config.purge.max_age_hours
|
retention = config.purge.max_age_minutes
|
||||||
cutoff = (datetime.now(timezone.utc) - timedelta(hours=retention)).isoformat()
|
cutoff = (datetime.now(timezone.utc) - timedelta(minutes=retention)).isoformat()
|
||||||
logger.info("Purge starting: retention=%dh (privacy=%s), cutoff=%s", retention, privacy_on, cutoff)
|
logger.info("Purge starting: retention=%dm (privacy=%s), cutoff=%s", retention, privacy_on, cutoff)
|
||||||
|
|
||||||
output_dir = Path(config.downloads.output_dir)
|
output_dir = Path(config.downloads.output_dir)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -26,14 +26,14 @@ ADMIN_WRITABLE_KEYS = {
|
||||||
"default_video_format",
|
"default_video_format",
|
||||||
"default_audio_format",
|
"default_audio_format",
|
||||||
"privacy_mode",
|
"privacy_mode",
|
||||||
"privacy_retention_hours",
|
"privacy_retention_minutes",
|
||||||
"max_concurrent",
|
"max_concurrent",
|
||||||
"session_mode",
|
"session_mode",
|
||||||
"session_timeout_hours",
|
"session_timeout_hours",
|
||||||
"admin_username",
|
"admin_username",
|
||||||
"admin_password_hash",
|
"admin_password_hash",
|
||||||
"purge_enabled",
|
"purge_enabled",
|
||||||
"purge_max_age_hours",
|
"purge_max_age_minutes",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -104,12 +104,12 @@ def apply_persisted_to_config(config, settings: dict) -> None:
|
||||||
config.admin.password_hash = settings["admin_password_hash"]
|
config.admin.password_hash = settings["admin_password_hash"]
|
||||||
if "purge_enabled" in settings:
|
if "purge_enabled" in settings:
|
||||||
config.purge.enabled = settings["purge_enabled"]
|
config.purge.enabled = settings["purge_enabled"]
|
||||||
if "purge_max_age_hours" in settings:
|
if "purge_max_age_minutes" in settings:
|
||||||
config.purge.max_age_hours = settings["purge_max_age_hours"]
|
config.purge.max_age_minutes = settings["purge_max_age_minutes"]
|
||||||
if "privacy_mode" in settings:
|
if "privacy_mode" in settings:
|
||||||
config.purge.privacy_mode = settings["privacy_mode"]
|
config.purge.privacy_mode = settings["privacy_mode"]
|
||||||
if "privacy_retention_hours" in settings:
|
if "privacy_retention_minutes" in settings:
|
||||||
config.purge.privacy_retention_hours = settings["privacy_retention_hours"]
|
config.purge.privacy_retention_minutes = settings["privacy_retention_minutes"]
|
||||||
|
|
||||||
logger.info("Applied %d persisted settings to config", len(settings))
|
logger.info("Applied %d persisted settings to config", len(settings))
|
||||||
|
|
||||||
|
|
@ -123,7 +123,7 @@ def _deserialize(key: str, raw: str) -> object:
|
||||||
|
|
||||||
# Type coercion for known keys
|
# Type coercion for known keys
|
||||||
bool_keys = {"privacy_mode", "purge_enabled"}
|
bool_keys = {"privacy_mode", "purge_enabled"}
|
||||||
int_keys = {"max_concurrent", "session_timeout_hours", "purge_max_age_hours", "privacy_retention_hours"}
|
int_keys = {"max_concurrent", "session_timeout_hours", "purge_max_age_minutes", "privacy_retention_minutes"}
|
||||||
|
|
||||||
if key in bool_keys:
|
if key in bool_keys:
|
||||||
return bool(value)
|
return bool(value)
|
||||||
|
|
|
||||||
31
backend/start.py
Normal file
31
backend/start.py
Normal file
|
|
@ -0,0 +1,31 @@
|
||||||
|
#!/usr/bin/env python
|
||||||
|
"""media.rip() entrypoint — reads config and launches uvicorn with the correct port."""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
# Port from env var (MEDIARIP__SERVER__PORT) or default 8000
|
||||||
|
port = os.environ.get("MEDIARIP__SERVER__PORT", "8000")
|
||||||
|
host = os.environ.get("MEDIARIP__SERVER__HOST", "0.0.0.0")
|
||||||
|
|
||||||
|
sys.exit(
|
||||||
|
os.execvp(
|
||||||
|
"python",
|
||||||
|
[
|
||||||
|
"python",
|
||||||
|
"-m",
|
||||||
|
"uvicorn",
|
||||||
|
"app.main:app",
|
||||||
|
"--host",
|
||||||
|
host,
|
||||||
|
"--port",
|
||||||
|
port,
|
||||||
|
],
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
|
|
@ -173,7 +173,7 @@ class TestPublicConfig:
|
||||||
data = resp.json()
|
data = resp.json()
|
||||||
assert data["session_mode"] == "isolated"
|
assert data["session_mode"] == "isolated"
|
||||||
assert data["default_theme"] == "dark"
|
assert data["default_theme"] == "dark"
|
||||||
assert data["purge_enabled"] is False
|
assert data["purge_enabled"] is True
|
||||||
assert data["max_concurrent_downloads"] == 3
|
assert data["max_concurrent_downloads"] == 3
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -42,7 +42,7 @@ class TestPurge:
|
||||||
async def test_purge_deletes_old_completed_jobs(self, db, tmp_path):
|
async def test_purge_deletes_old_completed_jobs(self, db, tmp_path):
|
||||||
config = AppConfig(
|
config = AppConfig(
|
||||||
downloads={"output_dir": str(tmp_path)},
|
downloads={"output_dir": str(tmp_path)},
|
||||||
purge={"max_age_hours": 24},
|
purge={"max_age_minutes": 1440},
|
||||||
)
|
)
|
||||||
sid = str(uuid.uuid4())
|
sid = str(uuid.uuid4())
|
||||||
|
|
||||||
|
|
@ -57,7 +57,7 @@ class TestPurge:
|
||||||
async def test_purge_skips_recent_completed(self, db, tmp_path):
|
async def test_purge_skips_recent_completed(self, db, tmp_path):
|
||||||
config = AppConfig(
|
config = AppConfig(
|
||||||
downloads={"output_dir": str(tmp_path)},
|
downloads={"output_dir": str(tmp_path)},
|
||||||
purge={"max_age_hours": 24},
|
purge={"max_age_minutes": 1440},
|
||||||
)
|
)
|
||||||
sid = str(uuid.uuid4())
|
sid = str(uuid.uuid4())
|
||||||
|
|
||||||
|
|
@ -72,7 +72,7 @@ class TestPurge:
|
||||||
async def test_purge_skips_active_jobs(self, db, tmp_path):
|
async def test_purge_skips_active_jobs(self, db, tmp_path):
|
||||||
config = AppConfig(
|
config = AppConfig(
|
||||||
downloads={"output_dir": str(tmp_path)},
|
downloads={"output_dir": str(tmp_path)},
|
||||||
purge={"max_age_hours": 0}, # purge everything terminal
|
purge={"max_age_minutes": 0}, # purge everything terminal
|
||||||
)
|
)
|
||||||
sid = str(uuid.uuid4())
|
sid = str(uuid.uuid4())
|
||||||
|
|
||||||
|
|
@ -88,7 +88,7 @@ class TestPurge:
|
||||||
async def test_purge_deletes_files(self, db, tmp_path):
|
async def test_purge_deletes_files(self, db, tmp_path):
|
||||||
config = AppConfig(
|
config = AppConfig(
|
||||||
downloads={"output_dir": str(tmp_path)},
|
downloads={"output_dir": str(tmp_path)},
|
||||||
purge={"max_age_hours": 0},
|
purge={"max_age_minutes": 0},
|
||||||
)
|
)
|
||||||
sid = str(uuid.uuid4())
|
sid = str(uuid.uuid4())
|
||||||
|
|
||||||
|
|
@ -107,7 +107,7 @@ class TestPurge:
|
||||||
async def test_purge_handles_missing_files(self, db, tmp_path):
|
async def test_purge_handles_missing_files(self, db, tmp_path):
|
||||||
config = AppConfig(
|
config = AppConfig(
|
||||||
downloads={"output_dir": str(tmp_path)},
|
downloads={"output_dir": str(tmp_path)},
|
||||||
purge={"max_age_hours": 0},
|
purge={"max_age_minutes": 0},
|
||||||
)
|
)
|
||||||
sid = str(uuid.uuid4())
|
sid = str(uuid.uuid4())
|
||||||
|
|
||||||
|
|
@ -123,7 +123,7 @@ class TestPurge:
|
||||||
async def test_purge_mixed_statuses(self, db, tmp_path):
|
async def test_purge_mixed_statuses(self, db, tmp_path):
|
||||||
config = AppConfig(
|
config = AppConfig(
|
||||||
downloads={"output_dir": str(tmp_path)},
|
downloads={"output_dir": str(tmp_path)},
|
||||||
purge={"max_age_hours": 0},
|
purge={"max_age_minutes": 0},
|
||||||
)
|
)
|
||||||
sid = str(uuid.uuid4())
|
sid = str(uuid.uuid4())
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -76,7 +76,7 @@ export interface PublicConfig {
|
||||||
default_video_format: string
|
default_video_format: string
|
||||||
default_audio_format: string
|
default_audio_format: string
|
||||||
privacy_mode: boolean
|
privacy_mode: boolean
|
||||||
privacy_retention_hours: number
|
privacy_retention_minutes: number
|
||||||
admin_enabled: boolean
|
admin_enabled: boolean
|
||||||
admin_setup_complete: boolean
|
admin_setup_complete: boolean
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,7 @@ const defaultVideoFormat = ref('auto')
|
||||||
const defaultAudioFormat = ref('auto')
|
const defaultAudioFormat = ref('auto')
|
||||||
const settingsSaved = ref(false)
|
const settingsSaved = ref(false)
|
||||||
const privacyMode = ref(false)
|
const privacyMode = ref(false)
|
||||||
const privacyRetentionHours = ref(24)
|
const privacyRetentionMinutes = ref(1440)
|
||||||
const purgeConfirming = ref(false)
|
const purgeConfirming = ref(false)
|
||||||
let purgeConfirmTimer: ReturnType<typeof setTimeout> | null = null
|
let purgeConfirmTimer: ReturnType<typeof setTimeout> | null = null
|
||||||
|
|
||||||
|
|
@ -32,8 +32,8 @@ const maxConcurrent = ref(3)
|
||||||
const sessionMode = ref('isolated')
|
const sessionMode = ref('isolated')
|
||||||
const sessionTimeoutHours = ref(72)
|
const sessionTimeoutHours = ref(72)
|
||||||
const adminUsername = ref('admin')
|
const adminUsername = ref('admin')
|
||||||
const purgeEnabled = ref(false)
|
const purgeEnabled = ref(true)
|
||||||
const purgeMaxAgeHours = ref(168)
|
const purgeMaxAgeMinutes = ref(1440)
|
||||||
|
|
||||||
// Change password state
|
// Change password state
|
||||||
const currentPassword = ref('')
|
const currentPassword = ref('')
|
||||||
|
|
@ -73,13 +73,13 @@ async function switchTab(tab: typeof activeTab.value) {
|
||||||
defaultVideoFormat.value = data.default_video_format || 'auto'
|
defaultVideoFormat.value = data.default_video_format || 'auto'
|
||||||
defaultAudioFormat.value = data.default_audio_format || 'auto'
|
defaultAudioFormat.value = data.default_audio_format || 'auto'
|
||||||
privacyMode.value = data.privacy_mode ?? false
|
privacyMode.value = data.privacy_mode ?? false
|
||||||
privacyRetentionHours.value = data.privacy_retention_hours ?? 24
|
privacyRetentionMinutes.value = data.privacy_retention_minutes ?? 1440
|
||||||
maxConcurrent.value = data.max_concurrent ?? 3
|
maxConcurrent.value = data.max_concurrent ?? 3
|
||||||
sessionMode.value = data.session_mode ?? 'isolated'
|
sessionMode.value = data.session_mode ?? 'isolated'
|
||||||
sessionTimeoutHours.value = data.session_timeout_hours ?? 72
|
sessionTimeoutHours.value = data.session_timeout_hours ?? 72
|
||||||
adminUsername.value = data.admin_username ?? 'admin'
|
adminUsername.value = data.admin_username ?? 'admin'
|
||||||
purgeEnabled.value = data.purge_enabled ?? false
|
purgeEnabled.value = data.purge_enabled ?? false
|
||||||
purgeMaxAgeHours.value = data.purge_max_age_hours ?? 168
|
purgeMaxAgeMinutes.value = data.purge_max_age_minutes ?? 1440
|
||||||
}
|
}
|
||||||
} catch {
|
} catch {
|
||||||
// Keep current values
|
// Keep current values
|
||||||
|
|
@ -94,13 +94,13 @@ async function saveAllSettings() {
|
||||||
default_video_format: defaultVideoFormat.value,
|
default_video_format: defaultVideoFormat.value,
|
||||||
default_audio_format: defaultAudioFormat.value,
|
default_audio_format: defaultAudioFormat.value,
|
||||||
privacy_mode: privacyMode.value,
|
privacy_mode: privacyMode.value,
|
||||||
privacy_retention_hours: privacyRetentionHours.value,
|
privacy_retention_minutes: privacyRetentionMinutes.value,
|
||||||
max_concurrent: maxConcurrent.value,
|
max_concurrent: maxConcurrent.value,
|
||||||
session_mode: sessionMode.value,
|
session_mode: sessionMode.value,
|
||||||
session_timeout_hours: sessionTimeoutHours.value,
|
session_timeout_hours: sessionTimeoutHours.value,
|
||||||
admin_username: adminUsername.value,
|
admin_username: adminUsername.value,
|
||||||
purge_enabled: purgeEnabled.value,
|
purge_enabled: purgeEnabled.value,
|
||||||
purge_max_age_hours: purgeMaxAgeHours.value,
|
purge_max_age_minutes: purgeMaxAgeMinutes.value,
|
||||||
})
|
})
|
||||||
if (ok) {
|
if (ok) {
|
||||||
await configStore.loadConfig()
|
await configStore.loadConfig()
|
||||||
|
|
@ -384,15 +384,15 @@ function formatFilesize(bytes: number | null): string {
|
||||||
<div class="retention-input-row">
|
<div class="retention-input-row">
|
||||||
<input
|
<input
|
||||||
type="number"
|
type="number"
|
||||||
v-model.number="privacyRetentionHours"
|
v-model.number="privacyRetentionMinutes"
|
||||||
min="1"
|
min="1"
|
||||||
max="8760"
|
max="525600"
|
||||||
class="settings-input retention-input"
|
class="settings-input retention-input"
|
||||||
/>
|
/>
|
||||||
<span class="retention-unit">hours</span>
|
<span class="retention-unit">minutes</span>
|
||||||
</div>
|
</div>
|
||||||
<p class="field-hint">
|
<p class="field-hint">
|
||||||
Data older than this is automatically purged (default: 24 hours).
|
Data older than this is automatically purged (default: 1440 min / 24 hours).
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -464,12 +464,12 @@ function formatFilesize(bytes: number | null): string {
|
||||||
<div class="retention-input-row">
|
<div class="retention-input-row">
|
||||||
<input
|
<input
|
||||||
type="number"
|
type="number"
|
||||||
v-model.number="purgeMaxAgeHours"
|
v-model.number="purgeMaxAgeMinutes"
|
||||||
min="1"
|
min="1"
|
||||||
max="87600"
|
max="5256000"
|
||||||
class="settings-input retention-input"
|
class="settings-input retention-input"
|
||||||
/>
|
/>
|
||||||
<span class="retention-unit">hours</span>
|
<span class="retention-unit">minutes</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -34,7 +34,7 @@ describe('config store', () => {
|
||||||
default_video_format: 'auto',
|
default_video_format: 'auto',
|
||||||
default_audio_format: 'auto',
|
default_audio_format: 'auto',
|
||||||
privacy_mode: false,
|
privacy_mode: false,
|
||||||
privacy_retention_hours: 24,
|
privacy_retention_minutes: 1440,
|
||||||
admin_enabled: true,
|
admin_enabled: true,
|
||||||
admin_setup_complete: false,
|
admin_setup_complete: false,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue