diff --git a/server/django/sensordata/serializers/generic_resource_serializer.py b/server/django/sensordata/serializers/generic_resource_serializer.py new file mode 100644 index 00000000..0e18f9a8 --- /dev/null +++ b/server/django/sensordata/serializers/generic_resource_serializer.py @@ -0,0 +1,15 @@ +from rest_framework import serializers +from ..models import Resource +from drf_spectacular.utils import extend_schema_field + +class GenericResourceSerializer(serializers.ModelSerializer): + resource_type = serializers.CharField(source='resource_type.name', read_only=True) + value = serializers.SerializerMethodField() + + class Meta: + model = Resource + fields = ['timestamp_created', 'value', 'resource_type'] + + @extend_schema_field(serializers.FloatField) + def get_value(self, obj) -> float: + return obj.get_value() diff --git a/server/django/sensordata/templates/admin_dashboard.html b/server/django/sensordata/templates/admin_dashboard.html new file mode 100644 index 00000000..b457353a --- /dev/null +++ b/server/django/sensordata/templates/admin_dashboard.html @@ -0,0 +1,18 @@ +{% extends 'base.html' %} +{% block content %} +

Admin Dashboard

+ + + + + + + + {% for device in devices %} + + + + {% endfor %} + +
Endpoint
{{ device.endpoint }}
+{% endblock %} diff --git a/server/django/sensordata/templates/base.html b/server/django/sensordata/templates/base.html new file mode 100644 index 00000000..2b50db53 --- /dev/null +++ b/server/django/sensordata/templates/base.html @@ -0,0 +1,163 @@ + + + + + {% load static %} + + + + + + + + + +
+ + + + + + + +
+
+
+
+
+

{{ title }}

+
+
+
+
+ +
+
+ {% block content %} + {% endblock %} +
+
+
+ +
+ + + + + + + diff --git a/server/django/sensordata/templates/base_license.html b/server/django/sensordata/templates/base_license.html new file mode 100644 index 00000000..113139e3 --- /dev/null +++ b/server/django/sensordata/templates/base_license.html @@ -0,0 +1,170 @@ + + + + + {% load static %} + + + + + + + + + +
+ + + + + + + +
+
+
+
+
+

{{ title }}

+
+
+
+
+ +
+
+ {% block content %} + {% endblock %} +
+
+
+ +
+ + + + + + + + diff --git a/server/django/sensordata/templates/base_login.html b/server/django/sensordata/templates/base_login.html new file mode 100644 index 00000000..60306a52 --- /dev/null +++ b/server/django/sensordata/templates/base_login.html @@ -0,0 +1,27 @@ + + + + + {% load static %} + + + + + + + + + +
+ {% block content %} + {% endblock %} +
+ + + + + + + + diff --git a/server/django/sensordata/templates/event_dashboard.html b/server/django/sensordata/templates/event_dashboard.html new file mode 100644 index 00000000..ffa08109 --- /dev/null +++ b/server/django/sensordata/templates/event_dashboard.html @@ -0,0 +1,24 @@ +{% extends 'base.html' %} +{% block content %} +

Event Dashboard

+ + + + + + + + + + + {% for event in events %} + + + + + + + {% endfor %} + +
DeviceEvent TypeStart TimeEnd Time
{{ event.device.endpoint }}{{ event.event_type }}{{ event.start_time }}{{ event.end_time }}
+{% endblock %} diff --git a/server/django/sensordata/templates/firmware_dashboard.html b/server/django/sensordata/templates/firmware_dashboard.html new file mode 100644 index 00000000..263429b0 --- /dev/null +++ b/server/django/sensordata/templates/firmware_dashboard.html @@ -0,0 +1,24 @@ +{% extends 'base.html' %} +{% block content %} +

Firmware Update Dashboard

