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.
Files changed (70) hide show
  1. {lockbot-2.6.2/python/lockbot.egg-info → lockbot-2.7.0}/PKG-INFO +1 -1
  2. {lockbot-2.6.2 → lockbot-2.7.0}/pyproject.toml +1 -1
  3. {lockbot-2.6.2 → lockbot-2.7.0}/python/lockbot/backend/app/bots/router.py +36 -5
  4. {lockbot-2.6.2 → lockbot-2.7.0}/python/lockbot/backend/app/bots/schemas.py +27 -1
  5. {lockbot-2.6.2 → lockbot-2.7.0/python/lockbot.egg-info}/PKG-INFO +1 -1
  6. {lockbot-2.6.2 → lockbot-2.7.0}/LICENSE +0 -0
  7. {lockbot-2.6.2 → lockbot-2.7.0}/MANIFEST.in +0 -0
  8. {lockbot-2.6.2 → lockbot-2.7.0}/README.md +0 -0
  9. {lockbot-2.6.2 → lockbot-2.7.0}/python/lockbot/__init__.py +0 -0
  10. {lockbot-2.6.2 → lockbot-2.7.0}/python/lockbot/backend/__init__.py +0 -0
  11. {lockbot-2.6.2 → lockbot-2.7.0}/python/lockbot/backend/app/__init__.py +0 -0
  12. {lockbot-2.6.2 → lockbot-2.7.0}/python/lockbot/backend/app/admin/__init__.py +0 -0
  13. {lockbot-2.6.2 → lockbot-2.7.0}/python/lockbot/backend/app/admin/router.py +0 -0
  14. {lockbot-2.6.2 → lockbot-2.7.0}/python/lockbot/backend/app/audit/__init__.py +0 -0
  15. {lockbot-2.6.2 → lockbot-2.7.0}/python/lockbot/backend/app/audit/models.py +0 -0
  16. {lockbot-2.6.2 → lockbot-2.7.0}/python/lockbot/backend/app/audit/router.py +0 -0
  17. {lockbot-2.6.2 → lockbot-2.7.0}/python/lockbot/backend/app/audit/service.py +0 -0
  18. {lockbot-2.6.2 → lockbot-2.7.0}/python/lockbot/backend/app/auth/__init__.py +0 -0
  19. {lockbot-2.6.2 → lockbot-2.7.0}/python/lockbot/backend/app/auth/dependencies.py +0 -0
  20. {lockbot-2.6.2 → lockbot-2.7.0}/python/lockbot/backend/app/auth/models.py +0 -0
  21. {lockbot-2.6.2 → lockbot-2.7.0}/python/lockbot/backend/app/auth/router.py +0 -0
  22. {lockbot-2.6.2 → lockbot-2.7.0}/python/lockbot/backend/app/auth/schemas.py +0 -0
  23. {lockbot-2.6.2 → lockbot-2.7.0}/python/lockbot/backend/app/backup/__init__.py +0 -0
  24. {lockbot-2.6.2 → lockbot-2.7.0}/python/lockbot/backend/app/backup/router.py +0 -0
  25. {lockbot-2.6.2 → lockbot-2.7.0}/python/lockbot/backend/app/backup/scheduler.py +0 -0
  26. {lockbot-2.6.2 → lockbot-2.7.0}/python/lockbot/backend/app/backup/service.py +0 -0
  27. {lockbot-2.6.2 → lockbot-2.7.0}/python/lockbot/backend/app/bots/__init__.py +0 -0
  28. {lockbot-2.6.2 → lockbot-2.7.0}/python/lockbot/backend/app/bots/encryption.py +0 -0
  29. {lockbot-2.6.2 → lockbot-2.7.0}/python/lockbot/backend/app/bots/manager.py +0 -0
  30. {lockbot-2.6.2 → lockbot-2.7.0}/python/lockbot/backend/app/bots/models.py +0 -0
  31. {lockbot-2.6.2 → lockbot-2.7.0}/python/lockbot/backend/app/bots/webhook_handler.py +0 -0
  32. {lockbot-2.6.2 → lockbot-2.7.0}/python/lockbot/backend/app/config.py +0 -0
  33. {lockbot-2.6.2 → lockbot-2.7.0}/python/lockbot/backend/app/database.py +0 -0
  34. {lockbot-2.6.2 → lockbot-2.7.0}/python/lockbot/backend/app/logs/__init__.py +0 -0
  35. {lockbot-2.6.2 → lockbot-2.7.0}/python/lockbot/backend/app/main.py +0 -0
  36. {lockbot-2.6.2 → lockbot-2.7.0}/python/lockbot/backend/app/rate_limit.py +0 -0
  37. {lockbot-2.6.2 → lockbot-2.7.0}/python/lockbot/backend/app/settings/__init__.py +0 -0
  38. {lockbot-2.6.2 → lockbot-2.7.0}/python/lockbot/backend/app/settings/models.py +0 -0
  39. {lockbot-2.6.2 → lockbot-2.7.0}/python/lockbot/backend/app/settings/router.py +0 -0
  40. {lockbot-2.6.2 → lockbot-2.7.0}/python/lockbot/core/__init__.py +0 -0
  41. {lockbot-2.6.2 → lockbot-2.7.0}/python/lockbot/core/base_bot.py +0 -0
  42. {lockbot-2.6.2 → lockbot-2.7.0}/python/lockbot/core/bot_instance.py +0 -0
  43. {lockbot-2.6.2 → lockbot-2.7.0}/python/lockbot/core/config.py +0 -0
  44. {lockbot-2.6.2 → lockbot-2.7.0}/python/lockbot/core/device_bot.py +0 -0
  45. {lockbot-2.6.2 → lockbot-2.7.0}/python/lockbot/core/device_usage_alert.py +0 -0
  46. {lockbot-2.6.2 → lockbot-2.7.0}/python/lockbot/core/device_usage_utils.py +0 -0
  47. {lockbot-2.6.2 → lockbot-2.7.0}/python/lockbot/core/entry.py +0 -0
  48. {lockbot-2.6.2 → lockbot-2.7.0}/python/lockbot/core/env.py +0 -0
  49. {lockbot-2.6.2 → lockbot-2.7.0}/python/lockbot/core/handler.py +0 -0
  50. {lockbot-2.6.2 → lockbot-2.7.0}/python/lockbot/core/i18n/__init__.py +0 -0
  51. {lockbot-2.6.2 → lockbot-2.7.0}/python/lockbot/core/i18n/en.py +0 -0
  52. {lockbot-2.6.2 → lockbot-2.7.0}/python/lockbot/core/i18n/zh.py +0 -0
  53. {lockbot-2.6.2 → lockbot-2.7.0}/python/lockbot/core/io.py +0 -0
  54. {lockbot-2.6.2 → lockbot-2.7.0}/python/lockbot/core/message_adapter.py +0 -0
  55. {lockbot-2.6.2 → lockbot-2.7.0}/python/lockbot/core/msg_utils.py +0 -0
  56. {lockbot-2.6.2 → lockbot-2.7.0}/python/lockbot/core/node_bot.py +0 -0
  57. {lockbot-2.6.2 → lockbot-2.7.0}/python/lockbot/core/platforms/__init__.py +0 -0
  58. {lockbot-2.6.2 → lockbot-2.7.0}/python/lockbot/core/platforms/infoflow.py +0 -0
  59. {lockbot-2.6.2 → lockbot-2.7.0}/python/lockbot/core/queue_bot.py +0 -0
  60. {lockbot-2.6.2 → lockbot-2.7.0}/python/lockbot/core/request.py +0 -0
  61. {lockbot-2.6.2 → lockbot-2.7.0}/python/lockbot/core/scheduler.py +0 -0
  62. {lockbot-2.6.2 → lockbot-2.7.0}/python/lockbot/core/utils.py +0 -0
  63. {lockbot-2.6.2 → lockbot-2.7.0}/python/lockbot.egg-info/SOURCES.txt +0 -0
  64. {lockbot-2.6.2 → lockbot-2.7.0}/python/lockbot.egg-info/dependency_links.txt +0 -0
  65. {lockbot-2.6.2 → lockbot-2.7.0}/python/lockbot.egg-info/requires.txt +0 -0
  66. {lockbot-2.6.2 → lockbot-2.7.0}/python/lockbot.egg-info/top_level.txt +0 -0
  67. {lockbot-2.6.2 → lockbot-2.7.0}/setup.cfg +0 -0
  68. {lockbot-2.6.2 → lockbot-2.7.0}/tools/create_super_admin.py +0 -0
  69. {lockbot-2.6.2 → lockbot-2.7.0}/tools/gen_keys.py +0 -0
  70. {lockbot-2.6.2 → lockbot-2.7.0}/tools/reset_super_admin_password.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: lockbot
3
- Version: 2.6.2
3
+ Version: 2.7.0
4
4
  Summary: Cluster resource management bot for IM platforms
5
5
  Author-email: Jianbang Yang <yangjianbang112@gmail.com>
6
6
  License: MIT
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "lockbot"
7
- version = "2.6.2"
7
+ version = "2.7.0"
8
8
  description = "Cluster resource management bot for IM platforms"
9
9
  readme = "README.md"
10
10
  license = {text = "MIT"}
@@ -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(body.cluster_configs, ensure_ascii=False),
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
- new_json = json.dumps(body.cluster_configs, ensure_ascii=False)
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(body.cluster_configs, dict):
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 body.cluster_configs.items()}
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": body.cluster_configs}
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)."""
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: lockbot
3
- Version: 2.6.2
3
+ Version: 2.7.0
4
4
  Summary: Cluster resource management bot for IM platforms
5
5
  Author-email: Jianbang Yang <yangjianbang112@gmail.com>
6
6
  License: MIT
File without changes
File without changes
File without changes
File without changes
File without changes