Commit 6f83043c31cde701499cc0af78a881d458a94f88

Authored by Alexis Koralewski
1 parent e2712b10
Exists in dev

Add websocket for agent_cmd & agent_survey models & views

CHANGELOG
  1 +13-01-2023 (AKo): v0.6.16.0
  2 + - Add websocket for agent_cmd & agent_survey models & views
  3 +
1 4 03-01-2023 (AKo): v0.6.15.4
2 5 - new usage of delimiter in cmd parsing in agent_detail view
3 6  
... ...
VERSION
1   -0.6.15.4
2 1 \ No newline at end of file
  2 +0.6.16.0
3 3 \ No newline at end of file
... ...
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
... ...
src/core/pyros_django/api/consumers.py 0 โ†’ 100644
... ... @@ -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}))
... ...
src/core/pyros_django/api/functions.py 0 โ†’ 100644
... ... @@ -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
... ...
src/core/pyros_django/api/routing.py 0 โ†’ 100644
... ... @@ -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
... ...
src/core/pyros_django/pyros/asgi.py 0 โ†’ 100644
... ... @@ -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:
... ...