+ + + + + + + + + + + {% for update in firmware_updates %} + + + + + + + {% endfor %} + +
VersionFile NameDownload URLCreated At
{{ update.version }}{{ update.file_name }}{{ update.download_url }}{{ update.created_at }}
+{% endblock %} diff --git a/server/django/sensordata/templates/graph_dashboard.html b/server/django/sensordata/templates/graph_dashboard.html new file mode 100644 index 00000000..4a9b1967 --- /dev/null +++ b/server/django/sensordata/templates/graph_dashboard.html @@ -0,0 +1,60 @@ +{% extends 'base.html' %} +{% block content %} +

Graph Dashboard

+ + + + + +{% endblock %} diff --git a/server/django/sensordata/templates/license.html b/server/django/sensordata/templates/license.html new file mode 100644 index 00000000..8679a7e3 --- /dev/null +++ b/server/django/sensordata/templates/license.html @@ -0,0 +1,300 @@ +{% extends 'base_license.html' %} + +{% block title %}Project Information and Licenses{% endblock %} + +{% block content %} +
+
+
+ +
+
+
+ +
+
+
+
+

This update pertains to the ongoing development of the Pump Monitor project, introduced at + the Zephyr Summit 2023. + We use of Open Source software to ensure supply chain resilience + in software components. This + approach reduces reliance on specific cloud providers, minimizing risks associated with + potential service disruptions.

+ +

Updates include hardware and firmware enhancements for the Pump Monitor, leveraging recent + Zephyr components like zbus + for improved performance. The project focuses on developing an Open Source IoT LwM2M and + Django server with a + user-friendly interface, prioritizing reliability and flexibility. Containerization and + straightforward design + contribute to ensuring operational stability and adaptability. +

+
+
+
+ +
+
+
+ +
+
+
+
+

Apache License

