diff --git a/src/client/app/admin/views/queue.chart.vue b/src/client/app/admin/views/queue.chart.vue
new file mode 100644
index 0000000000..c3d68d75af
--- /dev/null
+++ b/src/client/app/admin/views/queue.chart.vue
@@ -0,0 +1,181 @@
+<template>
+<div>
+	<ui-info warn v-if="latestStats && latestStats.waiting > 0">The queue is jammed.</ui-info>
+	<ui-horizon-group inputs v-if="latestStats" class="fit-bottom">
+		<ui-input :value="latestStats.activeSincePrevTick | number" type="text" readonly>
+			<span>Process</span>
+			<template #prefix><fa :icon="fasPlayCircle"/></template>
+			<template #suffix>jobs/tick</template>
+		</ui-input>
+		<ui-input :value="latestStats.active | number" type="text" readonly>
+			<span>Active</span>
+			<template #prefix><fa :icon="farPlayCircle"/></template>
+			<template #suffix>jobs</template>
+		</ui-input>
+		<ui-input :value="latestStats.waiting | number" type="text" readonly>
+			<span>Waiting</span>
+			<template #prefix><fa :icon="faStopCircle"/></template>
+			<template #suffix>jobs</template>
+		</ui-input>
+		<ui-input :value="latestStats.delayed | number" type="text" readonly>
+			<span>Delayed</span>
+			<template #prefix><fa :icon="faStopwatch"/></template>
+			<template #suffix>jobs</template>
+		</ui-input>
+	</ui-horizon-group>
+	<div ref="chart" class="wptihjuy"></div>
+</div>
+</template>
+
+<script lang="ts">
+import Vue from 'vue';
+import i18n from '../../i18n';
+import ApexCharts from 'apexcharts';
+import * as tinycolor from 'tinycolor2';
+import { faStopwatch, faPlayCircle as fasPlayCircle } from '@fortawesome/free-solid-svg-icons';
+import { faStopCircle, faPlayCircle as farPlayCircle } from '@fortawesome/free-regular-svg-icons';
+
+export default Vue.extend({
+	i18n: i18n('admin/views/queue.vue'),
+
+	props: {
+		type: {
+			type: String,
+			required: true
+		},
+		connection: {
+			required: true
+		},
+		limit: {
+			type: Number,
+			required: true
+		}
+	},
+
+	data() {
+		return {
+			stats: [],
+			chart: null,
+			faStopwatch, faStopCircle, farPlayCircle, fasPlayCircle
+		};
+	},
+
+	computed: {
+		latestStats(): any {
+			return this.stats.length > 0 ? this.stats[this.stats.length - 1][this.type] : null;
+		}
+	},
+
+	watch: {
+		stats(stats) {
+			this.chart.updateSeries([{
+				name: 'Process',
+				type: 'area',
+				data: stats.map((x, i) => ({ x: i, y: x[this.type].activeSincePrevTick }))
+			}, {
+				name: 'Active',
+				type: 'area',
+				data: stats.map((x, i) => ({ x: i, y: x[this.type].active }))
+			}, {
+				name: 'Waiting',
+				type: 'line',
+				data: stats.map((x, i) => ({ x: i, y: x[this.type].waiting }))
+			}, {
+				name: 'Delayed',
+				type: 'line',
+				data: stats.map((x, i) => ({ x: i, y: x[this.type].delayed }))
+			}]);
+		},
+	},
+
+	mounted() {
+		this.chart = new ApexCharts(this.$refs.chart, {
+			chart: {
+				id: this.type,
+				group: 'queue',
+				type: 'area',
+				height: 200,
+				animations: {
+					dynamicAnimation: {
+						enabled: false
+					}
+				},
+				toolbar: {
+					show: false
+				},
+				zoom: {
+					enabled: false
+				}
+			},
+			dataLabels: {
+				enabled: false
+			},
+			grid: {
+				clipMarkers: false,
+				borderColor: 'rgba(0, 0, 0, 0.1)',
+				xaxis: {
+					lines: {
+						show: true,
+					}
+				},
+			},
+			stroke: {
+				curve: 'straight',
+				width: 2
+			},
+			tooltip: {
+				enabled: false
+			},
+			legend: {
+				labels: {
+					colors: tinycolor(getComputedStyle(document.documentElement).getPropertyValue('--text')).toRgbString()
+				},
+			},
+			series: [] as any,
+			colors: ['#00E396', '#00BCD4', '#FFB300', '#e53935'],
+			xaxis: {
+				type: 'numeric',
+				labels: {
+					show: false
+				},
+				tooltip: {
+					enabled: false
+				}
+			},
+			yaxis: {
+				show: false,
+				min: 0,
+			}
+		});
+
+		this.chart.render();
+
+		this.connection.on('stats', this.onStats);
+		this.connection.on('statsLog', this.onStatsLog);
+
+		this.$once('hook:beforeDestroy', () => {
+			if (this.chart) this.chart.destroy();
+		});
+	},
+
+	methods: {
+		onStats(stats) {
+			this.stats.push(stats);
+			if (this.stats.length > this.limit) this.stats.shift();
+		},
+
+		onStatsLog(statsLog) {
+			for (const stats of statsLog.reverse()) {
+				this.onStats(stats);
+			}
+		},
+	}
+});
+</script>
+
+<style lang="stylus" scoped>
+.wptihjuy
+	min-height 200px !important
+	margin 0 -8px -8px -8px
+
+</style>
diff --git a/src/client/app/admin/views/queue.vue b/src/client/app/admin/views/queue.vue
index b379ba1850..358ed6cf74 100644
--- a/src/client/app/admin/views/queue.vue
+++ b/src/client/app/admin/views/queue.vue
@@ -4,57 +4,11 @@
 		<template #title><fa :icon="faChartBar"/> {{ $t('title') }}</template>
 		<section class="wptihjuy">
 			<header><fa :icon="faPaperPlane"/> Deliver</header>
