summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorかっこかり <67428053+kakkokari-gtyih@users.noreply.github.com>2025-05-22 12:06:07 +0900
committerGitHub <noreply@github.com>2025-05-22 12:06:07 +0900
commit000ed1f51f061118cc2e9d6d08822ea96c66a86a (patch)
tree7c01ba68642ae86f45f97a02d86a88bc08754cb1
parentfix(backend): 連合モードが「なし」の場合はactivity jsonへの... (diff)
downloadmisskey-000ed1f51f061118cc2e9d6d08822ea96c66a86a.tar.gz
misskey-000ed1f51f061118cc2e9d6d08822ea96c66a86a.tar.bz2
misskey-000ed1f51f061118cc2e9d6d08822ea96c66a86a.zip
fix(frontend): ジョブキューインスペクタの型エラー解消 (#16020)
* fix(frontend): ジョブキューインスペクタの型エラー解消 * fix * fix * fix * fix
-rw-r--r--packages/backend/src/core/QueueService.ts5
-rw-r--r--packages/backend/src/misc/json-schema.ts8
-rw-r--r--packages/backend/src/models/json-schema/queue.ts107
-rw-r--r--packages/backend/src/server/api/endpoints/admin/queue/jobs.ts10
-rw-r--r--packages/backend/src/server/api/endpoints/admin/queue/queue-stats.ts113
-rw-r--r--packages/backend/src/server/api/endpoints/admin/queue/queues.ts42
-rw-r--r--packages/backend/src/server/api/endpoints/admin/queue/show-job.ts7
-rw-r--r--packages/frontend/src/components/MkTl.vue36
-rw-r--r--packages/frontend/src/pages/admin/job-queue.chart.vue2
-rw-r--r--packages/frontend/src/pages/admin/job-queue.job.vue40
-rw-r--r--packages/frontend/src/pages/admin/job-queue.vue67
-rw-r--r--packages/misskey-js/etc/misskey-js.api.md27
-rw-r--r--packages/misskey-js/src/autogen/endpoint.ts12
-rw-r--r--packages/misskey-js/src/autogen/entities.ts4
-rw-r--r--packages/misskey-js/src/autogen/models.ts2
-rw-r--r--packages/misskey-js/src/autogen/types.ts101
-rw-r--r--packages/misskey-js/src/consts.ts12
-rw-r--r--packages/misskey-js/src/index.ts1
18 files changed, 501 insertions, 95 deletions
diff --git a/packages/backend/src/core/QueueService.ts b/packages/backend/src/core/QueueService.ts
index a1e806816b..04bbc7e38a 100644
--- a/packages/backend/src/core/QueueService.ts
+++ b/packages/backend/src/core/QueueService.ts
@@ -39,6 +39,7 @@ import type {
} from './QueueModule.js';
import type httpSignature from '@peertube/http-signature';
import type * as Bull from 'bullmq';
+import type { Packed } from '@/misc/json-schema.js';
export const QUEUE_TYPES = [
'system',
@@ -774,13 +775,13 @@ export class QueueService {
}
@bindThis
- private packJobData(job: Bull.Job) {
+ private packJobData(job: Bull.Job): Packed<'QueueJob'> {
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
const stacktrace = job.stacktrace ? job.stacktrace.filter(Boolean) : [];
stacktrace.reverse();
return {
- id: job.id,
+ id: job.id!,
name: job.name,
data: job.data,
opts: job.opts,
diff --git a/packages/backend/src/misc/json-schema.ts b/packages/backend/src/misc/json-schema.ts
index e4eb10efca..23f6b692a7 100644
--- a/packages/backend/src/misc/json-schema.ts
+++ b/packages/backend/src/misc/json-schema.ts
@@ -31,7 +31,11 @@ import { packedChannelSchema } from '@/models/json-schema/channel.js';
import { packedAntennaSchema } from '@/models/json-schema/antenna.js';
import { packedClipSchema } from '@/models/json-schema/clip.js';
import { packedFederationInstanceSchema } from '@/models/json-schema/federation-instance.js';
-import { packedQueueCountSchema } from '@/models/json-schema/queue.js';
+import {
+ packedQueueCountSchema,
+ packedQueueMetricsSchema,
+ packedQueueJobSchema,
+} from '@/models/json-schema/queue.js';
import { packedGalleryPostSchema } from '@/models/json-schema/gallery-post.js';
import {
packedEmojiDetailedAdminSchema,
@@ -100,6 +104,8 @@ export const refs = {
PageBlock: packedPageBlockSchema,
Channel: packedChannelSchema,
QueueCount: packedQueueCountSchema,
+ QueueMetrics: packedQueueMetricsSchema,
+ QueueJob: packedQueueJobSchema,
Antenna: packedAntennaSchema,
Clip: packedClipSchema,
FederationInstance: packedFederationInstanceSchema,
diff --git a/packages/backend/src/models/json-schema/queue.ts b/packages/backend/src/models/json-schema/queue.ts
index 2ecf5c831f..dad0cf57f6 100644
--- a/packages/backend/src/models/json-schema/queue.ts
+++ b/packages/backend/src/models/json-schema/queue.ts
@@ -28,3 +28,110 @@ export const packedQueueCountSchema = {
},
},
} as const;
+
+// Bull.Metrics
+export const packedQueueMetricsSchema = {
+ type: 'object',
+ properties: {
+ meta: {
+ type: 'object',
+ optional: false, nullable: false,
+ properties: {
+ count: {
+ type: 'number',
+ optional: false, nullable: false,
+ },
+ prevTS: {
+ type: 'number',
+ optional: false, nullable: false,
+ },
+ prevCount: {
+ type: 'number',
+ optional: false, nullable: false,
+ },
+ },
+ },
+ data: {
+ type: 'array',
+ optional: false, nullable: false,
+ items: {
+ type: 'number',
+ optional: false, nullable: false,
+ },
+ },
+ count: {
+ type: 'number',
+ optional: false, nullable: false,
+ },
+ },
+} as const;
+
+export const packedQueueJobSchema = {
+ type: 'object',
+ properties: {
+ id: {
+ type: 'string',
+ optional: false, nullable: false,
+ },
+ name: {
+ type: 'string',
+ optional: false, nullable: false,
+ },
+ data: {
+ type: 'object',
+ optional: false, nullable: false,
+ },
+ opts: {
+ type: 'object',
+ optional: false, nullable: false,
+ },
+ timestamp: {
+ type: 'number',
+ optional: false, nullable: false,
+ },
+ processedOn: {
+ type: 'number',
+ optional: true, nullable: false,
+ },
+ processedBy: {
+ type: 'string',
+ optional: true, nullable: false,
+ },
+ finishedOn: {
+ type: 'number',
+ optional: true, nullable: false,
+ },
+ progress: {
+ type: 'object',
+ optional: false, nullable: false,
+ },
+ attempts: {
+ type: 'number',
+ optional: false, nullable: false,
+ },
+ delay: {
+ type: 'number',
+ optional: false, nullable: false,
+ },
+ failedReason: {
+ type: 'string',
+ optional: false, nullable: false,
+ },
+ stacktrace: {
+ type: 'array',
+ optional: false, nullable: false,
+ items: {
+ type: 'string',
+ optional: false, nullable: false,
+ },
+ },
+ returnValue: {
+ type: 'object',
+ optional: false, nullable: false,
+ },
+ isFailed: {
+ type: 'boolean',
+ optional: false, nullable: false,
+ },
+ },
+} as const;
diff --git a/packages/backend/src/server/api/endpoints/admin/queue/jobs.ts b/packages/backend/src/server/api/endpoints/admin/queue/jobs.ts
index 79731c9786..155f2c4000 100644
--- a/packages/backend/src/server/api/endpoints/admin/queue/jobs.ts
+++ b/packages/backend/src/server/api/endpoints/admin/queue/jobs.ts
@@ -5,7 +5,6 @@
import { Injectable } from '@nestjs/common';
import { Endpoint } from '@/server/api/endpoint-base.js';
-import { ModerationLogService } from '@/core/ModerationLogService.js';
import { QUEUE_TYPES, QueueService } from '@/core/QueueService.js';
export const meta = {
@@ -14,6 +13,15 @@ export const meta = {
requireCredential: true,
requireModerator: true,
kind: 'read:admin:queue',
+
+ res: {
+ type: 'array',
+ optional: false, nullable: false,
+ items: {
+ optional: false, nullable: false,
+ ref: 'QueueJob',
+ },
+ },
} as const;
export const paramDef = {
diff --git a/packages/backend/src/server/api/endpoints/admin/queue/queue-stats.ts b/packages/backend/src/server/api/endpoints/admin/queue/queue-stats.ts
index 10ce48332a..0098160165 100644
--- a/packages/backend/src/server/api/endpoints/admin/queue/queue-stats.ts
+++ b/packages/backend/src/server/api/endpoints/admin/queue/queue-stats.ts
@@ -5,7 +5,6 @@
import { Injectable } from '@nestjs/common';
import { Endpoint } from '@/server/api/endpoint-base.js';
-import { ModerationLogService } from '@/core/ModerationLogService.js';
import { QUEUE_TYPES, QueueService } from '@/core/QueueService.js';
export const meta = {
@@ -14,6 +13,118 @@ export const meta = {
requireCredential: true,
requireModerator: true,
kind: 'read:admin:queue',
+
+ res: {
+ type: 'object',
+ optional: false, nullable: false,
+ properties: {
+ name: {
+ type: 'string',
+ optional: false, nullable: false,
+ enum: QUEUE_TYPES,
+ },
+ qualifiedName: {
+ type: 'string',
+ optional: false, nullable: false,
+ },
+ counts: {
+ type: 'object',
+ optional: false, nullable: false,
+ additionalProperties: {
+ type: 'number',
+ },
+ },
+ isPaused: {
+ type: 'boolean',
+ optional: false, nullable: false,
+ },
+ metrics: {
+ type: 'object',
+ optional: false, nullable: false,
+ properties: {
+ completed: {
+ optional: false, nullable: false,
+ ref: 'QueueMetrics',
+ },
+ failed: {
+ optional: false, nullable: false,
+ ref: 'QueueMetrics',
+ },
+ },
+ },
+ db: {
+ type: 'object',
+ optional: false, nullable: false,
+ properties: {
+ version: {
+ type: 'string',
+ optional: false, nullable: false,
+ },
+ mode: {
+ type: 'string',
+ optional: false, nullable: false,
+ enum: ['cluster', 'standalone', 'sentinel'],
+ },
+ runId: {
+ type: 'string',
+ optional: false, nullable: false,
+ },
+ processId: {
+ type: 'string',
+ optional: false, nullable: false,
+ },
+ port: {
+ type: 'number',
+ optional: false, nullable: false,
+ },
+ os: {
+ type: 'string',
+ optional: false, nullable: false,
+ },
+ uptime: {
+ type: 'number',
+ optional: false, nullable: false,
+ },
+ memory: {
+ type: 'object',
+ optional: false, nullable: false,
+ properties: {
+ total: {
+ type: 'number',
+ optional: false, nullable: false,
+ },
+ used: {
+ type: 'number',
+ optional: false, nullable: false,
+ },
+ fragmentationRatio: {
+ type: 'number',
+ optional: false, nullable: false,
+ },
+ peak: {
+ type: 'number',
+ optional: false, nullable: false,
+ },
+ },
+ },
+ clients: {
+ type: 'object',
+ optional: false, nullable: false,
+ properties: {
+ blocked: {
+ type: 'number',
+ optional: false, nullable: false,
+ },
+ connected: {
+ type: 'number',
+ optional: false, nullable: false,
+ },
+ },
+ },
+ },
+ }
+ },
+ },
} as const;
export const paramDef = {
diff --git a/packages/backend/src/server/api/endpoints/admin/queue/queues.ts b/packages/backend/src/server/api/endpoints/admin/queue/queues.ts
index 3a38275f60..8d27e38c84 100644
--- a/packages/backend/src/server/api/endpoints/admin/queue/queues.ts
+++ b/packages/backend/src/server/api/endpoints/admin/queue/queues.ts
@@ -5,7 +5,6 @@
import { Injectable } from '@nestjs/common';
import { Endpoint } from '@/server/api/endpoint-base.js';
-import { ModerationLogService } from '@/core/ModerationLogService.js';
import { QUEUE_TYPES, QueueService } from '@/core/QueueService.js';
export const meta = {
@@ -14,6 +13,47 @@ export const meta = {
requireCredential: true,
requireModerator: true,
kind: 'read:admin:queue',
+
+ res: {
+ type: 'array',
+ optional: false, nullable: false,
+ items: {
+ type: 'object',
+ optional: false, nullable: false,
+ properties: {
+ name: {
+ type: 'string',
+ optional: false, nullable: false,
+ enum: QUEUE_TYPES,
+ },
+ counts: {
+ type: 'object',
+ optional: false, nullable: false,
+ additionalProperties: {
+ type: 'number',
+ },
+ },
+ isPaused: {
+ type: 'boolean',
+ optional: false, nullable: false,
+ },
+ metrics: {
+ type: 'object',
+ optional: false, nullable: false,
+ properties: {
+ completed: {
+ optional: false, nullable: false,
+ ref: 'QueueMetrics',
+ },
+ failed: {
+ optional: false, nullable: false,
+ ref: 'QueueMetrics',
+ },
+ },
+ },
+ },
+ },
+ },
} as const;
export const paramDef = {
diff --git a/packages/backend/src/server/api/endpoints/admin/queue/show-job.ts b/packages/backend/src/server/api/endpoints/admin/queue/show-job.ts
index 63747b5540..1735c22674 100644
--- a/packages/backend/src/server/api/endpoints/admin/queue/show-job.ts
+++ b/packages/backend/src/server/api/endpoints/admin/queue/show-job.ts
@@ -5,7 +5,6 @@
import { Injectable } from '@nestjs/common';
import { Endpoint } from '@/server/api/endpoint-base.js';
-import { ModerationLogService } from '@/core/ModerationLogService.js';
import { QUEUE_TYPES, QueueService } from '@/core/QueueService.js';
export const meta = {
@@ -14,6 +13,11 @@ export const meta = {
requireCredential: true,
requireModerator: true,
kind: 'read:admin:queue',
+
+ res: {
+ optional: false, nullable: false,
+ ref: 'QueueJob',
+ },
} as const;
export const paramDef = {
@@ -28,7 +32,6 @@ export const paramDef = {
@Injectable()
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
constructor(
- private moderationLogService: ModerationLogService,
private queueService: QueueService,
) {
super(meta, paramDef, async (ps, me) => {
diff --git a/packages/frontend/src/components/MkTl.vue b/packages/frontend/src/components/MkTl.vue
index 95cc4d2a2a..30bf5389be 100644
--- a/packages/frontend/src/components/MkTl.vue
+++ b/packages/frontend/src/components/MkTl.vue
@@ -21,15 +21,19 @@ SPDX-License-Identifier: AGPL-3.0-only
</div>
</template>
-<script lang="ts" setup>
+<script lang="ts">
+export type TlEvent<E = any> = {
+ id: string;
+ timestamp: number;
+ data: E;
+};
+</script>
+
+<script lang="ts" setup generic="T extends unknown">
import { computed } from 'vue';
const props = defineProps<{
- events: {
- id: string;
- timestamp: number;
- data: any;
- }[];
+ events: TlEvent<T>[];
}>();
const events = computed(() => {
@@ -44,12 +48,12 @@ function getDateText(dateInstance: Date) {
return `${year.toString()}/${month.toString()}/${date.toString()} ${hour.toString().padStart(2, '0')}:00:00`;
}
-const items = computed<({
+type TlItem<T> = ({
id: string;
type: 'event';
timestamp: number;
- delta: number;
- data: any;
+ delta: number
+ data: T;
} | {
id: string;
type: 'date';
@@ -57,8 +61,10 @@ const items = computed<({
prevText: string;
next: Date | null;
nextText: string;
-})[]>(() => {
- const results = [];
+});
+
+const items = computed<TlItem<T>[]>(() => {
+ const results: TlItem<T>[] = [];
for (let i = 0; i < events.value.length; i++) {
const item = events.value[i];
@@ -97,19 +103,12 @@ const items = computed<({
</script>
<style lang="scss" module>
-.root {
-
-}
-
.items {
display: grid;
grid-template-columns: max-content 18px 1fr;
gap: 0 8px;
}
-.item {
-}
-
.center {
position: relative;
@@ -140,6 +139,7 @@ const items = computed<({
height: 100%;
background: color-mix(in srgb, var(--MI_THEME-accent), var(--MI_THEME-bg) 75%);
}
+
.centerPoint {
position: absolute;
top: 0;
diff --git a/packages/frontend/src/pages/admin/job-queue.chart.vue b/packages/frontend/src/pages/admin/job-queue.chart.vue
index f7b7eef87f..a1920e277b 100644
--- a/packages/frontend/src/pages/admin/job-queue.chart.vue
+++ b/packages/frontend/src/pages/admin/job-queue.chart.vue
@@ -48,6 +48,8 @@ watch(() => props.dataSet, () => {
});
onMounted(() => {
+ if (chartEl.value == null) return;
+
const vLineColor = store.s.darkMode ? 'rgba(255, 255, 255, 0.2)' : 'rgba(0, 0, 0, 0.2)';
chartInstance = new Chart(chartEl.value, {
diff --git a/packages/frontend/src/pages/admin/job-queue.job.vue b/packages/frontend/src/pages/admin/job-queue.job.vue
index 71efab0272..7d8cdde8b9 100644
--- a/packages/frontend/src/pages/admin/job-queue.job.vue
+++ b/packages/frontend/src/pages/admin/job-queue.job.vue
@@ -12,7 +12,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<template #suffix>
<MkTime :time="job.finishedOn ?? job.processedOn ?? job.timestamp" mode="relative"/>
- <span v-if="job.progress != null && job.progress > 0" style="margin-left: 1em;">{{ Math.floor(job.progress * 100) }}%</span>
+ <span v-if="job.progress != null && typeof job.progress === 'number' && job.progress > 0" style="margin-left: 1em;">{{ Math.floor(job.progress * 100) }}%</span>
<span v-if="job.opts.attempts != null && job.opts.attempts > 0 && job.attempts > 1" style="margin-left: 1em; color: var(--MI_THEME-warn); font-variant-numeric: diagonal-fractions;">{{ job.attempts }}/{{ job.opts.attempts }}</span>
<span v-if="job.isFailed && job.finishedOn != null" style="margin-left: 1em; color: var(--MI_THEME-error)"><i class="ti ti-circle-x"></i></span>
<span v-else-if="job.isFailed" style="margin-left: 1em; color: var(--MI_THEME-warn)"><i class="ti ti-alert-triangle"></i></span>
@@ -61,7 +61,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<MkButton rounded @click="copyRaw()"><i class="ti ti-copy"></i> Copy raw</MkButton>
<MkButton rounded @click="refresh()"><i class="ti ti-reload"></i> Refresh view</MkButton>
<MkButton rounded @click="promoteJob()"><i class="ti ti-player-track-next"></i> Promote</MkButton>
- <MkButton rounded @click="moveJob"><i class="ti ti-arrow-right"></i> Move to</MkButton>
+ <!-- <MkButton rounded @click="moveJob"><i class="ti ti-arrow-right"></i> Move to</MkButton> -->
<MkButton danger rounded style="margin-left: auto;" @click="removeJob()"><i class="ti ti-trash"></i> Remove</MkButton>
</div>
</template>
@@ -96,7 +96,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<template #key>Attempts</template>
<template #value>{{ job.attempts }} of {{ job.opts.attempts }}</template>
</MkKeyValue>
- <MkKeyValue v-if="job.progress != null && job.progress > 0">
+ <MkKeyValue v-if="job.progress != null && typeof job.progress === 'number' && job.progress > 0">
<template #key>Progress</template>
<template #value>{{ Math.floor(job.progress * 100) }}%</template>
</MkKeyValue>
@@ -150,7 +150,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<MkButton><i class="ti ti-device-floppy"></i> Update</MkButton>
</div>
<div v-else-if="tab === 'result'">
- <MkCode :code="job.returnValue"/>
+ <MkCode :code="String(job.returnValue)"/>
</div>
<div v-else-if="tab === 'error'" class="_gaps_s">
<MkCode v-for="log in job.stacktrace" :code="log" lang="stacktrace"/>
@@ -159,22 +159,20 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
-import { ref, computed, watch } from 'vue';
+import { ref, computed } from 'vue';
+import * as Misskey from 'misskey-js';
import JSON5 from 'json5';
-import type { Ref } from 'vue';
import * as os from '@/os.js';
import { i18n } from '@/i18n.js';
import MkButton from '@/components/MkButton.vue';
-import { misskeyApi } from '@/utility/misskey-api.js';
import MkTabs from '@/components/MkTabs.vue';
import MkFolder from '@/components/MkFolder.vue';
import MkCode from '@/components/MkCode.vue';
import MkKeyValue from '@/components/MkKeyValue.vue';
import MkCodeEditor from '@/components/MkCodeEditor.vue';
import MkTl from '@/components/MkTl.vue';
-import kmg from '@/filters/kmg.js';
-import bytes from '@/filters/bytes.js';
import { copyToClipboard } from '@/utility/copy-to-clipboard.js';
+import type { TlEvent } from '@/components/MkTl.vue';
function msSMH(v: number | null) {
if (v == null) return 'N/A';
@@ -189,25 +187,34 @@ function msSMH(v: number | null) {
}
const props = defineProps<{
- job: any;
- queueType: string;
+ job: Misskey.entities.QueueJob;
+ queueType: typeof Misskey.queueTypes[number];
}>();
const emit = defineEmits<{
- (ev: 'needRefresh'): void,
+ (ev: 'needRefresh'): void;
}>();
const tab = ref('info');
const editData = ref(JSON5.stringify(props.job.data, null, '\t'));
const canEdit = true;
+
+type TlType = TlEvent<{
+ type: 'created' | 'processed' | 'finished';
+} | {
+ type: 'attempt';
+ attempt: number;
+}>;
+
const timeline = computed(() => {
- const events = [{
+ const events: TlType[] = [{
id: 'created',
timestamp: props.job.timestamp,
data: {
type: 'created',
},
}];
+
if (props.job.attempts > 1) {
for (let i = 1; i < props.job.attempts; i++) {
events.push({
@@ -261,9 +268,10 @@ async function removeJob() {
os.apiWithDialog('admin/queue/remove-job', { queue: props.queueType, jobId: props.job.id });
}
-function moveJob() {
- // TODO
-}
+// TODO
+// function moveJob() {
+//
+// }
function refresh() {
emit('needRefresh');
diff --git a/packages/frontend/src/pages/admin/job-queue.vue b/packages/frontend/src/pages/admin/job-queue.vue
index 3d405c566f..8fae3bbb1c 100644
--- a/packages/frontend/src/pages/admin/job-queue.vue
+++ b/packages/frontend/src/pages/admin/job-queue.vue
@@ -37,9 +37,9 @@ SPDX-License-Identifier: AGPL-3.0-only
<template #footer>
<div class="_buttons">
<MkButton rounded @click="promoteAllJobs"><i class="ti ti-player-track-next"></i> Promote all jobs</MkButton>
- <MkButton rounded @click="createJob"><i class="ti ti-plus"></i> Add job</MkButton>
- <MkButton v-if="queueInfo.isPaused" rounded @click="resumeQueue"><i class="ti ti-player-play"></i> Resume queue</MkButton>
- <MkButton v-else rounded danger @click="pauseQueue"><i class="ti ti-player-pause"></i> Pause queue</MkButton>
+ <!-- <MkButton rounded @click="createJob"><i class="ti ti-plus"></i> Add job</MkButton> -->
+ <!-- <MkButton v-if="queueInfo.isPaused" rounded @click="resumeQueue"><i class="ti ti-player-play"></i> Resume queue</MkButton> -->
+ <!-- <MkButton v-else rounded danger @click="pauseQueue"><i class="ti ti-player-pause"></i> Pause queue</MkButton> -->
<MkButton rounded danger @click="clearQueue"><i class="ti ti-trash"></i> Empty queue</MkButton>
</div>
</template>
@@ -172,12 +172,11 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup>
import { ref, computed, watch } from 'vue';
-import JSON5 from 'json5';
+import * as Misskey from 'misskey-js';
import { debounce } from 'throttle-debounce';
import { useInterval } from '@@/js/use-interval.js';
import XChart from './job-queue.chart.vue';
import XJob from './job-queue.job.vue';
-import type { Ref } from 'vue';
import * as os from '@/os.js';
import { i18n } from '@/i18n.js';
import { definePage } from '@/page.js';
@@ -185,32 +184,18 @@ import MkButton from '@/components/MkButton.vue';
import { misskeyApi } from '@/utility/misskey-api.js';
import MkTabs from '@/components/MkTabs.vue';
import MkFolder from '@/components/MkFolder.vue';
-import MkCode from '@/components/MkCode.vue';
import MkKeyValue from '@/components/MkKeyValue.vue';
import MkTl from '@/components/MkTl.vue';
import kmg from '@/filters/kmg.js';
import MkInput from '@/components/MkInput.vue';
import bytes from '@/filters/bytes.js';
-import { copyToClipboard } from '@/utility/copy-to-clipboard.js';
-const QUEUE_TYPES = [
- 'system',
- 'endedPollNotification',
- 'deliver',
- 'inbox',
- 'db',
- 'relationship',
- 'objectStorage',
- 'userWebhookDeliver',
- 'systemWebhookDeliver',
-] as const;
-
-const tab: Ref<typeof QUEUE_TYPES[number] | '-'> = ref('-');
-const jobState = ref('all');
-const jobs = ref([]);
+const tab = ref<typeof Misskey.queueTypes[number] | '-'>('-');
+const jobState = ref<'all' | 'latest' | 'completed' | 'failed' | 'active' | 'delayed' | 'wait' | 'paused'>('all');
+const jobs = ref<Misskey.entities.QueueJob[]>([]);
const jobsFetching = ref(true);
-const queueInfos = ref([]);
-const queueInfo = ref();
+const queueInfos = ref<Misskey.entities.AdminQueueQueuesResponse>([]);
+const queueInfo = ref<Misskey.entities.AdminQueueQueueStatsResponse | null>(null);
const searchQuery = ref('');
async function fetchQueues() {
@@ -230,11 +215,11 @@ async function fetchJobs() {
queue: tab.value,
state: state === 'all' ? ['completed', 'failed', 'active', 'delayed', 'wait'] : state === 'latest' ? ['completed', 'failed'] : [state],
search: searchQuery.value.trim() === '' ? undefined : searchQuery.value,
- }).then(res => {
+ }).then((res: Misskey.entities.AdminQueueJobsResponse) => {
if (state === 'all') {
res.sort((a, b) => (a.processedOn ?? a.timestamp) > (b.processedOn ?? b.timestamp) ? -1 : 1);
} else if (state === 'latest') {
- res.sort((a, b) => a.processedOn > b.processedOn ? -1 : 1);
+ res.sort((a, b) => a.processedOn! > b.processedOn! ? -1 : 1);
} else if (state === 'delayed') {
res.sort((a, b) => (a.processedOn ?? a.timestamp) > (b.processedOn ?? b.timestamp) ? -1 : 1);
}
@@ -276,6 +261,8 @@ useInterval(() => {
});
async function clearQueue() {
+ if (tab.value === '-') return;
+
const { canceled } = await os.confirm({
type: 'warning',
title: i18n.ts.areYouSure,
@@ -289,6 +276,8 @@ async function clearQueue() {
}
async function promoteAllJobs() {
+ if (tab.value === '-') return;
+
const { canceled } = await os.confirm({
type: 'warning',
title: i18n.ts.areYouSure,
@@ -302,13 +291,15 @@ async function promoteAllJobs() {
}
async function removeJobs() {
+ if (tab.value === '-' || jobState.value === 'latest') return;
+
const { canceled } = await os.confirm({
type: 'warning',
title: i18n.ts.areYouSure,
});
if (canceled) return;
- os.apiWithDialog('admin/queue/clear', { queue: tab.value, state: jobState.value });
+ os.apiWithDialog('admin/queue/clear', { queue: tab.value, state: jobState.value === 'all' ? '*' : jobState.value });
fetchCurrentQueue();
fetchJobs();
@@ -324,16 +315,18 @@ async function refreshJob(jobId: string) {
const headerActions = computed(() => []);
-const headerTabs = computed(() =>
- [{
- key: '-',
- title: i18n.ts.overview,
- icon: 'ti ti-dashboard',
- }].concat(QUEUE_TYPES.map((t) => ({
- key: t,
- title: t,
- }))),
-);
+const headerTabs = computed<{
+ key: string;
+ title: string;
+ icon?: string;
+}[]>(() => [{
+ key: '-',
+ title: i18n.ts.jobQueue,
+ icon: 'ti ti-list-check',
+}, ...Misskey.queueTypes.map((q) => ({
+ key: q,
+ title: q,
+}))]);
definePage(() => ({
title: i18n.ts.jobQueue,
diff --git a/packages/misskey-js/etc/misskey-js.api.md b/packages/misskey-js/etc/misskey-js.api.md
index a305087fdb..7578275b03 100644
--- a/packages/misskey-js/etc/misskey-js.api.md
+++ b/packages/misskey-js/etc/misskey-js.api.md
@@ -276,12 +276,21 @@ type AdminQueueInboxDelayedResponse = operations['admin___queue___inbox-delayed'
type AdminQueueJobsRequest = operations['admin___queue___jobs']['requestBody']['content']['application/json'];
// @public (undocumented)
+type AdminQueueJobsResponse = operations['admin___queue___jobs']['responses']['200']['content']['application/json'];
+
+// @public (undocumented)
type AdminQueuePromoteJobsRequest = operations['admin___queue___promote-jobs']['requestBody']['content']['application/json'];
// @public (undocumented)
+type AdminQueueQueuesResponse = operations['admin___queue___queues']['responses']['200']['content']['application/json'];
+
+// @public (undocumented)
type AdminQueueQueueStatsRequest = operations['admin___queue___queue-stats']['requestBody']['content']['application/json'];
// @public (undocumented)
+type AdminQueueQueueStatsResponse = operations['admin___queue___queue-stats']['responses']['200']['content']['application/json'];
+
+// @public (undocumented)
type AdminQueueRemoveJobRequest = operations['admin___queue___remove-job']['requestBody']['content']['application/json'];
// @public (undocumented)
@@ -291,6 +300,9 @@ type AdminQueueRetryJobRequest = operations['admin___queue___retry-job']['reques
type AdminQueueShowJobRequest = operations['admin___queue___show-job']['requestBody']['content']['application/json'];
// @public (undocumented)
+type AdminQueueShowJobResponse = operations['admin___queue___show-job']['responses']['200']['content']['application/json'];
+
+// @public (undocumented)
type AdminQueueStatsResponse = operations['admin___queue___stats']['responses']['200']['content']['application/json'];
// @public (undocumented)
@@ -1532,11 +1544,15 @@ declare namespace entities {
AdminQueueDeliverDelayedResponse,
AdminQueueInboxDelayedResponse,
AdminQueueJobsRequest,
+ AdminQueueJobsResponse,
AdminQueuePromoteJobsRequest,
AdminQueueQueueStatsRequest,
+ AdminQueueQueueStatsResponse,
+ AdminQueueQueuesResponse,
AdminQueueRemoveJobRequest,
AdminQueueRetryJobRequest,
AdminQueueShowJobRequest,
+ AdminQueueShowJobResponse,
AdminQueueStatsResponse,
AdminRelaysAddRequest,
AdminRelaysAddResponse,
@@ -2117,6 +2133,8 @@ declare namespace entities {
PageBlock,
Channel,
QueueCount,
+ QueueMetrics,
+ QueueJob,
Antenna,
Clip,
FederationInstance,
@@ -3206,6 +3224,12 @@ type PureRenote = Omit<Note, 'renote' | 'renoteId' | 'reply' | 'replyId' | 'text
type QueueCount = components['schemas']['QueueCount'];
// @public (undocumented)
+type QueueJob = components['schemas']['QueueJob'];
+
+// @public (undocumented)
+type QueueMetrics = components['schemas']['QueueMetrics'];
+
+// @public (undocumented)
type QueueStats = {
deliver: {
activeSincePrevTick: number;
@@ -3225,6 +3249,9 @@ type QueueStats = {
type QueueStatsLog = QueueStats[];
// @public (undocumented)
+export const queueTypes: readonly ["system", "endedPollNotification", "deliver", "inbox", "db", "relationship", "objectStorage", "userWebhookDeliver", "systemWebhookDeliver"];
+
+// @public (undocumented)
type RenoteMuteCreateRequest = operations['renote-mute___create']['requestBody']['content']['application/json'];
// @public (undocumented)
diff --git a/packages/misskey-js/src/autogen/endpoint.ts b/packages/misskey-js/src/autogen/endpoint.ts
index a108cba7c1..f57a485033 100644
--- a/packages/misskey-js/src/autogen/endpoint.ts
+++ b/packages/misskey-js/src/autogen/endpoint.ts
@@ -79,11 +79,15 @@ import type {
AdminQueueDeliverDelayedResponse,
AdminQueueInboxDelayedResponse,
AdminQueueJobsRequest,
+ AdminQueueJobsResponse,
AdminQueuePromoteJobsRequest,
AdminQueueQueueStatsRequest,
+ AdminQueueQueueStatsResponse,
+ AdminQueueQueuesResponse,
AdminQueueRemoveJobRequest,
AdminQueueRetryJobRequest,
AdminQueueShowJobRequest,
+ AdminQueueShowJobResponse,
AdminQueueStatsResponse,
AdminRelaysAddRequest,
AdminRelaysAddResponse,
@@ -694,13 +698,13 @@ export type Endpoints = {
'admin/queue/clear': { req: AdminQueueClearRequest; res: EmptyResponse };
'admin/queue/deliver-delayed': { req: EmptyRequest; res: AdminQueueDeliverDelayedResponse };
'admin/queue/inbox-delayed': { req: EmptyRequest; res: AdminQueueInboxDelayedResponse };
- 'admin/queue/jobs': { req: AdminQueueJobsRequest; res: EmptyResponse };
+ 'admin/queue/jobs': { req: AdminQueueJobsRequest; res: AdminQueueJobsResponse };
'admin/queue/promote-jobs': { req: AdminQueuePromoteJobsRequest; res: EmptyResponse };
- 'admin/queue/queue-stats': { req: AdminQueueQueueStatsRequest; res: EmptyResponse };
- 'admin/queue/queues': { req: EmptyRequest; res: EmptyResponse };
+ 'admin/queue/queue-stats': { req: AdminQueueQueueStatsRequest; res: AdminQueueQueueStatsResponse };
+ 'admin/queue/queues': { req: EmptyRequest; res: AdminQueueQueuesResponse };
'admin/queue/remove-job': { req: AdminQueueRemoveJobRequest; res: EmptyResponse };
'admin/queue/retry-job': { req: AdminQueueRetryJobRequest; res: EmptyResponse };
- 'admin/queue/show-job': { req: AdminQueueShowJobRequest; res: EmptyResponse };
+ 'admin/queue/show-job': { req: AdminQueueShowJobRequest; res: AdminQueueShowJobResponse };
'admin/queue/stats': { req: EmptyRequest; res: AdminQueueStatsResponse };
'admin/relays/add': { req: AdminRelaysAddRequest; res: AdminRelaysAddResponse };
'admin/relays/list': { req: EmptyRequest; res: AdminRelaysListResponse };
diff --git a/packages/misskey-js/src/autogen/entities.ts b/packages/misskey-js/src/autogen/entities.ts
index 4b18cda5d8..b7286c1018 100644
--- a/packages/misskey-js/src/autogen/entities.ts
+++ b/packages/misskey-js/src/autogen/entities.ts
@@ -82,11 +82,15 @@ export type AdminQueueClearRequest = operations['admin___queue___clear']['reques
export type AdminQueueDeliverDelayedResponse = operations['admin___queue___deliver-delayed']['responses']['200']['content']['application/json'];
export type AdminQueueInboxDelayedResponse = operations['admin___queue___inbox-delayed']['responses']['200']['content']['application/json'];
export type AdminQueueJobsRequest = operations['admin___queue___jobs']['requestBody']['content']['application/json'];
+export type AdminQueueJobsResponse = operations['admin___queue___jobs']['responses']['200']['content']['application/json'];
export type AdminQueuePromoteJobsRequest = operations['admin___queue___promote-jobs']['requestBody']['content']['application/json'];
export type AdminQueueQueueStatsRequest = operations['admin___queue___queue-stats']['requestBody']['content']['application/json'];
+export type AdminQueueQueueStatsResponse = operations['admin___queue___queue-stats']['responses']['200']['content']['application/json'];
+export type AdminQueueQueuesResponse = operations['admin___queue___queues']['responses']['200']['content']['application/json'];
export type AdminQueueRemoveJobRequest = operations['admin___queue___remove-job']['requestBody']['content']['application/json'];
export type AdminQueueRetryJobRequest = operations['admin___queue___retry-job']['requestBody']['content']['application/json'];
export type AdminQueueShowJobRequest = operations['admin___queue___show-job']['requestBody']['content']['application/json'];
+export type AdminQueueShowJobResponse = operations['admin___queue___show-job']['responses']['200']['content']['application/json'];
export type AdminQueueStatsResponse = operations['admin___queue___stats']['responses']['200']['content']['application/json'];
export type AdminRelaysAddRequest = operations['admin___relays___add']['requestBody']['content']['application/json'];
export type AdminRelaysAddResponse = operations['admin___relays___add']['responses']['200']['content']['application/json'];
diff --git a/packages/misskey-js/src/autogen/models.ts b/packages/misskey-js/src/autogen/models.ts
index 354daf800b..babe2b1859 100644
--- a/packages/misskey-js/src/autogen/models.ts
+++ b/packages/misskey-js/src/autogen/models.ts
@@ -29,6 +29,8 @@ export type Page = components['schemas']['Page'];
export type PageBlock = components['schemas']['PageBlock'];
export type Channel = components['schemas']['Channel'];
export type QueueCount = components['schemas']['QueueCount'];
+export type QueueMetrics = components['schemas']['QueueMetrics'];
+export type QueueJob = components['schemas']['QueueJob'];
export type Antenna = components['schemas']['Antenna'];
export type Clip = components['schemas']['Clip'];
export type FederationInstance = components['schemas']['FederationInstance'];
diff --git a/packages/misskey-js/src/autogen/types.ts b/packages/misskey-js/src/autogen/types.ts
index 4e2c03c784..baa68ef471 100644
--- a/packages/misskey-js/src/autogen/types.ts
+++ b/packages/misskey-js/src/autogen/types.ts
@@ -4946,6 +4946,32 @@ export type components = {
failed: number;
delayed: number;
};
+ QueueMetrics: {
+ meta: {
+ count: number;
+ prevTS: number;
+ prevCount: number;
+ };
+ data: number[];
+ count: number;
+ };
+ QueueJob: {
+ id: string;
+ name: string;
+ data: Record<string, never>;
+ opts: Record<string, never>;
+ timestamp: number;
+ processedOn?: number;
+ processedBy?: string;
+ finishedOn?: number;
+ progress: Record<string, never>;
+ attempts: number;
+ delay: number;
+ failedReason: string;
+ stacktrace: string[];
+ returnValue: Record<string, never>;
+ isFailed: boolean;
+ };
Antenna: {
/** Format: id */
id: string;
@@ -9049,9 +9075,11 @@ export type operations = {
};
};
responses: {
- /** @description OK (without any results) */
- 204: {
- content: never;
+ /** @description OK (with results) */
+ 200: {
+ content: {
+ 'application/json': components['schemas']['QueueJob'][];
+ };
};
/** @description Client error */
400: {
@@ -9153,9 +9181,43 @@ export type operations = {
};
};
responses: {
- /** @description OK (without any results) */
- 204: {
- content: never;
+ /** @description OK (with results) */
+ 200: {
+ content: {
+ 'application/json': {
+ /** @enum {string} */
+ name: 'system' | 'endedPollNotification' | 'deliver' | 'inbox' | 'db' | 'relationship' | 'objectStorage' | 'userWebhookDeliver' | 'systemWebhookDeliver';
+ qualifiedName: string;
+ counts: {
+ [key: string]: number;
+ };
+ isPaused: boolean;
+ metrics: {
+ completed: components['schemas']['QueueMetrics'];
+ failed: components['schemas']['QueueMetrics'];
+ };
+ db: {
+ version: string;
+ /** @enum {string} */
+ mode: 'cluster' | 'standalone' | 'sentinel';
+ runId: string;
+ processId: string;
+ port: number;
+ os: string;
+ uptime: number;
+ memory: {
+ total: number;
+ used: number;
+ fragmentationRatio: number;
+ peak: number;
+ };
+ clients: {
+ blocked: number;
+ connected: number;
+ };
+ };
+ };
+ };
};
/** @description Client error */
400: {
@@ -9197,9 +9259,22 @@ export type operations = {
*/
admin___queue___queues: {
responses: {
- /** @description OK (without any results) */
- 204: {
- content: never;
+ /** @description OK (with results) */
+ 200: {
+ content: {
+ 'application/json': ({
+ /** @enum {string} */
+ name: 'system' | 'endedPollNotification' | 'deliver' | 'inbox' | 'db' | 'relationship' | 'objectStorage' | 'userWebhookDeliver' | 'systemWebhookDeliver';
+ counts: {
+ [key: string]: number;
+ };
+ isPaused: boolean;
+ metrics: {
+ completed: components['schemas']['QueueMetrics'];
+ failed: components['schemas']['QueueMetrics'];
+ };
+ })[];
+ };
};
/** @description Client error */
400: {
@@ -9356,9 +9431,11 @@ export type operations = {
};
};
responses: {
- /** @description OK (without any results) */
- 204: {
- content: never;
+ /** @description OK (with results) */
+ 200: {
+ content: {
+ 'application/json': components['schemas']['QueueJob'];
+ };
};
/** @description Client error */
400: {
diff --git a/packages/misskey-js/src/consts.ts b/packages/misskey-js/src/consts.ts
index 6d92915c44..a868f32f28 100644
--- a/packages/misskey-js/src/consts.ts
+++ b/packages/misskey-js/src/consts.ts
@@ -169,6 +169,18 @@ export const moderationLogTypes = [
'deleteChatRoom',
] as const;
+export const queueTypes = [
+ 'system',
+ 'endedPollNotification',
+ 'deliver',
+ 'inbox',
+ 'db',
+ 'relationship',
+ 'objectStorage',
+ 'userWebhookDeliver',
+ 'systemWebhookDeliver',
+] as const;
+
// See: packages/backend/src/core/ReversiService.ts@L410
export const reversiUpdateKeys = [
'map',
diff --git a/packages/misskey-js/src/index.ts b/packages/misskey-js/src/index.ts
index e4c9364aa1..bf32c37786 100644
--- a/packages/misskey-js/src/index.ts
+++ b/packages/misskey-js/src/index.ts
@@ -13,6 +13,7 @@ export const mutedNoteReasons = consts.mutedNoteReasons;
export const followingVisibilities = consts.followingVisibilities;
export const followersVisibilities = consts.followersVisibilities;
export const moderationLogTypes = consts.moderationLogTypes;
+export const queueTypes = consts.queueTypes;
export const reversiUpdateKeys = consts.reversiUpdateKeys;
// api extractor not supported yet