Files
Unraid-AppOps-Gateway-Public/backend/app/api/routes_connectors.py
T

134 lines
5.1 KiB
Python

from __future__ import annotations
from fastapi import APIRouter, Depends
from pydantic import BaseModel
from sqlmodel import Session, select
# Kept imported here for compatibility with tests and callers that patch the
# route module's Portainer connector class.
from ..connectors.portainer import PortainerConnector
from ..core.redaction import redact_message
from ..db.models import Connector
from ..db.session import get_session
from ..services.connector_health import refresh_connector_statuses, test_and_persist_connector
from ..services.inventory_discovery import (
bind_connector_to_discovered,
list_connector_bindings,
list_connector_candidates,
unbind_connector_from_app,
)
router = APIRouter(prefix='/api/connectors')
class ConnectorBindRequest(BaseModel):
discoveredContainerId: str
class ConnectorUnbindRequest(BaseModel):
appId: str
def _serialize_connector(connector: Connector) -> dict:
return {
'id': connector.id,
'name': connector.name,
'status': connector.status,
'lastTest': connector.last_test,
'capabilities': connector.capabilities,
'type': connector.type,
'endpoint': None,
'secretRef': connector.secret_ref,
}
def _required_env_vars(connector_id: str) -> list[str]:
mapping = {
'portainer': ['PORTAINER_URL', 'PORTAINER_API_KEY', 'PORTAINER_ENDPOINT_ID'],
'sonarr': ['SONARR_URL', 'SONARR_API_KEY'],
'radarr': ['RADARR_URL', 'RADARR_API_KEY'],
'prowlarr': ['PROWLARR_URL', 'PROWLARR_API_KEY'],
'deluge': ['DELUGE_URL', 'DELUGE_PASSWORD'],
'plex': ['PLEX_URL', 'PLEX_TOKEN'],
'tautulli': ['TAUTULLI_URL', 'TAUTULLI_API_KEY'],
'nginx_proxy_manager': ['NPM_URL', 'NPM_IDENTITY', 'NPM_SECRET'],
'authentik': ['AUTHENTIK_URL', 'AUTHENTIK_TOKEN'],
'widefrog': ['WIDEFROG_URL'],
'unraid': ['UNRAID_URL', 'UNRAID_API_KEY'],
'home_assistant': ['HOME_ASSISTANT_URL', 'HOME_ASSISTANT_TOKEN'],
}
return mapping.get(connector_id, [])
@router.get('')
def list_connectors(refresh: bool = False, session: Session = Depends(get_session)):
if refresh:
refresh_connector_statuses(session)
connectors = session.exec(select(Connector).order_by(Connector.id)).all()
return [_serialize_connector(connector) for connector in connectors]
@router.get('/{connector_id}')
def connector_status(connector_id: str, session: Session = Depends(get_session)):
connector = session.get(Connector, connector_id)
if not connector:
return {'success': False, 'message': 'connector_not_found', 'id': connector_id}
return _serialize_connector(connector)
@router.get('/{connector_id}/candidates')
def connector_candidates(connector_id: str, session: Session = Depends(get_session)):
connector = session.get(Connector, connector_id)
if not connector:
return {'success': False, 'message': 'connector_not_found', 'id': connector_id}
return list_connector_candidates(session, connector_id)
@router.get('/{connector_id}/bindings')
def connector_bindings(connector_id: str, session: Session = Depends(get_session)):
connector = session.get(Connector, connector_id)
if not connector:
return {'success': False, 'message': 'connector_not_found', 'id': connector_id}
return list_connector_bindings(session, connector_id)
@router.post('/{connector_id}/bind')
def bind_connector(connector_id: str, payload: ConnectorBindRequest, session: Session = Depends(get_session)):
connector = session.get(Connector, connector_id)
if not connector:
return {'success': False, 'message': 'connector_not_found', 'id': connector_id}
return bind_connector_to_discovered(session, connector_id, payload.discoveredContainerId)
@router.post('/{connector_id}/unbind')
def unbind_connector(connector_id: str, payload: ConnectorUnbindRequest, session: Session = Depends(get_session)):
connector = session.get(Connector, connector_id)
if not connector:
return {'success': False, 'message': 'connector_not_found', 'id': connector_id}
return unbind_connector_from_app(session, connector_id, payload.appId)
@router.post('/{connector_id}/test')
def test_connector(connector_id: str, session: Session = Depends(get_session)):
connector = session.get(Connector, connector_id)
if not connector:
return {'success': False, 'status': 'disconnected', 'message': f'Connector {connector_id} not found'}
result = test_and_persist_connector(session, connector_id)
status = str(result.get('status', 'disconnected'))
reason = result.get('reason')
success = status == 'connected'
payload: dict[str, object] = {
'success': success,
'status': status,
'id': connector_id,
'message': redact_message(str(reason)) if reason else ('Connector test succeeded.' if success else 'Connector test failed.'),
'missing_config': status == 'missing_config',
'required_env_vars': _required_env_vars(connector_id),
'secretRef': connector.secret_ref,
}
if reason:
payload['reason'] = redact_message(str(reason))
return payload