fixes and improvements
This commit is contained in:
@@ -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:
|
||||||
|
|||||||
@@ -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=""
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
Reference in New Issue
Block a user