Vessel location, minor tweaks

This commit is contained in:
Kumi 2022-09-18 16:45:35 +00:00
parent de34f1daf9
commit 3c8c0dce25
Signed by: kumi
GPG key ID: ECBCC9082395383F
23 changed files with 984 additions and 12 deletions

View file

@ -19,6 +19,7 @@ SECRET_KEY = ASK.secret_key
# SECURITY WARNING: don't run with debug turned on in production! # SECURITY WARNING: don't run with debug turned on in production!
DEBUG = ASK.config.getboolean("ACADEMON", "Debug", fallback=False) DEBUG = ASK.config.getboolean("ACADEMON", "Debug", fallback=False)
CRISPY_FAIL_SILENTLY = not DEBUG
ADMINS = [(ASK.config.get("ACADEMON", "AdminName"), ASK.config.get("ACADEMON", "AdminEmail"))] ADMINS = [(ASK.config.get("ACADEMON", "AdminName"), ASK.config.get("ACADEMON", "AdminEmail"))]
@ -35,6 +36,7 @@ INSTALLED_APPS = [
'django.contrib.messages', 'django.contrib.messages',
'django.contrib.staticfiles', 'django.contrib.staticfiles',
'ajax_datatable', 'ajax_datatable',
'crispy_forms',
'dbsettings', 'dbsettings',
'core', 'core',
] ]

View file

@ -1,8 +1,13 @@
from django.urls import path, include from django.urls import path, include
from .views.urls import VesselsDataTableURLView from .views.urls import VesselsDataTableURLView, VesselsDeleteURLView, VesselsEditURLView, StaticsURLView
from .views.vessels import VesselsLocationView
urlpatterns = [ urlpatterns = [
path("urls/datatable/vessels/", VesselsDataTableURLView.as_view(), name=""), path("urls/datatable/vessels/", VesselsDataTableURLView.as_view(), name=""),
path("urls/datatable/vessels/edit/<str:id>/", VesselsDataTableURLView.as_view(), name=""),
path("urls/datatable/vessels/delete/<str:id>/", VesselsDataTableURLView.as_view(), name=""),
path("urls/static/", StaticsURLView.as_view(), name=""),
path("vessels/location/<str:id>/", VesselsLocationView.as_view(), name=""),
] ]

View file

@ -2,8 +2,21 @@ from django.views import View
from django.http import JsonResponse from django.http import JsonResponse
from django.contrib.auth.mixins import LoginRequiredMixin from django.contrib.auth.mixins import LoginRequiredMixin
from django.urls import reverse_lazy from django.urls import reverse_lazy
from django.templatetags.static import static
class VesselsDataTableURLView(LoginRequiredMixin, View): class VesselsDataTableURLView(LoginRequiredMixin, View):
def get(self, request, *args, **kwargs): def get(self, request, *args, **kwargs):
return JsonResponse(reverse_lazy("core:admin_vessels_datatable"), safe=False) return JsonResponse(reverse_lazy("core:admin_vessels_datatable"), safe=False)
class VesselsEditURLView(LoginRequiredMixin, View):
def get(self, request, *args, **kwargs):
return JsonResponse(reverse_lazy("core:admin_vessels_edit", kwargs["id"]), safe=False)
class VesselsDeleteURLView(LoginRequiredMixin, View):
def get(self, request, *args, **kwargs):
return JsonResponse(reverse_lazy("core:admin_vessels_delete", kwargs["id"]), safe=False)
class StaticsURLView(View):
def get(self, request, *args, **kwargs):
return JsonResponse(static(request.GET["file"]), safe=False)

50
api/views/vessels.py Normal file
View file

@ -0,0 +1,50 @@
from django.views import View
from django.http import JsonResponse
from core.mixins.auth import StaffRequiredMixin
from core.models.vessel import Vessel
from pycruisemapper import CruiseMapper
class VesselsLocationView(StaffRequiredMixin, View):
def get(self, request, *args, **kwargs):
try:
vessel = Vessel.objects.get(id=kwargs["id"])
try:
ship = vessel.query_cruisemapper()
assert ship.location.latitude and ship.location.longitude
data = {
"status": "success",
"name": ship.name,
"location": ship.location.__dict__,
"destination": ship.destination,
}
except AssertionError:
data = {
"status": "error",
"error": f"CruiseMapper does not seem to know the location of {vessel.name}."
}
except Exception as e:
data = {
"status": "error",
"error": f"Something went wrong fetching data from CruiseMapper: {str(e)}"
}
except Vessel.DoesNotExist:
data = {
"status": "error",
"error": f"No Vessel object with ID {kwargs['id']} found"
}
except Exception as e:
data = {
"status": "error",
"error": f"Something unexpected went wrong: {str(e)}"
}
return JsonResponse(data)

