diff --git a/app/api/views.py b/app/api/views.py index 9b2d676..953056e 100644 --- a/app/api/views.py +++ b/app/api/views.py @@ -150,10 +150,10 @@ class CreateStationDataAPIView(APIView): def post(self, request, *args, **kwargs): # Parse the incoming JSON data try: - station_data = request.data.get('station') - sensors_data = request.data.get('sensors') + station_data = request.data.get('station', None) + sensors_data = request.data.get('sensors', None) - if not station_data or not sensors_data: + if not station_data: raise ValidationError("Both 'station' and 'sensors' are required.") # Use the get_or_create_station function to get or create the station @@ -165,6 +165,9 @@ def post(self, request, *args, **kwargs): # Record the time when the request was received time_received = datetime.datetime.now(datetime.timezone.utc) + if sensors_data: + return JsonResponse({"status": "success, but no sensor data found"}, status=200) + try: with transaction.atomic(): # Iterate through all sensors diff --git a/app/devices/migrations/0014_devicestatus_sensor_list.py b/app/devices/migrations/0014_devicestatus_sensor_list.py new file mode 100644 index 0000000..401ef80 --- /dev/null +++ b/app/devices/migrations/0014_devicestatus_sensor_list.py @@ -0,0 +1,18 @@ +# Generated by Django 5.1.2 on 2025-01-28 13:32 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('devices', '0013_device_current_user_measurement_user_and_more'), + ] + + operations = [ + migrations.AddField( + model_name='devicestatus', + name='sensor_list', + field=models.JSONField(null=True), + ), + ] diff --git a/app/devices/models.py b/app/devices/models.py index 7103664..1cbdf13 100644 --- a/app/devices/models.py +++ b/app/devices/models.py @@ -50,6 +50,7 @@ class DeviceStatus(models.Model): device = models.ForeignKey(Device, on_delete=models.CASCADE, related_name='status_list') battery_voltage = models.FloatField(null=True, blank=True) battery_soc = models.FloatField(null=True, blank=True) + sensor_list = models.JSONField(null=True) def save(self, *args, **kwargs): if not self.pk: # Check if the instance is new diff --git a/app/main/util.py b/app/main/util.py index ce44d7f..852f962 100644 --- a/app/main/util.py +++ b/app/main/util.py @@ -16,6 +16,12 @@ def get_or_create_station(station_info: dict): "voltage": 0, "percentage": 0 } + "sensor_list": [ + { + "model_id": 1, + "dimension_list": [1, 2, 3] + } + ] } creates a station_status entry with the information in station_info @@ -31,13 +37,14 @@ def get_or_create_station(station_info: dict): station.model = station_info['model'] station.firmware = station_info['firmware'] station.api_key = station_info['apikey'] - + # add a new DeviceStatus station_status = DeviceStatus.objects.create( time_received = datetime.datetime.now(datetime.timezone.utc), device = station, - battery_voltage = station_info['battery']['voltage'], - battery_soc = station_info['battery']['percentage'], + battery_voltage = station_info.get('battery', {}).get('voltage', None), + battery_soc = station_info.get('battery', {}).get('percentage', None), + sensor_list = station_info.get('sensor_list', None) ) # update firmware field diff --git a/app/staticfiles/debug_toolbar/css/print.css b/app/staticfiles/debug_toolbar/css/print.css new file mode 100644 index 0000000..58d3084 --- /dev/null +++ b/app/staticfiles/debug_toolbar/css/print.css @@ -0,0 +1,3 @@ +#djDebug { + display: none !important; +} diff --git a/app/staticfiles/debug_toolbar/css/toolbar.css b/app/staticfiles/debug_toolbar/css/toolbar.css new file mode 100644 index 0000000..a8699a4 --- /dev/null +++ b/app/staticfiles/debug_toolbar/css/toolbar.css @@ -0,0 +1,772 @@ +/* Variable definitions */ +:root { + /* Font families are the same as in Django admin/css/base.css */ + --djdt-font-family-primary: "Segoe UI", system-ui, Roboto, "Helvetica Neue", + Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", + "Segoe UI Symbol", "Noto Color Emoji"; + --djdt-font-family-monospace: ui-monospace, Menlo, Monaco, "Cascadia Mono", + "Segoe UI Mono", "Roboto Mono", "Oxygen Mono", "Ubuntu Monospace", + "Source Code Pro", "Fira Mono", "Droid Sans Mono", "Courier New", + monospace, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", + "Noto Color Emoji"; +} + +:root, +#djDebug[data-theme="light"] { + --djdt-font-color: black; + --djdt-background-color: white; + --djdt-panel-content-background-color: #eee; + --djdt-panel-content-table-background-color: var(--djdt-background-color); + --djdt-panel-title-background-color: #ffc; + --djdt-djdt-panel-content-table-strip-background-color: #f5f5f5; + --djdt--highlighted-background-color: lightgrey; + --djdt-toggle-template-background-color: #bbb; + + --djdt-sql-font-color: #333; + --djdt-pre-text-color: #555; + --djdt-path-and-locals: #777; + --djdt-stack-span-color: black; + --djdt-template-highlight-color: #333; + + --djdt-table-border-color: #ccc; + --djdt-button-border-color: var(--djdt-table-border-color); + --djdt-pre-border-color: var(--djdt-table-border-color); + --djdt-raw-border-color: var(--djdt-table-border-color); +} + +@media (prefers-color-scheme: dark) { + :root { + --djdt-font-color: #8393a7; + --djdt-background-color: #1e293bff; + --djdt-panel-content-background-color: #0f1729ff; + --djdt-panel-title-background-color: #242432; + --djdt-djdt-panel-content-table-strip-background-color: #324154ff; + --djdt--highlighted-background-color: #2c2a7dff; + --djdt-toggle-template-background-color: #282755; + + --djdt-sql-font-color: var(--djdt-font-color); + --djdt-pre-text-color: var(--djdt-font-color); + --djdt-path-and-locals: #65758cff; + --djdt-stack-span-color: #7c8fa4; + --djdt-template-highlight-color: var(--djdt-stack-span-color); + + --djdt-table-border-color: #324154ff; + --djdt-button-border-color: var(--djdt-table-border-color); + --djdt-pre-border-color: var(--djdt-table-border-color); + --djdt-raw-border-color: var(--djdt-table-border-color); + } +} + +#djDebug[data-theme="dark"] { + --djdt-font-color: #8393a7; + --djdt-background-color: #1e293bff; + --djdt-panel-content-background-color: #0f1729ff; + --djdt-panel-content-table-background-color: var(--djdt-background-color); + --djdt-panel-title-background-color: #242432; + --djdt-djdt-panel-content-table-strip-background-color: #324154ff; + --djdt--highlighted-background-color: #2c2a7dff; + --djdt-toggle-template-background-color: #282755; + + --djdt-sql-font-color: var(--djdt-font-color); + --djdt-pre-text-color: var(--djdt-font-color); + --djdt-path-and-locals: #65758cff; + --djdt-stack-span-color: #7c8fa4; + --djdt-template-highlight-color: var(--djdt-stack-span-color); + + --djdt-table-border-color: #324154ff; + --djdt-button-border-color: var(--djdt-table-border-color); + --djdt-pre-border-color: var(--djdt-table-border-color); + --djdt-raw-border-color: var(--djdt-table-border-color); +} + +/* Debug Toolbar CSS Reset, adapted from Eric Meyer's CSS Reset */ +#djDebug { + color: var(--djdt-font-color); + background: var(--djdt-background-color); +} +#djDebug, +#djDebug div, +#djDebug span, +#djDebug applet, +#djDebug object, +#djDebug iframe, +#djDebug h1, +#djDebug h2, +#djDebug h3, +#djDebug h4, +#djDebug h5, +#djDebug h6, +#djDebug p, +#djDebug blockquote, +#djDebug pre, +#djDebug a, +#djDebug abbr, +#djDebug acronym, +#djDebug address, +#djDebug big, +#djDebug cite, +#djDebug code, +#djDebug del, +#djDebug dfn, +#djDebug em, +#djDebug font, +#djDebug img, +#djDebug ins, +#djDebug kbd, +#djDebug q, +#djDebug s, +#djDebug samp, +#djDebug small, +#djDebug strike, +#djDebug strong, +#djDebug sub, +#djDebug sup, +#djDebug tt, +#djDebug var, +#djDebug b, +#djDebug u, +#djDebug i, +#djDebug center, +#djDebug dl, +#djDebug dt, +#djDebug dd, +#djDebug ol, +#djDebug ul, +#djDebug li, +#djDebug fieldset, +#djDebug form, +#djDebug label, +#djDebug legend, +#djDebug table, +#djDebug caption, +#djDebug tbody, +#djDebug tfoot, +#djDebug thead, +#djDebug tr, +#djDebug th, +#djDebug td, +#djDebug summary, +#djDebug button { + margin: 0; + padding: 0; + min-width: 0; + width: auto; + border: 0; + outline: 0; + font-size: 12px; + line-height: 1.5em; + color: var(--djdt-font-color); + vertical-align: baseline; + background-color: transparent; + font-family: var(--djdt-font-family-primary); + text-align: left; + text-shadow: none; + white-space: normal; + transition: none; +} + +#djDebug button { + background-color: #eee; + background-image: linear-gradient(to bottom, #eee, #cccccc); + border: 1px solid var(--djdt-button-border-color); + border-bottom: 1px solid #bbb; + border-radius: 3px; + color: #333; + line-height: 1; + padding: 0 8px; + text-align: center; + text-shadow: 0 1px 0 #eee; +} + +#djDebug button:hover { + background-color: #ddd; + background-image: linear-gradient(to bottom, #ddd, #bbb); + border-color: #bbb; + border-bottom-color: #999; + cursor: pointer; + text-shadow: 0 1px 0 #ddd; +} + +#djDebug button:active { + border: 1px solid #aaa; + border-bottom: 1px solid #888; + box-shadow: + inset 0 0 5px 2px #aaa, + 0 1px 0 0 #eee; +} + +#djDebug #djDebugToolbar { + background-color: #111; + width: 220px; + z-index: 100000000; + position: fixed; + top: 0; + bottom: 0; + right: 0; + opacity: 0.9; + overflow-y: auto; +} + +#djDebug #djDebugToolbar small { + color: #999; +} + +#djDebug #djDebugToolbar ul { + margin: 0; + padding: 0; + list-style: none; +} + +#djDebug #djDebugToolbar li { + border-bottom: 1px solid #222; + color: #fff; + display: block; + font-weight: bold; + float: none; + margin: 0; + padding: 0; + position: relative; + width: auto; +} + +#djDebug #djDebugToolbar input[type="checkbox"] { + float: right; + margin: 10px; +} + +#djDebug #djDebugToolbar li > a, +#djDebug #djDebugToolbar li > div.djdt-contentless { + font-weight: normal; + font-style: normal; + text-decoration: none; + display: block; + font-size: 16px; + padding: 7px 10px 8px 25px; + color: #fff; +} +#djDebug #djDebugToolbar li > div.djdt-disabled { + font-style: italic; + color: #999; +} + +#djDebug #djDebugToolbar li a:hover { + color: #111; + background-color: #ffc; +} + +#djDebug #djDebugToolbar li.djdt-active { + background: #333; +} + +#djDebug #djDebugToolbar li.djdt-active:before { + content: "▶"; + font-family: var(--djdt-font-family-primary); + position: absolute; + left: 0; + top: 50%; + transform: translateY(-50%); + color: #eee; + font-size: 150%; +} + +#djDebug #djDebugToolbar li.djdt-active a:hover { + color: #b36a60; + background-color: transparent; +} + +#djDebug #djDebugToolbar li small { + font-size: 12px; + color: #999; + font-style: normal; + text-decoration: none; +} + +#djDebug #djDebugToolbarHandle { + position: fixed; + transform: translateY(-100%) rotate(-90deg); + transform-origin: right bottom; + background-color: #fff; + border: 1px solid #111; + border-bottom: 0; + top: 0; + right: 0; + z-index: 100000000; + opacity: 0.75; +} + +#djDebug #djShowToolBarButton { + padding: 0 5px; + border: 4px solid #fff; + border-bottom-width: 0; + color: #fff; + font-size: 22px; + font-weight: bold; + background: #000; + opacity: 0.6; +} + +#djDebug #djShowToolBarButton:hover { + background-color: #111; + border-color: #ffe761; + cursor: move; + opacity: 1; +} + +#djDebug #djShowToolBarD { + color: #cf9; + font-size: 22px; +} + +#djDebug #djShowToolBarJ { + color: #cf9; + font-size: 16px; +} + +#djDebug pre, +#djDebug code { + display: block; + font-family: var(--djdt-font-family-monospace); + overflow: auto; +} + +#djDebug code { + font-size: 12px; + white-space: pre; +} + +#djDebug pre { + white-space: pre-wrap; + color: var(--djdt-pre-text-color); + border: 1px solid var(--djdt-pre-border-color); + border-collapse: collapse; + background-color: var(--djdt-background-color); + padding: 2px 3px; + margin-bottom: 3px; +} + +#djDebug .djdt-panelContent { + position: fixed; + margin: 0; + top: 0; + right: 220px; + bottom: 0; + left: 0px; + background-color: var(--djdt-panel-content-background-color); + color: #666; + z-index: 100000000; +} + +#djDebug .djdt-panelContent > div { + border-bottom: 1px solid #ddd; +} + +#djDebug .djDebugPanelTitle { + position: absolute; + background-color: var(--djdt-panel-title-background-color); + color: #666; + padding-left: 20px; + top: 0; + right: 0; + left: 0; + height: 50px; +} + +#djDebug .djDebugPanelTitle code { + display: inline; + font-size: inherit; +} + +#djDebug .djDebugPanelContent { + position: absolute; + top: 50px; + right: 0; + bottom: 0; + left: 0; + height: auto; + padding: 5px 0 0 20px; +} + +#djDebug .djDebugPanelContent .djdt-loader { + margin: 80px auto; + border: 6px solid white; + border-radius: 50%; + border-top: 6px solid #ffe761; + width: 38px; + height: 38px; + animation: spin 2s linear infinite; +} + +@keyframes spin { + 0% { + transform: rotate(0deg); + } + 100% { + transform: rotate(360deg); + } +} + +#djDebug .djDebugPanelContent .djdt-scroll { + height: 100%; + overflow: auto; + display: block; + padding: 0 10px 0 0; +} + +#djDebug h3 { + font-size: 24px; + font-weight: normal; + line-height: 50px; +} + +#djDebug h4 { + font-size: 20px; + font-weight: bold; + margin-top: 0.8em; +} + +#djDebug .djdt-panelContent table { + border: 1px solid var(--djdt-table-border-color); + border-collapse: collapse; + width: 100%; + background-color: var(--djdt-panel-content-table-background-color); + display: table; + margin-top: 0.8em; + overflow: auto; +} +#djDebug .djdt-panelContent tbody > tr:nth-child(odd):not(.djdt-highlighted) { + background-color: var(--djdt-panel-content-table-strip-background-color); +} +#djDebug .djdt-panelContent tbody td, +#djDebug .djdt-panelContent tbody th { + vertical-align: top; + padding: 2px 3px; +} +#djDebug .djdt-panelContent tbody td.djdt-time { + text-align: center; +} + +#djDebug .djdt-panelContent thead th { + padding: 1px 6px 1px 3px; + text-align: left; + font-weight: bold; + font-size: 14px; + white-space: nowrap; +} +#djDebug .djdt-panelContent tbody th { + width: 12em; + text-align: right; + color: #666; + padding-right: 0.5em; +} + +#djDebug .djTemplateContext { + background-color: var(--djdt-background-color); +} + +#djDebug .djdt-panelContent .djDebugClose { + position: absolute; + top: 4px; + right: 15px; + line-height: 16px; + border: 6px solid #ddd; + border-radius: 50%; + background: #fff; + color: #ddd; + font-weight: 900; + font-size: 20px; + height: 36px; + width: 36px; + padding: 0 0 5px; + box-sizing: border-box; + display: grid; + place-items: center; +} + +#djDebug .djdt-panelContent .djDebugClose:hover { + background: #c0695d; +} + +#djDebug .djdt-panelContent dt, +#djDebug .djdt-panelContent dd { + display: block; +} + +#djDebug .djdt-panelContent dt { + margin-top: 0.75em; +} + +#djDebug .djdt-panelContent dd { + margin-left: 10px; +} + +#djDebug a.toggleTemplate { + padding: 4px; + background-color: var(--djdt-toggle-template-background-color); + border-radius: 3px; +} + +#djDebug a.toggleTemplate:hover { + padding: 4px; + background-color: #444; + color: #ffe761; + border-radius: 3px; +} + +#djDebug .djDebugCollapsed { + color: var(--djdt-sql-font-color); +} + +#djDebug .djDebugUncollapsed { + color: var(--djdt-sql-font-color); +} + +#djDebug .djUnselected { + display: none; +} + +#djDebug tr.djSelected { + display: table-row; +} + +#djDebug .djDebugSql { + overflow-wrap: anywhere; +} + +#djDebug .djSQLDetailsDiv tbody th { + text-align: left; +} + +#djDebug span.djDebugLineChart { + background-color: #777; + height: 3px; + position: absolute; + bottom: 0; + top: 0; + left: 0; + display: block; + z-index: 1000000001; +} +#djDebug span.djDebugLineChartWarning { + background-color: #900; +} + +#djDebug .highlight { + color: var(--djdt-font-color); +} +#djDebug .highlight .err { + color: var(--djdt-font-color); +} /* Error */ + +/* +Styles for pygments HTMLFormatter + +- This should match debug_toolbar/panels/templates/views.py::template_source +- Each line needs to be prefixed with #djDebug .highlight as well. +- The .w definition needs to include: + white-space: pre-wrap + +To regenerate: + + from pygments.formatters import HtmlFormatter + print(HtmlFormatter(wrapcode=True).get_style_defs()) + */ +#djDebug .highlight pre { line-height: 125%; } +#djDebug .highlight td.linenos .normal { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; } +#djDebug .highlight span.linenos { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; } +#djDebug .highlight td.linenos .special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; } +#djDebug .highlight span.linenos.special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; } +#djDebug .highlight .hll { background-color: #ffffcc } +#djDebug .highlight .c { color: #3D7B7B; font-style: italic } /* Comment */ +#djDebug .highlight .err { border: 1px solid #FF0000 } /* Error */ +#djDebug .highlight .k { color: #008000; font-weight: bold } /* Keyword */ +#djDebug .highlight .o { color: #666666 } /* Operator */ +#djDebug .highlight .ch { color: #3D7B7B; font-style: italic } /* Comment.Hashbang */ +#djDebug .highlight .cm { color: #3D7B7B; font-style: italic } /* Comment.Multiline */ +#djDebug .highlight .cp { color: #9C6500 } /* Comment.Preproc */ +#djDebug .highlight .cpf { color: #3D7B7B; font-style: italic } /* Comment.PreprocFile */ +#djDebug .highlight .c1 { color: #3D7B7B; font-style: italic } /* Comment.Single */ +#djDebug .highlight .cs { color: #3D7B7B; font-style: italic } /* Comment.Special */ +#djDebug .highlight .gd { color: #A00000 } /* Generic.Deleted */ +#djDebug .highlight .ge { font-style: italic } /* Generic.Emph */ +#djDebug .highlight .ges { font-weight: bold; font-style: italic } /* Generic.EmphStrong */ +#djDebug .highlight .gr { color: #E40000 } /* Generic.Error */ +#djDebug .highlight .gh { color: #000080; font-weight: bold } /* Generic.Heading */ +#djDebug .highlight .gi { color: #008400 } /* Generic.Inserted */ +#djDebug .highlight .go { color: #717171 } /* Generic.Output */ +#djDebug .highlight .gp { color: #000080; font-weight: bold } /* Generic.Prompt */ +#djDebug .highlight .gs { font-weight: bold } /* Generic.Strong */ +#djDebug .highlight .gu { color: #800080; font-weight: bold } /* Generic.Subheading */ +#djDebug .highlight .gt { color: #0044DD } /* Generic.Traceback */ +#djDebug .highlight .kc { color: #008000; font-weight: bold } /* Keyword.Constant */ +#djDebug .highlight .kd { color: #008000; font-weight: bold } /* Keyword.Declaration */ +#djDebug .highlight .kn { color: #008000; font-weight: bold } /* Keyword.Namespace */ +#djDebug .highlight .kp { color: #008000 } /* Keyword.Pseudo */ +#djDebug .highlight .kr { color: #008000; font-weight: bold } /* Keyword.Reserved */ +#djDebug .highlight .kt { color: #B00040 } /* Keyword.Type */ +#djDebug .highlight .m { color: #666666 } /* Literal.Number */ +#djDebug .highlight .s { color: #BA2121 } /* Literal.String */ +#djDebug .highlight .na { color: #687822 } /* Name.Attribute */ +#djDebug .highlight .nb { color: #008000 } /* Name.Builtin */ +#djDebug .highlight .nc { color: #0000FF; font-weight: bold } /* Name.Class */ +#djDebug .highlight .no { color: #880000 } /* Name.Constant */ +#djDebug .highlight .nd { color: #AA22FF } /* Name.Decorator */ +#djDebug .highlight .ni { color: #717171; font-weight: bold } /* Name.Entity */ +#djDebug .highlight .ne { color: #CB3F38; font-weight: bold } /* Name.Exception */ +#djDebug .highlight .nf { color: #0000FF } /* Name.Function */ +#djDebug .highlight .nl { color: #767600 } /* Name.Label */ +#djDebug .highlight .nn { color: #0000FF; font-weight: bold } /* Name.Namespace */ +#djDebug .highlight .nt { color: #008000; font-weight: bold } /* Name.Tag */ +#djDebug .highlight .nv { color: #19177C } /* Name.Variable */ +#djDebug .highlight .ow { color: #AA22FF; font-weight: bold } /* Operator.Word */ +#djDebug .highlight .w { color: #bbbbbb; white-space: pre-wrap } /* Text.Whitespace */ +#djDebug .highlight .mb { color: #666666 } /* Literal.Number.Bin */ +#djDebug .highlight .mf { color: #666666 } /* Literal.Number.Float */ +#djDebug .highlight .mh { color: #666666 } /* Literal.Number.Hex */ +#djDebug .highlight .mi { color: #666666 } /* Literal.Number.Integer */ +#djDebug .highlight .mo { color: #666666 } /* Literal.Number.Oct */ +#djDebug .highlight .sa { color: #BA2121 } /* Literal.String.Affix */ +#djDebug .highlight .sb { color: #BA2121 } /* Literal.String.Backtick */ +#djDebug .highlight .sc { color: #BA2121 } /* Literal.String.Char */ +#djDebug .highlight .dl { color: #BA2121 } /* Literal.String.Delimiter */ +#djDebug .highlight .sd { color: #BA2121; font-style: italic } /* Literal.String.Doc */ +#djDebug .highlight .s2 { color: #BA2121 } /* Literal.String.Double */ +#djDebug .highlight .se { color: #AA5D1F; font-weight: bold } /* Literal.String.Escape */ +#djDebug .highlight .sh { color: #BA2121 } /* Literal.String.Heredoc */ +#djDebug .highlight .si { color: #A45A77; font-weight: bold } /* Literal.String.Interpol */ +#djDebug .highlight .sx { color: #008000 } /* Literal.String.Other */ +#djDebug .highlight .sr { color: #A45A77 } /* Literal.String.Regex */ +#djDebug .highlight .s1 { color: #BA2121 } /* Literal.String.Single */ +#djDebug .highlight .ss { color: #19177C } /* Literal.String.Symbol */ +#djDebug .highlight .bp { color: #008000 } /* Name.Builtin.Pseudo */ +#djDebug .highlight .fm { color: #0000FF } /* Name.Function.Magic */ +#djDebug .highlight .vc { color: #19177C } /* Name.Variable.Class */ +#djDebug .highlight .vg { color: #19177C } /* Name.Variable.Global */ +#djDebug .highlight .vi { color: #19177C } /* Name.Variable.Instance */ +#djDebug .highlight .vm { color: #19177C } /* Name.Variable.Magic */ +#djDebug .highlight .il { color: #666666 } /* Literal.Number.Integer.Long */ + +#djDebug svg.djDebugLineChart { + width: 100%; + height: 1.5em; +} + +#djDebug svg.djDebugLineChartWarning rect { + fill: #900; +} + +#djDebug svg.djDebugLineChartInTransaction rect { + fill: #d3ff82; +} +#djDebug svg.djDebugLineChart line { + stroke: #94b24d; +} + +#djDebug .djDebugRowWarning .djdt-time { + color: red; +} +#djDebug .djdt-panelContent table .djdt-toggle { + width: 14px; + padding-top: 3px; +} +#djDebug .djdt-panelContent table .djdt-actions { + min-width: 70px; + white-space: nowrap; +} +#djDebug .djdt-color:after { + content: "\00a0"; +} +#djDebug .djToggleSwitch { + box-sizing: content-box; + padding: 0; + border: 1px solid #999; + border-radius: 0; + width: 12px; + color: #777; + background: linear-gradient(to bottom, #fff, #dcdcdc); +} +#djDebug .djNoToggleSwitch { + height: 14px; + width: 14px; + display: inline-block; +} + +#djDebug .djSQLDetailsDiv { + margin-top: 0.8em; +} + +#djDebug .djdt-stack span { + color: var(--djdt-stack-span-color); + font-weight: bold; +} +#djDebug .djdt-stack span.djdt-path, +#djDebug .djdt-stack pre.djdt-locals, +#djDebug .djdt-stack pre.djdt-locals span { + color: var(--djdt-path-and-locals); + font-weight: normal; +} +#djDebug .djdt-stack span.djdt-code { + font-weight: normal; +} +#djDebug .djdt-stack pre.djdt-locals { + margin: 0 27px 27px 27px; +} +#djDebug .djdt-raw { + background-color: #fff; + border: 1px solid var(--djdt-raw-border-color); + margin-top: 0.8em; + padding: 5px; + white-space: pre-wrap; +} + +#djDebug .djdt-width-20 { + width: 20%; +} +#djDebug .djdt-width-30 { + width: 30%; +} +#djDebug .djdt-width-60 { + width: 60%; +} +#djDebug .djdt-max-height-100 { + max-height: 100%; +} +#djDebug .djdt-highlighted { + background-color: var(--djdt--highlighted-background-color); +} +#djDebug tr.djdt-highlighted.djdt-profile-row { + background-color: #ffc; +} +#djDebug tr.djdt-highlighted.djdt-profile-row:nth-child(2n + 1) { + background-color: #dd9; +} +@keyframes djdt-flash-new { + from { + background-color: green; + } + to { + background-color: inherit; + } +} +#djDebug .flash-new { + animation: djdt-flash-new 1s; +} + +.djdt-hidden { + display: none; +} + +#djDebug #djDebugToolbar a#djToggleThemeButton { + display: flex; + align-items: center; + cursor: pointer; +} +#djToggleThemeButton > svg { + margin-left: auto; +} +#djDebug[data-theme="light"] #djToggleThemeButton svg.theme-light, +#djDebug[data-theme="dark"] #djToggleThemeButton svg.theme-dark, +#djDebug[data-theme="auto"] #djToggleThemeButton svg.theme-auto { + display: block; + height: 1rem; + width: 1rem; +} diff --git a/app/staticfiles/debug_toolbar/js/history.js b/app/staticfiles/debug_toolbar/js/history.js new file mode 100644 index 0000000..314ddb3 --- /dev/null +++ b/app/staticfiles/debug_toolbar/js/history.js @@ -0,0 +1,109 @@ +import { $$, ajaxForm, replaceToolbarState } from "./utils.js"; + +const djDebug = document.getElementById("djDebug"); + +function difference(setA, setB) { + const _difference = new Set(setA); + for (const elem of setB) { + _difference.delete(elem); + } + return _difference; +} + +/** + * Create an array of dataset properties from a NodeList. + */ +function pluckData(nodes, key) { + const data = []; + nodes.forEach(function (obj) { + data.push(obj.dataset[key]); + }); + return data; +} + +function refreshHistory() { + const formTarget = djDebug.querySelector(".refreshHistory"); + const container = document.getElementById("djdtHistoryRequests"); + const oldIds = new Set( + pluckData(container.querySelectorAll("tr[data-store-id]"), "storeId") + ); + + ajaxForm(formTarget) + .then(function (data) { + // Remove existing rows first then re-populate with new data + container + .querySelectorAll("tr[data-store-id]") + .forEach(function (node) { + node.remove(); + }); + data.requests.forEach(function (request) { + container.innerHTML = request.content + container.innerHTML; + }); + }) + .then(function () { + const allIds = new Set( + pluckData( + container.querySelectorAll("tr[data-store-id]"), + "storeId" + ) + ); + const newIds = difference(allIds, oldIds); + const lastRequestId = newIds.values().next().value; + return { + allIds, + newIds, + lastRequestId, + }; + }) + .then(function (refreshInfo) { + refreshInfo.newIds.forEach(function (newId) { + const row = container.querySelector( + `tr[data-store-id="${newId}"]` + ); + row.classList.add("flash-new"); + }); + setTimeout(() => { + container + .querySelectorAll("tr[data-store-id]") + .forEach((row) => { + row.classList.remove("flash-new"); + }); + }, 2000); + }); +} + +function switchHistory(newStoreId) { + const formTarget = djDebug.querySelector( + ".switchHistory[data-store-id='" + newStoreId + "']" + ); + const tbody = formTarget.closest("tbody"); + + const highlighted = tbody.querySelector(".djdt-highlighted"); + if (highlighted) { + highlighted.classList.remove("djdt-highlighted"); + } + formTarget.closest("tr").classList.add("djdt-highlighted"); + + ajaxForm(formTarget).then(function (data) { + if (Object.keys(data).length === 0) { + const container = document.getElementById("djdtHistoryRequests"); + container.querySelector( + 'button[data-store-id="' + newStoreId + '"]' + ).innerHTML = "Switch [EXPIRED]"; + } + replaceToolbarState(newStoreId, data); + }); +} + +$$.on(djDebug, "click", ".switchHistory", function (event) { + event.preventDefault(); + switchHistory(this.dataset.storeId); +}); + +$$.on(djDebug, "click", ".refreshHistory", function (event) { + event.preventDefault(); + refreshHistory(); +}); +// We don't refresh the whole toolbar each fetch or ajax request, +// so we need to refresh the history when we open the panel +$$.onPanelRender(djDebug, "HistoryPanel", refreshHistory); diff --git a/app/staticfiles/debug_toolbar/js/redirect.js b/app/staticfiles/debug_toolbar/js/redirect.js new file mode 100644 index 0000000..6976de4 --- /dev/null +++ b/app/staticfiles/debug_toolbar/js/redirect.js @@ -0,0 +1 @@ +document.getElementById("redirect_to").focus(); diff --git a/app/staticfiles/debug_toolbar/js/timer.js b/app/staticfiles/debug_toolbar/js/timer.js new file mode 100644 index 0000000..a88ab0d --- /dev/null +++ b/app/staticfiles/debug_toolbar/js/timer.js @@ -0,0 +1,88 @@ +import { $$ } from "./utils.js"; + +function insertBrowserTiming() { + const timingOffset = performance.timing.navigationStart, + timingEnd = performance.timing.loadEventEnd, + totalTime = timingEnd - timingOffset; + function getLeft(stat) { + if (totalTime !== 0) { + return ( + ((performance.timing[stat] - timingOffset) / totalTime) * 100.0 + ); + } else { + return 0; + } + } + function getCSSWidth(stat, endStat) { + let width = 0; + if (totalTime !== 0) { + width = + ((performance.timing[endStat] - performance.timing[stat]) / + totalTime) * + 100.0; + } + const denominator = 100.0 - getLeft(stat); + if (denominator !== 0) { + // Calculate relative percent (same as sql panel logic) + width = (100.0 * width) / denominator; + } else { + width = 0; + } + return width < 1 ? "2px" : width + "%"; + } + function addRow(tbody, stat, endStat) { + const row = document.createElement("tr"); + if (endStat) { + // Render a start through end bar + row.innerHTML = + "