mirror of
				https://github.com/mail-in-a-box/mailinabox.git
				synced 2025-10-30 18:50:53 +00:00 
			
		
		
		
	Split the User Activity/IMAP connections tab into two tables to better deal with the quantity of data
This commit is contained in:
		
							parent
							
								
									212b0b74cb
								
							
						
					
					
						commit
						36d9cbb4e8
					
				| @ -102,6 +102,18 @@ def add_reports(app, env, authorized_personnel_only): | |||||||
| 		finally: | 		finally: | ||||||
| 			db_conn_factory.close(conn) | 			db_conn_factory.close(conn) | ||||||
| 
 | 
 | ||||||
|  | 	@app.route('/reports/uidata/imap-details', methods=['POST']) | ||||||
|  | 	@authorized_personnel_only | ||||||
|  | 	@json_payload | ||||||
|  | 	def get_imap_details(payload): | ||||||
|  | 		conn = db_conn_factory.connect() | ||||||
|  | 		try: | ||||||
|  | 			return jsonify(uidata.imap_details(conn, payload)) | ||||||
|  | 		except uidata.InvalidArgsError as e: | ||||||
|  | 			return ('invalid request', 400) | ||||||
|  | 		finally: | ||||||
|  | 			db_conn_factory.close(conn) | ||||||
|  | 
 | ||||||
