arthexis 0.1.13__py3-none-any.whl → 0.1.15__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.
- {arthexis-0.1.13.dist-info → arthexis-0.1.15.dist-info}/METADATA +224 -221
- arthexis-0.1.15.dist-info/RECORD +110 -0
- {arthexis-0.1.13.dist-info → arthexis-0.1.15.dist-info}/licenses/LICENSE +674 -674
- config/__init__.py +5 -5
- config/active_app.py +15 -15
- config/asgi.py +43 -43
- config/auth_app.py +7 -7
- config/celery.py +32 -32
- config/context_processors.py +67 -69
- config/horologia_app.py +7 -7
- config/loadenv.py +11 -11
- config/logging.py +59 -48
- config/middleware.py +25 -25
- config/offline.py +49 -49
- config/settings.py +691 -682
- config/settings_helpers.py +109 -109
- config/urls.py +171 -166
- config/wsgi.py +17 -17
- core/admin.py +3795 -2809
- core/admin_history.py +50 -50
- core/admindocs.py +151 -151
- core/apps.py +356 -272
- core/auto_upgrade.py +57 -57
- core/backends.py +265 -236
- core/changelog.py +342 -0
- core/entity.py +149 -133
- core/environment.py +61 -61
- core/fields.py +168 -168
- core/form_fields.py +75 -75
- core/github_helper.py +188 -25
- core/github_issues.py +178 -172
- core/github_repos.py +72 -0
- core/lcd_screen.py +78 -78
- core/liveupdate.py +25 -25
- core/log_paths.py +114 -100
- core/mailer.py +85 -85
- core/middleware.py +91 -91
- core/models.py +3637 -2795
- core/notifications.py +105 -105
- core/public_wifi.py +267 -227
- core/reference_utils.py +108 -108
- core/release.py +840 -368
- core/rfid_import_export.py +113 -0
- core/sigil_builder.py +149 -149
- core/sigil_context.py +20 -20
- core/sigil_resolver.py +315 -315
- core/system.py +952 -493
- core/tasks.py +408 -394
- core/temp_passwords.py +181 -181
- core/test_system_info.py +186 -139
- core/tests.py +2168 -1521
- core/tests_liveupdate.py +17 -17
- core/urls.py +11 -11
- core/user_data.py +641 -633
- core/views.py +2201 -1417
- core/widgets.py +213 -94
- core/workgroup_urls.py +17 -17
- core/workgroup_views.py +94 -94
- nodes/admin.py +1720 -1161
- nodes/apps.py +87 -85
- nodes/backends.py +160 -160
- nodes/dns.py +203 -203
- nodes/feature_checks.py +133 -133
- nodes/lcd.py +165 -165
- nodes/models.py +1764 -1597
- nodes/reports.py +411 -411
- nodes/rfid_sync.py +195 -0
- nodes/signals.py +18 -0
- nodes/tasks.py +46 -46
- nodes/tests.py +3830 -3116
- nodes/urls.py +15 -14
- nodes/utils.py +121 -105
- nodes/views.py +683 -619
- ocpp/admin.py +948 -948
- ocpp/apps.py +25 -25
- ocpp/consumers.py +1565 -1459
- ocpp/evcs.py +844 -844
- ocpp/evcs_discovery.py +158 -158
- ocpp/models.py +917 -917
- ocpp/reference_utils.py +42 -42
- ocpp/routing.py +11 -11
- ocpp/simulator.py +745 -745
- ocpp/status_display.py +26 -26
- ocpp/store.py +601 -541
- ocpp/tasks.py +31 -31
- ocpp/test_export_import.py +130 -130
- ocpp/test_rfid.py +913 -702
- ocpp/tests.py +4445 -4094
- ocpp/transactions_io.py +189 -189
- ocpp/urls.py +50 -50
- ocpp/views.py +1479 -1251
- pages/admin.py +769 -539
- pages/apps.py +10 -10
- pages/checks.py +40 -40
- pages/context_processors.py +127 -119
- pages/defaults.py +13 -13
- pages/forms.py +198 -198
- pages/middleware.py +209 -153
- pages/models.py +643 -426
- pages/tasks.py +74 -0
- pages/tests.py +3025 -2200
- pages/urls.py +26 -25
- pages/utils.py +23 -12
- pages/views.py +1176 -1128
- arthexis-0.1.13.dist-info/RECORD +0 -105
- nodes/actions.py +0 -70
- {arthexis-0.1.13.dist-info → arthexis-0.1.15.dist-info}/WHEEL +0 -0
- {arthexis-0.1.13.dist-info → arthexis-0.1.15.dist-info}/top_level.txt +0 -0
nodes/urls.py
CHANGED
|
@@ -1,14 +1,15 @@
|
|
|
1
|
-
from django.urls import path
|
|
2
|
-
|
|
3
|
-
from . import views
|
|
4
|
-
|
|
5
|
-
urlpatterns = [
|
|
6
|
-
path("info/", views.node_info, name="node-info"),
|
|
7
|
-
path("list/", views.node_list, name="node-list"),
|
|
8
|
-
path("register/", views.register_node, name="register-node"),
|
|
9
|
-
path("screenshot/", views.capture, name="node-screenshot"),
|
|
10
|
-
path("net-message/", views.net_message, name="net-message"),
|
|
11
|
-
path("last-message/", views.last_net_message, name="last-net-message"),
|
|
12
|
-
path("rfid/export/", views.export_rfids, name="node-rfid-export"),
|
|
13
|
-
path("
|
|
14
|
-
|
|
1
|
+
from django.urls import path
|
|
2
|
+
|
|
3
|
+
from . import views
|
|
4
|
+
|
|
5
|
+
urlpatterns = [
|
|
6
|
+
path("info/", views.node_info, name="node-info"),
|
|
7
|
+
path("list/", views.node_list, name="node-list"),
|
|
8
|
+
path("register/", views.register_node, name="register-node"),
|
|
9
|
+
path("screenshot/", views.capture, name="node-screenshot"),
|
|
10
|
+
path("net-message/", views.net_message, name="net-message"),
|
|
11
|
+
path("last-message/", views.last_net_message, name="last-net-message"),
|
|
12
|
+
path("rfid/export/", views.export_rfids, name="node-rfid-export"),
|
|
13
|
+
path("rfid/import/", views.import_rfids, name="node-rfid-import"),
|
|
14
|
+
path("<slug:endpoint>/", views.public_node_endpoint, name="node-public-endpoint"),
|
|
15
|
+
]
|
nodes/utils.py
CHANGED
|
@@ -1,105 +1,121 @@
|
|
|
1
|
-
from datetime import datetime
|
|
2
|
-
from pathlib import Path
|
|
3
|
-
import hashlib
|
|
4
|
-
import logging
|
|
5
|
-
import shutil
|
|
6
|
-
import subprocess
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
from
|
|
10
|
-
from selenium
|
|
11
|
-
from selenium.
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
logger.error("
|
|
74
|
-
raise RuntimeError(
|
|
75
|
-
if
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
if
|
|
104
|
-
|
|
105
|
-
|
|
1
|
+
from datetime import datetime
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
import hashlib
|
|
4
|
+
import logging
|
|
5
|
+
import shutil
|
|
6
|
+
import subprocess
|
|
7
|
+
import uuid
|
|
8
|
+
|
|
9
|
+
from django.conf import settings
|
|
10
|
+
from selenium import webdriver
|
|
11
|
+
from selenium.webdriver.firefox.options import Options
|
|
12
|
+
from selenium.common.exceptions import WebDriverException
|
|
13
|
+
|
|
14
|
+
from .classifiers import run_default_classifiers, suppress_default_classifiers
|
|
15
|
+
from .models import ContentSample
|
|
16
|
+
|
|
17
|
+
SCREENSHOT_DIR = settings.LOG_DIR / "screenshots"
|
|
18
|
+
CAMERA_DIR = settings.LOG_DIR / "camera"
|
|
19
|
+
logger = logging.getLogger(__name__)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def capture_screenshot(url: str, cookies=None) -> Path:
|
|
23
|
+
"""Capture a screenshot of ``url`` and save it to :data:`SCREENSHOT_DIR`.
|
|
24
|
+
|
|
25
|
+
``cookies`` can be an iterable of Selenium cookie mappings which will be
|
|
26
|
+
applied after the initial navigation and before the screenshot is taken.
|
|
27
|
+
"""
|
|
28
|
+
options = Options()
|
|
29
|
+
options.add_argument("-headless")
|
|
30
|
+
try:
|
|
31
|
+
with webdriver.Firefox(options=options) as browser:
|
|
32
|
+
browser.set_window_size(1280, 720)
|
|
33
|
+
SCREENSHOT_DIR.mkdir(parents=True, exist_ok=True)
|
|
34
|
+
filename = SCREENSHOT_DIR / f"{datetime.utcnow():%Y%m%d%H%M%S}.png"
|
|
35
|
+
try:
|
|
36
|
+
browser.get(url)
|
|
37
|
+
except WebDriverException as exc:
|
|
38
|
+
logger.error("Failed to load %s: %s", url, exc)
|
|
39
|
+
if cookies:
|
|
40
|
+
for cookie in cookies:
|
|
41
|
+
try:
|
|
42
|
+
browser.add_cookie(cookie)
|
|
43
|
+
except WebDriverException as exc:
|
|
44
|
+
logger.error("Failed to apply cookie for %s: %s", url, exc)
|
|
45
|
+
browser.get(url)
|
|
46
|
+
if not browser.save_screenshot(str(filename)):
|
|
47
|
+
raise RuntimeError("Screenshot capture failed")
|
|
48
|
+
return filename
|
|
49
|
+
except WebDriverException as exc:
|
|
50
|
+
logger.error("Failed to capture screenshot from %s: %s", url, exc)
|
|
51
|
+
raise RuntimeError(f"Screenshot capture failed: {exc}") from exc
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def capture_rpi_snapshot(timeout: int = 10) -> Path:
|
|
55
|
+
"""Capture a snapshot using the Raspberry Pi camera stack."""
|
|
56
|
+
|
|
57
|
+
tool_path = shutil.which("rpicam-still")
|
|
58
|
+
if not tool_path:
|
|
59
|
+
raise RuntimeError("rpicam-still is not available")
|
|
60
|
+
CAMERA_DIR.mkdir(parents=True, exist_ok=True)
|
|
61
|
+
timestamp = datetime.utcnow()
|
|
62
|
+
unique_suffix = uuid.uuid4().hex
|
|
63
|
+
filename = CAMERA_DIR / f"{timestamp:%Y%m%d%H%M%S}-{unique_suffix}.jpg"
|
|
64
|
+
try:
|
|
65
|
+
result = subprocess.run(
|
|
66
|
+
[tool_path, "-o", str(filename), "-t", "1"],
|
|
67
|
+
capture_output=True,
|
|
68
|
+
text=True,
|
|
69
|
+
check=False,
|
|
70
|
+
timeout=timeout,
|
|
71
|
+
)
|
|
72
|
+
except Exception as exc: # pragma: no cover - depends on camera stack
|
|
73
|
+
logger.error("Failed to invoke %s: %s", tool_path, exc)
|
|
74
|
+
raise RuntimeError(f"Snapshot capture failed: {exc}") from exc
|
|
75
|
+
if result.returncode != 0:
|
|
76
|
+
error = (result.stderr or result.stdout or "Snapshot capture failed").strip()
|
|
77
|
+
logger.error("rpicam-still exited with %s: %s", result.returncode, error)
|
|
78
|
+
raise RuntimeError(error)
|
|
79
|
+
if not filename.exists():
|
|
80
|
+
logger.error("Snapshot file %s was not created", filename)
|
|
81
|
+
raise RuntimeError("Snapshot capture failed")
|
|
82
|
+
return filename
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def save_screenshot(
|
|
86
|
+
path: Path,
|
|
87
|
+
node=None,
|
|
88
|
+
method: str = "",
|
|
89
|
+
transaction_uuid=None,
|
|
90
|
+
*,
|
|
91
|
+
content: str | None = None,
|
|
92
|
+
):
|
|
93
|
+
"""Save screenshot file info if not already recorded.
|
|
94
|
+
|
|
95
|
+
Returns the created :class:`ContentSample` or ``None`` if duplicate.
|
|
96
|
+
"""
|
|
97
|
+
|
|
98
|
+
original = path
|
|
99
|
+
if not path.is_absolute():
|
|
100
|
+
path = settings.LOG_DIR / path
|
|
101
|
+
with path.open("rb") as fh:
|
|
102
|
+
digest = hashlib.sha256(fh.read()).hexdigest()
|
|
103
|
+
if ContentSample.objects.filter(hash=digest).exists():
|
|
104
|
+
logger.info("Duplicate screenshot content; record not created")
|
|
105
|
+
return None
|
|
106
|
+
stored_path = (original if not original.is_absolute() else path).as_posix()
|
|
107
|
+
data = {
|
|
108
|
+
"node": node,
|
|
109
|
+
"path": stored_path,
|
|
110
|
+
"method": method,
|
|
111
|
+
"hash": digest,
|
|
112
|
+
"kind": ContentSample.IMAGE,
|
|
113
|
+
}
|
|
114
|
+
if transaction_uuid is not None:
|
|
115
|
+
data["transaction_uuid"] = transaction_uuid
|
|
116
|
+
if content is not None:
|
|
117
|
+
data["content"] = content
|
|
118
|
+
with suppress_default_classifiers():
|
|
119
|
+
sample = ContentSample.objects.create(**data)
|
|
120
|
+
run_default_classifiers(sample)
|
|
121
|
+
return sample
|