Commit 6f83043c31cde701499cc0af78a881d458a94f88
1 parent
e2712b10
Exists in
dev
Add websocket for agent_cmd & agent_survey models & views
Showing
14 changed files
with
457 additions
and
92 deletions
Show diff stats
CHANGELOG
VERSION
docker/docker-compose.yml
... | ... | @@ -25,6 +25,16 @@ services: |
25 | 25 | interval: 5s |
26 | 26 | retries: 20 |
27 | 27 | |
28 | + redis: | |
29 | + container_name: redis | |
30 | + image: redis:latest | |
31 | + restart: always | |
32 | + ports: | |
33 | + - "6379:6379" | |
34 | + networks: | |
35 | + pyros-network: | |
36 | + ipv4_address: 172.19.0.5 | |
37 | + | |
28 | 38 | # service image of python, that let users to interact with python scripts such as pyros. |
29 | 39 | pyros: |
30 | 40 | # path to the Dockerfile of this image |
... | ... | @@ -61,6 +71,7 @@ services: |
61 | 71 | # starting db service before install service |
62 | 72 | depends_on: |
63 | 73 | - db |
74 | + - redis | |
64 | 75 | # create network to allow images to communicate with other images within the same network |
65 | 76 | networks: |
66 | 77 | pyros-network: |
... | ... | @@ -85,6 +96,8 @@ services: |
85 | 96 | ports: |
86 | 97 | - "${PHPMYADMIN_PORT:-8081}:80" |
87 | 98 | |
99 | + | |
100 | + | |
88 | 101 | # declaring volumes |
89 | 102 | volumes: |
90 | 103 | db: | ... | ... |
install/requirements_dev.txt
... | ... | @@ -22,3 +22,14 @@ GitPython==3.1.24 |
22 | 22 | # For working with date |
23 | 23 | python-dateutil==2.8.2 |
24 | 24 | |
25 | +# For MCO CNES | |
26 | + | |
27 | +astropy | |
28 | +photutils | |
29 | +scipy | |
30 | + | |
31 | +# for websocket | |
32 | +channels | |
33 | +daphne | |
34 | +attrs==22.1.0 | |
35 | +channels_redis==3.4.1 | |
25 | 36 | \ No newline at end of file | ... | ... |
... | ... | @@ -0,0 +1,133 @@ |
1 | +# consumers.py | |
2 | +from django.contrib.auth.models import User | |
3 | +from .serializers import AgentCmdSerializer, AgentSurveySerializer | |
4 | +from common.models import AgentCmd, AgentSurvey | |
5 | +from asgiref.sync import async_to_sync | |
6 | +import json | |
7 | +from channels.db import database_sync_to_async | |
8 | +from .functions import get_agent_survey_instance, get_list_agent_cmd | |
9 | + | |
10 | +from channels.generic.websocket import AsyncWebsocketConsumer | |
11 | + | |
12 | + | |
13 | +class AgentCmdConsumer(AsyncWebsocketConsumer): | |
14 | + async def connect(self): | |
15 | + self.agent_name = self.scope["url_route"]["kwargs"]["agent_name"] | |
16 | + # Join room group | |
17 | + await self.channel_layer.group_add(f'agentcmd_{self.agent_name}_observers', self.channel_name) | |
18 | + await self.accept() | |
19 | + # try: | |
20 | + # agent_cmds = await database_sync_to_async(get_list_agent_cmd)(self.agent_name) | |
21 | + # except Exception as e: | |
22 | + # print(e) | |
23 | + await self.channel_layer.group_send( | |
24 | + f'agentcmd_{self.agent_name}_observers', {"type": "send_agentcmd_instance", "data": None,"action":"list"} | |
25 | + ) | |
26 | + | |
27 | + def get_agentcmd(self,id): | |
28 | + return AgentCmdSerializer(AgentCmd.objects.get(id=id)).data | |
29 | + | |
30 | + # async def disconnect(self, close_code): | |
31 | + # # Leave room group | |
32 | + # await self.channel_layer.group_discard(f'agentcmd_{self.agent_name}_observers', self.channel_name) | |
33 | + # await self.close(close_code) | |
34 | + | |
35 | + # Receive message from WebSocket | |
36 | + async def receive(self, text_data): | |
37 | + text_data_json = json.loads(text_data) | |
38 | + message = text_data_json["message"] | |
39 | + | |
40 | + # Send message to room group | |
41 | + await self.channel_layer.group_send( | |
42 | + f'agentcmd_{self.agent_name}_observers', {"type": "new_agentcmd_instance", "agent_name": self.agent_name,"action":"create"} | |
43 | + ) | |
44 | + | |
45 | + # ON Receive, send data to observers | |
46 | + async def send_agentcmd_instance(self, event): | |
47 | + data = event.get("data",None) | |
48 | + action = event["action"] | |
49 | + if action == "list": | |
50 | + data = await database_sync_to_async(get_list_agent_cmd)(self.agent_name) | |
51 | + elif action == "create" or action == "update": | |
52 | + id = data | |
53 | + data = await database_sync_to_async(self.get_agentcmd)(id) | |
54 | + # Send message to WebSocket | |
55 | + await self.send(text_data=json.dumps({"data": data,"action":action})) | |
56 | + | |
57 | + | |
58 | + | |
59 | +class AgentSurveyDetailConsumer(AsyncWebsocketConsumer): | |
60 | + async def connect(self): | |
61 | + self.agent_name = self.scope["url_route"]["kwargs"].get("agent_name") | |
62 | + print(self.agent_name) | |
63 | + # Join room group | |
64 | + await self.channel_layer.group_add(f'agentsurvey_{self.agent_name}_observers', self.channel_name) | |
65 | + await self.accept() | |
66 | + # try: | |
67 | + # agent_survey = await database_sync_to_async(self.get_agent_survey_instance)() | |
68 | + # except Exception as e: | |
69 | + # print("exception websocket : ",e) | |
70 | + await self.channel_layer.group_send( | |
71 | + f'agentsurvey_{self.agent_name}_observers', {"type": "send_agentsurvey_instance", "data": None} | |
72 | + ) | |
73 | + | |
74 | + def get_agent_survey_instance(self): | |
75 | + agent_survey = AgentSurvey.objects.get(name=self.agent_name) | |
76 | + return AgentSurveySerializer(agent_survey).data | |
77 | + | |
78 | + # async def disconnect(self, close_code): | |
79 | + # # Leave room group | |
80 | + # await self.channel_layer.group_discard(f'agentsurvey_{self.agent_name}_observers', self.channel_name) | |
81 | + # await self.close(close_code) | |
82 | + | |
83 | + # Receive message from WebSocket | |
84 | + async def receive(self, text_data): | |
85 | + text_data_json = json.loads(text_data) | |
86 | + message = text_data_json["message"] | |
87 | + | |
88 | + # Send message to room group | |
89 | + await self.channel_layer.group_send( | |
90 | + f'agentsurvey_{self.agent_name}_observers', {"type": "new_agentsurvey_instance", "agent_name": self.agent_name,"action":"create"} | |
91 | + ) | |
92 | + | |
93 | + # Receive message from room group | |
94 | + async def send_agentsurvey_instance(self, event): | |
95 | + data = await database_sync_to_async(self.get_agent_survey_instance)() | |
96 | + # Send message to WebSocket | |
97 | + await self.send(text_data=json.dumps({"data": data})) | |
98 | + | |
99 | + | |
100 | +class AgentSurveyConsumer(AsyncWebsocketConsumer): | |
101 | + async def connect(self): | |
102 | + # Join room group | |
103 | + await self.channel_layer.group_add(f'agentsurvey_observers', self.channel_name) | |
104 | + await self.accept() | |
105 | + # try: | |
106 | + # agent_survey = await database_sync_to_async(get_agent_survey_instance)() | |
107 | + # except Exception as e: | |
108 | + # print(e) | |
109 | + await self.channel_layer.group_send( | |
110 | + f'agentsurvey_observers', {"type": "send_agentsurvey_instance", "data": None} | |
111 | + ) | |
112 | + | |
113 | + | |
114 | + # async def disconnect(self, close_code): | |
115 | + # # Leave room group | |
116 | + # await self.channel_layer.group_discard(f'agentsurvey_observers', self.channel_name) | |
117 | + # await self.close(close_code) | |
118 | + | |
119 | + # Receive message from WebSocket | |
120 | + async def receive(self, text_data): | |
121 | + text_data_json = json.loads(text_data) | |
122 | + message = text_data_json["message"] | |
123 | + | |
124 | + # Send message to room group | |
125 | + await self.channel_layer.group_send( | |
126 | + f'agentsurvey_observers', {"type": "new_agentsurvey_instance", "agent_name": self.agent_name,"action":"create"} | |
127 | + ) | |
128 | + | |
129 | + # Receive message from room group | |
130 | + async def send_agentsurvey_instance(self, event): | |
131 | + data = await database_sync_to_async(get_agent_survey_instance)() | |
132 | + # Send message to WebSocket | |
133 | + await self.send(text_data=json.dumps({"data": data})) | ... | ... |
... | ... | @@ -0,0 +1,24 @@ |
1 | +from .serializers import AgentCmdSerializer, AgentSurveySerializer | |
2 | +from common.models import AgentCmd, AgentSurvey | |
3 | +from datetime import datetime, timezone, timedelta | |
4 | + | |
5 | + | |
6 | +def get_list_agent_cmd(agent_name): | |
7 | + commands_sent_by_agent = AgentCmd.get_commands_sent_by_agent(agent_name) | |
8 | + commands_recivied_by_agent = AgentCmd.get_commands_sent_to_agent(agent_name) | |
9 | + number = 20 | |
10 | + agent_cmds = commands_sent_by_agent | commands_recivied_by_agent | |
11 | + agent_cmds = agent_cmds.exclude(full_name="get_specific_cmds") | |
12 | + agent_cmds = agent_cmds.exclude(full_name="get_all_cmds") | |
13 | + agent_cmds = agent_cmds.order_by("-s_deposit_time") | |
14 | + agent_cmds = agent_cmds[:number] | |
15 | + return AgentCmdSerializer(agent_cmds,many=True).data | |
16 | + | |
17 | + | |
18 | +def get_agent_survey_instance(): | |
19 | + agents = AgentSurvey.objects.all() | |
20 | + datetime_now = datetime.utcnow() | |
21 | + date_minus_two_days = datetime_now - timedelta(days=2) | |
22 | + date_minus_two_days = date_minus_two_days.replace(tzinfo=timezone.utc) | |
23 | + agents = agents.exclude(updated__lt=date_minus_two_days) | |
24 | + return AgentSurveySerializer(agents,many=True).data | |
0 | 25 | \ No newline at end of file | ... | ... |
... | ... | @@ -0,0 +1,10 @@ |
1 | +from django.urls import path, re_path | |
2 | +from .views import AgentCmdViewSet | |
3 | +from .consumers import AgentCmdConsumer, AgentSurveyConsumer, AgentSurveyDetailConsumer | |
4 | + | |
5 | + | |
6 | +websocket_urlpatterns = [ | |
7 | + re_path(r"^ws/agent_cmd/(?P<agent_name>\w+)/$", AgentCmdConsumer.as_asgi()), | |
8 | + re_path(r"^ws/agent_survey/(?P<agent_name>\w+)/$", AgentSurveyDetailConsumer.as_asgi()), | |
9 | + re_path(r"^ws/agent_survey/", AgentSurveyConsumer.as_asgi()), | |
10 | +] | |
0 | 11 | \ No newline at end of file | ... | ... |
src/core/pyros_django/api/views.py
... | ... | @@ -294,7 +294,7 @@ class AgentCmdViewSet(viewsets.ModelViewSet): |
294 | 294 | http_method_names = ["get"] |
295 | 295 | def get_queryset(self): |
296 | 296 | agent_name = self.request.query_params.get('agent_name') |
297 | - number = self.request.query_params.get('number') | |
297 | + number = self.request.query_params.get('number',20) | |
298 | 298 | if agent_name is None: |
299 | 299 | if "agent_name" in self.kwargs: |
300 | 300 | agent_name = self.kwargs["agent_name"] | ... | ... |
src/core/pyros_django/common/models.py
... | ... | @@ -25,6 +25,10 @@ from django.db.models.expressions import F |
25 | 25 | from django.db.models.query import QuerySet |
26 | 26 | from model_utils import Choices |
27 | 27 | from django.utils import timezone |
28 | +from asgiref.sync import async_to_sync | |
29 | +from channels.layers import get_channel_layer | |
30 | +from django.db.models.signals import post_save | |
31 | +from django.dispatch import receiver | |
28 | 32 | # Project imports |
29 | 33 | from user_manager.models import PyrosUser |
30 | 34 | # DeviceCommand is used by class Command |
... | ... | @@ -1143,6 +1147,74 @@ class AgentCmd(models.Model): |
1143 | 1147 | # Optimization, but does not work, why ?... |
1144 | 1148 | # self.save(update_fields=["state"]) |
1145 | 1149 | |
1150 | + # override save to use websocket | |
1151 | + # def save(self, *args, **kwargs): | |
1152 | + # super().save(*args, **kwargs) | |
1153 | + # try: | |
1154 | + # if self.pk == None: | |
1155 | + # action = "create" | |
1156 | + # else: | |
1157 | + # action = "update" | |
1158 | + # agent_survey = AgentSurvey.objects.all().values_list("name",flat=True) | |
1159 | + # # initialize value, there is always a sender | |
1160 | + # agent_name = self.sender | |
1161 | + # if self.sender in agent_survey: | |
1162 | + # agent_name = self.sender | |
1163 | + # # send to agentcmd_observers group a message to execute function new_agentcmd_agent_name_instance | |
1164 | + # if self.recipient in agent_survey: | |
1165 | + # agent_name = self.recipient | |
1166 | + # # send to agentcmd_observers group a message to execute function new_agentcmd_agent_name_instance | |
1167 | + # async_to_sync(get_channel_layer().group_send)( | |
1168 | + # f'agentcmd_{agent_name}_observers', {"type": f"send_agentcmd_instance","data":self.id,"action":action} | |
1169 | + # ) | |
1170 | + # except Exception as e: | |
1171 | + # print(e) | |
1172 | + | |
1173 | +# signals decorator to trigger function after Model AgentCmd call save() | |
1174 | +@receiver(post_save, sender=AgentCmd) | |
1175 | +def send_agentcmd_to_websocket(sender, instance, created, **kwargs): | |
1176 | + agent_survey = AgentSurvey.objects.all().values_list("name",flat=True) | |
1177 | + if instance.sender in agent_survey: | |
1178 | + agent_name = instance.sender | |
1179 | + # send to agentcmd_observers group a message to execute function new_agentcmd_agent_name_instance | |
1180 | + async_to_sync(get_channel_layer().group_send)( | |
1181 | + f'agentcmd_{agent_name}_observers', {"type": f"send_agentcmd_instance","action":"list"} | |
1182 | + ) | |
1183 | + elif instance.recipient in agent_survey: | |
1184 | + agent_name = instance.recipient | |
1185 | + # send to agentcmd_observers group a message to execute function new_agentcmd_agent_name_instance | |
1186 | + async_to_sync(get_channel_layer().group_send)( | |
1187 | + f'agentcmd_{agent_name}_observers', {"type": f"send_agentcmd_instance","action":"list"} | |
1188 | + ) | |
1189 | + # if created: | |
1190 | + # action = "create" | |
1191 | + # else: | |
1192 | + # action = "update" | |
1193 | + # agent_survey = AgentSurvey.objects.all().values_list("name",flat=True) | |
1194 | + # # initialize value, there is always a sender | |
1195 | + # agent_name = instance.sender | |
1196 | + # if instance.sender in agent_survey: | |
1197 | + # agent_name = selinstancef.sender | |
1198 | + # # send to agentcmd_observers group a message to execute function new_agentcmd_agent_name_instance | |
1199 | + # if instance.recipient in agent_survey: | |
1200 | + # agent_name = instance.recipient | |
1201 | + # # send to agentcmd_observers group a message to execute function new_agentcmd_agent_name_instance | |
1202 | + # async_to_sync(get_channel_layer().group_send)( | |
1203 | + # f'agentcmd_{agent_name}_observers', {"type": f"send_agentcmd_instance","data":instance.id,"action":action} | |
1204 | + # ) | |
1205 | + | |
1206 | + | |
1207 | +@receiver(post_save, sender=AgentSurvey) | |
1208 | +def send_agentsurvey_to_websocket(sender, instance, created, **kwargs): | |
1209 | + agent_name = instance.name | |
1210 | + # send to agentsurvey_observers group a message to execute function new_agentcmd_agent_name_instance | |
1211 | + async_to_sync(get_channel_layer().group_send)( | |
1212 | + f'agentsurvey_{agent_name}_observers', {"type": f"send_agentsurvey_instance"} | |
1213 | + ) | |
1214 | + async_to_sync(get_channel_layer().group_send)( | |
1215 | + f'agentsurvey_observers', {"type": f"send_agentsurvey_instance"} | |
1216 | + ) | |
1217 | + | |
1146 | 1218 | |
1147 | 1219 | # TODO: OLD Config : ร virer (mais utilisรฉ dans dashboard/templatetags/tags.py) |
1148 | 1220 | class Config(models.Model): | ... | ... |
src/core/pyros_django/dashboard/templates/dashboard/agent_detail.html
... | ... | @@ -113,7 +113,7 @@ |
113 | 113 | {% endfor %} {% endcomment %} |
114 | 114 | <div id="additional_fields" style="display:inline;"></div> |
115 | 115 | {% comment %} <input type="text" name="cmd_args" id="cmd_args"></input> {% endcomment %} |
116 | - <input id="send_cmd_btn" type="submit" value="Send command"></input> | |
116 | + <input id="send_cmd_btn" class="btn btn-info" type="submit" value="Send command"></input> | |
117 | 117 | </form> |
118 | 118 | </div> |
119 | 119 | {% comment %} {% else %} {% endcomment %} |
... | ... | @@ -138,8 +138,11 @@ |
138 | 138 | <thead> |
139 | 139 | <tr> |
140 | 140 | <th>Agent</th> |
141 | + {% if agentsst_computers %} | |
142 | + <th> Computer </th> | |
143 | + {% endif %} | |
141 | 144 | <th>Mode</th> |
142 | - <th style="min-width:10vw;">status</th> | |
145 | + <th style="min-width:16vw;">status</th> | |
143 | 146 | {% if CAN_SEND_COMMAND %} |
144 | 147 | <th>Actions</th> |
145 | 148 | {% endif %} |
... | ... | @@ -150,6 +153,9 @@ |
150 | 153 | <tr> |
151 | 154 | <td><a href="{% url 'agent_detail' agent %}"> {{ agent }}</a> </td> |
152 | 155 | {% comment %} <td> {{ status_of_agents|get_item:agent }}</td> {% endcomment %} |
156 | + {% if agentsst_computers %} | |
157 | + <td> {{agentsst_computers|get_item:agent}} </td> | |
158 | + {% endif %} | |
153 | 159 | <td class="agent_mode"></td> |
154 | 160 | <td class="agent_status"></td> |
155 | 161 | {% if CAN_SEND_COMMAND %} |
... | ... | @@ -463,51 +469,47 @@ |
463 | 469 | get_parameters_of_selected_cmd(); |
464 | 470 | }); |
465 | 471 | |
466 | - function get_agents_status(){ | |
467 | - get_agent_status(); | |
472 | + function update_agents_status(){ | |
468 | 473 | $('.agent_status').each(function(index){ |
469 | 474 | let agent_name = document.getElementsByClassName("agent_status")[index].parentElement.children[0].textContent; |
470 | 475 | agent_name = agent_name.trim(); |
471 | 476 | let agent_status = $(this); |
472 | 477 | let agent_mode = $(this).prev(); |
473 | - fetch("/api/agent_survey/"+agent_name+"?format=json").then(response => response.json()).then(function(data){ | |
474 | - agent_status.text(data.status); | |
475 | - agent_mode.text(data.mode); | |
476 | - if (main_agent_status == "EXITING"){ | |
477 | - document.getElementsByClassName("agent_status")[index].parentElement.children[3].querySelector("#startbtn").disabled = true; | |
478 | - document.getElementsByClassName("agent_status")[index].parentElement.children[3].querySelector("#stopbtn").disabled = true; | |
479 | - document.getElementsByClassName("agent_status")[index].parentElement.children[3].querySelector("#restartbtn").disabled = true; | |
480 | - document.getElementsByClassName("agent_status")[index].parentElement.children[3].querySelector("#restarthardbtn").disabled = true; | |
481 | - } | |
482 | - else{ | |
483 | - if (data.status == "EXITING"){ | |
484 | - document.getElementsByClassName("agent_status")[index].parentElement.children[3].querySelector("#startbtn").disabled = false; | |
478 | + | |
479 | + if (agent_status != null){ | |
480 | + | |
481 | + var ws = new WebSocket(`ws://${window.location.host}/ws/agent_survey/${agent_name}/`) | |
482 | + ws.onmessage = function (e) { | |
483 | + allData = JSON.parse(e.data); | |
484 | + data = allData.data; | |
485 | + agent_status.text(data.status); | |
486 | + agent_mode.text(data.mode); | |
487 | + if (main_agent_status == "EXITING"){ | |
488 | + document.getElementsByClassName("agent_status")[index].parentElement.children[3].querySelector("#startbtn").disabled = true; | |
485 | 489 | document.getElementsByClassName("agent_status")[index].parentElement.children[3].querySelector("#stopbtn").disabled = true; |
486 | 490 | document.getElementsByClassName("agent_status")[index].parentElement.children[3].querySelector("#restartbtn").disabled = true; |
487 | 491 | document.getElementsByClassName("agent_status")[index].parentElement.children[3].querySelector("#restarthardbtn").disabled = true; |
488 | 492 | } |
489 | 493 | else{ |
490 | - document.getElementsByClassName("agent_status")[index].parentElement.children[3].querySelector("#startbtn").disabled = true; | |
491 | - document.getElementsByClassName("agent_status")[index].parentElement.children[3].querySelector("#stopbtn").disabled = false; | |
492 | - document.getElementsByClassName("agent_status")[index].parentElement.children[3].querySelector("#restartbtn").disabled = false; | |
493 | - document.getElementsByClassName("agent_status")[index].parentElement.children[3].querySelector("#restarthardbtn").disabled = false; | |
494 | + if (data.status == "EXITING"){ | |
495 | + document.getElementsByClassName("agent_status")[index].parentElement.children[3].querySelector("#startbtn").disabled = false; | |
496 | + document.getElementsByClassName("agent_status")[index].parentElement.children[3].querySelector("#stopbtn").disabled = true; | |
497 | + document.getElementsByClassName("agent_status")[index].parentElement.children[3].querySelector("#restartbtn").disabled = true; | |
498 | + document.getElementsByClassName("agent_status")[index].parentElement.children[3].querySelector("#restarthardbtn").disabled = true; | |
499 | + } | |
500 | + else{ | |
501 | + document.getElementsByClassName("agent_status")[index].parentElement.children[3].querySelector("#startbtn").disabled = true; | |
502 | + document.getElementsByClassName("agent_status")[index].parentElement.children[3].querySelector("#stopbtn").disabled = false; | |
503 | + document.getElementsByClassName("agent_status")[index].parentElement.children[3].querySelector("#restartbtn").disabled = false; | |
504 | + document.getElementsByClassName("agent_status")[index].parentElement.children[3].querySelector("#restarthardbtn").disabled = false; | |
505 | + } | |
506 | + | |
494 | 507 | } |
495 | - | |
496 | 508 | } |
497 | - }); | |
498 | - | |
509 | + } | |
499 | 510 | }); |
500 | 511 | } |
501 | - get_agents_status(); | |
502 | - setInterval(get_agents_status,4000); | |
503 | - var main_agent_status; | |
504 | - var previous_status = "none"; | |
505 | - async function get_agent_status(){ | |
506 | - await fetch("/api/agent_survey/{{agent_name}}?format=json").then(response => response.json()).then(function(data){ | |
507 | - main_agent_status = data.status; | |
508 | - }); | |
509 | - | |
510 | - } | |
512 | + | |
511 | 513 | function refresh_agent_info(){ |
512 | 514 | |
513 | 515 | fetch("/api/agent_survey/{{agent_name}}?format=json").then(response => response.json()).then(function(data){ |
... | ... | @@ -533,46 +535,64 @@ |
533 | 535 | } |
534 | 536 | function websocket_agentcmd(){ |
535 | 537 | var ws = new WebSocket(`ws://${window.location.host}/ws/agent_cmd/{{agent_name}}/`) |
536 | - //var ws = new WebSocket(`ws://${window.location.host}/ws/test/{{agent_name}}/`) | |
537 | - /*ws.onopen = function(){ | |
538 | - ws.send(JSON.stringify({ | |
539 | - action: "list", | |
540 | - request_id: new Date().getTime(), | |
541 | - })) | |
542 | - }*/ | |
543 | - /* | |
544 | - ws.onopen = function(){ | |
545 | - ws.send(JSON.stringify({ | |
546 | - action: "subscribe_to_agent_cmd_activity", | |
547 | - request_id: new Date().getTime(), | |
548 | - })) | |
549 | - } | |
550 | - ws.onmessage = function(e){ | |
551 | - console.log(e) | |
552 | - app.commands = e.data | |
553 | - }*/ | |
554 | - ws.onopen = function (e) { | |
555 | - ws.send( | |
556 | - JSON.stringify({ | |
557 | - action: "list", | |
558 | - request_id: new Date().getTime(), | |
559 | - }) | |
560 | - ); | |
561 | - console.log(ws) | |
562 | - }; | |
563 | - | |
538 | + last_id = null; | |
564 | 539 | ws.onmessage = function (e) { |
565 | 540 | allData = JSON.parse(e.data); |
566 | 541 | if (allData.action === "list") { |
567 | 542 | app.commands = allData.data; |
568 | - app.$forceUpdate(); | |
569 | - } else if (allData.action === "create") { | |
570 | - app.commands.push(allData.data); | |
543 | + } | |
544 | + /* | |
545 | + // OLD (tried to optimize) | |
546 | + else if (allData.action == "create") { | |
547 | + console.log("last id"); | |
548 | + console.log(last_id) | |
549 | + console.log("current id"); | |
550 | + console.log(allData.data.id) | |
551 | + app.commands.unshift(allData.data); | |
552 | + last_id = allData.data.id | |
571 | 553 | } |
554 | + else if (allData.action == "update"){ | |
555 | + if (last_id == allData.data.id){ | |
556 | + console.log("last id"); | |
557 | + console.log(last_id) | |
558 | + console.log("current id"); | |
559 | + console.log(allData.data.id) | |
560 | + app.commands.shift(); | |
561 | + app.commands.unshift(allData.data); | |
562 | + last_id = allData.data.id | |
563 | + } | |
564 | + } | |
565 | + */ | |
566 | + app.$forceUpdate(); | |
572 | 567 | }; |
573 | - ws.onclose = function(e) { | |
568 | + ws.onerror = function(error) { | |
574 | 569 | let now = new Date(); |
575 | - console.error(now.toLocaleString() + ':' + now.getMilliseconds() + ' โ Analysis socket closed with event code = ' + e.code + ' and reason=' + e.reason); | |
570 | + let msg = now.toLocaleString() + ':' + now.getMilliseconds() + ' โ WebSocket error: ' + error; | |
571 | + console.error(msg); | |
572 | + } | |
573 | + } | |
574 | + var previous_status = "none"; | |
575 | + function websocket_agentsurvey(){ | |
576 | + var ws = new WebSocket(`ws://${window.location.host}/ws/agent_survey/{{agent_name}}/`) | |
577 | + last_id = null; | |
578 | + ws.onmessage = function (e) { | |
579 | + allData = JSON.parse(e.data); | |
580 | + app2.agent = allData.data; | |
581 | + data = allData.data | |
582 | + main_agent_status = data.status | |
583 | + if (data.status == "EXITING"){ | |
584 | + $("#agent_title").text(data.name + " (OFF)").css("color","red") | |
585 | + } | |
586 | + else{ | |
587 | + $("#agent_title").text(data.name + " (ON)").css("color","green") | |
588 | + } | |
589 | + | |
590 | + if (previous_status != data.status){ | |
591 | + display_form_cmd(data.status); | |
592 | + previous_status = data.status; | |
593 | + } | |
594 | + | |
595 | + app2.$forceUpdate(); | |
576 | 596 | }; |
577 | 597 | ws.onerror = function(error) { |
578 | 598 | let now = new Date(); |
... | ... | @@ -597,12 +617,14 @@ |
597 | 617 | }); |
598 | 618 | $(document).ready(function() { |
599 | 619 | |
600 | - refresh_agent_info(); | |
620 | + //refresh_agent_info(); | |
601 | 621 | //setInterval(refresh_command_column,5000); |
602 | - setInterval(refresh_agent_info,4000); | |
603 | - refresh_cmds_table(); | |
604 | - setInterval(refresh_cmds_table,10000); | |
605 | - //websocket_agentcmd(); | |
622 | + //setInterval(refresh_agent_info,4000); | |
623 | + //refresh_cmds_table(); | |
624 | + //setInterval(refresh_cmds_table,10000); | |
625 | + websocket_agentcmd(); | |
626 | + websocket_agentsurvey(); | |
627 | + update_agents_status(); | |
606 | 628 | $("cmdform").hide(); |
607 | 629 | $("cmdform_exiting").hide(); |
608 | 630 | }); | ... | ... |
src/core/pyros_django/dashboard/templates/dashboard/agents_state.html
... | ... | @@ -33,7 +33,7 @@ |
33 | 33 | <tr> |
34 | 34 | <th> Agent </th> |
35 | 35 | <th> Mode </th> |
36 | - <th> Status </th> | |
36 | + <th style="min-width:16vw;"> Status</th> | |
37 | 37 | <th> Udpated </th> |
38 | 38 | <th> Current command </th> |
39 | 39 | <th> Nb restart attempts </th> |
... | ... | @@ -95,11 +95,25 @@ |
95 | 95 | app.agents = data; |
96 | 96 | }); |
97 | 97 | } |
98 | + function websocket_agentsurvey(){ | |
99 | + var ws = new WebSocket(`ws://${window.location.host}/ws/agent_survey/`) | |
100 | + ws.onmessage = function (e) { | |
101 | + allData = JSON.parse(e.data); | |
102 | + app.agents = allData.data; | |
103 | + | |
104 | + app.$forceUpdate(); | |
105 | + }; | |
106 | + ws.onerror = function(error) { | |
107 | + let now = new Date(); | |
108 | + let msg = now.toLocaleString() + ':' + now.getMilliseconds() + ' โ WebSocket error: ' + error; | |
109 | + console.error(msg); | |
110 | + } | |
111 | + } | |
98 | 112 | $(document).ready(function() { |
99 | - | |
100 | - refresh_status_table(); | |
113 | + websocket_agentsurvey(); | |
114 | + //refresh_status_table(); | |
101 | 115 | //setInterval(refresh_command_column,5000); |
102 | - setInterval(refresh_status_table,4000); | |
116 | + //setInterval(refresh_status_table,4000); | |
103 | 117 | }); |
104 | 118 | var app = new Vue({ |
105 | 119 | delimiters:["[[","]]"], | ... | ... |
src/core/pyros_django/dashboard/views.py
... | ... | @@ -218,8 +218,13 @@ def get_last_all_cmds(agent_name): |
218 | 218 | else: |
219 | 219 | # AgentSST doesn't have do_stop cmd... (for the moment) |
220 | 220 | last_agent_all_cmds = AgentCmd.send_cmd_from_to("System",agent_name,"get_all_cmds") |
221 | + max_wait_time = 3 | |
222 | + current_wait_time = 0 | |
221 | 223 | while not AgentCmd.objects.get(id=last_agent_all_cmds.id).is_executed() and not AgentCmd.objects.get(id=last_agent_all_cmds.id).is_exec_error(): |
222 | 224 | time.sleep(0.5) |
225 | + current_wait_time+=0.5 | |
226 | + if max_wait_time <= current_wait_time: | |
227 | + break | |
223 | 228 | return AgentCmd.objects.get(id=last_agent_all_cmds.id) |
224 | 229 | |
225 | 230 | @login_required |
... | ... | @@ -309,14 +314,14 @@ def agent_detail(request, agent_name): |
309 | 314 | return JsonResponse({"agent_general_commands":agent_general_commands,"specific_cmd_with_args":specific_cmd_with_args,"unimplemented_command":unimplemented_command,"cmd_with_choices":cmd_with_choices,"cmds_description":cmds_description},safe=False) |
310 | 315 | if request.GET.get("cmd"): |
311 | 316 | return JsonResponse(None,safe=False) |
312 | - commands_sent_by_agent = AgentCmd.get_commands_sent_by_agent(agent_name) | |
313 | - commands_recivied_by_agent = AgentCmd.get_commands_sent_to_agent(agent_name) | |
314 | - agent_cmds = commands_sent_by_agent | commands_recivied_by_agent | |
315 | - agent_cmds = agent_cmds.exclude(full_name="get_specific_cmds") | |
316 | - agent_cmds = agent_cmds.exclude(full_name="get_all_cmds") | |
317 | - agent_cmds = agent_cmds.order_by("-s_deposit_time") | |
318 | - paginator = Paginator(agent_cmds, pyros_settings.NB_ELEMENT_PER_PAGE) | |
319 | - page_number = request.GET.get("page",1) | |
317 | + # commands_sent_by_agent = AgentCmd.get_commands_sent_by_agent(agent_name) | |
318 | + # commands_recivied_by_agent = AgentCmd.get_commands_sent_to_agent(agent_name) | |
319 | + # agent_cmds = commands_sent_by_agent | commands_recivied_by_agent | |
320 | + # agent_cmds = agent_cmds.exclude(full_name="get_specific_cmds") | |
321 | + # agent_cmds = agent_cmds.exclude(full_name="get_all_cmds") | |
322 | + # agent_cmds = agent_cmds.order_by("-s_deposit_time") | |
323 | + # paginator = Paginator(agent_cmds, pyros_settings.NB_ELEMENT_PER_PAGE) | |
324 | + # page_number = request.GET.get("page",1) | |
320 | 325 | config = OBSConfig(os.environ["PATH_TO_OBSCONF_FILE"],os.environ["unit_name"]) |
321 | 326 | managed_agents = None |
322 | 327 | agents_status = None |
... | ... | @@ -349,12 +354,12 @@ def agent_detail(request, agent_name): |
349 | 354 | break |
350 | 355 | obj, created = Majordome.objects.get_or_create(id=1) |
351 | 356 | CAN_SEND_COMMAND = obj.soft_mode == Majordome.MANUAL_MODE |
352 | - try: | |
353 | - commands = paginator.page(page_number) | |
354 | - except PageNotAnInteger: | |
355 | - commands = paginator.page(1) | |
356 | - except EmptyPage: | |
357 | - commands = paginator.page(paginator.num_pages) | |
357 | + # try: | |
358 | + # commands = paginator.page(page_number) | |
359 | + # except PageNotAnInteger: | |
360 | + # commands = paginator.page(1) | |
361 | + # except EmptyPage: | |
362 | + # commands = paginator.page(paginator.num_pages) | |
358 | 363 | return render(request, "dashboard/agent_detail.html", locals()) |
359 | 364 | |
360 | 365 | @login_required | ... | ... |
... | ... | @@ -0,0 +1,25 @@ |
1 | +import os | |
2 | + | |
3 | +from channels.auth import AuthMiddlewareStack | |
4 | +from channels.routing import ProtocolTypeRouter, URLRouter | |
5 | +from channels.security.websocket import AllowedHostsOriginValidator | |
6 | +from django.core.asgi import get_asgi_application | |
7 | +# from django.urls import path | |
8 | + | |
9 | + | |
10 | +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "pyros.settings") | |
11 | +# Initialize Django ASGI application early to ensure the AppRegistry | |
12 | +# is populated before importing code that may import ORM models. | |
13 | +from api.routing import websocket_urlpatterns | |
14 | +django_asgi_app = get_asgi_application() | |
15 | + | |
16 | +application = ProtocolTypeRouter({ | |
17 | + # Django's ASGI application to handle traditional HTTP requests | |
18 | + "http": django_asgi_app, | |
19 | + | |
20 | + "websocket": AllowedHostsOriginValidator( | |
21 | + AuthMiddlewareStack( | |
22 | + URLRouter(websocket_urlpatterns) | |
23 | + ) | |
24 | + ), | |
25 | +}) | ... | ... |
src/core/pyros_django/pyros/settings.py
... | ... | @@ -191,6 +191,10 @@ if not os.path.isfile(PATH_TO_OBSCONF_FILE): |
191 | 191 | # Application definition |
192 | 192 | |
193 | 193 | INSTALLED_APPS = [ |
194 | + # websocket server (asgi server) | |
195 | + 'daphne', | |
196 | + # websocket for django | |
197 | + 'channels', | |
194 | 198 | 'django.contrib.admin', |
195 | 199 | 'django.contrib.auth', |
196 | 200 | 'django.contrib.contenttypes', |
... | ... | @@ -230,6 +234,17 @@ INSTALLED_APPS = [ |
230 | 234 | #'kombu.transport.django' |
231 | 235 | ] |
232 | 236 | |
237 | +CHANNEL_LAYERS = { | |
238 | + "default": { | |
239 | + | |
240 | + "BACKEND": "channels_redis.core.RedisChannelLayer", | |
241 | + "CONFIG": { | |
242 | + #"hosts": [("127.0.0.1", 6379)], | |
243 | + "hosts": [("172.19.0.5", 6379)], | |
244 | + }, | |
245 | + }, | |
246 | +} | |
247 | + | |
233 | 248 | REST_FRAMEWORK = { |
234 | 249 | 'DEFAULT_AUTHENTICATION_CLASSES': [ |
235 | 250 | 'rest_framework.authentication.TokenAuthentication', |
... | ... | @@ -270,6 +285,7 @@ TEMPLATES = [ |
270 | 285 | ] |
271 | 286 | |
272 | 287 | WSGI_APPLICATION = 'pyros.wsgi.application' |
288 | +ASGI_APPLICATION = "pyros.asgi.application" | |
273 | 289 | |
274 | 290 | FIXTURE_DIRS = ( |
275 | 291 | 'misc/fixtures/', |
... | ... | @@ -294,7 +310,7 @@ so Django highly recommends activating a strict mode for MySQL to prevent data l |
294 | 310 | mysql_options = { 'init_command': "SET sql_mode='STRICT_TRANS_TABLES'" } |
295 | 311 | from src.core.pyros_django.obsconfig.obsconfig_class import OBSConfig |
296 | 312 | import socket |
297 | -obsconfig = OBSConfig(os.environ["PATH_TO_OBSCONF_FILE"]) | |
313 | +obsconfig = OBSConfig(os.environ.get("PATH_TO_OBSCONF_FILE",os.path.join(BASE_DIR,"../../../privatedev/config/default/observatory_default.yml"))) | |
298 | 314 | database_computer = obsconfig.get_database_for_unit(obsconfig.unit_name)["computer"] |
299 | 315 | database_file = obsconfig.get_database_environment(obsconfig.unit_name) |
300 | 316 | current_computer = socket.gethostname() |
... | ... | @@ -424,6 +440,23 @@ STATICFILES_DIRS = ( |
424 | 440 | # Used for deployment (DEBUG = False). Need to run "python manage.py collectstatic" to fill it. |
425 | 441 | STATIC_ROOT = os.path.join(os.path.dirname(BASE_DIR), 'public', 'static') |
426 | 442 | |
443 | +# LOGGING = { | |
444 | +# 'version': 1, | |
445 | +# 'disable_existing_loggers': False, | |
446 | +# 'handlers': { | |
447 | +# 'file': { | |
448 | +# 'level': 'DEBUG', | |
449 | +# 'class': 'logging.FileHandler', | |
450 | +# 'filename': '../../../djangodebug.log', | |
451 | +# }, | |
452 | +# }, | |
453 | +# 'loggers': { | |
454 | +# 'daphne': { | |
455 | +# 'handlers': ['file'], | |
456 | +# 'level': 'DEBUG', | |
457 | +# }, | |
458 | +# }, | |
459 | +# } | |
427 | 460 | |
428 | 461 | # EP added |
429 | 462 | if not DEBUG: | ... | ... |