-
Notifications
You must be signed in to change notification settings - Fork 23
Update Admin Panel to work via iframes #799
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 49 commits
d238b5b
3a867d2
8577796
dc55709
7db0687
3fb5d51
4eb5e15
d545a8b
1099bf4
c0c8983
f20e2a6
98baeba
d016323
35a1c3e
e64d4c7
b007f38
3be7d48
357fa37
35d1a44
b6cd4b4
9fd0a2c
93bf978
eedd693
8b21202
3e21134
3c6cc25
e79ad04
2fde80d
d2f516e
52db0e0
27b0919
75c25a3
5409788
4358d7c
149637e
3564bf2
6d37aa9
69ace4c
63dded2
f52d1cb
8d37570
dee6453
eb9e896
2a65892
da6abd4
33c38bb
c46f4dd
307222d
820893a
7ec13d0
f42e5d7
2058a85
09cba15
57e5908
f3b67a6
764eec1
78a2221
c4825dc
c0dcf94
6051f88
d5eb255
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,17 +1,19 @@ | ||
from django.contrib import admin | ||
from django.urls import path | ||
from django.urls import path, re_path | ||
|
||
from meshapi.admin.password_reset import ( | ||
AdminPasswordResetCompleteView, | ||
AdminPasswordResetConfirmView, | ||
AdminPasswordResetDoneView, | ||
AdminPasswordResetView, | ||
) | ||
from meshdb.views import admin_iframe_view | ||
|
||
urlpatterns = [ | ||
path("password_reset/", AdminPasswordResetView.as_view(), name="admin_password_reset"), | ||
path("password_reset/done/", AdminPasswordResetDoneView.as_view(), name="password_reset_done"), | ||
path("password_reset/<uidb64>/<token>/", AdminPasswordResetConfirmView.as_view(), name="password_reset_confirm"), | ||
path("password_reset/done/", AdminPasswordResetCompleteView.as_view(), name="password_reset_complete"), | ||
re_path(r"^panel/.*$", admin_iframe_view), # Match any other /admin/* URL | ||
path("", admin.site.urls), | ||
] |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,107 @@ | ||
import datetime | ||
|
||
from bs4 import BeautifulSoup | ||
from django.contrib.auth.models import Group, User | ||
from django.test import Client, TestCase | ||
from rest_framework.authtoken.models import TokenProxy | ||
|
||
from meshapi.models import LOS, AccessPoint, Building, Device, Install, Link, Member, Node, Sector | ||
from meshapi.tests.sample_data import sample_building, sample_device, sample_install, sample_member, sample_node | ||
from meshapi_hooks.hooks import CelerySerializerHook | ||
|
||
|
||
class TestAdminPanel(TestCase): | ||
c = Client() | ||
|
||
def setUp(self) -> None: | ||
sample_install_copy = sample_install.copy() | ||
self.building_1 = Building(**sample_building) | ||
self.building_1.save() | ||
sample_install_copy["building"] = self.building_1 | ||
|
||
self.building_2 = Building(**sample_building) | ||
self.building_2.save() | ||
|
||
self.los = LOS( | ||
from_building=self.building_1, | ||
to_building=self.building_2, | ||
analysis_date=datetime.date(2024, 1, 1), | ||
source=LOS.LOSSource.HUMAN_ANNOTATED, | ||
) | ||
self.los.save() | ||
|
||
self.member = Member(**sample_member) | ||
self.member.save() | ||
sample_install_copy["member"] = self.member | ||
|
||
self.install = Install(**sample_install_copy) | ||
self.install.save() | ||
|
||
self.node1 = Node(**sample_node) | ||
self.node1.save() | ||
self.node2 = Node(**sample_node) | ||
self.node2.save() | ||
|
||
self.device1 = Device(**sample_device) | ||
self.device1.node = self.node1 | ||
self.device1.save() | ||
|
||
self.device2 = Device(**sample_device) | ||
self.device2.node = self.node2 | ||
self.device2.save() | ||
|
||
self.sector = Sector( | ||
radius=1, | ||
azimuth=45, | ||
width=180, | ||
**sample_device, | ||
) | ||
self.sector.node = self.node2 | ||
self.sector.save() | ||
|
||
self.access_point = AccessPoint( | ||
**sample_device, | ||
latitude=0, | ||
longitude=0, | ||
) | ||
self.access_point.node = self.node2 | ||
self.access_point.save() | ||
|
||
self.link = Link( | ||
from_device=self.device1, | ||
to_device=self.device2, | ||
status=Link.LinkStatus.ACTIVE, | ||
) | ||
self.link.save() | ||
|
||
self.admin_user = User.objects.create_superuser( | ||
username="admin", password="admin_password", email="[email protected]" | ||
) | ||
self.c.login(username="admin", password="admin_password") | ||
|
||
self.test_group = Group.objects.create(name="Test group") | ||
|
||
self.test_auth_token = TokenProxy.objects.create(user=self.admin_user) | ||
|
||
self.test_webhook = CelerySerializerHook.objects.create( | ||
user=self.admin_user, target="http://example.com", event="building.created", headers="" | ||
) | ||
|
||
def test_iframe_loads(self): | ||
route = "/admin/panel/" | ||
code = 200 | ||
response = self.c.get(route) | ||
self.assertEqual(code, response.status_code, f"Could not view {route} in the admin panel.") | ||
|
||
decoded_panel = response.content.decode() | ||
soup = BeautifulSoup(decoded_panel, "html.parser") | ||
iframe = soup.find(id="admin_panel_iframe") | ||
iframe_src = iframe.attrs["src"] | ||
self.assertEqual("/admin/", iframe_src) | ||
iframe_response = self.c.get(iframe_src) | ||
self.assertEqual(code, iframe_response.status_code, f"Could not view {route} in the admin panel.") | ||
|
||
# TODO (wdn): Add more tests checking if navigating to xyz page works | ||
# Unfortunately, because that is a lot of javascript, it's tricky to test. | ||
# It may be possible to run selenium integration tests or something to validate | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'd support selenium-based tests for integration testing deployed copies of the application (dev, gamma, etc) but probably we don't want it in our unit testing pipeline, those things are slow enough already There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Probably not in our unit testing pipeline, no. Are you OK with me handling this in a separate PR or would you like me to take a crack at a solution for this here? |
||
# that functionality |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,81 @@ | ||
{% load static %} | ||
|
||
{% load env_extras %} | ||
|
||
<!DOCTYPE html> | ||
<html> | ||
<head> | ||
<script> | ||
const MAP_BASE_URL = "{% get_env_var 'ADMIN_MAP_BASE_URL' %}"; | ||
const PANEL_URL = "/admin/panel/"; | ||
</script> | ||
<!--We don't want people navigating to this URL, so we're gonna redirect them | ||
if they find themselves here--> | ||
<script src="{% static 'admin/panel_url_check.js' %}"></script> | ||
<!--Script that powers this iframed view and communicates between the admin panel | ||
and the map--> | ||
<script src="{% static '/admin/map.js' %}" defer></script> | ||
<title>{% block title %}{% endblock %}</title> | ||
<link rel="stylesheet" href="{% block stylesheet %}{% static "admin/css/base.css" %}{% endblock %}"> | ||
{% block dark-mode-vars %} | ||
<link rel="stylesheet" href="{% static "admin/css/dark_mode.css" %}"> | ||
<script src="{% static "admin/js/theme.js" %}" defer></script> | ||
{% endblock %} | ||
{% if not is_popup and is_nav_sidebar_enabled %} | ||
<link rel="stylesheet" href="{% static "admin/css/nav_sidebar.css" %}"> | ||
<script src="{% static 'admin/js/nav_sidebar.js' %}" defer></script> | ||
{% endif %} | ||
{% block extrastyle %}{% endblock %} | ||
<link rel="stylesheet" href="{% static "admin/admin_ext.css" %}"> | ||
{% if LANGUAGE_BIDI %}<link rel="stylesheet" href="{% block stylesheet_rtl %}{% static "admin/css/rtl.css" %}{% endblock %}">{% endif %} | ||
{% block extrahead %}{% endblock %} | ||
{% block responsive %} | ||
<meta name="viewport" content="user-scalable=no, width=device-width, initial-scale=1.0, maximum-scale=1.0"> | ||
<link rel="stylesheet" href="{% static "admin/css/responsive.css" %}"> | ||
{% if LANGUAGE_BIDI %}<link rel="stylesheet" href="{% static "admin/css/responsive_rtl.css" %}">{% endif %} | ||
{% endblock %} | ||
{% block blockbots %}<meta name="robots" content="NONE,NOARCHIVE">{% endblock %} | ||
<link rel="stylesheet" href="{% static '/admin/iframed.css'%}"/> | ||
</head> | ||
<body> | ||
<div id="page_container"> | ||
<div id="admin_panel_div" class="frameGrow"> | ||
<iframe src="/admin/" id="admin_panel_iframe" class="frameGrow"></iframe> | ||
</div> | ||
|
||
<div class="floating-button-above"> | ||
<a href="#" class="button" style="display: inline-block" id="show_map_button"> | ||
<img src="{% static '/admin/map/img/map.png' %}" height="16px" title="Show Map"> | ||
</a> | ||
</div> | ||
|
||
<div id="map_controls"> | ||
<!-- This handle is always visible, unless you're resizing, in which case | ||
goes big and invisible to block the iframes from stealing focus --> | ||
<div class="handle" id="handle"> | ||
<span class="vert-align-helper"></span> | ||
<img class="handlebar" id="handlebar" src="{% static '/admin/map/img/handlebar.svg' %}" height="60px"/> | ||
</div> | ||
<!-- Only shows up during resizes --> | ||
<div class="handle hidden" id="substituteHandle"> | ||
<span class="vert-align-helper"></span> | ||
<img class="handlebar" id="substituteHandlebar" src="{% static '/admin/map/img/handlebar.svg' %}" height="60px"/> | ||
</div> | ||
<div class="floating-button"> | ||
<a href="#" class="button" style="display: inline-block" id="map_hide_button"> | ||
<img src="{% static '/admin/map/img/cross.png' %}" height="24px" title="Hide Map"> | ||
</a> | ||
</div> | ||
<div class="floating-button-below"> | ||
<a href="#" class="button" style="display: inline-block" id="map_recenter_button"> | ||
<img src="{% static '/admin/map/img/recenter.png' %}" height="24px" title="Recenter map"> | ||
</a> | ||
</div> | ||
</div> | ||
|
||
<div id="map_panel_div"> | ||
<iframe src="{% get_env_var 'ADMIN_MAP_BASE_URL' %}" id="map_panel"></iframe> | ||
</div> | ||
</div> | ||
</body> | ||
</html> |
This file was deleted.
This file was deleted.
This file was deleted.
This file was deleted.
This file was deleted.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
from django.contrib.admin.views.decorators import staff_member_required | ||
from django.http import HttpRequest, HttpResponse | ||
from django.shortcuts import render | ||
|
||
|
||
@staff_member_required | ||
def admin_iframe_view(request: HttpRequest) -> HttpResponse: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Might want to add a test for this authentication/redirect |
||
return render(request, "admin/iframed.html") |
Uh oh!
There was an error while loading. Please reload this page.