Resolve #4462
This commit is contained in:
parent
5a0a297634
commit
80a2172715
@ -26,6 +26,7 @@
|
|||||||
<option value="hashtags">{{ $t('@.widgets.hashtags') }}</option>
|
<option value="hashtags">{{ $t('@.widgets.hashtags') }}</option>
|
||||||
<option value="posts-monitor">{{ $t('@.widgets.posts-monitor') }}</option>
|
<option value="posts-monitor">{{ $t('@.widgets.posts-monitor') }}</option>
|
||||||
<option value="server">{{ $t('@.widgets.server') }}</option>
|
<option value="server">{{ $t('@.widgets.server') }}</option>
|
||||||
|
<option value="queue">{{ $t('@.widgets.queue') }}</option>
|
||||||
<option value="nav">{{ $t('@.widgets.nav') }}</option>
|
<option value="nav">{{ $t('@.widgets.nav') }}</option>
|
||||||
<option value="tips">{{ $t('@.widgets.tips') }}</option>
|
<option value="tips">{{ $t('@.widgets.tips') }}</option>
|
||||||
</select>
|
</select>
|
||||||
|
@ -31,3 +31,4 @@ Vue.component('mkw-version', wVersion);
|
|||||||
Vue.component('mkw-hashtags', wHashtags);
|
Vue.component('mkw-hashtags', wHashtags);
|
||||||
Vue.component('mkw-instance', wInstance);
|
Vue.component('mkw-instance', wInstance);
|
||||||
Vue.component('mkw-post-form', wPostForm);
|
Vue.component('mkw-post-form', wPostForm);
|
||||||
|
Vue.component('mkw-queue', () => import('./queue.vue').then(m => m.default));
|
||||||
|
157
src/client/app/common/views/widgets/queue.vue
Normal file
157
src/client/app/common/views/widgets/queue.vue
Normal file
@ -0,0 +1,157 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<ui-container :show-header="!props.compact">
|
||||||
|
<template #header><fa :icon="faTasks"/>Queue</template>
|
||||||
|
|
||||||
|
<div class="mntrproz">
|
||||||
|
<div>
|
||||||
|
<b>In</b>
|
||||||
|
<span v-if="latestStats">{{ latestStats.inbox.active | number }} / {{ latestStats.inbox.delayed | number }}</span>
|
||||||
|
<div ref="in"></div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<b>Out</b>
|
||||||
|
<span v-if="latestStats">{{ latestStats.deliver.active | number }} / {{ latestStats.deliver.delayed | number }}</span>
|
||||||
|
<div ref="out"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</ui-container>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import define from '../../define-widget';
|
||||||
|
import { faTasks } from '@fortawesome/free-solid-svg-icons';
|
||||||
|
import ApexCharts from 'apexcharts';
|
||||||
|
|
||||||
|
export default define({
|
||||||
|
name: 'queue',
|
||||||
|
props: () => ({
|
||||||
|
compact: false
|
||||||
|
})
|
||||||
|
}).extend({
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
stats: [],
|
||||||
|
inChart: null,
|
||||||
|
outChart: null,
|
||||||
|
faTasks
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
watch: {
|
||||||
|
stats(stats) {
|
||||||
|
this.inChart.updateSeries([{
|
||||||
|
data: stats.map((x, i) => ({ x: i, y: x.inbox.active }))
|
||||||
|
}, {
|
||||||
|
data: stats.map((x, i) => ({ x: i, y: x.inbox.delayed }))
|
||||||
|
}]);
|
||||||
|
this.outChart.updateSeries([{
|
||||||
|
data: stats.map((x, i) => ({ x: i, y: x.deliver.active }))
|
||||||
|
}, {
|
||||||
|
data: stats.map((x, i) => ({ x: i, y: x.deliver.delayed }))
|
||||||
|
}]);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
computed: {
|
||||||
|
latestStats(): any {
|
||||||
|
return this.stats[this.stats.length - 1];
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
mounted() {
|
||||||
|
const chartOpts = {
|
||||||
|
chart: {
|
||||||
|
type: 'area',
|
||||||
|
height: 70,
|
||||||
|
animations: {
|
||||||
|
dynamicAnimation: {
|
||||||
|
enabled: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
sparkline: {
|
||||||
|
enabled: true,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
tooltip: {
|
||||||
|
enabled: false
|
||||||
|
},
|
||||||
|
stroke: {
|
||||||
|
curve: 'straight',
|
||||||
|
width: 1
|
||||||
|
},
|
||||||
|
series: [{
|
||||||
|
data: [] as any
|
||||||
|
}, {
|
||||||
|
data: [] as any
|
||||||
|
}],
|
||||||
|
yaxis: {
|
||||||
|
min: 0,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
this.inChart = new ApexCharts(this.$refs.in, chartOpts);
|
||||||
|
this.outChart = new ApexCharts(this.$refs.out, chartOpts);
|
||||||
|
|
||||||
|
this.inChart.render();
|
||||||
|
this.outChart.render();
|
||||||
|
|
||||||
|
const connection = this.$root.stream.useSharedConnection('queueStats');
|
||||||
|
connection.on('stats', this.onStats);
|
||||||
|
connection.on('statsLog', this.onStatsLog);
|
||||||
|
connection.send('requestLog', {
|
||||||
|
id: Math.random().toString().substr(2, 8),
|
||||||
|
length: 50
|
||||||
|
});
|
||||||
|
|
||||||
|
this.$once('hook:beforeDestroy', () => {
|
||||||
|
connection.dispose();
|
||||||
|
this.inChart.destroy();
|
||||||
|
this.outChart.destroy();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
func() {
|
||||||
|
this.props.compact = !this.props.compact;
|
||||||
|
this.save();
|
||||||
|
},
|
||||||
|
|
||||||
|
onStats(stats) {
|
||||||
|
this.stats.push(stats);
|
||||||
|
if (this.stats.length > 50) this.stats.shift();
|
||||||
|
},
|
||||||
|
|
||||||
|
onStatsLog(statsLog) {
|
||||||
|
for (const stats of statsLog.reverse()) {
|
||||||
|
this.onStats(stats);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="stylus" scoped>
|
||||||
|
.mntrproz
|
||||||
|
display flex
|
||||||
|
padding 4px
|
||||||
|
|
||||||
|
> div
|
||||||
|
width 50%
|
||||||
|
padding 4px
|
||||||
|
|
||||||
|
> b
|
||||||
|
display block
|
||||||
|
font-size 12px
|
||||||
|
color var(--text)
|
||||||
|
|
||||||
|
> span
|
||||||
|
position absolute
|
||||||
|
top 4px
|
||||||
|
right 4px
|
||||||
|
opacity 0.7
|
||||||
|
font-size 12px
|
||||||
|
color var(--text)
|
||||||
|
|
||||||
|
</style>
|
@ -27,6 +27,7 @@
|
|||||||
<option value="hashtags">{{ $t('@.widgets.hashtags') }}</option>
|
<option value="hashtags">{{ $t('@.widgets.hashtags') }}</option>
|
||||||
<option value="posts-monitor">{{ $t('@.widgets.posts-monitor') }}</option>
|
<option value="posts-monitor">{{ $t('@.widgets.posts-monitor') }}</option>
|
||||||
<option value="server">{{ $t('@.widgets.server') }}</option>
|
<option value="server">{{ $t('@.widgets.server') }}</option>
|
||||||
|
<option value="queue">{{ $t('@.widgets.queue') }}</option>
|
||||||
<option value="nav">{{ $t('@.widgets.nav') }}</option>
|
<option value="nav">{{ $t('@.widgets.nav') }}</option>
|
||||||
<option value="tips">{{ $t('@.widgets.tips') }}</option>
|
<option value="tips">{{ $t('@.widgets.tips') }}</option>
|
||||||
</select>
|
</select>
|
||||||
|
@ -19,6 +19,7 @@
|
|||||||
<option value="posts-monitor">{{ $t('@.widgets.posts-monitor') }}</option>
|
<option value="posts-monitor">{{ $t('@.widgets.posts-monitor') }}</option>
|
||||||
<option value="version">{{ $t('@.widgets.version') }}</option>
|
<option value="version">{{ $t('@.widgets.version') }}</option>
|
||||||
<option value="server">{{ $t('@.widgets.server') }}</option>
|
<option value="server">{{ $t('@.widgets.server') }}</option>
|
||||||
|
<option value="queue">{{ $t('@.widgets.queue') }}</option>
|
||||||
<option value="memo">{{ $t('@.widgets.memo') }}</option>
|
<option value="memo">{{ $t('@.widgets.memo') }}</option>
|
||||||
<option value="nav">{{ $t('@.widgets.nav') }}</option>
|
<option value="nav">{{ $t('@.widgets.nav') }}</option>
|
||||||
<option value="tips">{{ $t('@.widgets.tips') }}</option>
|
<option value="tips">{{ $t('@.widgets.tips') }}</option>
|
||||||
|
43
src/daemons/queue-stats.ts
Normal file
43
src/daemons/queue-stats.ts
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
import * as Deque from 'double-ended-queue';
|
||||||
|
import Xev from 'xev';
|
||||||
|
import { deliverQueue, inboxQueue } from '../queue';
|
||||||
|
|
||||||
|
const ev = new Xev();
|
||||||
|
|
||||||
|
const interval = 1000;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Report queue stats regularly
|
||||||
|
*/
|
||||||
|
export default function() {
|
||||||
|
const log = new Deque<any>();
|
||||||
|
|
||||||
|
ev.on('requestQueueStatsLog', x => {
|
||||||
|
ev.emit(`queueStatsLog:${x.id}`, log.toArray().slice(0, x.length || 50));
|
||||||
|
});
|
||||||
|
|
||||||
|
async function tick() {
|
||||||
|
const deliverJobCounts = await deliverQueue.getJobCounts();
|
||||||
|
const inboxJobCounts = await inboxQueue.getJobCounts();
|
||||||
|
|
||||||
|
const stats = {
|
||||||
|
deliver: {
|
||||||
|
active: Math.floor(Math.random() * 100),
|
||||||
|
delayed: Math.floor(Math.random() * 1000),
|
||||||
|
},
|
||||||
|
inbox: {
|
||||||
|
active: Math.floor(Math.random() * 100),
|
||||||
|
delayed: Math.floor(Math.random() * 1000),
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
ev.emit('queueStats', stats);
|
||||||
|
|
||||||
|
log.unshift(stats);
|
||||||
|
if (log.length > 200) log.pop();
|
||||||
|
}
|
||||||
|
|
||||||
|
tick();
|
||||||
|
|
||||||
|
setInterval(tick, interval);
|
||||||
|
}
|
@ -16,6 +16,7 @@ import Xev from 'xev';
|
|||||||
import Logger from './services/logger';
|
import Logger from './services/logger';
|
||||||
import serverStats from './daemons/server-stats';
|
import serverStats from './daemons/server-stats';
|
||||||
import notesStats from './daemons/notes-stats';
|
import notesStats from './daemons/notes-stats';
|
||||||
|
import queueStats from './daemons/queue-stats';
|
||||||
import loadConfig from './config/load';
|
import loadConfig from './config/load';
|
||||||
import { Config } from './config/types';
|
import { Config } from './config/types';
|
||||||
import { lessThan } from './prelude/array';
|
import { lessThan } from './prelude/array';
|
||||||
@ -50,6 +51,7 @@ function main() {
|
|||||||
if (program.daemons) {
|
if (program.daemons) {
|
||||||
serverStats();
|
serverStats();
|
||||||
notesStats();
|
notesStats();
|
||||||
|
queueStats();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5,6 +5,7 @@ import hybridTimeline from './hybrid-timeline';
|
|||||||
import globalTimeline from './global-timeline';
|
import globalTimeline from './global-timeline';
|
||||||
import notesStats from './notes-stats';
|
import notesStats from './notes-stats';
|
||||||
import serverStats from './server-stats';
|
import serverStats from './server-stats';
|
||||||
|
import queueStats from './queue-stats';
|
||||||
import userList from './user-list';
|
import userList from './user-list';
|
||||||
import messaging from './messaging';
|
import messaging from './messaging';
|
||||||
import messagingIndex from './messaging-index';
|
import messagingIndex from './messaging-index';
|
||||||
@ -23,6 +24,7 @@ export default {
|
|||||||
globalTimeline,
|
globalTimeline,
|
||||||
notesStats,
|
notesStats,
|
||||||
serverStats,
|
serverStats,
|
||||||
|
queueStats,
|
||||||
userList,
|
userList,
|
||||||
messaging,
|
messaging,
|
||||||
messagingIndex,
|
messagingIndex,
|
||||||
|
41
src/server/api/stream/channels/queue-stats.ts
Normal file
41
src/server/api/stream/channels/queue-stats.ts
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
import autobind from 'autobind-decorator';
|
||||||
|
import Xev from 'xev';
|
||||||
|
import Channel from '../channel';
|
||||||
|
|
||||||
|
const ev = new Xev();
|
||||||
|
|
||||||
|
export default class extends Channel {
|
||||||
|
public readonly chName = 'queueStats';
|
||||||
|
public static shouldShare = true;
|
||||||
|
public static requireCredential = false;
|
||||||
|
|
||||||
|
@autobind
|
||||||
|
public async init(params: any) {
|
||||||
|
ev.addListener('queueStats', this.onStats);
|
||||||
|
}
|
||||||
|
|
||||||
|
@autobind
|
||||||
|
private onStats(stats: any) {
|
||||||
|
this.send('stats', stats);
|
||||||
|
}
|
||||||
|
|
||||||
|
@autobind
|
||||||
|
public onMessage(type: string, body: any) {
|
||||||
|
switch (type) {
|
||||||
|
case 'requestLog':
|
||||||
|
ev.once(`queueStatsLog:${body.id}`, statsLog => {
|
||||||
|
this.send('statsLog', statsLog);
|
||||||
|
});
|
||||||
|
ev.emit('requestQueueStatsLog', {
|
||||||
|
id: body.id,
|
||||||
|
length: body.length
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@autobind
|
||||||
|
public dispose() {
|
||||||
|
ev.removeListener('queueStats', this.onStats);
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user