| 	@app.route('/reports/uidata/flagged-connections', methods=['POST']) | 	@app.route('/reports/uidata/flagged-connections', methods=['POST']) | ||||||
| 	@authorized_personnel_only | 	@authorized_personnel_only | ||||||
| 	@json_payload | 	@json_payload | ||||||
|  | |||||||
| @ -2,7 +2,8 @@ export default Vue.component('chart-table', { | |||||||
|     props: { |     props: { | ||||||
|         items: Array, |         items: Array, | ||||||
|         fields: Array, |         fields: Array, | ||||||
|         caption: String |         caption: String, | ||||||
|  |         small: { type:Boolean, default:true } | ||||||
|     }, |     }, | ||||||
| 
 | 
 | ||||||
|     /* <b-table-lite striped small :fields="fields_x" :items="items" caption-top><template #table-caption><span class="text-nowrap">{{caption}}</span></template></b-table>*/ |     /* <b-table-lite striped small :fields="fields_x" :items="items" caption-top><template #table-caption><span class="text-nowrap">{{caption}}</span></template></b-table>*/ | ||||||
| @ -19,7 +20,7 @@ export default Vue.component('chart-table', { | |||||||
|         var table = ce('b-table-lite', { |         var table = ce('b-table-lite', { | ||||||
|             props: { |             props: { | ||||||
|                 'striped': true, |                 'striped': true, | ||||||
|                 'small': true, |                 'small': this.small, | ||||||
|                 'fields': this.fields_x, |                 'fields': this.fields_x, | ||||||
|                 'items': this.items, |                 'items': this.items, | ||||||
|                 'caption-top': true |                 'caption-top': true | ||||||
|  | |||||||
| @ -493,6 +493,8 @@ export class BvTableField { | |||||||
|         } |         } | ||||||
|         else if (ft.type == 'number') { |         else if (ft.type == 'number') { | ||||||
|             if (ft.subtype == 'plain' || |             if (ft.subtype == 'plain' || | ||||||
|  |                 ft.subtype === null || | ||||||
|  |                 ft.subtype === undefined || | ||||||
|                 ft.subtype == 'decimal' && isNaN(ft.places) |                 ft.subtype == 'decimal' && isNaN(ft.places) | ||||||
|                ) |                ) | ||||||
|             { |             { | ||||||
|  | |||||||
| @ -84,26 +84,30 @@ | |||||||
| 
 | 
 | ||||||
|     <b-tab> |     <b-tab> | ||||||
|       <template #title> |       <template #title> | ||||||
|         IMAP Connections<sup v-if="imap_details.items.length >= get_row_limit()">*</sup> ({{imap_details.items.length}}) |         IMAP Connections | ||||||
|       </template> |       </template> | ||||||
|  | 
 | ||||||
|       <b-table |       <b-table | ||||||
|         class="sticky-table-header-0 bg-light" |         tbody-tr-class="cursor-pointer" | ||||||
|         small |         selectable | ||||||
|  |         select-mode="single" | ||||||
|         :filter="show_only_flagged_filter" |         :filter="show_only_flagged_filter" | ||||||
|         :filter-function="table_filter_cb" |         :filter-function="table_filter_cb" | ||||||
|         tbody-tr-class="cursor-pointer" |         :items="imap_conn_summary.items" | ||||||
|         details-td-class="cursor-default" |         :fields="imap_conn_summary.fields" | ||||||
|         @row-clicked="row_clicked" |         @row-clicked="load_imap_details"> | ||||||
|         :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-table>        | ||||||
|  | 
 | ||||||
|  |       <div v-if="imap_details" class="bg-white"> | ||||||
|  |         <div class="mt-3 text-center bg-info p-1">{{imap_details._desc}} ({{imap_details.items.length}} rows<sup v-if="imap_details.items.length >= get_row_limit()">*</sup>)</div> | ||||||
|  |         <b-table | ||||||
|  |           class="sticky-table-header-0" | ||||||
|  |           small | ||||||
|  |           :items="imap_details.items" | ||||||
|  |           :fields="imap_details.fields"> | ||||||
|  |         </b-table> | ||||||
|  |       </div> | ||||||
|  |        | ||||||
|     </b-tab> |     </b-tab> | ||||||
| 
 | 
 | ||||||
|   </b-tabs> |   </b-tabs> | ||||||
|  | |||||||
| @ -3,9 +3,10 @@ | |||||||
| */ | */ | ||||||
| 
 | 
 | ||||||
| import wbr_text from "./wbr-text.js"; | import wbr_text from "./wbr-text.js"; | ||||||
|  | import chart_table from "./chart-table.js"; | ||||||
| import message_headers_view from "./message_headers_view.js"; | import message_headers_view from "./message_headers_view.js"; | ||||||
| import UserSettings from "./settings.js"; | import UserSettings from "./settings.js"; | ||||||
| import { MailBvTable, ConnectionDisposition } from "./charting.js"; | import { BvTable, MailBvTable, ConnectionDisposition } from "./charting.js"; | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| export default Vue.component('panel-user-activity', function(resolve, reject) { | export default Vue.component('panel-user-activity', function(resolve, reject) { | ||||||
| @ -19,7 +20,8 @@ export default Vue.component('panel-user-activity', function(resolve, reject) { | |||||||
| 
 | 
 | ||||||
|         components: { |         components: { | ||||||
|             'wbr-text': wbr_text, |             'wbr-text': wbr_text, | ||||||
|             'message-headers-view': message_headers_view |             'message-headers-view': message_headers_view, | ||||||
|  |             'chart-table': chart_table, | ||||||
|         }, |         }, | ||||||
|          |          | ||||||
|         data: function() { |         data: function() { | ||||||
| @ -36,6 +38,7 @@ export default Vue.component('panel-user-activity', function(resolve, reject) { | |||||||
|                 data_date_range: null, /* date range for active table data */ |                 data_date_range: null, /* date range for active table data */ | ||||||
|                 sent_mail: null, |                 sent_mail: null, | ||||||
|                 received_mail: null, |                 received_mail: null, | ||||||
|  |                 imap_conn_summary: null, | ||||||
|                 imap_details: null, |                 imap_details: null, | ||||||
|                 lmtp_id: null, /* for message headers modal */ |                 lmtp_id: null, /* for message headers modal */ | ||||||
|                 all_users: [], |                 all_users: [], | ||||||
| @ -169,11 +172,19 @@ export default Vue.component('panel-user-activity', function(resolve, reject) { | |||||||
|                 f.label = 'Envelope From (user)'; |                 f.label = 'Envelope From (user)'; | ||||||
|             }, |             }, | ||||||
| 
 | 
 | ||||||
|  |             combine_imap_conn_summary_fields: function() { | ||||||
|  |                 // remove 'first_conn_time'
 | ||||||
|  |                 this.imap_conn_summary.combine_fields('first_connection_time'); | ||||||
|  |                 // clear the label for the 'total' column (pct)
 | ||||||
|  |                 const f_total = this.imap_conn_summary.get_field('total'); | ||||||
|  |                 f_total.label = ''; | ||||||
|  |             }, | ||||||
|  | 
 | ||||||
|             combine_imap_details_fields: function() { |             combine_imap_details_fields: function() { | ||||||
|                 // remove these fields
 |                 // remove these fields
 | ||||||
|                 this.imap_details.combine_fields([ |                 this.imap_details.combine_fields([ | ||||||
|                     'disconnect_reason', |                     'remote_host', | ||||||
|                     'connection_security', |                     'disposition', | ||||||
|                 ]); |                 ]); | ||||||
|             }, |             }, | ||||||
| 
 | 
 | ||||||
| @ -272,16 +283,21 @@ export default Vue.component('panel-user-activity', function(resolve, reject) { | |||||||
|                         .get_field('connect_time') |                         .get_field('connect_time') | ||||||
|                         .add_tdClass('text-nowrap'); |                         .add_tdClass('text-nowrap'); | ||||||
| 
 | 
 | ||||||
|                     /* setup imap_details */ |                      | ||||||
|                     this.imap_details = new MailBvTable( |                     /* setup imap_conn_summary */ | ||||||
|                         response.data.imap_details, { |                     this.imap_conn_summary = new MailBvTable( | ||||||
|                             _showDetails: true |                         response.data.imap_conn_summary | ||||||
|  |                     ); | ||||||
|  |                     this.combine_imap_conn_summary_fields(); | ||||||
|  |                     this.imap_conn_summary.flag_fields(); | ||||||
|  |                     ['last_connection_time','count'] | ||||||
|  |                         .forEach(name => { | ||||||
|  |                             const f = this.imap_conn_summary.get_field(name); | ||||||
|  |                             f.add_cls('text-nowrap', 'tdClass'); | ||||||
|                         }); |                         }); | ||||||
|                     this.combine_imap_details_fields(); | 
 | ||||||
|                     this.imap_details |                     /* clear imap_details */ | ||||||
|                         .flag_fields() |                     this.imap_details = null; | ||||||
|                         .get_field('connect_time') |  | ||||||
|                         .add_tdClass('text-nowrap'); |  | ||||||
| 
 | 
 | ||||||
|                 }).catch(error => { |                 }).catch(error => { | ||||||
|                     this.$root.handleError(error); |                     this.$root.handleError(error); | ||||||
| @ -304,6 +320,36 @@ export default Vue.component('panel-user-activity', function(resolve, reject) { | |||||||
|                  |                  | ||||||
|                 // show the modal dialog
 |                 // show the modal dialog
 | ||||||
|                 this.$refs.message_headers_modal.show(); |                 this.$refs.message_headers_modal.show(); | ||||||
|  |             }, | ||||||
|  | 
 | ||||||
|  |             load_imap_details: function(item, index, event) { | ||||||
|  |                 this.$emit('loading', 1); | ||||||
|  |                 this.imap_details = null; | ||||||
|  |                 const promise = axios.post('reports/uidata/imap-details', { | ||||||
|  |                     row_limit: this.get_row_limit(), | ||||||
|  |                     user_id: this.user_id.trim(), | ||||||
|  |                     start_date: this.date_range[0], | ||||||
|  |                     end_date: this.date_range[1], | ||||||
|  |                     disposition: item.disposition, | ||||||
|  |                     remote_host: item.remote_host | ||||||
|  |                 }).then(response => { | ||||||
|  |                     this.imap_details = new MailBvTable( | ||||||
|  |                         response.data.imap_details | ||||||
|  |                     ); | ||||||
|  |                     this.combine_imap_details_fields(); | ||||||
|  |                     this.imap_details.get_field('connect_time') | ||||||
|  |                         .add_tdClass('text-nowrap'); | ||||||
|  |                     this.imap_details.get_field('disconnect_time') | ||||||
|  |                         .add_tdClass('text-nowrap'); | ||||||
|  |                     this.imap_details._desc = | ||||||
|  |                         `${item.remote_host}/${item.disposition}`; | ||||||
|  |                      | ||||||
|  |                 }).catch(error => { | ||||||
|  |                     this.$root.handleError(error); | ||||||
|  |                      | ||||||
|  |                 }).finally( () => { | ||||||
|  |                     this.$emit('loading', -1); | ||||||
|  |                 }); | ||||||
|             } |             } | ||||||
|              |              | ||||||
|         } |         } | ||||||
|  | |||||||
| @ -22,7 +22,7 @@ class Timeseries(object): | |||||||
|         # parsefmt is a date parser string to be used to re-interpret |         # parsefmt is a date parser string to be used to re-interpret | ||||||
|         # "bin" grouping dates (data.dates) to native dates. server |         # "bin" grouping dates (data.dates) to native dates. server | ||||||
|         # always returns utc dates |         # always returns utc dates | ||||||
|         parsefmt = '%Y-%m-%d %H:%M:%S' |         self.parsefmt = '%Y-%m-%d %H:%M:%S' | ||||||
| 
 | 
 | ||||||
|         self.dates = []   # dates must be "bin" date strings |         self.dates = []   # dates must be "bin" date strings | ||||||
|         self.series = [] |         self.series = [] | ||||||
| @ -31,7 +31,7 @@ class Timeseries(object): | |||||||
|             'range': [ self.start, self.end ], |             'range': [ self.start, self.end ], | ||||||
|             'range_parse_format': '%Y-%m-%d %H:%M:%S', |             'range_parse_format': '%Y-%m-%d %H:%M:%S', | ||||||
|             'binsize': self.binsize, |             'binsize': self.binsize, | ||||||
|             'date_parse_format': parsefmt, |             'date_parse_format': self.parsefmt, | ||||||
|             'y': desc, |             'y': desc, | ||||||
|             'dates': self.dates, |             'dates': self.dates, | ||||||
|             'series': self.series |             'series': self.series | ||||||
|  | |||||||
| @ -3,6 +3,7 @@ from .select_list_suggestions import select_list_suggestions | |||||||
| from .messages_sent import messages_sent | from .messages_sent import messages_sent | ||||||
| from .messages_received import messages_received | from .messages_received import messages_received | ||||||
| from .user_activity import user_activity | from .user_activity import user_activity | ||||||
|  | from .imap_details import imap_details | ||||||
| from .remote_sender_activity import remote_sender_activity | from .remote_sender_activity import remote_sender_activity | ||||||
| from .flagged_connections import flagged_connections | from .flagged_connections import flagged_connections | ||||||
| from .capture_db_stats import capture_db_stats | from .capture_db_stats import capture_db_stats | ||||||
|  | |||||||
							
								
								
									
										25
									
								
								management/reporting/uidata/imap_details.1.sql
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								management/reporting/uidata/imap_details.1.sql
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,25 @@ | |||||||
|  | -- | ||||||
|  | -- details on user imap connections | ||||||
|  | -- | ||||||
|  | SELECT | ||||||
|  |   connect_time, | ||||||
|  |   disconnect_time, | ||||||
|  |   CASE WHEN remote_host='unknown' THEN remote_ip ELSE remote_host END AS `remote_host`, | ||||||
|  |   sasl_method, | ||||||
|  |   disconnect_reason, | ||||||
|  |   connection_security, | ||||||
|  |   disposition, | ||||||
|  |   in_bytes, | ||||||
|  |   out_bytes | ||||||
|  | FROM | ||||||
|  |   imap_connection | ||||||
|  | WHERE | ||||||
|  |   sasl_username = :user_id AND | ||||||
|  |   connect_time >= :start_date AND | ||||||
|  |   connect_time < :end_date AND | ||||||
|  |   (:remote_host IS NULL OR | ||||||
|  |      remote_host = :remote_host OR remote_ip = :remote_host) AND | ||||||
|  |   (:disposition IS NULL OR | ||||||
|  |      disposition = :disposition) | ||||||
|  | ORDER BY | ||||||
|  |   connect_time | ||||||
							
								
								
									
										83
									
								
								management/reporting/uidata/imap_details.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										83
									
								
								management/reporting/uidata/imap_details.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,83 @@ | |||||||
|  | from .Timeseries import Timeseries | ||||||
|  | from .exceptions import InvalidArgsError | ||||||
|  | 
 | ||||||
|  | with open(__file__.replace('.py','.1.sql')) as fp: | ||||||
|  |     select_1 = fp.read() | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def imap_details(conn, args): | ||||||
|  |     ''' | ||||||
|  |     details on imap connections | ||||||
|  |     '''     | ||||||
|  |     try: | ||||||
|  |         user_id = args['user_id'] | ||||||
|  | 
 | ||||||
|  |         # use Timeseries to get a normalized start/end range | ||||||
|  |         ts = Timeseries( | ||||||
|  |             'IMAP details', | ||||||
|  |             args['start_date'], | ||||||
|  |             args['end_date'], | ||||||
|  |             0 | ||||||
|  |         ) | ||||||
|  | 
 | ||||||
|  |         # optional | ||||||
|  |         remote_host = args.get('remote_host') | ||||||
|  |         disposition = args.get('disposition') | ||||||
|  |          | ||||||
|  |     except KeyError: | ||||||
|  |         raise InvalidArgsError() | ||||||
|  | 
 | ||||||
|  |     # limit results | ||||||
|  |     try: | ||||||
|  |         limit = 'LIMIT ' + str(int(args.get('row_limit', 1000))); | ||||||
|  |     except ValueError: | ||||||
|  |         limit = 'LIMIT 1000' | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |     c = conn.cursor() | ||||||
|  | 
 | ||||||
|  |     imap_details = { | ||||||
|  |         'start': ts.start, | ||||||
|  |         'end': ts.end, | ||||||
|  |         'y': 'IMAP Details', | ||||||
|  |         'fields': [ | ||||||
|  |             'connect_time', | ||||||
|  |             'disconnect_time', | ||||||
|  |             'remote_host', | ||||||
|  |             'sasl_method', | ||||||
|  |             'disconnect_reason', | ||||||
|  |             'connection_security', | ||||||
|  |             'disposition', | ||||||
|  |             'in_bytes', | ||||||
|  |             'out_bytes' | ||||||
|  |         ], | ||||||
|  |         'field_types': [ | ||||||
|  |             { 'type':'datetime', 'format': ts.parsefmt }, # connect_time | ||||||
|  |             { 'type':'datetime', 'format': ts.parsefmt }, # disconnect_time | ||||||
|  |             'text/plain',    # remote_host | ||||||
|  |             'text/plain',    # sasl_method | ||||||
|  |             'text/plain',    # disconnect_reason | ||||||
|  |             'text/plain',    # connection_security | ||||||
|  |             'text/plain',    # disposition | ||||||
|  |             'number/size',   # in_bytes, | ||||||
|  |             'number/size',   # out_bytes, | ||||||
|  |         ], | ||||||
|  |         'items': [] | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     for row in c.execute(select_1 + limit, { | ||||||
|  |             'user_id': user_id, | ||||||
|  |             'start_date': ts.start, | ||||||
|  |             'end_date': ts.end, | ||||||
|  |             'remote_host': remote_host, | ||||||
|  |             'disposition': disposition | ||||||
|  |     }): | ||||||
|  |         v = [] | ||||||
|  |         for key in imap_details['fields']: | ||||||
|  |             v.append(row[key]) | ||||||
|  |         imap_details['items'].append(v) | ||||||
|  | 
 | ||||||
|  |      | ||||||
|  |     return { | ||||||
|  |         'imap_details': imap_details | ||||||
|  |     } | ||||||
| @ -1,20 +1,22 @@ | |||||||
| -- | -- | ||||||
| -- details on user imap connections | -- imap connection summary | ||||||
| -- | -- | ||||||
| SELECT | SELECT | ||||||
|   connect_time, |   count(*) as `count`, | ||||||
|   CASE WHEN remote_host='unknown' THEN remote_ip ELSE remote_host END AS `remote_host`, |  | ||||||
|   sasl_method, |  | ||||||
|   disconnect_reason, |  | ||||||
|   connection_security, |  | ||||||
|   disposition, |   disposition, | ||||||
|   in_bytes, |   CASE WHEN remote_host='unknown' THEN remote_ip ELSE remote_host END AS `remote_host`, | ||||||
|   out_bytes |   sum(in_bytes) as `in_bytes`, | ||||||
|  |   sum(out_bytes) as `out_bytes`, | ||||||
|  |   min(connect_time) as `first_connection_time`, | ||||||
|  |   max(connect_time) as `last_connection_time` | ||||||
| FROM | FROM | ||||||
|   imap_connection |   imap_connection | ||||||
| WHERE | WHERE | ||||||
|   sasl_username = :user_id AND |   sasl_username = :user_id AND | ||||||
|   connect_time >= :start_date AND |   connect_time >= :start_date AND | ||||||
|   connect_time < :end_date |   connect_time < :end_date | ||||||
|  | GROUP BY | ||||||
|  |   disposition, | ||||||
|  |   CASE WHEN remote_host='unknown' THEN remote_ip ELSE remote_host END | ||||||
| ORDER BY | ORDER BY | ||||||
|   connect_time |   `count` DESC, disposition | ||||||
|  | |||||||
| @ -200,50 +200,64 @@ def user_activity(conn, args): | |||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|     # |     # | ||||||
|     # imap connections by user |     # IMAP connections by disposition, by remote host | ||||||
|  |     #   Disposition | ||||||
|  |     #   Remote host | ||||||
|  |     #   Count | ||||||
|  |     #   In bytes (sum) | ||||||
|  |     #   Out bytes (sum) | ||||||
|  |     #   % of total | ||||||
|     # |     # | ||||||
| 
 | 
 | ||||||
|     imap_details = { |     imap_conn_summary = { | ||||||
|         'start': ts.start, |         'start': ts.start, | ||||||
|         'end': ts.end, |         'end': ts.end, | ||||||
|         'y': 'IMAP Details', |         'y': 'IMAP connection summary by host and disposition', | ||||||
|         'fields': [ |         'fields': [ | ||||||
|             'connect_time', |             'count', | ||||||
|  |             'total', | ||||||
|             'remote_host', |             'remote_host', | ||||||
|             'sasl_method', |  | ||||||
|             'disconnect_reason', |  | ||||||
|             'connection_security', |  | ||||||
|             'disposition', |             'disposition', | ||||||
|  |             'first_connection_time', | ||||||
|  |             'last_connection_time', | ||||||
|             'in_bytes', |             'in_bytes', | ||||||
|             'out_bytes' |             'out_bytes', | ||||||
|         ], |         ], | ||||||
|         'field_types': [ |         'field_types': [ | ||||||
|             { 'type':'datetime', 'format': '%Y-%m-%d %H:%M:%S' },# connect_time |             'number',        # count | ||||||
|  |             { 'type': 'number/percent', 'places': 1 }, # total | ||||||
|             'text/plain',    # remote_host |             'text/plain',    # remote_host | ||||||
|             'text/plain',    # sasl_method |  | ||||||
|             'text/plain',    # disconnect_reason |  | ||||||
|             'text/plain',    # connection_security |  | ||||||
|             'text/plain',    # disposition |             'text/plain',    # disposition | ||||||
|  |             { 'type':'datetime', 'format': ts.parsefmt }, # first_conn_time | ||||||
|  |             { 'type':'datetime', 'format': ts.parsefmt }, # last_conn_time | ||||||
|             'number/size',   # in_bytes, |             'number/size',   # in_bytes, | ||||||
|             'number/size',   # out_bytes, |             'number/size',   # out_bytes, | ||||||
|         ], |         ], | ||||||
|         'items': [] |         'items': [] | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     count_field_idx = 0 | ||||||
|  |     total_field_idx = 1 | ||||||
|  |     total = 0 | ||||||
|     for row in c.execute(select_3 + limit, { |     for row in c.execute(select_3 + limit, { | ||||||
|             'user_id': user_id, |             'user_id': user_id, | ||||||
|             'start_date': ts.start, |             'start_date': ts.start, | ||||||
|             'end_date': ts.end |             'end_date': ts.end | ||||||
|     }): |     }): | ||||||
|         v = [] |         v = [] | ||||||
|         for key in imap_details['fields']: |         for key in imap_conn_summary['fields']: | ||||||
|             v.append(row[key]) |             if key=='count': | ||||||
|         imap_details['items'].append(v) |                 total += row[key] | ||||||
|  |             if key!='total': | ||||||
|  |                 v.append(row[key]) | ||||||
| 
 | 
 | ||||||
|  |         imap_conn_summary['items'].append(v) | ||||||
| 
 | 
 | ||||||
|  |     for v in imap_conn_summary['items']: | ||||||
|  |         v.insert(total_field_idx, v[count_field_idx] / total) | ||||||
|          |          | ||||||
|     return { |     return { | ||||||
|         'sent_mail': sent_mail, |         'sent_mail': sent_mail, | ||||||
|         'received_mail': received_mail, |         'received_mail': received_mail, | ||||||
|         'imap_details': imap_details |         'imap_conn_summary': imap_conn_summary | ||||||
|     } |     } | ||||||
|  | |||||||
		Loading…
	
		Reference in New Issue
	
	Block a user