View file

@ -0,0 +1,23 @@
# Generated by Django 4.1.1 on 2022-09-18 04:56
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('core', '0005_alter_profile_first_name_alter_profile_last_name'),
]
operations = [
migrations.AlterField(
model_name='vessel',
name='imo',
field=models.IntegerField(blank=True, null=True, verbose_name='IMO'),
),
migrations.AlterField(
model_name='vessel',
name='mmsi',
field=models.IntegerField(blank=True, null=True, verbose_name='MMSI'),
),
]

View file

@ -5,8 +5,8 @@ from pycruisemapper.classes import CruiseMapper, Ship
class Vessel(models.Model): class Vessel(models.Model):
name = models.CharField(max_length=64) name = models.CharField(max_length=64)
imo = models.IntegerField(null=True, blank=True) imo = models.IntegerField("IMO", null=True, blank=True)
mmsi = models.IntegerField(null=True, blank=True) mmsi = models.IntegerField("MMSI", null=True, blank=True)
def query_cruisemapper(self) -> Ship: def query_cruisemapper(self) -> Ship:
cm = CruiseMapper() cm = CruiseMapper()

View file

@ -1,11 +1,12 @@
from django.urls import path from django.urls import path
from ..views.frontend import NotImplementedView from ..views.frontend import NotImplementedView
from ..views.admin.vessels import AdminVesselsListView, AdminVesselsListDataTableView from ..views.admin.vessels import AdminVesselsListView, AdminVesselsListDataTableView, AdminVesselsCreateView
urlpatterns = [ urlpatterns = [
path("admin/", NotImplementedView.as_view(), name="admin"), path("admin/", NotImplementedView.as_view(), name="admin"),
path("vessels/", AdminVesselsListView.as_view(), name="admin_vessels"), path("vessels/", AdminVesselsListView.as_view(), name="admin_vessels"),
path("vessels/new/", AdminVesselsCreateView.as_view(), name="admin_vessels_create"),
path("vessels/datatable/", AdminVesselsListDataTableView.as_view(), name="admin_vessels_datatable"), path("vessels/datatable/", AdminVesselsListDataTableView.as_view(), name="admin_vessels_datatable"),
] ]

View file

@ -1,16 +1,21 @@
from django.views.generic import ListView, TemplateView from django.views.generic import TemplateView, UpdateView, DeleteView, CreateView
from django.urls import reverse_lazy
from ajax_datatable.views import AjaxDatatableView from ajax_datatable.views import AjaxDatatableView
from crispy_forms.helper import FormHelper
from crispy_forms.layout import Submit
from ...models.vessel import Vessel from ...models.vessel import Vessel
from ...mixins.frontend import TitleMixin
from ...mixins.auth import SuperuserRequiredMixin from ...mixins.auth import SuperuserRequiredMixin
class AdminVesselsListView(SuperuserRequiredMixin, TemplateView): class AdminVesselsListView(TitleMixin, SuperuserRequiredMixin, TemplateView):
title = "Vessels"
template_name = "core/admin/vessels_list.html" template_name = "core/admin/vessels_list.html"
class AdminVesselsListDataTableView(AjaxDatatableView): class AdminVesselsListDataTableView(SuperuserRequiredMixin, AjaxDatatableView):
model = Vessel model = Vessel
title = 'Vessels' title = 'Vessels'
initial_order = [["name", "asc"], ] initial_order = [["name", "asc"], ]
@ -23,4 +28,62 @@ class AdminVesselsListDataTableView(AjaxDatatableView):
{'name': 'name', 'visible': True, }, {'name': 'name', 'visible': True, },
{'name': 'imo', 'visible': True, }, {'name': 'imo', 'visible': True, },
{'name': 'mmsi', 'visible': True, }, {'name': 'mmsi', 'visible': True, },
{'name': 'edit', 'title': 'Options', 'placeholder': True, 'searchable': False, 'orderable': False, },
] ]
def customize_row(self, row, obj):
row['edit'] = f"""
<a href="#" class="btn btn-info btn-edit"
onclick="editVessel(this); return false;">
<i class="fas fa-pencil"></i> Edit
</a>
<a href="#" class="btn btn-danger"
onclick="deleteVessel(this); return false;">
<i class="fas fa-trash-can"></i> Delete
</a>
<a href="#" class="btn btn-success"
onclick="locateVessel(this); return false;">
<i class="fas fa-location-crosshairs"></i> Locate
</a>
"""
class AdminVesselsCreateView(TitleMixin, SuperuserRequiredMixin, CreateView):
model = Vessel
title = "Create Vessel"
template_name = "core/admin/vessels_new.html"
fields = ["name", "imo", "mmsi"]
def get_form(self, form_class=None):
form = super().get_form(form_class)
form.helper = FormHelper()
form.helper.add_input(Submit('submit', 'Create', css_class='btn-primary'))
return form
def get_success_url(self):
return reverse_lazy("core:admin_vessels")
class AdminVesselsUpdateView(TitleMixin, SuperuserRequiredMixin, UpdateView):
model = Vessel
title = "Edit Vessel"
template_name = "core/admin/vessels_edit.html"
fields = ["name", "imo", "mmsi"]
def get_form(self, form_class=None):
form = super().get_form(form_class)
form.helper = FormHelper()
form.helper.add_input(Submit('submit', 'Edit', css_class='btn-primary'))
return form
def get_success_url(self):
return reverse_lazy("core:admin_vessels")
class AdminVesselsDeleteView(TitleMixin, SuperuserRequiredMixin, DeleteView):
model = Vessel
title = "Delete Vessel"
template_name = "core/admin/vessels_delete.html"
def get_success_url(self):
return reverse_lazy("core:admin_vessels")

