suprema-biostar-mcp 1.0.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.
- biostar_x_mcp_server/__init__.py +25 -0
- biostar_x_mcp_server/__main__.py +15 -0
- biostar_x_mcp_server/config.py +87 -0
- biostar_x_mcp_server/handlers/__init__.py +35 -0
- biostar_x_mcp_server/handlers/access_handler.py +2162 -0
- biostar_x_mcp_server/handlers/audit_handler.py +489 -0
- biostar_x_mcp_server/handlers/auth_handler.py +216 -0
- biostar_x_mcp_server/handlers/base_handler.py +228 -0
- biostar_x_mcp_server/handlers/card_handler.py +746 -0
- biostar_x_mcp_server/handlers/device_handler.py +4344 -0
- biostar_x_mcp_server/handlers/door_handler.py +3969 -0
- biostar_x_mcp_server/handlers/event_handler.py +1331 -0
- biostar_x_mcp_server/handlers/file_handler.py +212 -0
- biostar_x_mcp_server/handlers/help_web_handler.py +379 -0
- biostar_x_mcp_server/handlers/log_handler.py +1051 -0
- biostar_x_mcp_server/handlers/navigation_handler.py +109 -0
- biostar_x_mcp_server/handlers/occupancy_handler.py +541 -0
- biostar_x_mcp_server/handlers/user_handler.py +3568 -0
- biostar_x_mcp_server/schemas/__init__.py +21 -0
- biostar_x_mcp_server/schemas/access.py +158 -0
- biostar_x_mcp_server/schemas/audit.py +73 -0
- biostar_x_mcp_server/schemas/auth.py +24 -0
- biostar_x_mcp_server/schemas/cards.py +128 -0
- biostar_x_mcp_server/schemas/devices.py +496 -0
- biostar_x_mcp_server/schemas/doors.py +306 -0
- biostar_x_mcp_server/schemas/events.py +104 -0
- biostar_x_mcp_server/schemas/files.py +7 -0
- biostar_x_mcp_server/schemas/help.py +29 -0
- biostar_x_mcp_server/schemas/logs.py +33 -0
- biostar_x_mcp_server/schemas/occupancy.py +19 -0
- biostar_x_mcp_server/schemas/tool_response.py +29 -0
- biostar_x_mcp_server/schemas/users.py +166 -0
- biostar_x_mcp_server/server.py +335 -0
- biostar_x_mcp_server/session.py +221 -0
- biostar_x_mcp_server/tool_manager.py +172 -0
- biostar_x_mcp_server/tools/__init__.py +45 -0
- biostar_x_mcp_server/tools/access.py +510 -0
- biostar_x_mcp_server/tools/audit.py +227 -0
- biostar_x_mcp_server/tools/auth.py +59 -0
- biostar_x_mcp_server/tools/cards.py +269 -0
- biostar_x_mcp_server/tools/categories.py +197 -0
- biostar_x_mcp_server/tools/devices.py +1552 -0
- biostar_x_mcp_server/tools/doors.py +865 -0
- biostar_x_mcp_server/tools/events.py +305 -0
- biostar_x_mcp_server/tools/files.py +28 -0
- biostar_x_mcp_server/tools/help.py +80 -0
- biostar_x_mcp_server/tools/logs.py +123 -0
- biostar_x_mcp_server/tools/navigation.py +89 -0
- biostar_x_mcp_server/tools/occupancy.py +91 -0
- biostar_x_mcp_server/tools/users.py +1113 -0
- biostar_x_mcp_server/utils/__init__.py +31 -0
- biostar_x_mcp_server/utils/category_mapper.py +206 -0
- biostar_x_mcp_server/utils/decorators.py +101 -0
- biostar_x_mcp_server/utils/language_detector.py +51 -0
- biostar_x_mcp_server/utils/search.py +42 -0
- biostar_x_mcp_server/utils/timezone.py +122 -0
- suprema_biostar_mcp-1.0.1.dist-info/METADATA +163 -0
- suprema_biostar_mcp-1.0.1.dist-info/RECORD +61 -0
- suprema_biostar_mcp-1.0.1.dist-info/WHEEL +4 -0
- suprema_biostar_mcp-1.0.1.dist-info/entry_points.txt +2 -0
- suprema_biostar_mcp-1.0.1.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
"""
|
|
2
|
+
BioStar X Session Management
|
|
3
|
+
Handles API authentication and session lifecycle
|
|
4
|
+
"""
|
|
5
|
+
import asyncio
|
|
6
|
+
import logging
|
|
7
|
+
from datetime import datetime
|
|
8
|
+
from typing import Optional, Dict, Any
|
|
9
|
+
import httpx
|
|
10
|
+
from .config import Config
|
|
11
|
+
|
|
12
|
+
logger = logging.getLogger(__name__)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class BioStarSession:
|
|
16
|
+
"""Manages BioStar X API session and authentication."""
|
|
17
|
+
|
|
18
|
+
def __init__(self, config: Config):
|
|
19
|
+
self.config = config
|
|
20
|
+
self.client: Optional[httpx.AsyncClient] = None
|
|
21
|
+
self.session_id: Optional[str] = None
|
|
22
|
+
self.user_id: Optional[str] = None
|
|
23
|
+
self.last_refresh: Optional[datetime] = None
|
|
24
|
+
self.timezone_offset_minutes: Optional[int] = None
|
|
25
|
+
self._lock = asyncio.Lock()
|
|
26
|
+
|
|
27
|
+
async def __aenter__(self):
|
|
28
|
+
await self.connect()
|
|
29
|
+
return self
|
|
30
|
+
|
|
31
|
+
async def __aexit__(self, exc_type, exc_val, exc_tb):
|
|
32
|
+
await self.disconnect()
|
|
33
|
+
|
|
34
|
+
async def connect(self):
|
|
35
|
+
"""Initialize HTTP client."""
|
|
36
|
+
if not self.client:
|
|
37
|
+
logger.info(f"Connecting to BioStar server at: {self.config.biostar_url}")
|
|
38
|
+
logger.info(f"SSL verification: {self.config.verify_ssl}, Timeout: {self.config.api_timeout}s")
|
|
39
|
+
|
|
40
|
+
self.client = httpx.AsyncClient(
|
|
41
|
+
base_url=self.config.biostar_url,
|
|
42
|
+
timeout=httpx.Timeout(self.config.api_timeout),
|
|
43
|
+
verify=self.config.verify_ssl,
|
|
44
|
+
headers={
|
|
45
|
+
"Content-Type": "application/json",
|
|
46
|
+
"Accept": "application/json"
|
|
47
|
+
}
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
async def set_session_id_async(self, session_id: str):
|
|
51
|
+
"""Set session ID from external source (async version)."""
|
|
52
|
+
logger.info(f"Setting external session ID: {session_id}")
|
|
53
|
+
self.session_id = session_id
|
|
54
|
+
self.last_refresh = datetime.now()
|
|
55
|
+
|
|
56
|
+
if not self.client:
|
|
57
|
+
await self.connect()
|
|
58
|
+
logger.info("Client initialized for external session")
|
|
59
|
+
|
|
60
|
+
if self.client and self.session_id:
|
|
61
|
+
self.client.headers["bs-session-id"] = self.session_id
|
|
62
|
+
logger.info(f"Updated client headers with session ID: {session_id[:8]}...")
|
|
63
|
+
|
|
64
|
+
def set_session_id(self, session_id: str):
|
|
65
|
+
"""Set session ID from external source (sync version)."""
|
|
66
|
+
logger.info(f"Setting external session ID: {session_id}")
|
|
67
|
+
self.session_id = session_id
|
|
68
|
+
self.last_refresh = datetime.now()
|
|
69
|
+
|
|
70
|
+
if self.client and self.session_id:
|
|
71
|
+
self.client.headers["bs-session-id"] = self.session_id
|
|
72
|
+
logger.info("Updated client headers with session ID")
|
|
73
|
+
|
|
74
|
+
async def disconnect(self):
|
|
75
|
+
"""Close HTTP client and logout."""
|
|
76
|
+
if self.session_id:
|
|
77
|
+
try:
|
|
78
|
+
await self.logout()
|
|
79
|
+
except Exception as e:
|
|
80
|
+
logger.error(f"Error during logout: {e}")
|
|
81
|
+
|
|
82
|
+
if self.client:
|
|
83
|
+
await self.client.aclose()
|
|
84
|
+
self.client = None
|
|
85
|
+
|
|
86
|
+
async def login(self, username: Optional[str] = None, password: Optional[str] = None) -> Dict[str, Any]:
|
|
87
|
+
"""Login to BioStar X API."""
|
|
88
|
+
async with self._lock:
|
|
89
|
+
if not self.client:
|
|
90
|
+
await self.connect()
|
|
91
|
+
|
|
92
|
+
login_username = username or self.config.biostar_username
|
|
93
|
+
login_password = password or self.config.biostar_password
|
|
94
|
+
|
|
95
|
+
if not login_username or not login_password:
|
|
96
|
+
raise ValueError("Username and password are required. Set them in .env or provide as arguments.")
|
|
97
|
+
|
|
98
|
+
try:
|
|
99
|
+
response = await self.client.post(
|
|
100
|
+
"/api/login",
|
|
101
|
+
json={
|
|
102
|
+
"User": {
|
|
103
|
+
"login_id": login_username,
|
|
104
|
+
"password": login_password
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
)
|
|
108
|
+
response.raise_for_status()
|
|
109
|
+
|
|
110
|
+
data = response.json()
|
|
111
|
+
self.session_id = response.headers.get("bs-session-id")
|
|
112
|
+
self.user_id = data.get("User", {}).get("user_id")
|
|
113
|
+
self.last_refresh = datetime.now()
|
|
114
|
+
|
|
115
|
+
if self.session_id:
|
|
116
|
+
self.client.headers["bs-session-id"] = self.session_id
|
|
117
|
+
|
|
118
|
+
logger.info(f"Successfully logged in as user {self.user_id}")
|
|
119
|
+
return data
|
|
120
|
+
|
|
121
|
+
except httpx.HTTPStatusError as e:
|
|
122
|
+
logger.error(f"Login failed: {e.response.status_code} - {e.response.text}")
|
|
123
|
+
raise
|
|
124
|
+
except Exception as e:
|
|
125
|
+
logger.error(f"Login error: {e}")
|
|
126
|
+
raise
|
|
127
|
+
|
|
128
|
+
async def logout(self) -> None:
|
|
129
|
+
"""Logout from BioStar X API."""
|
|
130
|
+
if not self.session_id:
|
|
131
|
+
return
|
|
132
|
+
|
|
133
|
+
try:
|
|
134
|
+
await self.client.post("/api/logout")
|
|
135
|
+
logger.info("Successfully logged out")
|
|
136
|
+
except Exception as e:
|
|
137
|
+
logger.error(f"Logout error: {e}")
|
|
138
|
+
finally:
|
|
139
|
+
self.session_id = None
|
|
140
|
+
self.user_id = None
|
|
141
|
+
self.last_refresh = None
|
|
142
|
+
self.timezone_offset_minutes = None
|
|
143
|
+
if self.client:
|
|
144
|
+
self.client.headers.pop("bs-session-id", None)
|
|
145
|
+
|
|
146
|
+
async def refresh_session(self) -> None:
|
|
147
|
+
"""Refresh session if needed."""
|
|
148
|
+
if not self.session_id:
|
|
149
|
+
await self.login()
|
|
150
|
+
return
|
|
151
|
+
|
|
152
|
+
if self.last_refresh:
|
|
153
|
+
elapsed = datetime.now() - self.last_refresh
|
|
154
|
+
if elapsed.total_seconds() > self.config.session_refresh_interval:
|
|
155
|
+
logger.info("Session refresh needed")
|
|
156
|
+
await self.login()
|
|
157
|
+
|
|
158
|
+
async def request(
|
|
159
|
+
self,
|
|
160
|
+
method: str,
|
|
161
|
+
endpoint: str,
|
|
162
|
+
json: Optional[Dict[str, Any]] = None,
|
|
163
|
+
params: Optional[Dict[str, Any]] = None,
|
|
164
|
+
**kwargs
|
|
165
|
+
) -> httpx.Response:
|
|
166
|
+
"""Make authenticated request to BioStar X API."""
|
|
167
|
+
if not self.client:
|
|
168
|
+
await self.connect()
|
|
169
|
+
logger.info("Client initialized on first request")
|
|
170
|
+
|
|
171
|
+
if self.session_id:
|
|
172
|
+
self.client.headers["bs-session-id"] = self.session_id
|
|
173
|
+
logger.info(f"Session headers set on first request: {self.session_id[:8]}...")
|
|
174
|
+
|
|
175
|
+
await self.refresh_session()
|
|
176
|
+
|
|
177
|
+
try:
|
|
178
|
+
response = await self.client.request(
|
|
179
|
+
method=method,
|
|
180
|
+
url=endpoint,
|
|
181
|
+
json=json,
|
|
182
|
+
params=params,
|
|
183
|
+
**kwargs
|
|
184
|
+
)
|
|
185
|
+
response.raise_for_status()
|
|
186
|
+
return response
|
|
187
|
+
|
|
188
|
+
except httpx.HTTPStatusError as e:
|
|
189
|
+
if e.response.status_code == 401:
|
|
190
|
+
logger.info("Session expired, re-authenticating")
|
|
191
|
+
await self.login()
|
|
192
|
+
response = await self.client.request(
|
|
193
|
+
method=method,
|
|
194
|
+
url=endpoint,
|
|
195
|
+
json=json,
|
|
196
|
+
params=params,
|
|
197
|
+
**kwargs
|
|
198
|
+
)
|
|
199
|
+
response.raise_for_status()
|
|
200
|
+
return response
|
|
201
|
+
raise
|
|
202
|
+
|
|
203
|
+
async def get(self, endpoint: str, **kwargs) -> httpx.Response:
|
|
204
|
+
"""Make GET request."""
|
|
205
|
+
return await self.request("GET", endpoint, **kwargs)
|
|
206
|
+
|
|
207
|
+
async def post(self, endpoint: str, json: Dict[str, Any], **kwargs) -> httpx.Response:
|
|
208
|
+
"""Make POST request."""
|
|
209
|
+
return await self.request("POST", endpoint, json=json, **kwargs)
|
|
210
|
+
|
|
211
|
+
async def put(self, endpoint: str, json: Dict[str, Any], **kwargs) -> httpx.Response:
|
|
212
|
+
"""Make PUT request."""
|
|
213
|
+
return await self.request("PUT", endpoint, json=json, **kwargs)
|
|
214
|
+
|
|
215
|
+
async def delete(self, endpoint: str, **kwargs) -> httpx.Response:
|
|
216
|
+
"""Make DELETE request."""
|
|
217
|
+
return await self.request("DELETE", endpoint, **kwargs)
|
|
218
|
+
|
|
219
|
+
def is_authenticated(self) -> bool:
|
|
220
|
+
"""Check if session is authenticated."""
|
|
221
|
+
return self.session_id is not None
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
"""
|
|
2
|
+
BioStar X MCP Server Tool Manager
|
|
3
|
+
Dynamic loading and management of tool categories
|
|
4
|
+
"""
|
|
5
|
+
import logging
|
|
6
|
+
from typing import List, Set, Optional, Dict
|
|
7
|
+
from mcp.types import Tool
|
|
8
|
+
|
|
9
|
+
from .tools.categories import (
|
|
10
|
+
get_tools_for_category,
|
|
11
|
+
get_category_for_tool,
|
|
12
|
+
get_all_categories,
|
|
13
|
+
CATEGORY_DESCRIPTIONS
|
|
14
|
+
)
|
|
15
|
+
from .tools.auth import AUTH_TOOLS
|
|
16
|
+
from .tools.users import USER_TOOLS
|
|
17
|
+
from .tools.events import EVENT_TOOLS
|
|
18
|
+
from .tools.doors import DOOR_TOOLS
|
|
19
|
+
from .tools.devices import DEVICE_TOOLS
|
|
20
|
+
from .tools.access import ACCESS_TOOLS
|
|
21
|
+
from .tools.audit import AUDIT_TOOLS
|
|
22
|
+
from .tools.cards import CARD_TOOLS
|
|
23
|
+
from .tools.navigation import NAVIGATION_TOOLS
|
|
24
|
+
from .tools.occupancy import OCCUPANCY_TOOLS
|
|
25
|
+
from .tools.files import FILE_TOOLS
|
|
26
|
+
from .tools.logs import LOG_TOOLS
|
|
27
|
+
from .tools.help import HELP_TOOLS
|
|
28
|
+
|
|
29
|
+
logger = logging.getLogger(__name__)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class ToolManager:
|
|
33
|
+
"""Manages dynamic loading and unloading of tool categories."""
|
|
34
|
+
|
|
35
|
+
def __init__(self, max_categories: int = 15):
|
|
36
|
+
self.max_categories = max_categories
|
|
37
|
+
self.active_categories: Set[str] = set()
|
|
38
|
+
self.loaded_tools: Dict[str, List[Tool]] = {}
|
|
39
|
+
self.usage_count: Dict[str, int] = {}
|
|
40
|
+
|
|
41
|
+
def load_category(self, category: str) -> bool:
|
|
42
|
+
"""Load tools for a specific category."""
|
|
43
|
+
logger.debug(f"Attempting to load category: {category}")
|
|
44
|
+
|
|
45
|
+
if category in self.active_categories:
|
|
46
|
+
logger.debug(f"Category {category} already loaded")
|
|
47
|
+
return True
|
|
48
|
+
|
|
49
|
+
if category not in get_all_categories():
|
|
50
|
+
logger.warning(f"Unknown category: {category}")
|
|
51
|
+
return False
|
|
52
|
+
|
|
53
|
+
# Check if we need to unload a category
|
|
54
|
+
if len(self.active_categories) >= self.max_categories:
|
|
55
|
+
logger.debug(f"Max categories reached ({self.max_categories}), unloading least used")
|
|
56
|
+
self._unload_least_used_category()
|
|
57
|
+
|
|
58
|
+
# Load the tools for this category
|
|
59
|
+
tools = self._get_tools_for_category(category)
|
|
60
|
+
logger.debug(f"Retrieved {len(tools)} tools for category: {category}")
|
|
61
|
+
|
|
62
|
+
if tools:
|
|
63
|
+
self.loaded_tools[category] = tools
|
|
64
|
+
self.active_categories.add(category)
|
|
65
|
+
self.usage_count[category] = 0
|
|
66
|
+
logger.info(f"Loaded category: {category} with {len(tools)} tools")
|
|
67
|
+
return True
|
|
68
|
+
else:
|
|
69
|
+
logger.error(f"No tools found for category: {category}")
|
|
70
|
+
return False
|
|
71
|
+
|
|
72
|
+
def unload_category(self, category: str) -> bool:
|
|
73
|
+
"""Unload tools for a specific category."""
|
|
74
|
+
if category not in self.active_categories:
|
|
75
|
+
return False
|
|
76
|
+
|
|
77
|
+
self.active_categories.remove(category)
|
|
78
|
+
self.loaded_tools.pop(category, None)
|
|
79
|
+
self.usage_count.pop(category, None)
|
|
80
|
+
logger.info(f"Unloaded category: {category}")
|
|
81
|
+
return True
|
|
82
|
+
|
|
83
|
+
def get_active_tools(self) -> List[Tool]:
|
|
84
|
+
"""Get all currently loaded tools."""
|
|
85
|
+
tools = []
|
|
86
|
+
seen_names = set()
|
|
87
|
+
|
|
88
|
+
for category in self.active_categories:
|
|
89
|
+
category_tools = self.loaded_tools.get(category, [])
|
|
90
|
+
for tool in category_tools:
|
|
91
|
+
tool_name = tool.name
|
|
92
|
+
if tool_name not in seen_names:
|
|
93
|
+
seen_names.add(tool_name)
|
|
94
|
+
tools.append(tool)
|
|
95
|
+
else:
|
|
96
|
+
logger.warning(f"Duplicate tool name found: {tool_name} from category {category}, skipping")
|
|
97
|
+
|
|
98
|
+
logger.info(f"Returning {len(tools)} unique tools from {len(self.active_categories)} categories")
|
|
99
|
+
return tools
|
|
100
|
+
|
|
101
|
+
def get_tool_category(self, tool_name: str) -> Optional[str]:
|
|
102
|
+
"""Get the category a tool belongs to."""
|
|
103
|
+
return get_category_for_tool(tool_name)
|
|
104
|
+
|
|
105
|
+
def record_tool_usage(self, tool_name: str) -> None:
|
|
106
|
+
"""Record that a tool was used."""
|
|
107
|
+
category = self.get_tool_category(tool_name)
|
|
108
|
+
if category and category in self.usage_count:
|
|
109
|
+
self.usage_count[category] += 1
|
|
110
|
+
|
|
111
|
+
def _unload_least_used_category(self) -> None:
|
|
112
|
+
"""Unload the least recently used category."""
|
|
113
|
+
if not self.active_categories:
|
|
114
|
+
return
|
|
115
|
+
|
|
116
|
+
# Never unload auth category
|
|
117
|
+
candidates = [c for c in self.active_categories if c != "auth"]
|
|
118
|
+
if not candidates:
|
|
119
|
+
return
|
|
120
|
+
|
|
121
|
+
# Find least used category
|
|
122
|
+
least_used = min(candidates, key=lambda c: self.usage_count.get(c, 0))
|
|
123
|
+
self.unload_category(least_used)
|
|
124
|
+
|
|
125
|
+
# Category to tools mapping (without vector_solis)
|
|
126
|
+
_CATEGORY_TOOLS_MAP = {
|
|
127
|
+
"auth": AUTH_TOOLS,
|
|
128
|
+
"users": USER_TOOLS,
|
|
129
|
+
"events": EVENT_TOOLS,
|
|
130
|
+
"doors": DOOR_TOOLS,
|
|
131
|
+
"access": ACCESS_TOOLS,
|
|
132
|
+
"devices": DEVICE_TOOLS,
|
|
133
|
+
"audit": AUDIT_TOOLS,
|
|
134
|
+
"cards": CARD_TOOLS,
|
|
135
|
+
"navigation": NAVIGATION_TOOLS,
|
|
136
|
+
"occupancy": OCCUPANCY_TOOLS,
|
|
137
|
+
"files": FILE_TOOLS,
|
|
138
|
+
"logs": LOG_TOOLS,
|
|
139
|
+
"help": HELP_TOOLS,
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
def _get_tools_for_category(self, category: str) -> List[Tool]:
|
|
143
|
+
"""Get tool definitions for a category."""
|
|
144
|
+
logger.debug(f"Getting tools for category: {category}")
|
|
145
|
+
|
|
146
|
+
tools = self._CATEGORY_TOOLS_MAP.get(category)
|
|
147
|
+
|
|
148
|
+
if tools is None:
|
|
149
|
+
logger.warning(f"Unknown category: {category}")
|
|
150
|
+
return []
|
|
151
|
+
|
|
152
|
+
return tools
|
|
153
|
+
|
|
154
|
+
def get_category_info(self) -> Dict[str, Dict[str, any]]:
|
|
155
|
+
"""Get information about all categories."""
|
|
156
|
+
info = {}
|
|
157
|
+
for category in get_all_categories():
|
|
158
|
+
info[category] = {
|
|
159
|
+
"description": CATEGORY_DESCRIPTIONS.get(category, ""),
|
|
160
|
+
"tools": get_tools_for_category(category),
|
|
161
|
+
"loaded": category in self.active_categories,
|
|
162
|
+
"usage_count": self.usage_count.get(category, 0)
|
|
163
|
+
}
|
|
164
|
+
return info
|
|
165
|
+
|
|
166
|
+
def load_all_categories(self) -> int:
|
|
167
|
+
"""Load all available categories. Returns number of categories loaded."""
|
|
168
|
+
count = 0
|
|
169
|
+
for category in get_all_categories():
|
|
170
|
+
if self.load_category(category):
|
|
171
|
+
count += 1
|
|
172
|
+
return count
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
"""
|
|
2
|
+
BioStar X MCP Server - Tool Definitions
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from .auth import AUTH_TOOLS
|
|
6
|
+
from .users import USER_TOOLS
|
|
7
|
+
from .doors import DOOR_TOOLS
|
|
8
|
+
from .access import ACCESS_TOOLS
|
|
9
|
+
from .devices import DEVICE_TOOLS
|
|
10
|
+
from .events import EVENT_TOOLS
|
|
11
|
+
from .audit import AUDIT_TOOLS
|
|
12
|
+
from .cards import CARD_TOOLS
|
|
13
|
+
from .navigation import NAVIGATION_TOOLS
|
|
14
|
+
from .occupancy import OCCUPANCY_TOOLS
|
|
15
|
+
from .files import FILE_TOOLS
|
|
16
|
+
from .logs import LOG_TOOLS
|
|
17
|
+
from .categories import (
|
|
18
|
+
TOOL_CATEGORIES,
|
|
19
|
+
CATEGORY_DESCRIPTIONS,
|
|
20
|
+
get_tools_for_category,
|
|
21
|
+
get_category_for_tool,
|
|
22
|
+
get_all_categories
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
__all__ = [
|
|
26
|
+
# Tool lists
|
|
27
|
+
"AUTH_TOOLS",
|
|
28
|
+
"USER_TOOLS",
|
|
29
|
+
"DOOR_TOOLS",
|
|
30
|
+
"ACCESS_TOOLS",
|
|
31
|
+
"DEVICE_TOOLS",
|
|
32
|
+
"EVENT_TOOLS",
|
|
33
|
+
"AUDIT_TOOLS",
|
|
34
|
+
"CARD_TOOLS",
|
|
35
|
+
"NAVIGATION_TOOLS",
|
|
36
|
+
"OCCUPANCY_TOOLS",
|
|
37
|
+
"FILE_TOOLS",
|
|
38
|
+
"LOG_TOOLS",
|
|
39
|
+
# Categories
|
|
40
|
+
"TOOL_CATEGORIES",
|
|
41
|
+
"CATEGORY_DESCRIPTIONS",
|
|
42
|
+
"get_tools_for_category",
|
|
43
|
+
"get_category_for_tool",
|
|
44
|
+
"get_all_categories",
|
|
45
|
+
]
|