forked from mirrors/misskey
fix(frontend): ジョブキューインスペクタの型エラー解消 (#16020)
* fix(frontend): ジョブキューインスペクタの型エラー解消 * fix * fix * fix * fix
This commit is contained in:
@@ -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;
|
||||
|
||||
@@ -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, {
|
||||
|
||||
@@ -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');
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user