mirror of
https://github.com/jokob-sk/NetAlertX.git
synced 2026-04-03 08:41:35 -07:00
feat(api): Enhance session events API with pagination, sorting, and filtering
- Added support for pagination (page and limit) in the session events endpoint. - Implemented sorting functionality based on specified columns and directions. - Introduced free-text search capability for session events. - Updated SQL queries to retrieve all events and added a new SQL constant for events. - Refactored GraphQL types and helpers to support new plugin and event queries. - Created new GraphQL resolvers for plugins and events with pagination and filtering. - Added comprehensive tests for new GraphQL endpoints and session events functionality.
This commit is contained in:
@@ -1,7 +1,5 @@
|
||||
import graphene
|
||||
from graphene import (
|
||||
ObjectType, String, Int, Boolean, List, Field, InputObjectType, Argument
|
||||
)
|
||||
from graphene import ObjectType, List, Field, Argument, String
|
||||
import json
|
||||
import sys
|
||||
import os
|
||||
@@ -19,175 +17,30 @@ from helper import ( # noqa: E402 [flake8 lint suppression]
|
||||
get_setting_value,
|
||||
)
|
||||
|
||||
# Define a base URL with the user's home directory
|
||||
from .graphql_types import ( # noqa: E402 [flake8 lint suppression]
|
||||
FilterOptionsInput, PageQueryOptionsInput,
|
||||
Device, DeviceResult,
|
||||
Setting, SettingResult,
|
||||
LangString, LangStringResult,
|
||||
AppEvent, AppEventResult,
|
||||
PluginQueryOptionsInput, PluginEntry,
|
||||
PluginsObjectsResult, PluginsEventsResult, PluginsHistoryResult,
|
||||
EventQueryOptionsInput, EventEntry, EventsResult,
|
||||
)
|
||||
from .graphql_helpers import ( # noqa: E402 [flake8 lint suppression]
|
||||
mixed_type_sort_key,
|
||||
apply_common_pagination,
|
||||
apply_plugin_filters,
|
||||
apply_events_filters,
|
||||
)
|
||||
|
||||
folder = apiPath
|
||||
|
||||
|
||||
# --- DEVICES ---
|
||||
# Pagination and Sorting Input Types
|
||||
class SortOptionsInput(InputObjectType):
|
||||
field = String()
|
||||
order = String()
|
||||
|
||||
|
||||
class FilterOptionsInput(InputObjectType):
|
||||
filterColumn = String()
|
||||
filterValue = String()
|
||||
|
||||
|
||||
class PageQueryOptionsInput(InputObjectType):
|
||||
page = Int()
|
||||
limit = Int()
|
||||
sort = List(SortOptionsInput)
|
||||
search = String()
|
||||
status = String()
|
||||
filters = List(FilterOptionsInput)
|
||||
|
||||
|
||||
# Device ObjectType
|
||||
class Device(ObjectType):
|
||||
rowid = Int(description="Database row ID")
|
||||
devMac = String(description="Device MAC address (e.g., 00:11:22:33:44:55)")
|
||||
devName = String(description="Device display name/alias")
|
||||
devOwner = String(description="Device owner")
|
||||
devType = String(description="Device type classification")
|
||||
devVendor = String(description="Hardware vendor from OUI lookup")
|
||||
devFavorite = Int(description="Favorite flag (0 or 1)")
|
||||
devGroup = String(description="Device group")
|
||||
devComments = String(description="User comments")
|
||||
devFirstConnection = String(description="Timestamp of first discovery")
|
||||
devLastConnection = String(description="Timestamp of last connection")
|
||||
devLastIP = String(description="Last known IP address")
|
||||
devPrimaryIPv4 = String(description="Primary IPv4 address")
|
||||
devPrimaryIPv6 = String(description="Primary IPv6 address")
|
||||
devVlan = String(description="VLAN identifier")
|
||||
devForceStatus = String(description="Force device status (online/offline/dont_force)")
|
||||
devStaticIP = Int(description="Static IP flag (0 or 1)")
|
||||
devScan = Int(description="Scan flag (0 or 1)")
|
||||
devLogEvents = Int(description="Log events flag (0 or 1)")
|
||||
devAlertEvents = Int(description="Alert events flag (0 or 1)")
|
||||
devAlertDown = Int(description="Alert on down flag (0 or 1)")
|
||||
devSkipRepeated = Int(description="Skip repeated alerts flag (0 or 1)")
|
||||
devLastNotification = String(description="Timestamp of last notification")
|
||||
devPresentLastScan = Int(description="Present in last scan flag (0 or 1)")
|
||||
devIsNew = Int(description="Is new device flag (0 or 1)")
|
||||
devLocation = String(description="Device location")
|
||||
devIsArchived = Int(description="Is archived flag (0 or 1)")
|
||||
devParentMAC = String(description="Parent device MAC address")
|
||||
devParentPort = String(description="Parent device port")
|
||||
devIcon = String(description="Base64-encoded HTML/SVG markup used to render the device icon")
|
||||
devGUID = String(description="Unique device GUID")
|
||||
devSite = String(description="Site name")
|
||||
devSSID = String(description="SSID connected to")
|
||||
devSyncHubNode = String(description="Sync hub node name")
|
||||
devSourcePlugin = String(description="Plugin that discovered the device")
|
||||
devCustomProps = String(description="Base64-encoded custom properties in JSON format")
|
||||
devStatus = String(description="Online/Offline status")
|
||||
devIsRandomMac = Int(description="Calculated: Is MAC address randomized?")
|
||||
devParentChildrenCount = Int(description="Calculated: Number of children attached to this parent")
|
||||
devIpLong = String(description="Calculated: IP address in long format (returned as string to support the full unsigned 32-bit range)")
|
||||
devFilterStatus = String(description="Calculated: Device status for UI filtering")
|
||||
devFQDN = String(description="Fully Qualified Domain Name")
|
||||
devParentRelType = String(description="Relationship type to parent")
|
||||
devReqNicsOnline = Int(description="Required NICs online flag")
|
||||
devMacSource = String(description="Source tracking for devMac (USER, LOCKED, NEWDEV, or plugin prefix)")
|
||||
devNameSource = String(description="Source tracking for devName (USER, LOCKED, NEWDEV, or plugin prefix)")
|
||||
devFQDNSource = String(description="Source tracking for devFQDN (USER, LOCKED, NEWDEV, or plugin prefix)")
|
||||
devLastIPSource = String(description="Source tracking for devLastIP (USER, LOCKED, NEWDEV, or plugin prefix)")
|
||||
devVendorSource = String(description="Source tracking for devVendor (USER, LOCKED, NEWDEV, or plugin prefix)")
|
||||
devSSIDSource = String(description="Source tracking for devSSID (USER, LOCKED, NEWDEV, or plugin prefix)")
|
||||
devParentMACSource = String(description="Source tracking for devParentMAC (USER, LOCKED, NEWDEV, or plugin prefix)")
|
||||
devParentPortSource = String(description="Source tracking for devParentPort (USER, LOCKED, NEWDEV, or plugin prefix)")
|
||||
devParentRelTypeSource = String(description="Source tracking for devParentRelType (USER, LOCKED, NEWDEV, or plugin prefix)")
|
||||
devVlanSource = String(description="Source tracking for devVlan")
|
||||
devFlapping = Int(description="Indicates flapping device (device changing between online/offline states frequently)")
|
||||
devCanSleep = Int(description="Can this device sleep? (0 or 1). When enabled, offline periods within NTFPRCS_sleep_time are reported as Sleeping instead of Down.")
|
||||
devIsSleeping = Int(description="Computed: Is device currently in a sleep window? (0 or 1)")
|
||||
|
||||
|
||||
class DeviceResult(ObjectType):
|
||||
devices = List(Device)
|
||||
count = Int()
|
||||
db_count = Int(description="Total device count in the database, before any status/filter/search is applied")
|
||||
|
||||
|
||||
# --- SETTINGS ---
|
||||
|
||||
|
||||
# Setting ObjectType
|
||||
class Setting(ObjectType):
|
||||
setKey = String(description="Unique configuration key")
|
||||
setName = String(description="Human-readable setting name")
|
||||
setDescription = String(description="Detailed description of the setting")
|
||||
setType = String(description="Config-driven type definition used to determine value type and UI rendering")
|
||||
setOptions = String(description="JSON string of available options")
|
||||
setGroup = String(description="UI group for categorization")
|
||||
setValue = String(description="Current value")
|
||||
setEvents = String(description="JSON string of events")
|
||||
setOverriddenByEnv = Boolean(description="Whether the value is currently overridden by an environment variable")
|
||||
|
||||
|
||||
class SettingResult(ObjectType):
|
||||
settings = List(Setting, description="List of setting objects")
|
||||
count = Int(description="Total count of settings")
|
||||
|
||||
# --- LANGSTRINGS ---
|
||||
|
||||
|
||||
# In-memory cache for lang strings
|
||||
_langstrings_cache = {} # caches lists per file (core JSON or plugin)
|
||||
_langstrings_cache_mtime = {} # tracks last modified times
|
||||
|
||||
|
||||
# LangString ObjectType
|
||||
class LangString(ObjectType):
|
||||
langCode = String(description="Language code (e.g., en_us, de_de)")
|
||||
langStringKey = String(description="Unique translation key")
|
||||
langStringText = String(description="Translated text content")
|
||||
|
||||
|
||||
class LangStringResult(ObjectType):
|
||||
langStrings = List(LangString, description="List of language string objects")
|
||||
count = Int(description="Total count of strings")
|
||||
|
||||
|
||||
# --- APP EVENTS ---
|
||||
|
||||
class AppEvent(ObjectType):
|
||||
index = Int(description="Internal index")
|
||||
guid = String(description="Unique event GUID")
|
||||
appEventProcessed = Int(description="Processing status (0 or 1)")
|
||||
dateTimeCreated = String(description="Event creation timestamp")
|
||||
|
||||
objectType = String(description="Type of the related object (Device, Setting, etc.)")
|
||||
objectGuid = String(description="GUID of the related object")
|
||||
objectPlugin = String(description="Plugin associated with the object")
|
||||
objectPrimaryId = String(description="Primary identifier of the object")
|
||||
objectSecondaryId = String(description="Secondary identifier of the object")
|
||||
objectForeignKey = String(description="Foreign key reference")
|
||||
objectIndex = Int(description="Object index")
|
||||
|
||||
objectIsNew = Int(description="Is the object new? (0 or 1)")
|
||||
objectIsArchived = Int(description="Is the object archived? (0 or 1)")
|
||||
objectStatusColumn = String(description="Column used for status")
|
||||
objectStatus = String(description="Object status value")
|
||||
|
||||
appEventType = String(description="Type of application event")
|
||||
|
||||
helper1 = String(description="Generic helper field 1")
|
||||
helper2 = String(description="Generic helper field 2")
|
||||
helper3 = String(description="Generic helper field 3")
|
||||
extra = String(description="Additional JSON data")
|
||||
|
||||
|
||||
class AppEventResult(ObjectType):
|
||||
appEvents = List(AppEvent, description="List of application events")
|
||||
count = Int(description="Total count of events")
|
||||
|
||||
|
||||
# ----------------------------------------------------------------------------------------------
|
||||
|
||||
# Define Query Type with Pagination Support
|
||||
class Query(ObjectType):
|
||||
# --- DEVICES ---
|
||||
devices = Field(DeviceResult, options=PageQueryOptionsInput())
|
||||
@@ -652,15 +505,75 @@ class Query(ObjectType):
|
||||
|
||||
return LangStringResult(langStrings=langStrings, count=len(langStrings))
|
||||
|
||||
# --- PLUGINS_OBJECTS ---
|
||||
pluginsObjects = Field(PluginsObjectsResult, options=PluginQueryOptionsInput())
|
||||
|
||||
# helps sorting inconsistent dataset mixed integers and strings
|
||||
def mixed_type_sort_key(value):
|
||||
if value is None or value == "":
|
||||
return (2, "") # Place None or empty strings last
|
||||
def resolve_pluginsObjects(self, info, options=None):
|
||||
return _resolve_plugin_table("table_plugins_objects.json", options, PluginsObjectsResult)
|
||||
|
||||
# --- PLUGINS_EVENTS ---
|
||||
pluginsEvents = Field(PluginsEventsResult, options=PluginQueryOptionsInput())
|
||||
|
||||
def resolve_pluginsEvents(self, info, options=None):
|
||||
return _resolve_plugin_table("table_plugins_events.json", options, PluginsEventsResult)
|
||||
|
||||
# --- PLUGINS_HISTORY ---
|
||||
pluginsHistory = Field(PluginsHistoryResult, options=PluginQueryOptionsInput())
|
||||
|
||||
def resolve_pluginsHistory(self, info, options=None):
|
||||
return _resolve_plugin_table("table_plugins_history.json", options, PluginsHistoryResult)
|
||||
|
||||
# --- EVENTS ---
|
||||
events = Field(EventsResult, options=EventQueryOptionsInput())
|
||||
|
||||
def resolve_events(self, info, options=None):
|
||||
try:
|
||||
with open(folder + "table_events.json", "r") as f:
|
||||
data = json.load(f).get("data", [])
|
||||
except (FileNotFoundError, json.JSONDecodeError) as e:
|
||||
mylog("none", f"[graphql_schema] Error loading events data: {e}")
|
||||
return EventsResult(entries=[], count=0, db_count=0)
|
||||
|
||||
db_count = len(data)
|
||||
data = apply_events_filters(data, options)
|
||||
data, total_count = apply_common_pagination(data, options)
|
||||
return EventsResult(
|
||||
entries=[EventEntry(**r) for r in data],
|
||||
count=total_count,
|
||||
db_count=db_count,
|
||||
)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Private resolver helper — shared by all three plugin table resolvers
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def _resolve_plugin_table(json_file, options, ResultType):
|
||||
try:
|
||||
return (0, int(value)) # Integers get priority
|
||||
except (ValueError, TypeError):
|
||||
return (1, str(value)) # Strings come next
|
||||
with open(folder + json_file, "r") as f:
|
||||
data = json.load(f).get("data", [])
|
||||
except (FileNotFoundError, json.JSONDecodeError) as e:
|
||||
mylog("none", f"[graphql_schema] Error loading {json_file}: {e}")
|
||||
return ResultType(entries=[], count=0, db_count=0)
|
||||
|
||||
# Scope to the requested plugin + foreignKey FIRST so db_count
|
||||
# reflects the total for THIS plugin, not the entire table.
|
||||
if options:
|
||||
if options.plugin:
|
||||
pl = options.plugin.lower()
|
||||
data = [r for r in data if str(r.get("plugin", "")).lower() == pl]
|
||||
if options.foreignKey:
|
||||
fk = options.foreignKey.lower()
|
||||
data = [r for r in data if str(r.get("foreignKey", "")).lower() == fk]
|
||||
|
||||
db_count = len(data)
|
||||
data = apply_plugin_filters(data, options)
|
||||
data, total_count = apply_common_pagination(data, options)
|
||||
return ResultType(
|
||||
entries=[PluginEntry(**r) for r in data],
|
||||
count=total_count,
|
||||
db_count=db_count,
|
||||
)
|
||||
|
||||
|
||||
# Schema Definition
|
||||
|
||||
Reference in New Issue
Block a user