View file

@ -7,6 +7,7 @@ from django.views.decorators.cache import never_cache
from django.core.exceptions import PermissionDenied from django.core.exceptions import PermissionDenied
from django.http.response import HttpResponseRedirect from django.http.response import HttpResponseRedirect
from django.urls import reverse from django.urls import reverse
from django.contrib import messages
from ..models.auth import OTPSession, Profile from ..models.auth import OTPSession, Profile
from ..forms.auth import OTPForm from ..forms.auth import OTPForm
@ -38,6 +39,10 @@ class LoginView(DjangoLoginView):
return HttpResponseRedirect(self.get_otp_url()) return HttpResponseRedirect(self.get_otp_url())
def form_invalid(self, form):
messages.error(self.request, "The username or password you entered was incorrect. Please try again.")
return super().form_invalid(form)
class OTPView(DjangoLoginView): class OTPView(DjangoLoginView):
authentication_form = OTPForm authentication_form = OTPForm
@ -60,3 +65,7 @@ class OTPView(DjangoLoginView):
profile, _ = Profile.objects.get_or_create(user=user) profile, _ = Profile.objects.get_or_create(user=user)
profile.save() profile.save()
return HttpResponseRedirect(self.get_success_url()) return HttpResponseRedirect(self.get_success_url())
def form_invalid(self, form):
messages.error(self.request, "The One-Time Password you entered was incorrect. Please try again.")
return super().form_invalid(form)

View file

@ -6,6 +6,7 @@ django-storages
boto3 boto3
django-ajax-datatable django-ajax-datatable
pytz pytz
django-crispy-forms
dbsettings dbsettings
django-autosecretkey django-autosecretkey

657
static/core/dist/css/leaflet.css vendored Normal file
View file