-			<ui-info warn v-if="latestStats && latestStats.deliver.waiting > 0">The queue is jammed.</ui-info>
-			<ui-horizon-group inputs v-if="latestStats" class="fit-bottom">
-				<ui-input :value="latestStats.deliver.activeSincePrevTick | number" type="text" readonly>
-					<span>Process</span>
-					<template #prefix><fa :icon="fasPlayCircle"/></template>
-					<template #suffix>jobs/tick</template>
-				</ui-input>
-				<ui-input :value="latestStats.deliver.active | number" type="text" readonly>
-					<span>Active</span>
-					<template #prefix><fa :icon="farPlayCircle"/></template>
-					<template #suffix>jobs</template>
-				</ui-input>
-				<ui-input :value="latestStats.deliver.waiting | number" type="text" readonly>
-					<span>Waiting</span>
-					<template #prefix><fa :icon="faStopCircle"/></template>
-					<template #suffix>jobs</template>
-				</ui-input>
-				<ui-input :value="latestStats.deliver.delayed | number" type="text" readonly>
-					<span>Delayed</span>
-					<template #prefix><fa :icon="faStopwatch"/></template>
-					<template #suffix>jobs</template>
-				</ui-input>
-			</ui-horizon-group>
-			<div ref="deliverChart" class="chart"></div>
+			<x-chart v-if="connection" :connection="connection" :limit="chartLimit" type="deliver"/>
 		</section>
 		<section class="wptihjuy">
 			<header><fa :icon="faInbox"/> Inbox</header>
-			<ui-info warn v-if="latestStats && latestStats.inbox.waiting > 0">The queue is jammed.</ui-info>
-			<ui-horizon-group inputs v-if="latestStats" class="fit-bottom">
-				<ui-input :value="latestStats.inbox.activeSincePrevTick | number" type="text" readonly>
-					<span>Process</span>
-					<template #prefix><fa :icon="fasPlayCircle"/></template>
-					<template #suffix>jobs/tick</template>
-				</ui-input>
-				<ui-input :value="latestStats.inbox.active | number" type="text" readonly>
-					<span>Active</span>
-					<template #prefix><fa :icon="farPlayCircle"/></template>
-					<template #suffix>jobs</template>
-				</ui-input>
-				<ui-input :value="latestStats.inbox.waiting | number" type="text" readonly>
-					<span>Waiting</span>
-					<template #prefix><fa :icon="faStopCircle"/></template>
-					<template #suffix>jobs</template>
-				</ui-input>
-				<ui-input :value="latestStats.inbox.delayed | number" type="text" readonly>
-					<span>Delayed</span>
-					<template #prefix><fa :icon="faStopwatch"/></template>
-					<template #suffix>jobs</template>
-				</ui-input>
-			</ui-horizon-group>
-			<div ref="inboxChart" class="chart"></div>
+			<x-chart v-if="connection" :connection="connection" :limit="chartLimit" type="inbox"/>
 		</section>
 		<section>
 			<ui-button @click="removeAllJobs">{{ $t('remove-all-jobs') }}</ui-button>
