mirror of
https://github.com/jwinterm/modoboa.git
synced 2026-10-17 05:41:25 +02:00
Added graphical statistics to domain detail page.
This commit is contained in:
@@ -9,11 +9,13 @@
|
||||
"lint": "vue-cli-service lint"
|
||||
},
|
||||
"dependencies": {
|
||||
"apexcharts": "^3.25.0",
|
||||
"core-js": "^3.6.5",
|
||||
"js-cookie": "^2.2.1",
|
||||
"luxon": "^1.26.0",
|
||||
"vee-validate": "^3.4.5",
|
||||
"vue": "^2.6.11",
|
||||
"vue-apexcharts": "^1.6.0",
|
||||
"vue-cookie": "^1.1.4",
|
||||
"vue-gettext": "^2.1.10",
|
||||
"vue-router": "^3.2.0",
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
import repository from './repository'
|
||||
|
||||
const resource = 'statistics'
|
||||
|
||||
export default {
|
||||
getStatistics ({ graphSet, period, graphicName, searchQuery, start, end }) {
|
||||
const args = {
|
||||
gset: graphSet,
|
||||
period,
|
||||
graphic: graphicName,
|
||||
searchquery: searchQuery,
|
||||
start: start,
|
||||
end: end
|
||||
}
|
||||
return repository.get(`${resource}/`, { params: args })
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,8 @@
|
||||
<template>
|
||||
<v-card>
|
||||
<v-card-title>
|
||||
<translate class="headline">General information</translate>
|
||||
</v-card-title>
|
||||
<v-card-text>
|
||||
<v-row>
|
||||
<v-col cols="6"><translate>Creation date</translate></v-col>
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
<template>
|
||||
<v-menu
|
||||
v-model="menu"
|
||||
:close-on-content-click="false"
|
||||
transition="scale-transition"
|
||||
offset-y
|
||||
min-width="auto"
|
||||
>
|
||||
<template v-slot:activator="{ on, attrs }">
|
||||
<v-text-field
|
||||
:value="value"
|
||||
:label="label"
|
||||
prepend-icon="mdi-calendar"
|
||||
readonly
|
||||
v-bind="attrs"
|
||||
v-on="on"
|
||||
></v-text-field>
|
||||
</template>
|
||||
<v-date-picker
|
||||
:value="value"
|
||||
@input="update"
|
||||
no-title
|
||||
>
|
||||
</v-date-picker>
|
||||
</v-menu>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: ['value', 'label'],
|
||||
data () {
|
||||
return {
|
||||
menu: false
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
update (value) {
|
||||
this.$emit('input', value)
|
||||
this.menu = false
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,162 @@
|
||||
<template>
|
||||
<v-card>
|
||||
<v-toolbar dense elevation="0">
|
||||
<v-toolbar-title>{{ title }}</v-toolbar-title>
|
||||
<v-spacer />
|
||||
<v-menu
|
||||
v-model="menu"
|
||||
:nudge-width="200"
|
||||
offset-y
|
||||
>
|
||||
<template v-slot:activator="{ on, attrs }">
|
||||
<v-btn
|
||||
small
|
||||
v-bind="attrs"
|
||||
v-on="on"
|
||||
>
|
||||
<v-icon small>mdi-calendar</v-icon>
|
||||
<translate>Period</translate>
|
||||
<span v-if="period">: {{ period }}</span>
|
||||
</v-btn>
|
||||
</template>
|
||||
<v-layout>
|
||||
<v-row>
|
||||
<v-col cols="6" class="d-flex flex-column align-start py-3 px-6">
|
||||
<translate class="subtitle">Absolute time range</translate>
|
||||
<date-field v-model="start" :label="'From'|translate" />
|
||||
<date-field v-model="end" :label="'To'|translate" />
|
||||
<v-btn color="primary" @click="setCustomPeriod"><translate>Apply</translate></v-btn>
|
||||
</v-col>
|
||||
<v-col cols="6">
|
||||
<translate class="subtitle">Relative time ranges</translate>
|
||||
<v-list>
|
||||
<v-list-item
|
||||
v-for="period in periods"
|
||||
:key="period.value"
|
||||
@click="setPeriod(period.value)"
|
||||
>
|
||||
<v-list-item-title>{{ period.name }}</v-list-item-title>
|
||||
</v-list-item>
|
||||
</v-list>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-layout>
|
||||
</v-menu>
|
||||
</v-toolbar>
|
||||
<v-card-text>
|
||||
<apexchart
|
||||
v-if="statistics"
|
||||
type="area"
|
||||
height="350"
|
||||
:options="options"
|
||||
:series="statistics.series"
|
||||
/>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import VueApexCharts from 'vue-apexcharts'
|
||||
import statistics from '@/api/statistics'
|
||||
import DateField from './DateField'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
apexchart: VueApexCharts,
|
||||
DateField
|
||||
},
|
||||
props: ['domain', 'graphicSet', 'graphicName'],
|
||||
computed: {
|
||||
title () {
|
||||
if (this.statistics) {
|
||||
return this.statistics.title
|
||||
}
|
||||
return ''
|
||||
},
|
||||
colors () {
|
||||
if (this.graphicName === 'averagetraffic') {
|
||||
return ['#ff6347', '#ffff00', '#4682B4', '#7cfc00', '#ffa500', '#d3d3d3']
|
||||
}
|
||||
return ['#ffa500', '#41d1cc']
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
dateMenu: false,
|
||||
menu: false,
|
||||
options: {
|
||||
colors: this.colors,
|
||||
chart: {
|
||||
type: 'area',
|
||||
stacked: false,
|
||||
zoom: {
|
||||
type: 'x',
|
||||
enabled: true,
|
||||
autoScaleYaxis: true
|
||||
},
|
||||
toolbar: {
|
||||
autoSelected: 'zoom'
|
||||
}
|
||||
},
|
||||
dataLabels: {
|
||||
enabled: false
|
||||
},
|
||||
yaxis: {
|
||||
},
|
||||
xaxis: {
|
||||
type: 'datetime'
|
||||
}
|
||||
},
|
||||
end: null,
|
||||
start: null,
|
||||
statistics: null,
|
||||
period: 'day',
|
||||
periods: [
|
||||
{ value: 'day', name: this.$gettext('Day') },
|
||||
{ value: 'week', name: this.$gettext('Week') },
|
||||
{ value: 'month', name: this.$gettext('Month') },
|
||||
{ value: 'year', name: this.$gettext('Year') }
|
||||
]
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
setCustomPeriod () {
|
||||
this.period = 'custom'
|
||||
this.fetchStatistics()
|
||||
},
|
||||
setPeriod (period) {
|
||||
if (period !== this.period) {
|
||||
this.period = period
|
||||
this.fetchStatistics()
|
||||
}
|
||||
},
|
||||
fetchStatistics () {
|
||||
const args = {
|
||||
graphSet: this.graphicSet,
|
||||
period: this.period,
|
||||
graphicName: this.graphicName,
|
||||
searchQuery: this.domain.name
|
||||
}
|
||||
if (this.period === 'custom') {
|
||||
args.start = this.start
|
||||
args.end = this.end
|
||||
}
|
||||
statistics.getStatistics(args).then(resp => {
|
||||
this.statistics = resp.data.graphs[this.graphicName]
|
||||
})
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
this.fetchStatistics()
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.v-card {
|
||||
z-index: 5;
|
||||
}
|
||||
.v-menu__content {
|
||||
background-color: white !important;
|
||||
}
|
||||
</style>
|
||||
@@ -11,11 +11,22 @@
|
||||
<v-row>
|
||||
<v-col cols="6">
|
||||
<domain-summary :domain="domain" />
|
||||
<div class="mt-4" />
|
||||
</v-col>
|
||||
<v-col cols="6">
|
||||
<domain-dns-summary :domain="domain" />
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-layout>
|
||||
<v-layout>
|
||||
<v-row>
|
||||
<v-col cols="12">
|
||||
<time-serie-chart :domain="domain" graphic-set="mailtraffic" graphic-name="averagetraffic" />
|
||||
</v-col>
|
||||
<v-col cols="12">
|
||||
<time-serie-chart :domain="domain" graphic-set="mailtraffic" graphic-name="averagetrafficsize" />
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-layout>
|
||||
<v-dialog v-model="showEditForm"
|
||||
persistent
|
||||
max-width="800px"
|
||||
@@ -40,13 +51,15 @@ import AdministratorList from '@/components/domains/AdministratorList'
|
||||
import DomainForm from '@/components/domains/DomainForm'
|
||||
import DomainDNSSummary from '@/components/domains/DomainDNSSummary'
|
||||
import DomainSummary from '@/components/domains/DomainSummary'
|
||||
import TimeSerieChart from '@/components/tools/TimeSerieChart'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
AdministratorList,
|
||||
DomainForm,
|
||||
'domain-dns-summary': DomainDNSSummary,
|
||||
DomainSummary
|
||||
DomainSummary,
|
||||
TimeSerieChart
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
@@ -55,6 +68,8 @@ export default {
|
||||
showAdminList: false
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
},
|
||||
mounted () {
|
||||
domains.getDomain(this.$route.params.id).then(resp => {
|
||||
this.domain = resp.data
|
||||
|
||||
@@ -12,7 +12,8 @@
|
||||
<v-menu offset-y>
|
||||
<template v-slot:activator="{ on, attrs }">
|
||||
<v-btn color="primary" v-bind="attrs" v-on="on">
|
||||
<v-icon left>mdi-plus</v-icon> <translate>New</translate>
|
||||
<v-icon left>mdi-plus</v-icon>
|
||||
<translate>New</translate>
|
||||
</v-btn>
|
||||
</template>
|
||||
<v-list dense>
|
||||
|
||||
@@ -1954,6 +1954,18 @@ anymatch@~3.1.1:
|
||||
normalize-path "^3.0.0"
|
||||
picomatch "^2.0.4"
|
||||
|
||||
apexcharts@^3.25.0:
|
||||
version "3.25.0"
|
||||
resolved "https://registry.yarnpkg.com/apexcharts/-/apexcharts-3.25.0.tgz#f3f0f9f344f997230f5c7f2918408aa072627496"
|
||||
integrity sha512-uM7OF+jLL4ba79noYcrMwMgJW8DI+Ff28CCQoGq23g25z8nGSQEoU+u12YWlECA9gBA5tbmdaQhMxjlK+M6B9Q==
|
||||
dependencies:
|
||||
svg.draggable.js "^2.2.2"
|
||||
svg.easing.js "^2.0.0"
|
||||
svg.filter.js "^2.0.2"
|
||||
svg.pathmorphing.js "^0.1.3"
|
||||
svg.resize.js "^1.4.3"
|
||||
svg.select.js "^3.0.1"
|
||||
|
||||
aproba@^1.1.1:
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/aproba/-/aproba-1.2.0.tgz#6802e6264efd18c790a1b0d517f0f2627bf2c94a"
|
||||
@@ -9062,6 +9074,61 @@ svg-tags@^1.0.0:
|
||||
resolved "https://registry.yarnpkg.com/svg-tags/-/svg-tags-1.0.0.tgz#58f71cee3bd519b59d4b2a843b6c7de64ac04764"
|
||||
integrity sha1-WPcc7jvVGbWdSyqEO2x95krAR2Q=
|
||||
|
||||
svg.draggable.js@^2.2.2:
|
||||
version "2.2.2"
|
||||
resolved "https://registry.yarnpkg.com/svg.draggable.js/-/svg.draggable.js-2.2.2.tgz#c514a2f1405efb6f0263e7958f5b68fce50603ba"
|
||||
integrity sha512-JzNHBc2fLQMzYCZ90KZHN2ohXL0BQJGQimK1kGk6AvSeibuKcIdDX9Kr0dT9+UJ5O8nYA0RB839Lhvk4CY4MZw==
|
||||
dependencies:
|
||||
svg.js "^2.0.1"
|
||||
|
||||
svg.easing.js@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/svg.easing.js/-/svg.easing.js-2.0.0.tgz#8aa9946b0a8e27857a5c40a10eba4091e5691f12"
|
||||
integrity sha1-iqmUawqOJ4V6XEChDrpAkeVpHxI=
|
||||
dependencies:
|
||||
svg.js ">=2.3.x"
|
||||
|
||||
svg.filter.js@^2.0.2:
|
||||
version "2.0.2"
|
||||
resolved "https://registry.yarnpkg.com/svg.filter.js/-/svg.filter.js-2.0.2.tgz#91008e151389dd9230779fcbe6e2c9a362d1c203"
|
||||
integrity sha1-kQCOFROJ3ZIwd5/L5uLJo2LRwgM=
|
||||
dependencies:
|
||||
svg.js "^2.2.5"
|
||||
|
||||
svg.js@>=2.3.x, svg.js@^2.0.1, svg.js@^2.2.5, svg.js@^2.4.0, svg.js@^2.6.5:
|
||||
version "2.7.1"
|
||||
resolved "https://registry.yarnpkg.com/svg.js/-/svg.js-2.7.1.tgz#eb977ed4737001eab859949b4a398ee1bb79948d"
|
||||
integrity sha512-ycbxpizEQktk3FYvn/8BH+6/EuWXg7ZpQREJvgacqn46gIddG24tNNe4Son6omdXCnSOaApnpZw6MPCBA1dODA==
|
||||
|
||||
svg.pathmorphing.js@^0.1.3:
|
||||
version "0.1.3"
|
||||
resolved "https://registry.yarnpkg.com/svg.pathmorphing.js/-/svg.pathmorphing.js-0.1.3.tgz#c25718a1cc7c36e852ecabc380e758ac09bb2b65"
|
||||
integrity sha512-49HWI9X4XQR/JG1qXkSDV8xViuTLIWm/B/7YuQELV5KMOPtXjiwH4XPJvr/ghEDibmLQ9Oc22dpWpG0vUDDNww==
|
||||
dependencies:
|
||||
svg.js "^2.4.0"
|
||||
|
||||
svg.resize.js@^1.4.3:
|
||||
version "1.4.3"
|
||||
resolved "https://registry.yarnpkg.com/svg.resize.js/-/svg.resize.js-1.4.3.tgz#885abd248e0cd205b36b973c4b578b9a36f23332"
|
||||
integrity sha512-9k5sXJuPKp+mVzXNvxz7U0uC9oVMQrrf7cFsETznzUDDm0x8+77dtZkWdMfRlmbkEEYvUn9btKuZ3n41oNA+uw==
|
||||
dependencies:
|
||||
svg.js "^2.6.5"
|
||||
svg.select.js "^2.1.2"
|
||||
|
||||
svg.select.js@^2.1.2:
|
||||
version "2.1.2"
|
||||
resolved "https://registry.yarnpkg.com/svg.select.js/-/svg.select.js-2.1.2.tgz#e41ce13b1acff43a7441f9f8be87a2319c87be73"
|
||||
integrity sha512-tH6ABEyJsAOVAhwcCjF8mw4crjXSI1aa7j2VQR8ZuJ37H2MBUbyeqYr5nEO7sSN3cy9AR9DUwNg0t/962HlDbQ==
|
||||
dependencies:
|
||||
svg.js "^2.2.5"
|
||||
|
||||
svg.select.js@^3.0.1:
|
||||
version "3.0.1"
|
||||
resolved "https://registry.yarnpkg.com/svg.select.js/-/svg.select.js-3.0.1.tgz#a4198e359f3825739226415f82176a90ea5cc917"
|
||||
integrity sha512-h5IS/hKkuVCbKSieR9uQCj9w+zLHoPh+ce19bBYyqF53g6mnPB8sAtIbe1s9dh2S2fCmYX2xel1Ln3PJBbK4kw==
|
||||
dependencies:
|
||||
svg.js "^2.6.5"
|
||||
|
||||
svgo@^1.0.0:
|
||||
version "1.3.2"
|
||||
resolved "https://registry.yarnpkg.com/svgo/-/svgo-1.3.2.tgz#b6dc511c063346c9e415b81e43401145b96d4167"
|
||||
@@ -9627,6 +9694,11 @@ void-elements@^3.1.0:
|
||||
resolved "https://registry.yarnpkg.com/void-elements/-/void-elements-3.1.0.tgz#614f7fbf8d801f0bb5f0661f5b2f5785750e4f09"
|
||||
integrity sha1-YU9/v42AHwu18GYfWy9XhXUOTwk=
|
||||
|
||||
vue-apexcharts@^1.6.0:
|
||||
version "1.6.0"
|
||||
resolved "https://registry.yarnpkg.com/vue-apexcharts/-/vue-apexcharts-1.6.0.tgz#c7d3afd93f712433d404e5ceeb4e3aa65f422af2"
|
||||
integrity sha512-sT6tuVTLBwfH3TA7azecDNS/W70bmz14ZJI7aE7QIqcG9I6OywyH7x3hcOeY1v1DxttI8Svc5RuYj4Dd+A5F4g==
|
||||
|
||||
vue-cli-plugin-axios@~0.0.4:
|
||||
version "0.0.4"
|
||||
resolved "https://registry.yarnpkg.com/vue-cli-plugin-axios/-/vue-cli-plugin-axios-0.0.4.tgz#29d4eb48275c7fe15b92e1fd5d95fbe2a966436f"
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
"""App. related serializers."""
|
||||
|
||||
from django.utils.translation import ugettext as _
|
||||
|
||||
from rest_framework import serializers
|
||||
|
||||
|
||||
PERIODS = [
|
||||
("day", "Day"),
|
||||
("week", "Week"),
|
||||
("month", "Month"),
|
||||
("year", "Year"),
|
||||
("custom", "Custom")
|
||||
]
|
||||
|
||||
|
||||
class StatisticsInputSerializer(serializers.Serializer):
|
||||
"""Serializer used to filter statistics."""
|
||||
|
||||
gset = serializers.CharField()
|
||||
period = serializers.ChoiceField(choices=PERIODS)
|
||||
graphic = serializers.CharField(required=False)
|
||||
searchquery = serializers.CharField(required=False)
|
||||
start = serializers.DateField(required=False)
|
||||
end = serializers.DateField(required=False)
|
||||
|
||||
def validate(self, data):
|
||||
condition = (
|
||||
data["period"] == "custom" and
|
||||
(not data.get("start") or not data.get("end"))
|
||||
)
|
||||
if condition:
|
||||
raise serializers.ValidationError(
|
||||
_("You must provide start and end dates when period is custom")
|
||||
)
|
||||
return data
|
||||
|
||||
|
||||
class GraphPointSerializer(serializers.Serializer):
|
||||
"""A serializer to represent a point in a curve."""
|
||||
|
||||
x = serializers.FloatField()
|
||||
y = serializers.FloatField()
|
||||
|
||||
|
||||
class GraphCurveSerializer(serializers.Serializer):
|
||||
"""A serializer to represent a curve in a graph."""
|
||||
|
||||
name = serializers.CharField()
|
||||
backgroundColor = serializers.CharField()
|
||||
data = GraphPointSerializer(many=True)
|
||||
|
||||
|
||||
class GraphSerializer(serializers.Serializer):
|
||||
"""A serializer to represent a graph."""
|
||||
|
||||
title = serializers.CharField()
|
||||
series = GraphCurveSerializer(many=True)
|
||||
|
||||
|
||||
class StatisticsSerializer(serializers.Serializer):
|
||||
"""Serializer to return statistics."""
|
||||
|
||||
graphs = GraphSerializer(many=True)
|
||||
@@ -0,0 +1,11 @@
|
||||
"""App related API urls."""
|
||||
|
||||
from rest_framework import routers
|
||||
|
||||
from . import viewsets
|
||||
|
||||
router = routers.SimpleRouter()
|
||||
router.register(
|
||||
r"statistics", viewsets.StatisticsViewSet, basename="statistics")
|
||||
|
||||
urlpatterns = router.urls
|
||||
@@ -0,0 +1,45 @@
|
||||
"""App. related viewsets."""
|
||||
|
||||
import time
|
||||
|
||||
from drf_spectacular.utils import extend_schema
|
||||
from rest_framework import permissions, response, viewsets
|
||||
|
||||
from ... import signals
|
||||
from . import serializers
|
||||
|
||||
|
||||
class StatisticsViewSet(viewsets.ViewSet):
|
||||
"""A viewset to provide extra route related to mail statistics."""
|
||||
|
||||
permission_classes = (permissions.IsAuthenticated, )
|
||||
|
||||
@extend_schema(
|
||||
parameters=[serializers.StatisticsInputSerializer],
|
||||
responses={200: serializers.StatisticsSerializer}
|
||||
)
|
||||
def list(self, request, **kwargs):
|
||||
serializer = serializers.StatisticsInputSerializer(data=request.GET)
|
||||
serializer.is_valid(raise_exception=True)
|
||||
graph_sets = {}
|
||||
for result in signals.get_graph_sets.send(
|
||||
sender="index", user=request.user):
|
||||
graph_sets.update(result[1])
|
||||
gset = serializer.validated_data["gset"]
|
||||
fname = graph_sets[gset].get_file_name(
|
||||
request.user, serializer.validated_data.get("searchquery"))
|
||||
period = serializer.validated_data["period"]
|
||||
if period == "custom":
|
||||
start = int(
|
||||
time.mktime(serializer.validated_data["start"].timetuple())
|
||||
)
|
||||
end = int(
|
||||
time.mktime(serializer.validated_data["end"].timetuple())
|
||||
)
|
||||
else:
|
||||
end = int(time.mktime(time.localtime()))
|
||||
start = "-1{}".format(period)
|
||||
graphs = graph_sets[gset].export(
|
||||
fname, start, end, serializer.validated_data.get("graphic")
|
||||
)
|
||||
return response.Response({"graphs": graphs})
|
||||
+22
-27
@@ -1,12 +1,11 @@
|
||||
"""Classes to define graphics."""
|
||||
|
||||
import datetime
|
||||
import json
|
||||
import inspect
|
||||
from itertools import chain
|
||||
import os
|
||||
|
||||
from lxml import etree
|
||||
import six
|
||||
|
||||
from django.conf import settings
|
||||
from django.utils.encoding import smart_bytes, smart_text
|
||||
from django.utils.translation import ugettext as _, ugettext_lazy
|
||||
@@ -18,7 +17,6 @@ from modoboa.parameters import tools as param_tools
|
||||
|
||||
|
||||
class Curve:
|
||||
|
||||
"""Graphic curve.
|
||||
|
||||
Simple way to represent a graphic curve.
|
||||
@@ -96,36 +94,33 @@ class Graphic:
|
||||
return smart_text(dpath)
|
||||
|
||||
def export(self, rrdfile, start, end):
|
||||
"""Export data to XML using rrdtool and convert it to JSON."""
|
||||
"""Export data to JSON using rrdtool."""
|
||||
result = []
|
||||
cmdargs = []
|
||||
for curve in self._curves:
|
||||
result += [{
|
||||
"name": str(curve.legend),
|
||||
"color": curve.color, "data": []
|
||||
"backgroundColor": curve.color, "data": []
|
||||
}]
|
||||
cmdargs += curve.to_rrd_command_args(rrdfile)
|
||||
code = 0
|
||||
|
||||
cmd = "{} xport --start {} --end {} ".format(
|
||||
cmd = "{} xport --json -t --start {} --end {} ".format(
|
||||
self.rrdtool_binary, str(start), str(end))
|
||||
cmd += " ".join(cmdargs)
|
||||
code, output = exec_cmd(smart_bytes(cmd))
|
||||
if code:
|
||||
return []
|
||||
|
||||
tree = etree.fromstring(output)
|
||||
timestamp = int(tree.xpath('/xport/meta/start')[0].text)
|
||||
step = int(tree.xpath('/xport/meta/step')[0].text)
|
||||
for row in tree.xpath('/xport/data/row'):
|
||||
for vindex, value in enumerate(row.findall('v')):
|
||||
if value.text == 'NaN':
|
||||
result[vindex]['data'].append({'x': timestamp, 'y': 0})
|
||||
else:
|
||||
result[vindex]['data'].append(
|
||||
{'x': timestamp, 'y': float(value.text)}
|
||||
)
|
||||
timestamp += step
|
||||
xport = json.loads(output)
|
||||
for row in xport['data']:
|
||||
timestamp = int(row[0])
|
||||
date = datetime.datetime.fromtimestamp(timestamp).isoformat(sep=' ')
|
||||
for (vindex, value) in enumerate(row[1:]):
|
||||
result[vindex]['data'].append(
|
||||
{'x': date, 'y': value, 'timestamp': timestamp}
|
||||
)
|
||||
|
||||
return result
|
||||
|
||||
|
||||
@@ -158,13 +153,14 @@ class GraphicSet(object):
|
||||
"""Return database file name."""
|
||||
return self.file_name
|
||||
|
||||
def export(self, rrdfile, start, end):
|
||||
def export(self, rrdfile, start, end, graphic=None):
|
||||
result = {}
|
||||
for graph in self.graphics:
|
||||
result[graph.display_name] = {
|
||||
"title": six.text_type(graph.title),
|
||||
"curves": graph.export(rrdfile, start, end)
|
||||
}
|
||||
if graphic is None or graphic == graph.display_name:
|
||||
result[graph.display_name] = {
|
||||
"title": graph.title,
|
||||
"series": graph.export(rrdfile, start, end)
|
||||
}
|
||||
return result
|
||||
|
||||
|
||||
@@ -246,10 +242,9 @@ class MailTraffic(GraphicSet):
|
||||
raise exceptions.PermDeniedException
|
||||
return results[0].name
|
||||
|
||||
def get_file_name(self, request):
|
||||
def get_file_name(self, user, searchq):
|
||||
"""Retrieve file name according to user and args."""
|
||||
searchq = request.GET.get("searchquery", None)
|
||||
return self._check_domain_access(request.user, searchq)
|
||||
return self._check_domain_access(user, searchq)
|
||||
|
||||
|
||||
class AccountCreationGraphic(Graphic):
|
||||
|
||||
@@ -21,6 +21,7 @@ urlpatterns = [
|
||||
path('', include("modoboa.limits.urls_api")),
|
||||
path('', include("modoboa.relaydomains.urls_api")),
|
||||
path('', include("modoboa.dnstools.api.v2.urls")),
|
||||
path('', include("modoboa.maillog.api.v2.urls")),
|
||||
]
|
||||
|
||||
urlpatterns += exts_pool.get_urls(category="api")
|
||||
|
||||
Reference in New Issue
Block a user