@ -0,0 +1,657 @@
/* required styles */
.leaflet-pane,
.leaflet-tile,
.leaflet-marker-icon,
.leaflet-marker-shadow,
.leaflet-tile-container,
.leaflet-pane > svg,
.leaflet-pane > canvas,
.leaflet-zoom-box,
.leaflet-image-layer,
.leaflet-layer {
position: absolute;
left: 0;
top: 0;
}
.leaflet-container {
overflow: hidden;
}
.leaflet-tile,
.leaflet-marker-icon,
.leaflet-marker-shadow {
-webkit-user-select: none;
-moz-user-select: none;
user-select: none;
-webkit-user-drag: none;
}
/* Prevents IE11 from highlighting tiles in blue */
.leaflet-tile::selection {
background: transparent;
}
/* Safari renders non-retina tile on retina better with this, but Chrome is worse */
.leaflet-safari .leaflet-tile {
image-rendering: -webkit-optimize-contrast;
}
/* hack that prevents hw layers "stretching" when loading new tiles */
.leaflet-safari .leaflet-tile-container {
width: 1600px;
height: 1600px;
-webkit-transform-origin: 0 0;
}
.leaflet-marker-icon,
.leaflet-marker-shadow {
display: block;
}
/* .leaflet-container svg: reset svg max-width decleration shipped in Joomla! (joomla.org) 3.x */
/* .leaflet-container img: map is broken in FF if you have max-width: 100% on tiles */
.leaflet-container .leaflet-overlay-pane svg {
max-width: none !important;
max-height: none !important;
}
.leaflet-container .leaflet-marker-pane img,
.leaflet-container .leaflet-shadow-pane img,
.leaflet-container .leaflet-tile-pane img,
.leaflet-container img.leaflet-image-layer,
.leaflet-container .leaflet-tile {
max-width: none !important;
max-height: none !important;
width: auto;
padding: 0;
}
.leaflet-container.leaflet-touch-zoom {
-ms-touch-action: pan-x pan-y;
touch-action: pan-x pan-y;
}
.leaflet-container.leaflet-touch-drag {
-ms-touch-action: pinch-zoom;
/* Fallback for FF which doesn't support pinch-zoom */
touch-action: none;
touch-action: pinch-zoom;
}
.leaflet-container.leaflet-touch-drag.leaflet-touch-zoom {
-ms-touch-action: none;
touch-action: none;
}
.leaflet-container {
-webkit-tap-highlight-color: transparent;
}
.leaflet-container a {
-webkit-tap-highlight-color: rgba(51, 181, 229, 0.4);
}
.leaflet-tile {
filter: inherit;
visibility: hidden;
}
.leaflet-tile-loaded {
visibility: inherit;
}
.leaflet-zoom-box {
width: 0;
height: 0;
-moz-box-sizing: border-box;
box-sizing: border-box;
z-index: 800;
}
/* workaround for https://bugzilla.mozilla.org/show_bug.cgi?id=888319 */
.leaflet-overlay-pane svg {
-moz-user-select: none;
}
.leaflet-pane { z-index: 400; }
.leaflet-tile-pane { z-index: 200; }
.leaflet-overlay-pane { z-index: 400; }
.leaflet-shadow-pane { z-index: 500; }
.leaflet-marker-pane { z-index: 600; }
.leaflet-tooltip-pane { z-index: 650; }
.leaflet-popup-pane { z-index: 700; }
.leaflet-map-pane canvas { z-index: 100; }
.leaflet-map-pane svg { z-index: 200; }
.leaflet-vml-shape {
width: 1px;
height: 1px;
}
.lvml {
behavior: url(#default#VML);
display: inline-block;
position: absolute;
}
/* control positioning */
.leaflet-control {
position: relative;
z-index: 800;
pointer-events: visiblePainted; /* IE 9-10 doesn't have auto */
pointer-events: auto;
}
.leaflet-top,
.leaflet-bottom {
position: absolute;
z-index: 1000;
pointer-events: none;
}
.leaflet-top {
top: 0;
}
.leaflet-right {
right: 0;
}
.leaflet-bottom {
bottom: 0;
}
.leaflet-left {
left: 0;
}
.leaflet-control {
float: left;
clear: both;
}
.leaflet-right .leaflet-control {
float: right;
}
.leaflet-top .leaflet-control {
margin-top: 10px;
}
.leaflet-bottom .leaflet-control {
margin-bottom: 10px;
}
.leaflet-left .leaflet-control {
margin-left: 10px;
}
.leaflet-right .leaflet-control {
margin-right: 10px;
}
/* zoom and fade animations */
.leaflet-fade-anim .leaflet-popup {
opacity: 0;
-webkit-transition: opacity 0.2s linear;
-moz-transition: opacity 0.2s linear;
transition: opacity 0.2s linear;
}
.leaflet-fade-anim .leaflet-map-pane .leaflet-popup {
opacity: 1;
}
.leaflet-zoom-animated {
-webkit-transform-origin: 0 0;
-ms-transform-origin: 0 0;
transform-origin: 0 0;
}
svg.leaflet-zoom-animated {
will-change: transform;
}
.leaflet-zoom-anim .leaflet-zoom-animated {
-webkit-transition: -webkit-transform 0.25s cubic-bezier(0,0,0.25,1);
-moz-transition: -moz-transform 0.25s cubic-bezier(0,0,0.25,1);
transition: transform 0.25s cubic-bezier(0,0,0.25,1);
}
.leaflet-zoom-anim .leaflet-tile,
.leaflet-pan-anim .leaflet-tile {
-webkit-transition: none;
-moz-transition: none;
transition: none;
}
.leaflet-zoom-anim .leaflet-zoom-hide {
visibility: hidden;
}
/* cursors */
.leaflet-interactive {
cursor: pointer;
}
.leaflet-grab {
cursor: -webkit-grab;
cursor: -moz-grab;
cursor: grab;
}
.leaflet-crosshair,
.leaflet-crosshair .leaflet-interactive {
cursor: crosshair;
}
.leaflet-popup-pane,
.leaflet-control {
cursor: auto;
}
.leaflet-dragging .leaflet-grab,
.leaflet-dragging .leaflet-grab .leaflet-interactive,
.leaflet-dragging .leaflet-marker-draggable {
cursor: move;
cursor: -webkit-grabbing;
cursor: -moz-grabbing;
cursor: grabbing;
}
/* marker & overlays interactivity */
.leaflet-marker-icon,
.leaflet-marker-shadow,
.leaflet-image-layer,
.leaflet-pane > svg path,
.leaflet-tile-container {
pointer-events: none;
}
.leaflet-marker-icon.leaflet-interactive,
.leaflet-image-layer.leaflet-interactive,
.leaflet-pane > svg path.leaflet-interactive,
svg.leaflet-image-layer.leaflet-interactive path {
pointer-events: visiblePainted; /* IE 9-10 doesn't have auto */
pointer-events: auto;
}
/* visual tweaks */
.leaflet-container {
background: #ddd;
outline-offset: 1px;
}
.leaflet-container a {
color: #0078A8;
}
.leaflet-zoom-box {
border: 2px dotted #38f;
background: rgba(255,255,255,0.5);
}
/* general typography */
.leaflet-container {
font-family: "Helvetica Neue", Arial, Helvetica, sans-serif;
font-size: 12px;
font-size: 0.75rem;
line-height: 1.5;
}
/* general toolbar styles */
.leaflet-bar {
box-shadow: 0 1px 5px rgba(0,0,0,0.65);
border-radius: 4px;
}
.leaflet-bar a {
background-color: #fff;
border-bottom: 1px solid #ccc;
width: 26px;
height: 26px;
line-height: 26px;
display: block;
text-align: center;
text-decoration: none;
color: black;
}
.leaflet-bar a,
.leaflet-control-layers-toggle {
background-position: 50% 50%;
background-repeat: no-repeat;
display: block;
}
.leaflet-bar a:hover,
.leaflet-bar a:focus {
background-color: #f4f4f4;
}
.leaflet-bar a:first-child {
border-top-left-radius: 4px;
border-top-right-radius: 4px;
}
.leaflet-bar a:last-child {
border-bottom-left-radius: 4px;
border-bottom-right-radius: 4px;
border-bottom: none;
}
.leaflet-bar a.leaflet-disabled {
cursor: default;
background-color: #f4f4f4;
color: #bbb;
}
.leaflet-touch .leaflet-bar a {
width: 30px;
height: 30px;
line-height: 30px;
}
.leaflet-touch .leaflet-bar a:first-child {
border-top-left-radius: 2px;
border-top-right-radius: 2px;
}
.leaflet-touch .leaflet-bar a:last-child {
border-bottom-left-radius: 2px;
border-bottom-right-radius: 2px;
}
/* zoom control */
.leaflet-control-zoom-in,
.leaflet-control-zoom-out {
font: bold 18px 'Lucida Console', Monaco, monospace;
text-indent: 1px;
}
.leaflet-touch .leaflet-control-zoom-in, .leaflet-touch .leaflet-control-zoom-out {
font-size: 22px;
}
/* layers control */
.leaflet-control-layers {
box-shadow: 0 1px 5px rgba(0,0,0,0.4);
background: #fff;
border-radius: 5px;
}
.leaflet-control-layers-toggle {
background-image: url(images/layers.png);
width: 36px;
height: 36px;
}
.leaflet-retina .leaflet-control-layers-toggle {
background-image: url(images/layers-2x.png);
background-size: 26px 26px;
}
.leaflet-touch .leaflet-control-layers-toggle {
width: 44px;
height: 44px;
}
.leaflet-control-layers .leaflet-control-layers-list,
.leaflet-control-layers-expanded .leaflet-control-layers-toggle {
display: none;
}
.leaflet-control-layers-expanded .leaflet-control-layers-list {
display: block;
position: relative;
}
.leaflet-control-layers-expanded {
padding: 6px 10px 6px 6px;
color: #333;
background: #fff;
}
.leaflet-control-layers-scrollbar {
overflow-y: scroll;
overflow-x: hidden;
padding-right: 5px;
}
.leaflet-control-layers-selector {
margin-top: 2px;
position: relative;
top: 1px;
}
.leaflet-control-layers label {
display: block;
font-size: 13px;
font-size: 1.08333em;
}
.leaflet-control-layers-separator {
height: 0;
border-top: 1px solid #ddd;
margin: 5px -10px 5px -6px;
}
/* Default icon URLs */
.leaflet-default-icon-path { /* used only in path-guessing heuristic, see L.Icon.Default */
background-image: url(images/marker-icon.png);
}
/* attribution and scale controls */
.leaflet-container .leaflet-control-attribution {
background: #fff;
background: rgba(255, 255, 255, 0.8);
margin: 0;
}
.leaflet-control-attribution,
.leaflet-control-scale-line {
padding: 0 5px;
color: #333;
line-height: 1.4;
}
.leaflet-control-attribution a {
text-decoration: none;
}
.leaflet-control-attribution a:hover,
.leaflet-control-attribution a:focus {
text-decoration: underline;
}
.leaflet-control-attribution svg {
display: inline !important;
}
.leaflet-left .leaflet-control-scale {
margin-left: 5px;
}
.leaflet-bottom .leaflet-control-scale {
margin-bottom: 5px;
}
.leaflet-control-scale-line {
border: 2px solid #777;
border-top: none;
line-height: 1.1;
padding: 2px 5px 1px;
white-space: nowrap;
overflow: hidden;
-moz-box-sizing: border-box;
box-sizing: border-box;
background: #fff;
background: rgba(255, 255, 255, 0.5);
}
.leaflet-control-scale-line:not(:first-child) {
border-top: 2px solid #777;
border-bottom: none;
margin-top: -2px;
}
.leaflet-control-scale-line:not(:first-child):not(:last-child) {
border-bottom: 2px solid #777;
}
.leaflet-touch .leaflet-control-attribution,
.leaflet-touch .leaflet-control-layers,
.leaflet-touch .leaflet-bar {
box-shadow: none;
}
.leaflet-touch .leaflet-control-layers,
.leaflet-touch .leaflet-bar {
border: 2px solid rgba(0,0,0,0.2);
background-clip: padding-box;
}
/* popup */
.leaflet-popup {
position: absolute;
text-align: center;
margin-bottom: 20px;
}
.leaflet-popup-content-wrapper {
padding: 1px;
text-align: left;
border-radius: 12px;
}
.leaflet-popup-content {
margin: 13px 24px 13px 20px;
line-height: 1.3;
font-size: 13px;
font-size: 1.08333em;
min-height: 1px;
}
.leaflet-popup-content p {
margin: 17px 0;
margin: 1.3em 0;
}
.leaflet-popup-tip-container {
width: 40px;
height: 20px;
position: absolute;
left: 50%;
margin-top: -1px;
margin-left: -20px;
overflow: hidden;
pointer-events: none;
}
.leaflet-popup-tip {
width: 17px;
height: 17px;
padding: 1px;
margin: -10px auto 0;
pointer-events: auto;
-webkit-transform: rotate(45deg);
-moz-transform: rotate(45deg);
-ms-transform: rotate(45deg);
transform: rotate(45deg);
}
.leaflet-popup-content-wrapper,
.leaflet-popup-tip {
background: white;
color: #333;
box-shadow: 0 3px 14px rgba(0,0,0,0.4);
}
.leaflet-container a.leaflet-popup-close-button {
position: absolute;
top: 0;
right: 0;
border: none;
text-align: center;
width: 24px;
height: 24px;
font: 16px/24px Tahoma, Verdana, sans-serif;
color: #757575;
text-decoration: none;
background: transparent;
}
.leaflet-container a.leaflet-popup-close-button:hover,
.leaflet-container a.leaflet-popup-close-button:focus {
color: #585858;
}
.leaflet-popup-scrolled {
overflow: auto;
border-bottom: 1px solid #ddd;
border-top: 1px solid #ddd;
}
.leaflet-oldie .leaflet-popup-content-wrapper {
-ms-zoom: 1;
}
.leaflet-oldie .leaflet-popup-tip {
width: 24px;
margin: 0 auto;
-ms-filter: "progid:DXImageTransform.Microsoft.Matrix(M11=0.70710678, M12=0.70710678, M21=-0.70710678, M22=0.70710678)";
filter: progid:DXImageTransform.Microsoft.Matrix(M11=0.70710678, M12=0.70710678, M21=-0.70710678, M22=0.70710678);
}
.leaflet-oldie .leaflet-control-zoom,
.leaflet-oldie .leaflet-control-layers,
.leaflet-oldie .leaflet-popup-content-wrapper,
.leaflet-oldie .leaflet-popup-tip {
border: 1px solid #999;
}
/* div icon */
.leaflet-div-icon {
background: #fff;
border: 1px solid #666;
}
/* Tooltip */
/* Base styles for the element that has a tooltip */
.leaflet-tooltip {
position: absolute;
padding: 6px;
background-color: #fff;
border: 1px solid #fff;
border-radius: 3px;
color: #222;
white-space: nowrap;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
pointer-events: none;
box-shadow: 0 1px 3px rgba(0,0,0,0.4);
}
.leaflet-tooltip.leaflet-interactive {
cursor: pointer;
pointer-events: auto;
}
.leaflet-tooltip-top:before,
.leaflet-tooltip-bottom:before,
.leaflet-tooltip-left:before,
.leaflet-tooltip-right:before {
position: absolute;
pointer-events: none;
border: 6px solid transparent;
background: transparent;
content: "";
}
/* Directions */
.leaflet-tooltip-bottom {
margin-top: 6px;
}
.leaflet-tooltip-top {
margin-top: -6px;
}
.leaflet-tooltip-bottom:before,
.leaflet-tooltip-top:before {
left: 50%;
margin-left: -6px;
}
.leaflet-tooltip-top:before {
bottom: 0;
margin-bottom: -12px;
border-top-color: #fff;
}
.leaflet-tooltip-bottom:before {
top: 0;
margin-top: -12px;
margin-left: -6px;
border-bottom-color: #fff;
}
.leaflet-tooltip-left {
margin-left: -6px;
}
.leaflet-tooltip-right {
margin-left: 6px;
}
.leaflet-tooltip-left:before,
.leaflet-tooltip-right:before {
top: 50%;
margin-top: -6px;
}
.leaflet-tooltip-left:before {
right: 0;
margin-right: -12px;
border-left-color: #fff;
}
.leaflet-tooltip-right:before {
left: 0;
margin-left: -12px;
border-right-color: #fff;
}
/* Printing */
@media print {
/* Prevent printers from removing background-images of controls. */
.leaflet-control {
-webkit-print-color-adjust: exact;
color-adjust: exact;
}
}

BIN
static/core/dist/images/layers-2x.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

BIN
static/core/dist/images/layers.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 696 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

BIN
static/core/dist/images/marker-icon.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 618 B

6
static/core/dist/js/leaflet.js vendored Normal file

File diff suppressed because one or more lines are too long

View file

@ -7,3 +7,48 @@ $(document).ready(function () {
); );
}); });
}); });
function deleteVessel(row) {
id = row.closest('tr').id.substr(4);
$.getJSON("/api/urls/datatable/vessels/delete/" + id + "/")
.done(function (data) {
window.location.href = data;
});
}
function editVessel(row) {
id = row.closest('tr').id.substr(4);
$.getJSON("/api/urls/datatable/vessels/edit/" + id + "/")
.done(function (data) {
window.location.href = data;
});
}
function locateVessel(row) {
id = row.closest('tr').id.substr(4);
$.getJSON("/api/vessels/location/" + id + "/")
.done(function (data) {
if (data["status"] == "success") {
$('#locationModal .modal-body').html('<div id="map"></div>');
$('#locationModalLabel').text("Location of " + data["name"]);
$('#map').css('height', '400px');
$.getJSON("/api/urls/static/?file=" + encodeURIComponent("core/dist/images/"))
.done(function (data2) {
L.Icon.Default.prototype.options.imagePath = data2;
var map = L.map('map').setView([data["location"]["latitude"], data["location"]["longitude"]], 8);
L.tileLayer('https://tileserver.kumi.systems/{z}/{x}/{y}.png', {
maxZoom: 19,
attribution: '© OpenStreetMap'
}).addTo(map);
var marker = L.marker([data["location"]["latitude"], data["location"]["longitude"]]).addTo(map);
marker.bindPopup("<b>" + data["name"] + "</b><br>En route to " + data["destination"] + ".").openPopup();
$('#locationModal').modal('show');
setTimeout(function () {
window.dispatchEvent(new Event('resize'));
}, 1000);
})
} else {
$('#locationModal .modal-body').html("<strong>" + data["error"] + "</strong>");
}
});
}