@@ -94,74 +48,31 @@
 
 <script lang="ts">
 import Vue from 'vue';
+import { faTasks, faInbox } from '@fortawesome/free-solid-svg-icons';
+import { faPaperPlane, faChartBar } from '@fortawesome/free-regular-svg-icons';
 import i18n from '../../i18n';
-import ApexCharts from 'apexcharts';
-import * as tinycolor from 'tinycolor2';
-import { faTasks, faInbox, faStopwatch, faPlayCircle as fasPlayCircle } from '@fortawesome/free-solid-svg-icons';
-import { faPaperPlane, faStopCircle, faPlayCircle as farPlayCircle, faChartBar } from '@fortawesome/free-regular-svg-icons';
-
-const limit = 200;
+import XChart from './queue.chart.vue';
 
 export default Vue.extend({
 	i18n: i18n('admin/views/queue.vue'),
 
+	components: {
+		XChart
+	},
+
 	data() {
 		return {
-			stats: [],
-			deliverChart: null,
-			inboxChart: null,
+			connection: null,
+			chartLimit: 200,
 			jobs: [],
 			jobsLimit: 50,
 			domain: 'deliver',
 			state: 'delayed',
-			faTasks, faPaperPlane, faInbox, faStopwatch, faStopCircle, farPlayCircle, fasPlayCircle, faChartBar
+			faTasks, faPaperPlane, faInbox, faChartBar
 		};
 	},
 
-	computed: {
-		latestStats(): any {
-			return this.stats[this.stats.length - 1];
-		}
-	},
-
 	watch: {
-		stats(stats) {
-			this.inboxChart.updateSeries([{
-				name: 'Process',
-				type: 'area',
-				data: stats.map((x, i) => ({ x: i, y: x.inbox.activeSincePrevTick }))
-			}, {
-				name: 'Active',
-				type: 'area',
-				data: stats.map((x, i) => ({ x: i, y: x.inbox.active }))
-			}, {
-				name: 'Waiting',
-				type: 'line',
-				data: stats.map((x, i) => ({ x: i, y: x.inbox.waiting }))
-			}, {
-				name: 'Delayed',
-				type: 'line',
-				data: stats.map((x, i) => ({ x: i, y: x.inbox.delayed }))
-			}]);
-			this.deliverChart.updateSeries([{
-				name: 'Process',
-				type: 'area',
-				data: stats.map((x, i) => ({ x: i, y: x.deliver.activeSincePrevTick }))
-			}, {
-				name: 'Active',
-				type: 'area',
-				data: stats.map((x, i) => ({ x: i, y: x.deliver.active }))
-			}, {
-				name: 'Waiting',
-				type: 'line',
-				data: stats.map((x, i) => ({ x: i, y: x.deliver.waiting }))
-			}, {
-				name: 'Delayed',
-				type: 'line',
-				data: stats.map((x, i) => ({ x: i, y: x.deliver.delayed }))
-			}]);
-		},
-
 		domain() {
 			this.jobs = [];
 			this.fetchJobs();
@@ -176,83 +87,14 @@ export default Vue.extend({
 	mounted() {
 		this.fetchJobs();
 
-		const chartOpts = id => ({
-			chart: {
-				id,
-				group: 'queue',
-				type: 'area',
-				height: 200,
-				animations: {
-					dynamicAnimation: {
-						enabled: false
-					}
-				},
-				toolbar: {
-					show: false
-				},
-				zoom: {
-					enabled: false
-				}
-			},
-			dataLabels: {
-				enabled: false
-			},
-			grid: {
-				clipMarkers: false,
-				borderColor: 'rgba(0, 0, 0, 0.1)',
-				xaxis: {
-					lines: {
-						show: true,
-					}
-				},
-			},
-			stroke: {
-				curve: 'straight',
-				width: 2
-			},
-			tooltip: {
-				enabled: false
-			},
-			legend: {
-				labels: {
-					colors: tinycolor(getComputedStyle(document.documentElement).getPropertyValue('--text')).toRgbString()
-				},
-			},
-			series: [] as any,
-			colors: ['#00E396', '#00BCD4', '#FFB300', '#e53935'],
-			xaxis: {
-				type: 'numeric',
-				labels: {
-					show: false
-				},
-				tooltip: {
-					enabled: false
-				}
-			},
-			yaxis: {
-				show: false,
-				min: 0,
-			}
-		});
-
-		this.inboxChart = new ApexCharts(this.$refs.inboxChart, chartOpts('a'));
-		this.deliverChart = new ApexCharts(this.$refs.deliverChart, chartOpts('b'));
-
-		this.inboxChart.render();
-		this.deliverChart.render();
-
-		const connection = this.$root.stream.useSharedConnection('queueStats');
-		connection.on('stats', this.onStats);
-		connection.on('statsLog', this.onStatsLog);
-		connection.send('requestLog', {
+		this.connection = this.$root.stream.useSharedConnection('queueStats');
+		this.connection.send('requestLog', {
 			id: Math.random().toString().substr(2, 8),
-			length: limit
+			length: this.chartLimit
 		});
 
 		this.$once('hook:beforeDestroy', () => {
-			connection.dispose();
-			this.inboxChart.destroy();
-			this.deliverChart.destroy();
+			this.connection.dispose();
 		});
 	},
 
@@ -274,17 +116,6 @@ export default Vue.extend({
 			});
 		},
 
-		onStats(stats) {
-			this.stats.push(stats);
-			if (this.stats.length > limit) this.stats.shift();
-		},
-
-		onStatsLog(statsLog) {
-			for (const stats of statsLog.reverse()) {
-				this.onStats(stats);
-			}
-		},
-
 		fetchJobs() {
 			this.$root.api('admin/queue/jobs', {
 				domain: this.domain,
@@ -299,11 +130,6 @@ export default Vue.extend({
 </script>
 
 <style lang="stylus" scoped>
-.wptihjuy
-	> .chart
-		min-height 200px !important
-		margin 0 -8px
-
 .xvvuvgsv
 	> b
 		margin-right 16px
diff --git a/src/daemons/queue-stats.ts b/src/daemons/queue-stats.ts
index f9ba90a0b7..e560354c74 100644
--- a/src/daemons/queue-stats.ts
+++ b/src/daemons/queue-stats.ts
@@ -1,6 +1,6 @@
 import * as Deque from 'double-ended-queue';
 import Xev from 'xev';
-import { deliverQueue, inboxQueue } from '../queue';
+import { deliverQueue, inboxQueue, dbQueue, objectStorageQueue } from '../queue';
 
 const ev = new Xev();
 
@@ -18,6 +18,8 @@ export default function() {
 
 	let activeDeliverJobs = 0;
 	let activeInboxJobs = 0;
+	let activeDbJobs = 0;
+	let activeObjectStorageJobs = 0;
 
 	deliverQueue.on('global:active', () => {
 		activeDeliverJobs++;
@@ -27,9 +29,19 @@ export default function() {
 		activeInboxJobs++;
 	});
 
+	dbQueue.on('global:active', () => {
+		activeDbJobs++;
+	});
+
+	objectStorageQueue.on('global:active', () => {
+		activeObjectStorageJobs++;
+	});
+
 	async function tick() {
 		const deliverJobCounts = await deliverQueue.getJobCounts();
 		const inboxJobCounts = await inboxQueue.getJobCounts();
+		const dbJobCounts = await dbQueue.getJobCounts();
+		const objectStorageJobCounts = await objectStorageQueue.getJobCounts();
 
 		const stats = {
 			deliver: {
@@ -43,7 +55,19 @@ export default function() {
 				active: inboxJobCounts.active,
 				waiting: inboxJobCounts.waiting,
 				delayed: inboxJobCounts.delayed
-			}
+			},
+			db: {
+				activeSincePrevTick: activeDbJobs,
+				active: dbJobCounts.active,
+				waiting: dbJobCounts.waiting,
+				delayed: dbJobCounts.delayed
+			},
+			objectStorage: {
+				activeSincePrevTick: activeObjectStorageJobs,
+				active: objectStorageJobCounts.active,
+				waiting: objectStorageJobCounts.waiting,
+				delayed: objectStorageJobCounts.delayed
+			},
 		};
 
 		ev.emit('queueStats', stats);
@@ -53,6 +77,8 @@ export default function() {
 
 		activeDeliverJobs = 0;
 		activeInboxJobs = 0;
+		activeDbJobs = 0;
+		activeObjectStorageJobs = 0;
 	}
 
 	tick();