extended logs
This commit is contained in:
@@ -1,3 +1,4 @@
|
|||||||
|
from datetime import date
|
||||||
import io
|
import io
|
||||||
import zipfile
|
import zipfile
|
||||||
|
|
||||||
@@ -18,6 +19,7 @@ def list_backups(
|
|||||||
search: str | None = Query(default=None),
|
search: str | None = Query(default=None),
|
||||||
backup_type: str | None = Query(default=None, pattern="^(export|binary)$"),
|
backup_type: str | None = Query(default=None, pattern="^(export|binary)$"),
|
||||||
router_id: int | None = Query(default=None),
|
router_id: int | None = Query(default=None),
|
||||||
|
created_on: date | None = Query(default=None),
|
||||||
sort_by: str = Query(default="created_at"),
|
sort_by: str = Query(default="created_at"),
|
||||||
order: str = Query(default="desc", pattern="^(asc|desc)$"),
|
order: str = Query(default="desc", pattern="^(asc|desc)$"),
|
||||||
current_user: User = Depends(get_current_user),
|
current_user: User = Depends(get_current_user),
|
||||||
@@ -29,6 +31,7 @@ def list_backups(
|
|||||||
search=search,
|
search=search,
|
||||||
backup_type=backup_type,
|
backup_type=backup_type,
|
||||||
router_id=router_id,
|
router_id=router_id,
|
||||||
|
created_on=created_on,
|
||||||
sort_by=sort_by,
|
sort_by=sort_by,
|
||||||
order=order,
|
order=order,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import difflib
|
import difflib
|
||||||
from datetime import datetime, timedelta, timezone
|
from datetime import date, datetime, time, timedelta, timezone
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
from fastapi import HTTPException
|
from fastapi import HTTPException
|
||||||
@@ -146,6 +146,7 @@ class BackupService:
|
|||||||
search: str | None = None,
|
search: str | None = None,
|
||||||
backup_type: str | None = None,
|
backup_type: str | None = None,
|
||||||
router_id: int | None = None,
|
router_id: int | None = None,
|
||||||
|
created_on: date | None = None,
|
||||||
sort_by: str = 'created_at',
|
sort_by: str = 'created_at',
|
||||||
order: str = 'desc',
|
order: str = 'desc',
|
||||||
):
|
):
|
||||||
@@ -160,6 +161,10 @@ class BackupService:
|
|||||||
query = query.filter(Backup.backup_type == backup_type)
|
query = query.filter(Backup.backup_type == backup_type)
|
||||||
if router_id:
|
if router_id:
|
||||||
query = query.filter(Backup.router_id == router_id)
|
query = query.filter(Backup.router_id == router_id)
|
||||||
|
if created_on:
|
||||||
|
day_start = datetime.combine(created_on, time.min)
|
||||||
|
next_day = day_start + timedelta(days=1)
|
||||||
|
query = query.filter(Backup.created_at >= day_start, Backup.created_at < next_day)
|
||||||
|
|
||||||
sort_map = {
|
sort_map = {
|
||||||
'created_at': Backup.created_at,
|
'created_at': Backup.created_at,
|
||||||
|
|||||||
@@ -173,14 +173,35 @@ class RouterService:
|
|||||||
auth_mode = result.get('auth_mode')
|
auth_mode = result.get('auth_mode')
|
||||||
http_status = result.get('http_status')
|
http_status = result.get('http_status')
|
||||||
backup_available = result.get('backup_available')
|
backup_available = result.get('backup_available')
|
||||||
|
hostname = result.get('hostname')
|
||||||
|
model = result.get('model')
|
||||||
|
version = result.get('version')
|
||||||
|
uptime = result.get('uptime')
|
||||||
|
server = result.get('server')
|
||||||
|
|
||||||
details = [f'via {transport}']
|
details = [f'via {transport}', f'target={router.host}:{router.port}']
|
||||||
if auth_mode:
|
if router.device_type == 'routeros':
|
||||||
details.append(f'auth={auth_mode}')
|
if router.ssh_user:
|
||||||
if http_status:
|
details.append(f'user={router.ssh_user}')
|
||||||
details.append(f'http={http_status}')
|
if hostname:
|
||||||
if backup_available is not None:
|
details.append(f'hostname={hostname}')
|
||||||
details.append(f'backup_available={"yes" if backup_available else "no"}')
|
if model and model != 'Unknown':
|
||||||
|
details.append(f'model={model}')
|
||||||
|
if version and version != 'Unknown':
|
||||||
|
details.append(f'version={version}')
|
||||||
|
if uptime and uptime != 'Unknown':
|
||||||
|
details.append(f'uptime={uptime}')
|
||||||
|
else:
|
||||||
|
if auth_mode:
|
||||||
|
details.append(f'auth={auth_mode}')
|
||||||
|
if http_status:
|
||||||
|
details.append(f'http={http_status}')
|
||||||
|
if server:
|
||||||
|
details.append(f'server={server}')
|
||||||
|
if backup_available is not None:
|
||||||
|
details.append(f'backup_available={"yes" if backup_available else "no"}')
|
||||||
|
if hostname:
|
||||||
|
details.append(f'hostname={hostname}')
|
||||||
|
|
||||||
detail_suffix = f' ({", ".join(details)})' if details else ''
|
detail_suffix = f' ({", ".join(details)})' if details else ''
|
||||||
if result.get('success'):
|
if result.get('success'):
|
||||||
|
|||||||
125
backend/tests/test_routeros_logging_and_files_filters.py
Normal file
125
backend/tests/test_routeros_logging_and_files_filters.py
Normal file
@@ -0,0 +1,125 @@
|
|||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
from app.db.session import SessionLocal
|
||||||
|
from app.models.backup import Backup
|
||||||
|
|
||||||
|
from fastapi.testclient import TestClient
|
||||||
|
|
||||||
|
from app.main import app
|
||||||
|
|
||||||
|
|
||||||
|
def _login(client: TestClient) -> dict[str, str]:
|
||||||
|
response = client.post('/api/auth/login', data={'username': 'admin', 'password': 'admin'})
|
||||||
|
token = response.json()['access_token']
|
||||||
|
return {'Authorization': f'Bearer {token}'}
|
||||||
|
|
||||||
|
|
||||||
|
def test_routeros_connection_test_creates_verbose_operation_log(monkeypatch):
|
||||||
|
from app.services import router_service as router_service_module
|
||||||
|
|
||||||
|
monkeypatch.setattr(
|
||||||
|
router_service_module.router_service,
|
||||||
|
'probe_connection',
|
||||||
|
lambda router, global_ssh_key=None, global_settings=None: {
|
||||||
|
'success': True,
|
||||||
|
'tested_at': datetime(2026, 4, 13, 10, 30, 0),
|
||||||
|
'model': 'RB5009UG+S+',
|
||||||
|
'uptime': '1d2h',
|
||||||
|
'hostname': 'rb5009-core',
|
||||||
|
'version': '7.18.2',
|
||||||
|
'error': None,
|
||||||
|
'transport': 'ssh',
|
||||||
|
'server': None,
|
||||||
|
'auth_mode': 'ssh',
|
||||||
|
'http_status': None,
|
||||||
|
'backup_available': None,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
with TestClient(app) as client:
|
||||||
|
headers = _login(client)
|
||||||
|
create_response = client.post(
|
||||||
|
'/api/routers',
|
||||||
|
json={
|
||||||
|
'name': 'core01',
|
||||||
|
'device_type': 'routeros',
|
||||||
|
'host': '10.10.10.1',
|
||||||
|
'port': 2222,
|
||||||
|
'ssh_user': 'backup',
|
||||||
|
'ssh_password': 'secret',
|
||||||
|
'ssh_key': None,
|
||||||
|
},
|
||||||
|
headers=headers,
|
||||||
|
)
|
||||||
|
assert create_response.status_code == 200
|
||||||
|
device_id = create_response.json()['id']
|
||||||
|
|
||||||
|
response = client.get(f'/api/routers/{device_id}/test-connection', headers=headers)
|
||||||
|
assert response.status_code == 200
|
||||||
|
|
||||||
|
logs_response = client.get('/api/logs', headers=headers)
|
||||||
|
assert logs_response.status_code == 200
|
||||||
|
assert any(
|
||||||
|
'Connection test OK for RouterOS device core01' in item['message']
|
||||||
|
and 'via ssh' in item['message']
|
||||||
|
and 'target=10.10.10.1:2222' in item['message']
|
||||||
|
and 'user=backup' in item['message']
|
||||||
|
and 'hostname=rb5009-core' in item['message']
|
||||||
|
and 'model=RB5009UG+S+' in item['message']
|
||||||
|
and 'version=7.18.2' in item['message']
|
||||||
|
and 'uptime=1d2h' in item['message']
|
||||||
|
for item in logs_response.json()
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_files_endpoint_filters_backups_by_created_on_date(monkeypatch):
|
||||||
|
from app.services import router_service as router_service_module
|
||||||
|
|
||||||
|
monkeypatch.setattr(
|
||||||
|
router_service_module.router_service,
|
||||||
|
'export',
|
||||||
|
lambda router, global_ssh_key=None: f'/system identity set name={router.name}',
|
||||||
|
)
|
||||||
|
|
||||||
|
with TestClient(app) as client:
|
||||||
|
headers = _login(client)
|
||||||
|
create_response = client.post(
|
||||||
|
'/api/routers',
|
||||||
|
json={
|
||||||
|
'name': 'archive01',
|
||||||
|
'device_type': 'routeros',
|
||||||
|
'host': '10.10.10.2',
|
||||||
|
'port': 22,
|
||||||
|
'ssh_user': 'admin',
|
||||||
|
'ssh_password': 'secret',
|
||||||
|
'ssh_key': None,
|
||||||
|
},
|
||||||
|
headers=headers,
|
||||||
|
)
|
||||||
|
assert create_response.status_code == 200
|
||||||
|
device_id = create_response.json()['id']
|
||||||
|
|
||||||
|
first = client.post(f'/api/backups/router/{device_id}/export', headers=headers)
|
||||||
|
second = client.post(f'/api/backups/router/{device_id}/export', headers=headers)
|
||||||
|
assert first.status_code == 200
|
||||||
|
assert second.status_code == 200
|
||||||
|
|
||||||
|
with SessionLocal() as db:
|
||||||
|
first_backup = db.query(Backup).filter(Backup.id == first.json()['id']).first()
|
||||||
|
second_backup = db.query(Backup).filter(Backup.id == second.json()['id']).first()
|
||||||
|
first_backup.created_at = datetime(2026, 4, 12, 9, 15, 0)
|
||||||
|
second_backup.created_at = datetime(2026, 4, 13, 11, 45, 0)
|
||||||
|
db.add(first_backup)
|
||||||
|
db.add(second_backup)
|
||||||
|
db.commit()
|
||||||
|
|
||||||
|
filtered = client.get(f'/api/backups?router_id={device_id}&created_on=2026-04-13', headers=headers)
|
||||||
|
assert filtered.status_code == 200
|
||||||
|
payload = filtered.json()
|
||||||
|
assert len(payload) == 1
|
||||||
|
assert payload[0]['created_at'].startswith('2026-04-13T11:45:00')
|
||||||
|
|
||||||
|
previous_day = client.get(f'/api/backups?router_id={device_id}&created_on=2026-04-12', headers=headers)
|
||||||
|
assert previous_day.status_code == 200
|
||||||
|
assert len(previous_day.json()) == 1
|
||||||
|
assert previous_day.json()[0]['created_at'].startswith('2026-04-12T09:15:00')
|
||||||
@@ -32,6 +32,11 @@
|
|||||||
<p-dropdown [options]="routerOptions" [(ngModel)]="routerId" optionLabel="label" optionValue="value"></p-dropdown>
|
<p-dropdown [options]="routerOptions" [(ngModel)]="routerId" optionLabel="label" optionValue="value"></p-dropdown>
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
|
<span class="form-field">
|
||||||
|
<label>{{ 'files.dateLabel' | translate }}</label>
|
||||||
|
<input pInputText type="date" [(ngModel)]="createdOn" [placeholder]="'files.datePlaceholder' | translate" />
|
||||||
|
</span>
|
||||||
|
|
||||||
<span class="form-field">
|
<span class="form-field">
|
||||||
<label>{{ 'files.sortLabel' | translate }}</label>
|
<label>{{ 'files.sortLabel' | translate }}</label>
|
||||||
<p-dropdown [options]="sortOptions" [(ngModel)]="sortBy" optionLabel="label" optionValue="value"></p-dropdown>
|
<p-dropdown [options]="sortOptions" [(ngModel)]="sortBy" optionLabel="label" optionValue="value"></p-dropdown>
|
||||||
|
|||||||
@@ -70,6 +70,7 @@ export class FilesPageComponent implements OnInit {
|
|||||||
search = '';
|
search = '';
|
||||||
backupType: 'export' | 'binary' | '' = '';
|
backupType: 'export' | 'binary' | '' = '';
|
||||||
routerId: number | null = null;
|
routerId: number | null = null;
|
||||||
|
createdOn = '';
|
||||||
sortBy = 'created_at';
|
sortBy = 'created_at';
|
||||||
order: 'asc' | 'desc' = 'desc';
|
order: 'asc' | 'desc' = 'desc';
|
||||||
diffText = '';
|
diffText = '';
|
||||||
@@ -187,6 +188,7 @@ export class FilesPageComponent implements OnInit {
|
|||||||
if (this.search.trim()) params = params.set('search', this.search.trim());
|
if (this.search.trim()) params = params.set('search', this.search.trim());
|
||||||
if (this.backupType) params = params.set('backup_type', this.backupType);
|
if (this.backupType) params = params.set('backup_type', this.backupType);
|
||||||
if (this.routerId !== null) params = params.set('router_id', String(this.routerId));
|
if (this.routerId !== null) params = params.set('router_id', String(this.routerId));
|
||||||
|
if (this.createdOn) params = params.set('created_on', this.createdOn);
|
||||||
|
|
||||||
this.api.http.get<BackupFile[]>(`${this.api.baseUrl}/backups`, { params }).subscribe({
|
this.api.http.get<BackupFile[]>(`${this.api.baseUrl}/backups`, { params }).subscribe({
|
||||||
next: (files) => {
|
next: (files) => {
|
||||||
@@ -209,6 +211,7 @@ export class FilesPageComponent implements OnInit {
|
|||||||
this.search = '';
|
this.search = '';
|
||||||
this.backupType = '';
|
this.backupType = '';
|
||||||
this.routerId = null;
|
this.routerId = null;
|
||||||
|
this.createdOn = '';
|
||||||
this.sortBy = 'created_at';
|
this.sortBy = 'created_at';
|
||||||
this.order = 'desc';
|
this.order = 'desc';
|
||||||
this.load();
|
this.load();
|
||||||
|
|||||||
@@ -255,6 +255,8 @@
|
|||||||
"searchPlaceholder": "Search by file or router",
|
"searchPlaceholder": "Search by file or router",
|
||||||
"typeLabel": "Type",
|
"typeLabel": "Type",
|
||||||
"routerLabel": "Device",
|
"routerLabel": "Device",
|
||||||
|
"dateLabel": "Date",
|
||||||
|
"datePlaceholder": "Pick a date",
|
||||||
"sortLabel": "Sort by",
|
"sortLabel": "Sort by",
|
||||||
"orderLabel": "Order",
|
"orderLabel": "Order",
|
||||||
"allTypes": "All types",
|
"allTypes": "All types",
|
||||||
|
|||||||
@@ -255,6 +255,8 @@
|
|||||||
"searchPlaceholder": "Buscar por archivo o router",
|
"searchPlaceholder": "Buscar por archivo o router",
|
||||||
"typeLabel": "Tipo",
|
"typeLabel": "Tipo",
|
||||||
"routerLabel": "Dispositivo",
|
"routerLabel": "Dispositivo",
|
||||||
|
"dateLabel": "Fecha",
|
||||||
|
"datePlaceholder": "Selecciona una fecha",
|
||||||
"sortLabel": "Ordenar por",
|
"sortLabel": "Ordenar por",
|
||||||
"orderLabel": "Orden",
|
"orderLabel": "Orden",
|
||||||
"allTypes": "Todos los tipos",
|
"allTypes": "Todos los tipos",
|
||||||
|
|||||||
@@ -255,6 +255,8 @@
|
|||||||
"searchPlaceholder": "Søk etter fil eller ruter",
|
"searchPlaceholder": "Søk etter fil eller ruter",
|
||||||
"typeLabel": "Type",
|
"typeLabel": "Type",
|
||||||
"routerLabel": "Enhet",
|
"routerLabel": "Enhet",
|
||||||
|
"dateLabel": "Dato",
|
||||||
|
"datePlaceholder": "Velg dato",
|
||||||
"sortLabel": "Sorter etter",
|
"sortLabel": "Sorter etter",
|
||||||
"orderLabel": "Rekkefølge",
|
"orderLabel": "Rekkefølge",
|
||||||
"allTypes": "Alle typer",
|
"allTypes": "Alle typer",
|
||||||
|
|||||||
@@ -255,6 +255,8 @@
|
|||||||
"searchPlaceholder": "Szukaj po pliku lub routerze",
|
"searchPlaceholder": "Szukaj po pliku lub routerze",
|
||||||
"typeLabel": "Typ",
|
"typeLabel": "Typ",
|
||||||
"routerLabel": "Urządzenie",
|
"routerLabel": "Urządzenie",
|
||||||
|
"dateLabel": "Data",
|
||||||
|
"datePlaceholder": "Wybierz datę",
|
||||||
"sortLabel": "Sortowanie",
|
"sortLabel": "Sortowanie",
|
||||||
"orderLabel": "Kolejność",
|
"orderLabel": "Kolejność",
|
||||||
"allTypes": "Wszystkie typy",
|
"allTypes": "Wszystkie typy",
|
||||||
|
|||||||
Reference in New Issue
Block a user