From dea562a03e290bafb61a3758c7c5886d5533db20 Mon Sep 17 00:00:00 2001 From: Tomas Cejka <cejkat@cesnet.cz> Date: Thu, 23 Aug 2018 14:49:53 +0200 Subject: [PATCH] updated views and templates to show Rules&Routes Dashboard was updated to show the list of Rules (instead of Routes used previously) that can contain one or more Routes. New detail page rule_details.html was created as a clone of route_details.html. If a Rule contains only single Route, the statistics (and graphs) are shown directly, otherwise, a separate pages are used. --- flowspec/admin.py | 1 + flowspec/forms.py | 15 +- flowspec/models.py | 44 ++- flowspec/validators.py | 4 +- flowspec/views.py | 93 ++++--- flowspy/urls.py | 3 +- templates/dashboard.html | 58 ++-- templates/flowspy/route_details.html | 101 +++---- templates/flowspy/rule_details.html | 387 +++++++++++++++++++++++++++ 9 files changed, 558 insertions(+), 148 deletions(-) create mode 100644 templates/flowspy/rule_details.html diff --git a/flowspec/admin.py b/flowspec/admin.py index ddb49aad..27d77c1c 100644 --- a/flowspec/admin.py +++ b/flowspec/admin.py @@ -31,6 +31,7 @@ from longerusername.forms import UserCreationForm, UserChangeForm class RuleAdmin(admin.ModelAdmin): form = RuleForm + list_display = ('name', 'status', 'applier_username', 'applier_peers', 'get_match', 'get_then', 'response', "comments") class RouteAdmin(admin.ModelAdmin): form = RouteForm diff --git a/flowspec/forms.py b/flowspec/forms.py index aaad8a06..18f3a735 100644 --- a/flowspec/forms.py +++ b/flowspec/forms.py @@ -130,7 +130,7 @@ class RuleForm(forms.ModelForm): port = self.cleaned_data.get('port', None) destination = self.cleaned_data.get('destination', None) destinationports = self.cleaned_data.get('destinationport', None) - #user = self.cleaned_data.get('applier', None) + user = self.cleaned_data.get('applier', None) for route in existing_routes: if name != route.name: @@ -143,6 +143,7 @@ class RouteForm(forms.ModelForm): sourceport = PortRangeForm() destinationport = PortRangeForm() port = PortRangeForm() + rule = RuleForm() class Meta: model = Route @@ -179,11 +180,13 @@ class RouteForm(forms.ModelForm): raise forms.ValidationError(error) ## check if same rule exists with other name - #user = self.cleaned_data['applier'] - #if user.is_superuser: - # peers = Peer.objects.all() - #else: - # peers = user.userprofile.peers.all() + rule = self.cleaned_data['rule'] + user = rule.applier + print(vars(rule)) + if user.is_superuser: + peers = Peer.objects.all() + else: + peers = user.userprofile.peers.all() existing_routes = Route.objects.all() #existing_routes = existing_routes.filter(rule__applier__userprofile__peers__in=peers) name = self.cleaned_data.get('name', None) diff --git a/flowspec/models.py b/flowspec/models.py index 391d0d98..c576485c 100644 --- a/flowspec/models.py +++ b/flowspec/models.py @@ -300,6 +300,37 @@ class Rule(models.Model): } logger.info(mail_body, extra=d) + def get_absolute_url(self): + return reverse('rule-details', kwargs={'rule_slug': self.name}) + + def get_then(self): + ret = '' + then_statements = self.then.all() + for statement in then_statements: + if statement.action_value: + ret = "%s %s %s" %(ret, statement.action, statement.action_value) + else: + ret = "%s %s" %(ret, statement.action) + return ret + + get_then.short_description = 'Then statement' + get_then.allow_tags = True + + def get_match(self): + res = '' + if self.routes: + for r in self.routes.all(): + res = res + r.get_match() + return res + get_match.short_description = 'Match statement' + get_match.allow_tags = True + + def get_responses(self): + if self.routes: + return ', '.join([str(r.response) for r in self.routes.all()]) + else: + return '' + class Route(models.Model): name = models.SlugField(max_length=128, verbose_name=_("Name")) @@ -533,12 +564,13 @@ class Route(models.Model): def get_then(self): ret = '' - then_statements = self.then.all() - for statement in then_statements: - if statement.action_value: - ret = "%s %s %s" %(ret, statement.action, statement.action_value) - else: - ret = "%s %s" %(ret, statement.action) + if self.rule: + then_statements = self.rule.then.all() + for statement in then_statements: + if statement.action_value: + ret = "%s %s %s" %(ret, statement.action, statement.action_value) + else: + ret = "%s %s" %(ret, statement.action) return ret get_then.short_description = 'Then statement' diff --git a/flowspec/validators.py b/flowspec/validators.py index a02a4db4..1fee33f1 100644 --- a/flowspec/validators.py +++ b/flowspec/validators.py @@ -165,8 +165,8 @@ def clean_route_form(data): return _('Once destination port is matched, destination has to be filled as well. Either deselect destination port or fill destination address') if not (source or sourceports or ports or destination or destinationports): return _('Fill at least a Rule Match Condition') - if not user.is_superuser and then[0].action not in settings.UI_USER_THEN_ACTIONS: - return _('This action "%s" is not permitted') % (then[0].action) + #if not user or (not user.is_superuser and then[0].action not in settings.UI_USER_THEN_ACTIONS): + # return _('This action "%s" is not permitted') % (then[0].action) if then and then[0] else _("No Then action given.") def check_if_rule_exists(fields, queryset): diff --git a/flowspec/views.py b/flowspec/views.py index 78df4d6b..6b228a93 100644 --- a/flowspec/views.py +++ b/flowspec/views.py @@ -89,7 +89,7 @@ def welcome(request): @login_required @never_cache def dashboard(request): - all_group_routes = [] + all_group_rules = [] message = '' try: peers = request.user.get_profile().peers.select_related('user_profile') @@ -104,13 +104,13 @@ def dashboard(request): ) if peers: if request.user.is_superuser: - all_group_routes = Route.objects.all().order_by('-last_updated')[:10] + all_group_rules = Rule.objects.all().order_by('-last_updated')[:10] else: query = Q() for peer in peers: query |= Q(applier__userprofile__in=peer.user_profile.all()) - all_group_routes = Route.objects.filter(query) - if all_group_routes is None: + all_group_rules = Rule.objects.filter(query) + if all_group_rules is None: message = 'You have not added any rules yet' else: message = 'You are not associated with a peer.' @@ -125,7 +125,7 @@ def dashboard(request): request, 'dashboard.html', { - 'routes': all_group_routes.prefetch_related( + 'routes': all_group_rules.prefetch_related( 'applier', 'applier', 'fragmenttype', @@ -161,7 +161,7 @@ def group_routes(request): @login_required @never_cache def group_routes_ajax(request): - all_group_routes = [] + all_group_rules = [] try: peers = request.user.get_profile().peers.prefetch_related('networks') except UserProfile.DoesNotExist: @@ -172,22 +172,22 @@ def group_routes_ajax(request): {'error': error} ) if request.user.is_superuser: - all_group_routes = Route.objects.all() + all_group_rules = Rule.objects.all() else: query = Q() for peer in peers: query |= Q(applier__userprofile__in=peer.user_profile.all()) - all_group_routes = Route.objects.filter(query) + all_group_rules = Route.objects.filter(query) jresp = {} - routes = build_routes_json(all_group_routes) - jresp['aaData'] = routes + rules = build_routes_json(all_group_rules) + jresp['aaData'] = rules return HttpResponse(json.dumps(jresp), mimetype='application/json') @login_required @never_cache def overview_routes_ajax(request): - all_group_routes = [] + all_group_rules = [] try: peers = request.user.get_profile().peers.all().select_related() except UserProfile.DoesNotExist: @@ -196,36 +196,41 @@ def overview_routes_ajax(request): query = Q() for peer in peers: query |= Q(applier__userprofile__in=peer.user_profile.all()) - all_group_routes = Route.objects.filter(query) + all_group_rules = Route.objects.filter(query) if request.user.is_superuser or request.user.has_perm('accounts.overview'): - all_group_routes = Route.objects.all() + all_group_rules = Route.objects.all() jresp = {} - routes = build_routes_json(all_group_routes) - jresp['aaData'] = routes + rules = build_routes_json(all_group_rules) + jresp['aaData'] = rules return HttpResponse(json.dumps(jresp), mimetype='application/json') -def build_routes_json(groutes): +def build_routes_json(grules): routes = [] - for r in groutes.prefetch_related( + for r in grules.prefetch_related( 'applier', - 'fragmenttype', - 'protocol', - 'dscp', + 'routes', ): rd = {} rd['id'] = r.pk - rd['port'] = r.port - rd['sourceport'] = r.sourceport - rd['destinationport'] = r.destinationport - # name with link to rule details rd['name'] = r.name rd['details'] = '<a href="%s">%s</a>' % (r.get_absolute_url(), r.name) + rd['routes'] = list() + for routei in r.routes.all(): + route = {} + route['port'] = routei.port + route['sourceport'] = routei.sourceport + route['destinationport'] = routei.destinationport + route['response'] = "%s" % routei.response + route['match'] = routei.get_match() + rd['routes'].append(route) + # name with link to rule details if not r.comments: rd['comments'] = 'Not Any' else: rd['comments'] = r.comments rd['match'] = r.get_match() + rd['response'] = r.get_responses() rd['then'] = r.get_then() rd['status'] = r.status # in case there is no applier (this should not occur) @@ -235,23 +240,8 @@ def build_routes_json(groutes): rd['applier'] = 'unknown' rd['peer'] = '' else: - peers = r.applier.get_profile().peers.select_related('networks') - username = None - for peer in peers: - if username: - break - for network in peer.networks.all(): - net = IPNetwork(network) - if IPNetwork(r.destination) in net: - username = peer.peer_name - break - try: - rd['peer'] = username - except UserProfile.DoesNotExist: - rd['peer'] = '' - + rd['peer'] = r.helper_get_matching_peers()[1] rd['expires'] = "%s" % r.expires - rd['response'] = "%s" % r.response routes.append(rd) return routes @@ -277,7 +267,12 @@ def add_route(request): return HttpResponseRedirect(reverse("group-routes")) if request.method == "GET": form = RouteForm(initial={'applier': applier}) - if not request.user.is_superuser: + form.fields['expires'] = forms.DateField() + form.fields['applier'] = forms.ModelChoiceField(queryset=User.objects.filter(pk=request.user.pk), required=True, empty_label=None) + if request.user.is_superuser: + form.fields['then'] = forms.ModelMultipleChoiceField(queryset=ThenAction.objects.all().order_by('action'), required=True) + form.fields['protocol'] = forms.ModelMultipleChoiceField(queryset=MatchProtocol.objects.all().order_by('protocol'), required=False) + else: form.fields['then'] = forms.ModelMultipleChoiceField(queryset=ThenAction.objects.filter(action__in=settings.UI_USER_THEN_ACTIONS).order_by('action'), required=True) form.fields['protocol'] = forms.ModelMultipleChoiceField(queryset=MatchProtocol.objects.filter(protocol__in=settings.UI_USER_PROTOCOLS).order_by('protocol'), required=False) return render_to_response('apply.html', {'form': form, @@ -828,12 +823,26 @@ def lookupShibAttr(attrmap, requestMeta): return '' +# show the details of specific route +@login_required +@never_cache +def ruledetails(request, rule_slug): + rule = get_object_or_404(Rule, name=rule_slug) + now = datetime.datetime.now() + last_msrm_delay_time = get_last_msrm_delay_time() + return render(request, 'flowspy/rule_details.html', { + 'rule': rule, + 'mytime': now, + 'last_msrm_delay_time': last_msrm_delay_time, + 'tz' : settings.TIME_ZONE, + 'route_comments_len' : len(str(rule.comments)) + }) + # show the details of specific route @login_required @never_cache def routedetails(request, route_slug): route = get_object_or_404(Route, name=route_slug) - #return render(request, 'flowspy/route_details.html', {'route': route}) now = datetime.datetime.now() last_msrm_delay_time = get_last_msrm_delay_time() return render(request, 'flowspy/route_details.html', { diff --git a/flowspy/urls.py b/flowspy/urls.py index 445eeb19..8f85f47e 100644 --- a/flowspy/urls.py +++ b/flowspy/urls.py @@ -63,7 +63,8 @@ urlpatterns = patterns( ), url(r'^overview/?$', 'flowspec.views.overview', name="overview"), url(r'^api/', include(router.urls)), - url(r'^details/(?P<route_slug>[\w\-]+)/$', 'flowspec.views.routedetails', name="route-details"), + url(r'^details/(?P<rule_slug>[\w\-]+)/$', 'flowspec.views.ruledetails', name="rule-details"), + url(r'^routedetails/(?P<route_slug>[\w\-]+)/$', 'flowspec.views.routedetails', name="route-details"), url(r'^routestats/(?P<route_slug>[\w\-]+)/$', 'flowspec.views.routestats', name="routestats"), url(r'^admin/', include(admin.site.urls)), ) diff --git a/templates/dashboard.html b/templates/dashboard.html index c632ef3a..9d02572f 100644 --- a/templates/dashboard.html +++ b/templates/dashboard.html @@ -37,24 +37,24 @@ <div class="panel-body"> <div class=panel>{{message}}</div> <ul class="timeline"> - {% for route in routes %} + {% for rule in rules %} <li class="{% cycle '' 'timeline-inverted' %}"> - {% if route.status == 'EXPIRED' or route.status == 'ADMININACTIVE' or route.status == 'INACTIVE' or route.status == 'OUTOFSYNC'%} - {% if route.status == 'EXPIRED' or route.status == 'ADMININACTIVE' or route.status == 'INACTIVE' %} + {% if rule.status == 'EXPIRED' or rule.status == 'ADMININACTIVE' or rule.status == 'INACTIVE' or rule.status == 'OUTOFSYNC'%} + {% if rule.status == 'EXPIRED' or rule.status == 'ADMININACTIVE' or rule.status == 'INACTIVE' %} <div class="timeline-badge"><i class="fa fa-adjust"></i></div> {% else %} - {% if route.status == 'OUTOFSYNC' %} + {% if rule.status == 'OUTOFSYNC' %} <div class="timeline-badge danger"><i class="fa fa-bug"></i></div> {% else %} <div class="timeline-badge danger"><i class="fa fa-exclamation"></i></div> {% endif %} {% endif %} {% else %} - {% if route.status == 'ACTIVE' %} + {% if rule.status == 'ACTIVE' %} <div class="timeline-badge success"><i class="fa fa-shield"></i></div> {% else %} - {% if route.status == 'PENDING' %} + {% if rule.status == 'PENDING' %} <div class="timeline-badge info"><i class="fa fa-spinner"></i></div> {% else %} <div class="timeline-badge warning"><i class="fa fa-exclamation"></i></div> @@ -64,60 +64,60 @@ <div class="timeline-panel"> <div class="timeline-heading"> - <h4 class="timeline-title"><a href="{{ route.get_absolute_url }}">{{route.name}}</a></h4> + <h4 class="timeline-title"><a href="{{ rule.get_absolute_url }}">{{rule.name}}</a></h4> <p> - <small class="text-muted"><i class="fa fa-pencil-square-o"></i> {% trans "Last update" %}: {{route.last_updated}} {% trans "by" %} {{route.applier}}</small> + <small class="text-muted"><i class="fa fa-pencil-square-o"></i> {% trans "Last update" %}: {{rule.last_updated}} {% trans "by" %} {{rule.applier}}</small> </p> <p> - <small class="text-muted"><i class="fa fa-clock-o"></i> {% trans "Expires" %}: {{ route.expires|date:"d M y" }}</small> + <small class="text-muted"><i class="fa fa-clock-o"></i> {% trans "Expires" %}: {{ rule.expires|date:"d M y" }}</small> </p> </div> <div class="timeline-body"> <small> <p> - {% if route.status == 'EXPIRED' or route.status == 'ADMININACTIVE' or route.status == 'INACTIVE' or route.status == 'OUTOFSYNC'%} - {% if route.status == 'EXPIRED' or route.status == 'ADMININACTIVE' or route.status == 'INACTIVE' %} + {% if rule.status == 'EXPIRED' or rule.status == 'ADMININACTIVE' or rule.status == 'INACTIVE' or rule.status == 'OUTOFSYNC'%} + {% if rule.status == 'EXPIRED' or rule.status == 'ADMININACTIVE' or rule.status == 'INACTIVE' %} <span class="label label-default">DEACTIVATED</span> {% else %} - {% if route.status == 'OUTOFSYNC' %} + {% if rule.status == 'OUTOFSYNC' %} <span class="label label-danger">ERROR</span> {% else %} - <span class="label label-info">{{route.status}}</span> + <span class="label label-info">{{rule.status}}</span> {% endif %} {% endif %} {% else %} - {% if route.status == 'ACTIVE' %} - <span class="label label-success">{{route.status}}</span> + {% if rule.status == 'ACTIVE' %} + <span class="label label-success">{{rule.status}}</span> {% else %} - {% if route.status == 'PENDING' %} - <span class="label label-info">{{route.status}}</span> + {% if rule.status == 'PENDING' %} + <span class="label label-info">{{rule.status}}</span> {% else %} - <span class="label label-warning">{{route.status}}</span> + <span class="label label-warning">{{rule.status}}</span> {% endif %} {% endif %} {% endif %} </p> - {{ route.get_match|safe|escape }} + {{ rule.get_match|safe|escape }} <dl class="dl-horizontal"> - <dt>Then</dt><dd>{{ route.get_then }}</dd> + <dt>Then</dt><dd>{{ rule.get_then }}</dd> </dl> </small> <div> - {% ifequal route.status 'ACTIVE' %} - <a href="{% url edit-route route.name %}" class="btn-info btn btn-xs btn-outline" id="edit_button_{{route.pk}}">{% trans "Edit" %}</a> - <button class="del_buttonpre btn-outline btn btn-xs btn-warning" id="{{route.name}}" data-routename="{{route.name}}">{% trans "Deactivate" %}</button> + {% ifequal rule.status 'ACTIVE' %} + <a href="{% url edit-route rule.name %}" class="btn-info btn btn-xs btn-outline" id="edit_button_{{rule.pk}}">{% trans "Edit" %}</a> + <button class="del_buttonpre btn-outline btn btn-xs btn-warning" id="{{rule.name}}" data-routename="{{rule.name}}">{% trans "Deactivate" %}</button> {% else %} - {% if route.status == 'EXPIRED' or route.status == 'ADMININACTIVE' or route.status == 'INACTIVE' %} - <a href="{% url edit-route route.name %}" class="btn-info btn btn-xs btn-outline" id="edit_button_{{route.pk}}" type="button">{% trans "Reactivate" %}</a> + {% if rule.status == 'EXPIRED' or rule.status == 'ADMININACTIVE' or rule.status == 'INACTIVE' %} + <a href="{% url edit-route rule.name %}" class="btn-info btn btn-xs btn-outline" id="edit_button_{{rule.pk}}" type="button">{% trans "Reactivate" %}</a> {% else %} - {% ifequal route.status 'OUTOFSYNC' %} - <a href="{% url edit-route route.name %}" class="btn-info btn btn-xs btn-outline" id="edit_button_{{route.pk}}">{% trans "ReSync" %}</a> + {% ifequal rule.status 'OUTOFSYNC' %} + <a href="{% url edit-route rule.name %}" class="btn-info btn btn-xs btn-outline" id="edit_button_{{rule.pk}}">{% trans "ReSync" %}</a> {% else %} - {% ifequal route.status 'ERROR' %} - <a href="{% url edit-route route.name %}" class="btn-info btn btn-xs btn-outline" id="edit_button_{{route.pk}}">{% trans "Fix it!" %}</a> + {% ifequal rule.status 'ERROR' %} + <a href="{% url edit-route rule.name %}" class="btn-info btn btn-xs btn-outline" id="edit_button_{{rule.pk}}">{% trans "Fix it!" %}</a> {% else %} - {% endifequal %} diff --git a/templates/flowspy/route_details.html b/templates/flowspy/route_details.html index 3558476b..fa740c95 100644 --- a/templates/flowspy/route_details.html +++ b/templates/flowspy/route_details.html @@ -19,17 +19,19 @@ function myreloadPage() { </div> <div class="row"> <div class="col-md-12"> - <div> - <i class="fa fa-clock-o"></i> {% trans "Expires" %}: {{ route.expires|date:"d M y" }} - </div> - </div> - <div class="col-md-12"> - <div> - <i class="fa fa-pencil-square-o"></i> {% trans "Last rule edit" %}: {{route.last_updated}} {% trans "by" %} {{route.applier}} - </div> <div> <h2>{% trans 'About' %}</h2> - {{ route.get_then }} + + {% if route.comments %} + <p> + <div> + Comments: {{ route.comments|slice:"0:300" }} + {% if route_comments_len > 300 %} + ... + {% endif %} + </div> + {% endif %} + {% trans 'all'%} {% if route.protocol.count %} {% for proto in route.protocol.all %} @@ -53,49 +55,21 @@ function myreloadPage() { {% endfor %} ) {% endif %} - {% if route.status = 'EXPIRED' or route.status = 'ADMININACTIVE' or route.status = 'INACTIVE' %} - <span class="label label-default">DEACTIVATED</span> - {% elif route.status = 'OUTOFSYNC' %} - <span class="label label-danger">ERROR</span> - {% elif route.status = 'ACTIVE' %} - <span class="label label-success">{{ route.status }}</span> - {% elif route.status = 'PENDING' %} - <span class="label label-info">{{ route.status }}</span> - {% else %} - <span class="label label-danger">{{ route.status }}</span> - {% endif %} - {% if route.status != 'PENDING' %} - - <a href="{% url edit-route route.name %}" class="btn-info btn btn-sm btn-outline">{% trans "Edit" %}</a> - {% if route.status = 'ACTIVE' %} - <button class="del_button btn-warning btn btn-sm btn-outline" id="{{ route.name }}" data-routename="{{ route.name }}">{% trans "Deactivate" %}</button> - {% endif %} - {% endif %} - - {% if route.comments %} - <p> - <div> - Comments: {{ route.comments|slice:"0:300" }} - {% if route_comments_len > 300 %} - ... - {% endif %} - </div> - {% endif %} - </div> - <div> - <h2>Statistics</h2> - <div>(all times are in {{ tz }}; current System time: {{ mytime|date:'Y-m-d H:i' }}, active rules will be updated every 5 minutes, last duration for measurement was {{ last_msrm_delay_time }})</div> - <div><span id="traffic-plot-loading">(Loading data...)</span> - <h3>Number of packets (absolute)</h3> - <div><canvas id="traffic-plot-pkts-abs" width=200 height=200></canvas></div> - <h3>Number of packets (relative)</h3> - <div><canvas id="traffic-plot-pkts-rel" width=200 height=200></canvas></div> - <h3>Number of bytes (absolute)</h3> - <div><canvas id="traffic-plot-bytes-abs" width=200 height=200></canvas></div> - <h3>Number of bytes (relative)</h3> - <div><canvas id="traffic-plot-bytes-rel" width=200 height=200></canvas></div> + <div> + <h2>Statistics</h2> + <div>(all times are in {{ tz }}; current System time: {{ mytime|date:'Y-m-d H:i' }}, active rules will be updated every 5 minutes, last duration for measurement was {{ last_msrm_delay_time }})</div> + <div><span id="traffic-plot-loading">(Loading data...)</span> + <h3>Number of packets (absolute)</h3> + <div><canvas id="traffic-plot-pkts-abs" width=200 height=200></canvas></div> + <h3>Number of packets (relative)</h3> + <div><canvas id="traffic-plot-pkts-rel" width=200 height=200></canvas></div> + <h3>Number of bytes (absolute)</h3> + <div><canvas id="traffic-plot-bytes-abs" width=200 height=200></canvas></div> + <h3>Number of bytes (relative)</h3> + <div><canvas id="traffic-plot-bytes-rel" width=200 height=200></canvas></div> + </div> + <div><a href="{% url routestats route.name %}">Download raw data in JSON</a></div> </div> - <div><a href="{% url routestats route.name %}">Download raw data in JSON</a></div> </div> {% comment %}check if graphs plugin in installed apps{% endcomment %} {% if 'graphs' in INSTALLED_APPS %} @@ -115,9 +89,9 @@ function myreloadPage() { </div> {% endif %} - {% if route.comments %} + {% if rule.comments %} <p> - <div><h2>Comments</h2><pre>{{ route.comments }}</pre></div> + <div><h2>Comments</h2><pre>{{ rule.comments }}</pre></div> {% endif %} </div> @@ -291,15 +265,18 @@ function plotGraph(data) } $(document).ready(function() { - var statsurl = "{% url routestats route.name %}"; - $.get(statsurl).done(function(data) { - if (data["error"]) { - $("#traffic-plot-loading").text("No data, try later"); - } else { - $("#traffic-plot-loading").hide(); - plotGraph(data); - } - }); + var statsurls = Array({% for route in rule.routes.all %}"{% url routestats route.name %}",{% endfor %}); + for (i = 0; i < statsurls.length; i++) { + statsurl = statsurls[i]; + $.get(statsurl).done(function(data) { + if (data["error"]) { + $("#traffic-plot-loading").text("No data, try later"); + } else { + $("#traffic-plot-loading").hide(); + plotGraph(data); + } + }); + } }); </script> diff --git a/templates/flowspy/rule_details.html b/templates/flowspy/rule_details.html new file mode 100644 index 00000000..5120df88 --- /dev/null +++ b/templates/flowspy/rule_details.html @@ -0,0 +1,387 @@ +{% extends "base.html" %} +{% load i18n %} + +{% block contentplaceholder %} +<script> +setInterval("myreloadPage()", 30*1000); +function myreloadPage() { + //location.reload(true); + location.reload(false); +} +</script> + +<div class="row"> + <div class="col-lg-12"> + <h1 class="page-header">Rule {{ rule.name }}</h1> + <div>(all times are in {{ tz }}; current System time: {{ mytime|date:'Y-m-d H:i' }})</div> + <br> + </div> +</div> +<div class="row"> + <div class="col-md-12"> + <div> + <i class="fa fa-clock-o"></i> {% trans "Expires" %}: {{ rule.expires|date:"d M y" }} + </div> + </div> + <div class="col-md-12"> + <div> + <i class="fa fa-pencil-square-o"></i> {% trans "Last rule edit" %}: {{rule.last_updated}} {% trans "by" %} {{rule.applier}} + </div> + <div> + <h2>{% trans 'About' %}</h2> + {{ rule.get_then }} + + {% if rule.status = 'EXPIRED' or rule.status = 'ADMININACTIVE' or rule.status = 'INACTIVE' %} + <span class="label label-default">DEACTIVATED</span> + {% elif rule.status = 'OUTOFSYNC' %} + <span class="label label-danger">ERROR</span> + {% elif rule.status = 'ACTIVE' %} + <span class="label label-success">{{ rule.status }}</span> + {% elif rule.status = 'PENDING' %} + <span class="label label-info">{{ rule.status }}</span> + {% else %} + <span class="label label-danger">{{ rule.status }}</span> + {% endif %} + {% if rule.status != 'PENDING' %} + + {% if rule.routes.count == 1 %} + <a href="{% url edit-route rule.name %}" class="btn-info btn btn-sm btn-outline">{% trans "Edit" %}</a> + {% endif %} + {% if rule.status = 'ACTIVE' %} + <button class="del_button btn-warning btn btn-sm btn-outline" id="{{ rule.name }}" data-routename="{{ rule.name }}">{% trans "Deactivate" %}</button> + {% endif %} + {% endif %} + + {% if rule.comments %} + <p> + <div> + Comments: {{ rule.comments|slice:"0:300" }} + {% if rule_comments_len > 300 %} + ... + {% endif %} + </div> + {% endif %} + + <h3>Routes:</h3> + + {% for route in rule.routes.all %} + {% trans 'all'%} + {% if route.protocol.count %} + {% for proto in route.protocol.all %} + {{ proto }} {% if not forloop.last %},{% endif %} + {% endfor %} + {% endif %} + {% trans 'traffic from' %} + {{ route.source }} + {% if route.sourceport %} {% trans 'port' %} + {{ route.sourceport }} + {% endif %} + {% trans 'to' %} + {{ route.destination }} + {% if route.destinationport %} {% trans 'port' %} + {{ route.destinationport }} + {% endif %} + {% if route.fragmenttype.count %} + ({% trans 'Fragmentypes' %}: + {% for f in route.fragmenttype.all %} + {{ f }} {% if not forloop.last %},{% endif %} + {% endfor %} + ) + {% endif %} + {% if rule.routes.count == 1 %} + <div> + <h2>Statistics</h2> + <div>(all times are in {{ tz }}; current System time: {{ mytime|date:'Y-m-d H:i' }}, active rules will be updated every 5 minutes, last duration for measurement was {{ last_msrm_delay_time }})</div> + <div><span id="traffic-plot-loading">(Loading data...)</span> + <h3>Number of packets (absolute)</h3> + <div><canvas id="traffic-plot-pkts-abs" width=200 height=200></canvas></div> + <h3>Number of packets (relative)</h3> + <div><canvas id="traffic-plot-pkts-rel" width=200 height=200></canvas></div> + <h3>Number of bytes (absolute)</h3> + <div><canvas id="traffic-plot-bytes-abs" width=200 height=200></canvas></div> + <h3>Number of bytes (relative)</h3> + <div><canvas id="traffic-plot-bytes-rel" width=200 height=200></canvas></div> + </div> + <div><a href="{% url routestats route.name %}">Download raw data in JSON</a></div> + {% else %} + <div><a href="{% url route-details route.name %}">Details</a></div> + {% endif %} + {% endfor %} + </div> + </div> + {% comment %}check if graphs plugin in installed apps{% endcomment %} + {% if 'graphs' in INSTALLED_APPS %} + <link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/bootstrap.daterangepicker/2/daterangepicker.css" /> + + <div class="col-md-12 graphs-wrapper" style="display: none"> + <h3>{% trans 'Graphs' %}</h3> + <div class="col-md-3"> + <div id="reportrange" style="background: #fff; cursor: pointer; padding: 5px 10px; border: 1px solid #ccc;"> + <i class="glyphicon glyphicon-calendar fa fa-calendar"></i> + <span></span> <b class="caret"></b> + </div> + </div> + <div class="graphs col-md-12" data-url="{% url graphs route.name %}" > + loading... + </div> + </div> + {% endif %} + + {% if rule.comments %} + <p> + <div><h2>Comments</h2><pre>{{ rule.comments }}</pre></div> + {% endif %} + +</div> +{% endblock %} + +{% block pagejsbottom %} + +<script src="{{STATIC_URL}}js/Chart.min.js" type="text/javascript"></script> + + +<script src="{{STATIC_URL}}js/moment.min.js"></script> +<!--<script src="{{STATIC_URL}}js/hammer.min.js"></script>--> +<script src="{{STATIC_URL}}js/chartjs-plugin-zoom.min.js"></script> + +<script type="text/javascript"> +function plotGraph(data) +{ + var xdata = Array(); + var ydata = Array(); + var ydatarel = Array(); + var ybytesdata = Array(); + var ybytesdatarel = Array(); + + for (i=0; i<data["data"].length; i++) { + var d = data["data"][data["data"].length - 1 - i]; + xdata[i] = d.ts.replace(/\..*/, '').replace('T', ' '); + ydata[i] = d.value.packets; + ybytesdata[i] = d.value.bytes; + if (i == 0) { + ydatarel[i] = 0; + ybytesdatarel[i] = 0; + } else { + delta = (ydata[i]===undefined) ? undefined : (ydata[i-1]===undefined) ? ydata[i] : (ydata[i] - ydata[i-1]); + ydatarel[i] = (delta===undefined || delta>=0) ? delta : 0; + + bytesdelta = (ybytesdata[i]===undefined) ? undefined : (ybytesdata[i-1]===undefined) ? ybytesdata[i] : (ybytesdata[i] - ybytesdata[i-1]); + ybytesdatarel[i] = (bytesdelta===undefined || bytesdelta>=0) ? bytesdelta : 0; + } + } + + var graphpktsabs = document.getElementById("traffic-plot-pkts-abs"); + var graphpktsrel = document.getElementById("traffic-plot-pkts-rel"); + var graphbytesabs = document.getElementById("traffic-plot-bytes-abs"); + var graphbytesrel = document.getElementById("traffic-plot-bytes-rel"); + graphpktsabs.width = 80; + graphpktsabs.height = 20; + graphpktsrel.width = 80; + graphpktsrel.height = 20; + graphbytesabs.width = 80; + graphbytesabs.height = 20; + graphbytesrel.width = 80; + graphbytesrel.height = 20; + + var graphabssetting = { + type: 'line', + data: { + labels: xdata, + datasets: [{ + label: '# packets', + data: ydata, + borderWidth: 2, + borderColor: "#3c37c6", + pointBackgroundColor: "#3c37c6", + backgroundColor: "#99bfff" + }] + }, + options: { + elements: { + line: { tension: 0, } // disables bezier curves + }, + scales: { + yAxes: [{ + ticks: { + beginAtZero:true + } + }] + }, zoom: { + enabled: true, + drag: true, + mode: 'x', + //limits: { + // max: 10, + // min: 0.5 + //} + } + + } + } + var graphrelsetting = { + type: 'bar', + data: { + labels: xdata, + datasets: [{ + label: '# packets', + data: ydatarel, + borderWidth: 2, + borderColor: "#c63737", + pointBackgroundColor: "#c63737", + backgroundColor: "#ff877a" + }] + }, + options: { + elements: { + line: { tension: 0, } // disables bezier curves + }, + scales: { + yAxes: [{ + ticks: { + beginAtZero:true + } + }] + } + } + } + var graphbytesabssetting = { + type: 'line', + data: { + labels: xdata, + datasets: [{ + label: '# bytes', + data: ybytesdata, + borderWidth: 2, + borderColor: "#3c37c6", + pointBackgroundColor: "#3c37c6", + backgroundColor: "#99bfff" + }] + }, + options: { + elements: { + line: { tension: 0, } // disables bezier curves + }, + scales: { + yAxes: [{ + ticks: { + beginAtZero:true + } + }] + } + } + } + var graphbytesrelsetting = { + type: 'bar', + data: { + labels: xdata, + datasets: [{ + label: '# bytes', + data: ybytesdatarel, + borderWidth: 2, + borderColor: "#c63737", + pointBackgroundColor: "#c63737", + backgroundColor: "#ff877a" + }] + }, + options: { + elements: { + line: { tension: 0, } // disables bezier curves + }, + scales: { + yAxes: [{ + ticks: { + beginAtZero:true + } + }] + } + } + } + var pktsabsChart = new Chart(graphpktsabs, graphabssetting); + var pktsrelChart = new Chart(graphpktsrel, graphrelsetting); + var bytesabsChart = new Chart(graphbytesabs, graphbytesabssetting); + var bytesrelChart = new Chart(graphbytesrel, graphbytesrelsetting); +} + +$(document).ready(function() { + var statsurls = Array({% for route in rule.routes.all %}"{% url routestats route.name %}",{% endfor %}); + for (i = 0; i < statsurls.length; i++) { + statsurl = statsurls[i]; + $.get(statsurl).done(function(data) { + if (data["error"]) { + $("#traffic-plot-loading").text("No data, try later"); + } else { + $("#traffic-plot-loading").hide(); + plotGraph(data); + } + }); + } +}); +</script> + + +{% if 'graphs' in INSTALLED_APPS %} +<script src="https://cdn.jsdelivr.net/momentjs/2.9.0/moment.min.js"></script> +<script src="https://cdn.jsdelivr.net/bootstrap.daterangepicker/2/daterangepicker.js"></script> +<script type="text/javascript"> + $(document).ready(function () { + + var url = $('.graphs').data('url'); + var start = moment().subtract(1, 'days').format('X') + var end = moment().format('X') + + function cb(start, end) { + $('#reportrange span').html(start.format('MMMM D, YYYY') + ' - ' + end.format('MMMM D, YYYY')); + $('.graphs').load(url + '?start=' + start.format('X') + '&end=' + end.format('X'), function () { + $('.graphs-wrapper').show(); + }); + } + cb(moment().subtract(29, 'days'), moment()); + + $('#reportrange').daterangepicker({ + ranges: { + 'Today': [moment().subtract(1, 'days'), moment()], + 'Yesterday': [moment().subtract(2, 'days'), moment().subtract(1, 'days')], + 'Last 7 Days': [moment().subtract(6, 'days'), moment()], + 'Last 30 Days': [moment().subtract(29, 'days'), moment()], + 'This Month': [moment().startOf('month'), moment().endOf('month')], + 'Last Month': [moment().subtract(1, 'month').startOf('month'), moment().subtract(1, 'month').endOf('month')] + } + }, cb(moment().subtract(1, 'days'), moment())); + + $('body').on('apply.daterangepicker', '#reportrange', function(ev, picker) { + cb(picker.startDate, picker.endDate); + }); + + + }); +</script> +{% endif %} + +<script type="text/javascript"> + + var delete_triggerd = false; + + $(document).ready(function () { + $('body').on('click', ".del_button", function(){ + if (delete_triggerd) + return; + delete_triggerd = true; + + last_element = false; + var my = $(this); + my.html('Deactivating...') + var routename = $(this).data("routename"); + var delurl = "{% url delete-route 'route_placeholder'%}".replace('route_placeholder', routename.toString()); + $.ajax({ + type: 'POST', + url: delurl, + cache: false, + success: function(data) { + $('.del_button').addClass('disabled').text('Done'); + } + }); + return false; + }); + }); +</script> +{% endblock %} -- GitLab