fixes and improvements

This commit is contained in:
2026-05-11 17:00:46 +02:00
parent 67a619e148
commit 80d38ed86d
6 changed files with 40 additions and 25 deletions
@@ -43,7 +43,7 @@ def get_monitoring_overview(
store=Depends(get_settings_store), store=Depends(get_settings_store),
) -> dict[str, Any]: ) -> dict[str, Any]:
"""Return one lightweight monitoring row per configured machine.""" """Return one lightweight monitoring row per configured machine."""
machines = store.list_machines() machines = [m for m in store.list_machines() if m.get("enabled")]
rows: list[dict[str, Any]] = [] rows: list[dict[str, Any]] = []
for machine in machines: for machine in machines:
try: try:
@@ -53,8 +53,8 @@ def _resolve_machine(store: SettingsStore, machine_id: str | None) -> dict[str,
@router.get("/machines") @router.get("/machines")
def get_machines(store: SettingsStore = Depends(get_settings_store)) -> list[dict[str, Any]]: def get_machines(store: SettingsStore = Depends(get_settings_store)) -> list[dict[str, Any]]:
"""Return monitoring machines for the UI.""" """Return enabled monitoring machines for the UI."""
return store.list_machines() return [m for m in store.list_machines() if m.get("enabled")]
@router.get("/poller") @router.get("/poller")
@@ -318,9 +318,6 @@ def reset_local_database(
media_index = MediaIndex() media_index = MediaIndex()
media_removed = remove_sqlite_database(media_index.db_path) media_removed = remove_sqlite_database(media_index.db_path)
# Recreate the default local machine immediately so the UI remains usable.
store.ensure_defaults()
return { return {
"status": "reset", "status": "reset",
"settings_db_removed": bool(settings_removed), "settings_db_removed": bool(settings_removed),
@@ -355,7 +355,7 @@ class SettingsStore:
) )
def list_machines(self) -> list[dict[str, Any]]: def list_machines(self) -> list[dict[str, Any]]:
self.ensure_defaults() self.init_schema()
with self.connect() as conn: with self.connect() as conn:
rows = conn.execute( rows = conn.execute(
"SELECT * FROM monitoring_machines ORDER BY CASE WHEN id = ? THEN 0 ELSE 1 END, name COLLATE NOCASE", "SELECT * FROM monitoring_machines ORDER BY CASE WHEN id = ? THEN 0 ELSE 1 END, name COLLATE NOCASE",
@@ -366,7 +366,7 @@ class SettingsStore:
def get_machine(self, machine_id: str | None) -> dict[str, Any] | None: def get_machine(self, machine_id: str | None) -> dict[str, Any] | None:
if not machine_id: if not machine_id:
return None return None
self.ensure_defaults() self.init_schema()
with self.connect() as conn: with self.connect() as conn:
row = conn.execute("SELECT * FROM monitoring_machines WHERE id = ?", (machine_id,)).fetchone() row = conn.execute("SELECT * FROM monitoring_machines WHERE id = ?", (machine_id,)).fetchone()
return self._row_to_machine(row) if row else None return self._row_to_machine(row) if row else None
@@ -375,7 +375,7 @@ class SettingsStore:
"""Return the full machine config including secrets.""" """Return the full machine config including secrets."""
if not machine_id: if not machine_id:
return None return None
self.ensure_defaults() self.init_schema()
with self.connect() as conn: with self.connect() as conn:
row = conn.execute("SELECT * FROM monitoring_machines WHERE id = ?", (machine_id,)).fetchone() row = conn.execute("SELECT * FROM monitoring_machines WHERE id = ?", (machine_id,)).fetchone()
if not row: if not row:
+24 -4
View File
@@ -128,12 +128,14 @@ def mock_ssh():
@pytest.fixture @pytest.fixture
def test_client(mock_jellyfin, mock_jellyseerr, mock_ssh): def test_client(mock_jellyfin, mock_jellyseerr, mock_ssh, tmp_path):
"""FastAPI test client with mocked dependencies.""" """FastAPI test client with mocked dependencies."""
app.dependency_overrides[get_jellyfin_client] = lambda: mock_jellyfin app.dependency_overrides[get_jellyfin_client] = lambda: mock_jellyfin
app.dependency_overrides[get_jellyseerr_client] = lambda: mock_jellyseerr app.dependency_overrides[get_jellyseerr_client] = lambda: mock_jellyseerr
app.dependency_overrides[get_ssh_client] = lambda: mock_ssh app.dependency_overrides[get_ssh_client] = lambda: mock_ssh
app.dependency_overrides[get_user_id] = lambda: "user123" app.dependency_overrides[get_user_id] = lambda: "user123"
store = SettingsStore(tmp_path / "settings.sqlite")
app.dependency_overrides[get_settings_store] = lambda: store
auth_settings = SimpleNamespace(auth_enabled=False) auth_settings = SimpleNamespace(auth_enabled=False)
with patch("media_library_viewer_api.auth.get_settings", return_value=auth_settings): with patch("media_library_viewer_api.auth.get_settings", return_value=auth_settings):
client = TestClient(app) client = TestClient(app)
@@ -294,7 +296,7 @@ class TestSettingsReset:
settings_module.MediaIndex = original_media_index settings_module.MediaIndex = original_media_index
assert response.status_code == 400 assert response.status_code == 400
def test_reset_local_database_wipes_state_and_reseeds_local_machine(self, test_client, tmp_path): def test_reset_local_database_wipes_state_and_leaves_no_machines(self, test_client, tmp_path):
store = SettingsStore(tmp_path / "settings.sqlite") store = SettingsStore(tmp_path / "settings.sqlite")
store.ensure_defaults() store.ensure_defaults()
media_db = tmp_path / "media.sqlite" media_db = tmp_path / "media.sqlite"
@@ -324,8 +326,8 @@ class TestSettingsReset:
assert payload["status"] == "reset" assert payload["status"] == "reset"
assert not media_db.exists() assert not media_db.exists()
assert not media_wal.exists() assert not media_wal.exists()
assert store.get_machine("local") is not None assert store.get_machine("local") is None
assert len(store.list_machines()) == 1 assert len(store.list_machines()) == 0
# --- Users --- # --- Users ---
@@ -644,7 +646,20 @@ class TestJobs:
# --- Monitoring --- # --- Monitoring ---
class TestMonitoring: class TestMonitoring:
def _ensure_machine(self):
store = app.dependency_overrides[get_settings_store]()
if not store.list_machines():
store.upsert_machine({
"name": "Test Machine",
"mode": "ssh",
"enabled": True,
"services": ["monitoring", "files", "jellyfin"],
"host": "test-host",
"username": "test-user",
})
def test_status(self, test_client, mock_ssh): def test_status(self, test_client, mock_ssh):
self._ensure_machine()
mock_ssh.run.return_value = CommandResult( mock_ssh.run.return_value = CommandResult(
command="...", exit_status=0, stdout="running pid=1234\n", stderr="" command="...", exit_status=0, stdout="running pid=1234\n", stderr=""
) )
@@ -653,6 +668,7 @@ class TestMonitoring:
assert "running" in response.json()["status"] assert "running" in response.json()["status"]
def test_metrics_empty(self, test_client, mock_ssh): def test_metrics_empty(self, test_client, mock_ssh):
self._ensure_machine()
mock_ssh.run.return_value = CommandResult( mock_ssh.run.return_value = CommandResult(
command="...", exit_status=0, stdout="", stderr="" command="...", exit_status=0, stdout="", stderr=""
) )
@@ -662,6 +678,7 @@ class TestMonitoring:
assert data["samples"] == [] assert data["samples"] == []
def test_disk(self, test_client, mock_ssh): def test_disk(self, test_client, mock_ssh):
self._ensure_machine()
mock_ssh.run.return_value = CommandResult( mock_ssh.run.return_value = CommandResult(
command="df ...", command="df ...",
exit_status=0, exit_status=0,
@@ -674,6 +691,7 @@ class TestMonitoring:
assert data["used_pct"] == "50%" assert data["used_pct"] == "50%"
def test_start(self, test_client, mock_ssh): def test_start(self, test_client, mock_ssh):
self._ensure_machine()
mock_ssh.run.return_value = CommandResult( mock_ssh.run.return_value = CommandResult(
command="...", exit_status=0, stdout="started pid=5678\n", stderr="" command="...", exit_status=0, stdout="started pid=5678\n", stderr=""
) )
@@ -682,6 +700,7 @@ class TestMonitoring:
assert "started" in response.json()["message"] assert "started" in response.json()["message"]
def test_stop(self, test_client, mock_ssh): def test_stop(self, test_client, mock_ssh):
self._ensure_machine()
mock_ssh.run.return_value = CommandResult( mock_ssh.run.return_value = CommandResult(
command="...", exit_status=0, stdout="stopped pid=5678\n", stderr="" command="...", exit_status=0, stdout="stopped pid=5678\n", stderr=""
) )
@@ -689,6 +708,7 @@ class TestMonitoring:
assert response.status_code == 200 assert response.status_code == 200
def test_restart(self, test_client, mock_ssh): def test_restart(self, test_client, mock_ssh):
self._ensure_machine()
mock_ssh.run.return_value = CommandResult( mock_ssh.run.return_value = CommandResult(
command="...", exit_status=0, stdout="stopped pid=5678\nstarted pid=9999\n", stderr="" command="...", exit_status=0, stdout="stopped pid=5678\nstarted pid=9999\n", stderr=""
) )
+10 -12
View File
@@ -1378,17 +1378,15 @@ export function Settings() {
> >
Edit Edit
</Button> </Button>
{selectedMachine.id !== "local" && ( <Button
<Button variant="outlined"
variant="outlined" color="error"
color="error" onClick={() =>
onClick={() => setDeleteMachineId(selectedMachine.id)
setDeleteMachineId(selectedMachine.id) }
} >
> Delete
Delete </Button>
</Button>
)}
</Stack> </Stack>
</Stack> </Stack>
) : null} ) : null}
@@ -1443,7 +1441,7 @@ export function Settings() {
(machineDraft.mode === "ssh" && !machineDraft.host.trim()) (machineDraft.mode === "ssh" && !machineDraft.host.trim())
} }
secondaryAction={ secondaryAction={
machineDraft.id && machineDraft.id !== "local" ? ( machineDraft.id ? (
<Button <Button
variant="outlined" variant="outlined"
color="error" color="error"