diff options
| author | かっこかり <67428053+kakkokari-gtyih@users.noreply.github.com> | 2025-05-22 12:06:07 +0900 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2025-05-22 12:06:07 +0900 |
| commit | 000ed1f51f061118cc2e9d6d08822ea96c66a86a (patch) | |
| tree | 7c01ba68642ae86f45f97a02d86a88bc08754cb1 | |
| parent | fix(backend): 連合モードが「なし」の場合はactivity jsonへの... (diff) | |
| download | misskey-000ed1f51f061118cc2e9d6d08822ea96c66a86a.tar.gz misskey-000ed1f51f061118cc2e9d6d08822ea96c66a86a.tar.bz2 misskey-000ed1f51f061118cc2e9d6d08822ea96c66a86a.zip | |
fix(frontend): ジョブキューインスペクタの型エラー解消 (#16020)
* fix(frontend): ジョブキューインスペクタの型エラー解消
* fix
* fix
* fix
* fix
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 |