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,6 +25,16 @@ services: | ||
25 | interval: 5s | 25 | interval: 5s |
26 | retries: 20 | 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 | # service image of python, that let users to interact with python scripts such as pyros. | 38 | # service image of python, that let users to interact with python scripts such as pyros. |
29 | pyros: | 39 | pyros: |
30 | # path to the Dockerfile of this image | 40 | # path to the Dockerfile of this image |
@@ -61,6 +71,7 @@ services: | @@ -61,6 +71,7 @@ services: | ||
61 | # starting db service before install service | 71 | # starting db service before install service |
62 | depends_on: | 72 | depends_on: |
63 | - db | 73 | - db |
74 | + - redis | ||
64 | # create network to allow images to communicate with other images within the same network | 75 | # create network to allow images to communicate with other images within the same network |
65 | networks: | 76 | networks: |
66 | pyros-network: | 77 | pyros-network: |
@@ -85,6 +96,8 @@ services: | @@ -85,6 +96,8 @@ services: | ||
85 | ports: | 96 | ports: |
86 | - "${PHPMYADMIN_PORT:-8081}:80" | 97 | - "${PHPMYADMIN_PORT:-8081}:80" |
87 | 98 | ||
99 | + | ||
100 | + | ||
88 | # declaring volumes | 101 | # declaring volumes |
89 | volumes: | 102 | volumes: |
90 | db: | 103 | db: |
install/requirements_dev.txt
@@ -22,3 +22,14 @@ GitPython==3.1.24 | @@ -22,3 +22,14 @@ GitPython==3.1.24 | ||
22 | # For working with date | 22 | # For working with date |
23 | python-dateutil==2.8.2 | 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 | \ No newline at end of file | 36 | \ No newline at end of file |
@@ -0,0 +1,133 @@ | @@ -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 @@ | @@ -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 | \ No newline at end of file | 25 | \ No newline at end of file |
@@ -0,0 +1,10 @@ | @@ -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 | \ No newline at end of file | 11 | \ No newline at end of file |
src/core/pyros_django/api/views.py
@@ -294,7 +294,7 @@ class AgentCmdViewSet(viewsets.ModelViewSet): | @@ -294,7 +294,7 @@ class AgentCmdViewSet(viewsets.ModelViewSet): | ||
294 | http_method_names = ["get"] | 294 | http_method_names = ["get"] |
295 | def get_queryset(self): | 295 | def get_queryset(self): |
296 | agent_name = self.request.query_params.get('agent_name') | 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 | if agent_name is None: | 298 | if agent_name is None: |
299 | if "agent_name" in self.kwargs: | 299 | if "agent_name" in self.kwargs: |
300 | agent_name = self.kwargs["agent_name"] | 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,6 +25,10 @@ from django.db.models.expressions import F | ||
25 | from django.db.models.query import QuerySet | 25 | from django.db.models.query import QuerySet |
26 | from model_utils import Choices | 26 | from model_utils import Choices |
27 | from django.utils import timezone | 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 | # Project imports | 32 | # Project imports |
29 | from user_manager.models import PyrosUser | 33 | from user_manager.models import PyrosUser |
30 | # DeviceCommand is used by class Command | 34 | # DeviceCommand is used by class Command |
@@ -1143,6 +1147,74 @@ class AgentCmd(models.Model): | @@ -1143,6 +1147,74 @@ class AgentCmd(models.Model): | ||
1143 | # Optimization, but does not work, why ?... | 1147 | # Optimization, but does not work, why ?... |
1144 | # self.save(update_fields=["state"]) | 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 | # TODO: OLD Config : à virer (mais utilisé dans dashboard/templatetags/tags.py) | 1219 | # TODO: OLD Config : à virer (mais utilisé dans dashboard/templatetags/tags.py) |
1148 | class Config(models.Model): | 1220 | class Config(models.Model): |
src/core/pyros_django/dashboard/templates/dashboard/agent_detail.html
@@ -113,7 +113,7 @@ | @@ -113,7 +113,7 @@ | ||
113 | {% endfor %} {% endcomment %} | 113 | {% endfor %} {% endcomment %} |
114 | <div id="additional_fields" style="display:inline;"></div> | 114 | <div id="additional_fields" style="display:inline;"></div> |
115 | {% comment %} <input type="text" name="cmd_args" id="cmd_args"></input> {% endcomment %} | 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 | </form> | 117 | </form> |
118 | </div> | 118 | </div> |
119 | {% comment %} {% else %} {% endcomment %} | 119 | {% comment %} {% else %} {% endcomment %} |
@@ -138,8 +138,11 @@ | @@ -138,8 +138,11 @@ | ||
138 | <thead> | 138 | <thead> |
139 | <tr> | 139 | <tr> |
140 | <th>Agent</th> | 140 | <th>Agent</th> |
141 | + {% if agentsst_computers %} | ||
142 | + <th> Computer </th> | ||
143 | + {% endif %} | ||
141 | <th>Mode</th> | 144 | <th>Mode</th> |
142 | - <th style="min-width:10vw;">status</th> | 145 | + <th style="min-width:16vw;">status</th> |
143 | {% if CAN_SEND_COMMAND %} | 146 | {% if CAN_SEND_COMMAND %} |
144 | <th>Actions</th> | 147 | <th>Actions</th> |
145 | {% endif %} | 148 | {% endif %} |
@@ -150,6 +153,9 @@ | @@ -150,6 +153,9 @@ | ||
150 | <tr> | 153 | <tr> |
151 | <td><a href="{% url 'agent_detail' agent %}"> {{ agent }}</a> </td> | 154 | <td><a href="{% url 'agent_detail' agent %}"> {{ agent }}</a> </td> |
152 | {% comment %} <td> {{ status_of_agents|get_item:agent }}</td> {% endcomment %} | 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 | <td class="agent_mode"></td> | 159 | <td class="agent_mode"></td> |
154 | <td class="agent_status"></td> | 160 | <td class="agent_status"></td> |
155 | {% if CAN_SEND_COMMAND %} | 161 | {% if CAN_SEND_COMMAND %} |
@@ -463,51 +469,47 @@ | @@ -463,51 +469,47 @@ | ||
463 | get_parameters_of_selected_cmd(); | 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 | $('.agent_status').each(function(index){ | 473 | $('.agent_status').each(function(index){ |
469 | let agent_name = document.getElementsByClassName("agent_status")[index].parentElement.children[0].textContent; | 474 | let agent_name = document.getElementsByClassName("agent_status")[index].parentElement.children[0].textContent; |
470 | agent_name = agent_name.trim(); | 475 | agent_name = agent_name.trim(); |
471 | let agent_status = $(this); | 476 | let agent_status = $(this); |
472 | let agent_mode = $(this).prev(); | 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 | document.getElementsByClassName("agent_status")[index].parentElement.children[3].querySelector("#stopbtn").disabled = true; | 489 | document.getElementsByClassName("agent_status")[index].parentElement.children[3].querySelector("#stopbtn").disabled = true; |
486 | document.getElementsByClassName("agent_status")[index].parentElement.children[3].querySelector("#restartbtn").disabled = true; | 490 | document.getElementsByClassName("agent_status")[index].parentElement.children[3].querySelector("#restartbtn").disabled = true; |
487 | document.getElementsByClassName("agent_status")[index].parentElement.children[3].querySelector("#restarthardbtn").disabled = true; | 491 | document.getElementsByClassName("agent_status")[index].parentElement.children[3].querySelector("#restarthardbtn").disabled = true; |
488 | } | 492 | } |
489 | else{ | 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 | function refresh_agent_info(){ | 513 | function refresh_agent_info(){ |
512 | 514 | ||
513 | fetch("/api/agent_survey/{{agent_name}}?format=json").then(response => response.json()).then(function(data){ | 515 | fetch("/api/agent_survey/{{agent_name}}?format=json").then(response => response.json()).then(function(data){ |
@@ -533,46 +535,64 @@ | @@ -533,46 +535,64 @@ | ||
533 | } | 535 | } |
534 | function websocket_agentcmd(){ | 536 | function websocket_agentcmd(){ |
535 | var ws = new WebSocket(`ws://${window.location.host}/ws/agent_cmd/{{agent_name}}/`) | 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 | ws.onmessage = function (e) { | 539 | ws.onmessage = function (e) { |
565 | allData = JSON.parse(e.data); | 540 | allData = JSON.parse(e.data); |
566 | if (allData.action === "list") { | 541 | if (allData.action === "list") { |
567 | app.commands = allData.data; | 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 | let now = new Date(); | 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 | ws.onerror = function(error) { | 597 | ws.onerror = function(error) { |
578 | let now = new Date(); | 598 | let now = new Date(); |
@@ -597,12 +617,14 @@ | @@ -597,12 +617,14 @@ | ||
597 | }); | 617 | }); |
598 | $(document).ready(function() { | 618 | $(document).ready(function() { |
599 | 619 | ||
600 | - refresh_agent_info(); | 620 | + //refresh_agent_info(); |
601 | //setInterval(refresh_command_column,5000); | 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 | $("cmdform").hide(); | 628 | $("cmdform").hide(); |
607 | $("cmdform_exiting").hide(); | 629 | $("cmdform_exiting").hide(); |
608 | }); | 630 | }); |
src/core/pyros_django/dashboard/templates/dashboard/agents_state.html
@@ -33,7 +33,7 @@ | @@ -33,7 +33,7 @@ | ||
33 | <tr> | 33 | <tr> |
34 | <th> Agent </th> | 34 | <th> Agent </th> |
35 | <th> Mode </th> | 35 | <th> Mode </th> |
36 | - <th> Status </th> | 36 | + <th style="min-width:16vw;"> Status</th> |
37 | <th> Udpated </th> | 37 | <th> Udpated </th> |
38 | <th> Current command </th> | 38 | <th> Current command </th> |
39 | <th> Nb restart attempts </th> | 39 | <th> Nb restart attempts </th> |
@@ -95,11 +95,25 @@ | @@ -95,11 +95,25 @@ | ||
95 | app.agents = data; | 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 | $(document).ready(function() { | 112 | $(document).ready(function() { |
99 | - | ||
100 | - refresh_status_table(); | 113 | + websocket_agentsurvey(); |
114 | + //refresh_status_table(); | ||
101 | //setInterval(refresh_command_column,5000); | 115 | //setInterval(refresh_command_column,5000); |
102 | - setInterval(refresh_status_table,4000); | 116 | + //setInterval(refresh_status_table,4000); |
103 | }); | 117 | }); |
104 | var app = new Vue({ | 118 | var app = new Vue({ |
105 | delimiters:["[[","]]"], | 119 | delimiters:["[[","]]"], |
src/core/pyros_django/dashboard/views.py
@@ -218,8 +218,13 @@ def get_last_all_cmds(agent_name): | @@ -218,8 +218,13 @@ def get_last_all_cmds(agent_name): | ||
218 | else: | 218 | else: |
219 | # AgentSST doesn't have do_stop cmd... (for the moment) | 219 | # AgentSST doesn't have do_stop cmd... (for the moment) |
220 | last_agent_all_cmds = AgentCmd.send_cmd_from_to("System",agent_name,"get_all_cmds") | 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 | 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(): | 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 | time.sleep(0.5) | 224 | time.sleep(0.5) |
225 | + current_wait_time+=0.5 | ||
226 | + if max_wait_time <= current_wait_time: | ||
227 | + break | ||
223 | return AgentCmd.objects.get(id=last_agent_all_cmds.id) | 228 | return AgentCmd.objects.get(id=last_agent_all_cmds.id) |
224 | 229 | ||
225 | @login_required | 230 | @login_required |
@@ -309,14 +314,14 @@ def agent_detail(request, agent_name): | @@ -309,14 +314,14 @@ def agent_detail(request, agent_name): | ||
309 | 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) | 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 | if request.GET.get("cmd"): | 315 | if request.GET.get("cmd"): |
311 | return JsonResponse(None,safe=False) | 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 | config = OBSConfig(os.environ["PATH_TO_OBSCONF_FILE"],os.environ["unit_name"]) | 325 | config = OBSConfig(os.environ["PATH_TO_OBSCONF_FILE"],os.environ["unit_name"]) |
321 | managed_agents = None | 326 | managed_agents = None |
322 | agents_status = None | 327 | agents_status = None |
@@ -349,12 +354,12 @@ def agent_detail(request, agent_name): | @@ -349,12 +354,12 @@ def agent_detail(request, agent_name): | ||
349 | break | 354 | break |
350 | obj, created = Majordome.objects.get_or_create(id=1) | 355 | obj, created = Majordome.objects.get_or_create(id=1) |
351 | CAN_SEND_COMMAND = obj.soft_mode == Majordome.MANUAL_MODE | 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 | return render(request, "dashboard/agent_detail.html", locals()) | 363 | return render(request, "dashboard/agent_detail.html", locals()) |
359 | 364 | ||
360 | @login_required | 365 | @login_required |
@@ -0,0 +1,25 @@ | @@ -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,6 +191,10 @@ if not os.path.isfile(PATH_TO_OBSCONF_FILE): | ||
191 | # Application definition | 191 | # Application definition |
192 | 192 | ||
193 | INSTALLED_APPS = [ | 193 | INSTALLED_APPS = [ |
194 | + # websocket server (asgi server) | ||
195 | + 'daphne', | ||
196 | + # websocket for django | ||
197 | + 'channels', | ||
194 | 'django.contrib.admin', | 198 | 'django.contrib.admin', |
195 | 'django.contrib.auth', | 199 | 'django.contrib.auth', |
196 | 'django.contrib.contenttypes', | 200 | 'django.contrib.contenttypes', |
@@ -230,6 +234,17 @@ INSTALLED_APPS = [ | @@ -230,6 +234,17 @@ INSTALLED_APPS = [ | ||
230 | #'kombu.transport.django' | 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 | REST_FRAMEWORK = { | 248 | REST_FRAMEWORK = { |
234 | 'DEFAULT_AUTHENTICATION_CLASSES': [ | 249 | 'DEFAULT_AUTHENTICATION_CLASSES': [ |
235 | 'rest_framework.authentication.TokenAuthentication', | 250 | 'rest_framework.authentication.TokenAuthentication', |
@@ -270,6 +285,7 @@ TEMPLATES = [ | @@ -270,6 +285,7 @@ TEMPLATES = [ | ||
270 | ] | 285 | ] |
271 | 286 | ||
272 | WSGI_APPLICATION = 'pyros.wsgi.application' | 287 | WSGI_APPLICATION = 'pyros.wsgi.application' |
288 | +ASGI_APPLICATION = "pyros.asgi.application" | ||
273 | 289 | ||
274 | FIXTURE_DIRS = ( | 290 | FIXTURE_DIRS = ( |
275 | 'misc/fixtures/', | 291 | 'misc/fixtures/', |
@@ -294,7 +310,7 @@ so Django highly recommends activating a strict mode for MySQL to prevent data l | @@ -294,7 +310,7 @@ so Django highly recommends activating a strict mode for MySQL to prevent data l | ||
294 | mysql_options = { 'init_command': "SET sql_mode='STRICT_TRANS_TABLES'" } | 310 | mysql_options = { 'init_command': "SET sql_mode='STRICT_TRANS_TABLES'" } |
295 | from src.core.pyros_django.obsconfig.obsconfig_class import OBSConfig | 311 | from src.core.pyros_django.obsconfig.obsconfig_class import OBSConfig |
296 | import socket | 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 | database_computer = obsconfig.get_database_for_unit(obsconfig.unit_name)["computer"] | 314 | database_computer = obsconfig.get_database_for_unit(obsconfig.unit_name)["computer"] |
299 | database_file = obsconfig.get_database_environment(obsconfig.unit_name) | 315 | database_file = obsconfig.get_database_environment(obsconfig.unit_name) |
300 | current_computer = socket.gethostname() | 316 | current_computer = socket.gethostname() |
@@ -424,6 +440,23 @@ STATICFILES_DIRS = ( | @@ -424,6 +440,23 @@ STATICFILES_DIRS = ( | ||
424 | # Used for deployment (DEBUG = False). Need to run "python manage.py collectstatic" to fill it. | 440 | # Used for deployment (DEBUG = False). Need to run "python manage.py collectstatic" to fill it. |
425 | STATIC_ROOT = os.path.join(os.path.dirname(BASE_DIR), 'public', 'static') | 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 | # EP added | 461 | # EP added |
429 | if not DEBUG: | 462 | if not DEBUG: |