View file

@ -0,0 +1,18 @@
{% extends "core/base.html"%}
{% load crispy_forms_tags %}
{% block content %}
<div class="card">
<div class="card-header">
<h3 class="card-title">Delete Vessel</h3>
<div class="card-tools">
<ul class="pagination pagination-sm float-right">
<a href="{% url "core:admin_vessels" %}" class="btn btn-danger"><i class="fas fa-xmark"></i> Abort</a>
</ul>
</div>
</div>
<div class="card-body">
{% crispy form %}
</div>
</div>
{% endblock %}

View file

@ -0,0 +1,18 @@
{% extends "core/base.html"%}
{% load crispy_forms_tags %}
{% block content %}
<div class="card">
<div class="card-header">
<h3 class="card-title">Edit Vessel</h3>
<div class="card-tools">
<ul class="pagination pagination-sm float-right">
<a href="{% url "core:admin_vessels" %}" class="btn btn-danger"><i class="fas fa-xmark"></i> Abort</a>
</ul>
</div>
</div>
<div class="card-body">
{% crispy form %}
</div>
</div>
{% endblock %}

View file

@ -5,6 +5,47 @@
<link href="{% static 'ajax_datatable/css/style.css' %}" rel="stylesheet" /> <link href="{% static 'ajax_datatable/css/style.css' %}" rel="stylesheet" />
<link rel='stylesheet' href="{% static 'core/dist/css/dataTables.bootstrap4.min.css' %}"> <link rel='stylesheet' href="{% static 'core/dist/css/dataTables.bootstrap4.min.css' %}">
<link rel='stylesheet' href="{% static 'core/dist/css/buttons.bootstrap.min.css' %}"> <link rel='stylesheet' href="{% static 'core/dist/css/buttons.bootstrap.min.css' %}">
<link rel='stylesheet' href="{% static 'core/dist/css/leaflet.css' %}">
{% endblock %}
{% block content %}
<div class="card">
<div class="card-header">
<h3 class="card-title">List of Vessels</h3>
<div class="card-tools">
<ul class="pagination pagination-sm float-right">
<a href="{% url "core:admin_vessels_create" %}" class="btn btn-success"><i class="fas fa-plus"></i> New
Vessel</a>
</ul>
</div>
</div>
<div class="card-body">
<div class="table-responsive">
<table id="datatable_vessels" width="100%"
class="table table-striped table-bordered dt-responsive compact nowrap">
</table>
</div>
</div>
</div>
<div class="modal fade" id="locationModal" tabindex="-1" role="dialog" aria-labelledby="locationModalLabel"
aria-hidden="true">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="locationModalLabel"></h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
</div>
</div>
</div>
</div>
{% endblock %} {% endblock %}
{% block scripts %} {% block scripts %}
@ -19,4 +60,8 @@
<script src="{% static 'core/dist/js/jszip.min.js' %}"></script> <script src="{% static 'core/dist/js/jszip.min.js' %}"></script>
<script src="{% static 'core/dist/js/pdfmake.min.js' %}"></script> <script src="{% static 'core/dist/js/pdfmake.min.js' %}"></script>
<script src="{% static 'core/dist/js/vfs_fonts.js' %}"></script> <script src="{% static 'core/dist/js/vfs_fonts.js' %}"></script>
<script src="{% static 'core/dist/js/leaflet.js' %}"></script>
<script type="text/javascript" src="{% static 'core/js/admin/vessels_datatable.js' %}"></script>
{% endblock %} {% endblock %}

