arthexis 0.1.9__py3-none-any.whl → 0.1.26__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.
Potentially problematic release.
This version of arthexis might be problematic. Click here for more details.
- arthexis-0.1.26.dist-info/METADATA +272 -0
- arthexis-0.1.26.dist-info/RECORD +111 -0
- {arthexis-0.1.9.dist-info → arthexis-0.1.26.dist-info}/licenses/LICENSE +674 -674
- config/__init__.py +5 -5
- config/active_app.py +15 -15
- config/asgi.py +29 -29
- config/auth_app.py +7 -7
- config/celery.py +32 -25
- config/context_processors.py +67 -68
- config/horologia_app.py +7 -7
- config/loadenv.py +11 -11
- config/logging.py +59 -48
- config/middleware.py +71 -25
- config/offline.py +49 -49
- config/settings.py +676 -492
- config/settings_helpers.py +109 -0
- config/urls.py +228 -159
- config/wsgi.py +17 -17
- core/admin.py +4052 -2066
- core/admin_history.py +50 -50
- core/admindocs.py +192 -151
- core/apps.py +350 -223
- core/auto_upgrade.py +72 -0
- core/backends.py +311 -124
- core/changelog.py +403 -0
- core/entity.py +149 -133
- core/environment.py +60 -43
- core/fields.py +168 -75
- core/form_fields.py +75 -0
- core/github_helper.py +188 -25
- core/github_issues.py +183 -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 +89 -83
- core/middleware.py +91 -91
- core/models.py +5041 -2195
- core/notifications.py +105 -105
- core/public_wifi.py +267 -227
- core/reference_utils.py +107 -0
- core/release.py +940 -346
- core/rfid_import_export.py +113 -0
- core/sigil_builder.py +149 -131
- core/sigil_context.py +20 -20
- core/sigil_resolver.py +250 -284
- core/system.py +1425 -230
- core/tasks.py +538 -199
- core/temp_passwords.py +181 -0
- core/test_system_info.py +202 -43
- core/tests.py +2673 -1069
- core/tests_liveupdate.py +17 -17
- core/urls.py +11 -11
- core/user_data.py +681 -495
- core/views.py +2484 -789
- core/widgets.py +213 -51
- nodes/admin.py +2236 -445
- nodes/apps.py +98 -70
- nodes/backends.py +160 -53
- nodes/dns.py +203 -0
- nodes/feature_checks.py +133 -0
- nodes/lcd.py +165 -165
- nodes/models.py +2375 -870
- nodes/reports.py +411 -0
- nodes/rfid_sync.py +210 -0
- nodes/signals.py +18 -0
- nodes/tasks.py +141 -46
- nodes/tests.py +5045 -1489
- nodes/urls.py +29 -13
- nodes/utils.py +172 -73
- nodes/views.py +1768 -304
- ocpp/admin.py +1775 -481
- ocpp/apps.py +25 -25
- ocpp/consumers.py +1843 -630
- ocpp/evcs.py +844 -928
- ocpp/evcs_discovery.py +158 -0
- ocpp/models.py +1417 -640
- ocpp/network.py +398 -0
- ocpp/reference_utils.py +42 -0
- ocpp/routing.py +11 -9
- ocpp/simulator.py +745 -368
- ocpp/status_display.py +26 -0
- ocpp/store.py +603 -403
- ocpp/tasks.py +479 -31
- ocpp/test_export_import.py +131 -130
- ocpp/test_rfid.py +1072 -540
- ocpp/tests.py +5494 -2296
- ocpp/transactions_io.py +197 -165
- ocpp/urls.py +50 -50
- ocpp/views.py +2024 -912
- pages/admin.py +1123 -396
- pages/apps.py +45 -10
- pages/checks.py +40 -40
- pages/context_processors.py +151 -85
- pages/defaults.py +13 -0
- pages/forms.py +221 -0
- pages/middleware.py +213 -153
- pages/models.py +720 -252
- pages/module_defaults.py +156 -0
- pages/site_config.py +137 -0
- pages/tasks.py +74 -0
- pages/tests.py +4009 -1389
- pages/urls.py +38 -20
- pages/utils.py +93 -12
- pages/views.py +1736 -762
- arthexis-0.1.9.dist-info/METADATA +0 -168
- arthexis-0.1.9.dist-info/RECORD +0 -92
- core/workgroup_urls.py +0 -17
- core/workgroup_views.py +0 -94
- nodes/actions.py +0 -70
- {arthexis-0.1.9.dist-info → arthexis-0.1.26.dist-info}/WHEEL +0 -0
- {arthexis-0.1.9.dist-info → arthexis-0.1.26.dist-info}/top_level.txt +0 -0
nodes/tasks.py
CHANGED
|
@@ -1,46 +1,141 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
import
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
from
|
|
9
|
-
from .
|
|
10
|
-
|
|
11
|
-
|
|
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
|
-
|
|
1
|
+
import base64
|
|
2
|
+
import json
|
|
3
|
+
import logging
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
import pyperclip
|
|
7
|
+
import requests
|
|
8
|
+
from celery import shared_task
|
|
9
|
+
from cryptography.hazmat.primitives import hashes, serialization
|
|
10
|
+
from cryptography.hazmat.primitives.asymmetric import padding
|
|
11
|
+
from pyperclip import PyperclipException
|
|
12
|
+
|
|
13
|
+
from .models import ContentSample, NetMessage, Node
|
|
14
|
+
from .utils import capture_screenshot, save_screenshot
|
|
15
|
+
|
|
16
|
+
logger = logging.getLogger(__name__)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@shared_task
|
|
20
|
+
def sample_clipboard() -> None:
|
|
21
|
+
"""Save current clipboard contents to a :class:`ContentSample` entry."""
|
|
22
|
+
try:
|
|
23
|
+
content = pyperclip.paste()
|
|
24
|
+
except PyperclipException as exc: # pragma: no cover - depends on OS clipboard
|
|
25
|
+
logger.error("Clipboard error: %s", exc)
|
|
26
|
+
return
|
|
27
|
+
if not content:
|
|
28
|
+
logger.info("Clipboard is empty")
|
|
29
|
+
return
|
|
30
|
+
if ContentSample.objects.filter(content=content, kind=ContentSample.TEXT).exists():
|
|
31
|
+
logger.info("Duplicate clipboard content; sample not created")
|
|
32
|
+
return
|
|
33
|
+
node = Node.get_local()
|
|
34
|
+
ContentSample.objects.create(content=content, node=node, kind=ContentSample.TEXT)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
@shared_task
|
|
38
|
+
def capture_node_screenshot(
|
|
39
|
+
url: str | None = None, port: int = 8000, method: str = "TASK"
|
|
40
|
+
) -> str:
|
|
41
|
+
"""Capture a screenshot of ``url`` and record it as a :class:`ContentSample`."""
|
|
42
|
+
if url is None:
|
|
43
|
+
url = f"http://localhost:{port}"
|
|
44
|
+
try:
|
|
45
|
+
path: Path = capture_screenshot(url)
|
|
46
|
+
except Exception as exc: # pragma: no cover - depends on selenium setup
|
|
47
|
+
logger.error("Screenshot capture failed: %s", exc)
|
|
48
|
+
return ""
|
|
49
|
+
node = Node.get_local()
|
|
50
|
+
save_screenshot(path, node=node, method=method)
|
|
51
|
+
return str(path)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
@shared_task
|
|
55
|
+
def poll_unreachable_upstream() -> None:
|
|
56
|
+
"""Poll upstream nodes for queued NetMessages."""
|
|
57
|
+
|
|
58
|
+
local = Node.get_local()
|
|
59
|
+
if not local or not local.has_feature("celery-queue"):
|
|
60
|
+
return
|
|
61
|
+
|
|
62
|
+
private_key = local.get_private_key()
|
|
63
|
+
if not private_key:
|
|
64
|
+
logger.warning("Node %s cannot sign upstream polls", getattr(local, "pk", None))
|
|
65
|
+
return
|
|
66
|
+
|
|
67
|
+
requester_payload = {"requester": str(local.uuid)}
|
|
68
|
+
payload_json = json.dumps(requester_payload, separators=(",", ":"), sort_keys=True)
|
|
69
|
+
try:
|
|
70
|
+
signature = base64.b64encode(
|
|
71
|
+
private_key.sign(
|
|
72
|
+
payload_json.encode(),
|
|
73
|
+
padding.PKCS1v15(),
|
|
74
|
+
hashes.SHA256(),
|
|
75
|
+
)
|
|
76
|
+
).decode()
|
|
77
|
+
except Exception as exc:
|
|
78
|
+
logger.warning("Failed to sign upstream poll request: %s", exc)
|
|
79
|
+
return
|
|
80
|
+
|
|
81
|
+
headers = {"Content-Type": "application/json", "X-Signature": signature}
|
|
82
|
+
upstream_nodes = Node.objects.filter(current_relation=Node.Relation.UPSTREAM)
|
|
83
|
+
for upstream in upstream_nodes:
|
|
84
|
+
if not upstream.public_key:
|
|
85
|
+
continue
|
|
86
|
+
response = None
|
|
87
|
+
for url in upstream.iter_remote_urls("/nodes/net-message/pull/"):
|
|
88
|
+
try:
|
|
89
|
+
response = requests.post(
|
|
90
|
+
url, data=payload_json, headers=headers, timeout=5
|
|
91
|
+
)
|
|
92
|
+
except Exception as exc:
|
|
93
|
+
logger.warning("Polling upstream node %s via %s failed: %s", upstream.pk, url, exc)
|
|
94
|
+
continue
|
|
95
|
+
if response.ok:
|
|
96
|
+
break
|
|
97
|
+
logger.warning(
|
|
98
|
+
"Upstream node %s returned status %s", upstream.pk, response.status_code
|
|
99
|
+
)
|
|
100
|
+
response = None
|
|
101
|
+
if response is None or not response.ok:
|
|
102
|
+
continue
|
|
103
|
+
try:
|
|
104
|
+
body = response.json()
|
|
105
|
+
except ValueError:
|
|
106
|
+
logger.warning("Upstream node %s returned invalid JSON", upstream.pk)
|
|
107
|
+
continue
|
|
108
|
+
messages = body.get("messages", [])
|
|
109
|
+
if not isinstance(messages, list) or not messages:
|
|
110
|
+
continue
|
|
111
|
+
try:
|
|
112
|
+
public_key = serialization.load_pem_public_key(upstream.public_key.encode())
|
|
113
|
+
except Exception:
|
|
114
|
+
logger.warning("Upstream node %s has invalid public key", upstream.pk)
|
|
115
|
+
continue
|
|
116
|
+
for item in messages:
|
|
117
|
+
if not isinstance(item, dict):
|
|
118
|
+
continue
|
|
119
|
+
payload = item.get("payload")
|
|
120
|
+
payload_signature = item.get("signature")
|
|
121
|
+
if not isinstance(payload, dict) or not payload_signature:
|
|
122
|
+
continue
|
|
123
|
+
payload_text = json.dumps(payload, separators=(",", ":"), sort_keys=True)
|
|
124
|
+
try:
|
|
125
|
+
public_key.verify(
|
|
126
|
+
base64.b64decode(payload_signature),
|
|
127
|
+
payload_text.encode(),
|
|
128
|
+
padding.PKCS1v15(),
|
|
129
|
+
hashes.SHA256(),
|
|
130
|
+
)
|
|
131
|
+
except Exception:
|
|
132
|
+
logger.warning(
|
|
133
|
+
"Signature verification failed for upstream node %s", upstream.pk
|
|
134
|
+
)
|
|
135
|
+
continue
|
|
136
|
+
try:
|
|
137
|
+
NetMessage.receive_payload(payload, sender=upstream)
|
|
138
|
+
except ValueError as exc:
|
|
139
|
+
logger.warning(
|
|
140
|
+
"Discarded upstream message from node %s: %s", upstream.pk, exc
|
|
141
|
+
)
|