mirror of
https://github.com/mail-in-a-box/mailinabox.git
synced 2026-03-05 15:57:23 +01:00
Add IMAP connection reporting
Fix binsizes and barwidths on timeseries charts Fix timezone issue in timeseries scales
This commit is contained in:
@@ -57,7 +57,7 @@ Vue.component('chart-multi-line-timeseries', {
|
||||
.text("no data");
|
||||
}
|
||||
|
||||
this.xscale = d3.scaleUtc()
|
||||
this.xscale = d3.scaleTime()
|
||||
.domain(d3.extent(this.tsdata.dates))
|
||||
.nice()
|
||||
.range([this.margin.left, this.width - this.margin.right])
|
||||
|
||||
@@ -101,13 +101,14 @@ Vue.component('chart-stacked-bar-timeseries', {
|
||||
.text("no data");
|
||||
}
|
||||
|
||||
this.xscale = d3.scaleUtc()
|
||||
this.xscale = d3.scaleTime()
|
||||
.domain(d3.extent(this.tsdata.dates))
|
||||
.nice()
|
||||
.range([this.margin.left, this.width - this.margin.right])
|
||||
|
||||
var barwidth = this.tsdata.barwidth(this.xscale, 1);
|
||||
var padding = barwidth / 2;
|
||||
var barwidth = this.tsdata.barwidth(this.xscale);
|
||||
var padding_x = barwidth / 2;
|
||||
var padding_y = ChartVue.get_yAxisLegendBounds(this.tsdata).height + 2;
|
||||
|
||||
this.yscale = d3.scaleLinear()
|
||||
.domain([
|
||||
@@ -115,28 +116,30 @@ Vue.component('chart-stacked-bar-timeseries', {
|
||||
d3.sum(this.tsdata.series, s => d3.max(s.values))
|
||||
])
|
||||
.range([
|
||||
this.height - this.margin.bottom,
|
||||
this.height - this.margin.bottom - padding_y,
|
||||
this.margin.top,
|
||||
]);
|
||||
|
||||
svg.append("g")
|
||||
.call(this.xAxis.bind(this, padding))
|
||||
|
||||
var g = svg.append("g")
|
||||
.attr("transform", `translate(0, ${padding_y})`);
|
||||
|
||||
g.append("g")
|
||||
.call(this.xAxis.bind(this, padding_x))
|
||||
.attr("font-size", ChartPrefs.axis_font_size);
|
||||
|
||||
svg.append("g")
|
||||
.call(this.yAxis.bind(this))
|
||||
g.append("g")
|
||||
.call(this.yAxis.bind(this, padding_y))
|
||||
.attr("font-size", ChartPrefs.axis_font_size);
|
||||
|
||||
|
||||
for (var s_idx=0; s_idx<this.tsdata.series.length; s_idx++) {
|
||||
svg.append("g")
|
||||
g.append("g")
|
||||
.datum(s_idx)
|
||||
.attr("fill", this.colors[s_idx])
|
||||
.selectAll("rect")
|
||||
.data(this.stacked[s_idx])
|
||||
.join("rect")
|
||||
.attr("x", d => this.xscale(d.data.date) - barwidth/2 + padding)
|
||||
.attr("y", d => this.yscale(d[1]))
|
||||
.attr("x", d => this.xscale(d.data.date) - barwidth/2 + padding_x)
|
||||
.attr("y", d => this.yscale(d[1]) + padding_y)
|
||||
.attr("height", d => this.yscale(d[0]) - this.yscale(d[1]))
|
||||
.attr("width", barwidth)
|
||||
.call( hover.bind(this) )
|
||||
@@ -146,7 +149,13 @@ Vue.component('chart-stacked-bar-timeseries', {
|
||||
;
|
||||
}
|
||||
|
||||
var hovinfo = svg.append("g");
|
||||
g.append("g")
|
||||
.attr("transform", `translate(${this.margin.left}, 0)`)
|
||||
.call(
|
||||
g => ChartVue.add_yAxisLegend(g, this.tsdata, this.colors)
|
||||
);
|
||||
|
||||
var hovinfo = g.append("g");
|
||||
|
||||
function hover(rect) {
|
||||
if ("ontouchstart" in document) rect
|
||||
@@ -165,10 +174,11 @@ Vue.component('chart-stacked-bar-timeseries', {
|
||||
var s_name = this.tsdata.series[s_idx].name;
|
||||
var v = d.data[s_name];
|
||||
var x = Number(rect.attr('x')) + barwidth/2;
|
||||
|
||||
//var y = Number(rect.attr('y')) + Number(rect.attr('height'))/2;
|
||||
var y = Number(rect.attr('y'));
|
||||
hovinfo.attr(
|
||||
"transform",
|
||||
`translate( ${x}, ${rect.attr('y')} )`)
|
||||
`translate( ${x}, ${y} )`)
|
||||
.append('text')
|
||||
.attr("font-family", ChartPrefs.default_font_family)
|
||||
.attr("font-size", ChartPrefs.default_font_size)
|
||||
@@ -203,18 +213,16 @@ Vue.component('chart-stacked-bar-timeseries', {
|
||||
return x;
|
||||
},
|
||||
|
||||
yAxis: function(g) {
|
||||
yAxis: function(padding, g) {
|
||||
var y = g.attr(
|
||||
"transform",
|
||||
`translate(${this.margin.left},0)`
|
||||
`translate(${this.margin.left},${padding})`
|
||||
).call(
|
||||
d3.axisLeft(this.yscale)
|
||||
.ticks(this.height/50)
|
||||
).call(g =>
|
||||
g.select(".domain").remove()
|
||||
).call(g => {
|
||||
ChartVue.add_yAxisLegend(g, this.tsdata, this.colors);
|
||||
});
|
||||
).call(
|
||||
g => g.select(".domain").remove()
|
||||
);
|
||||
|
||||
return y;
|
||||
},
|
||||
|
||||
@@ -748,6 +748,15 @@ class ChartVue {
|
||||
return svg;
|
||||
}
|
||||
|
||||
|
||||
static get_yAxisLegendBounds(data) {
|
||||
const h = ChartPrefs.axis_font_size;
|
||||
return {
|
||||
width: h + 6,
|
||||
height: h * data.series.length
|
||||
};
|
||||
}
|
||||
|
||||
static add_yAxisLegend(g, data, colors) {
|
||||
//var gtick = g.select(".tick:last-of-type").append("g");
|
||||
const h = ChartPrefs.axis_font_size;
|
||||
@@ -853,9 +862,8 @@ class TimeseriesData {
|
||||
}
|
||||
|
||||
static binsizeOfRange(range) {
|
||||
// target 100-120 datapoints
|
||||
const target = 100;
|
||||
const tolerance = 0.2; // 20%
|
||||
// target roughly 75 datapoints
|
||||
const target = 75;
|
||||
|
||||
if (typeof range[0] == 'string') {
|
||||
var parser = d3.utcParse('%Y-%m-%d %H:%M:%S');
|
||||
@@ -865,27 +873,46 @@ class TimeseriesData {
|
||||
const span_min = Math.ceil(
|
||||
(range[1].getTime() - range[0].getTime()) / (1000*60*target)
|
||||
);
|
||||
const bin_days = Math.floor(span_min / (24*60));
|
||||
const bin_hours = Math.floor((span_min - bin_days*24*60) / 60);
|
||||
|
||||
var bin_days = Math.floor(span_min / (24*60));
|
||||
var bin_hours = Math.floor((span_min - bin_days*24*60) / 60);
|
||||
if (bin_days >= 1) {
|
||||
return bin_days * 24 * 60 +
|
||||
(bin_hours > (24 * tolerance) ? bin_hours*60: 0);
|
||||
if (bin_hours > 18) {
|
||||
bin_days += 1;
|
||||
bin_hours = 0;
|
||||
}
|
||||
else if (bin_hours > 6) {
|
||||
bin_hours = 12;
|
||||
}
|
||||
else {
|
||||
bin_hours = 0;
|
||||
}
|
||||
return bin_days * 24 * 60 + bin_hours*60;
|
||||
}
|
||||
|
||||
const bin_mins = span_min - bin_days*24*60 - bin_hours*60;
|
||||
if (bin_hours >= 1) {
|
||||
return bin_hours * 60 +
|
||||
(bin_mins > (60 * tolerance) ? bin_mins: 0 );
|
||||
var bin_mins = span_min - bin_days*24*60 - bin_hours*60;
|
||||
if (bin_mins > 45) {
|
||||
bin_hours += 1
|
||||
bin_mins = 0;
|
||||
}
|
||||
return bin_mins;
|
||||
else if (bin_mins > 15) {
|
||||
bin_mins = 30;
|
||||
}
|
||||
else {
|
||||
bin_mins = 0;
|
||||
}
|
||||
return bin_hours * 60 + bin_mins;
|
||||
}
|
||||
|
||||
barwidth(xscale, barspacing) {
|
||||
barwidth(xscale, barspacing, max_width) {
|
||||
/* get the width of a bar in a bar chart */
|
||||
var start = this.range[0];
|
||||
var end = this.range[1];
|
||||
var bins = (end.getTime() - start.getTime()) / (1000 * this.binsizeTimespan());
|
||||
return Math.max(1, (xscale.range()[1] - xscale.range()[0])/bins - (barspacing || 0));
|
||||
if (this.dates.length == 0) return 0; // no data
|
||||
barspacing = (barspacing === undefined) ? 2 : barspacing;
|
||||
max_width = (max_width === undefined) ? 75 : max_width;
|
||||
var first_date = this.dates[0];
|
||||
var last_date = this.dates[this.dates.length-1];
|
||||
var bins = (last_date.getTime() - first_date.getTime()) / (1000 * 60 * this.binsize);
|
||||
return Math.min(max_width, Math.max(1, (xscale(last_date) - xscale(first_date))/bins - barspacing));
|
||||
}
|
||||
|
||||
formatDateTimeLong(d) {
|
||||
|
||||
@@ -50,7 +50,7 @@ Vue.component('panel-messages-sent', function(resolve, reject) {
|
||||
},
|
||||
|
||||
height_recip: function() {
|
||||
return this.height / 2;
|
||||
return (this.height / 3) *2;
|
||||
},
|
||||
|
||||
radius_recip_pie: function() {
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
<b-button variant="primary" @click="change_user">Change user</b-button>
|
||||
</b-input-group-append>
|
||||
</b-input-group>
|
||||
<b-alert variant="warning" class="ml-2" :show="sent_mail && sent_mail.items.length>=get_row_limit() || received_mail && received_mail.items.length>=get_row_limit()"><sup>*</sup> Tables limited to {{ get_row_limit() }} rows <router-link to="/settings"><b-icon icon="gear-fill"></b-icon></router-link></b-alert>
|
||||
<b-alert variant="warning" class="ml-2" :show="sent_mail && sent_mail.items.length>=get_row_limit() || received_mail && received_mail.items.length>=get_row_limit() || imap_details && imap_details.items.length>=get_row_limit()"><sup>*</sup> Tables limited to {{ get_row_limit() }} rows <router-link to="/settings"><b-icon icon="gear-fill"></b-icon></router-link></b-alert>
|
||||
<b-form-checkbox class="ml-auto" v-model="show_only_flagged" @change="show_only_flagged_change()">Flagged only</b-form-checkbox>
|
||||
</b-form>
|
||||
|
||||
@@ -42,7 +42,7 @@
|
||||
</b-table>
|
||||
</b-tab>
|
||||
|
||||
<b-tab :title="`Received mail (${received_mail.items.length})`">
|
||||
<b-tab>
|
||||
<template #title>
|
||||
Received mail<sup v-if="received_mail.items.length >= get_row_limit()">*</sup> ({{received_mail.items.length}})
|
||||
</template>
|
||||
@@ -72,5 +72,31 @@
|
||||
</template>
|
||||
</b-table>
|
||||
</b-tab>
|
||||
|
||||
|
||||
<b-tab>
|
||||
<template #title>
|
||||
IMAP Connections<sup v-if="imap_details.items.length >= get_row_limit()">*</sup> ({{imap_details.items.length}})
|
||||
</template>
|
||||
<b-table
|
||||
class="sticky-table-header-0 bg-light"
|
||||
small
|
||||
:filter="show_only_flagged_filter"
|
||||
:filter-function="table_filter_cb"
|
||||
tbody-tr-class="cursor-pointer"
|
||||
details-td-class="cursor-default"
|
||||
@row-clicked="row_clicked"
|
||||
:items="imap_details.items"
|
||||
:fields="imap_details.fields">
|
||||
<template #row-details="row">
|
||||
<b-card>
|
||||
<div><strong>Connection disposition</strong>: {{ disposition_formatter(row.item.disposition) }}</div>
|
||||
<div><strong>Connection security</strong> {{ row.item.connection_security }}</div>
|
||||
<div><strong>Disconnect reason</strong> {{ row.item.disconnect_reason }}</div>
|
||||
</b-card>
|
||||
</template>
|
||||
</b-table>
|
||||
</b-tab>
|
||||
|
||||
</b-tabs>
|
||||
</div>
|
||||
|
||||
@@ -28,6 +28,7 @@ Vue.component('panel-user-activity', function(resolve, reject) {
|
||||
data_date_range: null, /* date range for active table data */
|
||||
sent_mail: null,
|
||||
received_mail: null,
|
||||
imap_details: null,
|
||||
all_users: [],
|
||||
disposition_formatter: ConnectionDisposition.formatter,
|
||||
};
|
||||
@@ -147,6 +148,15 @@ Vue.component('panel-user-activity', function(resolve, reject) {
|
||||
f.label = 'Envelope From (user)';
|
||||
},
|
||||
|
||||
combine_imap_details_fields: function() {
|
||||
// remove these fields
|
||||
this.imap_details.combine_fields([
|
||||
'disconnect_reason',
|
||||
'connection_security',
|
||||
]);
|
||||
},
|
||||
|
||||
|
||||
get_row_limit: function() {
|
||||
return UserSettings.get().row_limit;
|
||||
},
|
||||
@@ -239,7 +249,18 @@ Vue.component('panel-user-activity', function(resolve, reject) {
|
||||
this.received_mail
|
||||
.flag_fields()
|
||||
.get_field('connect_time')
|
||||
.add_tdClass('text-nowrap');
|
||||
.add_tdClass('text-nowrap');
|
||||
|
||||
/* setup imap_details */
|
||||
this.imap_details = new MailBvTable(
|
||||
response.data.imap_details, {
|
||||
_showDetails: true
|
||||
});
|
||||
this.combine_imap_details_fields();
|
||||
this.imap_details
|
||||
.flag_fields()
|
||||
.get_field('connect_time')
|
||||
.add_tdClass('text-nowrap');
|
||||
|
||||
}).catch(error => {
|
||||
this.$root.handleError(error);
|
||||
|
||||
Reference in New Issue
Block a user