+
+TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+1. Definitions.
+
+"License" shall mean the terms and conditions for use, reproduction,
+and distribution as defined by Sections 1 through 9 of this document.
+
+"Licensor" shall mean the copyright owner or entity authorized by
+the copyright owner that is granting the License.
+
+"Legal Entity" shall mean the union of the acting entity and all
+other entities that control, are controlled by, or are under common
+control with that entity. For the purposes of this definition,
+"control" means (i) the power, direct or indirect, to cause the
+direction or management of such entity, whether by contract or
+otherwise, or (ii) ownership of fifty percent (50%) or more of the
+outstanding shares, or (iii) beneficial ownership of such entity.
+
+"You" (or "Your") shall mean an individual or Legal Entity
+exercising permissions granted by this License.
+
+"Source" form shall mean the preferred form for making modifications,
+including but not limited to software source code, documentation
+source, and configuration files.
+
+"Object" form shall mean any form resulting from mechanical
+transformation or translation of a Source form, including but
+not limited to compiled object code, generated documentation,
+and conversions to other media types.
+
+"Work" shall mean the work of authorship, whether in Source or
+Object form, made available under the License, as indicated by a
+copyright notice that is included in or attached to the work
+(an example is provided in the Appendix below).
+
+"Derivative Works" shall mean any work, whether in Source or Object
+form, that is based on (or derived from) the Work and for which the
+editorial revisions, annotations, elaborations, or other modifications
+represent, as a whole, an original work of authorship. For the purposes
+of this License, Derivative Works shall not include works that remain
+separable from, or merely link (or bind by name) to the interfaces of,
+the Work and Derivative Works thereof.
+
+"Contribution" shall mean any work of authorship, including
+the original version of the Work and any modifications or additions
+to that Work or Derivative Works thereof, that is intentionally
+submitted to Licensor for inclusion in the Work by the copyright owner
+or by an individual or Legal Entity authorized to submit on behalf of
+the copyright owner. For the purposes of this definition, "submitted"
+means any form of electronic, verbal, or written communication sent
+to the Licensor or its representatives, including but not limited to
+communication on electronic mailing lists, source code control systems,
+and issue tracking systems that are managed by, or on behalf of, the
+Licensor for the purpose of discussing and improving the Work, but
+excluding communication that is conspicuously marked or otherwise
+designated in writing by the copyright owner as "Not a Contribution."
+
+"Contributor" shall mean Licensor and any individual or Legal Entity
+on behalf of whom a Contribution has been received by Licensor and
+subsequently incorporated within the Work.
+
+2. Grant of Copyright License. Subject to the terms and conditions of
+this License, each Contributor hereby grants to You a perpetual,
+worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+copyright license to reproduce, prepare Derivative Works of,
+publicly display, publicly perform, sublicense, and distribute the
+Work and such Derivative Works in Source or Object form.
+
+3. Grant of Patent License. Subject to the terms and conditions of
+this License, each Contributor hereby grants to You a perpetual,
+worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+(except as stated in this section) patent license to make, have made,
+use, offer to sell, sell, import, and otherwise transfer the Work,
+where such license applies only to those patent claims licensable
+by such Contributor that are necessarily infringed by their
+Contribution(s) alone or by combination of their Contribution(s)
+with the Work to which such Contribution(s) was submitted. If You
+institute patent litigation against any entity (including a
+cross-claim or counterclaim in a lawsuit) alleging that the Work
+or a Contribution incorporated within the Work constitutes direct
+or contributory patent infringement, then any patent licenses
+granted to You under this License for that Work shall terminate
+as of the date such litigation is filed.
+
+4. Redistribution. You may reproduce and distribute copies of the
+Work or Derivative Works thereof in any medium, with or without
+modifications, and in Source or Object form, provided that You
+meet the following conditions:
+
+(a) You must give any other recipients of the Work or
+Derivative Works a copy of this License; and
+
+(b) You must cause any modified files to carry prominent notices
+stating that You changed the files; and
+
+(c) You must retain, in the Source form of any Derivative Works
+that You distribute, all copyright, patent, trademark, and
+attribution notices from the Source form of the Work,
+excluding those notices that do not pertain to any part of
+the Derivative Works; and
+
+(d) If the Work includes a "NOTICE" text file as part of its
+distribution, then any Derivative Works that You distribute must
+include a readable copy of the attribution notices contained
+within such NOTICE file, excluding those notices that do not
+pertain to any part of the Derivative Works, in at least one
+of the following places: within a NOTICE text file distributed
+as part of the Derivative Works; within the Source form or
+documentation, if provided along with the Derivative Works; or,
+within a display generated by the Derivative Works, if and
+wherever such third-party notices normally appear. The contents
+of the NOTICE file are for informational purposes only and
+do not modify the License. You may add Your own attribution
+notices within Derivative Works that You distribute, alongside
+or as an addendum to the NOTICE text from the Work, provided
+that such additional attribution notices cannot be construed
+as modifying the License.
+
+You may add Your own copyright statement to Your modifications and
+may provide additional or different license terms and conditions
+for use, reproduction, or distribution of Your modifications, or
+for any such Derivative Works as a whole, provided Your use,
+reproduction, and distribution of the Work otherwise complies with
+the conditions stated in this License.
+
+5. Submission of Contributions. Unless You explicitly state otherwise,
+any Contribution intentionally submitted for inclusion in the Work
+by You to the Licensor shall be under the terms and conditions of
+this License, without any additional terms or conditions.
+Notwithstanding the above, nothing herein shall supersede or modify
+the terms of any separate license agreement you may have executed
+with Licensor regarding such Contributions.
+
+6. Trademarks. This License does not grant permission to use the trade
+names, trademarks, service marks, or product names of the Licensor,
+except as required for reasonable and customary use in describing the
+origin of the Work and reproducing the content of the NOTICE file.
+
+7. Disclaimer of Warranty. Unless required by applicable law or
+agreed to in writing, Licensor provides the Work (and each
+Contributor provides its Contributions) on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+implied, including, without limitation, any warranties or conditions
+of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+PARTICULAR PURPOSE. You are solely responsible for determining the
+appropriateness of using or redistributing the Work and assume any
+risks associated with Your exercise of permissions under this License.
+
+8. Limitation of Liability. In no event and under no legal theory,
+whether in tort (including negligence), contract, or otherwise,
+unless required by applicable law (such as deliberate and grossly
+negligent acts) or agreed to in writing, shall any Contributor be
+liable to You for damages, including any direct, indirect, special,
+incidental, or consequential damages of any character arising as a
+result of this License or out of the use or inability to use the
+Work (including but not limited to damages for loss of goodwill,
+work stoppage, computer failure or malfunction, or any and all
+other commercial damages or losses), even if such Contributor
+has been advised of the possibility of such damages.
+
+9. Accepting Warranty or Additional Liability. While redistributing
+the Work or Derivative Works thereof, You may choose to offer,
+and charge a fee for, acceptance of support, warranty, indemnity,
+or other liability obligations and/or rights consistent with this
+License. However, in accepting such obligations, You may act only
+on Your own behalf and on Your sole responsibility, not on behalf
+of any other Contributor, and only if You agree to indemnify,
+defend, and hold each Contributor harmless for any liability
+incurred by, or claims asserted against, such Contributor by reason
+of your accepting any such warranty or additional liability.
+
+END OF TERMS AND CONDITIONS
+
+APPENDIX: How to apply the Apache License to your work.
+
+To apply the Apache License to your work, attach the following
+boilerplate notice, with the fields enclosed by brackets "[]"
+replaced with your own identifying information. (Don't include
+the brackets!) The text should be enclosed in the appropriate
+comment syntax for the file format. We also recommend that a
+file or class name and description of purpose be included on the
+same "printed page" as the copyright notice for easier
+identification within third-party archives.
+
+Copyright [yyyy] [name of copyright owner]
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+                        
+
+
+
+ +
+
+
+ +
+
+
+
+

