astrapi-core 26.4.22__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.
- astrapi_core-26.4.22/.claude/settings.json +7 -0
- astrapi_core-26.4.22/.claude-memory.md +164 -0
- astrapi_core-26.4.22/.github/workflows/publish.yml +31 -0
- astrapi_core-26.4.22/.gitignore +40 -0
- astrapi_core-26.4.22/.gitlab-ci.yml +24 -0
- astrapi_core-26.4.22/.vscode/settings.json +15 -0
- astrapi_core-26.4.22/CLAUDE.md +3 -0
- astrapi_core-26.4.22/PKG-INFO +3 -0
- astrapi_core-26.4.22/astrapi_core/modules/activity_log/__init__.py +6 -0
- astrapi_core-26.4.22/astrapi_core/modules/activity_log/api.py +104 -0
- astrapi_core-26.4.22/astrapi_core/modules/activity_log/engine.py +41 -0
- astrapi_core-26.4.22/astrapi_core/modules/activity_log/icon-outline.svg +1 -0
- astrapi_core-26.4.22/astrapi_core/modules/activity_log/icon.svg +1 -0
- astrapi_core-26.4.22/astrapi_core/modules/activity_log/modul.yaml +4 -0
- astrapi_core-26.4.22/astrapi_core/modules/activity_log/templates/content.html +71 -0
- astrapi_core-26.4.22/astrapi_core/modules/activity_log/templates/modals/detail.html +111 -0
- astrapi_core-26.4.22/astrapi_core/modules/activity_log/templates/modals/log_viewer.html +25 -0
- astrapi_core-26.4.22/astrapi_core/modules/activity_log/templates/partials/rows.html +78 -0
- astrapi_core-26.4.22/astrapi_core/modules/activity_log/ui.py +44 -0
- astrapi_core-26.4.22/astrapi_core/modules/notify/__init__.py +26 -0
- astrapi_core-26.4.22/astrapi_core/modules/notify/api.py +122 -0
- astrapi_core-26.4.22/astrapi_core/modules/notify/backends/__init__.py +2 -0
- astrapi_core-26.4.22/astrapi_core/modules/notify/backends/email.py +117 -0
- astrapi_core-26.4.22/astrapi_core/modules/notify/backends/ntfy.py +102 -0
- astrapi_core-26.4.22/astrapi_core/modules/notify/engine.py +332 -0
- astrapi_core-26.4.22/astrapi_core/modules/notify/icon-outline.svg +1 -0
- astrapi_core-26.4.22/astrapi_core/modules/notify/icon.svg +1 -0
- astrapi_core-26.4.22/astrapi_core/modules/notify/schema.py +60 -0
- astrapi_core-26.4.22/astrapi_core/modules/notify/templates/content.html +236 -0
- astrapi_core-26.4.22/astrapi_core/modules/notify/templates/modals/backend_select.html +215 -0
- astrapi_core-26.4.22/astrapi_core/modules/notify/templates/modals/channel.html +178 -0
- astrapi_core-26.4.22/astrapi_core/modules/notify/templates/modals/job.html +218 -0
- astrapi_core-26.4.22/astrapi_core/modules/notify/templates/partials/test_badge.html +22 -0
- astrapi_core-26.4.22/astrapi_core/modules/notify/ui.py +307 -0
- astrapi_core-26.4.22/astrapi_core/modules/scheduler/__init__.py +20 -0
- astrapi_core-26.4.22/astrapi_core/modules/scheduler/api.py +80 -0
- astrapi_core-26.4.22/astrapi_core/modules/scheduler/engine.py +374 -0
- astrapi_core-26.4.22/astrapi_core/modules/scheduler/icon-outline.svg +1 -0
- astrapi_core-26.4.22/astrapi_core/modules/scheduler/icon.svg +1 -0
- astrapi_core-26.4.22/astrapi_core/modules/scheduler/job_runner.py +135 -0
- astrapi_core-26.4.22/astrapi_core/modules/scheduler/templates/content.html +16 -0
- astrapi_core-26.4.22/astrapi_core/modules/scheduler/templates/modals/edit.html +369 -0
- astrapi_core-26.4.22/astrapi_core/modules/scheduler/templates/partials/list.html +126 -0
- astrapi_core-26.4.22/astrapi_core/modules/scheduler/ui.py +168 -0
- astrapi_core-26.4.22/astrapi_core/modules/settings/__init__.py +11 -0
- astrapi_core-26.4.22/astrapi_core/modules/settings/engine.py +58 -0
- astrapi_core-26.4.22/astrapi_core/modules/settings/icon-outline.svg +1 -0
- astrapi_core-26.4.22/astrapi_core/modules/settings/icon.svg +1 -0
- astrapi_core-26.4.22/astrapi_core/modules/settings/templates/content.html +4 -0
- astrapi_core-26.4.22/astrapi_core/modules/settings/templates/partials/module_card.html +164 -0
- astrapi_core-26.4.22/astrapi_core/modules/settings/templates/partials/ssh_key.html +35 -0
- astrapi_core-26.4.22/astrapi_core/modules/settings/ui.py +116 -0
- astrapi_core-26.4.22/astrapi_core/modules/system/__init__.py +14 -0
- astrapi_core-26.4.22/astrapi_core/modules/system/api.py +48 -0
- astrapi_core-26.4.22/astrapi_core/modules/system/engine.py +287 -0
- astrapi_core-26.4.22/astrapi_core/modules/system/icon-outline.svg +1 -0
- astrapi_core-26.4.22/astrapi_core/modules/system/icon.svg +1 -0
- astrapi_core-26.4.22/astrapi_core/modules/system/templates/content.html +51 -0
- astrapi_core-26.4.22/astrapi_core/modules/system/templates/modals/update.html +18 -0
- astrapi_core-26.4.22/astrapi_core/modules/system/templates/partials/metrics.html +204 -0
- astrapi_core-26.4.22/astrapi_core/modules/system/templates/partials/update_log.html +49 -0
- astrapi_core-26.4.22/astrapi_core/modules/system/ui.py +56 -0
- astrapi_core-26.4.22/astrapi_core/modules/system/updater.py +296 -0
- astrapi_core-26.4.22/astrapi_core/navigation.yaml +24 -0
- astrapi_core-26.4.22/astrapi_core/system/__init__.py +0 -0
- astrapi_core-26.4.22/astrapi_core/system/activity_log.py +310 -0
- astrapi_core-26.4.22/astrapi_core/system/cmd.py +100 -0
- astrapi_core-26.4.22/astrapi_core/system/db.py +304 -0
- astrapi_core-26.4.22/astrapi_core/system/format.py +13 -0
- astrapi_core-26.4.22/astrapi_core/system/health.py +43 -0
- astrapi_core-26.4.22/astrapi_core/system/logger.py +170 -0
- astrapi_core-26.4.22/astrapi_core/system/paths.py +164 -0
- astrapi_core-26.4.22/astrapi_core/system/reachability.py +37 -0
- astrapi_core-26.4.22/astrapi_core/system/secrets.py +124 -0
- astrapi_core-26.4.22/astrapi_core/system/systemd.py +42 -0
- astrapi_core-26.4.22/astrapi_core/system/version.py +62 -0
- astrapi_core-26.4.22/astrapi_core/ui/__init__.py +12 -0
- astrapi_core-26.4.22/astrapi_core/ui/_base.py +80 -0
- astrapi_core-26.4.22/astrapi_core/ui/app.py +473 -0
- astrapi_core-26.4.22/astrapi_core/ui/crud_blueprint.py +321 -0
- astrapi_core-26.4.22/astrapi_core/ui/crud_router.py +101 -0
- astrapi_core-26.4.22/astrapi_core/ui/fastapi_templates.py +21 -0
- astrapi_core-26.4.22/astrapi_core/ui/field_resolver.py +28 -0
- astrapi_core-26.4.22/astrapi_core/ui/htmx_crud_router.py +192 -0
- astrapi_core-26.4.22/astrapi_core/ui/icons/.gitkeep +0 -0
- astrapi_core-26.4.22/astrapi_core/ui/icons/circle-outline.svg +1 -0
- astrapi_core-26.4.22/astrapi_core/ui/icons/circle.svg +1 -0
- astrapi_core-26.4.22/astrapi_core/ui/icons/moon.svg +1 -0
- astrapi_core-26.4.22/astrapi_core/ui/icons/sun.svg +1 -0
- astrapi_core-26.4.22/astrapi_core/ui/icons.py +84 -0
- astrapi_core-26.4.22/astrapi_core/ui/module_loader.py +213 -0
- astrapi_core-26.4.22/astrapi_core/ui/module_registry.py +374 -0
- astrapi_core-26.4.22/astrapi_core/ui/page_factory.py +99 -0
- astrapi_core-26.4.22/astrapi_core/ui/render.py +68 -0
- astrapi_core-26.4.22/astrapi_core/ui/schema_loader.py +63 -0
- astrapi_core-26.4.22/astrapi_core/ui/settings_registry.py +206 -0
- astrapi_core-26.4.22/astrapi_core/ui/static/css/app.css +1265 -0
- astrapi_core-26.4.22/astrapi_core/ui/static/icons/icon-192.png +0 -0
- astrapi_core-26.4.22/astrapi_core/ui/static/icons/icon-512.png +0 -0
- astrapi_core-26.4.22/astrapi_core/ui/static/js/app.js +197 -0
- astrapi_core-26.4.22/astrapi_core/ui/static/js/components/navigation.js +34 -0
- astrapi_core-26.4.22/astrapi_core/ui/static/manifest.json +24 -0
- astrapi_core-26.4.22/astrapi_core/ui/static/swagger.html +26 -0
- astrapi_core-26.4.22/astrapi_core/ui/static/ui_map.html +400 -0
- astrapi_core-26.4.22/astrapi_core/ui/storage.py +218 -0
- astrapi_core-26.4.22/astrapi_core/ui/store.py +99 -0
- astrapi_core-26.4.22/astrapi_core/ui/swagger_utils.py +174 -0
- astrapi_core-26.4.22/astrapi_core/ui/templates/_base.html +88 -0
- astrapi_core-26.4.22/astrapi_core/ui/templates/content.html +12 -0
- astrapi_core-26.4.22/astrapi_core/ui/templates/index.html +90 -0
- astrapi_core-26.4.22/astrapi_core/ui/templates/navigation/index.html +93 -0
- astrapi_core-26.4.22/astrapi_core/ui/templates/navigation/items.yaml +21 -0
- astrapi_core-26.4.22/astrapi_core/ui/templates/partials/components/card.html +21 -0
- astrapi_core-26.4.22/astrapi_core/ui/templates/partials/components/sprites/icons.svg +191 -0
- astrapi_core-26.4.22/astrapi_core/ui/templates/partials/components/ui_macros.html +290 -0
- astrapi_core-26.4.22/astrapi_core/ui/templates/partials/confirm_modal.html +57 -0
- astrapi_core-26.4.22/astrapi_core/ui/templates/partials/create_edit/create_edit_modal.html +198 -0
- astrapi_core-26.4.22/astrapi_core/ui/templates/partials/create_edit/field_renderer.html +124 -0
- astrapi_core-26.4.22/astrapi_core/ui/templates/partials/create_edit/list_field.html +40 -0
- astrapi_core-26.4.22/astrapi_core/ui/templates/partials/list_wrapper_inner.html +275 -0
- astrapi_core-26.4.22/astrapi_core/ui/templates/partials/lists/settings.html +61 -0
- astrapi_core-26.4.22/astrapi_core/ui/templates/partials/log_content.html +12 -0
- astrapi_core-26.4.22/astrapi_core/ui/templates/partials/log_modal.html +127 -0
- astrapi_core-26.4.22/astrapi_core/ui/templates/partials/notify_card.html +119 -0
- astrapi_core-26.4.22/astrapi_core/ui/templates/partials/preview_modal.html +57 -0
- astrapi_core-26.4.22/astrapi_core/ui/templates/partials/settings_modal.html +156 -0
- astrapi_core-26.4.22/astrapi_core.egg-info/PKG-INFO +3 -0
- astrapi_core-26.4.22/astrapi_core.egg-info/SOURCES.txt +147 -0
- astrapi_core-26.4.22/astrapi_core.egg-info/dependency_links.txt +1 -0
- astrapi_core-26.4.22/astrapi_core.egg-info/top_level.txt +1 -0
- astrapi_core-26.4.22/dev_app/config.yaml +8 -0
- astrapi_core-26.4.22/dev_app/modules/demo_items/__init__.py +6 -0
- astrapi_core-26.4.22/dev_app/modules/demo_items/api.py +18 -0
- astrapi_core-26.4.22/dev_app/modules/demo_items/modul.yaml +6 -0
- astrapi_core-26.4.22/dev_app/modules/demo_items/schema.yaml +52 -0
- astrapi_core-26.4.22/dev_app/modules/demo_items/storage.py +4 -0
- astrapi_core-26.4.22/dev_app/modules/demo_items/templates/partials/list.html +10 -0
- astrapi_core-26.4.22/dev_app/modules/demo_items/templates/partials/list_header.html +3 -0
- astrapi_core-26.4.22/dev_app/modules/demo_items/templates/partials/list_row.html +3 -0
- astrapi_core-26.4.22/dev_app/modules/demo_items/ui.py +14 -0
- astrapi_core-26.4.22/dev_app/modules/demo_log/__init__.py +5 -0
- astrapi_core-26.4.22/dev_app/modules/demo_log/modul.yaml +5 -0
- astrapi_core-26.4.22/dev_app/modules/demo_log/templates/partials/list.html +42 -0
- astrapi_core-26.4.22/dev_app/modules/demo_log/ui.py +23 -0
- astrapi_core-26.4.22/icon-migration.md +181 -0
- astrapi_core-26.4.22/main.py +89 -0
- astrapi_core-26.4.22/pyproject.toml +14 -0
- astrapi_core-26.4.22/requirements.txt +32 -0
- astrapi_core-26.4.22/setup.cfg +4 -0
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
# astrapi-core – Claude Memory
|
|
2
|
+
|
|
3
|
+
Projektkontext für Claude Code. Wird im Repo versioniert, damit es auf jedem PC verfügbar ist.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Was ist astrapi-core?
|
|
8
|
+
|
|
9
|
+
Full-stack Python-Framework, das **FastAPI** (JSON-APIs und HTML-UI) in einer ASGI-App kombiniert.
|
|
10
|
+
Basis für alle `ctl`-Apps (packagectl, backupctl, …).
|
|
11
|
+
Stellt Modul-System, generisches CRUD, Storage, Scheduler, Notifications und Activity-Log bereit.
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## Stack
|
|
16
|
+
|
|
17
|
+
| Komponente | Details |
|
|
18
|
+
|---|---|
|
|
19
|
+
| API | FastAPI (`/api/...`) |
|
|
20
|
+
| UI | FastAPI + HTMX + Jinja2 (`/`) – kein Flask mehr! |
|
|
21
|
+
| Persistenz | SQLite (`SqliteStorage`, direkte SQL-Helpers) |
|
|
22
|
+
| Scheduler | APScheduler |
|
|
23
|
+
| Verschlüsselung | Fernet (Secrets) |
|
|
24
|
+
| Python | ≥ 3.11 |
|
|
25
|
+
|
|
26
|
+
**Einstiegspunkt für Apps:** `astrapi.core.ui.create(api: FastAPI, app_root, config, extra_init, modules)`
|
|
27
|
+
|
|
28
|
+
> **Hinweis:** Flask und a2wsgi wurden entfernt. `create()` nimmt jetzt die FastAPI-Instanz als erstes Argument und konfiguriert sie in-place.
|
|
29
|
+
|
|
30
|
+
---
|
|
31
|
+
|
|
32
|
+
## Verzeichnisstruktur
|
|
33
|
+
|
|
34
|
+
```
|
|
35
|
+
astrapi/core/
|
|
36
|
+
├── ui/
|
|
37
|
+
│ ├── app.py # Flask-Factory (Haupt-Entry-Point)
|
|
38
|
+
│ ├── _base.py # Module-Dataclass
|
|
39
|
+
│ ├── module_registry.py # Auto-Discovery & Registrierung
|
|
40
|
+
│ ├── module_loader.py # Liest modul.yaml + settings.yaml
|
|
41
|
+
│ ├── crud_router.py # Generischer FastAPI-CRUD-Router
|
|
42
|
+
│ ├── crud_blueprint.py # Generischer Flask-CRUD-Blueprint
|
|
43
|
+
│ ├── storage.py # SqliteStorage (ehem. YamlStorage)
|
|
44
|
+
│ ├── settings_registry.py # Settings (SQLite-backed, thread-safe)
|
|
45
|
+
│ ├── schema_loader.py # Parst schema.yaml für Formulare
|
|
46
|
+
│ ├── field_resolver.py # Dynamische Feldoptionen
|
|
47
|
+
│ ├── page_factory.py # Auto-Routes /<key>, /ui/<key>/content
|
|
48
|
+
│ ├── static/ # CSS, JS, Icons
|
|
49
|
+
│ └── templates/ # Jinja2-Templates (index.html, partials/)
|
|
50
|
+
│
|
|
51
|
+
├── system/
|
|
52
|
+
│ ├── db.py # SQLite-Verbindungspool, register_table, CRUD-Helpers
|
|
53
|
+
│ ├── activity_log.py # Job-Run-Tracking (Runs + Log-Lines)
|
|
54
|
+
│ ├── secrets.py # Fernet-Secrets (getrennt von Backup)
|
|
55
|
+
│ ├── version.py # CalVer YY.MM.patch.devN
|
|
56
|
+
│ ├── cmd.py # Subprocess-Helpers
|
|
57
|
+
│ ├── health.py # Health-Check-Endpoints
|
|
58
|
+
│ ├── paths.py # Runtime-Pfade
|
|
59
|
+
│ └── systemd.py # sd_notify / Watchdog
|
|
60
|
+
│
|
|
61
|
+
└── modules/ # Eingebaute Core-Module
|
|
62
|
+
├── activity_log/ # Job-History-Viewer
|
|
63
|
+
├── notify/ # Benachrichtigungen (Email, Webhook, …)
|
|
64
|
+
├── scheduler/ # APScheduler-UI + engine
|
|
65
|
+
├── settings/ # Globale Einstellungs-UI
|
|
66
|
+
└── sysinfo/ # CPU/RAM/Disk (psutil)
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
---
|
|
70
|
+
|
|
71
|
+
## Key-API (Imports für Apps)
|
|
72
|
+
|
|
73
|
+
```python
|
|
74
|
+
from astrapi.core.ui import Module, create
|
|
75
|
+
from astrapi.core.ui.crud_router import make_crud_router
|
|
76
|
+
from astrapi.core.ui.crud_blueprint import make_crud_router # UI-CRUD-Router (FastAPI)
|
|
77
|
+
from astrapi.core.ui.storage import SqliteStorage, YamlStorage # YamlStorage = Alias
|
|
78
|
+
from astrapi.core.ui.settings_registry import get, set, get_module, set_module
|
|
79
|
+
from astrapi.core.system.db import register_table, load_config, get_item, create_item, update_item, delete_item
|
|
80
|
+
from astrapi.core.system.secrets import set_secret, get_secret, get_secret_safe
|
|
81
|
+
from astrapi.core.system.activity_log import add_log_entry, list_runs_for_item, get_log_lines
|
|
82
|
+
from astrapi.core.modules.notify import engine as notify_engine # notify_engine.send(...)
|
|
83
|
+
from astrapi.core.modules.scheduler.engine import configure, init, register_action
|
|
84
|
+
from astrapi.core.ui.module_loader import load_modul
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
> **Hinweis:** `YamlStorage` ist ein Alias für `SqliteStorage`. Die alten YAML-Dateien werden beim ersten Zugriff automatisch migriert.
|
|
88
|
+
|
|
89
|
+
---
|
|
90
|
+
|
|
91
|
+
## Modul-Konvention
|
|
92
|
+
|
|
93
|
+
Jedes Modul unter `{app}/modules/<key>/` oder `core/modules/<key>/`:
|
|
94
|
+
|
|
95
|
+
| Datei | Inhalt |
|
|
96
|
+
|---|---|
|
|
97
|
+
| `__init__.py` | Erstellt `Module`-Instanz, registriert Scheduler-Actions |
|
|
98
|
+
| `modul.yaml` | `label`, `icon`, `nav_group`, `card_actions` |
|
|
99
|
+
| `settings.yaml` | Einstellungsfelder (Typ text/password/select, …) |
|
|
100
|
+
| `schema.yaml` | Formularfelder für CRUD-Modal |
|
|
101
|
+
| `api.py` | FastAPI-Router (`make_crud_router` o. manuell) |
|
|
102
|
+
| `ui.py` | FastAPI-Router (`make_crud_router` + Zusatz-Routen) |
|
|
103
|
+
| `engine.py` | Business-Logik |
|
|
104
|
+
| `storage.py` | `store = SqliteStorage(KEY)` |
|
|
105
|
+
| `templates/content.html` | Vollständiger Modul-Inhalt (page-header + Listenbereich) |
|
|
106
|
+
| `templates/partials/card_body.html` | Card-Body-Snippet (meta-grid), eingebunden per `content_template` |
|
|
107
|
+
| `templates/partials/list_header.html` | Tabellen-Header-Spalten (optional) |
|
|
108
|
+
| `templates/partials/list_row.html` | Tabellen-Zeilen-Spalten (optional) |
|
|
109
|
+
| `templates/partials/` | Weitere kleine HTMX-Fragmente (rows, metrics, …) |
|
|
110
|
+
| `templates/modals/` | Eigenständige Modal-Dialoge (edit.html, log.html, …) |
|
|
111
|
+
|
|
112
|
+
**Card-Action-Typen:** `run`, `run_debug`, `log`, `search`, `bar-chart`, `power-on`, `power-off`, `scan-host-key`, `preview`, `archives`, `stats`
|
|
113
|
+
|
|
114
|
+
---
|
|
115
|
+
|
|
116
|
+
## Template-Auflösung
|
|
117
|
+
|
|
118
|
+
Priorität: **App-Templates > Core-Templates > Modul-Templates**
|
|
119
|
+
Realisiert via `ChoiceLoader` – Apps können Core-Templates überschreiben.
|
|
120
|
+
|
|
121
|
+
---
|
|
122
|
+
|
|
123
|
+
## Datenbank
|
|
124
|
+
|
|
125
|
+
- `db.configure(path)` – muss vor jedem DB-Zugriff aufgerufen werden
|
|
126
|
+
- `register_table(key, ddl, list_fields, col_in, col_out)` – Tabelle deklarieren
|
|
127
|
+
- `create_all_registered_tables()` – alle registrierten Tabellen anlegen
|
|
128
|
+
- Generische CRUD-Helpers: `load_config`, `get_item`, `create_item`, `update_item`, `delete_item`
|
|
129
|
+
- Thread-lokale Verbindungen (`check_same_thread=False`)
|
|
130
|
+
|
|
131
|
+
---
|
|
132
|
+
|
|
133
|
+
## Settings
|
|
134
|
+
|
|
135
|
+
```python
|
|
136
|
+
# Global
|
|
137
|
+
from astrapi.core.ui.settings_registry import get, set
|
|
138
|
+
val = get("MY_KEY", default="fallback")
|
|
139
|
+
set("MY_KEY", "value")
|
|
140
|
+
|
|
141
|
+
# Modul-spezifisch
|
|
142
|
+
from astrapi.core.ui.settings_registry import get_module, set_module
|
|
143
|
+
val = get_module("mymod", "timeout", default="30")
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
---
|
|
147
|
+
|
|
148
|
+
## schema.yaml – Feld-Typen
|
|
149
|
+
|
|
150
|
+
`text`, `number`, `boolean`, `select` (mit `options`-Liste), `list` (Multi-Value), `password` (verschlüsselt in DB)
|
|
151
|
+
|
|
152
|
+
---
|
|
153
|
+
|
|
154
|
+
## Versionsschema
|
|
155
|
+
|
|
156
|
+
CalVer: `YY.MM.patch.devN` – monatlicher Reset des Patch-Counters.
|
|
157
|
+
Release-Automatisierung via `release.sh`.
|
|
158
|
+
|
|
159
|
+
---
|
|
160
|
+
|
|
161
|
+
## Tests
|
|
162
|
+
|
|
163
|
+
- pytest, httpx (FastAPI-Tests), playwright (E2E / Browser-Tests)
|
|
164
|
+
- Entwicklungs-App: `dev_app/` mit Demo-Modulen (`demo_items`, `demo_log`)
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
name: Publish to PyPI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
tags:
|
|
6
|
+
- "v*"
|
|
7
|
+
|
|
8
|
+
jobs:
|
|
9
|
+
publish:
|
|
10
|
+
runs-on: ubuntu-latest
|
|
11
|
+
permissions:
|
|
12
|
+
id-token: write # für Trusted Publishing (optional, aber empfohlen)
|
|
13
|
+
|
|
14
|
+
steps:
|
|
15
|
+
- uses: actions/checkout@v4
|
|
16
|
+
with:
|
|
17
|
+
fetch-depth: 0 # nötig für setuptools-scm
|
|
18
|
+
|
|
19
|
+
- uses: actions/setup-python@v5
|
|
20
|
+
with:
|
|
21
|
+
python-version: "3.12"
|
|
22
|
+
|
|
23
|
+
- name: Build
|
|
24
|
+
run: |
|
|
25
|
+
pip install --quiet build
|
|
26
|
+
python -m build --wheel --sdist
|
|
27
|
+
|
|
28
|
+
- name: Publish to PyPI
|
|
29
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
30
|
+
with:
|
|
31
|
+
password: ${{ secrets.PYPI_API_TOKEN }}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# Python
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*.egg-info/
|
|
5
|
+
|
|
6
|
+
# Virtuelle Umgebungen
|
|
7
|
+
.venv/
|
|
8
|
+
venv/
|
|
9
|
+
env/
|
|
10
|
+
|
|
11
|
+
# Build & Distribution
|
|
12
|
+
build/
|
|
13
|
+
dist/
|
|
14
|
+
*.zip
|
|
15
|
+
|
|
16
|
+
# Tests & Coverage
|
|
17
|
+
.pytest_cache/
|
|
18
|
+
.coverage
|
|
19
|
+
htmlcov/
|
|
20
|
+
|
|
21
|
+
# Datenbank
|
|
22
|
+
app/data/*.db
|
|
23
|
+
app/data/*.db-shm
|
|
24
|
+
app/data/*.db-wal
|
|
25
|
+
dev_app/data/*.db
|
|
26
|
+
dev_app/data/*.db-shm
|
|
27
|
+
dev_app/data/*.db-wal
|
|
28
|
+
|
|
29
|
+
# Logs
|
|
30
|
+
*.log
|
|
31
|
+
|
|
32
|
+
# Secrets
|
|
33
|
+
.env
|
|
34
|
+
.env.*
|
|
35
|
+
*.key
|
|
36
|
+
|
|
37
|
+
# IDE
|
|
38
|
+
.idea/
|
|
39
|
+
.vscode/
|
|
40
|
+
.release.env
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
stages:
|
|
2
|
+
- publish
|
|
3
|
+
|
|
4
|
+
publish:
|
|
5
|
+
stage: publish
|
|
6
|
+
image: python:3.12-slim
|
|
7
|
+
rules:
|
|
8
|
+
- if: '$CI_COMMIT_TAG =~ /^v/'
|
|
9
|
+
before_script:
|
|
10
|
+
- apt-get update -qq && apt-get install -y -qq git
|
|
11
|
+
- git fetch --tags
|
|
12
|
+
script:
|
|
13
|
+
- pip install --quiet build twine
|
|
14
|
+
- python -m build --wheel --sdist
|
|
15
|
+
- twine upload
|
|
16
|
+
--repository-url "${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/packages/pypi"
|
|
17
|
+
--username "__token__"
|
|
18
|
+
--password "$GITLAB_TOKEN_SECRET"
|
|
19
|
+
dist/*
|
|
20
|
+
- twine upload
|
|
21
|
+
--repository-url "https://gitlab.com/api/v4/projects/81004951/packages/pypi"
|
|
22
|
+
--username "__token__"
|
|
23
|
+
--password "$GITLAB_TOKEN_SECRET"
|
|
24
|
+
dist/*
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"python-envs.pythonProjects": [
|
|
3
|
+
{
|
|
4
|
+
"path": ".",
|
|
5
|
+
"envManager": "ms-python.python:venv",
|
|
6
|
+
"packageManager": "ms-python.python:pip"
|
|
7
|
+
}
|
|
8
|
+
],
|
|
9
|
+
"python.defaultInterpreterPath": "${workspaceFolder}/.venv/bin/python",
|
|
10
|
+
"python.testing.pytestEnabled": true,
|
|
11
|
+
"python.testing.unittestEnabled": false,
|
|
12
|
+
"python.testing.pytestArgs": [
|
|
13
|
+
"tests"
|
|
14
|
+
]
|
|
15
|
+
}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
# core/modules/activity_log/api.py
|
|
2
|
+
import json
|
|
3
|
+
from datetime import datetime, timedelta
|
|
4
|
+
|
|
5
|
+
from fastapi import APIRouter, Request
|
|
6
|
+
from fastapi.responses import HTMLResponse
|
|
7
|
+
|
|
8
|
+
from astrapi_core.ui.fastapi_templates import get_templates
|
|
9
|
+
from .engine import (
|
|
10
|
+
list_activity, get_activity_log, clear_activity_log, get_log_lines,
|
|
11
|
+
enrich, registered_modules, fmt_duration, fmt_bytes,
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
router = APIRouter(tags=["activity_log"])
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@router.get("/clear-confirm", response_class=HTMLResponse)
|
|
18
|
+
def activity_log_clear_confirm(request: Request):
|
|
19
|
+
return get_templates().TemplateResponse(request, "partials/confirm_modal.html", {
|
|
20
|
+
"description": "Alle Activity-Log-Einträge",
|
|
21
|
+
"verb": "löschen",
|
|
22
|
+
"confirm_url": "/api/activity_log/clear",
|
|
23
|
+
"method": "delete",
|
|
24
|
+
"container_id": "tab-activity_log",
|
|
25
|
+
"loading_id": "activity_log-loading",
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@router.delete("/clear", response_class=HTMLResponse)
|
|
30
|
+
def activity_log_clear(request: Request):
|
|
31
|
+
clear_activity_log()
|
|
32
|
+
return get_templates().TemplateResponse(request, "activity_log/content.html", {
|
|
33
|
+
"entries": [],
|
|
34
|
+
"modules": registered_modules(),
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
@router.get("/tab", response_class=HTMLResponse)
|
|
39
|
+
def activity_log_tab(request: Request):
|
|
40
|
+
entries = enrich(list_activity(limit=200))
|
|
41
|
+
return get_templates().TemplateResponse(request, "activity_log/content.html", {
|
|
42
|
+
"entries": entries,
|
|
43
|
+
"modules": registered_modules(),
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
@router.get("/rows", response_class=HTMLResponse)
|
|
48
|
+
def activity_log_rows(
|
|
49
|
+
request: Request,
|
|
50
|
+
log_type: str = "",
|
|
51
|
+
module: str = "",
|
|
52
|
+
status: str = "",
|
|
53
|
+
date_range: str = "30d",
|
|
54
|
+
search: str = "",
|
|
55
|
+
):
|
|
56
|
+
date_from = None
|
|
57
|
+
if date_range == "24h":
|
|
58
|
+
date_from = (datetime.now() - timedelta(days=1)).strftime("%Y-%m-%d")
|
|
59
|
+
elif date_range == "7d":
|
|
60
|
+
date_from = (datetime.now() - timedelta(days=7)).strftime("%Y-%m-%d")
|
|
61
|
+
elif date_range == "30d":
|
|
62
|
+
date_from = (datetime.now() - timedelta(days=30)).strftime("%Y-%m-%d")
|
|
63
|
+
|
|
64
|
+
entries = enrich(list_activity(
|
|
65
|
+
limit=200,
|
|
66
|
+
log_type=log_type or None,
|
|
67
|
+
module=module or None,
|
|
68
|
+
status=status or None,
|
|
69
|
+
date_from=date_from,
|
|
70
|
+
search=search or None,
|
|
71
|
+
))
|
|
72
|
+
return get_templates().TemplateResponse(request, "activity_log/partials/rows.html", {
|
|
73
|
+
"entries": entries,
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
@router.get("/{log_id}/detail", response_class=HTMLResponse)
|
|
78
|
+
def activity_log_detail(request: Request, log_id: int):
|
|
79
|
+
entry = get_activity_log(log_id)
|
|
80
|
+
if not entry:
|
|
81
|
+
return HTMLResponse("<div>Log-Eintrag nicht gefunden</div>")
|
|
82
|
+
entry["duration_fmt"] = fmt_duration(entry.get("duration_s"))
|
|
83
|
+
entry["bytes_fmt"] = fmt_bytes(entry.get("bytes_processed"))
|
|
84
|
+
if entry.get("metadata"):
|
|
85
|
+
try:
|
|
86
|
+
entry["metadata_dict"] = json.loads(entry["metadata"])
|
|
87
|
+
except Exception:
|
|
88
|
+
entry["metadata_dict"] = {}
|
|
89
|
+
return get_templates().TemplateResponse(request, "activity_log/modals/detail.html", {
|
|
90
|
+
"entry": entry,
|
|
91
|
+
})
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
@router.get("/{log_id}/log", response_class=HTMLResponse)
|
|
95
|
+
def activity_log_viewer(request: Request, log_id: int):
|
|
96
|
+
entry = get_activity_log(log_id)
|
|
97
|
+
if not entry:
|
|
98
|
+
return HTMLResponse("<div>Log nicht gefunden</div>")
|
|
99
|
+
lines = get_log_lines(log_id)
|
|
100
|
+
full_log = "\n".join(f"[{r['level']}] {r['line']}" for r in lines) if lines else entry.get("full_log", "")
|
|
101
|
+
return get_templates().TemplateResponse(request, "activity_log/modals/log_viewer.html", {
|
|
102
|
+
"entry": entry,
|
|
103
|
+
"full_log": full_log,
|
|
104
|
+
})
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# core/modules/activity_log/engine.py
|
|
2
|
+
|
|
3
|
+
from astrapi_core.system.activity_log import (
|
|
4
|
+
log_activity, update_activity_log,
|
|
5
|
+
list_activity, get_activity_log, clear_activity_log,
|
|
6
|
+
get_log_lines, get_latest_activity_log_id, list_runs_for_item,
|
|
7
|
+
history_start, history_finish, list_history,
|
|
8
|
+
append_log_line,
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
KEY = "activity_log"
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def fmt_duration(s: int | None) -> str:
|
|
15
|
+
if s is None:
|
|
16
|
+
return "—"
|
|
17
|
+
if s < 60:
|
|
18
|
+
return f"{s}s"
|
|
19
|
+
m, sec = divmod(s, 60)
|
|
20
|
+
if m < 60:
|
|
21
|
+
return f"{m}m {sec}s"
|
|
22
|
+
h, min_ = divmod(m, 60)
|
|
23
|
+
return f"{h}h {min_}m"
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
from astrapi_core.system.format import fmt_bytes
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def enrich(entries: list) -> list:
|
|
30
|
+
for e in entries:
|
|
31
|
+
e["duration_fmt"] = fmt_duration(e.get("duration_s"))
|
|
32
|
+
e["bytes_fmt"] = fmt_bytes(e.get("bytes_processed"))
|
|
33
|
+
return entries
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def registered_modules() -> list[str]:
|
|
37
|
+
try:
|
|
38
|
+
from astrapi_core.ui.module_registry import _mod_registry
|
|
39
|
+
return [key for key in _mod_registry if not key.startswith("_")]
|
|
40
|
+
except Exception:
|
|
41
|
+
return []
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M4 2A2 2 0 0 0 2 4V20A2 2 0 0 0 4 22H12.41A7 7 0 0 0 16 23A7 7 0 0 0 23 16A7 7 0 0 0 18 9.3V8L12 2H4M4 4H11V9H16A7 7 0 0 0 9 16A7 7 0 0 0 10.26 20H4V4M16 11A5 5 0 0 1 21 16A5 5 0 0 1 16 21A5 5 0 0 1 11 16A5 5 0 0 1 16 11M15 12V17L18.61 19.16L19.36 17.94L16.5 16.25V12H15Z" /></svg>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M4 2C2.89 2 2 2.89 2 4V20A2 2 0 0 0 4 22H12.41A7 7 0 0 0 16 23A7 7 0 0 0 23 16A7 7 0 0 0 18 9.3V8L12 2H4M11 3.5L16.5 9H11V3.5M16 11A5 5 0 0 1 21 16A5 5 0 0 1 16 21A5 5 0 0 1 11 16A5 5 0 0 1 16 11M15 12V17L18.61 19.16L19.36 17.94L16.5 16.25V12H15Z" /></svg>
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
{% extends "content.html" %}
|
|
2
|
+
{% block inner %}
|
|
3
|
+
|
|
4
|
+
<div class="ds-list-table-wrap">
|
|
5
|
+
<div class="table-toolbar" data-debug-label="table-toolbar">
|
|
6
|
+
<div class="table-toolbar-title">Activity Log</div>
|
|
7
|
+
<div class="table-toolbar-right">
|
|
8
|
+
<select class="form-select table-filter-select" name="log_type"
|
|
9
|
+
hx-get="/api/activity_log/rows" hx-target="#activity-log-rows" hx-swap="innerHTML" hx-trigger="change"
|
|
10
|
+
hx-include="[name='module'],[name='status'],[name='date_range']">
|
|
11
|
+
<option value="">Alle Typen</option>
|
|
12
|
+
<option value="job">Jobs</option>
|
|
13
|
+
<option value="scheduler">Scheduler</option>
|
|
14
|
+
<option value="error">Errors</option>
|
|
15
|
+
<option value="warning">Warnings</option>
|
|
16
|
+
<option value="system">System</option>
|
|
17
|
+
</select>
|
|
18
|
+
<select class="form-select table-filter-select" name="module"
|
|
19
|
+
hx-get="/api/activity_log/rows" hx-target="#activity-log-rows" hx-swap="innerHTML" hx-trigger="change"
|
|
20
|
+
hx-include="[name='log_type'],[name='status'],[name='date_range']">
|
|
21
|
+
<option value="">Alle Module</option>
|
|
22
|
+
{% for m in modules %}
|
|
23
|
+
<option value="{{ m }}">{{ m.replace('_',' ').title() }}</option>
|
|
24
|
+
{% endfor %}
|
|
25
|
+
</select>
|
|
26
|
+
<select class="form-select table-filter-select" name="status"
|
|
27
|
+
hx-get="/api/activity_log/rows" hx-target="#activity-log-rows" hx-swap="innerHTML" hx-trigger="change"
|
|
28
|
+
hx-include="[name='log_type'],[name='module'],[name='date_range']">
|
|
29
|
+
<option value="">Alle Status</option>
|
|
30
|
+
<option value="ok">OK</option>
|
|
31
|
+
<option value="error">Fehler</option>
|
|
32
|
+
<option value="warning">Warnung</option>
|
|
33
|
+
<option value="running">Läuft</option>
|
|
34
|
+
<option value="skipped">Übersprungen</option>
|
|
35
|
+
</select>
|
|
36
|
+
<select class="form-select table-filter-select" name="date_range"
|
|
37
|
+
hx-get="/api/activity_log/rows" hx-target="#activity-log-rows" hx-swap="innerHTML" hx-trigger="change"
|
|
38
|
+
hx-include="[name='log_type'],[name='module'],[name='status']">
|
|
39
|
+
<option value="24h">Letzte 24h</option>
|
|
40
|
+
<option value="7d">7 Tage</option>
|
|
41
|
+
<option value="30d" selected>30 Tage</option>
|
|
42
|
+
<option value="">Alle</option>
|
|
43
|
+
</select>
|
|
44
|
+
<button class="btn btn-danger btn-sm"
|
|
45
|
+
hx-get="/api/activity_log/clear-confirm"
|
|
46
|
+
hx-target="body" hx-swap="beforeend">
|
|
47
|
+
Log leeren
|
|
48
|
+
</button>
|
|
49
|
+
</div>
|
|
50
|
+
</div>
|
|
51
|
+
<div class="ds-list-table-wrap-scroll">
|
|
52
|
+
<table class="ds-list-table">
|
|
53
|
+
<thead>
|
|
54
|
+
<tr>
|
|
55
|
+
<th>Zeitpunkt</th>
|
|
56
|
+
<th class="col-hide-sm">Typ</th>
|
|
57
|
+
<th class="col-hide-sm">Modul</th>
|
|
58
|
+
<th>Beschreibung</th>
|
|
59
|
+
<th class="col-status">Status</th>
|
|
60
|
+
<th class="col-hide-sm">Dauer</th>
|
|
61
|
+
<th class="col-actions"></th>
|
|
62
|
+
</tr>
|
|
63
|
+
</thead>
|
|
64
|
+
<tbody id="activity-log-rows">
|
|
65
|
+
{% include "activity_log/partials/rows.html" %}
|
|
66
|
+
</tbody>
|
|
67
|
+
</table>
|
|
68
|
+
</div>
|
|
69
|
+
</div>
|
|
70
|
+
|
|
71
|
+
{% endblock %}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
{# activity_log/modals/detail.html #}
|
|
2
|
+
{% from "partials/components/ui_macros.html" import modal_title, modal_footer_simple %}
|
|
3
|
+
<div class="ds-modal-backdrop">
|
|
4
|
+
<div class="ds-modal" style="max-width:600px;">
|
|
5
|
+
|
|
6
|
+
{{ modal_title(title="Activity Log – Details") }}
|
|
7
|
+
|
|
8
|
+
<div style="display:grid; grid-template-columns:130px 1fr; gap:10px 16px; font-size:13px; padding:4px 0;">
|
|
9
|
+
|
|
10
|
+
<div style="color:var(--text-3); font-weight:600;">Zeitpunkt</div>
|
|
11
|
+
<div>{{ entry.created_at }}</div>
|
|
12
|
+
|
|
13
|
+
<div style="color:var(--text-3); font-weight:600;">Typ</div>
|
|
14
|
+
<div>
|
|
15
|
+
<span style="font-family:var(--mono); font-size:11px; padding:2px 6px;
|
|
16
|
+
border-radius:3px; background:rgba(255,255,255,0.07);">
|
|
17
|
+
{{ entry.log_type }}
|
|
18
|
+
</span>
|
|
19
|
+
</div>
|
|
20
|
+
|
|
21
|
+
<div style="color:var(--text-3); font-weight:600;">Modul</div>
|
|
22
|
+
<div>
|
|
23
|
+
<span style="font-family:var(--mono); font-size:11px; padding:2px 6px;
|
|
24
|
+
border-radius:3px; background:rgba(255,255,255,0.07);">
|
|
25
|
+
{{ entry.module }}
|
|
26
|
+
</span>
|
|
27
|
+
</div>
|
|
28
|
+
|
|
29
|
+
<div style="color:var(--text-3); font-weight:600;">Beschreibung</div>
|
|
30
|
+
<div>{{ entry.description }}</div>
|
|
31
|
+
|
|
32
|
+
<div style="color:var(--text-3); font-weight:600;">Status</div>
|
|
33
|
+
<div>
|
|
34
|
+
{% if entry.status == 'ok' %}<span style="color:var(--g);">✓ OK</span>
|
|
35
|
+
{% elif entry.status == 'error' %}<span style="color:var(--err);">✗ Fehler</span>
|
|
36
|
+
{% elif entry.status == 'warning' %}<span style="color:#f59e0b;">⚠ Warnung</span>
|
|
37
|
+
{% elif entry.status == 'running' %}<span style="color:#60a5fa;">● läuft</span>
|
|
38
|
+
{% else %}<span>{{ entry.status }}</span>{% endif %}
|
|
39
|
+
</div>
|
|
40
|
+
|
|
41
|
+
{% if entry.mode %}
|
|
42
|
+
<div style="color:var(--text-3); font-weight:600;">Modus</div>
|
|
43
|
+
<div>{{ entry.mode }}</div>
|
|
44
|
+
{% endif %}
|
|
45
|
+
|
|
46
|
+
{% if entry.duration_fmt %}
|
|
47
|
+
<div style="color:var(--text-3); font-weight:600;">Dauer</div>
|
|
48
|
+
<div style="font-family:var(--mono);">{{ entry.duration_fmt }}</div>
|
|
49
|
+
{% endif %}
|
|
50
|
+
|
|
51
|
+
{% if entry.bytes_processed %}
|
|
52
|
+
<div style="color:var(--text-3); font-weight:600;">Bytes</div>
|
|
53
|
+
<div style="font-family:var(--mono);">{{ entry.bytes_fmt }}</div>
|
|
54
|
+
{% endif %}
|
|
55
|
+
|
|
56
|
+
{% if entry.items_count %}
|
|
57
|
+
<div style="color:var(--text-3); font-weight:600;">Items</div>
|
|
58
|
+
<div>{{ entry.items_count }}</div>
|
|
59
|
+
{% endif %}
|
|
60
|
+
|
|
61
|
+
{% if entry.finished_at %}
|
|
62
|
+
<div style="color:var(--text-3); font-weight:600;">Beendet</div>
|
|
63
|
+
<div>{{ entry.finished_at }}</div>
|
|
64
|
+
{% endif %}
|
|
65
|
+
|
|
66
|
+
{% if entry.error_message %}
|
|
67
|
+
<div style="color:var(--text-3); font-weight:600;">Fehler-Code</div>
|
|
68
|
+
<div style="font-family:var(--mono);">{{ entry.error_code or '—' }}</div>
|
|
69
|
+
|
|
70
|
+
<div style="color:var(--text-3); font-weight:600; grid-column:1">Fehlermeldung</div>
|
|
71
|
+
<div style="grid-column:2; font-family:var(--mono); font-size:11px;
|
|
72
|
+
padding:8px; background:rgba(255,0,0,0.06); border-radius:4px; overflow-x:auto;">
|
|
73
|
+
{{ entry.error_message }}
|
|
74
|
+
</div>
|
|
75
|
+
{% endif %}
|
|
76
|
+
|
|
77
|
+
{% if entry.error_traceback %}
|
|
78
|
+
<div style="color:var(--text-3); font-weight:600; grid-column:1/3;">Traceback</div>
|
|
79
|
+
<div style="grid-column:1/3; font-family:var(--mono); font-size:10px;
|
|
80
|
+
padding:8px; background:rgba(255,0,0,0.04); border-radius:4px;
|
|
81
|
+
overflow-x:auto; white-space:pre-wrap;">{{ entry.error_traceback }}</div>
|
|
82
|
+
{% endif %}
|
|
83
|
+
|
|
84
|
+
{% if entry.metadata_dict %}
|
|
85
|
+
<div style="color:var(--text-3); font-weight:600; grid-column:1/3;">Metadaten</div>
|
|
86
|
+
<div style="grid-column:1/3; font-family:var(--mono); font-size:11px;
|
|
87
|
+
padding:8px; background:rgba(255,255,255,0.03); border-radius:4px;">
|
|
88
|
+
{% for k, v in entry.metadata_dict.items() %}
|
|
89
|
+
<div><span style="color:var(--text-3);">{{ k }}:</span> {{ v }}</div>
|
|
90
|
+
{% endfor %}
|
|
91
|
+
</div>
|
|
92
|
+
{% endif %}
|
|
93
|
+
|
|
94
|
+
</div>
|
|
95
|
+
|
|
96
|
+
<div class="ds-modal-footer-simple">
|
|
97
|
+
{% if entry.log_type in ('job', 'scheduler') %}
|
|
98
|
+
<button class="btn btn-primary"
|
|
99
|
+
hx-get="/api/activity_log/{{ entry.id }}/log"
|
|
100
|
+
hx-target="body" hx-swap="beforeend">
|
|
101
|
+
Log anzeigen
|
|
102
|
+
</button>
|
|
103
|
+
{% endif %}
|
|
104
|
+
<button class="btn btn-secondary"
|
|
105
|
+
onclick="closeModal(this)">
|
|
106
|
+
Schließen
|
|
107
|
+
</button>
|
|
108
|
+
</div>
|
|
109
|
+
|
|
110
|
+
</div>
|
|
111
|
+
</div>
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{# activity_log/modals/log_viewer.html #}
|
|
2
|
+
{% from "partials/components/ui_macros.html" import modal_title %}
|
|
3
|
+
<div class="ds-modal-backdrop">
|
|
4
|
+
<div class="ds-modal" style="max-width:1000px; width:90%;">
|
|
5
|
+
|
|
6
|
+
{{ modal_title(title="Log: " + entry.description) }}
|
|
7
|
+
|
|
8
|
+
<pre style="margin:0; padding:12px; background:var(--bg); color:var(--text-2);
|
|
9
|
+
font-family:var(--mono); font-size:11px; line-height:1.5;
|
|
10
|
+
overflow-x:auto; white-space:pre-wrap; word-break:break-word;
|
|
11
|
+
max-height:65vh; overflow-y:auto; border-radius:4px;">{{ full_log }}</pre>
|
|
12
|
+
|
|
13
|
+
<div class="ds-modal-footer-simple">
|
|
14
|
+
<button class="btn btn-secondary"
|
|
15
|
+
onclick="navigator.clipboard.writeText(this.closest('.ds-modal').querySelector('pre').innerText)">
|
|
16
|
+
Kopieren
|
|
17
|
+
</button>
|
|
18
|
+
<button class="btn btn-secondary"
|
|
19
|
+
onclick="closeModal(this)">
|
|
20
|
+
Schließen
|
|
21
|
+
</button>
|
|
22
|
+
</div>
|
|
23
|
+
|
|
24
|
+
</div>
|
|
25
|
+
</div>
|