standkit 0.3.7__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.
- standkit/__init__.py +11 -0
- standkit/health.py +163 -0
- standkit/lifecycle.py +191 -0
- standkit/logs.py +221 -0
- standkit/models.py +211 -0
- standkit/platform.py +174 -0
- standkit/registry.py +223 -0
- standkit/secrets.py +144 -0
- standkit-0.3.7.dist-info/METADATA +155 -0
- standkit-0.3.7.dist-info/RECORD +38 -0
- standkit-0.3.7.dist-info/WHEEL +5 -0
- standkit-0.3.7.dist-info/entry_points.txt +4 -0
- standkit-0.3.7.dist-info/licenses/LICENSE +21 -0
- standkit-0.3.7.dist-info/top_level.txt +3 -0
- standkit_agent/__init__.py +9 -0
- standkit_agent/__main__.py +192 -0
- standkit_agent/audit.py +93 -0
- standkit_agent/security.py +300 -0
- standkit_agent/server.py +386 -0
- standkit_hub/__init__.py +13 -0
- standkit_hub/__main__.py +131 -0
- standkit_hub/agent_control.py +216 -0
- standkit_hub/assets/bpmkit-icon.ico +0 -0
- standkit_hub/assets/icon.png +0 -0
- standkit_hub/client.py +159 -0
- standkit_hub/config.py +156 -0
- standkit_hub/logs_browser.py +174 -0
- standkit_hub/redis_min.py +326 -0
- standkit_hub/security.py +146 -0
- standkit_hub/server.py +881 -0
- standkit_hub/shortcut.py +264 -0
- standkit_hub/web/app.js +803 -0
- standkit_hub/web/bpmkit-logo-dark.svg +5 -0
- standkit_hub/web/bpmkit-logo.svg +5 -0
- standkit_hub/web/favicon.png +0 -0
- standkit_hub/web/favicon.svg +4 -0
- standkit_hub/web/index.html +267 -0
- standkit_hub/web/style.css +843 -0
standkit_hub/shortcut.py
ADDED
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Ярлык веб-дашборда standkit на рабочем столе — кроссплатформенно:
|
|
3
|
+
|
|
4
|
+
- Windows: ``.lnk`` через ``WScript.Shell`` из PowerShell (без pywin32);
|
|
5
|
+
- Linux: freedesktop ``.desktop``-файл (меню приложений + копия на
|
|
6
|
+
рабочий стол, если такая папка есть);
|
|
7
|
+
- macOS: осознанно НЕ реализовано — возвращается понятный статус.
|
|
8
|
+
|
|
9
|
+
Ярлык запускает ``python -m standkit_hub`` — хаб сам поднимает локальный
|
|
10
|
+
веб-сервер и открывает системный браузер (см. standkit_hub/__main__.py).
|
|
11
|
+
|
|
12
|
+
Все публичные функции возвращают ``ShortcutResult`` и НИКОГДА не бросают
|
|
13
|
+
исключений наружу (в т.ч. на неподдерживаемой ОС или при сбое внешней
|
|
14
|
+
команды) — вызывающий код просто показывает ``message`` пользователю.
|
|
15
|
+
|
|
16
|
+
Иконка берётся из уже упакованных ресурсов ``standkit_hub/assets``
|
|
17
|
+
(``importlib.resources`` — работает и из установленного пакета, не только
|
|
18
|
+
из исходников репозитория) и извлекается на диск рядом с ярлыком/записью
|
|
19
|
+
меню, т.к. .lnk/.desktop не умеют встраивать иконку из архива напрямую.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
from __future__ import annotations
|
|
23
|
+
|
|
24
|
+
import importlib.resources
|
|
25
|
+
import os
|
|
26
|
+
import shutil
|
|
27
|
+
import subprocess
|
|
28
|
+
import sys
|
|
29
|
+
from dataclasses import dataclass
|
|
30
|
+
from pathlib import Path
|
|
31
|
+
from typing import Optional
|
|
32
|
+
|
|
33
|
+
_APP_NAME = "BPMkit Диспетчер"
|
|
34
|
+
_WINDOWS_SHORTCUT_NAME = "BPMkit Диспетчер.lnk"
|
|
35
|
+
_WINDOWS_ICON_ASSET = "bpmkit-icon.ico"
|
|
36
|
+
_LINUX_DESKTOP_FILE_NAME = "bpmkit-standkit.desktop"
|
|
37
|
+
_LINUX_ICON_ASSET = "icon.png"
|
|
38
|
+
_LINUX_ICON_FILE_NAME = "bpmkit-standkit.png"
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
@dataclass
|
|
42
|
+
class ShortcutResult:
|
|
43
|
+
"""Результат install/uninstall — без исключений, только статус + текст для пользователя."""
|
|
44
|
+
|
|
45
|
+
ok: bool
|
|
46
|
+
path: Optional[str]
|
|
47
|
+
message: str
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def _extract_asset(asset_name: str, dest_path: Path) -> Path:
|
|
51
|
+
"""
|
|
52
|
+
Извлекает файл ресурса пакета (``standkit_hub/assets/<asset_name>``) на
|
|
53
|
+
диск по ``dest_path`` (создавая родительские папки при необходимости).
|
|
54
|
+
"""
|
|
55
|
+
resource = importlib.resources.files("standkit_hub") / "assets" / asset_name
|
|
56
|
+
with importlib.resources.as_file(resource) as src:
|
|
57
|
+
data = Path(src).read_bytes()
|
|
58
|
+
dest_path.parent.mkdir(parents=True, exist_ok=True)
|
|
59
|
+
dest_path.write_bytes(data)
|
|
60
|
+
return dest_path
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
# ============================== Windows ==============================
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def _windows_desktop_dir() -> Path:
|
|
67
|
+
return Path.home() / "Desktop"
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def _windows_local_appdata_bpmkit_dir() -> Path:
|
|
71
|
+
local_appdata = os.environ.get("LOCALAPPDATA")
|
|
72
|
+
base = Path(local_appdata) if local_appdata else Path.home() / "AppData" / "Local"
|
|
73
|
+
return base / "BPMkit"
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def _windows_pythonw_executable() -> str:
|
|
77
|
+
"""``pythonw.exe`` рядом с текущим интерпретатором (без консольного окна); фолбэк — ``sys.executable``."""
|
|
78
|
+
candidate = Path(sys.executable).with_name("pythonw.exe")
|
|
79
|
+
if candidate.exists():
|
|
80
|
+
return str(candidate)
|
|
81
|
+
return sys.executable
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def build_windows_shortcut_script(lnk_path: Path, target: str, icon_path: Path, working_dir: Path) -> str:
|
|
85
|
+
"""
|
|
86
|
+
Собирает PowerShell-скрипт создания ``.lnk`` через ``WScript.Shell``
|
|
87
|
+
(без pywin32). Вынесено в отдельную чистую функцию специально ради
|
|
88
|
+
тестируемости без реального ``subprocess.run``.
|
|
89
|
+
"""
|
|
90
|
+
return (
|
|
91
|
+
"$WshShell = New-Object -ComObject WScript.Shell\n"
|
|
92
|
+
f'$Shortcut = $WshShell.CreateShortcut("{lnk_path}")\n'
|
|
93
|
+
f'$Shortcut.TargetPath = "{target}"\n'
|
|
94
|
+
'$Shortcut.Arguments = "-m standkit_hub"\n'
|
|
95
|
+
f'$Shortcut.IconLocation = "{icon_path}"\n'
|
|
96
|
+
f'$Shortcut.WorkingDirectory = "{working_dir}"\n'
|
|
97
|
+
"$Shortcut.Save()\n"
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def _install_windows() -> ShortcutResult:
|
|
102
|
+
desktop_dir = _windows_desktop_dir()
|
|
103
|
+
lnk_path = desktop_dir / _WINDOWS_SHORTCUT_NAME
|
|
104
|
+
|
|
105
|
+
try:
|
|
106
|
+
icon_path = _extract_asset(
|
|
107
|
+
_WINDOWS_ICON_ASSET, _windows_local_appdata_bpmkit_dir() / _WINDOWS_ICON_ASSET
|
|
108
|
+
)
|
|
109
|
+
except (FileNotFoundError, OSError) as exc:
|
|
110
|
+
return ShortcutResult(ok=False, path=None, message=f"Не удалось извлечь иконку: {exc}")
|
|
111
|
+
|
|
112
|
+
target = _windows_pythonw_executable()
|
|
113
|
+
script = build_windows_shortcut_script(lnk_path, target, icon_path, Path.home())
|
|
114
|
+
|
|
115
|
+
try:
|
|
116
|
+
proc = subprocess.run(
|
|
117
|
+
["powershell", "-NoProfile", "-NonInteractive", "-Command", script],
|
|
118
|
+
capture_output=True,
|
|
119
|
+
text=True,
|
|
120
|
+
timeout=30,
|
|
121
|
+
)
|
|
122
|
+
except (OSError, subprocess.SubprocessError) as exc:
|
|
123
|
+
return ShortcutResult(ok=False, path=None, message=f"Не удалось запустить PowerShell: {exc}")
|
|
124
|
+
|
|
125
|
+
if proc.returncode != 0:
|
|
126
|
+
detail = (proc.stderr or proc.stdout or "").strip()
|
|
127
|
+
return ShortcutResult(ok=False, path=None, message=f"PowerShell вернул ошибку: {detail}")
|
|
128
|
+
|
|
129
|
+
return ShortcutResult(ok=True, path=str(lnk_path), message=f"Ярлык создан: {lnk_path}")
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def _uninstall_windows() -> ShortcutResult:
|
|
133
|
+
lnk_path = _windows_desktop_dir() / _WINDOWS_SHORTCUT_NAME
|
|
134
|
+
if not lnk_path.exists():
|
|
135
|
+
return ShortcutResult(ok=True, path=str(lnk_path), message="Ярлык уже отсутствует")
|
|
136
|
+
try:
|
|
137
|
+
lnk_path.unlink()
|
|
138
|
+
except OSError as exc:
|
|
139
|
+
return ShortcutResult(ok=False, path=str(lnk_path), message=f"Не удалось удалить ярлык: {exc}")
|
|
140
|
+
return ShortcutResult(ok=True, path=str(lnk_path), message=f"Ярлык удалён: {lnk_path}")
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
# ================================ Linux ================================
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def _linux_applications_dir() -> Path:
|
|
147
|
+
return Path.home() / ".local" / "share" / "applications"
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
def _linux_icons_dir() -> Path:
|
|
151
|
+
return Path.home() / ".local" / "share" / "icons"
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
def _linux_desktop_dir() -> Optional[Path]:
|
|
155
|
+
candidate = Path.home() / "Desktop"
|
|
156
|
+
return candidate if candidate.is_dir() else None
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def _linux_exec_command() -> str:
|
|
160
|
+
if shutil.which("standkit-gui"):
|
|
161
|
+
return "standkit-gui"
|
|
162
|
+
return "python3 -m standkit_hub"
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
def build_desktop_entry(exec_cmd: str, icon_path: Path) -> str:
|
|
166
|
+
"""Содержимое freedesktop ``.desktop``-файла — чистая функция, тестируется без файловой системы."""
|
|
167
|
+
return (
|
|
168
|
+
"[Desktop Entry]\n"
|
|
169
|
+
"Type=Application\n"
|
|
170
|
+
f"Name={_APP_NAME}\n"
|
|
171
|
+
f"Exec={exec_cmd}\n"
|
|
172
|
+
f"Icon={icon_path}\n"
|
|
173
|
+
"Terminal=false\n"
|
|
174
|
+
"Categories=Utility;\n"
|
|
175
|
+
)
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
def _install_linux() -> ShortcutResult:
|
|
179
|
+
try:
|
|
180
|
+
icon_path = _extract_asset(_LINUX_ICON_ASSET, _linux_icons_dir() / _LINUX_ICON_FILE_NAME)
|
|
181
|
+
except (FileNotFoundError, OSError) as exc:
|
|
182
|
+
return ShortcutResult(ok=False, path=None, message=f"Не удалось извлечь иконку: {exc}")
|
|
183
|
+
|
|
184
|
+
content = build_desktop_entry(_linux_exec_command(), icon_path)
|
|
185
|
+
|
|
186
|
+
apps_dir = _linux_applications_dir()
|
|
187
|
+
entry_path = apps_dir / _LINUX_DESKTOP_FILE_NAME
|
|
188
|
+
try:
|
|
189
|
+
apps_dir.mkdir(parents=True, exist_ok=True)
|
|
190
|
+
entry_path.write_text(content, encoding="utf-8")
|
|
191
|
+
entry_path.chmod(0o755)
|
|
192
|
+
except OSError as exc:
|
|
193
|
+
return ShortcutResult(ok=False, path=None, message=f"Не удалось записать .desktop-файл: {exc}")
|
|
194
|
+
|
|
195
|
+
desktop_dir = _linux_desktop_dir()
|
|
196
|
+
if desktop_dir is not None:
|
|
197
|
+
desktop_copy = desktop_dir / _LINUX_DESKTOP_FILE_NAME
|
|
198
|
+
try:
|
|
199
|
+
desktop_copy.write_text(content, encoding="utf-8")
|
|
200
|
+
desktop_copy.chmod(0o755)
|
|
201
|
+
except OSError:
|
|
202
|
+
# Копия на рабочий стол — best-effort; запись в меню приложений
|
|
203
|
+
# (apps_dir) уже создана и считается основным результатом.
|
|
204
|
+
pass
|
|
205
|
+
|
|
206
|
+
return ShortcutResult(ok=True, path=str(entry_path), message=f"Ярлык создан: {entry_path}")
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
def _uninstall_linux() -> ShortcutResult:
|
|
210
|
+
candidates = [_linux_applications_dir() / _LINUX_DESKTOP_FILE_NAME]
|
|
211
|
+
desktop_dir = _linux_desktop_dir()
|
|
212
|
+
if desktop_dir is not None:
|
|
213
|
+
candidates.append(desktop_dir / _LINUX_DESKTOP_FILE_NAME)
|
|
214
|
+
|
|
215
|
+
removed_any = False
|
|
216
|
+
for path in candidates:
|
|
217
|
+
if path.exists():
|
|
218
|
+
try:
|
|
219
|
+
path.unlink()
|
|
220
|
+
removed_any = True
|
|
221
|
+
except OSError as exc:
|
|
222
|
+
return ShortcutResult(ok=False, path=str(path), message=f"Не удалось удалить {path}: {exc}")
|
|
223
|
+
|
|
224
|
+
icon_path = _linux_icons_dir() / _LINUX_ICON_FILE_NAME
|
|
225
|
+
if icon_path.exists():
|
|
226
|
+
try:
|
|
227
|
+
icon_path.unlink()
|
|
228
|
+
except OSError:
|
|
229
|
+
pass
|
|
230
|
+
|
|
231
|
+
message = "Ярлык удалён" if removed_any else "Ярлык уже отсутствовал"
|
|
232
|
+
return ShortcutResult(ok=True, path=str(candidates[0]), message=message)
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
# ============================ macOS / прочее ============================
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
def _unsupported(action: str) -> ShortcutResult:
|
|
239
|
+
return ShortcutResult(
|
|
240
|
+
ok=False,
|
|
241
|
+
path=None,
|
|
242
|
+
message=f"{action} ярлыка не поддерживается на платформе {sys.platform!r} (macOS и т.п.)",
|
|
243
|
+
)
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
# ================================ API ================================
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
def install_desktop_shortcut() -> ShortcutResult:
|
|
250
|
+
"""Создаёт ярлык веб-дашборда standkit на рабочем столе (Windows ``.lnk`` / Linux ``.desktop``)."""
|
|
251
|
+
if sys.platform == "win32":
|
|
252
|
+
return _install_windows()
|
|
253
|
+
if sys.platform.startswith("linux"):
|
|
254
|
+
return _install_linux()
|
|
255
|
+
return _unsupported("Создание")
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
def uninstall_desktop_shortcut() -> ShortcutResult:
|
|
259
|
+
"""Удаляет ранее созданный ярлык веб-дашборда standkit."""
|
|
260
|
+
if sys.platform == "win32":
|
|
261
|
+
return _uninstall_windows()
|
|
262
|
+
if sys.platform.startswith("linux"):
|
|
263
|
+
return _uninstall_linux()
|
|
264
|
+
return _unsupported("Удаление")
|