From 7c3c2c87e2bd224c666a5acc02b8a2f12c26efaf Mon Sep 17 00:00:00 2001 From: JFrgs Date: Thu, 14 Oct 2021 17:16:04 +0300 Subject: [PATCH 01/28] One addition on Makefile --- Makefile | 7 ++++++- backend/app/app/api/api_v1/endpoints/utils.py | 1 - 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 7a219d49f..2e8a442dd 100644 --- a/Makefile +++ b/Makefile @@ -55,4 +55,9 @@ db-init-simple: #simple scenario with 3 UEs, 3 Cells, 1 gNB ./backend/app/app/db/init_paths_simple.sh db-reset: - docker-compose exec db psql -h localhost -U postgres -d app -c 'TRUNCATE TABLE cell, gnb, monitoring, path, points, ue RESTART IDENTITY;' \ No newline at end of file + docker-compose exec db psql -h localhost -U postgres -d app -c 'TRUNCATE TABLE cell, gnb, monitoring, path, points, ue RESTART IDENTITY;' + +#Individual logs + +logs-location: + docker-compose logs -f backend 2>&1 | grep -E "(handovers|monitoringType|'ack')" \ No newline at end of file diff --git a/backend/app/app/api/api_v1/endpoints/utils.py b/backend/app/app/api/api_v1/endpoints/utils.py index 92197f5c0..669cd773b 100644 --- a/backend/app/app/api/api_v1/endpoints/utils.py +++ b/backend/app/app/api/api_v1/endpoints/utils.py @@ -30,7 +30,6 @@ def __init__(self, group=None, target=None, name=None, args=(), kwargs=None): def run(self): - # logging.warning(f'Looping... ^_^ User: {path}') current_user = self._args[0] supi = self._args[1] From 1b8022a7165913e799ef1ae01846d4365f2d334f Mon Sep 17 00:00:00 2001 From: Anastasios Gogos <13665570+tgogos@users.noreply.github.com> Date: Fri, 15 Oct 2021 13:51:11 +0300 Subject: [PATCH 02/28] add comments to dashboard.js --- backend/app/app/static/js/dashboard.js | 42 +++++++++++++++++++++++--- 1 file changed, 37 insertions(+), 5 deletions(-) diff --git a/backend/app/app/static/js/dashboard.js b/backend/app/app/static/js/dashboard.js index 2584961d2..e7e4db6e5 100644 --- a/backend/app/app/static/js/dashboard.js +++ b/backend/app/app/static/js/dashboard.js @@ -1,18 +1,33 @@ +// =============================================== +// Global variables +// =============================================== var gNBs = null; var cells = null; var ues = null; var paths = null; + + + +// =============================================== +// Document ready +// =============================================== $( document ).ready(function() { api_get_gNBs(); api_get_Cells() api_get_UEs() api_get_paths() }); +// =============================================== + +// Ajax request to get gNBs data +// on success: update the card at the top of the page +// and fill the datatable with values +// function api_get_gNBs() { var url = app.api_url + '/gNBs/?skip=0&limit=100'; @@ -48,6 +63,11 @@ function api_get_gNBs() { } + +// Ajax request to get Cells data +// on success: update the card at the top of the page +// and fill the datatable with values +// function api_get_Cells() { var url = app.api_url + '/Cells/?skip=0&limit=100'; @@ -83,7 +103,10 @@ function api_get_Cells() { } - +// Ajax request to get UEs data +// on success: update the card at the top of the page +// and fill the datatable with values +// function api_get_UEs() { var url = app.api_url + '/UEs/?skip=0&limit=100'; @@ -119,6 +142,11 @@ function api_get_UEs() { } + +// Ajax request to get Paths data +// on success: update the card at the top of the page +// and fill the datatable with values +// function api_get_paths() { var url = app.api_url + '/frontend/location/?skip=0&limit=100'; @@ -153,12 +181,17 @@ function api_get_paths() { } - +// Helper function to update the numbers displayed on every card +// function ui_update_card( element_id, number ) { $( element_id ).html(number); } + +// =============================================== +// Datatable functions +// =============================================== function ui_init_datatable_gNBs() { $('#dt-gNBs').DataTable( { data: gNBs, @@ -177,7 +210,6 @@ function ui_init_datatable_gNBs() { ] } ); } - function ui_init_datatable_Cells() { $('#dt-cells').DataTable( { data: cells, @@ -196,7 +228,6 @@ function ui_init_datatable_Cells() { ] } ); } - function ui_init_datatable_UEs() { $('#dt-ues').DataTable( { data: ues, @@ -216,4 +247,5 @@ function ui_init_datatable_UEs() { { "data": "speed", className: "dt-center" }, ] } ); -} \ No newline at end of file +} +// =============================================== \ No newline at end of file From df1096aa219b9b8133f6e461917d5a1f62ff5f04 Mon Sep 17 00:00:00 2001 From: JFrgs Date: Wed, 20 Oct 2021 16:44:32 +0300 Subject: [PATCH 03/28] Changes in monitoring subscription/notification --- backend/app/app/api/api_v1/api.py | 4 +- .../api/api_v1/endpoints/monitoringevent.py | 5 ++- backend/app/app/api/api_v1/endpoints/utils.py | 44 +++++++------------ backend/app/app/crud/crud_UE.py | 9 ++++ backend/app/app/crud/crud_monitoringevent.py | 4 +- backend/app/app/models/monitoringevent.py | 6 +-- backend/app/app/schemas/monitoringevent.py | 10 +++-- backend/app/app/tools/send_callback.py | 4 +- 8 files changed, 44 insertions(+), 42 deletions(-) diff --git a/backend/app/app/api/api_v1/api.py b/backend/app/app/api/api_v1/api.py index d3266ccf8..d33bd506c 100644 --- a/backend/app/app/api/api_v1/api.py +++ b/backend/app/app/api/api_v1/api.py @@ -5,8 +5,8 @@ api_router = APIRouter() api_router.include_router(login.router, tags=["login"]) api_router.include_router(users.router, prefix="/users", tags=["users"]) -api_router.include_router(utils.router, prefix="/utils", tags=["utils"]) -api_router.include_router(location_frontend.router, prefix="/frontend/location", tags=["Location Frontend"]) +api_router.include_router(utils.router, prefix="/utils", tags=["UI"]) +api_router.include_router(location_frontend.router, prefix="/frontend/location", tags=["Paths"]) api_router.include_router(gNB.router, prefix="/gNBs", tags=["gNBs"]) api_router.include_router(Cell.router, prefix="/Cells", tags=["Cells"]) api_router.include_router(UE.router, prefix="/UEs", tags=["UEs"]) diff --git a/backend/app/app/api/api_v1/endpoints/monitoringevent.py b/backend/app/app/api/api_v1/endpoints/monitoringevent.py index 42bc7b598..f30535f6b 100644 --- a/backend/app/app/api/api_v1/endpoints/monitoringevent.py +++ b/backend/app/app/api/api_v1/endpoints/monitoringevent.py @@ -80,14 +80,15 @@ def create_item( """ Create new subscription. """ - UE = crud.ue.get_ipv4(db=db, ipv4=str(item_in.ipv4Addr), owner_id=current_user.id) + UE = crud.ue.get_externalId(db=db, externalId=str(item_in.externalId), owner_id=current_user.id) if not UE: - raise HTTPException(status_code=409, detail="UE with this ipv4 doesn't exist") + raise HTTPException(status_code=409, detail="UE with this external identifier doesn't exist") if item_in.monitoringType == "LOCATION_REPORTING" and item_in.maximumNumberOfReports == 1: json_compatible_item_data = {} json_compatible_item_data["monitoringType"] = item_in.monitoringType json_compatible_item_data["locationInfo"] = {'cellId' : UE.Cell.cell_id, 'gNBId' : UE.Cell.gNB.gNB_id} + json_compatible_item_data["externalId"] = item_in.externalId return JSONResponse(content=json_compatible_item_data, status_code=200) elif item_in.monitoringType == "LOCATION_REPORTING" and item_in.maximumNumberOfReports>1: response = crud.monitoring.create_with_owner(db=db, obj_in=item_in, owner_id=current_user.id) diff --git a/backend/app/app/api/api_v1/endpoints/utils.py b/backend/app/app/api/api_v1/endpoints/utils.py index 669cd773b..dd16a39b1 100644 --- a/backend/app/app/api/api_v1/endpoints/utils.py +++ b/backend/app/app/api/api_v1/endpoints/utils.py @@ -1,10 +1,8 @@ -import threading, logging, time, requests, json -import fastapi -from typing import Any +import threading, logging, time, requests, ast +from typing import Any, List from fastapi import APIRouter, Depends, HTTPException, Path, responses from fastapi.encoders import jsonable_encoder from pydantic.networks import EmailStr -from sqlalchemy.orm import Session from app.db.session import SessionLocal from app import models, schemas, crud from app.api import deps @@ -92,7 +90,7 @@ def run(self): crud.ue.update(db=db, db_obj=UE, obj_in={"Cell_id" : cell_now.get('id')}) #Retrieve the subscription of the UE by ipv4 | This could be outside while true but then the user cannot subscribe when the loop runs - sub = crud.monitoring.get_sub_ipv4(db=db, ipv4=UE.ip_address_v4) + sub = crud.monitoring.get_sub_externalId(db=db, externalId=UE.external_identifier) #Validation of subscription if not sub: @@ -105,7 +103,7 @@ def run(self): sub = tools.check_numberOfReports(db=db, item_in=sub) if sub: #return the callback request only if subscription is valid try: - response = location_callback(UE.Cell.cell_id, UE.Cell.gNB.gNB_id, sub.notificationDestination) + response = location_callback(UE.external_identifier, UE.Cell.cell_id, UE.Cell.gNB.gNB_id, sub.notificationDestination, sub.link) logging.info(response.json()) except requests.exceptions.ConnectionError as ex: logging.warning("Failed to send the callback request") @@ -138,37 +136,25 @@ def stop(self): self._stop_threads = True +event_notifications = [] router = APIRouter() @router.post("/monitoring/callback") -def create_item(item: monitoringevent.MonitoringEventReport): +def create_item(item: monitoringevent.MonitoringNotification): logging.info(item.json()) + event_notifications.append(ast.literal_eval(item.json())) return {'ack' : 'TRUE'} -@router.post("/test-celery/", response_model=schemas.Msg, status_code=201) -def test_celery( - msg: schemas.Msg, - current_user: models.User = Depends(deps.get_current_active_superuser), -) -> Any: - """ - Test Celery worker. - """ - celery_app.send_task("app.worker.test_celery", args=[msg.msg]) - return {"msg": "Word received"} - - -@router.post("/test-email/", response_model=schemas.Msg, status_code=201) -def test_email( - email_to: EmailStr, - current_user: models.User = Depends(deps.get_current_active_superuser), -) -> Any: - """ - Test emails. - """ - send_test_email(email_to=email_to) - return {"msg": "Test email sent"} +@router.get("/monitoring/notifications") +def get_items( + skip: int = 0, + limit: int = 100 + ): + notification = event_notifications[skip:limit] + logging.info(notification) + return notification @router.post("/start-loop/", status_code=200) def initiate_movement( diff --git a/backend/app/app/crud/crud_UE.py b/backend/app/app/crud/crud_UE.py index c8c09dbc7..24fec9cdf 100644 --- a/backend/app/app/crud/crud_UE.py +++ b/backend/app/app/crud/crud_UE.py @@ -43,6 +43,15 @@ def get_ipv4( .first() ) + def get_externalId( + self, db: Session, *, externalId: str, owner_id: int + ) -> UE: + return ( + db.query(self.model) + .filter(UE.external_identifier == externalId, UE.owner_id == owner_id) + .first() + ) + def get_by_gNB( self, db: Session, *, gNB_id: int, skip: int = 0, limit: int = 100 ) -> List[UE]: diff --git a/backend/app/app/crud/crud_monitoringevent.py b/backend/app/app/crud/crud_monitoringevent.py index cfce7a115..855cc7ff9 100644 --- a/backend/app/app/crud/crud_monitoringevent.py +++ b/backend/app/app/crud/crud_monitoringevent.py @@ -31,7 +31,7 @@ def get_multi_by_owner( .all() ) - def get_sub_ipv4(self, db: Session, ipv4: str) -> Monitoring: - return db.query(self.model).filter(Monitoring.ipv4Addr == ipv4).first() + def get_sub_externalId(self, db: Session, externalId: str) -> Monitoring: + return db.query(self.model).filter(Monitoring.externalId == externalId).first() monitoring = CRUD_Monitoring(Monitoring) diff --git a/backend/app/app/models/monitoringevent.py b/backend/app/app/models/monitoringevent.py index 5fffe5dad..d7b0bcc01 100755 --- a/backend/app/app/models/monitoringevent.py +++ b/backend/app/app/models/monitoringevent.py @@ -16,10 +16,10 @@ class Monitoring(Base): link = Column(String, index=True) # mtcProviderId = Column(String, index=True) externalId = Column(String, index=True) - msisdn = Column(String, index=True) + # msisdn = Column(String, index=True) # externalGroupId = Column(String, index=True) - ipv4Addr = Column(String, index=True) - ipv6Addr = Column(String, index=True) + # ipv4Addr = Column(String, index=True) + # ipv6Addr = Column(String, index=True) notificationDestination = Column(String, index=True) monitoringType = Column(String, index=True) maximumNumberOfReports = Column(Integer, index=True) diff --git a/backend/app/app/schemas/monitoringevent.py b/backend/app/app/schemas/monitoringevent.py index 3ca54bffc..b24c26824 100644 --- a/backend/app/app/schemas/monitoringevent.py +++ b/backend/app/app/schemas/monitoringevent.py @@ -25,18 +25,19 @@ class MonitoringType(str, Enum): class MonitoringEventReport(BaseModel): # msisdn: Optional[str] = None + externalId: Optional[str] = Field("123456789@domain.com", description="Globally unique identifier containing a Domain Identifier and a Local Identifier. \@\") monitoringType: MonitoringType locationInfo: Optional[LocationInfo] = None class MonitoringEventSubscriptionCreate(BaseModel): # mtcProviderId: Optional[str] = Field(None, description="Identifies the MTC Service Provider and/or MTC Application") externalId: Optional[str] = Field("123456789@domain.com", description="Globally unique identifier containing a Domain Identifier and a Local Identifier. \@\") - msisdn: Optional[str] = Field("918369110173", description="Mobile Subscriber ISDN number that consists of Country Code, National Destination Code and Subscriber Number.") + # msisdn: Optional[str] = Field("918369110173", description="Mobile Subscriber ISDN number that consists of Country Code, National Destination Code and Subscriber Number.") # externalGroupId: Optional[str] = Field("Group1@domain.com", description="Identifies a group made up of one or more subscriptions associated to a group of IMSIs, containing a Domain Identifier and a Local Identifier. \@\") # addExtGroupIds: Optional[str] = None #Remember, when you actually trying to access the database through CRUD methods you need to typecast the pydantic types to strings, int etc. - ipv4Addr: Optional[IPvAnyAddress] = Field(None, description="String identifying an Ipv4 address") - ipv6Addr: Optional[IPvAnyAddress] = Field("0:0:0:0:0:0:0:1", description="String identifying an Ipv6 address. Default value ::1/128 (loopback)") + # ipv4Addr: Optional[IPvAnyAddress] = Field(None, description="String identifying an Ipv4 address") + # ipv6Addr: Optional[IPvAnyAddress] = Field("0:0:0:0:0:0:0:1", description="String identifying an Ipv6 address. Default value ::1/128 (loopback)") notificationDestination: AnyHttpUrl = "http://localhost:80/api/v1/utils/monitoring/callback" #Default value for development testing monitoringType: MonitoringType maximumNumberOfReports: Optional[int] = Field(None, description="Identifies the maximum number of event reports to be generated. Value 1 makes the Monitoring Request a One-time Request", ge=1) @@ -49,5 +50,8 @@ class MonitoringEventSubscription(MonitoringEventSubscriptionCreate): class Config: orm_mode = True +class MonitoringNotification(MonitoringEventReport): + subscription: AnyHttpUrl + class MonitoringEventReportReceived(BaseModel): ok: bool \ No newline at end of file diff --git a/backend/app/app/tools/send_callback.py b/backend/app/app/tools/send_callback.py index 354536551..abb1802f7 100644 --- a/backend/app/app/tools/send_callback.py +++ b/backend/app/app/tools/send_callback.py @@ -2,10 +2,12 @@ import json import logging -def location_callback(cellid, gnbid, callbackurl): +def location_callback(externaId, cellid, gnbid, callbackurl, subscription): url = callbackurl payload = json.dumps({ + "externalId" : externaId, + "subscription" : subscription, "monitoringType": "LOCATION_REPORTING", "locationInfo": { "cellId": str(cellid), From b3722344fd7ed1d266b044aa42f835d315b199f0 Mon Sep 17 00:00:00 2001 From: Anastasios Gogos <13665570+tgogos@users.noreply.github.com> Date: Wed, 20 Oct 2021 17:19:02 +0300 Subject: [PATCH 04/28] add external identifier to UE tooltip closes #9 --- backend/app/app/static/js/map.js | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/backend/app/app/static/js/map.js b/backend/app/app/static/js/map.js index 126df843f..13c0c8964 100644 --- a/backend/app/app/static/js/map.js +++ b/backend/app/app/static/js/map.js @@ -180,7 +180,7 @@ function api_get_UEs() { }, success: function(data) { - console.log(data); + // console.log(data); ues = data; ui_map_paint_UEs(); }, @@ -221,6 +221,7 @@ function ui_map_paint_UEs() { // ue.description +"
"+ "location: [" + ue.latitude.toFixed(6) + "," + ue.longitude.toFixed(6) +"]
"+ "Cell ID: " + ue.cell_id_hex +"
"+ + "External identifier: " + ue.external_identifier +"
"+ "Speed:"+ ue.speed) .addTo(ues_lg); // add to layer group @@ -233,6 +234,7 @@ function ui_map_paint_UEs() { // ue.description +"
"+ "location: [" + ue.latitude.toFixed(6) + "," + ue.longitude.toFixed(6) +"]
"+ "Cell ID: " + ue.cell_id_hex +"
"+ + "External identifier: " + ue.external_identifier +"
"+ "Speed:"+ ue.speed); } } @@ -443,8 +445,6 @@ function api_start_loop( ue ) { // function api_stop_loop( ue ) { - console.log(ue); - var url = app.api_url + '/utils/stop-loop/'; var data = { "supi": ue.supi @@ -553,8 +553,7 @@ function ui_add_ue_btn_listeners(){ $('.btn-ue').on('click', function(){ curr_supi = $(this).data("supi"); - // console.log(curr_supi); - // console.log($(this).data("running")); + if ( $(this).data("running") == false) { // start location UE loop From e4216bf6a5bab6491e907de1ca0df2a6db6c5a4d Mon Sep 17 00:00:00 2001 From: Anastasios Gogos <13665570+tgogos@users.noreply.github.com> Date: Thu, 21 Oct 2021 14:33:46 +0300 Subject: [PATCH 05/28] add fitbounds functionality to the map depending on the cells lat,long / closes #10 --- backend/app/app/static/js/map.js | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/backend/app/app/static/js/map.js b/backend/app/app/static/js/map.js index 13c0c8964..a9193f711 100644 --- a/backend/app/app/static/js/map.js +++ b/backend/app/app/static/js/map.js @@ -20,6 +20,7 @@ var cells_lg = L.layerGroup(), // markers var ue_markers = {}; var cell_markers = {}; +var map_bounds = []; // helper var for correct initialization var UEs_first_paint = true; @@ -131,14 +132,16 @@ function ui_initialize_map() { 'Imagery © Mapbox', mbUrl = 'https://api.mapbox.com/styles/v1/{id}/tiles/{z}/{x}/{y}?access_token=pk.eyJ1IjoibWFwYm94IiwiYSI6ImNpejY4NXVycTA2emYycXBndHRqcmZ3N3gifQ.rJcFIG214AriISLbB6B5aw'; - var grayscale = L.tileLayer(mbUrl, {id: 'mapbox/light-v9', tileSize: 512, zoomOffset: -1, attribution: mbAttr, maxZoom: 23}), + var grayscale = L.tileLayer(mbUrl, {id: 'mapbox/light-v9', tileSize: 512, zoomOffset: -1, attribution: mbAttr, maxZoom: 23}), streets = L.tileLayer(mbUrl, {id: 'mapbox/streets-v11', tileSize: 512, zoomOffset: -1, attribution: mbAttr, maxZoom: 23}); // map initialization mymap = L.map('mapid', { layers: [grayscale, cells_lg, cell_coverage_lg, ues_lg, paths_lg] - }).setView([37.996349, 23.819861], 17); + }).setView([48.499998, 23.383331], 5); // Geographical midpoint of Europe + //.setView([37.996349, 23.819861], 17); // previous "hard-coded" center for the first map scenario at NCSRD + var baseLayers = { "Grayscale": grayscale, @@ -309,8 +312,17 @@ function ui_map_paint_Cells() { fillColor: '#f03', fillOpacity: 0.05 }).addTo(cell_coverage_lg).addTo(mymap); - - } + + // keep (lat, long) to later set view of the map + map_bounds.push([cell.latitude,cell.longitude]); + } + + // if cells where found, map -> set view + if ( cells.length >0 ) { + var leaflet_bounds = new L.LatLngBounds(map_bounds); + mymap.fitBounds( leaflet_bounds ); + } + } From f30ff88bcf37e3b0ede5959f30614b017769ecc6 Mon Sep 17 00:00:00 2001 From: JFrgs Date: Thu, 21 Oct 2021 17:00:11 +0300 Subject: [PATCH 06/28] First try to store the last 100 subs/notifications #8 --- .../api/api_v1/endpoints/monitoringevent.py | 64 ++++++++++++++++--- backend/app/app/api/api_v1/endpoints/utils.py | 57 +++++++++++++++-- 2 files changed, 107 insertions(+), 14 deletions(-) diff --git a/backend/app/app/api/api_v1/endpoints/monitoringevent.py b/backend/app/app/api/api_v1/endpoints/monitoringevent.py index f30535f6b..f40d3136d 100644 --- a/backend/app/app/api/api_v1/endpoints/monitoringevent.py +++ b/backend/app/app/api/api_v1/endpoints/monitoringevent.py @@ -1,3 +1,4 @@ +import ast from typing import Any, List from fastapi import APIRouter, Depends, HTTPException, Path, Response, Request from fastapi.encoders import jsonable_encoder @@ -8,6 +9,7 @@ from app.api import deps from app import tools from app.core.config import settings +from app.api.api_v1.endpoints.utils import add_notifications location_header = settings.BACKEND_CORS_ORIGINS[1] + settings.API_V1_STR + "/3gpp-monitoring-event/v1/" @@ -39,6 +41,7 @@ def read_active_subscriptions( limit: int = 100, db: Session = Depends(deps.get_db), current_user: models.User = Depends(deps.get_current_active_user), + http_request: Request ) -> Any: """ Read all active subscriptions @@ -57,7 +60,17 @@ def read_active_subscriptions( crud.monitoring.remove(db=db, id=sub.id) subs.remove(sub) - return subs + json_compatible_item_data = jsonable_encoder(subs) + + for json_data in json_compatible_item_data: + json_data.pop("owner_id") + json_data.pop("id") + + http_response = JSONResponse(content=json_compatible_item_data, status_code=200) + + add_notifications(http_request, http_response, False) + return http_response + @@ -76,6 +89,7 @@ def create_item( db: Session = Depends(deps.get_db), item_in: schemas.MonitoringEventSubscriptionCreate, current_user: models.User = Depends(deps.get_current_active_user), + http_request: Request ) -> Any: """ Create new subscription. @@ -85,21 +99,32 @@ def create_item( raise HTTPException(status_code=409, detail="UE with this external identifier doesn't exist") if item_in.monitoringType == "LOCATION_REPORTING" and item_in.maximumNumberOfReports == 1: + json_compatible_item_data = {} json_compatible_item_data["monitoringType"] = item_in.monitoringType json_compatible_item_data["locationInfo"] = {'cellId' : UE.Cell.cell_id, 'gNBId' : UE.Cell.gNB.gNB_id} json_compatible_item_data["externalId"] = item_in.externalId - return JSONResponse(content=json_compatible_item_data, status_code=200) - elif item_in.monitoringType == "LOCATION_REPORTING" and item_in.maximumNumberOfReports>1: + + http_response = JSONResponse(content=json_compatible_item_data, status_code=200) + add_notifications(http_request, http_response, False) + + return http_response + elif item_in.monitoringType == "LOCATION_REPORTING" and item_in.maximumNumberOfReports>1: + response = crud.monitoring.create_with_owner(db=db, obj_in=item_in, owner_id=current_user.id) + json_compatible_item_data = jsonable_encoder(response) json_compatible_item_data.pop("owner_id") json_compatible_item_data.pop("id") link = location_header + scsAsId + "/subscriptions/" + str(response.id) - json_compatible_item_data["link"] = link + json_compatible_item_data["link"] = link crud.monitoring.update(db=db, db_obj=response, obj_in={"link" : link}) + response_header = {"location" : link} - return JSONResponse(content=json_compatible_item_data, status_code=201, headers=response_header) + http_response = JSONResponse(content=json_compatible_item_data, status_code=201, headers=response_header) + add_notifications(http_request, http_response, False) + + return http_response @@ -111,6 +136,7 @@ def update_item( db: Session = Depends(deps.get_db), item_in: schemas.MonitoringEventSubscription, current_user: models.User = Depends(deps.get_current_active_user), + http_request: Request ) -> Any: """ Update/Replace an existing subscription resource @@ -125,7 +151,14 @@ def update_item( if sub_validate_time: sub = crud.monitoring.update(db=db, db_obj=sub, obj_in=item_in) - return sub + + json_compatible_item_data = jsonable_encoder(sub) + json_compatible_item_data.pop("owner_id") + json_compatible_item_data.pop("id") + http_response = JSONResponse(content=json_compatible_item_data, status_code=200) + + add_notifications(http_request, http_response, False) + return http_response else: raise HTTPException(status_code=403, detail="Subscription has expired") @@ -137,6 +170,7 @@ def read_item( subscriptionId: str = Path(..., title="Identifier of the subscription resource"), db: Session = Depends(deps.get_db), current_user: models.User = Depends(deps.get_current_active_user), + http_request: Request ) -> Any: """ Get subscription by id @@ -152,7 +186,13 @@ def read_item( sub_validate_time = tools.check_expiration_time(expire_time=sub.monitorExpireTime) if sub_validate_time: - return sub + json_compatible_item_data = jsonable_encoder(sub) + json_compatible_item_data.pop("owner_id") + json_compatible_item_data.pop("id") + http_response = JSONResponse(content=json_compatible_item_data, status_code=200) + + add_notifications(http_request, http_response, False) + return http_response else: crud.monitoring.remove(db=db, id=id) raise HTTPException(status_code=403, detail="Subscription has expired") @@ -164,6 +204,7 @@ def delete_item( subscriptionId: str = Path(..., title="Identifier of the subscription resource"), db: Session = Depends(deps.get_db), current_user: models.User = Depends(deps.get_current_active_user), + http_request: Request ) -> Any: """ Delete a subscription @@ -178,7 +219,14 @@ def delete_item( if sub_validate_time: sub = crud.monitoring.remove(db=db, id=int(subscriptionId)) - return sub + + json_compatible_item_data = jsonable_encoder(sub) + json_compatible_item_data.pop("owner_id") + json_compatible_item_data.pop("id") + http_response = JSONResponse(content=json_compatible_item_data, status_code=200) + + add_notifications(http_request, http_response, False) + return http_response else: crud.monitoring.remove(db=db, id=id) raise HTTPException(status_code=403, detail="Subscription has expired") diff --git a/backend/app/app/api/api_v1/endpoints/utils.py b/backend/app/app/api/api_v1/endpoints/utils.py index dd16a39b1..7ebf79860 100644 --- a/backend/app/app/api/api_v1/endpoints/utils.py +++ b/backend/app/app/api/api_v1/endpoints/utils.py @@ -1,8 +1,11 @@ +import json import threading, logging, time, requests, ast from typing import Any, List -from fastapi import APIRouter, Depends, HTTPException, Path, responses +from fastapi import APIRouter, Depends, HTTPException, Path, Query, Request +from fastapi.responses import JSONResponse from fastapi.encoders import jsonable_encoder from pydantic.networks import EmailStr +from sqlalchemy.util.langhelpers import counter from app.db.session import SessionLocal from app import models, schemas, crud from app.api import deps @@ -137,25 +140,67 @@ def stop(self): event_notifications = [] +counter = 0 + +def add_notifications(request: Request, response: JSONResponse, is_notification: bool): + + global counter + + json_data = {} + json_data.update({"id" : counter}) + + #Request body check and trim + if(request.method == 'POST') or (request.method == 'PUT'): + req_body = request._body.decode("utf-8").replace('\n', '') + req_body = req_body.replace(' ', '') + json_data["request_body"] = req_body + + json_data["response_body"] = response.body.decode("utf-8") + json_data["method"] = request.method + json_data["status_code"] = response.status_code + json_data["isNotification"] = is_notification + + event_notifications.append(json_data) + if len(event_notifications) > 100: + event_notifications.pop(0) + + counter += 1 + + return json_data + router = APIRouter() @router.post("/monitoring/callback") -def create_item(item: monitoringevent.MonitoringNotification): +def create_item(item: monitoringevent.MonitoringNotification, request: Request): logging.info(item.json()) - event_notifications.append(ast.literal_eval(item.json())) - return {'ack' : 'TRUE'} + + http_response = JSONResponse(content={'ack' : 'TRUE'}, status_code=200) + add_notifications(request, http_response, True) + return http_response @router.get("/monitoring/notifications") -def get_items( +def get_notifications( skip: int = 0, limit: int = 100 ): notification = event_notifications[skip:limit] - logging.info(notification) return notification +@router.get("/monitoring/last_notifications") +def get_last_notifications( + id: int = Query(..., description="The id of the last retrieved item") + ): + updated_notification = [] + + for notification in event_notifications: + if notification.get('id') == id: + updated_notification = event_notifications[(id + 1):] + break + + return updated_notification + @router.post("/start-loop/", status_code=200) def initiate_movement( *, From a4ad2d32c2115045e3a35af1eeb3973c188189aa Mon Sep 17 00:00:00 2001 From: Anastasios Gogos <13665570+tgogos@users.noreply.github.com> Date: Thu, 21 Oct 2021 18:06:11 +0300 Subject: [PATCH 07/28] add option to select reload time interval --- backend/app/app/static/css/map.css | 2 ++ backend/app/app/static/js/map.js | 58 +++++++++++++++++++++++++++--- backend/app/app/ui/map.html | 16 ++++++++- 3 files changed, 71 insertions(+), 5 deletions(-) diff --git a/backend/app/app/static/css/map.css b/backend/app/app/static/css/map.css index e0004da53..185dc295d 100644 --- a/backend/app/app/static/css/map.css +++ b/backend/app/app/static/css/map.css @@ -7,3 +7,5 @@ .emu-pin-box .pin-bg-red {background: #c11515; border-color: #8a1818;} .emu-pin-box .pin-icon {position: absolute; width: 30px !important; height: 42px !important; font-size: 18px !important; left: 0; top: 8px; text-align: center;} .emu-pin-box .pin-text {position: absolute; width: 30px; height: 42px; font-size: 8px; left: 6px; top: 10px; font-weight: 600;} + +.map-reload-select {display: inline; width: auto;} \ No newline at end of file diff --git a/backend/app/app/static/js/map.js b/backend/app/app/static/js/map.js index a9193f711..157714af9 100644 --- a/backend/app/app/static/js/map.js +++ b/backend/app/app/static/js/map.js @@ -24,7 +24,10 @@ var map_bounds = []; // helper var for correct initialization var UEs_first_paint = true; -var UE_refresh_interval = null; +// for UE & map refresh +var UE_refresh_interval = null; +var UE_refresh_sec_default = 1000; // 1 sec +var UE_refresh_sec = 0; // 0 sec = off // template for UE buttons var ue_btn_tpl = ` ` @@ -75,6 +78,10 @@ $( document ).ready(function() { wait_for_UEs_data(); + // add listener to the select option for map refresh + ui_add_select_listener_map_reload(); + + }); $( window ).resize(function() { @@ -96,17 +103,40 @@ $( window ).resize(function() { function start_map_refresh_interval() { if (UE_refresh_interval == null) { - // start updating every second + + // specify the seconds between every interval + if ( UE_refresh_sec==0 ) { + UE_refresh_sec = UE_refresh_sec_default; + } + + // start updating UE_refresh_interval = setInterval(function(){ api_get_UEs(); - }, 1000); + }, UE_refresh_sec); + + // enable the select button + $('.map-reload-select').prop("disabled",false); + $('.map-reload-select').val(UE_refresh_sec); } } + function stop_map_refresh_interval() { // stop updating every second clearInterval( UE_refresh_interval ); UE_refresh_interval = null; + + // disable the select button + $('.map-reload-select').prop("disabled",true); + $('.map-reload-select').val(0); +} + + +function reload_map_refresh_interval( new_option ) { + + stop_map_refresh_interval(); + UE_refresh_sec = new_option; + start_map_refresh_interval(); } // =============================================== @@ -432,6 +462,7 @@ function api_start_loop( ue ) { $("#btn-ue-"+ue.id).data("running",true); $("#btn-ue-"+ue.id).removeClass('btn-success').addClass('btn-danger'); looping_UEs++; + if (looping_UEs == ues.length) { $('#btn-start-all').removeClass('btn-success').addClass('btn-danger'); $('#btn-start-all').text("Stop all"); @@ -480,6 +511,7 @@ function api_stop_loop( ue ) { $("#btn-ue-"+ue.id).data("running",false); $("#btn-ue-"+ue.id).addClass('btn-success').removeClass('btn-danger'); looping_UEs--; + if (looping_UEs == 0) { $('#btn-start-all').addClass('btn-success').removeClass('btn-danger'); $('#btn-start-all').text("Start all"); @@ -541,6 +573,12 @@ function ui_set_loop_btn_status_for(ue) { $('#btn-ue-'+ue.id).removeClass('btn-success').addClass('btn-danger'); $('#btn-ue-'+ue.id).data("running",data.running); + looping_UEs++; + if (looping_UEs == ues.length) { + $('#btn-start-all').removeClass('btn-success').addClass('btn-danger'); + $('#btn-start-all').text("Stop all"); + } + start_map_refresh_interval(); } }, @@ -588,7 +626,7 @@ function ui_add_ue_btn_listeners(){ -// Adds a listener start/stop ALL button +// Adds a listener to start/stop ALL button // function ui_add_ue_all_btn_listener() { $('#btn-start-all').on('click', function(){ @@ -615,4 +653,16 @@ function ui_add_ue_all_btn_listener() { $(this).text("Start all"); } }); +} + + +// Adds a listener to the select button (top left) +// to handle the reload interval for the map. +// On change, it takes the selected value (seconds) +// and reloads the interval +// +function ui_add_select_listener_map_reload(){ + $('.map-reload-select').on('change', function(){ + reload_map_refresh_interval( $(this).val() ); + }); } \ No newline at end of file diff --git a/backend/app/app/ui/map.html b/backend/app/app/ui/map.html index 8029d05f5..948df0d99 100644 --- a/backend/app/app/ui/map.html +++ b/backend/app/app/ui/map.html @@ -77,7 +77,7 @@
-
+
+
+ + + + +
From 2aeeb7af822a1b1c1b4ca27b8939bfb62631b6ea Mon Sep 17 00:00:00 2001 From: Anastasios Gogos <13665570+tgogos@users.noreply.github.com> Date: Fri, 22 Oct 2021 12:20:29 +0300 Subject: [PATCH 08/28] add cell details to tooltip --- backend/app/app/static/js/map.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/backend/app/app/static/js/map.js b/backend/app/app/static/js/map.js index 157714af9..97d654ccb 100644 --- a/backend/app/app/static/js/map.js +++ b/backend/app/app/static/js/map.js @@ -334,7 +334,11 @@ function ui_map_paint_Cells() { cell_markers[cell.cell_id] = L.marker([cell.latitude,cell.longitude], {icon: cell_icon_5g}).addTo(mymap) .bindTooltip(cell.cell_id) - .bindPopup(""+ cell.name +"
"+ cell.description) + .bindPopup(""+ cell.name +"
"+ + cell.description +"
"+ + "location: [" + cell.latitude.toFixed(6) + "," + cell.longitude.toFixed(6) +"]
"+ + "radius: " + cell.radius + ) .addTo(cells_lg); // add to layer group L.circle([cell.latitude,cell.longitude], cell.radius, { From 1aeb977837daf4b3a60f3a8553c09206b286e1b9 Mon Sep 17 00:00:00 2001 From: JFrgs Date: Fri, 22 Oct 2021 12:24:27 +0300 Subject: [PATCH 09/28] Timestamps added in notifications (Web UI) --- backend/app/app/api/api_v1/endpoints/utils.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/backend/app/app/api/api_v1/endpoints/utils.py b/backend/app/app/api/api_v1/endpoints/utils.py index 7ebf79860..1792fa12a 100644 --- a/backend/app/app/api/api_v1/endpoints/utils.py +++ b/backend/app/app/api/api_v1/endpoints/utils.py @@ -1,4 +1,4 @@ -import json +from datetime import datetime import threading, logging, time, requests, ast from typing import Any, List from fastapi import APIRouter, Depends, HTTPException, Path, Query, Request @@ -159,6 +159,7 @@ def add_notifications(request: Request, response: JSONResponse, is_notification: json_data["method"] = request.method json_data["status_code"] = response.status_code json_data["isNotification"] = is_notification + json_data["timestamp"] = datetime.now() event_notifications.append(json_data) if len(event_notifications) > 100: From 29566f94ad788431625c16c2c38340b9652bdcbf Mon Sep 17 00:00:00 2001 From: JFrgs Date: Fri, 22 Oct 2021 12:41:38 +0300 Subject: [PATCH 10/28] Check expiration time with UTC (minor change) --- backend/app/app/tools/check_subscription.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/backend/app/app/tools/check_subscription.py b/backend/app/app/tools/check_subscription.py index 6bea0db8b..3bc145866 100644 --- a/backend/app/app/tools/check_subscription.py +++ b/backend/app/app/tools/check_subscription.py @@ -2,7 +2,6 @@ import time from app.models.monitoringevent import Monitoring from app.crud.crud_monitoringevent import monitoring -from fastapi import HTTPException from sqlalchemy.orm import Session def check_expiration_time(expire_time): @@ -24,10 +23,10 @@ def check_expiration_time(expire_time): return True elif(day==time_now[2]): # print("Day == day now", day, time_now[2]) - if(hour>time_now[3]+3): #+3 is for timeZone (GMT+3) + if(hour>time_now[3]): # print(hour, time_now[3]) return True - elif(hour==time_now[3]+3): + elif(hour==time_now[3]): # print("Time == time now", hour, time_now[3]) if(minute>time_now[4]): # print(minute, time_now[4]) From ac6ee0c077edd79e8e56f23e7b272dbce7f5fa40 Mon Sep 17 00:00:00 2001 From: JFrgs Date: Fri, 22 Oct 2021 15:33:30 +0300 Subject: [PATCH 11/28] Added -1 functionality in last_notifications Authorization on last_notifications and notification endpoints (UI) --- backend/app/app/api/api_v1/endpoints/utils.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/backend/app/app/api/api_v1/endpoints/utils.py b/backend/app/app/api/api_v1/endpoints/utils.py index 1792fa12a..585e9e554 100644 --- a/backend/app/app/api/api_v1/endpoints/utils.py +++ b/backend/app/app/api/api_v1/endpoints/utils.py @@ -184,17 +184,22 @@ def create_item(item: monitoringevent.MonitoringNotification, request: Request): @router.get("/monitoring/notifications") def get_notifications( skip: int = 0, - limit: int = 100 + limit: int = 100, + current_user: models.User = Depends(deps.get_current_active_user) ): notification = event_notifications[skip:limit] return notification @router.get("/monitoring/last_notifications") def get_last_notifications( - id: int = Query(..., description="The id of the last retrieved item") + id: int = Query(..., description="The id of the last retrieved item"), + current_user: models.User = Depends(deps.get_current_active_user) ): updated_notification = [] + if id == -1: + return event_notifications + for notification in event_notifications: if notification.get('id') == id: updated_notification = event_notifications[(id + 1):] From ca29f583317005ce184f57ddf785a5709a8aff46 Mon Sep 17 00:00:00 2001 From: Anastasios Gogos <13665570+tgogos@users.noreply.github.com> Date: Fri, 22 Oct 2021 16:18:42 +0300 Subject: [PATCH 12/28] minor font-size change to dashboard css --- backend/app/app/static/css/dashboard.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/app/app/static/css/dashboard.css b/backend/app/app/static/css/dashboard.css index e4458c846..160507aa5 100644 --- a/backend/app/app/static/css/dashboard.css +++ b/backend/app/app/static/css/dashboard.css @@ -2,4 +2,4 @@ .card .spinner-grow {position: absolute; bottom: 1rem; right: 1rem;} .row table.dataTable thead th {border-bottom: none;} -.row table.dataTable.no-footer {border-bottom: none} \ No newline at end of file +.row table.dataTable.no-footer {border-bottom: none; font-size: 14px;} \ No newline at end of file From af2238f3ed3707125c183b10991400edf02f3fc1 Mon Sep 17 00:00:00 2001 From: Anastasios Gogos <13665570+tgogos@users.noreply.github.com> Date: Fri, 22 Oct 2021 16:19:35 +0300 Subject: [PATCH 13/28] add datatables and monitoring events closes #8 --- backend/app/app/static/css/map.css | 7 +- backend/app/app/static/js/map.js | 280 ++++++++++++++++++++++++++++- backend/app/app/ui/map.html | 97 ++++++++-- 3 files changed, 366 insertions(+), 18 deletions(-) diff --git a/backend/app/app/static/css/map.css b/backend/app/app/static/css/map.css index 185dc295d..f112fd4b3 100644 --- a/backend/app/app/static/css/map.css +++ b/backend/app/app/static/css/map.css @@ -8,4 +8,9 @@ .emu-pin-box .pin-icon {position: absolute; width: 30px !important; height: 42px !important; font-size: 18px !important; left: 0; top: 8px; text-align: center;} .emu-pin-box .pin-text {position: absolute; width: 30px; height: 42px; font-size: 8px; left: 6px; top: 10px; font-weight: 600;} -.map-reload-select {display: inline; width: auto;} \ No newline at end of file +.select-wrapper {display: inline-block; float: right;} +.select-wrapper select {display: inline; width: auto; height: 24px; font-size: 11px;} + + +.row table.dataTable thead th {border-bottom: none;} +.row table.dataTable.no-footer {border-bottom: none; font-size: 14px;} \ No newline at end of file diff --git a/backend/app/app/static/js/map.js b/backend/app/app/static/js/map.js index 97d654ccb..c983f5c7d 100644 --- a/backend/app/app/static/js/map.js +++ b/backend/app/app/static/js/map.js @@ -33,6 +33,17 @@ var UE_refresh_sec = 0; // 0 sec = off var ue_btn_tpl = ` ` var looping_UEs = 0; + +// variables used for events +var events = null; +var events_datatbl = null; +var events_first_fetch = true; +var latest_event_id_fetched = -1; + +// for events & datatables refresh +var events_refresh_interval = null; +var events_refresh_sec_default = 5000; // 5 sec +var events_refresh_sec = 5000; // 5 sec // =============================================== @@ -82,6 +93,10 @@ $( document ).ready(function() { ui_add_select_listener_map_reload(); + // events + api_get_all_monitoring_events(); + start_events_refresh_interval(); + }); $( window ).resize(function() { @@ -98,8 +113,9 @@ $( window ).resize(function() { // =============================================== // // Initializes the "UE_refresh_interval" -// which triggers an Ajax call every second +// which triggers an Ajax call every "UE_refresh_sec" // to fetch the UE data and update the map +// function start_map_refresh_interval() { if (UE_refresh_interval == null) { @@ -146,6 +162,56 @@ function reload_map_refresh_interval( new_option ) { + +// =============================================== +// Interval - event refresh functions +// =============================================== +// +// Initializes the "events_refresh_interval" +// which triggers an Ajax call every "events_refresh_sec" +// to fetch the event data and update datatable +// +function start_events_refresh_interval() { + + if (events_refresh_interval == null) { + + // start updating + events_refresh_interval = setInterval(function(){ + api_get_last_monitoring_events(); + }, events_refresh_sec); + + // enable the select button + $('.events-reload-select').prop("disabled",false); + $('.events-reload-select').val(events_refresh_sec); + } +} + + +function stop_events_refresh_interval() { + // stop updating every second + clearInterval( events_refresh_interval ); + events_refresh_interval = null; + + // disable the select button + $('.events-reload-select').prop("disabled",true); + $('.events-reload-select').val(0); +} + + +function reload_events_refresh_interval( new_option ) { + + stop_events_refresh_interval(); + events_refresh_sec = new_option; + start_events_refresh_interval(); +} +// =============================================== + + + + + + + // =============================================== // initialize the Leaflet.js map // =============================================== @@ -660,7 +726,7 @@ function ui_add_ue_all_btn_listener() { } -// Adds a listener to the select button (top left) +// Adds a listener to the select button (top right) // to handle the reload interval for the map. // On change, it takes the selected value (seconds) // and reloads the interval @@ -669,4 +735,214 @@ function ui_add_select_listener_map_reload(){ $('.map-reload-select').on('change', function(){ reload_map_refresh_interval( $(this).val() ); }); +} + + + +// Adds a listener to the select button (top right) +// to handle the reload interval for the events. +// On change, it takes the selected value (seconds) +// and reloads the interval +// +function ui_add_select_listener_events_reload(){ + $('.events-reload-select').on('change', function(){ + reload_events_refresh_interval( $(this).val() ); + }); +} + + + + + +// Ajax request to get all monitoring events data +// +// +function api_get_all_monitoring_events() { + + var url = app.api_url + '/utils/monitoring/notifications?skip=0&limit=100'; + + $.ajax({ + type: 'GET', + url: url, + contentType : 'application/json', + headers: { + "authorization": "Bearer " + app.auth_obj.access_token + }, + processData: false, + beforeSend: function() { + // + }, + success: function(data) + { + console.log(data); + events = data; + if ( events_first_fetch ) { + // initialize datatable + ui_init_datatable_events(); + events_first_fetch = false; + } + }, + error: function(err) + { + console.log(err); + }, + complete: function() + { + // + }, + timeout: 5000 + }); +} + + +// Ajax request to get all monitoring events data +// +// +function api_get_all_monitoring_events() { + + var url = app.api_url + '/utils/monitoring/notifications?skip=0&limit=100'; + + $.ajax({ + type: 'GET', + url: url, + contentType : 'application/json', + headers: { + "authorization": "Bearer " + app.auth_obj.access_token + }, + processData: false, + beforeSend: function() { + // + }, + success: function(data) + { + console.log(data); + events = data; + if ( events_first_fetch ) { + // initialize datatable + ui_init_datatable_events(); + events_first_fetch = false; + } + }, + error: function(err) + { + console.log(err); + }, + complete: function() + { + // + }, + timeout: 5000 + }); +} + + + + + +// Ajax request to get all monitoring events data +// +// +function api_get_last_monitoring_events() { + + var url = app.api_url + '/utils/monitoring/last_notifications?id=' + latest_event_id_fetched; + + $.ajax({ + type: 'GET', + url: url, + contentType : 'application/json', + headers: { + "authorization": "Bearer " + app.auth_obj.access_token + }, + processData: false, + beforeSend: function() { + // + }, + success: function(data) + { + // console.log(data); + events = data; + ui_append_datatable_events(data); + + }, + error: function(err) + { + console.log(err); + }, + complete: function() + { + // + }, + timeout: 5000 + }); +} + + + + +function ui_init_datatable_events() { + events_datatbl = $('#dt-events').DataTable( { + data: events, + responsive: true, + paging: false, + searching: false, + info: false, + pageLength: -1, + lengthMenu: [[10, 25, 50, -1], [10, 25, 50, "All"]], + columnDefs: [ + { + "targets": 0, + "data": "id", + "visible": true, + "orderable" : true, + "searchable": false, + }, + { + "targets": 5, + "data": null, + "defaultContent": '', + "orderable" : false, + "searchable": false, + "render": function ( data, type, row ) { + return row.id; + }, + }, + ], + columns: [ + { "data": "id", className: "dt-center" }, + { "data": "isNotification", + "render": function(data) { + if (data) { + return 'Notification'; + } + return 'Request'; + } + }, + // { "data": "isNotification", className: "dt-center" }, + { "data": "method", className: "dt-center" }, + { "data": "status_code", className: "dt-center" }, + { "data": "timestamp", className: "dt-center" }, + ] + } ); +} + + +function ui_append_datatable_events(data) { + + if (data.length == 0) return; + + for (const event of data) { + + // console.log(event); + + events_datatbl.rows.add( [{ + id: event.id, + isNotification: event.isNotification, + method: event.method, + status_code: event.status_code, + timestamp: event.timestamp, + }] ).draw( false ); + } + + // update id value of latest event + latest_event_id_fetched = data[ data.length-1 ].id } \ No newline at end of file diff --git a/backend/app/app/ui/map.html b/backend/app/app/ui/map.html index 948df0d99..da0505e56 100644 --- a/backend/app/app/ui/map.html +++ b/backend/app/app/ui/map.html @@ -32,7 +32,10 @@ - + + + +
- - - - + +
+ + +
+
+
+
Map +
+ + + + +
+
+
+
+
-
-
+ + + +
+ + + +
+
+
+
Monitoring Events +
+ + + + +
+
+
+
+ + + + + + + + + + + + +
IDTYPEMETHODRESPONSETIMESTAMPDETAILS
+ +
+
+
+
+
+ + + +