lockbot 2.9.0__tar.gz → 2.9.2__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.9.0/python/lockbot.egg-info → lockbot-2.9.2}/PKG-INFO +3 -1
  2. {lockbot-2.9.0 → lockbot-2.9.2}/README.md +2 -0
  3. {lockbot-2.9.0 → lockbot-2.9.2}/pyproject.toml +1 -1
  4. {lockbot-2.9.0 → lockbot-2.9.2}/python/lockbot/backend/app/bots/schemas.py +4 -0
  5. {lockbot-2.9.0 → lockbot-2.9.2}/python/lockbot/core/base_bot.py +68 -0
  6. {lockbot-2.9.0 → lockbot-2.9.2}/python/lockbot/core/device_bot.py +4 -1
  7. {lockbot-2.9.0 → lockbot-2.9.2}/python/lockbot/core/handler.py +2 -0
  8. {lockbot-2.9.0 → lockbot-2.9.2}/python/lockbot/core/i18n/en.py +6 -0
  9. {lockbot-2.9.0 → lockbot-2.9.2}/python/lockbot/core/i18n/zh.py +6 -0
  10. {lockbot-2.9.0 → lockbot-2.9.2}/python/lockbot/core/io.py +2 -0
  11. {lockbot-2.9.0 → lockbot-2.9.2}/python/lockbot/core/node_bot.py +7 -2
  12. {lockbot-2.9.0 → lockbot-2.9.2}/python/lockbot/core/queue_bot.py +10 -4
  13. {lockbot-2.9.0 → lockbot-2.9.2/python/lockbot.egg-info}/PKG-INFO +3 -1
  14. {lockbot-2.9.0 → lockbot-2.9.2}/LICENSE +0 -0
  15. {lockbot-2.9.0 → lockbot-2.9.2}/MANIFEST.in +0 -0
  16. {lockbot-2.9.0 → lockbot-2.9.2}/python/lockbot/__init__.py +0 -0
  17. {lockbot-2.9.0 → lockbot-2.9.2}/python/lockbot/backend/__init__.py +0 -0
  18. {lockbot-2.9.0 → lockbot-2.9.2}/python/lockbot/backend/app/__init__.py +0 -0
  19. {lockbot-2.9.0 → lockbot-2.9.2}/python/lockbot/backend/app/admin/__init__.py +0 -0
  20. {lockbot-2.9.0 → lockbot-2.9.2}/python/lockbot/backend/app/admin/router.py +0 -0
  21. {lockbot-2.9.0 → lockbot-2.9.2}/python/lockbot/backend/app/audit/__init__.py +0 -0
  22. {lockbot-2.9.0 → lockbot-2.9.2}/python/lockbot/backend/app/audit/models.py +0 -0
  23. {lockbot-2.9.0 → lockbot-2.9.2}/python/lockbot/backend/app/audit/router.py +0 -0
  24. {lockbot-2.9.0 → lockbot-2.9.2}/python/lockbot/backend/app/audit/service.py +0 -0
  25. {lockbot-2.9.0 → lockbot-2.9.2}/python/lockbot/backend/app/auth/__init__.py +0 -0
  26. {lockbot-2.9.0 → lockbot-2.9.2}/python/lockbot/backend/app/auth/dependencies.py +0 -0
  27. {lockbot-2.9.0 → lockbot-2.9.2}/python/lockbot/backend/app/auth/models.py +0 -0
  28. {lockbot-2.9.0 → lockbot-2.9.2}/python/lockbot/backend/app/auth/router.py +0 -0
  29. {lockbot-2.9.0 → lockbot-2.9.2}/python/lockbot/backend/app/auth/schemas.py +0 -0
  30. {lockbot-2.9.0 → lockbot-2.9.2}/python/lockbot/backend/app/backup/__init__.py +0 -0
  31. {lockbot-2.9.0 → lockbot-2.9.2}/python/lockbot/backend/app/backup/router.py +0 -0
  32. {lockbot-2.9.0 → lockbot-2.9.2}/python/lockbot/backend/app/backup/scheduler.py +0 -0
  33. {lockbot-2.9.0 → lockbot-2.9.2}/python/lockbot/backend/app/backup/service.py +0 -0
  34. {lockbot-2.9.0 → lockbot-2.9.2}/python/lockbot/backend/app/bots/__init__.py +0 -0
  35. {lockbot-2.9.0 → lockbot-2.9.2}/python/lockbot/backend/app/bots/encryption.py +0 -0
  36. {lockbot-2.9.0 → lockbot-2.9.2}/python/lockbot/backend/app/bots/manager.py +0 -0
  37. {lockbot-2.9.0 → lockbot-2.9.2}/python/lockbot/backend/app/bots/models.py +0 -0
  38. {lockbot-2.9.0 → lockbot-2.9.2}/python/lockbot/backend/app/bots/router.py +0 -0
  39. {lockbot-2.9.0 → lockbot-2.9.2}/python/lockbot/backend/app/bots/webhook_handler.py +0 -0
  40. {lockbot-2.9.0 → lockbot-2.9.2}/python/lockbot/backend/app/config.py +0 -0
  41. {lockbot-2.9.0 → lockbot-2.9.2}/python/lockbot/backend/app/database.py +0 -0
  42. {lockbot-2.9.0 → lockbot-2.9.2}/python/lockbot/backend/app/logs/__init__.py +0 -0
  43. {lockbot-2.9.0 → lockbot-2.9.2}/python/lockbot/backend/app/main.py +0 -0
  44. {lockbot-2.9.0 → lockbot-2.9.2}/python/lockbot/backend/app/rate_limit.py +0 -0
  45. {lockbot-2.9.0 → lockbot-2.9.2}/python/lockbot/backend/app/settings/__init__.py +0 -0
  46. {lockbot-2.9.0 → lockbot-2.9.2}/python/lockbot/backend/app/settings/models.py +0 -0
  47. {lockbot-2.9.0 → lockbot-2.9.2}/python/lockbot/backend/app/settings/router.py +0 -0
  48. {lockbot-2.9.0 → lockbot-2.9.2}/python/lockbot/core/__init__.py +0 -0
  49. {lockbot-2.9.0 → lockbot-2.9.2}/python/lockbot/core/bot_instance.py +0 -0
  50. {lockbot-2.9.0 → lockbot-2.9.2}/python/lockbot/core/config.py +0 -0
  51. {lockbot-2.9.0 → lockbot-2.9.2}/python/lockbot/core/device_usage_alert.py +0 -0
  52. {lockbot-2.9.0 → lockbot-2.9.2}/python/lockbot/core/device_usage_utils.py +0 -0
  53. {lockbot-2.9.0 → lockbot-2.9.2}/python/lockbot/core/entry.py +0 -0
  54. {lockbot-2.9.0 → lockbot-2.9.2}/python/lockbot/core/env.py +0 -0
  55. {lockbot-2.9.0 → lockbot-2.9.2}/python/lockbot/core/i18n/__init__.py +0 -0
  56. {lockbot-2.9.0 → lockbot-2.9.2}/python/lockbot/core/message_adapter.py +0 -0
  57. {lockbot-2.9.0 → lockbot-2.9.2}/python/lockbot/core/msg_utils.py +0 -0
  58. {lockbot-2.9.0 → lockbot-2.9.2}/python/lockbot/core/platforms/__init__.py +0 -0
  59. {lockbot-2.9.0 → lockbot-2.9.2}/python/lockbot/core/platforms/infoflow.py +0 -0
  60. {lockbot-2.9.0 → lockbot-2.9.2}/python/lockbot/core/request.py +0 -0
  61. {lockbot-2.9.0 → lockbot-2.9.2}/python/lockbot/core/scheduler.py +0 -0
  62. {lockbot-2.9.0 → lockbot-2.9.2}/python/lockbot/core/utils.py +0 -0
  63. {lockbot-2.9.0 → lockbot-2.9.2}/python/lockbot.egg-info/SOURCES.txt +0 -0
  64. {lockbot-2.9.0 → lockbot-2.9.2}/python/lockbot.egg-info/dependency_links.txt +0 -0
  65. {lockbot-2.9.0 → lockbot-2.9.2}/python/lockbot.egg-info/requires.txt +0 -0
  66. {lockbot-2.9.0 → lockbot-2.9.2}/python/lockbot.egg-info/top_level.txt +0 -0
  67. {lockbot-2.9.0 → lockbot-2.9.2}/setup.cfg +0 -0
  68. {lockbot-2.9.0 → lockbot-2.9.2}/tools/create_super_admin.py +0 -0
  69. {lockbot-2.9.0 → lockbot-2.9.2}/tools/gen_keys.py +0 -0
  70. {lockbot-2.9.0 → lockbot-2.9.2}/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.9.0