MIT License

+
+Copyright (c) 2014-2021 AdminLTE.IO
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+                        
+
+
+
+
+
+
+{% endblock %} + + diff --git a/server/django/sensordata/templates/pending_communication_dashboard.html b/server/django/sensordata/templates/pending_communication_dashboard.html new file mode 100644 index 00000000..1d0e5586 --- /dev/null +++ b/server/django/sensordata/templates/pending_communication_dashboard.html @@ -0,0 +1,24 @@ +{% extends 'base.html' %} +{% block content %} +

Pending Communication

+ + + + + + + + + + + {% for operation in pending_operations %} + + + + + + + {% endfor %} + +
DeviceCommandStatusTimestamp
{{ operation.resource.device.endpoint }}{{ operation.operation_type }}{{ operation.status }}{{ operation.timestamp_sent }}
+{% endblock %} diff --git a/server/django/sensordata/templates/registration/login.html b/server/django/sensordata/templates/registration/login.html new file mode 100644 index 00000000..93c8d8bf --- /dev/null +++ b/server/django/sensordata/templates/registration/login.html @@ -0,0 +1,23 @@ +{% extends 'base_login.html' %} +{% load static %} + +{% block content %} +
+
+ +

Login

+
+ {% csrf_token %} +
+ {{ form.username.label_tag }} + {{ form.username }} +
+
+ {{ form.password.label_tag }} + {{ form.password }} +
+ +
+
+
+{% endblock %} diff --git a/server/django/sensordata/urls.py b/server/django/sensordata/urls.py index d061220c..11fb2d1e 100644 --- a/server/django/sensordata/urls.py +++ b/server/django/sensordata/urls.py @@ -5,10 +5,10 @@ # from django.urls import path -from .views import PostSingleResourceView, PostCompositeResourceView - +from .views import PostSingleResourceView, PostCompositeResourceView, ResourceDataView urlpatterns = [ + path('data//', ResourceDataView.as_view(), name='resource-data'), path('resource/single', PostSingleResourceView.as_view(), name='post-single-resource'), path('resource/composite', PostCompositeResourceView.as_view(), name='post-composite-resource'), ] diff --git a/server/django/sensordata/views.py b/server/django/sensordata/views.py index e3f3f01f..041588a7 100644 --- a/server/django/sensordata/views.py +++ b/server/django/sensordata/views.py @@ -3,19 +3,21 @@ # # SPDX-License-Identifier: Apache-2.0 # - +import traceback, logging +from rest_framework.generics import ListAPIView from rest_framework.views import APIView from rest_framework.response import Response from rest_framework import status +from django.shortcuts import render +from django.contrib.auth.decorators import login_required +from .models import Endpoint, Resource, Event, EndpointOperation, Firmware, ResourceType from rest_framework.exceptions import ValidationError from .serializers.single_resource_serializer import SingleResourceSerializer from .serializers.composite_resource_serializer import CompositeResourceSerializer -import traceback -import logging +from .serializers.generic_resource_serializer import GenericResourceSerializer logger = logging.getLogger(__name__) - class PostSingleResourceView(APIView): serializer_class = SingleResourceSerializer @@ -45,3 +47,58 @@ def post(self, request): logger.error("Request data: %s", request.data) logger.error("Backtrace: %s", traceback.format_exc()) return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) + +class ResourceDataView(ListAPIView): + serializer_class = GenericResourceSerializer + + def get_queryset(self): + resource_name = self.kwargs.get('resource_name') + logger.debug(f"Received request for resource type: {resource_name}") + try: + resource_type = ResourceType.objects.get(name=resource_name) + logger.debug(f"Found resource type: {resource_type}") + return Resource.objects.filter(resource_type=resource_type) + except ResourceType.DoesNotExist: + logger.error(f"Resource type {resource_name} not found") + return Response({"error": f"{resource_name} resource type not found"}, status=status.HTTP_404_NOT_FOUND) + + def get(self, request, *args, **kwargs): + resource_name = self.kwargs.get('resource_name') + queryset = self.get_queryset() + + if not queryset.exists(): + logger.error(f"Resource type {resource_name} not found") + return Response({"error": f"{resource_name} resource type not found"}, status=status.HTTP_404_NOT_FOUND) + + logger.debug(f"{resource_name.capitalize()} resources: {queryset}") + serializer = self.get_serializer(queryset, many=True) + logger.debug(f"{resource_name.capitalize()} data: {serializer.data}") + return Response(serializer.data, status=status.HTTP_200_OK) + +@login_required +def license_dashboard_view(request): + return render(request, 'license.html', {'title': 'Project Information and Licenses'}) + +@login_required +def admin_dashboard_view(request): + devices = Endpoint.objects.all() + return render(request, 'admin_dashboard.html', {'devices': devices, 'title': 'Admin Dashboard'}) + +@login_required +def firmware_dashboard_view(request): + firmware_updates = Firmware.objects.all() + return render(request, 'firmware_dashboard.html', {'firmware_updates': firmware_updates, 'title': 'Firmware Update Dashboard'}) + +@login_required +def event_dashboard_view(request): + events = Event.objects.all() + return render(request, 'event_dashboard.html', {'events': events, 'title': 'Event Dashboard'}) + +@login_required +def graph_dashboard_view(request): + return render(request, 'graph_dashboard.html', {'title': 'Graph Dashboard'}) + +@login_required +def pending_communication_dashboard_view(request): + pending_operations = EndpointOperation.objects.filter(status='QUEUED') + return render(request, 'pending_communication_dashboard.html', {'pending_operations': pending_operations, 'title': 'Pending Communication'}) diff --git a/server/django/server/settings.py b/server/django/server/settings.py index 6835572b..249f73c9 100644 --- a/server/django/server/settings.py +++ b/server/django/server/settings.py @@ -96,7 +96,7 @@ "django.contrib.auth.middleware.AuthenticationMiddleware", "django.contrib.messages.middleware.MessageMiddleware", "django.middleware.clickjacking.XFrameOptionsMiddleware", - 'whitenoise.middleware.WhiteNoiseMiddleware', # Add this + 'whitenoise.middleware.WhiteNoiseMiddleware', ] REST_FRAMEWORK = { @@ -187,16 +187,11 @@ USE_TZ = True -# Static files (CSS, JavaScript, Images) -# https://docs.djangoproject.com/en/5.0/howto/static-files/ - -# The absolute path to the directory where collectstatic will collect static -# files for deployment. For simplicity, you can set it to be within your -# project directory. -STATIC_URL = 'static/' -STATIC_ROOT = os.path.join(BASE_DIR, 'staticfiles') - -# Default primary key field type -# https://docs.djangoproject.com/en/5.0/ref/settings/#default-auto-field +STATIC_URL = '/static/' +STATIC_ROOT = BASE_DIR / 'static_collect' +STATICFILES_DIRS = [ BASE_DIR / 'staticfiles'] +WHITENOISE_USE_FINDERS = True DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField" +LOGIN_REDIRECT_URL = '/admin_dashboard/' +LOGOUT_REDIRECT_URL = '/accounts/login/' diff --git a/server/django/server/urls.py b/server/django/server/urls.py index 64b55f05..048b4161 100644 --- a/server/django/server/urls.py +++ b/server/django/server/urls.py @@ -6,12 +6,21 @@ from django.contrib import admin from django.urls import include, path +from sensordata import views from django.conf.urls.static import static from django.conf import settings urlpatterns = [ path('admin/', admin.site.urls), path('leshan_api/', include('sensordata.urls')), + path('admin_dashboard/', views.admin_dashboard_view, name='admin_dashboard'), + path('firmware_dashboard/', views.firmware_dashboard_view, name='firmware_dashboard'), + path('event_dashboard/', views.event_dashboard_view, name='event_dashboard'), + path('graph_dashboard/', views.graph_dashboard_view, name='graph_dashboard'), + path('pending_communication_dashboard/', views.pending_communication_dashboard_view, name='pending_communication_dashboard'), + path('license/', views.license_dashboard_view, name='license_dashboard'), + path('accounts/', include('django.contrib.auth.urls')), + path('', views.admin_dashboard_view, name='default'), ] # Serve static firmware files diff --git a/server/django/staticfiles/css/style.css b/server/django/staticfiles/css/style.css new file mode 100644 index 00000000..86934bac --- /dev/null +++ b/server/django/staticfiles/css/style.css @@ -0,0 +1,46 @@ +.login-form { + width: 100%; + max-width: 400px; + padding: 30px; + background: #ffffff; + border-radius: 10px; + box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); + border: 1px solid #ddd; +} + +.login-form h1 { + text-align: center; + margin-bottom: 20px; + font-size: 24px; + font-weight: 700; +} + +.login-form .form-group { + margin-bottom: 20px; +} + +.login-form .form-control { + border-radius: 5px; + padding: 10px; + border: 1px solid #ccc; +} + +.login-form .btn-primary { + width: 100%; + padding: 10px; + border-radius: 5px; + font-size: 16px; + font-weight: 700; + background-color: #007bff; + border: none; +} + +.login-form .btn-primary:hover { + background-color: #0056b3; +} + +.login-form .form-text { + text-align: center; + font-size: 14px; + color: #666; +} diff --git a/server/django/staticfiles/img/flownexus_logo_dark.svg b/server/django/staticfiles/img/flownexus_logo_dark.svg new file mode 100644 index 00000000..ccdf4da3 --- /dev/null +++ b/server/django/staticfiles/img/flownexus_logo_dark.svg @@ -0,0 +1,112 @@ + + + + + + + + + + + + + + + + + + + diff --git a/server/django/staticfiles/img/flownexus_logo_gray.svg b/server/django/staticfiles/img/flownexus_logo_gray.svg new file mode 100644 index 00000000..1f77cfaf --- /dev/null +++ b/server/django/staticfiles/img/flownexus_logo_gray.svg @@ -0,0 +1,112 @@ + + + + + + + + + + + + + + + + + + + diff --git a/server/django/staticfiles/img/flownexus_logo_light.svg b/server/django/staticfiles/img/flownexus_logo_light.svg new file mode 100644 index 00000000..1c25d815 --- /dev/null +++ b/server/django/staticfiles/img/flownexus_logo_light.svg @@ -0,0 +1,112 @@ + + + + + + + + + + + + + + + + + + +