lockbot 2.6.2__tar.gz → 2.7.0__tar.gz
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.
- {lockbot-2.6.2/python/lockbot.egg-info → lockbot-2.7.0}/PKG-INFO +1 -1
- {lockbot-2.6.2 → lockbot-2.7.0}/pyproject.toml +1 -1
- {lockbot-2.6.2 → lockbot-2.7.0}/python/lockbot/backend/app/bots/router.py +36 -5
- {lockbot-2.6.2 → lockbot-2.7.0}/python/lockbot/backend/app/bots/schemas.py +27 -1
- {lockbot-2.6.2 → lockbot-2.7.0/python/lockbot.egg-info}/PKG-INFO +1 -1
- {lockbot-2.6.2 → lockbot-2.7.0}/LICENSE +0 -0
- {lockbot-2.6.2 → lockbot-2.7.0}/MANIFEST.in +0 -0
- {lockbot-2.6.2 → lockbot-2.7.0}/README.md +0 -0
- {lockbot-2.6.2 → lockbot-2.7.0}/python/lockbot/__init__.py +0 -0
- {lockbot-2.6.2 → lockbot-2.7.0}/python/lockbot/backend/__init__.py +0 -0
- {lockbot-2.6.2 → lockbot-2.7.0}/python/lockbot/backend/app/__init__.py +0 -0
- {lockbot-2.6.2 → lockbot-2.7.0}/python/lockbot/backend/app/admin/__init__.py +0 -0
- {lockbot-2.6.2 → lockbot-2.7.0}/python/lockbot/backend/app/admin/router.py +0 -0
- {lockbot-2.6.2 → lockbot-2.7.0}/python/lockbot/backend/app/audit/__init__.py +0 -0
- {lockbot-2.6.2 → lockbot-2.7.0}/python/lockbot/backend/app/audit/models.py +0 -0
- {lockbot-2.6.2 → lockbot-2.7.0}/python/lockbot/backend/app/audit/router.py +0 -0
- {lockbot-2.6.2 → lockbot-2.7.0}/python/lockbot/backend/app/audit/service.py +0 -0
- {lockbot-2.6.2 → lockbot-2.7.0}/python/lockbot/backend/app/auth/__init__.py +0 -0
- {lockbot-2.6.2 → lockbot-2.7.0}/python/lockbot/backend/app/auth/dependencies.py +0 -0
- {lockbot-2.6.2 → lockbot-2.7.0}/python/lockbot/backend/app/auth/models.py +0 -0
- {lockbot-2.6.2 → lockbot-2.7.0}/python/lockbot/backend/app/auth/router.py +0 -0
- {lockbot-2.6.2 → lockbot-2.7.0}/python/lockbot/backend/app/auth/schemas.py +0 -0
- {lockbot-2.6.2 → lockbot-2.7.0}/python/lockbot/backend/app/backup/__init__.py +0 -0
- {lockbot-2.6.2 → lockbot-2.7.0}/python/lockbot/backend/app/backup/router.py +0 -0
- {lockbot-2.6.2 → lockbot-2.7.0}/python/lockbot/backend/app/backup/scheduler.py +0 -0
- {lockbot-2.6.2 → lockbot-2.7.0}/python/lockbot/backend/app/backup/service.py +0 -0
- {lockbot-2.6.2 → lockbot-2.7.0}/python/lockbot/backend/app/bots/__init__.py +0 -0
- {lockbot-2.6.2 → lockbot-2.7.0}/python/lockbot/backend/app/bots/encryption.py +0 -0
- {lockbot-2.6.2 → lockbot-2.7.0}/python/lockbot/backend/app/bots/manager.py +0 -0
- {lockbot-2.6.2 → lockbot-2.7.0}/python/lockbot/backend/app/bots/models.py +0 -0
- {lockbot-2.6.2 → lockbot-2.7.0}/python/lockbot/backend/app/bots/webhook_handler.py +0 -0
- {lockbot-2.6.2 → lockbot-2.7.0}/python/lockbot/backend/app/config.py +0 -0
- {lockbot-2.6.2 → lockbot-2.7.0}/python/lockbot/backend/app/database.py +0 -0
- {lockbot-2.6.2 → lockbot-2.7.0}/python/lockbot/backend/app/logs/__init__.py +0 -0
- {lockbot-2.6.2 → lockbot-2.7.0}/python/lockbot/backend/app/main.py +0 -0
- {lockbot-2.6.2 → lockbot-2.7.0}/python/lockbot/backend/app/rate_limit.py +0 -0
- {lockbot-2.6.2 → lockbot-2.7.0}/python/lockbot/backend/app/settings/__init__.py +0 -0
- {lockbot-2.6.2 → lockbot-2.7.0}/python/lockbot/backend/app/settings/models.py +0 -0
- {lockbot-2.6.2 → lockbot-2.7.0}/python/lockbot/backend/app/settings/router.py +0 -0
- {lockbot-2.6.2 → lockbot-2.7.0}/python/lockbot/core/__init__.py +0 -0
- {lockbot-2.6.2 → lockbot-2.7.0}/python/lockbot/core/base_bot.py +0 -0
- {lockbot-2.6.2 → lockbot-2.7.0}/python/lockbot/core/bot_instance.py +0 -0
- {lockbot-2.6.2 → lockbot-2.7.0}/python/lockbot/core/config.py +0 -0
- {lockbot-2.6.2 → lockbot-2.7.0}/python/lockbot/core/device_bot.py +0 -0
- {lockbot-2.6.2 → lockbot-2.7.0}/python/lockbot/core/device_usage_alert.py +0 -0
- {lockbot-2.6.2 → lockbot-2.7.0}/python/lockbot/core/device_usage_utils.py +0 -0
- {lockbot-2.6.2 → lockbot-2.7.0}/python/lockbot/core/entry.py +0 -0
- {lockbot-2.6.2 → lockbot-2.7.0}/python/lockbot/core/env.py +0 -0
- {lockbot-2.6.2 → lockbot-2.7.0}/python/lockbot/core/handler.py +0 -0
- {lockbot-2.6.2 → lockbot-2.7.0}/python/lockbot/core/i18n/__init__.py +0 -0
- {lockbot-2.6.2 → lockbot-2.7.0}/python/lockbot/core/i18n/en.py +0 -0
- {lockbot-2.6.2 → lockbot-2.7.0}/python/lockbot/core/i18n/zh.py +0 -0
- {lockbot-2.6.2 → lockbot-2.7.0}/python/lockbot/core/io.py +0 -0
- {lockbot-2.6.2 → lockbot-2.7.0}/python/lockbot/core/message_adapter.py +0 -0
- {lockbot-2.6.2 → lockbot-2.7.0}/python/lockbot/core/msg_utils.py +0 -0
- {lockbot-2.6.2 → lockbot-2.7.0}/python/lockbot/core/node_bot.py +0 -0
- {lockbot-2.6.2 → lockbot-2.7.0}/python/lockbot/core/platforms/__init__.py +0 -0
- {lockbot-2.6.2 → lockbot-2.7.0}/python/lockbot/core/platforms/infoflow.py +0 -0
- {lockbot-2.6.2 → lockbot-2.7.0}/python/lockbot/core/queue_bot.py +0 -0
- {lockbot-2.6.2 → lockbot-2.7.0}/python/lockbot/core/request.py +0 -0
- {lockbot-2.6.2 → lockbot-2.7.0}/python/lockbot/core/scheduler.py +0 -0
- {lockbot-2.6.2 → lockbot-2.7.0}/python/lockbot/core/utils.py +0 -0
- {lockbot-2.6.2 → lockbot-2.7.0}/python/lockbot.egg-info/SOURCES.txt +0 -0
- {lockbot-2.6.2 → lockbot-2.7.0}/python/lockbot.egg-info/dependency_links.txt +0 -0
- {lockbot-2.6.2 → lockbot-2.7.0}/python/lockbot.egg-info/requires.txt +0 -0
- {lockbot-2.6.2 → lockbot-2.7.0}/python/lockbot.egg-info/top_level.txt +0 -0
- {lockbot-2.6.2 → lockbot-2.7.0}/setup.cfg +0 -0
- {lockbot-2.6.2 → lockbot-2.7.0}/tools/create_super_admin.py +0 -0
- {lockbot-2.6.2 → lockbot-2.7.0}/tools/gen_keys.py +0 -0
- {lockbot-2.6.2 → lockbot-2.7.0}/tools/reset_super_admin_password.py +0 -0
|
@@ -127,6 +127,28 @@ def _normalize_cluster_configs(cc):
|
|
|
127
127
|
return cc
|
|
128
128
|
|
|
129
129
|
|
|
130
|
+
def _device_dict_to_array(d: dict) -> list:
|
|
131
|
+
"""Convert DEVICE cluster_configs from dict to ordered array for API response.
|
|
132
|
+
|
|
133
|
+
Why: JS JSON.parse auto-sorts pure-numeric keys ("0","1","2"), losing user-defined
|
|
134
|
+
node ordering. Array format preserves insertion order in all environments.
|
|
135
|
+
"""
|
|
136
|
+
return [{"node_key": k, "devices": v} for k, v in d.items()]
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def _device_array_to_dict(arr: list) -> dict:
|
|
140
|
+
"""Convert DEVICE cluster_configs from ordered array to dict for DB storage.
|
|
141
|
+
|
|
142
|
+
DB continues to store dict format; conversion happens at API boundary only.
|
|
143
|
+
"""
|
|
144
|
+
return {item["node_key"]: item["devices"] for item in arr}
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def _is_device_array_format(cc) -> bool:
|
|
148
|
+
"""Check if cluster_configs is in the new array-of-dicts DEVICE format."""
|
|
149
|
+
return isinstance(cc, list) and bool(cc) and isinstance(cc[0], dict) and "node_key" in cc[0]
|
|
150
|
+
|
|
151
|
+
|
|
130
152
|
def _build_config_dict(bot: Bot, db: Session | None = None) -> dict:
|
|
131
153
|
"""
|
|
132
154
|
Build the full config dict from a DB Bot record.
|
|
@@ -213,7 +235,12 @@ def create_bot(
|
|
|
213
235
|
webhook_url=encryption.encrypt(body.webhook_url),
|
|
214
236
|
aes_key=encryption.encrypt(body.aes_key),
|
|
215
237
|
token=encryption.encrypt(body.token),
|
|
216
|
-
cluster_configs=json.dumps(
|
|
238
|
+
cluster_configs=json.dumps(
|
|
239
|
+
_device_array_to_dict(body.cluster_configs)
|
|
240
|
+
if _is_device_array_format(body.cluster_configs)
|
|
241
|
+
else body.cluster_configs,
|
|
242
|
+
ensure_ascii=False,
|
|
243
|
+
),
|
|
217
244
|
config_overrides=json.dumps(body.config_overrides or {}, ensure_ascii=False),
|
|
218
245
|
)
|
|
219
246
|
db.add(bot)
|
|
@@ -358,16 +385,20 @@ def update_bot(
|
|
|
358
385
|
changes["token"] = "updated"
|
|
359
386
|
bot.token = encryption.encrypt(body.token)
|
|
360
387
|
if body.cluster_configs is not None:
|
|
361
|
-
|
|
388
|
+
# Convert array format to dict for storage
|
|
389
|
+
cc_for_storage = body.cluster_configs
|
|
390
|
+
if _is_device_array_format(cc_for_storage):
|
|
391
|
+
cc_for_storage = _device_array_to_dict(cc_for_storage)
|
|
392
|
+
new_json = json.dumps(cc_for_storage, ensure_ascii=False)
|
|
362
393
|
if new_json != (bot.cluster_configs or ""):
|
|
363
394
|
old_configs = json.loads(bot.cluster_configs) if bot.cluster_configs else None
|
|
364
|
-
if isinstance(
|
|
395
|
+
if isinstance(cc_for_storage, dict):
|
|
365
396
|
# DEVICE bot: {node: [devices...]} → summary with device counts
|
|
366
|
-
node_summary = {k: len(v) if isinstance(v, list) else v for k, v in
|
|
397
|
+
node_summary = {k: len(v) if isinstance(v, list) else v for k, v in cc_for_storage.items()}
|
|
367
398
|
changes["cluster_configs"] = node_summary
|
|
368
399
|
else:
|
|
369
400
|
# NODE bot: list of node names → show from/to
|
|
370
|
-
changes["cluster_configs"] = {"from": old_configs, "to":
|
|
401
|
+
changes["cluster_configs"] = {"from": old_configs, "to": cc_for_storage}
|
|
371
402
|
bot.cluster_configs = new_json
|
|
372
403
|
if body.config_overrides is not None:
|
|
373
404
|
old_overrides = json.loads(bot.config_overrides) if bot.config_overrides else {}
|
|
@@ -2,9 +2,10 @@
|
|
|
2
2
|
Bot Pydantic schemas
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
|
+
import json
|
|
5
6
|
from datetime import datetime
|
|
6
7
|
|
|
7
|
-
from pydantic import BaseModel, field_validator
|
|
8
|
+
from pydantic import BaseModel, field_validator, model_validator
|
|
8
9
|
|
|
9
10
|
# ── config_overrides value bounds ─────────────────────────────────────────────
|
|
10
11
|
_CFG_RULES: dict[str, tuple[int, int] | None] = {
|
|
@@ -15,6 +16,24 @@ _CFG_RULES: dict[str, tuple[int, int] | None] = {
|
|
|
15
16
|
}
|
|
16
17
|
|
|
17
18
|
|
|
19
|
+
def _serialize_device_configs(cluster_configs_json: str) -> str:
|
|
20
|
+
"""Convert DEVICE cluster_configs from stored dict JSON to ordered array JSON.
|
|
21
|
+
|
|
22
|
+
DB stores: '{"2": ["a800"], "0": ["h20"]}'
|
|
23
|
+
API returns: '[{"node_key": "2", "devices": ["a800"]}, {"node_key": "0", "devices": ["h20"]}]'
|
|
24
|
+
|
|
25
|
+
This ensures JS frontend receives an array that preserves insertion order,
|
|
26
|
+
avoiding the JS engine's automatic sorting of numeric object keys.
|
|
27
|
+
"""
|
|
28
|
+
try:
|
|
29
|
+
cc = json.loads(cluster_configs_json)
|
|
30
|
+
if isinstance(cc, dict):
|
|
31
|
+
return json.dumps([{"node_key": k, "devices": v} for k, v in cc.items()], ensure_ascii=False)
|
|
32
|
+
except (json.JSONDecodeError, TypeError):
|
|
33
|
+
pass
|
|
34
|
+
return cluster_configs_json
|
|
35
|
+
|
|
36
|
+
|
|
18
37
|
def _validate_config_overrides(v: dict | None) -> dict | None:
|
|
19
38
|
if not v:
|
|
20
39
|
return v
|
|
@@ -93,6 +112,13 @@ class BotOut(BaseModel):
|
|
|
93
112
|
|
|
94
113
|
model_config = {"from_attributes": True}
|
|
95
114
|
|
|
115
|
+
@model_validator(mode="after")
|
|
116
|
+
def convert_device_cluster_configs(self):
|
|
117
|
+
"""For DEVICE bots, convert stored dict JSON to array JSON for frontend."""
|
|
118
|
+
if self.bot_type == "DEVICE" and self.cluster_configs:
|
|
119
|
+
self.cluster_configs = _serialize_device_configs(self.cluster_configs)
|
|
120
|
+
return self
|
|
121
|
+
|
|
96
122
|
|
|
97
123
|
class BotDetail(BotOut):
|
|
98
124
|
"""Detail view with sensitive fields (both masked and raw for show/copy)."""
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|