Initial curated Unraid-AppOps-Gateway source from 3bd6f6fee310125bfbb4d4650389a817f498724d
This commit is contained in:
commit
1dfbca08a1
528 files changed
+131054
No files matched your search
@@ -0,0 +1,143 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import UTC, datetime
|
||||
from typing import Any
|
||||
|
||||
from sqlmodel import Session, select
|
||||
|
||||
from ..db.models import AppSetting, Connector, Policy
|
||||
from ..core.time import utc_now
|
||||
from .inventory_discovery import list_drift_incidents, update_drift_incident_status
|
||||
from .secret_store import secret_storage_status
|
||||
|
||||
_INCIDENT_STATES_SETTING = 'incidents:states'
|
||||
|
||||
|
||||
def _now_iso() -> str:
|
||||
return datetime.now(UTC).replace(microsecond=0).isoformat().replace('+00:00', 'Z')
|
||||
|
||||
|
||||
def _incident_states(session: Session) -> dict[str, dict[str, Any]]:
|
||||
row = session.get(AppSetting, _INCIDENT_STATES_SETTING)
|
||||
value = row.value_json if row else {}
|
||||
items = value.get('items', {}) if isinstance(value, dict) else {}
|
||||
return {str(key): value for key, value in items.items() if isinstance(value, dict)}
|
||||
|
||||
|
||||
def _set_incident_states(session: Session, states: dict[str, dict[str, Any]]) -> None:
|
||||
row = session.get(AppSetting, _INCIDENT_STATES_SETTING)
|
||||
value = {'items': states}
|
||||
if not row:
|
||||
row = AppSetting(key=_INCIDENT_STATES_SETTING, value_json=value)
|
||||
else:
|
||||
row.value_json = value
|
||||
row.updated_at = utc_now()
|
||||
session.add(row)
|
||||
|
||||
|
||||
def _generated_incidents(session: Session) -> list[dict[str, Any]]:
|
||||
timestamp = _now_iso()
|
||||
incidents: list[dict[str, Any]] = []
|
||||
|
||||
connectors = session.exec(select(Connector).order_by(Connector.id)).all()
|
||||
degraded_connectors = [
|
||||
connector.id
|
||||
for connector in connectors
|
||||
if connector.status in {'missing_config', 'disconnected', 'error', 'auth_failed', 'unreachable'}
|
||||
]
|
||||
if degraded_connectors:
|
||||
incidents.append({
|
||||
'id': 'incident-connectors-config',
|
||||
'severity': 'high' if len(degraded_connectors) >= 5 else 'medium',
|
||||
'status': 'open',
|
||||
'title': 'Connector configuratie of health vraagt aandacht',
|
||||
'detectedTime': timestamp,
|
||||
'evidence': {
|
||||
'source': 'Connector Registry',
|
||||
'check': 'Connector readiness',
|
||||
'observed': f"{len(degraded_connectors)} degraded connector(s): {', '.join(degraded_connectors[:8])}",
|
||||
'expected': 'Alle bekende connectors connected of bewust optioneel.',
|
||||
'timestamp': timestamp,
|
||||
},
|
||||
'suggestedActions': ['connector.configure', 'connector.test'],
|
||||
})
|
||||
|
||||
volume_policy = session.get(Policy, 'policy-volume-delete')
|
||||
if not (volume_policy and volume_policy.voice == 'blocked' and volume_policy.manual == 'blocked'):
|
||||
incidents.append({
|
||||
'id': 'incident-policy-volume-delete',
|
||||
'severity': 'critical',
|
||||
'status': 'open',
|
||||
'title': 'Destructive storage policy niet fail-closed',
|
||||
'detectedTime': timestamp,
|
||||
'evidence': {
|
||||
'source': 'Policy Engine',
|
||||
'check': 'volume.delete guardrail',
|
||||
'observed': 'volume.delete is niet volledig blocked',
|
||||
'expected': 'voice=blocked en manual=blocked',
|
||||
'timestamp': timestamp,
|
||||
},
|
||||
'suggestedActions': ['policy.review'],
|
||||
})
|
||||
|
||||
storage = secret_storage_status()
|
||||
if not storage.get('productionReady'):
|
||||
incidents.append({
|
||||
'id': 'incident-secret-storage',
|
||||
'severity': 'high',
|
||||
'status': 'open',
|
||||
'title': 'Secret storage niet production-ready',
|
||||
'detectedTime': timestamp,
|
||||
'evidence': {
|
||||
'source': 'Secret Store',
|
||||
'check': 'Encrypted-at-rest readiness',
|
||||
'observed': 'Secret storage mist production-ready configuratie.',
|
||||
'expected': 'Encrypted secret storage met persistente sleutel.',
|
||||
'timestamp': timestamp,
|
||||
},
|
||||
'suggestedActions': ['secrets.health'],
|
||||
})
|
||||
|
||||
return incidents
|
||||
|
||||
|
||||
def list_incidents(session: Session) -> list[dict[str, Any]]:
|
||||
states = _incident_states(session)
|
||||
incidents: list[dict[str, Any]] = []
|
||||
for incident in _generated_incidents(session):
|
||||
state = states.get(incident['id'], {})
|
||||
status = str(state.get('status') or 'open')
|
||||
if status == 'resolved':
|
||||
continue
|
||||
incident['status'] = status if status in {'open', 'acknowledged', 'muted'} else 'open'
|
||||
incidents.append(incident)
|
||||
incidents.extend(list_drift_incidents(session))
|
||||
return incidents
|
||||
|
||||
|
||||
def update_incident_status(session: Session, incident_id: str, status: str) -> dict[str, Any]:
|
||||
if status not in {'open', 'acknowledged', 'muted', 'resolved'}:
|
||||
status = 'acknowledged'
|
||||
|
||||
drift_update = update_drift_incident_status(session, incident_id, status)
|
||||
if drift_update:
|
||||
return drift_update
|
||||
|
||||
current_ids = {incident['id'] for incident in _generated_incidents(session)}
|
||||
states = _incident_states(session)
|
||||
if incident_id not in current_ids and incident_id not in states:
|
||||
return {'success': False, 'message': 'incident_not_found', 'result': 'failed', 'status': 'failed'}
|
||||
|
||||
states[incident_id] = {'status': status, 'updatedAt': _now_iso()}
|
||||
_set_incident_states(session, states)
|
||||
session.commit()
|
||||
return {
|
||||
'success': True,
|
||||
'message': f'Incident {incident_id} status updated',
|
||||
'result': 'success',
|
||||
'status': 'success',
|
||||
}
|
||||
|
||||
|
||||
def count_open_incidents(session: Session) -> int:
|
||||
return len([incident for incident in list_incidents(session) if incident.get('status') == 'open'])
|
||||
Reference in new issue
Block a user