1616"""Define the JSON API router for the Inventory plugin.
1717
1818Mounted at ``/api/apps/inventory/`` via ``apps_router`` in
19- ``app/sep/api/router.py``. Like other plugin proxies, these routes rely on the
20- parent ``api_router`` for API authentication. The ``schema_endpoint`` helper
21- additionally attaches ``IsApiAuthenticated`` to the schema route only; the list
22- and detail handlers do not duplicate that dependency.
23-
24- Proxies read access to nodes, services, schemas, and tables to the inventory
25- HTTP API through ``InventoryAPI`` in ``app.sep.deps`` (``RemoteAPI`` toward the
26- inventory service). List handlers unwrap paginated ``items`` into a JSON array
27- for the schema-driven React client. The four entities are read-only here: no
28- handler creates, updates, or deletes one. The syncers author that data — PMM
29- supplies nodes and services, while ``MySQLSyncer`` discovers schemas and tables
30- from the database itself — and they write through the inventory service, which
31- remains the canonical CRUD surface at ``/api/inventory/*``.
32-
33- Besides those reads, this router mounts the ad-hoc inventory-sync trigger
34- (``POST /sync/``) and the running-state polling endpoint
35- (``GET /sync/status/``) consumed by the React inventory sync control. Schedule
36- discovery (``GET /``) and available-syncers (``GET /available-syncers/``) are
37- also mounted here so the React schedule UI can fetch its data
38- through the plugin API gateway. Periodic-task CRUD remains delegated to
19+ ``app/sep/api/router.py``, which supplies the API authentication these routes
20+ rely on.
21+
22+ This is an operator API only — the plugin ships no browser surface, so nothing
23+ here proxies entity reads. Read the catalog from the inventory service itself
24+ at ``/api/inventory/*``, which remains its canonical CRUD surface and the one
25+ the syncers write through.
26+
27+ The router mounts the ad-hoc inventory-sync trigger (``POST /sync/``), the
28+ running-state polling endpoint (``GET /sync/status/``), schedule discovery
29+ (``GET /``), available-syncers (``GET /available-syncers/``), and the
30+ per-service connectivity probe. Periodic-task CRUD remains delegated to
3931``/api/tasks/periodic/*`` as the single source of truth; this router does not
4032duplicate that surface.
4133"""
4234
4335from __future__ import annotations
4436
45- from typing import Any
46-
47- from fastapi import APIRouter , BackgroundTasks , Body , Request , Response , status
37+ from fastapi import APIRouter , BackgroundTasks , Body , Response , status
4838from sqlmodel import col
4939
5040from app .core .exceptions import HTTPBadRequestException
51- from app .core .pagination import (
52- build_proxied_page ,
53- PaginatedResponse ,
54- PaginationDep ,
55- )
56- from app .sep .apps .framework .api import schema_endpoint
5741from app .sep .apps .inventory .connectivity import probe_service_connectivity
5842from app .sep .apps .inventory .deps import (
5943 AvailableSyncer ,
6044 filter_syncers_by_name ,
6145 InternalTokenDep ,
62- inventory_plugin_query_params ,
63- inventory_service_detail_path ,
64- inventory_service_list_path ,
65- inventory_system_observation_path ,
6646 InventoryAvailableSyncersDep ,
6747 InventorySyncStatusResponse ,
6848 InventorySyncTriggerWrite ,
69- require_inventory_plugin_entity ,
7049 SyncersDep ,
71- SYSTEM_OBSERVATION_SEGMENT ,
72- unwrap_inventory_plugin_list_payload ,
73- )
74- from app .sep .apps .inventory .list_query import (
75- InventoryListQueryDep ,
76- list_query_upstream_params ,
7750)
7851from app .sep .apps .inventory .models import (
7952 INVENTORY_SYNC_TASK_NAME ,
8053 PluginTaskResponse ,
8154 SyncRunSummary ,
8255)
83- from app .sep .apps .inventory .schema import inventory_schema
8456from app .sep .apps .inventory .sync import run_inventory_sync
8557from app .sep .crud import SyncInstanceManager , SyncItemManager
8658from app .sep .deps import (
8759 CreatedServiceDep ,
88- InventoryAPI ,
8960 IsApiAdmin ,
9061 SessionDep ,
9162 TaskAPI ,
9566from app .tasks .models import INVENTORY_COLLECTION_TASK_NAME
9667
9768router = APIRouter ()
98- schema_endpoint (router = router , plugin_schema = inventory_schema )
9969
10070# Module-level singleton avoids the B008 lint warning about function calls in
10171# argument defaults; the optional-body semantics are unchanged.
@@ -151,9 +121,8 @@ async def inventory_sync_trigger(
151121async def inventory_sync_status (session : SessionDep ) -> InventorySyncStatusResponse :
152122 """Return whether an inventory-wide sync is running, plus recent run outcomes.
153123
154- Replaces the server-rendered ``sync_is_running`` template variable
155- used by the Jinja2 inventory page so the React control can poll the
156- same state without scraping HTML.
124+ Lets an operator poll a sync they triggered through ``POST /sync/``
125+ without scraping any rendered page.
157126
158127 :param session: SQLModel async session.
159128 :return: The running flag and the most recent runs, newest first.
@@ -190,9 +159,7 @@ async def inventory_plugin_tasks() -> list[PluginTaskResponse]:
190159 """Return the list of periodic task names for the Inventory plugin.
191160
192161 Hard-coded because the Inventory plugin's periodic tasks are a fixed pair
193- (``inventory-sync`` and ``inventory-collection``). The shape matches what the
194- React ``usePluginTasks('inventory')`` hook expects: a list of objects with at
195- minimum a ``name`` key.
162+ (``inventory-sync`` and ``inventory-collection``).
196163
197164 :return: The plugin's periodic tasks, each with its name and display name.
198165 """
@@ -221,82 +188,6 @@ async def inventory_available_syncers(
221188 return available_syncers
222189
223190
224- @router .get ("/{entity}/" )
225- async def inventory_list_entity (
226- request : Request ,
227- entity : str ,
228- inventory_api : InventoryAPI ,
229- pagination : PaginationDep ,
230- list_query : InventoryListQueryDep ,
231- ) -> PaginatedResponse [Any ]:
232- """List inventory nodes, services, schemas, or tables.
233-
234- :param request: Inbound request; its query string carries entity filters.
235- :param entity: Inventory entity type (nodes, services, schemas, tables).
236- :param inventory_api: Async client for the Inventory sub-app.
237- :param pagination: Validated offset/limit forwarded to the upstream call.
238- :param list_query: Allowlist-vetted sort/search for this entity.
239- :return: A paginated envelope echoing the requested window.
240- """
241- entity = require_inventory_plugin_entity (entity )
242- params = inventory_plugin_query_params (request )
243- params ["offset" ] = pagination .offset
244- params ["limit" ] = pagination .limit
245- # Drop raw sort/search before merging the validated adapter output so a
246- # blank or omitted search cannot leak through from the query string.
247- params .pop ("sort" , None )
248- params .pop ("search" , None )
249- params .update (list_query_upstream_params (list_query ))
250- data = await inventory_api .get (inventory_service_list_path (entity ), params = params )
251- items = unwrap_inventory_plugin_list_payload (data )
252- envelope = data if isinstance (data , dict ) else {}
253- return build_proxied_page (items , envelope , pagination , client_side_filtered = False )
254-
255-
256- @router .get (f"/nodes/{{node_id:int}}/{ SYSTEM_OBSERVATION_SEGMENT } " )
257- async def inventory_node_system_observation (
258- node_id : int ,
259- inventory_api : InventoryAPI ,
260- ) -> Any :
261- """Proxy the host-level system observation for a node (read-only).
262-
263- Forwards to the inventory sub-app's ``/nodes/{node_id}/system-observation``
264- endpoint via ``InventoryAPI``. This three-segment literal path cannot
265- collide with the two-segment ``/{entity}/{item_id:int}`` detail matcher. An
266- upstream HTTP 404 propagates unchanged, along with the ``detail`` that tells
267- a node whose observation has not been collected yet — which the React panel
268- renders as an empty state — apart from a node that does not exist.
269-
270- :param node_id: Primary key of the node.
271- :param inventory_api: Authenticated inventory ``RemoteAPI`` client.
272- :return: The host-level system observation payload.
273- """
274- return await inventory_api .get (inventory_system_observation_path ("nodes" , node_id ))
275-
276-
277- @router .get (f"/services/{{service_id:int}}/{ SYSTEM_OBSERVATION_SEGMENT } " )
278- async def inventory_service_system_observation (
279- service_id : int ,
280- inventory_api : InventoryAPI ,
281- ) -> Any :
282- """Proxy the service-level system observation for a service (read-only).
283-
284- Forwards to the inventory sub-app's
285- ``/services/{service_id}/system-observation`` endpoint via ``InventoryAPI``.
286- An upstream HTTP 404 propagates unchanged, along with the ``detail`` that
287- tells a service whose observation has not been collected yet — which the
288- React panel renders as an empty state — apart from a service that does not
289- exist.
290-
291- :param service_id: Primary key of the service.
292- :param inventory_api: Authenticated inventory ``RemoteAPI`` client.
293- :return: The service-level system observation payload.
294- """
295- return await inventory_api .get (
296- inventory_system_observation_path ("services" , service_id )
297- )
298-
299-
300191@router .post (
301192 "/services/{service_id:int}/check-connectivity/" ,
302193 dependencies = [IsApiAdmin ],
@@ -307,12 +198,9 @@ async def inventory_service_check_connectivity(
307198) -> ConnectivityCheckResponse :
308199 """Run a database connectivity probe for a service from its executor host.
309200
310- Backs the React connectivity control on the service detail page. A probe
311- that ran but could not connect is reported as HTTP 200 with
201+ A probe that ran but could not connect is reported as HTTP 200 with
312202 ``success=false`` and the upstream message in ``error``; only a probe that
313- could not be attempted at all is an error status. This three-segment
314- literal path cannot collide with the two-segment
315- ``/{entity}/{item_id:int}`` detail matcher.
203+ could not be attempted at all is an error status.
316204
317205 :param service: The service to probe, resolved from the path id.
318206 :param tasks_api: Authenticated Tasks ``RemoteAPI`` client.
@@ -324,14 +212,3 @@ async def inventory_service_check_connectivity(
324212 returns an unparseable body.
325213 """
326214 return await probe_service_connectivity (service , tasks_api )
327-
328-
329- @router .get ("/{entity}/{item_id:int}" )
330- async def inventory_get_entity (
331- entity : str ,
332- item_id : int ,
333- inventory_api : InventoryAPI ,
334- ) -> Any :
335- """Retrieve a single inventory node, service, schema, or table."""
336- entity = require_inventory_plugin_entity (entity )
337- return await inventory_api .get (inventory_service_detail_path (entity , item_id ))
0 commit comments