iflow-mcp_enuno-unifi-mcp-server 0.2.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (81) hide show
  1. iflow_mcp_enuno_unifi_mcp_server-0.2.1.dist-info/METADATA +1282 -0
  2. iflow_mcp_enuno_unifi_mcp_server-0.2.1.dist-info/RECORD +81 -0
  3. iflow_mcp_enuno_unifi_mcp_server-0.2.1.dist-info/WHEEL +4 -0
  4. iflow_mcp_enuno_unifi_mcp_server-0.2.1.dist-info/entry_points.txt +2 -0
  5. iflow_mcp_enuno_unifi_mcp_server-0.2.1.dist-info/licenses/LICENSE +201 -0
  6. src/__init__.py +3 -0
  7. src/__main__.py +6 -0
  8. src/api/__init__.py +5 -0
  9. src/api/client.py +727 -0
  10. src/api/site_manager_client.py +176 -0
  11. src/cache.py +483 -0
  12. src/config/__init__.py +5 -0
  13. src/config/config.py +321 -0
  14. src/main.py +2234 -0
  15. src/models/__init__.py +126 -0
  16. src/models/acl.py +41 -0
  17. src/models/backup.py +272 -0
  18. src/models/client.py +74 -0
  19. src/models/device.py +53 -0
  20. src/models/dpi.py +50 -0
  21. src/models/firewall_policy.py +123 -0
  22. src/models/firewall_zone.py +28 -0
  23. src/models/network.py +62 -0
  24. src/models/qos_profile.py +458 -0
  25. src/models/radius.py +141 -0
  26. src/models/reference_data.py +34 -0
  27. src/models/site.py +59 -0
  28. src/models/site_manager.py +120 -0
  29. src/models/topology.py +138 -0
  30. src/models/traffic_flow.py +137 -0
  31. src/models/traffic_matching_list.py +56 -0
  32. src/models/voucher.py +42 -0
  33. src/models/vpn.py +73 -0
  34. src/models/wan.py +48 -0
  35. src/models/zbf_matrix.py +49 -0
  36. src/resources/__init__.py +8 -0
  37. src/resources/clients.py +111 -0
  38. src/resources/devices.py +102 -0
  39. src/resources/networks.py +93 -0
  40. src/resources/site_manager.py +64 -0
  41. src/resources/sites.py +86 -0
  42. src/tools/__init__.py +25 -0
  43. src/tools/acls.py +328 -0
  44. src/tools/application.py +42 -0
  45. src/tools/backups.py +1173 -0
  46. src/tools/client_management.py +505 -0
  47. src/tools/clients.py +203 -0
  48. src/tools/device_control.py +325 -0
  49. src/tools/devices.py +354 -0
  50. src/tools/dpi.py +241 -0
  51. src/tools/dpi_tools.py +89 -0
  52. src/tools/firewall.py +417 -0
  53. src/tools/firewall_policies.py +430 -0
  54. src/tools/firewall_zones.py +515 -0
  55. src/tools/network_config.py +388 -0
  56. src/tools/networks.py +190 -0
  57. src/tools/port_forwarding.py +263 -0
  58. src/tools/qos.py +1070 -0
  59. src/tools/radius.py +763 -0
  60. src/tools/reference_data.py +107 -0
  61. src/tools/site_manager.py +466 -0
  62. src/tools/site_vpn.py +95 -0
  63. src/tools/sites.py +187 -0
  64. src/tools/topology.py +406 -0
  65. src/tools/traffic_flows.py +1062 -0
  66. src/tools/traffic_matching_lists.py +371 -0
  67. src/tools/vouchers.py +249 -0
  68. src/tools/vpn.py +76 -0
  69. src/tools/wans.py +30 -0
  70. src/tools/wifi.py +498 -0
  71. src/tools/zbf_matrix.py +326 -0
  72. src/utils/__init__.py +88 -0
  73. src/utils/audit.py +213 -0
  74. src/utils/exceptions.py +114 -0
  75. src/utils/helpers.py +159 -0
  76. src/utils/logger.py +105 -0
  77. src/utils/sanitize.py +244 -0
  78. src/utils/validators.py +160 -0
  79. src/webhooks/__init__.py +6 -0
  80. src/webhooks/handlers.py +196 -0
  81. src/webhooks/receiver.py +290 -0