3
+ Version: 2.9.2
4
4
  Summary: Cluster resource management bot for IM platforms
5
5
  Author-email: Jianbang Yang <yangjianbang112@gmail.com>
6
6
  License: MIT
@@ -53,6 +53,8 @@ Supports both standalone Flask deployment and a full platform mode with FastAPI
53
53
  [![PyPI version](https://img.shields.io/pypi/v/lockbot?color=blue)](https://pypi.org/project/lockbot/)
54
54
  [![Docker Image](https://img.shields.io/badge/ghcr.io-dynamicheart%2Flockbot-blue?logo=docker)](https://github.com/DynamicHeart/lockbot/pkgs/container/lockbot)
55
55
 
56
+ <img src="./docs/demo.gif" width="600" />
57
+
56
58
  ## Features
57
59
 
58
60
  - **Device Lock Bot** — Lock/unlock individual GPUs or devices on a cluster
@@ -10,6 +10,8 @@ Supports both standalone Flask deployment and a full platform mode with FastAPI
10
10
  [![PyPI version](https://img.shields.io/pypi/v/lockbot?color=blue)](https://pypi.org/project/lockbot/)
11
11
  [![Docker Image](https://img.shields.io/badge/ghcr.io-dynamicheart%2Flockbot-blue?logo=docker)](https://github.com/DynamicHeart/lockbot/pkgs/container/lockbot)
12
12
 
13
+ <img src="./docs/demo.gif" width="600" />
14
+
13
15
  ## Features
14
16
 
15
17
  - **Device Lock Bot** — Lock/unlock individual GPUs or devices on a cluster
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "lockbot"
7
- version = "2.9.0"
7
+ version = "2.9.2"
8
8
  description = "Cluster resource management bot for IM platforms"
9
9
  readme = "README.md"
10
10
  license = {text = "MIT"}
@@ -68,6 +68,10 @@ def _validate_config_overrides(v: dict | None) -> dict | None:
68
68
  if show_alias is not None and not isinstance(show_alias, bool):
69
69
  errors.append("SHOW_NODE_ALIAS must be a boolean")
70
70
 
71
+ allow_alias = v.get("ALLOW_USER_ALIAS")
72
+ if allow_alias is not None and not isinstance(allow_alias, bool):
73
+ errors.append("ALLOW_USER_ALIAS must be a boolean")
74
+
71
75
  max_dur = v.get("MAX_LOCK_DURATION")
72
76
  default_dur = v.get("DEFAULT_DURATION")
73
77
  if isinstance(max_dur, int) and isinstance(default_dur, int) and max_dur != -1 and default_dur > max_dur:
@@ -133,6 +133,74 @@ class BaseLockBot:
133
133
  alias = aliases.get(node_key)
134
134
  return f"{node_key}「{alias}」" if alias else node_key
135
135
 
136
+ def alias(self, user_id, command):
137
+ """Set or clear a node alias. Usage: alias <node_key> [alias_value]"""
138
+ if not self.config.get_val("ALLOW_USER_ALIAS"):
139
+ return self.show_error(user_id, t("alias.disabled", config=self.config))
140
+
141
+ import re
142
+
143
+ m = re.match(r"^\s*alias\s+([\w\d_-]+)(?:\s+(.{1,15}))?\s*$", command)
144
+ if not m:
145
+ return self.show_error(user_id, t("alias.usage", config=self.config))
146
+
147
+ node_key = m.group(1)
148
+ alias_val = (m.group(2) or "").strip()
149
+
150
+ cluster_configs = self.config.get_val("CLUSTER_CONFIGS")
151
+ if node_key not in cluster_configs:
152
+ return self.show_error(
153
+ user_id,
154
+ t(
155
+ "alias.invalid_node",
156
+ config=self.config,
157
+ node_key=node_key,
158
+ valid_keys=str(list(cluster_configs.keys())),
159
+ ),
160
+ )
161
+
162
+ aliases = self.config.get_val("NODE_ALIASES") or {}
163
+ if alias_val:
164
+ aliases[node_key] = alias_val
165
+ else:
166
+ aliases.pop(node_key, None)
167
+ self.config.set_val("NODE_ALIASES", aliases)
168
+
169
+ # Persist to DB if running under platform mode
170
+ self._persist_config_override("NODE_ALIASES", aliases)
171
+
172
+ if alias_val:
173
+ self.logger.info("user=%s set alias %s -> %s", user_id, node_key, alias_val)
174
+ msg = t("alias.set_ok", config=self.config, node_key=node_key, alias=alias_val)
175
+ else:
176
+ self.logger.info("user=%s cleared alias %s", user_id, node_key)
177
+ msg = t("alias.clear_ok", config=self.config, node_key=node_key)
178
+ return self.adapter.build_reply(msg, [user_id])
179
+
180
+ def _persist_config_override(self, key, value):
181
+ """Persist a single config_override key to DB (best-effort)."""
182
+ try:
183
+ import json
184
+
185
+ from lockbot.backend.app.bots.models import Bot
186
+ from lockbot.backend.app.database import SessionLocal
187
+
188
+ bot_id = self.config.get_val("BOT_ID")
189
+ if not bot_id:
190
+ return
191
+ db = SessionLocal()
192
+ try:
193
+ bot = db.query(Bot).filter(Bot.id == int(bot_id)).first()
194
+ if bot:
195
+ overrides = json.loads(bot.config_overrides) if bot.config_overrides else {}
196
+ overrides[key] = value
197
+ bot.config_overrides = json.dumps(overrides, ensure_ascii=False)
198
+ db.commit()
199
+ finally:
200
+ db.close()
201
+ except Exception:
202
+ pass
203
+
136
204
  def _whitelist_hint(self):
137
205
  """Return whitelist hint string if BOT_OWNER is configured, else empty string."""
138
206
  owner = self.config.get_val("BOT_OWNER")
@@ -39,7 +39,7 @@ class DeviceBot(BaseLockBot):
39
39
  _loader = staticmethod(create_or_load_device_state)
40
40
 
41
41
  def supported_commands(self):
42
- return ["lock", "slock", "unlock", "free", "kickout", "help", "h", "query"]
42
+ return ["lock", "slock", "unlock", "free", "kickout", "help", "h", "query", "alias"]
43
43
 
44
44
  def parse_command(self, user_id, command_key, command, parsing_duration=False):
45
45
  """
@@ -493,6 +493,9 @@ class DeviceBot(BaseLockBot):
493
493
  text += t("help.query_node_example", config=self.config, node=example_node)
494
494
  text += "\n"
495
495
 
496
+ if self.config.get_val("ALLOW_USER_ALIAS"):
497
+ text += f"6. 备注: alias {example_node} <备注>\n\n"
498
+
496
499
  return text
497
500
 
498
501
  def _check_and_notify(self) -> float | None:
@@ -55,6 +55,8 @@ def execute_command(msg_data, bot):
55
55
  return bot.query(user_id)
56
56
  elif cmd in ("help", "h"):
57
57
  return bot.print_help(user_id)
58
+ elif cmd == "alias":
59
+ return bot.alias(user_id, rcv_info)
58
60
  else:
59
61
  return bot.print_help(user_id, t("error.unknown_error", config=getattr(bot, "config", None), command=rcv_info))
60
62
  return bot.print_help(user_id, t("error.unknown_error", config=getattr(bot, "config", None), command=rcv_info))
@@ -173,6 +173,12 @@ MESSAGES = {
173
173
  "opkick.invalid_format": "Invalid opkick format. Usage: opkick <username> [node] [dev x,y]",
174
174
  "opkick.notify_kicked": "⚠️ [Operator Release]\n\nNode: {node_key}\n",
175
175
  "opkick.notify_kicked_reason": "Reason: {reason}\n\n",
176
+ # ── Alias command ──
177
+ "alias.usage": "Usage: alias <node_key> [label]\nExample: alias node0 10.10.10.56\nClear: alias node0",
178
+ "alias.invalid_node": "Node {node_key} does not exist. Available: {valid_keys}\n",
179
+ "alias.set_ok": "✅ Set {node_key} alias to「{alias}」\n\n",
180
+ "alias.clear_ok": "✅ Cleared alias for {node_key}\n\n",
181
+ "alias.disabled": "User alias command is not enabled for this bot",
176
182
  # ── Webhook: bot not running ──
177
183
  "webhook.bot_not_running": "⚠️ Bot {bot_name}(ID:{bot_id}) is not running. " # noqa: E501
178
184
  "Please contact the owner @{owner_username} to start it.",
@@ -166,6 +166,12 @@ MESSAGES = {
166
166
  "opkick.invalid_format": "opkick命令格式错误,正确格式: opkick <username> [node] [dev x,y]",
167
167
  "opkick.notify_kicked": "⚠️【运维释放】\n\n节点: {node_key}\n",
168
168
  "opkick.notify_kicked_reason": "原因: {reason}\n\n",
169
+ # ── Alias command ──
170
+ "alias.usage": "用法: alias <节点名> [备注]\n示例: alias node0 10.10.10.56\n清除: alias node0",
171
+ "alias.invalid_node": "节点 {node_key} 不存在,可用节点: {valid_keys}\n",
172
+ "alias.set_ok": "✅ 已设置 {node_key} 备注为「{alias}」\n\n",
173
+ "alias.clear_ok": "✅ 已清除 {node_key} 的备注\n\n",
174
+ "alias.disabled": "该机器人未开启用户自助设置备注功能",
169
175
  # ── Webhook: bot not running ──
170
176
  "webhook.bot_not_running": (
171
177
  "⚠️ 机器人 {bot_name}(ID:{bot_id}) 尚未启动,请联系管理人 @{owner_username} 启动后再使用。"
@@ -154,6 +154,8 @@ def create_or_load_device_state(config=None):
154
154
  clamped = apply_max_duration_limit(device_status["current_users"], max_duration, duration_whitelist)
155
155
  clamped_user_ids.update(clamped)
156
156
  default_device.update(**device_status)
157
+ # Always use config's dev_model as source of truth
158
+ default_device["dev_model"] = cluster_configs[node_key][dev_id]
157
159
 
158
160
  if clamped_user_ids:
159
161
  save_bot_state_to_file(default_state, config)
@@ -37,7 +37,7 @@ class NodeBot(BaseLockBot):
37
37
  _loader = staticmethod(create_or_load_node_state)
38
38
 
39
39
  def supported_commands(self):
40
- return ["lock", "slock", "unlock", "free", "kickout", "help", "h", "query"]
40
+ return ["lock", "slock", "unlock", "free", "kickout", "help", "h", "query", "alias"]
41
41
 
42
42
  def parse_command(self, user_id, command_key, command, parsing_duration=False):
43
43
  """
@@ -358,7 +358,12 @@ class NodeBot(BaseLockBot):
358
358
  reply_info += t("help.section4_title", config=self.config)
359
359
  reply_info += t("help.section5_title", config=self.config)
360
360
  reply_info += t("help.query_at_bot", config=self.config)
361
- reply_info += f" {example_node0}\n\n"
361
+ reply_info += f" {example_node0}\n"
362
+
363
+ if self.config.get_val("ALLOW_USER_ALIAS"):
364
+ reply_info += f"6. 备注: alias {example_node0} <备注>\n"
365
+
366
+ reply_info += "\n"
362
367
  return reply_info
363
368
 
364
369
  def _check_and_notify(self) -> float | None:
@@ -26,7 +26,7 @@ class QueueBot(NodeBot):
26
26
  """
27
27
 
28
28
  def supported_commands(self):
29
- return ["lock", "unlock", "free", "kickout", "kicklock", "help", "h", "book", "take", "query"]
29
+ return ["lock", "unlock", "free", "kickout", "kicklock", "help", "h", "book", "take", "query", "alias"]
30
30
 
31
31
  def lock(self, user_id, command):
32
32
  """
@@ -345,7 +345,12 @@ class QueueBot(NodeBot):
345
345
  parts.append(t("help.section_help_title_queue", config=self.config))
346
346
  parts.append(t("help.section_query_title_queue", config=self.config))
347
347
  parts.append(t("help.query_at_bot", config=self.config))
348
- parts.append(f" {example_node0}\n\n")
348
+ parts.append(f" {example_node0}\n")
349
+
350
+ if self.config.get_val("ALLOW_USER_ALIAS"):
351
+ parts.append(f"8. 备注: alias {example_node0} <备注>\n")
352
+
353
+ parts.append("\n")
349
354
  return "".join(parts)
350
355
 
351
356
  def _check_and_notify(self) -> float | None:
@@ -522,11 +527,12 @@ class QueueBot(NodeBot):
522
527
  usage_info = ""
523
528
  for node_key, node_status in nodes.items():
524
529
  if node_filter is None or node_key == node_filter:
530
+ display_name = self._display_node(node_key)
525
531
  if node_status["status"] == "idle":
526
- usage_info += "{:} {}\n".format(node_key, t("status.idle", config=self.config))
532
+ usage_info += "{:} {}\n".format(display_name, t("status.idle", config=self.config))
527
533
  else:
528
534
  for user_idx, user_info in enumerate(node_status["current_users"]):
529
- node_name = f"{node_key}" if user_idx == 0 else ""
535
+ node_name = f"{display_name}" if user_idx == 0 else ""
530
536
  uid = user_info["user_id"]
531
537
  duration = format_duration(
532
538
  remaining_duration(user_info["start_time"], user_info["duration"]),
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: lockbot
3
- Version: 2.9.0
3
+ Version: 2.9.2
4
4
  Summary: Cluster resource management bot for IM platforms
5
5
  Author-email: Jianbang Yang <yangjianbang112@gmail.com>
6
6
  License: MIT
@@ -53,6 +53,8 @@ Supports both standalone Flask deployment and a full platform mode with FastAPI
53
53
  [![PyPI version](https://img.shields.io/pypi/v/lockbot?color=blue)](https://pypi.org/project/lockbot/)
54
54
  [![Docker Image](https://img.shields.io/badge/ghcr.io-dynamicheart%2Flockbot-blue?logo=docker)](https://github.com/DynamicHeart/lockbot/pkgs/container/lockbot)
55
55
 
56
+ <img src="./docs/demo.gif" width="600" />
57
+
56
58
  ## Features
57
59
 
58
60
  - **Device Lock Bot** — Lock/unlock individual GPUs or devices on a cluster
File without changes
File without changes
File without changes
File without changes