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.
- iflow_mcp_enuno_unifi_mcp_server-0.2.1.dist-info/METADATA +1282 -0
- iflow_mcp_enuno_unifi_mcp_server-0.2.1.dist-info/RECORD +81 -0
- iflow_mcp_enuno_unifi_mcp_server-0.2.1.dist-info/WHEEL +4 -0
- iflow_mcp_enuno_unifi_mcp_server-0.2.1.dist-info/entry_points.txt +2 -0
- iflow_mcp_enuno_unifi_mcp_server-0.2.1.dist-info/licenses/LICENSE +201 -0
- src/__init__.py +3 -0
- src/__main__.py +6 -0
- src/api/__init__.py +5 -0
- src/api/client.py +727 -0
- src/api/site_manager_client.py +176 -0
- src/cache.py +483 -0
- src/config/__init__.py +5 -0
- src/config/config.py +321 -0
- src/main.py +2234 -0
- src/models/__init__.py +126 -0
- src/models/acl.py +41 -0
- src/models/backup.py +272 -0
- src/models/client.py +74 -0
- src/models/device.py +53 -0
- src/models/dpi.py +50 -0
- src/models/firewall_policy.py +123 -0
- src/models/firewall_zone.py +28 -0
- src/models/network.py +62 -0
- src/models/qos_profile.py +458 -0
- src/models/radius.py +141 -0
- src/models/reference_data.py +34 -0
- src/models/site.py +59 -0
- src/models/site_manager.py +120 -0
- src/models/topology.py +138 -0
- src/models/traffic_flow.py +137 -0
- src/models/traffic_matching_list.py +56 -0
- src/models/voucher.py +42 -0
- src/models/vpn.py +73 -0
- src/models/wan.py +48 -0
- src/models/zbf_matrix.py +49 -0
- src/resources/__init__.py +8 -0
- src/resources/clients.py +111 -0
- src/resources/devices.py +102 -0
- src/resources/networks.py +93 -0
- src/resources/site_manager.py +64 -0
- src/resources/sites.py +86 -0
- src/tools/__init__.py +25 -0
- src/tools/acls.py +328 -0
- src/tools/application.py +42 -0
- src/tools/backups.py +1173 -0
- src/tools/client_management.py +505 -0
- src/tools/clients.py +203 -0
- src/tools/device_control.py +325 -0
- src/tools/devices.py +354 -0
- src/tools/dpi.py +241 -0
- src/tools/dpi_tools.py +89 -0
- src/tools/firewall.py +417 -0
- src/tools/firewall_policies.py +430 -0
- src/tools/firewall_zones.py +515 -0
- src/tools/network_config.py +388 -0
- src/tools/networks.py +190 -0
- src/tools/port_forwarding.py +263 -0
- src/tools/qos.py +1070 -0
- src/tools/radius.py +763 -0
- src/tools/reference_data.py +107 -0
- src/tools/site_manager.py +466 -0
- src/tools/site_vpn.py +95 -0
- src/tools/sites.py +187 -0
- src/tools/topology.py +406 -0
- src/tools/traffic_flows.py +1062 -0
- src/tools/traffic_matching_lists.py +371 -0
- src/tools/vouchers.py +249 -0
- src/tools/vpn.py +76 -0
- src/tools/wans.py +30 -0
- src/tools/wifi.py +498 -0
- src/tools/zbf_matrix.py +326 -0
- src/utils/__init__.py +88 -0
- src/utils/audit.py +213 -0
- src/utils/exceptions.py +114 -0
- src/utils/helpers.py +159 -0
- src/utils/logger.py +105 -0
- src/utils/sanitize.py +244 -0
- src/utils/validators.py +160 -0
- src/webhooks/__init__.py +6 -0
- src/webhooks/handlers.py +196 -0
- src/webhooks/receiver.py +290 -0
src/resources/sites.py
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
"""Sites MCP resource implementation."""
|
|
2
|
+
|
|
3
|
+
from ..api import UniFiClient
|
|
4
|
+
from ..config import Settings
|
|
5
|
+
from ..models import Site
|
|
6
|
+
from ..utils import get_logger, validate_limit_offset
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class SitesResource:
|
|
10
|
+
"""MCP resource for UniFi sites."""
|
|
11
|
+
|
|
12
|
+
def __init__(self, settings: Settings) -> None:
|
|
13
|
+
"""Initialize sites resource.
|
|
14
|
+
|
|
15
|
+
Args:
|
|
16
|
+
settings: Application settings
|
|
17
|
+
"""
|
|
18
|
+
self.settings = settings
|
|
19
|
+
self.logger = get_logger(__name__, settings.log_level)
|
|
20
|
+
|
|
21
|
+
async def list_sites(self, limit: int | None = None, offset: int | None = None) -> list[Site]:
|
|
22
|
+
"""List all UniFi sites.
|
|
23
|
+
|
|
24
|
+
Args:
|
|
25
|
+
limit: Maximum number of sites to return
|
|
26
|
+
offset: Number of sites to skip
|
|
27
|
+
|
|
28
|
+
Returns:
|
|
29
|
+
List of Site objects
|
|
30
|
+
"""
|
|
31
|
+
limit, offset = validate_limit_offset(limit, offset)
|
|
32
|
+
|
|
33
|
+
async with UniFiClient(self.settings) as client:
|
|
34
|
+
# Authenticate first
|
|
35
|
+
await client.authenticate()
|
|
36
|
+
|
|
37
|
+
# Fetch sites from API
|
|
38
|
+
response = await client.get("/ea/sites")
|
|
39
|
+
|
|
40
|
+
# Extract sites data
|
|
41
|
+
sites_data = response.get("data", [])
|
|
42
|
+
|
|
43
|
+
# Apply pagination
|
|
44
|
+
paginated_data = sites_data[offset : offset + limit]
|
|
45
|
+
|
|
46
|
+
# Parse into Site models
|
|
47
|
+
sites = [Site(**site) for site in paginated_data]
|
|
48
|
+
|
|
49
|
+
self.logger.info(f"Retrieved {len(sites)} sites (offset={offset}, limit={limit})")
|
|
50
|
+
|
|
51
|
+
return sites
|
|
52
|
+
|
|
53
|
+
async def get_site(self, site_id: str) -> Site | None:
|
|
54
|
+
"""Get a specific site by ID.
|
|
55
|
+
|
|
56
|
+
Args:
|
|
57
|
+
site_id: Site identifier
|
|
58
|
+
|
|
59
|
+
Returns:
|
|
60
|
+
Site object or None if not found
|
|
61
|
+
"""
|
|
62
|
+
async with UniFiClient(self.settings) as client:
|
|
63
|
+
await client.authenticate()
|
|
64
|
+
|
|
65
|
+
response = await client.get("/ea/sites")
|
|
66
|
+
sites_data = response.get("data", [])
|
|
67
|
+
|
|
68
|
+
# Find the specific site
|
|
69
|
+
for site_data in sites_data:
|
|
70
|
+
if site_data.get("_id") == site_id or site_data.get("name") == site_id:
|
|
71
|
+
return Site(**site_data)
|
|
72
|
+
|
|
73
|
+
return None
|
|
74
|
+
|
|
75
|
+
def get_uri(self, site_id: str | None = None) -> str:
|
|
76
|
+
"""Get the MCP resource URI.
|
|
77
|
+
|
|
78
|
+
Args:
|
|
79
|
+
site_id: Optional site ID
|
|
80
|
+
|
|
81
|
+
Returns:
|
|
82
|
+
Resource URI
|
|
83
|
+
"""
|
|
84
|
+
if site_id:
|
|
85
|
+
return f"sites://{site_id}"
|
|
86
|
+
return "sites://"
|
src/tools/__init__.py
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"""MCP tools for UniFi MCP Server."""
|
|
2
|
+
|
|
3
|
+
from . import (
|
|
4
|
+
client_management,
|
|
5
|
+
clients,
|
|
6
|
+
device_control,
|
|
7
|
+
devices,
|
|
8
|
+
firewall,
|
|
9
|
+
network_config,
|
|
10
|
+
networks,
|
|
11
|
+
sites,
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
__all__ = [
|
|
15
|
+
# Phase 3: Read Operations
|
|
16
|
+
"devices",
|
|
17
|
+
"clients",
|
|
18
|
+
"networks",
|
|
19
|
+
"sites",
|
|
20
|
+
# Phase 4: Write Operations
|
|
21
|
+
"firewall",
|
|
22
|
+
"network_config",
|
|
23
|
+
"device_control",
|
|
24
|
+
"client_management",
|
|
25
|
+
]
|
src/tools/acls.py
ADDED
|
@@ -0,0 +1,328 @@
|
|
|
1
|
+
"""Access Control List (ACL) management tools."""
|
|
2
|
+
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
from ..api.client import UniFiClient
|
|
6
|
+
from ..config import Settings
|
|
7
|
+
from ..models import ACLRule
|
|
8
|
+
from ..utils import audit_action, get_logger, validate_confirmation
|
|
9
|
+
|
|
10
|
+
logger = get_logger(__name__)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
async def list_acl_rules(
|
|
14
|
+
site_id: str,
|
|
15
|
+
settings: Settings,
|
|
16
|
+
limit: int | None = None,
|
|
17
|
+
offset: int | None = None,
|
|
18
|
+
filter_expr: str | None = None,
|
|
19
|
+
) -> list[dict]:
|
|
20
|
+
"""List all ACL rules for a site.
|
|
21
|
+
|
|
22
|
+
Args:
|
|
23
|
+
site_id: Site identifier
|
|
24
|
+
settings: Application settings
|
|
25
|
+
limit: Maximum number of results
|
|
26
|
+
offset: Starting position
|
|
27
|
+
filter_expr: Filter expression
|
|
28
|
+
|
|
29
|
+
Returns:
|
|
30
|
+
List of ACL rules
|
|
31
|
+
"""
|
|
32
|
+
async with UniFiClient(settings) as client:
|
|
33
|
+
logger.info(f"Listing ACL rules for site {site_id}")
|
|
34
|
+
|
|
35
|
+
if not client.is_authenticated:
|
|
36
|
+
await client.authenticate()
|
|
37
|
+
|
|
38
|
+
params: dict[str, Any] = {}
|
|
39
|
+
if limit is not None:
|
|
40
|
+
params["limit"] = limit
|
|
41
|
+
if offset is not None:
|
|
42
|
+
params["offset"] = offset
|
|
43
|
+
if filter_expr:
|
|
44
|
+
params["filter"] = filter_expr
|
|
45
|
+
|
|
46
|
+
response = await client.get(f"/integration/v1/sites/{site_id}/acls", params=params)
|
|
47
|
+
data = response.get("data", [])
|
|
48
|
+
|
|
49
|
+
return [ACLRule(**rule).model_dump() for rule in data]
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
async def get_acl_rule(site_id: str, acl_rule_id: str, settings: Settings) -> dict:
|
|
53
|
+
"""Get details for a specific ACL rule.
|
|
54
|
+
|
|
55
|
+
Args:
|
|
56
|
+
site_id: Site identifier
|
|
57
|
+
acl_rule_id: ACL rule identifier
|
|
58
|
+
settings: Application settings
|
|
59
|
+
|
|
60
|
+
Returns:
|
|
61
|
+
ACL rule details
|
|
62
|
+
"""
|
|
63
|
+
async with UniFiClient(settings) as client:
|
|
64
|
+
logger.info(f"Getting ACL rule {acl_rule_id} for site {site_id}")
|
|
65
|
+
|
|
66
|
+
if not client.is_authenticated:
|
|
67
|
+
await client.authenticate()
|
|
68
|
+
|
|
69
|
+
response = await client.get(f"/integration/v1/sites/{site_id}/acls/{acl_rule_id}")
|
|
70
|
+
data = response.get("data", response)
|
|
71
|
+
|
|
72
|
+
return ACLRule(**data).model_dump() # type: ignore[no-any-return]
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
async def create_acl_rule(
|
|
76
|
+
site_id: str,
|
|
77
|
+
name: str,
|
|
78
|
+
action: str,
|
|
79
|
+
settings: Settings,
|
|
80
|
+
enabled: bool = True,
|
|
81
|
+
source_type: str | None = None,
|
|
82
|
+
source_id: str | None = None,
|
|
83
|
+
source_network: str | None = None,
|
|
84
|
+
destination_type: str | None = None,
|
|
85
|
+
destination_id: str | None = None,
|
|
86
|
+
destination_network: str | None = None,
|
|
87
|
+
protocol: str | None = None,
|
|
88
|
+
src_port: int | None = None,
|
|
89
|
+
dst_port: int | None = None,
|
|
90
|
+
priority: int = 100,
|
|
91
|
+
description: str | None = None,
|
|
92
|
+
confirm: bool = False,
|
|
93
|
+
dry_run: bool = False,
|
|
94
|
+
) -> dict:
|
|
95
|
+
"""Create a new ACL rule.
|
|
96
|
+
|
|
97
|
+
Args:
|
|
98
|
+
site_id: Site identifier
|
|
99
|
+
name: Rule name
|
|
100
|
+
action: Action to take (allow/deny)
|
|
101
|
+
settings: Application settings
|
|
102
|
+
enabled: Whether the rule is enabled
|
|
103
|
+
source_type: Source type (network/device/ip/any)
|
|
104
|
+
source_id: Source identifier
|
|
105
|
+
source_network: Source network CIDR
|
|
106
|
+
destination_type: Destination type
|
|
107
|
+
destination_id: Destination identifier
|
|
108
|
+
destination_network: Destination network CIDR
|
|
109
|
+
protocol: Protocol (tcp/udp/icmp/all)
|
|
110
|
+
src_port: Source port
|
|
111
|
+
dst_port: Destination port
|
|
112
|
+
priority: Rule priority (lower = higher priority)
|
|
113
|
+
description: Rule description
|
|
114
|
+
confirm: Confirmation flag (required)
|
|
115
|
+
dry_run: If True, validate but don't execute
|
|
116
|
+
|
|
117
|
+
Returns:
|
|
118
|
+
Created ACL rule
|
|
119
|
+
"""
|
|
120
|
+
validate_confirmation(confirm, "create ACL rule")
|
|
121
|
+
|
|
122
|
+
async with UniFiClient(settings) as client:
|
|
123
|
+
logger.info(f"Creating ACL rule '{name}' for site {site_id}")
|
|
124
|
+
|
|
125
|
+
if not client.is_authenticated:
|
|
126
|
+
await client.authenticate()
|
|
127
|
+
|
|
128
|
+
# Build request payload
|
|
129
|
+
payload = {
|
|
130
|
+
"name": name,
|
|
131
|
+
"enabled": enabled,
|
|
132
|
+
"action": action,
|
|
133
|
+
"priority": priority,
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
if description:
|
|
137
|
+
payload["description"] = description
|
|
138
|
+
if source_type:
|
|
139
|
+
payload["sourceType"] = source_type
|
|
140
|
+
if source_id:
|
|
141
|
+
payload["sourceId"] = source_id
|
|
142
|
+
if source_network:
|
|
143
|
+
payload["sourceNetwork"] = source_network
|
|
144
|
+
if destination_type:
|
|
145
|
+
payload["destinationType"] = destination_type
|
|
146
|
+
if destination_id:
|
|
147
|
+
payload["destinationId"] = destination_id
|
|
148
|
+
if destination_network:
|
|
149
|
+
payload["destinationNetwork"] = destination_network
|
|
150
|
+
if protocol:
|
|
151
|
+
payload["protocol"] = protocol
|
|
152
|
+
if src_port is not None:
|
|
153
|
+
payload["srcPort"] = src_port
|
|
154
|
+
if dst_port is not None:
|
|
155
|
+
payload["dstPort"] = dst_port
|
|
156
|
+
|
|
157
|
+
if dry_run:
|
|
158
|
+
logger.info(f"[DRY RUN] Would create ACL rule with payload: {payload}")
|
|
159
|
+
return {"dry_run": True, "payload": payload}
|
|
160
|
+
|
|
161
|
+
response = await client.post(f"/integration/v1/sites/{site_id}/acls", json_data=payload)
|
|
162
|
+
data = response.get("data", response)
|
|
163
|
+
|
|
164
|
+
# Audit the action
|
|
165
|
+
await audit_action(
|
|
166
|
+
settings,
|
|
167
|
+
action_type="create_acl_rule",
|
|
168
|
+
resource_type="acl_rule",
|
|
169
|
+
resource_id=data.get("_id", "unknown"),
|
|
170
|
+
site_id=site_id,
|
|
171
|
+
details={"name": name, "action": action},
|
|
172
|
+
)
|
|
173
|
+
|
|
174
|
+
return ACLRule(**data).model_dump() # type: ignore[no-any-return]
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
async def update_acl_rule(
|
|
178
|
+
site_id: str,
|
|
179
|
+
acl_rule_id: str,
|
|
180
|
+
settings: Settings,
|
|
181
|
+
name: str | None = None,
|
|
182
|
+
action: str | None = None,
|
|
183
|
+
enabled: bool | None = None,
|
|
184
|
+
source_type: str | None = None,
|
|
185
|
+
source_id: str | None = None,
|
|
186
|
+
source_network: str | None = None,
|
|
187
|
+
destination_type: str | None = None,
|
|
188
|
+
destination_id: str | None = None,
|
|
189
|
+
destination_network: str | None = None,
|
|
190
|
+
protocol: str | None = None,
|
|
191
|
+
src_port: int | None = None,
|
|
192
|
+
dst_port: int | None = None,
|
|
193
|
+
priority: int | None = None,
|
|
194
|
+
description: str | None = None,
|
|
195
|
+
confirm: bool = False,
|
|
196
|
+
dry_run: bool = False,
|
|
197
|
+
) -> dict:
|
|
198
|
+
"""Update an existing ACL rule.
|
|
199
|
+
|
|
200
|
+
Args:
|
|
201
|
+
site_id: Site identifier
|
|
202
|
+
acl_rule_id: ACL rule identifier
|
|
203
|
+
settings: Application settings
|
|
204
|
+
name: Rule name
|
|
205
|
+
action: Action to take
|
|
206
|
+
enabled: Whether the rule is enabled
|
|
207
|
+
source_type: Source type
|
|
208
|
+
source_id: Source identifier
|
|
209
|
+
source_network: Source network CIDR
|
|
210
|
+
destination_type: Destination type
|
|
211
|
+
destination_id: Destination identifier
|
|
212
|
+
destination_network: Destination network CIDR
|
|
213
|
+
protocol: Protocol
|
|
214
|
+
src_port: Source port
|
|
215
|
+
dst_port: Destination port
|
|
216
|
+
priority: Rule priority
|
|
217
|
+
description: Rule description
|
|
218
|
+
confirm: Confirmation flag (required)
|
|
219
|
+
dry_run: If True, validate but don't execute
|
|
220
|
+
|
|
221
|
+
Returns:
|
|
222
|
+
Updated ACL rule
|
|
223
|
+
"""
|
|
224
|
+
validate_confirmation(confirm, "update ACL rule")
|
|
225
|
+
|
|
226
|
+
async with UniFiClient(settings) as client:
|
|
227
|
+
logger.info(f"Updating ACL rule {acl_rule_id} for site {site_id}")
|
|
228
|
+
|
|
229
|
+
if not client.is_authenticated:
|
|
230
|
+
await client.authenticate()
|
|
231
|
+
|
|
232
|
+
# Build request payload with only provided fields
|
|
233
|
+
payload: dict[str, Any] = {}
|
|
234
|
+
if name is not None:
|
|
235
|
+
payload["name"] = name
|
|
236
|
+
if action is not None:
|
|
237
|
+
payload["action"] = action
|
|
238
|
+
if enabled is not None:
|
|
239
|
+
payload["enabled"] = enabled
|
|
240
|
+
if priority is not None:
|
|
241
|
+
payload["priority"] = priority
|
|
242
|
+
if description is not None:
|
|
243
|
+
payload["description"] = description
|
|
244
|
+
if source_type is not None:
|
|
245
|
+
payload["sourceType"] = source_type
|
|
246
|
+
if source_id is not None:
|
|
247
|
+
payload["sourceId"] = source_id
|
|
248
|
+
if source_network is not None:
|
|
249
|
+
payload["sourceNetwork"] = source_network
|
|
250
|
+
if destination_type is not None:
|
|
251
|
+
payload["destinationType"] = destination_type
|
|
252
|
+
if destination_id is not None:
|
|
253
|
+
payload["destinationId"] = destination_id
|
|
254
|
+
if destination_network is not None:
|
|
255
|
+
payload["destinationNetwork"] = destination_network
|
|
256
|
+
if protocol is not None:
|
|
257
|
+
payload["protocol"] = protocol
|
|
258
|
+
if src_port is not None:
|
|
259
|
+
payload["srcPort"] = src_port
|
|
260
|
+
if dst_port is not None:
|
|
261
|
+
payload["dstPort"] = dst_port
|
|
262
|
+
|
|
263
|
+
if dry_run:
|
|
264
|
+
logger.info(f"[DRY RUN] Would update ACL rule with payload: {payload}")
|
|
265
|
+
return {"dry_run": True, "payload": payload}
|
|
266
|
+
|
|
267
|
+
response = await client.put(
|
|
268
|
+
f"/integration/v1/sites/{site_id}/acls/{acl_rule_id}", json_data=payload
|
|
269
|
+
)
|
|
270
|
+
data = response.get("data", response)
|
|
271
|
+
|
|
272
|
+
# Audit the action
|
|
273
|
+
await audit_action(
|
|
274
|
+
settings,
|
|
275
|
+
action_type="update_acl_rule",
|
|
276
|
+
resource_type="acl_rule",
|
|
277
|
+
resource_id=acl_rule_id,
|
|
278
|
+
site_id=site_id,
|
|
279
|
+
details=payload,
|
|
280
|
+
)
|
|
281
|
+
|
|
282
|
+
return ACLRule(**data).model_dump() # type: ignore[no-any-return]
|
|
283
|
+
|
|
284
|
+
|
|
285
|
+
async def delete_acl_rule(
|
|
286
|
+
site_id: str,
|
|
287
|
+
acl_rule_id: str,
|
|
288
|
+
settings: Settings,
|
|
289
|
+
confirm: bool = False,
|
|
290
|
+
dry_run: bool = False,
|
|
291
|
+
) -> dict:
|
|
292
|
+
"""Delete an ACL rule.
|
|
293
|
+
|
|
294
|
+
Args:
|
|
295
|
+
site_id: Site identifier
|
|
296
|
+
acl_rule_id: ACL rule identifier
|
|
297
|
+
settings: Application settings
|
|
298
|
+
confirm: Confirmation flag (required)
|
|
299
|
+
dry_run: If True, validate but don't execute
|
|
300
|
+
|
|
301
|
+
Returns:
|
|
302
|
+
Deletion status
|
|
303
|
+
"""
|
|
304
|
+
validate_confirmation(confirm, "delete ACL rule")
|
|
305
|
+
|
|
306
|
+
async with UniFiClient(settings) as client:
|
|
307
|
+
logger.info(f"Deleting ACL rule {acl_rule_id} for site {site_id}")
|
|
308
|
+
|
|
309
|
+
if not client.is_authenticated:
|
|
310
|
+
await client.authenticate()
|
|
311
|
+
|
|
312
|
+
if dry_run:
|
|
313
|
+
logger.info(f"[DRY RUN] Would delete ACL rule {acl_rule_id}")
|
|
314
|
+
return {"dry_run": True, "acl_rule_id": acl_rule_id}
|
|
315
|
+
|
|
316
|
+
await client.delete(f"/integration/v1/sites/{site_id}/acls/{acl_rule_id}")
|
|
317
|
+
|
|
318
|
+
# Audit the action
|
|
319
|
+
await audit_action(
|
|
320
|
+
settings,
|
|
321
|
+
action_type="delete_acl_rule",
|
|
322
|
+
resource_type="acl_rule",
|
|
323
|
+
resource_id=acl_rule_id,
|
|
324
|
+
site_id=site_id,
|
|
325
|
+
details={},
|
|
326
|
+
)
|
|
327
|
+
|
|
328
|
+
return {"success": True, "message": f"ACL rule {acl_rule_id} deleted successfully"}
|
src/tools/application.py
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"""Application information tools."""
|
|
2
|
+
|
|
3
|
+
from ..api.client import UniFiClient
|
|
4
|
+
from ..config import Settings
|
|
5
|
+
from ..utils import get_logger
|
|
6
|
+
|
|
7
|
+
logger = get_logger(__name__)
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
async def get_application_info(settings: Settings) -> dict:
|
|
11
|
+
"""Get UniFi Network application information.
|
|
12
|
+
|
|
13
|
+
Args:
|
|
14
|
+
settings: Application settings
|
|
15
|
+
|
|
16
|
+
Returns:
|
|
17
|
+
Application information dictionary
|
|
18
|
+
|
|
19
|
+
Example:
|
|
20
|
+
>>> info = await get_application_info(settings)
|
|
21
|
+
>>> print(info["version"])
|
|
22
|
+
"""
|
|
23
|
+
async with UniFiClient(settings) as client:
|
|
24
|
+
logger.info("Fetching application information")
|
|
25
|
+
|
|
26
|
+
# Authenticate if not already done
|
|
27
|
+
if not client.is_authenticated:
|
|
28
|
+
await client.authenticate()
|
|
29
|
+
|
|
30
|
+
# Get application info
|
|
31
|
+
response = await client.get("/integration/v1/application/info")
|
|
32
|
+
|
|
33
|
+
# Extract data from response
|
|
34
|
+
data = response.get("data", response)
|
|
35
|
+
|
|
36
|
+
return {
|
|
37
|
+
"version": data.get("version"),
|
|
38
|
+
"build": data.get("build"),
|
|
39
|
+
"deployment_type": data.get("deploymentType"),
|
|
40
|
+
"capabilities": data.get("capabilities", []),
|
|
41
|
+
"system_info": data.get("systemInfo", {}),
|
|
42
|
+
}
|