src/models/radius.py ADDED
@@ -0,0 +1,141 @@
1
+ """RADIUS profile data models."""
2
+
3
+ from typing import Literal
4
+
5
+ from pydantic import BaseModel, ConfigDict, Field
6
+
7
+
8
+ class RADIUSProfile(BaseModel):
9
+ """UniFi RADIUS authentication profile."""
10
+
11
+ id: str = Field(..., description="RADIUS profile ID", alias="_id")
12
+ name: str = Field(..., description="Profile name")
13
+
14
+ # Authentication server configuration
15
+ auth_server: str = Field(..., description="Authentication server IP/hostname")
16
+ auth_port: int = Field(1812, description="Authentication port")
17
+ auth_secret: str | None = Field(None, description="Authentication server shared secret")
18
+
19
+ # Accounting server configuration (optional)
20
+ acct_server: str | None = Field(None, description="Accounting server IP/hostname")
21
+ acct_port: int = Field(1813, description="Accounting port")
22
+ acct_secret: str | None = Field(None, description="Accounting server shared secret")
23
+ use_same_secret: bool = Field(True, description="Use auth_secret for accounting")
24
+
25
+ # VLAN configuration
26
+ vlan_enabled: bool = Field(False, description="VLAN assignment enabled")
27
+ vlan_wlan_mode: str | None = Field(None, description="VLAN mode for WLAN")
28
+
29
+ # Advanced settings
30
+ enabled: bool = Field(True, description="Whether profile is enabled")
31
+ interim_update_interval: int | None = Field(
32
+ None, description="Interim accounting update interval (seconds)"
33
+ )
34
+ use_usg_accounting_server: bool = Field(False, description="Use USG as accounting proxy")
35
+
36
+ # Metadata
37
+ site_id: str | None = Field(None, description="Site ID")
38
+
39
+ model_config = ConfigDict(
40
+ populate_by_name=True,
41
+ json_schema_extra={
42
+ "example": {
43
+ "_id": "507f191e810c19729de860ea",
44
+ "name": "Corporate RADIUS",
45
+ "auth_server": "radius.example.com",
46
+ "auth_port": 1812,
47
+ "auth_secret": "secret123",
48
+ "acct_port": 1813,
49
+ "enabled": True,
50
+ "vlan_enabled": True,
51
+ }
52
+ },
53
+ )
54
+
55
+
56
+ class RADIUSAccount(BaseModel):
57
+ """RADIUS user account for guest access."""
58
+
59
+ id: str = Field(..., description="Account ID", alias="_id")
60
+ name: str = Field(..., description="Username")
61
+ password: str = Field(..., description="Password")
62
+
63
+ # Time-based settings
64
+ enabled: bool = Field(True, description="Account is enabled")
65
+ start_time: int | None = Field(None, description="Account activation time (Unix timestamp)")
66
+ end_time: int | None = Field(None, description="Account expiration time (Unix timestamp)")
67
+
68
+ # VLAN assignment
69
+ vlan_id: int | None = Field(None, description="Assigned VLAN ID")
70
+
71
+ # Tunnel settings (for WPA2-Enterprise)
72
+ tunnel_type: int | None = Field(None, description="RADIUS tunnel type")
73
+ tunnel_medium_type: int | None = Field(None, description="RADIUS tunnel medium type")
74
+
75
+ # Metadata
76
+ site_id: str = Field(..., description="Site ID")
77
+ note: str | None = Field(None, description="Admin notes")
78
+
79
+ model_config = ConfigDict(populate_by_name=True)
80
+
81
+
82
+ class GuestPortalConfig(BaseModel):
83
+ """Guest portal customization configuration."""
84
+
85
+ site_id: str = Field(..., description="Site ID")
86
+
87
+ # Portal settings
88
+ enabled: bool = Field(True, description="Guest portal enabled")
89
+ portal_title: str = Field("Guest WiFi", description="Portal page title")
90
+ redirect_enabled: bool = Field(False, description="Redirect after authentication")
91
+ redirect_url: str | None = Field(None, description="Redirect URL")
92
+
93
+ # Authentication method
94
+ auth_method: Literal["none", "password", "voucher", "radius", "external"] = Field(
95
+ "voucher", description="Authentication method"
96
+ )
97
+ password: str | None = Field(None, description="Portal password (if auth_method=password)")
98
+
99
+ # Terms of service
100
+ terms_of_service_enabled: bool = Field(False, description="Require ToS acceptance")
101
+ terms_of_service_text: str | None = Field(None, description="Terms of service text")
102
+
103
+ # Session limits
104
+ session_timeout: int = Field(480, description="Session timeout in minutes (0=unlimited)")
105
+ download_limit_kbps: int | None = Field(None, description="Download speed limit in kbps")
106
+ upload_limit_kbps: int | None = Field(None, description="Upload speed limit in kbps")
107
+
108
+ # Customization
109
+ background_image_url: str | None = Field(None, description="Background image URL")
110
+ logo_url: str | None = Field(None, description="Logo image URL")
111
+ custom_css: str | None = Field(None, description="Custom CSS")
112
+
113
+ model_config = ConfigDict(populate_by_name=True)
114
+
115
+
116
+ class HotspotPackage(BaseModel):
117
+ """Hotspot package with time and bandwidth limits."""
118
+
119
+ id: str = Field(..., description="Package ID", alias="_id")
120
+ name: str = Field(..., description="Package name")
121
+
122
+ # Time limits
123
+ duration_minutes: int = Field(..., description="Duration in minutes")
124
+
125
+ # Bandwidth limits
126
+ download_limit_kbps: int | None = Field(None, description="Download speed limit in kbps")
127
+ upload_limit_kbps: int | None = Field(None, description="Upload speed limit in kbps")
128
+
129
+ # Data quotas
130
+ download_quota_mb: int | None = Field(None, description="Download quota in MB")
131
+ upload_quota_mb: int | None = Field(None, description="Upload quota in MB")
132
+
133
+ # Pricing (for payment gateway integration)
134
+ price: float | None = Field(None, description="Package price")
135
+ currency: str = Field("USD", description="Currency code")
136
+
137
+ # Metadata
138
+ enabled: bool = Field(True, description="Package is available")
139
+ site_id: str = Field(..., description="Site ID")
140
+
141
+ model_config = ConfigDict(populate_by_name=True)
@@ -0,0 +1,34 @@
1
+ """Reference data models for supporting resources."""
2
+
3
+ from pydantic import BaseModel, ConfigDict, Field
4
+
5
+
6
+ class DeviceTag(BaseModel):
7
+ """UniFi device tag for WiFi broadcast assignments."""
8
+
9
+ id: str = Field(..., description="Tag ID", alias="_id")
10
+ name: str = Field(..., description="Tag name")
11
+ devices: list[str] | None = Field(None, description="Associated device IDs")
12
+ site_id: str | None = Field(None, description="Site ID")
13
+
14
+ model_config = ConfigDict(
15
+ populate_by_name=True,
16
+ json_schema_extra={
17
+ "example": {
18
+ "_id": "507f191e810c19729de860ea",
19
+ "name": "Guest WiFi APs",
20
+ "devices": ["device-id-1", "device-id-2"],
21
+ }
22
+ },
23
+ )
24
+
25
+
26
+ class Country(BaseModel):
27
+ """ISO country code and name."""
28
+
29
+ code: str = Field(..., description="ISO 3166-1 alpha-2 country code")
30
+ name: str = Field(..., description="Country name")
31
+
32
+ model_config = ConfigDict(
33
+ json_schema_extra={"example": {"code": "US", "name": "United States"}}
34
+ )
src/models/site.py ADDED
@@ -0,0 +1,59 @@
1
+ """Site data model."""
2
+
3
+ from pydantic import AliasChoices, BaseModel, ConfigDict, Field, model_validator
4
+
5
+
6
+ class Site(BaseModel):
7
+ """UniFi site information (compatible with Cloud and Local API)."""
8
+
9
+ model_config = ConfigDict(
10
+ populate_by_name=True,
11
+ extra="allow", # Allow extra fields from Cloud API
12
+ json_schema_extra={
13
+ "example": {
14
+ "_id": "default",
15
+ "name": "Default Site",
16
+ "desc": "Default site description",
17
+ }
18
+ },
19
+ )
20
+
21
+ id: str = Field(
22
+ ...,
23
+ description="Site ID",
24
+ alias="_id",
25
+ validation_alias=AliasChoices("_id", "siteId", "id"),
26
+ )
27
+
28
+ name: str | None = Field(
29
+ None,
30
+ description="Site name",
31
+ validation_alias=AliasChoices("name", "siteName"),
32
+ )
33
+
34
+ desc: str | None = Field(
35
+ None,
36
+ description="Site description",
37
+ validation_alias=AliasChoices("desc", "description"),
38
+ )
39
+
40
+ # Cloud API specific fields
41
+ is_owner: bool | None = Field(
42
+ None, alias="isOwner", description="Whether user owns the site (Cloud API)"
43
+ )
44
+
45
+ # Optional metadata fields (Local API)
46
+ attr_hidden_id: str | None = Field(None, description="Hidden ID attribute")
47
+ attr_no_delete: bool | None = Field(None, description="Whether site can be deleted")
48
+ role: str | None = Field(None, description="Site role")
49
+
50
+ @model_validator(mode="after")
51
+ def set_name_fallback(self) -> "Site":
52
+ """Set name from ID if not provided (Cloud API compatibility).
53
+
54
+ Returns:
55
+ The Site instance with name populated
56
+ """
57
+ if self.name is None or self.name == "":
58
+ self.name = f"Site {self.id}"
59
+ return self
@@ -0,0 +1,120 @@
1
+ """Site Manager API models."""
2
+
3
+ from typing import Literal
4
+
5
+ from pydantic import BaseModel, Field
6
+
7
+
8
+ class SiteHealthSummary(BaseModel):
9
+ """Health status for a site."""
10
+
11
+ site_id: str = Field(..., description="Site identifier")
12
+ site_name: str = Field(..., description="Site name")
13
+ status: Literal["healthy", "degraded", "down"] = Field(..., description="Health status")
14
+ devices_online: int = Field(0, description="Number of online devices")
15
+ devices_total: int = Field(0, description="Total number of devices")
16
+ clients_active: int = Field(0, description="Number of active clients")
17
+ uptime_percentage: float = Field(0.0, description="Uptime percentage")
18
+ last_updated: str = Field(..., description="Last update timestamp (ISO)")
19
+
20
+
21
+ class InternetHealthMetrics(BaseModel):
22
+ """Internet connectivity metrics."""
23
+
24
+ site_id: str | None = Field(None, description="Site identifier (None for aggregate)")
25
+ latency_ms: float | None = Field(None, description="Average latency in milliseconds")
26
+ packet_loss_percent: float = Field(0.0, description="Packet loss percentage")
27
+ jitter_ms: float | None = Field(None, description="Jitter in milliseconds")
28
+ bandwidth_up_mbps: float | None = Field(None, description="Upload bandwidth in Mbps")
29
+ bandwidth_down_mbps: float | None = Field(None, description="Download bandwidth in Mbps")
30
+ status: Literal["healthy", "degraded", "down"] = Field(..., description="Health status")
31
+ last_tested: str = Field(..., description="Last test timestamp (ISO)")
32
+
33
+
34
+ class CrossSiteStatistics(BaseModel):
35
+ """Aggregated statistics across multiple sites."""
36
+
37
+ total_sites: int = Field(0, description="Total number of sites")
38
+ sites_healthy: int = Field(0, description="Number of healthy sites")
39
+ sites_degraded: int = Field(0, description="Number of degraded sites")
40
+ sites_down: int = Field(0, description="Number of down sites")
41
+ total_devices: int = Field(0, description="Total number of devices")
42
+ devices_online: int = Field(0, description="Total online devices")
43
+ total_clients: int = Field(0, description="Total number of clients")
44
+ total_bandwidth_up_mbps: float = Field(0.0, description="Total upload bandwidth")
45
+ total_bandwidth_down_mbps: float = Field(0.0, description="Total download bandwidth")
46
+ site_summaries: list[SiteHealthSummary] = Field(
47
+ default_factory=list, description="Health summary for each site"
48
+ )
49
+
50
+
51
+ class VantagePoint(BaseModel):
52
+ """Vantage Point information."""
53
+
54
+ vantage_point_id: str = Field(..., description="Vantage Point identifier")
55
+ name: str = Field(..., description="Vantage Point name")
56
+ location: str | None = Field(None, description="Location")
57
+ latitude: float | None = Field(None, description="Latitude")
58
+ longitude: float | None = Field(None, description="Longitude")
59
+ status: Literal["active", "inactive"] = Field(..., description="Status")
60
+ site_ids: list[str] = Field(default_factory=list, description="Associated site IDs")
61
+
62
+
63
+ class SiteInventory(BaseModel):
64
+ """Comprehensive inventory for a single site."""
65
+
66
+ site_id: str = Field(..., description="Site identifier")
67
+ site_name: str = Field(..., description="Site name")
68
+ device_count: int = Field(0, description="Total devices")
69
+ device_types: dict[str, int] = Field(default_factory=dict, description="Count by device type")
70
+ client_count: int = Field(0, description="Total active clients")
71
+ network_count: int = Field(0, description="Total networks/VLANs")
72
+ ssid_count: int = Field(0, description="Total SSIDs")
73
+ uplink_count: int = Field(0, description="Total WAN uplinks")
74
+ vpn_tunnel_count: int = Field(0, description="Total VPN tunnels")
75
+ firewall_rule_count: int = Field(0, description="Total firewall rules")
76
+ last_updated: str = Field(..., description="Inventory timestamp (ISO)")
77
+
78
+
79
+ class SitePerformanceMetrics(BaseModel):
80
+ """Performance metrics for a single site."""
81
+
82
+ site_id: str = Field(..., description="Site identifier")
83
+ site_name: str = Field(..., description="Site name")
84
+ avg_latency_ms: float | None = Field(None, description="Average latency")
85
+ avg_bandwidth_up_mbps: float | None = Field(None, description="Average upload bandwidth")
86
+ avg_bandwidth_down_mbps: float | None = Field(None, description="Average download bandwidth")
87
+ uptime_percentage: float = Field(0.0, description="Uptime percentage")
88
+ device_online_percentage: float = Field(0.0, description="Percentage of devices online")
89
+ client_count: int = Field(0, description="Active clients")
90
+ health_status: Literal["healthy", "degraded", "down"] = Field(..., description="Overall status")
91
+
92
+
93
+ class CrossSitePerformanceComparison(BaseModel):
94
+ """Performance comparison across multiple sites."""
95
+
96
+ total_sites: int = Field(0, description="Number of sites compared")
97
+ best_performing_site: SitePerformanceMetrics | None = Field(
98
+ None, description="Site with best overall performance"
99
+ )
100
+ worst_performing_site: SitePerformanceMetrics | None = Field(
101
+ None, description="Site with worst overall performance"
102
+ )
103
+ average_uptime: float = Field(0.0, description="Average uptime across all sites")
104
+ average_latency_ms: float | None = Field(None, description="Average latency across sites")
105
+ site_metrics: list[SitePerformanceMetrics] = Field(
106
+ default_factory=list, description="Metrics for each site"
107
+ )
108
+
109
+
110
+ class CrossSiteSearchResult(BaseModel):
111
+ """Search result from cross-site search."""
112
+
113
+ total_results: int = Field(0, description="Total number of results found")
114
+ search_query: str = Field(..., description="Original search query")
115
+ result_type: Literal["device", "client", "network", "all"] = Field(
116
+ ..., description="Type of results"
117
+ )
118
+ results: list[dict] = Field(
119
+ default_factory=list, description="Search results with site context"
120
+ )
src/models/topology.py ADDED
@@ -0,0 +1,138 @@
1
+ """Network topology data models."""
2
+
3
+ from typing import Literal
4
+
5
+ from pydantic import BaseModel, ConfigDict, Field
6
+
7
+
8
+ class TopologyNode(BaseModel):
9
+ """A node in the network topology (device, client, or network)."""
10
+
11
+ node_id: str = Field(..., description="Unique node identifier")
12
+ node_type: Literal["device", "client", "network"] = Field(..., description="Type of node")
13
+ name: str | None = Field(None, description="Node name")
14
+ mac: str | None = Field(None, description="MAC address (for devices/clients)")
15
+ ip: str | None = Field(None, description="IP address")
16
+ model: str | None = Field(None, description="Device model (for devices)")
17
+ type_detail: str | None = Field(None, description="Device type detail (uap, usw, ugw, etc.)")
18
+
19
+ # Position and connectivity
20
+ uplink_device_id: str | None = Field(
21
+ None, description="ID of uplink device this node connects to"
22
+ )
23
+ uplink_port: int | None = Field(None, description="Uplink port number")
24
+ uplink_depth: int | None = Field(0, description="Depth in network hierarchy (0=gateway)")
25
+
26
+ # Status
27
+ state: int | None = Field(None, description="Node state (0=offline, 1=online)")
28
+ adopted: bool | None = Field(None, description="Whether device is adopted")
29
+
30
+ # Coordinates for visualization
31
+ x_coordinate: float | None = Field(None, description="X coordinate for layout")
32
+ y_coordinate: float | None = Field(None, description="Y coordinate for layout")
33
+
34
+ model_config = ConfigDict(
35
+ populate_by_name=True,
36
+ json_schema_extra={
37
+ "example": {
38
+ "node_id": "507f1f77bcf86cd799439011",
39
+ "node_type": "device",
40
+ "name": "AP-Office",
41
+ "mac": "aa:bb:cc:dd:ee:ff",
42
+ "ip": "192.168.1.10",
43
+ "model": "U6-LR",
44
+ "type_detail": "uap",
45
+ "uplink_device_id": "507f1f77bcf86cd799439012",
46
+ "uplink_port": 5,
47
+ "uplink_depth": 1,
48
+ "state": 1,
49
+ "adopted": True,
50
+ }
51
+ },
52
+ )
53
+
54
+
55
+ class TopologyConnection(BaseModel):
56
+ """A connection between two nodes in the network topology."""
57
+
58
+ connection_id: str = Field(..., description="Unique connection identifier")
59
+ source_node_id: str = Field(..., description="Source node ID")
60
+ target_node_id: str = Field(..., description="Target node ID")
61
+ connection_type: Literal["wired", "wireless", "uplink"] = Field(
62
+ ..., description="Type of connection"
63
+ )
64
+
65
+ # Port information
66
+ source_port: int | None = Field(None, description="Source port number")
67
+ target_port: int | None = Field(None, description="Target port number")
68
+ port_name: str | None = Field(None, description="Port name/description")
69
+
70
+ # Connection quality
71
+ speed_mbps: int | None = Field(None, description="Connection speed in Mbps")
72
+ duplex: str | None = Field(None, description="Duplex mode (full/half)")
73
+ link_quality: int | None = Field(None, description="Link quality percentage (0-100)")
74
+
75
+ # Status
76
+ status: str | None = Field(None, description="Connection status (up/down)")
77
+ is_uplink: bool = Field(False, description="Whether this is an uplink connection")
78
+
79
+ model_config = ConfigDict(
80
+ populate_by_name=True,
81
+ json_schema_extra={
82
+ "example": {
83
+ "connection_id": "conn_001",
84
+ "source_node_id": "507f1f77bcf86cd799439011",
85
+ "target_node_id": "507f1f77bcf86cd799439012",
86
+ "connection_type": "wired",
87
+ "source_port": 5,
88
+ "target_port": 1,
89
+ "speed_mbps": 1000,
90
+ "duplex": "full",
91
+ "link_quality": 100,
92
+ "status": "up",
93
+ "is_uplink": True,
94
+ }
95
+ },
96
+ )
97
+
98
+
99
+ class NetworkDiagram(BaseModel):
100
+ """Complete network topology structure."""
101
+
102
+ site_id: str = Field(..., description="Site identifier")
103
+ site_name: str | None = Field(None, description="Site name")
104
+ generated_at: str = Field(..., description="Timestamp when topology was generated (ISO)")
105
+
106
+ # Topology data
107
+ nodes: list[TopologyNode] = Field(default_factory=list, description="All nodes in the topology")
108
+ connections: list[TopologyConnection] = Field(
109
+ default_factory=list, description="All connections between nodes"
110
+ )
111
+
112
+ # Summary statistics
113
+ total_devices: int = Field(0, description="Total number of devices")
114
+ total_clients: int = Field(0, description="Total number of clients")
115
+ total_connections: int = Field(0, description="Total number of connections")
116
+ max_depth: int = Field(0, description="Maximum depth in network hierarchy")
117
+
118
+ # Layout metadata
119
+ layout_algorithm: str | None = Field(None, description="Algorithm used for node positioning")
120
+ has_coordinates: bool = Field(False, description="Whether nodes have position coordinates")
121
+
122
+ model_config = ConfigDict(
123
+ populate_by_name=True,
124
+ json_schema_extra={
125
+ "example": {
126
+ "site_id": "507f1f77bcf86cd799439013",
127
+ "site_name": "Default",
128
+ "generated_at": "2025-01-24T12:00:00Z",
129
+ "nodes": [],
130
+ "connections": [],
131
+ "total_devices": 5,
132
+ "total_clients": 12,
133
+ "total_connections": 16,
134
+ "max_depth": 3,
135
+ "has_coordinates": False,
136
+ }
137
+ },
138
+ )
@@ -0,0 +1,137 @@
1
+ """Traffic flow models."""
2
+
3
+ from typing import Literal
4
+
5
+ from pydantic import BaseModel, Field
6
+
7
+
8
+ class TrafficFlow(BaseModel):
9
+ """Individual traffic flow record."""
10
+
11
+ flow_id: str = Field(..., description="Flow identifier")
12
+ site_id: str = Field(..., description="Site identifier")
13
+ source_ip: str = Field(..., description="Source IP address")
14
+ source_port: int | None = Field(None, description="Source port")
15
+ destination_ip: str = Field(..., description="Destination IP address")
16
+ destination_port: int | None = Field(None, description="Destination port")
17
+ protocol: str = Field(..., description="Protocol (tcp/udp/icmp)")
18
+ application_id: str | None = Field(None, description="DPI application identifier")
19
+ application_name: str | None = Field(None, description="Application name")
20
+ bytes_sent: int = Field(0, description="Bytes sent")
21
+ bytes_received: int = Field(0, description="Bytes received")
22
+ packets_sent: int = Field(0, description="Packets sent")
23
+ packets_received: int = Field(0, description="Packets received")
24
+ start_time: str = Field(..., description="Flow start timestamp (ISO)")
25
+ end_time: str | None = Field(None, description="Flow end timestamp (ISO)")
26
+ duration: int | None = Field(None, description="Flow duration in seconds")
27
+ client_mac: str | None = Field(None, description="Client MAC address")
28
+ device_id: str | None = Field(None, description="Device identifier")
29
+
30
+
31
+ class FlowStatistics(BaseModel):
32
+ """Aggregated flow statistics."""
33
+
34
+ site_id: str = Field(..., description="Site identifier")
35
+ time_range: str = Field(..., description="Time range for statistics")
36
+ total_flows: int = Field(0, description="Total number of flows")
37
+ total_bytes_sent: int = Field(0, description="Total bytes sent")
38
+ total_bytes_received: int = Field(0, description="Total bytes received")
39
+ total_bytes: int = Field(0, description="Total bytes")
40
+ total_packets_sent: int = Field(0, description="Total packets sent")
41
+ total_packets_received: int = Field(0, description="Total packets received")
42
+ unique_sources: int = Field(0, description="Number of unique source IPs")
43
+ unique_destinations: int = Field(0, description="Number of unique destination IPs")
44
+ top_applications: list[dict] = Field(
45
+ default_factory=list, description="Top applications by bandwidth"
46
+ )
47
+
48
+
49
+ class FlowRisk(BaseModel):
50
+ """Risk assessment data for a flow."""
51
+
52
+ flow_id: str = Field(..., description="Flow identifier")
53
+ risk_score: float = Field(..., description="Risk score (0-100)")
54
+ risk_level: Literal["low", "medium", "high", "critical"] = Field(..., description="Risk level")
55
+ indicators: list[str] = Field(default_factory=list, description="List of risk indicators")
56
+ threat_type: str | None = Field(None, description="Type of threat detected")
57
+ description: str | None = Field(None, description="Risk description")
58
+
59
+
60
+ class FlowView(BaseModel):
61
+ """Saved flow view configuration."""
62
+
63
+ view_id: str = Field(..., description="View identifier")
64
+ site_id: str = Field(..., description="Site identifier")
65
+ name: str = Field(..., description="View name")
66
+ description: str | None = Field(None, description="View description")
67
+ filter_expression: str | None = Field(None, description="Filter expression")
68
+ time_range: str = Field("24h", description="Time range")
69
+ created_at: str = Field(..., description="Creation timestamp (ISO)")
70
+
71
+
72
+ class FlowStreamUpdate(BaseModel):
73
+ """Real-time flow update for streaming."""
74
+
75
+ update_type: Literal["new", "update", "closed"] = Field(..., description="Update type")
76
+ flow: TrafficFlow = Field(..., description="Flow data")
77
+ timestamp: str = Field(..., description="Update timestamp (ISO)")
78
+ bandwidth_rate: dict[str, float] | None = Field(
79
+ None, description="Current bandwidth rates (bps)"
80
+ )
81
+
82
+
83
+ class ConnectionState(BaseModel):
84
+ """Connection state tracking."""
85
+
86
+ flow_id: str = Field(..., description="Flow identifier")
87
+ state: Literal["active", "closed", "timed_out"] = Field(..., description="Connection state")
88
+ last_seen: str = Field(..., description="Last activity timestamp (ISO)")
89
+ total_duration: int | None = Field(None, description="Total duration in seconds")
90
+ termination_reason: str | None = Field(None, description="Reason for connection closure")
91
+
92
+
93
+ class ClientFlowAggregation(BaseModel):
94
+ """Client traffic aggregation with enhanced metrics."""
95
+
96
+ client_mac: str = Field(..., description="Client MAC address")
97
+ client_ip: str | None = Field(None, description="Client IP address")
98
+ site_id: str = Field(..., description="Site identifier")
99
+ total_flows: int = Field(0, description="Total number of flows")
100
+ total_bytes: int = Field(0, description="Total bytes transferred")
101
+ total_packets: int = Field(0, description="Total packets transferred")
102
+ active_flows: int = Field(0, description="Currently active flows")
103
+ closed_flows: int = Field(0, description="Closed flows")
104
+ auth_failures: int = Field(0, description="Authentication failure count")
105
+ top_applications: list[dict] = Field(
106
+ default_factory=list, description="Top applications by bandwidth"
107
+ )
108
+ top_destinations: list[dict] = Field(default_factory=list, description="Top destination IPs")
109
+
110
+
111
+ class FlowExportConfig(BaseModel):
112
+ """Configuration for flow export."""
113
+
114
+ export_format: Literal["csv", "json", "pcap"] = Field(..., description="Export format")
115
+ time_range: str = Field(..., description="Time range for export")
116
+ include_fields: list[str] | None = Field(
117
+ None, description="Specific fields to include (None = all)"
118
+ )
119
+ filter_expression: str | None = Field(None, description="Filter expression")
120
+ max_records: int | None = Field(None, description="Maximum number of records")
121
+
122
+
123
+ class BlockFlowAction(BaseModel):
124
+ """Result of a flow block action."""
125
+
126
+ action_id: str = Field(..., description="Block action identifier")
127
+ block_type: Literal["source_ip", "destination_ip", "application"] = Field(
128
+ ..., description="Type of block"
129
+ )
130
+ blocked_target: str = Field(..., description="Blocked IP or application ID")
131
+ rule_id: str | None = Field(None, description="Created firewall rule ID")
132
+ zone_id: str | None = Field(None, description="Zone ID if using ZBF")
133
+ duration: Literal["permanent", "temporary"] | None = Field(
134
+ None, description="Block duration type"
135
+ )
136
+ expires_at: str | None = Field(None, description="Expiration timestamp for temporary blocks")
137
+ created_at: str = Field(..., description="Creation timestamp (ISO)")