View file

@ -0,0 +1,18 @@
{% extends "core/base.html"%}
{% load crispy_forms_tags %}
{% block content %}
<div class="card">
<div class="card-header">
<h3 class="card-title">Create New Vessel</h3>
<div class="card-tools">
<ul class="pagination pagination-sm float-right">
<a href="{% url "core:admin_vessels" %}" class="btn btn-danger"><i class="fas fa-xmark"></i> Abort</a>
</ul>
</div>
</div>
<div class="card-body">
{% crispy form %}
</div>
</div>
{% endblock %}

View file

@ -60,8 +60,6 @@ All rights reserved.
<script src="{% static "core/dist/js/jquery.min.js" %}"></script> <script src="{% static "core/dist/js/jquery.min.js" %}"></script>
<script src="{% static "core/dist/js/bootstrap.bundle.min.js" %}"></script> <script src="{% static "core/dist/js/bootstrap.bundle.min.js" %}"></script>
<script src="{% static "core/dist/js/adminlte.js" %}?v=3.2.0"></script> <script src="{% static "core/dist/js/adminlte.js" %}?v=3.2.0"></script>
<script src="{% static "core/dist/js/Chart.min.js" %}"></script>
<script src="{% static "core/dist/js/dashboard3.js" %}"></script>
{% block scripts %} {% block scripts %}
{% endblock %} {% endblock %}