simo 2.8.3__py3-none-any.whl → 2.8.5__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 simo might be problematic. Click here for more details.
- simo/__pycache__/settings.cpython-312.pyc +0 -0
- simo/automation/__pycache__/gateways.cpython-312.pyc +0 -0
- simo/automation/gateways.py +40 -31
- simo/backups/__pycache__/tasks.cpython-312.pyc +0 -0
- simo/backups/tasks.py +1 -1
- simo/core/__pycache__/controllers.cpython-312.pyc +0 -0
- simo/core/__pycache__/models.cpython-312.pyc +0 -0
- simo/core/controllers.py +2 -0
- simo/core/management/_hub_template/hub/supervisor.conf +1 -0
- simo/core/models.py +1 -0
- simo/core/templates/admin/component_history.html +2 -2
- simo/fleet/__pycache__/admin.cpython-312.pyc +0 -0
- simo/fleet/__pycache__/apps.cpython-312.pyc +0 -0
- simo/fleet/__pycache__/models.cpython-312.pyc +0 -0
- simo/fleet/__pycache__/socket_consumers.cpython-312.pyc +0 -0
- simo/fleet/__pycache__/tasks.cpython-312.pyc +0 -0
- simo/fleet/admin.py +24 -3
- simo/fleet/apps.py +10 -0
- simo/fleet/models.py +12 -1
- simo/fleet/socket_consumers.py +31 -7
- simo/fleet/tasks.py +13 -0
- simo/fleet/templates/admin/colonel_history.html +5 -0
- simo/generic/__pycache__/forms.cpython-312.pyc +0 -0
- simo/generic/__pycache__/gateways.cpython-312.pyc +0 -0
- simo/generic/__pycache__/models.cpython-312.pyc +0 -0
- simo/generic/__pycache__/tasks.cpython-312.pyc +0 -0
- simo/generic/forms.py +6 -0
- simo/generic/gateways.py +0 -38
- simo/generic/models.py +41 -37
- simo/generic/tasks.py +80 -0
- simo/notifications/__pycache__/utils.cpython-312.pyc +0 -0
- simo/settings.py +25 -1
- simo/users/__pycache__/tasks.cpython-312.pyc +0 -0
- {simo-2.8.3.dist-info → simo-2.8.5.dist-info}/METADATA +1 -1
- {simo-2.8.3.dist-info → simo-2.8.5.dist-info}/RECORD +39 -30
- {simo-2.8.3.dist-info → simo-2.8.5.dist-info}/LICENSE.md +0 -0
- {simo-2.8.3.dist-info → simo-2.8.5.dist-info}/WHEEL +0 -0
- {simo-2.8.3.dist-info → simo-2.8.5.dist-info}/entry_points.txt +0 -0
- {simo-2.8.3.dist-info → simo-2.8.5.dist-info}/top_level.txt +0 -0
|
Binary file
|
|
Binary file
|
simo/automation/gateways.py
CHANGED
|
@@ -6,7 +6,6 @@ import json
|
|
|
6
6
|
import time
|
|
7
7
|
import multiprocessing
|
|
8
8
|
import threading
|
|
9
|
-
import traceback
|
|
10
9
|
from django.conf import settings
|
|
11
10
|
from django.utils import timezone
|
|
12
11
|
from django.db import connection as db_connection
|
|
@@ -43,10 +42,7 @@ class ScriptRunHandler(multiprocessing.Process):
|
|
|
43
42
|
def run(self):
|
|
44
43
|
db_connection.connect()
|
|
45
44
|
self.component = Component.objects.get(id=self.component_id)
|
|
46
|
-
|
|
47
|
-
tz = pytz.timezone(self.component.zone.instance.timezone)
|
|
48
|
-
except:
|
|
49
|
-
tz = pytz.timezone('UTC')
|
|
45
|
+
tz = pytz.timezone(self.component.zone.instance.timezone)
|
|
50
46
|
timezone.activate(tz)
|
|
51
47
|
introduce_instance(self.component.zone.instance)
|
|
52
48
|
self.logger = get_component_logger(self.component)
|
|
@@ -235,8 +231,10 @@ class AutomationsGatewayHandler(GatesHandler, BaseObjectCommandsGatewayHandler):
|
|
|
235
231
|
def watch_scripts(self):
|
|
236
232
|
drop_current_instance()
|
|
237
233
|
# observe running scripts and drop the ones that are no longer alive
|
|
238
|
-
|
|
239
|
-
|
|
234
|
+
for id, data in list(self.running_scripts.items()):
|
|
235
|
+
if time.time() - data['start_time'] < 5:
|
|
236
|
+
continue
|
|
237
|
+
process = data['proc']
|
|
240
238
|
|
|
241
239
|
comp = Component.objects.filter(id=id).first()
|
|
242
240
|
if comp and comp.value == 'finished':
|
|
@@ -252,12 +250,18 @@ class AutomationsGatewayHandler(GatesHandler, BaseObjectCommandsGatewayHandler):
|
|
|
252
250
|
self.running_scripts.pop(id)
|
|
253
251
|
continue
|
|
254
252
|
else:
|
|
253
|
+
# it as been observed that is_alive might sometimes report false
|
|
254
|
+
# however the process is actually still running
|
|
255
|
+
process.kill()
|
|
255
256
|
self.last_death = time.time()
|
|
256
257
|
self.running_scripts.pop(id, None) # no longer running for sure!
|
|
257
258
|
if not comp or comp.value != 'running':
|
|
258
259
|
continue
|
|
259
260
|
|
|
260
261
|
if id not in self.terminating_scripts: # was not intentionaly terminated
|
|
262
|
+
if comp:
|
|
263
|
+
tz = pytz.timezone(comp.zone.instance.timezone)
|
|
264
|
+
timezone.activate(tz)
|
|
261
265
|
logger = get_component_logger(comp)
|
|
262
266
|
logger.log(logging.INFO, "-------DEAD!-------")
|
|
263
267
|
comp.value = 'error'
|
|
@@ -348,30 +352,35 @@ class AutomationsGatewayHandler(GatesHandler, BaseObjectCommandsGatewayHandler):
|
|
|
348
352
|
|
|
349
353
|
def start_script(self, component):
|
|
350
354
|
print("START SCRIPT %s" % str(component))
|
|
355
|
+
|
|
351
356
|
if component.id in self.running_scripts:
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
pass
|
|
357
|
-
self.running_scripts.pop(component.id, None)
|
|
358
|
-
elif component.id not in self.terminating_scripts \
|
|
359
|
-
and self.running_scripts[component.id].is_alive():
|
|
357
|
+
# it appears that the script is running and is perfectly healthy
|
|
358
|
+
# so we make sure it has correct value, do nothing else and return!
|
|
359
|
+
if component.id not in self.terminating_scripts \
|
|
360
|
+
and self.running_scripts[component.id]['proc'].is_alive():
|
|
360
361
|
if component.value != 'running':
|
|
361
362
|
component.value = 'running'
|
|
362
363
|
component.save()
|
|
363
364
|
return
|
|
364
|
-
else:
|
|
365
|
-
try:
|
|
366
|
-
self.running_scripts[component.id].kill()
|
|
367
|
-
except:
|
|
368
|
-
pass
|
|
369
365
|
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
366
|
+
# script is in terminating state or is no longer alive
|
|
367
|
+
# since starting of a new script was requested, we kill it viciously
|
|
368
|
+
# and continue on!
|
|
369
|
+
try:
|
|
370
|
+
self.running_scripts[component.id]['proc'].kill()
|
|
371
|
+
except:
|
|
372
|
+
pass
|
|
373
|
+
self.running_scripts.pop(component.id, None)
|
|
374
|
+
|
|
375
|
+
|
|
376
|
+
process = ScriptRunHandler(
|
|
377
|
+
component.id, multiprocessing.Event(), daemon=True
|
|
373
378
|
)
|
|
374
|
-
|
|
379
|
+
process.start()
|
|
380
|
+
self.running_scripts[component.id] = {
|
|
381
|
+
'proc': process, 'start_time': time.time()
|
|
382
|
+
}
|
|
383
|
+
|
|
375
384
|
|
|
376
385
|
def stop_script(self, component, stop_status='stopped'):
|
|
377
386
|
self.terminating_scripts.add(component.id)
|
|
@@ -389,17 +398,17 @@ class AutomationsGatewayHandler(GatesHandler, BaseObjectCommandsGatewayHandler):
|
|
|
389
398
|
elif stop_status == 'stopped':
|
|
390
399
|
logger.log(logging.INFO, "-------STOP-------")
|
|
391
400
|
|
|
392
|
-
if self.running_scripts[component.id].exit_in_use.is_set()\
|
|
393
|
-
and not self.running_scripts[component.id].exin_in_use_fail.is_set():
|
|
394
|
-
self.running_scripts[component.id].exit_event.set()
|
|
401
|
+
if self.running_scripts[component.id]['proc'].exit_in_use.is_set()\
|
|
402
|
+
and not self.running_scripts[component.id]['proc'].exin_in_use_fail.is_set():
|
|
403
|
+
self.running_scripts[component.id]['proc'].exit_event.set()
|
|
395
404
|
else:
|
|
396
|
-
self.running_scripts[component.id].terminate()
|
|
405
|
+
self.running_scripts[component.id]['proc'].terminate()
|
|
397
406
|
|
|
398
407
|
def kill():
|
|
399
408
|
start = time.time()
|
|
400
409
|
terminated = False
|
|
401
410
|
while start > time.time() - 2:
|
|
402
|
-
if not self.running_scripts[component.id].is_alive():
|
|
411
|
+
if not self.running_scripts[component.id]['proc'].is_alive():
|
|
403
412
|
terminated = True
|
|
404
413
|
break
|
|
405
414
|
time.sleep(0.1)
|
|
@@ -408,12 +417,12 @@ class AutomationsGatewayHandler(GatesHandler, BaseObjectCommandsGatewayHandler):
|
|
|
408
417
|
logger.log(logging.INFO, "-------GATEWAY KILL-------")
|
|
409
418
|
else:
|
|
410
419
|
logger.log(logging.INFO, "-------KILL!-------")
|
|
411
|
-
self.running_scripts[component.id].kill()
|
|
420
|
+
self.running_scripts[component.id]['proc'].kill()
|
|
412
421
|
|
|
413
422
|
component.set(stop_status)
|
|
414
423
|
self.terminating_scripts.remove(component.id)
|
|
415
424
|
# making sure it's fully killed along with it's child processes
|
|
416
|
-
self.running_scripts[component.id].kill()
|
|
425
|
+
self.running_scripts[component.id]['proc'].kill()
|
|
417
426
|
self.running_scripts.pop(component.id, None)
|
|
418
427
|
logger.handlers = []
|
|
419
428
|
|
|
Binary file
|
simo/backups/tasks.py
CHANGED
|
@@ -315,7 +315,7 @@ def perform_backup():
|
|
|
315
315
|
|
|
316
316
|
if other_month_folders:
|
|
317
317
|
# delete old backups to free up at least 20G of space
|
|
318
|
-
while shutil.disk_usage(
|
|
318
|
+
while shutil.disk_usage(sd_mountpoint).free < 20 * 1024 * 1024 * 1024:
|
|
319
319
|
remove_folder = other_month_folders.pop()[0]
|
|
320
320
|
print(f"REMOVE: {remove_folder}")
|
|
321
321
|
shutil.rmtree(remove_folder)
|
|
Binary file
|
|
Binary file
|
simo/core/controllers.py
CHANGED
simo/core/models.py
CHANGED
|
@@ -14,6 +14,7 @@ from django.conf import settings
|
|
|
14
14
|
from django.core.files.storage import FileSystemStorage
|
|
15
15
|
from timezone_utils.choices import ALL_TIMEZONES_CHOICES
|
|
16
16
|
from location_field.models.plain import PlainLocationField
|
|
17
|
+
from actstream import action # do not delete from here!
|
|
17
18
|
from model_utils import FieldTracker
|
|
18
19
|
from dirtyfields import DirtyFieldsMixin
|
|
19
20
|
from simo.core.utils.mixins import SimoAdminMixin
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
<h3>Value:</h3>
|
|
3
3
|
<ul style="margin-left: 1.5em;">
|
|
4
4
|
{% for history_item in value_history %}
|
|
5
|
-
<li>[{{ history_item.user }}] {{ history_item.date }} - <strong>{{ history_item.value }}</strong></li>
|
|
5
|
+
<li>[{{ history_item.user }}] {{ history_item.date|date:"Y-m-d H:i:s" }} - <strong>{{ history_item.value }}</strong></li>
|
|
6
6
|
{% endfor %}
|
|
7
7
|
</ul>
|
|
8
8
|
{% endif %}
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
<h3>Security:</h3>
|
|
13
13
|
<ul style="margin-left: 1.5em;">
|
|
14
14
|
{% for history_item in arm_status_history %}
|
|
15
|
-
<li>[{{ history_item.user }}] {{ history_item.date }} - <strong>{{ history_item.value }}</strong></li>
|
|
15
|
+
<li>[{{ history_item.user }}] {{ history_item.date|date:"Y-m-d H:i:s" }} - <strong>{{ history_item.value }}</strong></li>
|
|
16
16
|
{% endfor %}
|
|
17
17
|
</ul>
|
|
18
18
|
{% endif %}
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
simo/fleet/admin.py
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
from threading import Timer
|
|
2
|
+
from actstream.models import actor_stream
|
|
2
3
|
from django.contrib import admin
|
|
3
4
|
from django.utils.safestring import mark_safe
|
|
4
5
|
from django.template.loader import render_to_string
|
|
@@ -60,10 +61,8 @@ class ColonelAdmin(admin.ModelAdmin):
|
|
|
60
61
|
readonly_fields = (
|
|
61
62
|
'type', 'uid', 'connected', 'last_seen',
|
|
62
63
|
'firmware_version', 'newer_firmware_available',
|
|
64
|
+
'history',
|
|
63
65
|
)
|
|
64
|
-
fields = (
|
|
65
|
-
'name', 'instance', 'enabled', 'firmware_auto_update'
|
|
66
|
-
) + readonly_fields + ('pwm_frequency', 'logs_stream', 'log', )
|
|
67
66
|
|
|
68
67
|
actions = (
|
|
69
68
|
'check_for_upgrade', 'update_firmware', 'update_config', 'restart',
|
|
@@ -73,6 +72,19 @@ class ColonelAdmin(admin.ModelAdmin):
|
|
|
73
72
|
|
|
74
73
|
inlines = InterfaceInline, ColonelPinsInline
|
|
75
74
|
|
|
75
|
+
fieldsets = (
|
|
76
|
+
("", {'fields': (
|
|
77
|
+
'name', 'instance', 'enabled', 'firmware_auto_update',
|
|
78
|
+
'type', 'uid', 'connected', 'last_seen',
|
|
79
|
+
'firmware_version', 'newer_firmware_available',
|
|
80
|
+
'pwm_frequency', 'logs_stream', 'log'
|
|
81
|
+
)}),
|
|
82
|
+
("History", {
|
|
83
|
+
'fields': ('history',),
|
|
84
|
+
'classes': ('collapse',),
|
|
85
|
+
}),
|
|
86
|
+
)
|
|
87
|
+
|
|
76
88
|
def get_queryset(self, request):
|
|
77
89
|
qs = super().get_queryset(request)
|
|
78
90
|
return qs.filter(instance=get_current_instance())
|
|
@@ -165,6 +177,15 @@ class ColonelAdmin(admin.ModelAdmin):
|
|
|
165
177
|
return mark_safe('<img src="%s" alt="True">' % static('admin/img/icon-yes.svg'))
|
|
166
178
|
return mark_safe('<img src="%s" alt="False">' % static('admin/img/icon-no.svg'))
|
|
167
179
|
|
|
180
|
+
def history(self, obj):
|
|
181
|
+
if not obj:
|
|
182
|
+
return ''
|
|
183
|
+
actions = actor_stream(obj)[:100]
|
|
184
|
+
if not len(actions):
|
|
185
|
+
return ''
|
|
186
|
+
return render_to_string(
|
|
187
|
+
'admin/colonel_history.html', {'actions': actor_stream(obj)[:100]}
|
|
188
|
+
)
|
|
168
189
|
|
|
169
190
|
@admin.register(Interface)
|
|
170
191
|
class InterfaceAdmin(admin.ModelAdmin):
|
simo/fleet/apps.py
ADDED
simo/fleet/models.py
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import requests
|
|
2
2
|
import time
|
|
3
|
+
from actstream import action
|
|
3
4
|
from django.core.exceptions import ValidationError
|
|
4
5
|
from django.db import transaction
|
|
5
6
|
from django.db import models
|
|
@@ -281,6 +282,17 @@ def after_colonel_save(sender, instance, created, *args, **kwargs):
|
|
|
281
282
|
Interface.objects.create(colonel=instance, no=1)
|
|
282
283
|
Interface.objects.create(colonel=instance, no=2)
|
|
283
284
|
|
|
285
|
+
if 'socket_connected' in instance.get_dirty_fields():
|
|
286
|
+
if instance.socket_connected:
|
|
287
|
+
verb = 'connected'
|
|
288
|
+
else:
|
|
289
|
+
verb = 'disconnected'
|
|
290
|
+
action.send(
|
|
291
|
+
instance, target=instance, verb=verb,
|
|
292
|
+
instance_id=instance.instance.id,
|
|
293
|
+
action_type='colonel_status', value=verb
|
|
294
|
+
)
|
|
295
|
+
|
|
284
296
|
|
|
285
297
|
@receiver(post_save, sender=Component)
|
|
286
298
|
def post_component_save(sender, instance, created, *args, **kwargs):
|
|
@@ -472,7 +484,6 @@ def post_interface_save(sender, instance, created, *args, **kwargs):
|
|
|
472
484
|
|
|
473
485
|
|
|
474
486
|
|
|
475
|
-
|
|
476
487
|
@receiver(post_delete, sender=Interface)
|
|
477
488
|
def post_interface_delete(sender, instance, *args, **kwargs):
|
|
478
489
|
with transaction.atomic():
|
simo/fleet/socket_consumers.py
CHANGED
|
@@ -5,6 +5,7 @@ import pytz
|
|
|
5
5
|
import traceback
|
|
6
6
|
import sys
|
|
7
7
|
import zlib
|
|
8
|
+
import time
|
|
8
9
|
from django.db import transaction
|
|
9
10
|
from logging.handlers import RotatingFileHandler
|
|
10
11
|
from django.utils import timezone
|
|
@@ -27,10 +28,15 @@ from .controllers import TTLock
|
|
|
27
28
|
|
|
28
29
|
@capture_socket_errors
|
|
29
30
|
class FleetConsumer(AsyncWebsocketConsumer):
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def __init__(self, *args, **kwargs):
|
|
34
|
+
super().__init__(*args, **kwargs)
|
|
35
|
+
self.colonel = None
|
|
36
|
+
self.colonel_logger = None
|
|
37
|
+
self.connected = False
|
|
38
|
+
self.mqtt_client = None
|
|
39
|
+
self.last_seen = 0
|
|
34
40
|
|
|
35
41
|
|
|
36
42
|
async def disconnect(self, code):
|
|
@@ -189,10 +195,26 @@ class FleetConsumer(AsyncWebsocketConsumer):
|
|
|
189
195
|
self.colonel.minor_upgrade_available
|
|
190
196
|
)
|
|
191
197
|
|
|
192
|
-
await asyncio.sleep(
|
|
198
|
+
await asyncio.sleep(2)
|
|
199
|
+
|
|
193
200
|
# Default pinging system sometimes get's lost somewhere,
|
|
194
|
-
# therefore we use our own to ensure connection
|
|
201
|
+
# therefore we use our own to ensure connection and understand if
|
|
202
|
+
# colonel is connected or not
|
|
203
|
+
|
|
195
204
|
await self.send_data({'command': 'ping'})
|
|
205
|
+
ping_start = time.time()
|
|
206
|
+
await asyncio.sleep(0.1)
|
|
207
|
+
|
|
208
|
+
while ping_start > self.last_seen:
|
|
209
|
+
if time.time() - ping_start > 3:
|
|
210
|
+
def disconnect_socket():
|
|
211
|
+
self.colonel.socket_connected = False
|
|
212
|
+
self.colonel.save(update_fields=['socket_connected'])
|
|
213
|
+
await sync_to_async(disconnect_socket, thread_sensitive=True)()
|
|
214
|
+
break
|
|
215
|
+
await asyncio.sleep(0.1)
|
|
216
|
+
|
|
217
|
+
|
|
196
218
|
|
|
197
219
|
async def firmware_update(self, to_version):
|
|
198
220
|
print("Firmware update: ", str(self.colonel))
|
|
@@ -366,8 +388,9 @@ class FleetConsumer(AsyncWebsocketConsumer):
|
|
|
366
388
|
drop_current_instance()
|
|
367
389
|
try:
|
|
368
390
|
if text_data:
|
|
369
|
-
print(f"{self.colonel}: {text_data}")
|
|
370
391
|
data = json.loads(text_data)
|
|
392
|
+
if 'ping' not in data:
|
|
393
|
+
print(f"{self.colonel}: {text_data}")
|
|
371
394
|
if 'get_config' in data:
|
|
372
395
|
config = await self.get_config_data()
|
|
373
396
|
print("Send config: ", config)
|
|
@@ -453,6 +476,7 @@ class FleetConsumer(AsyncWebsocketConsumer):
|
|
|
453
476
|
|
|
454
477
|
|
|
455
478
|
async def log_colonel_connected(self):
|
|
479
|
+
self.last_seen = time.time()
|
|
456
480
|
|
|
457
481
|
def save_last_seen():
|
|
458
482
|
self.colonel.socket_connected = True
|
simo/fleet/tasks.py
CHANGED
|
@@ -5,6 +5,18 @@ from simo.core.middleware import drop_current_instance
|
|
|
5
5
|
from celeryc import celery_app
|
|
6
6
|
|
|
7
7
|
|
|
8
|
+
@celery_app.task
|
|
9
|
+
def check_colonels_connected():
|
|
10
|
+
from .models import Colonel
|
|
11
|
+
drop_current_instance()
|
|
12
|
+
for lost_colonel in Colonel.objects.filter(
|
|
13
|
+
socket_connected=True,
|
|
14
|
+
last_seen__lt=timezone.now() - datetime.timedelta(seconds=20)
|
|
15
|
+
):
|
|
16
|
+
lost_colonel.socket_connected = False
|
|
17
|
+
lost_colonel.save()
|
|
18
|
+
|
|
19
|
+
|
|
8
20
|
@celery_app.task
|
|
9
21
|
def check_colonel_components_alive():
|
|
10
22
|
from simo.core.models import Component
|
|
@@ -24,4 +36,5 @@ def check_colonel_components_alive():
|
|
|
24
36
|
|
|
25
37
|
@celery_app.on_after_finalize.connect
|
|
26
38
|
def setup_periodic_tasks(sender, **kwargs):
|
|
39
|
+
sender.add_periodic_task(10, check_colonels_connected.s())
|
|
27
40
|
sender.add_periodic_task(20, check_colonel_components_alive.s())
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
simo/generic/forms.py
CHANGED
|
@@ -157,6 +157,11 @@ class ThermostatConfigForm(BaseComponentForm):
|
|
|
157
157
|
|
|
158
158
|
class AlarmBreachEventForm(forms.Form):
|
|
159
159
|
uid = HiddenField(required=False)
|
|
160
|
+
threshold = forms.IntegerField(
|
|
161
|
+
label="Number of sensors breached",
|
|
162
|
+
min_value=1, max_value=5, initial=1,
|
|
163
|
+
help_text="React when at least given amount of sensors were breached."
|
|
164
|
+
)
|
|
160
165
|
component = Select2ModelChoiceField(
|
|
161
166
|
queryset=Component.objects.all(),
|
|
162
167
|
url='autocomplete-component',
|
|
@@ -173,6 +178,7 @@ class AlarmBreachEventForm(forms.Form):
|
|
|
173
178
|
help_text="Event will not fire if alarm group is disarmed "
|
|
174
179
|
"within given timeframe of seconds after the breach."
|
|
175
180
|
)
|
|
181
|
+
|
|
176
182
|
prefix = 'breach_events'
|
|
177
183
|
|
|
178
184
|
def clean(self):
|
simo/generic/gateways.py
CHANGED
|
@@ -185,8 +185,6 @@ class GenericGatewayHandler(
|
|
|
185
185
|
('watch_thermostats', 60),
|
|
186
186
|
('watch_alarm_clocks', 30),
|
|
187
187
|
('watch_watering', 60),
|
|
188
|
-
('watch_alarm_events', 1),
|
|
189
|
-
('watch_timers', 1),
|
|
190
188
|
('watch_main_states', 60),
|
|
191
189
|
('watch_groups', 60)
|
|
192
190
|
)
|
|
@@ -328,42 +326,6 @@ class GenericGatewayHandler(
|
|
|
328
326
|
other_group.refresh_status()
|
|
329
327
|
|
|
330
328
|
|
|
331
|
-
def watch_alarm_events(self):
|
|
332
|
-
from .controllers import AlarmGroup
|
|
333
|
-
drop_current_instance()
|
|
334
|
-
for alarm in Component.objects.filter(
|
|
335
|
-
controller_uid=AlarmGroup.uid, value='breached',
|
|
336
|
-
meta__breach_start__gt=0
|
|
337
|
-
):
|
|
338
|
-
for uid, event in alarm.controller.events_map.items():
|
|
339
|
-
if uid in alarm.meta.get('events_triggered', []):
|
|
340
|
-
continue
|
|
341
|
-
if time.time() - alarm.meta['breach_start'] < event['delay']:
|
|
342
|
-
continue
|
|
343
|
-
try:
|
|
344
|
-
getattr(event['component'], event['breach_action'])()
|
|
345
|
-
except Exception:
|
|
346
|
-
print(traceback.format_exc(), file=sys.stderr)
|
|
347
|
-
if not alarm.meta.get('events_triggered'):
|
|
348
|
-
alarm.meta['events_triggered'] = [uid]
|
|
349
|
-
else:
|
|
350
|
-
alarm.meta['events_triggered'].append(uid)
|
|
351
|
-
alarm.save(update_fields=['meta'])
|
|
352
|
-
|
|
353
|
-
def watch_timers(self):
|
|
354
|
-
drop_current_instance()
|
|
355
|
-
for component in Component.objects.filter(
|
|
356
|
-
meta__timer_to__gt=0
|
|
357
|
-
).filter(meta__timer_to__lt=time.time()):
|
|
358
|
-
component.meta['timer_to'] = 0
|
|
359
|
-
component.meta['timer_start'] = 0
|
|
360
|
-
component.save()
|
|
361
|
-
try:
|
|
362
|
-
component.controller._on_timer_end()
|
|
363
|
-
except Exception as e:
|
|
364
|
-
print(traceback.format_exc(), file=sys.stderr)
|
|
365
|
-
|
|
366
|
-
|
|
367
329
|
def set_get_day_evening_night_morning(self, state):
|
|
368
330
|
if state.value not in ('day', 'night', 'evening', 'morning'):
|
|
369
331
|
return
|
simo/generic/models.py
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
import time
|
|
2
2
|
import sys
|
|
3
3
|
import traceback
|
|
4
|
-
from threading import Timer
|
|
5
4
|
from django.db.models.signals import pre_save, post_save, post_delete
|
|
6
5
|
from django.dispatch import receiver
|
|
7
|
-
from simo.core.models import
|
|
6
|
+
from simo.core.models import Component
|
|
8
7
|
from simo.users.models import InstanceUser
|
|
9
|
-
|
|
8
|
+
from .tasks import (
|
|
9
|
+
notify_users_on_alarm_group_breach,
|
|
10
|
+
fire_breach_events
|
|
11
|
+
)
|
|
10
12
|
|
|
11
13
|
|
|
12
14
|
@receiver(post_save, sender=Component)
|
|
@@ -21,7 +23,7 @@ def handle_alarm_groups(sender, instance, *args, **kwargs):
|
|
|
21
23
|
|
|
22
24
|
from .controllers import AlarmGroup
|
|
23
25
|
|
|
24
|
-
for
|
|
26
|
+
for ag in Component.objects.filter(
|
|
25
27
|
controller_uid=AlarmGroup.uid,
|
|
26
28
|
config__components__contains=instance.id,
|
|
27
29
|
).exclude(value='disarmed'):
|
|
@@ -30,48 +32,44 @@ def handle_alarm_groups(sender, instance, *args, **kwargs):
|
|
|
30
32
|
}
|
|
31
33
|
stats[instance.arm_status] += 1
|
|
32
34
|
for slave in Component.objects.filter(
|
|
33
|
-
pk__in=
|
|
35
|
+
pk__in=ag.config['components'],
|
|
34
36
|
).exclude(pk=instance.pk):
|
|
35
37
|
stats[slave.arm_status] += 1
|
|
36
|
-
alarm_group.config['stats'] = stats
|
|
37
|
-
alarm_group.save(update_fields=['config'])
|
|
38
38
|
|
|
39
|
-
|
|
39
|
+
print(f"STATS OF {ag} are: {stats}")
|
|
40
|
+
ag.config['stats'] = stats
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
if stats['disarmed'] == len(ag.config['components']):
|
|
40
44
|
alarm_group_value = 'disarmed'
|
|
41
|
-
elif stats['armed'] == len(
|
|
45
|
+
elif stats['armed'] == len(ag.config['components']):
|
|
42
46
|
alarm_group_value = 'armed'
|
|
43
47
|
elif stats['breached']:
|
|
44
|
-
if alarm_group.value != 'breached':
|
|
45
|
-
def notify_users_security_breach(alarm_group_component_id):
|
|
46
|
-
alarm_group_component = Component.objects.filter(
|
|
47
|
-
id=alarm_group_component_id, value='breached'
|
|
48
|
-
).first()
|
|
49
|
-
if not alarm_group_component:
|
|
50
|
-
return
|
|
51
|
-
breached_components = Component.objects.filter(
|
|
52
|
-
pk__in=alarm_group_component.config['components'],
|
|
53
|
-
arm_status='breached'
|
|
54
|
-
)
|
|
55
|
-
body = "Security Breach! " + '; '.join(
|
|
56
|
-
[str(c) for c in breached_components]
|
|
57
|
-
)
|
|
58
|
-
from simo.notifications.utils import notify_users
|
|
59
|
-
notify_users(
|
|
60
|
-
'alarm', str(alarm_group_component), body,
|
|
61
|
-
component=alarm_group_component
|
|
62
|
-
)
|
|
63
|
-
if alarm_group.config.get('notify_on_breach') is not None:
|
|
64
|
-
t = Timer(
|
|
65
|
-
# give it one second to finish with other db processes.
|
|
66
|
-
alarm_group.config['notify_on_breach'] + 1,
|
|
67
|
-
notify_users_security_breach, [alarm_group.id]
|
|
68
|
-
)
|
|
69
|
-
t.start()
|
|
70
48
|
alarm_group_value = 'breached'
|
|
71
49
|
else:
|
|
72
50
|
alarm_group_value = 'pending-arm'
|
|
73
51
|
|
|
74
|
-
|
|
52
|
+
print(f"{ag} value: {alarm_group_value}")
|
|
53
|
+
|
|
54
|
+
if alarm_group_value == 'breached' and instance.arm_status == 'breached':
|
|
55
|
+
if ag.value != 'breached':
|
|
56
|
+
ag.meta['breach_times'] = [time.time()]
|
|
57
|
+
else:
|
|
58
|
+
ag.meta['breach_times'].append(time.time())
|
|
59
|
+
|
|
60
|
+
ag.save(update_fields=['meta', 'config'])
|
|
61
|
+
ag.controller.set(alarm_group_value)
|
|
62
|
+
|
|
63
|
+
if alarm_group_value == 'breached' and instance.arm_status == 'breached':
|
|
64
|
+
for event in ag.config['breach_events']:
|
|
65
|
+
if event['uid'] in ag.meta.get('events_triggered', []):
|
|
66
|
+
continue
|
|
67
|
+
threshold = event.get('threshold', 1)
|
|
68
|
+
if len(ag.meta['breach_times']) < threshold:
|
|
69
|
+
continue
|
|
70
|
+
fire_breach_events.apply_async(
|
|
71
|
+
args=[ag.id], countdown=event['delay']
|
|
72
|
+
)
|
|
75
73
|
|
|
76
74
|
|
|
77
75
|
@receiver(pre_save, sender=Component)
|
|
@@ -85,8 +83,14 @@ def manage_alarm_groups(sender, instance, *args, **kwargs):
|
|
|
85
83
|
return
|
|
86
84
|
|
|
87
85
|
if instance.value == 'breached':
|
|
88
|
-
instance.meta['breach_start'] = time.time()
|
|
89
86
|
instance.meta['events_triggered'] = []
|
|
87
|
+
|
|
88
|
+
if instance.config.get('notify_on_breach') is not None:
|
|
89
|
+
notify_users_on_alarm_group_breach.apply_async(
|
|
90
|
+
args=[instance.id],
|
|
91
|
+
countdown=instance.config['notify_on_breach']
|
|
92
|
+
)
|
|
93
|
+
|
|
90
94
|
elif instance.get_dirty_fields()['value'] == 'breached' \
|
|
91
95
|
and instance.value == 'disarmed':
|
|
92
96
|
instance.meta['breach_start'] = None
|
simo/generic/tasks.py
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import sys, traceback, time
|
|
2
|
+
from celeryc import celery_app
|
|
3
|
+
from simo.core.middleware import drop_current_instance, drop_current_instance
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
@celery_app.task
|
|
7
|
+
def notify_users_on_alarm_group_breach(ag_id):
|
|
8
|
+
from simo.core.models import Component
|
|
9
|
+
drop_current_instance()
|
|
10
|
+
ag = Component.objects.filter(id=ag_id).first()
|
|
11
|
+
if not ag:
|
|
12
|
+
return
|
|
13
|
+
if ag.value != 'breached':
|
|
14
|
+
# no longer breached, somebody disarmed it,
|
|
15
|
+
# no need to send any notifications
|
|
16
|
+
return
|
|
17
|
+
|
|
18
|
+
breached_components = Component.objects.filter(
|
|
19
|
+
pk__in=ag.config['components'],
|
|
20
|
+
arm_status='breached'
|
|
21
|
+
)
|
|
22
|
+
body = "Security Breach! " + '; '.join(
|
|
23
|
+
[str(c) for c in breached_components]
|
|
24
|
+
)
|
|
25
|
+
from simo.notifications.utils import notify_users
|
|
26
|
+
notify_users(
|
|
27
|
+
'alarm', str(ag), body, component=ag,
|
|
28
|
+
instance=ag.zone.instance
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@celery_app.task
|
|
33
|
+
def fire_breach_events(ag_id):
|
|
34
|
+
from simo.core.models import Component
|
|
35
|
+
drop_current_instance()
|
|
36
|
+
ag = Component.objects.filter(id=ag_id).first()
|
|
37
|
+
if not ag:
|
|
38
|
+
return
|
|
39
|
+
if ag.value != 'breached':
|
|
40
|
+
# no longer breached, somebody disarmed it,
|
|
41
|
+
# no need to send any notifications
|
|
42
|
+
return
|
|
43
|
+
for uid, event in ag.controller.events_map.items():
|
|
44
|
+
if uid in ag.meta.get('events_triggered', []):
|
|
45
|
+
continue
|
|
46
|
+
threshold = event.get('threshold', 1)
|
|
47
|
+
if len(ag.meta['breach_times']) < threshold:
|
|
48
|
+
continue
|
|
49
|
+
if time.time() - ag.meta['breach_times'][threshold - 1] < event['delay']:
|
|
50
|
+
continue
|
|
51
|
+
try:
|
|
52
|
+
getattr(event['component'], event['breach_action'])()
|
|
53
|
+
except Exception:
|
|
54
|
+
print(traceback.format_exc(), file=sys.stderr)
|
|
55
|
+
if not ag.meta.get('events_triggered'):
|
|
56
|
+
ag.meta['events_triggered'] = [uid]
|
|
57
|
+
else:
|
|
58
|
+
ag.meta['events_triggered'].append(uid)
|
|
59
|
+
ag.save(update_fields=['meta'])
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
@celery_app.task
|
|
63
|
+
def watch_timers():
|
|
64
|
+
from simo.core.models import Component
|
|
65
|
+
drop_current_instance()
|
|
66
|
+
for component in Component.objects.filter(
|
|
67
|
+
meta__timer_to__gt=0
|
|
68
|
+
).filter(meta__timer_to__lt=time.time()):
|
|
69
|
+
component.meta['timer_to'] = 0
|
|
70
|
+
component.meta['timer_start'] = 0
|
|
71
|
+
component.save()
|
|
72
|
+
try:
|
|
73
|
+
component.controller._on_timer_end()
|
|
74
|
+
except Exception as e:
|
|
75
|
+
print(traceback.format_exc(), file=sys.stderr)
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
@celery_app.on_after_finalize.connect
|
|
79
|
+
def setup_periodic_tasks(sender, **kwargs):
|
|
80
|
+
sender.add_periodic_task(1, watch_timers.s())
|
|
Binary file
|
simo/settings.py
CHANGED
|
@@ -3,6 +3,7 @@ Django settings for SIMO.io project.
|
|
|
3
3
|
"""
|
|
4
4
|
import sys
|
|
5
5
|
import os
|
|
6
|
+
import datetime
|
|
6
7
|
|
|
7
8
|
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
|
|
8
9
|
BASE_DIR = '/etc/SIMO'
|
|
@@ -288,4 +289,27 @@ ACTSTREAM_SETTINGS = {
|
|
|
288
289
|
'USE_PREFETCH': True,
|
|
289
290
|
'USE_JSONFIELD': True,
|
|
290
291
|
'GFK_FETCH_DEPTH': 1,
|
|
291
|
-
}
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
|
|
295
|
+
DATETIME_FORMAT = 'Y-m-d H:i:s'
|
|
296
|
+
|
|
297
|
+
|
|
298
|
+
class TimestampedStream:
|
|
299
|
+
"""Adds timestamps to all the prints"""
|
|
300
|
+
|
|
301
|
+
def __init__(self, stream):
|
|
302
|
+
self.stream = stream
|
|
303
|
+
|
|
304
|
+
def write(self, data):
|
|
305
|
+
if data != '\n':
|
|
306
|
+
timestamp = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
|
|
307
|
+
self.stream.write(f'[{timestamp}] {data}')
|
|
308
|
+
else:
|
|
309
|
+
self.stream.write(data)
|
|
310
|
+
|
|
311
|
+
def flush(self):
|
|
312
|
+
self.stream.flush()
|
|
313
|
+
|
|
314
|
+
|
|
315
|
+
sys.stdout = TimestampedStream(sys.stdout)
|
|
Binary file
|
|
@@ -2,7 +2,7 @@ simo/__init__.py,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
|
|
|
2
2
|
simo/asgi.py,sha256=L8CUVZLM32IMzWDZ4IShdDN-m69t7oxAUeHods4-xNM,822
|
|
3
3
|
simo/celeryc.py,sha256=eab7_e9rw0c__DCeoUFUh_tjAGVlulxVrk75BaJf57Q,1512
|
|
4
4
|
simo/conf.py,sha256=H2BhXAV8MEDVXF8AbkaLSfR4ULd-9_bS4bnhE5sE5fg,112
|
|
5
|
-
simo/settings.py,sha256=
|
|
5
|
+
simo/settings.py,sha256=C04iruyTTGqrcYuZjW_EN7V8MNPWNnsde4dZiQ3tr4Y,7523
|
|
6
6
|
simo/urls.py,sha256=d8g-wN0Xr2PVIV8RZl_h_PMN9KGZNIE9to2hQj1p1TU,2497
|
|
7
7
|
simo/__pycache__/__init__.cpython-312.pyc,sha256=a12_Zr7kC5DXzcFxA5eMu-TiSU5xbdF5cdKq-gwc3x0,159
|
|
8
8
|
simo/__pycache__/__init__.cpython-38.pyc,sha256=j81de0BqHMr6bs0C7cuYrXl7HwtK_vv8hDEtAdSwDJc,153
|
|
@@ -12,7 +12,7 @@ simo/__pycache__/celeryc.cpython-312.pyc,sha256=MQlG6VeEdCpXPpiiAwZavriWKwHbhwTM
|
|
|
12
12
|
simo/__pycache__/celeryc.cpython-38.pyc,sha256=eSRoaKwfYlxVaxAiwqpQ2ndEcx7W-VpZtbxRFSV8UYg,1653
|
|
13
13
|
simo/__pycache__/conf.cpython-312.pyc,sha256=q63YJWqaaaQLz3qXW8clENjvH1zUfY_k34_m56n5gRY,320
|
|
14
14
|
simo/__pycache__/conf.cpython-38.pyc,sha256=MYP2yk3ULxiYwZsZR6tCLjKnU-z03A3avzQzIn66y3k,273
|
|
15
|
-
simo/__pycache__/settings.cpython-312.pyc,sha256=
|
|
15
|
+
simo/__pycache__/settings.cpython-312.pyc,sha256=lrMjm4OihDlNrull26G_TaWUEJkFbo4Po5ir5efSRU4,7886
|
|
16
16
|
simo/__pycache__/settings.cpython-38.pyc,sha256=4w3ds3D9S78zbsovXsXC05PYBAafDrtsOhX14FT0YyE,6149
|
|
17
17
|
simo/__pycache__/urls.cpython-312.pyc,sha256=mIg_YD7zgjmIzfWzpGpikMqanGKP2O-iuA1ixqQngnc,3689
|
|
18
18
|
simo/__pycache__/urls.cpython-38.pyc,sha256=u0x6EqT8S1YfDOSPgbI8Kf-RDlveY9OV-EDXMYKAQ7w,2125
|
|
@@ -21,7 +21,7 @@ simo/automation/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
|
21
21
|
simo/automation/app_widgets.py,sha256=gaqImMZjuMHm7nIb9a4D-Y3qipz_WhSPAHXcwGx4Uzs,199
|
|
22
22
|
simo/automation/controllers.py,sha256=66aGjlfgigcePXiLmDkbVrxbm-Z26klMKIiOvaMH71k,11545
|
|
23
23
|
simo/automation/forms.py,sha256=UWnkxw8pILPK0smRPTo4SLgsZl78zOySx7JIc30Bgtk,10228
|
|
24
|
-
simo/automation/gateways.py,sha256=
|
|
24
|
+
simo/automation/gateways.py,sha256=8qgBDhkM_xYGmeEbnH5WXs5m3ihMp8Yzx7yj6XG8DNM,16382
|
|
25
25
|
simo/automation/helpers.py,sha256=iP-fxxB8HsFQy3k2CjFubu86aMqvWgmh-p24DiyOrek,4330
|
|
26
26
|
simo/automation/models.py,sha256=zt-jkzyq5ddqGT864OkJzCsvov2vZ0nO4ez3hAeZkXg,934
|
|
27
27
|
simo/automation/serializers.py,sha256=PjyFrjdPK1mBsgbNhyqMi9SWzcymqTa742ipy0LhAN4,1996
|
|
@@ -34,7 +34,7 @@ simo/automation/__pycache__/controllers.cpython-312.pyc,sha256=QV63g3UlpMAA-yCaZ
|
|
|
34
34
|
simo/automation/__pycache__/controllers.cpython-38.pyc,sha256=CL-0Tq9B4-E36fYfWT1XEBTq1dkq1W8003f6MrBnQU0,8391
|
|
35
35
|
simo/automation/__pycache__/forms.cpython-312.pyc,sha256=63rU0rWZk-Rz5qoMZiXl743WPc9NVm5d8bSd8w52T4E,12347
|
|
36
36
|
simo/automation/__pycache__/forms.cpython-38.pyc,sha256=cpA5hA2Iz3JsPC0Dq01ki1I7S9c5DKRcXveHApI1dJo,7772
|
|
37
|
-
simo/automation/__pycache__/gateways.cpython-312.pyc,sha256=
|
|
37
|
+
simo/automation/__pycache__/gateways.cpython-312.pyc,sha256=mQWn7s8qqF-CIw9Zn80RbbaFJjTiCdaN_grMsfwa-nY,22405
|
|
38
38
|
simo/automation/__pycache__/gateways.cpython-38.pyc,sha256=nHujqChMCqqxHbZezP3MisavjKDhczqzFGurO10h-lc,11113
|
|
39
39
|
simo/automation/__pycache__/helpers.cpython-312.pyc,sha256=aDFtzBE72szi4gzVxK_NiAEC__wCmdztw0UKu2lVU58,5853
|
|
40
40
|
simo/automation/__pycache__/helpers.cpython-38.pyc,sha256=fNjSyn4Mfq7-JQx-bdsnj-rSxgu20dVJ9-5ZEMT6yiM,3627
|
|
@@ -61,7 +61,7 @@ simo/backups/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
|
61
61
|
simo/backups/admin.py,sha256=cEakxnQlOHvUf8LdBdekXpDAvnqPoVN8y7pnN3WK29A,2487
|
|
62
62
|
simo/backups/dynamic_settings.py,sha256=Q52RLa3UQsmAhqkwR16cM6pbBnIbXqmVQ2oIUP2ZVD0,416
|
|
63
63
|
simo/backups/models.py,sha256=-tgILgkqmBEuxBwoymKZN1a0UVQzmJvqWrIGYMMFDaQ,695
|
|
64
|
-
simo/backups/tasks.py,sha256=
|
|
64
|
+
simo/backups/tasks.py,sha256=UtWMsHONhL_rBAedCk7Mirbp0sZ-glF49bnR2mdTM4U,13370
|
|
65
65
|
simo/backups/__pycache__/__init__.cpython-312.pyc,sha256=wl1RXj84eHmQ_WAvVl7oahd1qa8ezF1HcwRQzho7DyU,167
|
|
66
66
|
simo/backups/__pycache__/__init__.cpython-38.pyc,sha256=vzOf-JIMeZ6P85FyvTpYev3mscFosUy-SJTshcQbOHU,161
|
|
67
67
|
simo/backups/__pycache__/admin.cpython-312.pyc,sha256=4rXygr4247idEgFvadmM4L7-1rRChe7zgoGYTzo2UGI,3863
|
|
@@ -70,6 +70,7 @@ simo/backups/__pycache__/dynamic_settings.cpython-312.pyc,sha256=Sf8IorKxo6r3R0Q
|
|
|
70
70
|
simo/backups/__pycache__/dynamic_settings.cpython-38.pyc,sha256=51gJFjn_XqQBRoHeubo6ppb9pNuFQKI5hAR0ms9flE8,731
|
|
71
71
|
simo/backups/__pycache__/models.cpython-312.pyc,sha256=45nfTQzX8YuKI67KEe0nAdlZEQHAZjJu44NUr4PC5WI,1746
|
|
72
72
|
simo/backups/__pycache__/models.cpython-38.pyc,sha256=zclX7HwOT4_izweyKNQ8LmgSHb3hNcYcfsSiwwfQoLY,1220
|
|
73
|
+
simo/backups/__pycache__/tasks.cpython-312.pyc,sha256=Vg95hEynVXVA89bkG0BN5Cw6KkuVCV4JCiKLsd7xI_8,18369
|
|
73
74
|
simo/backups/__pycache__/tasks.cpython-38.pyc,sha256=bKz_Rxt_H0lC0d9_4Dxqv7cirQDSH9LVurZZC0LU94s,9179
|
|
74
75
|
simo/backups/migrations/0001_initial.py,sha256=0LzCusTUyYf61ksiepdnqXIuYYNTNd_djh_Wa484HA0,770
|
|
75
76
|
simo/backups/migrations/0002_backuplog_backup_level_backup_size.py,sha256=w9T9MQWuecy91OZE1fMExriwPuXA8HMPKsPwXhmC8_k,1023
|
|
@@ -97,7 +98,7 @@ simo/core/auto_urls.py,sha256=FqKhH0fF7cGO6P2YrjblwG4JA2UkVXj3lreJUOB2Jq4,1194
|
|
|
97
98
|
simo/core/autocomplete_views.py,sha256=x3MKOZvXYS3xVQ-V1S7Liv_U5bxr-uc0gePa85wv5nA,4561
|
|
98
99
|
simo/core/base_types.py,sha256=WypW8hTfzveuTQtruGjLYAGQZIuczxTlW-SdRk3iQug,666
|
|
99
100
|
simo/core/context.py,sha256=LKw1I4iIRnlnzoTCuSLLqDX7crHdBnMo3hjqYvVmzFc,1557
|
|
100
|
-
simo/core/controllers.py,sha256=
|
|
101
|
+
simo/core/controllers.py,sha256=4u67VNwU4Zts64aVddqhoPt9ZRbpTmAHPCdu-pFfIX0,37407
|
|
101
102
|
simo/core/dynamic_settings.py,sha256=bUs58XEZOCIEhg1TigR3LmYggli13KMryBZ9pC7ugAQ,1872
|
|
102
103
|
simo/core/events.py,sha256=1_KIk5pJqdLPRQlCQ9xSyALst2Cn0b2lAEAJ3QjwIjE,4801
|
|
103
104
|
simo/core/filters.py,sha256=6wbn8C2WvKTTjtfMwwLBp2Fib1V0-DMpS4iqJd6jJQo,2540
|
|
@@ -107,7 +108,7 @@ simo/core/gateways.py,sha256=Y2BME6zSyeUq_e-hzEUF6gErCUCP6nFxedkLZKiLVOo,4141
|
|
|
107
108
|
simo/core/loggers.py,sha256=EBdq23gTQScVfQVH-xeP90-wII2DQFDjoROAW6ggUP4,1645
|
|
108
109
|
simo/core/managers.py,sha256=Ampwe5K7gfE6IJULNCV35V8ysmMOdS_wz7mRzfaLZUw,3014
|
|
109
110
|
simo/core/middleware.py,sha256=zX6N4P_KR7gG8N2-NcR7jKtuCEhCGRh51g4EktAhP7w,3272
|
|
110
|
-
simo/core/models.py,sha256=
|
|
111
|
+
simo/core/models.py,sha256=Nw-XT0v0y5pBYlKKhzoI68HiEMMTp7fqZQZgKmeOW7Q,23752
|
|
111
112
|
simo/core/permissions.py,sha256=Ef4NO7QwwDd3Z-v61R0BeCBXxTOJz9qBvzRTIB5tHwI,2943
|
|
112
113
|
simo/core/routing.py,sha256=X1_IHxyA-_Q7hw1udDoviVP4_FSBDl8GYETTC2zWTbY,499
|
|
113
114
|
simo/core/serializers.py,sha256=adqe8oYP5f7hGFXcxa-Zmce2KOwy3hZOaWE3MaAFtpM,23157
|
|
@@ -141,7 +142,7 @@ simo/core/__pycache__/base_types.cpython-312.pyc,sha256=Lnq2NL9B5hfwJARJYC447Rdv
|
|
|
141
142
|
simo/core/__pycache__/base_types.cpython-38.pyc,sha256=CX-qlF7CefRi_mCE954wYa9rUFR88mOl6g7fybDRu7g,803
|
|
142
143
|
simo/core/__pycache__/context.cpython-312.pyc,sha256=8rsN2Er-Sx3rrVmO0Gk4cem3euGh0kTELXj667GGZ5E,2193
|
|
143
144
|
simo/core/__pycache__/context.cpython-38.pyc,sha256=NlTHt2GvXxA21AhBkeyOLfRFUuXw7wmwqyNhhcDl2cw,1373
|
|
144
|
-
simo/core/__pycache__/controllers.cpython-312.pyc,sha256=
|
|
145
|
+
simo/core/__pycache__/controllers.cpython-312.pyc,sha256=pNjKIaSvzEWJlI6_yQdmakyFYNgrxv6dyexVvoScUj4,52897
|
|
145
146
|
simo/core/__pycache__/controllers.cpython-38.pyc,sha256=LtrQQ8egOIOuQbAckeM-z8OfbzS4W8VQ3vBnryAm3iU,32086
|
|
146
147
|
simo/core/__pycache__/dynamic_settings.cpython-312.pyc,sha256=WUZ6XF4kZb6zPf541PkKmiQaBIw-r5C6F3EUUZiTEnE,3331
|
|
147
148
|
simo/core/__pycache__/dynamic_settings.cpython-38.pyc,sha256=wGpnscX1DxFpRl54MQURhjz2aD3NJohSzw9JCFnzh2Y,2384
|
|
@@ -161,7 +162,7 @@ simo/core/__pycache__/managers.cpython-312.pyc,sha256=RpeR1Z0GtSZht4_a4iDfTU_E8P
|
|
|
161
162
|
simo/core/__pycache__/managers.cpython-38.pyc,sha256=6RTIxyjOgpQGtAqcUyE2vFPS09w1V5Wmd_vOV7rHRRI,3370
|
|
162
163
|
simo/core/__pycache__/middleware.cpython-312.pyc,sha256=2VhHTVY-rdPNqNX0wst2ioVbHD5uMqHkY-tpujLdpH0,4195
|
|
163
164
|
simo/core/__pycache__/middleware.cpython-38.pyc,sha256=SgTLFNkKxvJ62hevSAVNZHgHdG_u2p7AZBhrj-jfFPs,2649
|
|
164
|
-
simo/core/__pycache__/models.cpython-312.pyc,sha256=
|
|
165
|
+
simo/core/__pycache__/models.cpython-312.pyc,sha256=z6RR7Rv4POCpVl5acn2VHVhLLRh59YhO1jq_JSO8CmI,31960
|
|
165
166
|
simo/core/__pycache__/models.cpython-38.pyc,sha256=A4JsWsDMBaQ_U5sV5cX0c_Uox9mP5fAqn_712EjfNS4,19605
|
|
166
167
|
simo/core/__pycache__/permissions.cpython-312.pyc,sha256=yqG6t9NZZtL30Hr7razjiG6JDGKiz0Qjcjxgv1C93vM,4450
|
|
167
168
|
simo/core/__pycache__/permissions.cpython-38.pyc,sha256=UdtxCTXPEbe99vgZOfRz9wfKSYvUn9hSRbpIV9CJSyI,2988
|
|
@@ -252,7 +253,7 @@ simo/core/management/_hub_template/hub/celeryc.py,sha256=3ksDXftIZKJ4Cq9WNKJERdZ
|
|
|
252
253
|
simo/core/management/_hub_template/hub/manage.py,sha256=PNNlw3EVeIJDgkG0l-klqoxsKWfTYWG9jzRG0upmAaI,620
|
|
253
254
|
simo/core/management/_hub_template/hub/nginx.conf,sha256=40hvXL42MeiqqkLURNcDQsRudv1dNFLJnvb2-Y3RCkk,2394
|
|
254
255
|
simo/core/management/_hub_template/hub/settings.py,sha256=4QhvhbtLRxHvAntwqG_qeAAtpDUqKvN4jzw9u3vqff8,361
|
|
255
|
-
simo/core/management/_hub_template/hub/supervisor.conf,sha256
|
|
256
|
+
simo/core/management/_hub_template/hub/supervisor.conf,sha256=11c39qVa1V5qf6OOK2PJhqKJXi5wfMhQNwd-TdWq2yA,2393
|
|
256
257
|
simo/core/management/_hub_template/hub/urls.py,sha256=Ydm-1BkYAzWeEF-MKSDIFf-7aE4qNLPm48-SA51XgJQ,25
|
|
257
258
|
simo/core/management/_hub_template/hub/wsgi.py,sha256=Lo-huLHnMDTxSmMBOodVFMWBls9poddrV2KRzXU0xGo,280
|
|
258
259
|
simo/core/management/commands/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
@@ -10307,7 +10308,7 @@ simo/core/templates/admin/action_intermediate_form.html,sha256=rOWF3UDv-SQZUfOzp
|
|
|
10307
10308
|
simo/core/templates/admin/base.html,sha256=yACgUEp8VLmvWwlLM0ygaeorWdn6ykX-7NkXFz3iZAI,6281
|
|
10308
10309
|
simo/core/templates/admin/clearable_easy_thumbnails_widget.html,sha256=Gh0z_KIEt3QC53HKJ_3QiPt9nIFCvwAx1gCny-dPHOM,704
|
|
10309
10310
|
simo/core/templates/admin/component_change_list.html,sha256=N3seVjTkIRXIXKGsleB7oWUnPlMRXDIqOPqfEyq4PNE,3320
|
|
10310
|
-
simo/core/templates/admin/component_history.html,sha256=
|
|
10311
|
+
simo/core/templates/admin/component_history.html,sha256=7vkWSDu5xugkrb79IffJ-CZts9h2EMZr9Gi4tqHBc0I,579
|
|
10311
10312
|
simo/core/templates/admin/formset_widget.html,sha256=NAonHNLyicsoE4Iir3My4AlVSz4Q7-1OA19zgsIOjJA,3563
|
|
10312
10313
|
simo/core/templates/admin/index.html,sha256=AJpt1FA4g6IAFQA99-3glJ88RmoWJ0YXBIz_DUt-R14,4015
|
|
10313
10314
|
simo/core/templates/admin/item_name_display.html,sha256=Y3zk-rc-y8_6dC6_WrSy6tx6Z-6IZzZ5w3a1hMxlpz4,357
|
|
@@ -10403,8 +10404,9 @@ simo/core/utils/__pycache__/type_constants.cpython-38.pyc,sha256=ERC5U7T5pThjLrs
|
|
|
10403
10404
|
simo/core/utils/__pycache__/validators.cpython-312.pyc,sha256=w8-XwptooefG-7gIIngVt1WWssIqNU3P1jPHBoYSYH4,1879
|
|
10404
10405
|
simo/core/utils/__pycache__/validators.cpython-38.pyc,sha256=gjeBOjL_keMoRDjdn8v-3F3wcjPIT3Xx5KpTalo0e-Y,1247
|
|
10405
10406
|
simo/fleet/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
10406
|
-
simo/fleet/admin.py,sha256=
|
|
10407
|
+
simo/fleet/admin.py,sha256=J_kiwYIGMTxnRuytl_YoM7Pxrj5LzlN9xC8E7Ag5yvQ,7006
|
|
10407
10408
|
simo/fleet/api.py,sha256=rJwAYJvp0uslW7O6Q4XOpOa8hfCdy3keUD7b3UNL43E,3424
|
|
10409
|
+
simo/fleet/apps.py,sha256=je8mRXMcRq4lABQZlyF2G2hOCkBUicR9I2jvrLDA8eI,238
|
|
10408
10410
|
simo/fleet/auto_urls.py,sha256=vrfrooPyY4pDuQjya-eLxCgZldfhwbEeEiXa7diO_CY,847
|
|
10409
10411
|
simo/fleet/base_types.py,sha256=wL9RVkHr0gA7HI1wZq0pruGEIgvQqpfnCL4cC3ywsvw,102
|
|
10410
10412
|
simo/fleet/ble.py,sha256=eHA_9ABjbmH1vUVCv9hiPXQL2GZZSEVwfO0xyI1S0nI,1081
|
|
@@ -10412,19 +10414,20 @@ simo/fleet/controllers.py,sha256=HV6onYd11YAc_f_8NFHAbv5IJZUEd1bDGTRMQtfFe-g,289
|
|
|
10412
10414
|
simo/fleet/forms.py,sha256=VcqATlX-omZruUgUC2fJpeLNUGSR-szmf36_M3aa5dg,66850
|
|
10413
10415
|
simo/fleet/gateways.py,sha256=C7dyapWDlJ5erYPNLkSoH50I8kj0lIXicSno0_CrdXc,5783
|
|
10414
10416
|
simo/fleet/managers.py,sha256=ZNeHFSkF5kzsl9E1DCBevOW6kXJlD6kw0LU4B-JMOG8,828
|
|
10415
|
-
simo/fleet/models.py,sha256=
|
|
10417
|
+
simo/fleet/models.py,sha256=lPiIurCNK--4mPFsg_QLo6GHbpYfE8Aju_G4VAKsRLM,17895
|
|
10416
10418
|
simo/fleet/routing.py,sha256=cofGsVWXMfPDwsJ6HM88xxtRxHwERhJ48Xyxc8mxg5o,149
|
|
10417
10419
|
simo/fleet/serializers.py,sha256=PQnjp7LaEpMts-om2OPV5XOU9ut6KFWiePPDCXK0M98,2679
|
|
10418
|
-
simo/fleet/socket_consumers.py,sha256=
|
|
10419
|
-
simo/fleet/tasks.py,sha256=
|
|
10420
|
+
simo/fleet/socket_consumers.py,sha256=fFsnpAvItGT1tYbkAkpTxBjG5v0cT-o-fmQRM9S1wsg,19630
|
|
10421
|
+
simo/fleet/tasks.py,sha256=VSY0cMFIs7Ocjz0_HwRLp-yaDdBl1T8U9T-7b8Ggegc,1308
|
|
10420
10422
|
simo/fleet/utils.py,sha256=wNJvURzLP3-aho3D3rfg07N9kWCaMIw5gOsmeeO9Nlg,4740
|
|
10421
10423
|
simo/fleet/views.py,sha256=3F8im6BsSOaK3KEuBNESE4sDbS_dWHYaOdhTR4cCLjE,5189
|
|
10422
10424
|
simo/fleet/__pycache__/__init__.cpython-312.pyc,sha256=-BZyG4uq87W18Ra1pKTjSzDgiLEAcw254NqdRX-vSJo,165
|
|
10423
10425
|
simo/fleet/__pycache__/__init__.cpython-38.pyc,sha256=pIZE7EL6-cuJ3pQtaSwjKLrKLsTYelp1k9sRhXKLh6s,159
|
|
10424
|
-
simo/fleet/__pycache__/admin.cpython-312.pyc,sha256=
|
|
10426
|
+
simo/fleet/__pycache__/admin.cpython-312.pyc,sha256=8TOuku1Atv-LyaVi4OICTc43iBFPgxCjrgjA9q6CN1s,9890
|
|
10425
10427
|
simo/fleet/__pycache__/admin.cpython-38.pyc,sha256=iweeu5AkaggBhQntP6-VF_eEodkNc6E7zKy0VjfwC2o,6652
|
|
10426
10428
|
simo/fleet/__pycache__/api.cpython-312.pyc,sha256=OM7s1FqGqTC2vwOOto0GhZrObEnI58SxMcTWaPtHD4Y,5892
|
|
10427
10429
|
simo/fleet/__pycache__/api.cpython-38.pyc,sha256=rZ1mkfkaMBEXhi9sw_jTKdk2CPJhBNxoImtjQ3Rf1VY,4016
|
|
10430
|
+
simo/fleet/__pycache__/apps.cpython-312.pyc,sha256=S8OK4R0W9VbNfD4Nos_CybjZ3AXS8CxvRaRUJO57xQA,707
|
|
10428
10431
|
simo/fleet/__pycache__/auto_urls.cpython-312.pyc,sha256=32yXKNoqMxNgYvsspUgx1A84LVQqr8LP4BRvURNCgeY,1026
|
|
10429
10432
|
simo/fleet/__pycache__/auto_urls.cpython-38.pyc,sha256=jHsvfwAumiBusr91QK1-qC-nmpPEC3r2uMGG8g0fABE,769
|
|
10430
10433
|
simo/fleet/__pycache__/base_types.cpython-312.pyc,sha256=cq-Pnje7FoMP608U_L_gsCfY_JMY23z87Uao-AxjNBw,304
|
|
@@ -10438,14 +10441,15 @@ simo/fleet/__pycache__/gateways.cpython-312.pyc,sha256=ZZGBAH2w9YmFvSrajZY8fUXd_
|
|
|
10438
10441
|
simo/fleet/__pycache__/gateways.cpython-38.pyc,sha256=MIpXuGWitGNdsxJ99fWvMXJ6sVE96ac7iR4K4aM4Sds,5148
|
|
10439
10442
|
simo/fleet/__pycache__/managers.cpython-312.pyc,sha256=sgcaERbhjilkFDCPqc8YZwSiEfRkXpufe0qDPqgLOiU,1733
|
|
10440
10443
|
simo/fleet/__pycache__/managers.cpython-38.pyc,sha256=Vmm23zoQnS3-uS5_WJt2n3wtjhLiEhLWaYxXJCU6Gts,1339
|
|
10441
|
-
simo/fleet/__pycache__/models.cpython-312.pyc,sha256=
|
|
10444
|
+
simo/fleet/__pycache__/models.cpython-312.pyc,sha256=OkCAXSrrMaxfsclJQV0yhlcBFerKV-xA7FQfR-2e7e0,25400
|
|
10442
10445
|
simo/fleet/__pycache__/models.cpython-38.pyc,sha256=AXk1Q_nnHDXirHYgM3EW5pLsrR2CaPWk4EuvGCuDUpI,14131
|
|
10443
10446
|
simo/fleet/__pycache__/routing.cpython-312.pyc,sha256=vafYpGAtYc2NYxBQObMX6eIZfVZflOYgzjYv0SL1jAQ,385
|
|
10444
10447
|
simo/fleet/__pycache__/routing.cpython-38.pyc,sha256=aPrCmxFKVyB8R8ZbJDwdPdFfvT7CvobovvZeq_mqRgY,314
|
|
10445
10448
|
simo/fleet/__pycache__/serializers.cpython-312.pyc,sha256=reKKBMohl7vi7iJ6wjRbOBoFn7J9ny8EE9wv5spBHYM,4912
|
|
10446
10449
|
simo/fleet/__pycache__/serializers.cpython-38.pyc,sha256=l_FzORWCM1hcSZV0AaGRO-p0CMTcEfqnLGgbn2IVvI0,3648
|
|
10447
|
-
simo/fleet/__pycache__/socket_consumers.cpython-312.pyc,sha256=
|
|
10450
|
+
simo/fleet/__pycache__/socket_consumers.cpython-312.pyc,sha256=r2n7h89t1HgI16UdUsaR9nrFFPgb-b2z9emn2eMdPZQ,28536
|
|
10448
10451
|
simo/fleet/__pycache__/socket_consumers.cpython-38.pyc,sha256=lEC1SkY_KgRY0QoBUMPjnbFwSa7qmCf-4eNQ45hAy68,14141
|
|
10452
|
+
simo/fleet/__pycache__/tasks.cpython-312.pyc,sha256=1XDir2GWxl7c17n0ZuCxOFKsLjaCqXgqVT4TcDvzhm8,1817
|
|
10449
10453
|
simo/fleet/__pycache__/tasks.cpython-38.pyc,sha256=RoNxL2WUiW67s9O9DjaYVVjCBSZu2nje0Qn9FJkWVS0,1116
|
|
10450
10454
|
simo/fleet/__pycache__/utils.cpython-312.pyc,sha256=mrICzm_po8QGUaRddn0TvBp5RDLBe1Jklg7xZr0YduM,4995
|
|
10451
10455
|
simo/fleet/__pycache__/utils.cpython-38.pyc,sha256=obUd-X2Y-ybx4icqUWq_qwIxrP9yyarJjexWAfO4MTI,3344
|
|
@@ -10586,17 +10590,19 @@ simo/fleet/migrations/__pycache__/0044_auto_20241210_0707.cpython-312.pyc,sha256
|
|
|
10586
10590
|
simo/fleet/migrations/__pycache__/0044_auto_20241210_0707.cpython-38.pyc,sha256=M99PMBDgOdS_0KIEhlUamHWeTVCTdIHFXIvYTRX15Z8,1029
|
|
10587
10591
|
simo/fleet/migrations/__pycache__/__init__.cpython-312.pyc,sha256=1rujN3qD3L0Q2MRB-gxwRKyShgUTX9NBpDGaIl42ozU,176
|
|
10588
10592
|
simo/fleet/migrations/__pycache__/__init__.cpython-38.pyc,sha256=5k1KW0jeSDzw6RnVPRq4CaO13Lg7M0F-pxA_gqqZ6Mg,170
|
|
10593
|
+
simo/fleet/templates/admin/colonel_history.html,sha256=YfA6LDVExk1sAWhBuiCLA6vb3XcBNN7_fpJNZzGFtB0,169
|
|
10589
10594
|
simo/fleet/templates/fleet/controllers_info/Button.md,sha256=GIuxqG617174NEtpPeCGVocxO4YMe7-CacgVSu_L5-E,739
|
|
10590
10595
|
simo/fleet/templates/fleet/controllers_info/ENS160AirQualitySensor.md,sha256=3LSTY9YPFuVPIbVsYCAifcotrXJcOXl2k774_vo6nAE,770
|
|
10591
10596
|
simo/generic/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
10592
10597
|
simo/generic/app_widgets.py,sha256=y8W3jR76Hh26O9pPQyg2SophMbYIOtAWD33MPKbB8Mg,856
|
|
10593
10598
|
simo/generic/base_types.py,sha256=u3SlfpNYaCwkVBwomWgso4ODzL71ay9MhiAW-bxgnDU,341
|
|
10594
10599
|
simo/generic/controllers.py,sha256=NZmrJ1BYzAwHJ1vN-OMvqnH13ihvjUhZqdxq-l__1Jk,46268
|
|
10595
|
-
simo/generic/forms.py,sha256=
|
|
10596
|
-
simo/generic/gateways.py,sha256=
|
|
10597
|
-
simo/generic/models.py,sha256=
|
|
10600
|
+
simo/generic/forms.py,sha256=1Gt3FycP4Da8LbtOuOGTpLSf2dCDEE-Sdr_X3qlA1aw,26265
|
|
10601
|
+
simo/generic/gateways.py,sha256=kjqGMjlLXlGNreKLQtHoSnw4xan1IB8-KF8AqiZ82T0,16057
|
|
10602
|
+
simo/generic/models.py,sha256=59fkYowOX0imviIhA6uwupvuharrpBykmBm674rJNoI,7279
|
|
10598
10603
|
simo/generic/routing.py,sha256=elQVZmgnPiieEuti4sJ7zITk1hlRxpgbotcutJJgC60,228
|
|
10599
10604
|
simo/generic/socket_consumers.py,sha256=qesKZVhI56Kh7vdIUDD3hzDUi0FcXwIfcmE_a3YS6JQ,1772
|
|
10605
|
+
simo/generic/tasks.py,sha256=5jhi7Jv7lfaM3T8GArWKaDqQfuvdBsKqz5obN6NVUqk,2570
|
|
10600
10606
|
simo/generic/__pycache__/__init__.cpython-312.pyc,sha256=-34GwswSg1zc1becA8lPwUpnd4ek2IoduCsIUr0KTNo,167
|
|
10601
10607
|
simo/generic/__pycache__/__init__.cpython-38.pyc,sha256=mLu54WS9KIl-pHwVCBKpsDFIlOqml--JsOVzAUHg6cU,161
|
|
10602
10608
|
simo/generic/__pycache__/app_widgets.cpython-312.pyc,sha256=ywoKk91YSEZxpyt9haG509_c0G9DMJVpae_y1iiZxJU,1937
|
|
@@ -10605,16 +10611,17 @@ simo/generic/__pycache__/base_types.cpython-312.pyc,sha256=h8Mwu49i-zmwTbL33JaLJ
|
|
|
10605
10611
|
simo/generic/__pycache__/base_types.cpython-38.pyc,sha256=aV5NdIuvXR-ItKpI__MwcyPZHD6Z882TFdgYkPCkr1I,493
|
|
10606
10612
|
simo/generic/__pycache__/controllers.cpython-312.pyc,sha256=TgHV-dNMJqYuFOgeJTxeh4Mihb4lHnfhid0CmdhZCR4,52737
|
|
10607
10613
|
simo/generic/__pycache__/controllers.cpython-38.pyc,sha256=jJjwKVaDYyazrRGNjUFoY74nr_jX_DEnsC9KjyxZCgc,30427
|
|
10608
|
-
simo/generic/__pycache__/forms.cpython-312.pyc,sha256=
|
|
10614
|
+
simo/generic/__pycache__/forms.cpython-312.pyc,sha256=nTyrgmEmOrgORJSOxUI_i2O7wVUXCQL9MYhaT76Jmew,34869
|
|
10609
10615
|
simo/generic/__pycache__/forms.cpython-38.pyc,sha256=k8lz3taXdWAg5P9jcnw66mWH51pCc4SOsg32kVEtBCg,19416
|
|
10610
|
-
simo/generic/__pycache__/gateways.cpython-312.pyc,sha256
|
|
10616
|
+
simo/generic/__pycache__/gateways.cpython-312.pyc,sha256=ixeotxPeqHkINziDnnkymlKR2wYMb5UR2sxiioMQxhY,19309
|
|
10611
10617
|
simo/generic/__pycache__/gateways.cpython-38.pyc,sha256=GIeMT51oZU2OCFD4eUDFdSRRYE0Qf14AcOr_gdUqG94,12705
|
|
10612
|
-
simo/generic/__pycache__/models.cpython-312.pyc,sha256=
|
|
10618
|
+
simo/generic/__pycache__/models.cpython-312.pyc,sha256=ggaeX6BQa-0-KG50HadpRCWeW84Fbog0muT2gBkqLNQ,10190
|
|
10613
10619
|
simo/generic/__pycache__/models.cpython-38.pyc,sha256=MZpum7syAFxuulf47K7gtUlJJ7xRD-IBUBAwUM1ZRnw,5825
|
|
10614
10620
|
simo/generic/__pycache__/routing.cpython-312.pyc,sha256=_wQPZeAgwlGtnafw9VcabgqjyJxzDFywHBIFbGhzYRE,452
|
|
10615
10621
|
simo/generic/__pycache__/routing.cpython-38.pyc,sha256=xtxTUTBTdivzFyA5Wh7k-hUj1WDO_FiRq6HYXdbr9Ks,382
|
|
10616
10622
|
simo/generic/__pycache__/socket_consumers.cpython-312.pyc,sha256=zERTr2bHXLKSXCoIov6MaFRrgfeS1A0IHCXbaPfVvK4,2814
|
|
10617
10623
|
simo/generic/__pycache__/socket_consumers.cpython-38.pyc,sha256=FaVCf_uJI2uwj1Zz-jwsOXou65oV9gFCIB8e-YKquRk,1662
|
|
10624
|
+
simo/generic/__pycache__/tasks.cpython-312.pyc,sha256=zEyNpFVmEJoZdeYKNZ7cEmPtIyZMTwEqzZJZZwMYl-o,4494
|
|
10618
10625
|
simo/generic/migrations/0001_initial.py,sha256=7FpPcfpRU5ya0b8s2KbxR5a3npf92YruvZltUybjzys,676
|
|
10619
10626
|
simo/generic/migrations/0002_auto_20241126_0726.py,sha256=SX38JwP732QooOm5HM1Xo7Th_Mv_6YZloT3eozULOhs,922
|
|
10620
10627
|
simo/generic/migrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
@@ -10719,6 +10726,7 @@ simo/notifications/__pycache__/models.cpython-312.pyc,sha256=VKa2ZQxzBH22YAUlsmh
|
|
|
10719
10726
|
simo/notifications/__pycache__/models.cpython-38.pyc,sha256=PoqLuOnlaAWQQ-20AtqhvAlLSkakPmdn7J7wGvHNW3g,2449
|
|
10720
10727
|
simo/notifications/__pycache__/serializers.cpython-312.pyc,sha256=on-lu0Sj7vEl0_-2ul19Yk59gYHIFESXZKpA9RKNewQ,1226
|
|
10721
10728
|
simo/notifications/__pycache__/serializers.cpython-38.pyc,sha256=7-eRGKYuQ4g1SpKOMpz17SIiu1HmaMoYv-cJbaO9QGA,1028
|
|
10729
|
+
simo/notifications/__pycache__/utils.cpython-312.pyc,sha256=o-CnxcAtX7tHo2CdGBKjLTd1OU3z57zUCaWlYzEuv88,2423
|
|
10722
10730
|
simo/notifications/__pycache__/utils.cpython-38.pyc,sha256=4ZnI-pmWji84EXBkPrl4ir1kGjfanO4bH5--bLNRxCA,1648
|
|
10723
10731
|
simo/notifications/migrations/0001_initial.py,sha256=Zh69AQ-EKlQKfqfnMDVRcxvo1MxRY-TFLCdnNcgqi6g,2003
|
|
10724
10732
|
simo/notifications/migrations/0002_notification_instance.py,sha256=B3msbMeKvsuq-V7gvRADRjj5PFLayhi3pQvHZjqzO5g,563
|
|
@@ -10777,6 +10785,7 @@ simo/users/__pycache__/sso_urls.cpython-312.pyc,sha256=FQLOFu310j7pOhTNlg2wyUybq
|
|
|
10777
10785
|
simo/users/__pycache__/sso_urls.cpython-38.pyc,sha256=uAwDozpOmrhUald-8tOHANILXkH7-TI8fNYXOtPkSY8,402
|
|
10778
10786
|
simo/users/__pycache__/sso_views.cpython-312.pyc,sha256=FZGDRdt7pb8qROe8ZAEGSTfeZtLCGNBvQiXpDtrc4dE,6011
|
|
10779
10787
|
simo/users/__pycache__/sso_views.cpython-38.pyc,sha256=PLRF6FYCxRhnmgnN_gUS-pdQlH7lofLU1Xhgw3vDO_Y,4019
|
|
10788
|
+
simo/users/__pycache__/tasks.cpython-312.pyc,sha256=cu-VV1HWXToHR875DFC2E9_x1cn1mD5mXYqltk0QOuw,1955
|
|
10780
10789
|
simo/users/__pycache__/tasks.cpython-38.pyc,sha256=XLMKt3suT7BlcXrJZoH9ZIhhtBuqyiW4lsOB9IbBkko,1225
|
|
10781
10790
|
simo/users/__pycache__/utils.cpython-312.pyc,sha256=dJtfFncpI2QtTSZoUUNCfUsDoofOOfVtMRXNm5ZXC20,3515
|
|
10782
10791
|
simo/users/__pycache__/utils.cpython-38.pyc,sha256=cgxUwEaBgxT360mw4E03J7u4vi3dhw6K3n7-8WNvyFU,1888
|
|
@@ -10924,9 +10933,9 @@ simo/users/templates/invitations/expired_msg.html,sha256=47DEQpj8HBSa-_TImW-5JCe
|
|
|
10924
10933
|
simo/users/templates/invitations/expired_suggestion.html,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
10925
10934
|
simo/users/templates/invitations/taken_msg.html,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
10926
10935
|
simo/users/templates/invitations/taken_suggestion.html,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
10927
|
-
simo-2.8.
|
|
10928
|
-
simo-2.8.
|
|
10929
|
-
simo-2.8.
|
|
10930
|
-
simo-2.8.
|
|
10931
|
-
simo-2.8.
|
|
10932
|
-
simo-2.8.
|
|
10936
|
+
simo-2.8.5.dist-info/LICENSE.md,sha256=M7wm1EmMGDtwPRdg7kW4d00h1uAXjKOT3HFScYQMeiE,34916
|
|
10937
|
+
simo-2.8.5.dist-info/METADATA,sha256=J6rygMMxiXfeOMVZEhetZjpGJKHF_m2obJCRJ5D7y00,2008
|
|
10938
|
+
simo-2.8.5.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
|
|
10939
|
+
simo-2.8.5.dist-info/entry_points.txt,sha256=S9PwnUYmTSW7681GKDCxUbL0leRJIaRk6fDQIKgbZBA,135
|
|
10940
|
+
simo-2.8.5.dist-info/top_level.txt,sha256=GmS1hrAbpVqn9OWZh6UX82eIOdRLgYA82RG9fe8v4Rs,5
|
|
10941
|
+
simo-2.8.5.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|