mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-04-29 18:52:19 +00:00
Add progress bars and progress text
This commit is contained in:
@@ -1,71 +1,75 @@
|
|||||||
<template>
|
<template>
|
||||||
<div
|
<div
|
||||||
v-if="!workspaceStore.focusMode"
|
v-if="!workspaceStore.focusMode"
|
||||||
class="ml-1 flex gap-x-0.5 pt-1"
|
class="ml-1 flex flex-col gap-1 pt-1"
|
||||||
@mouseenter="isTopMenuHovered = true"
|
@mouseenter="isTopMenuHovered = true"
|
||||||
@mouseleave="isTopMenuHovered = false"
|
@mouseleave="isTopMenuHovered = false"
|
||||||
>
|
>
|
||||||
<div class="min-w-0 flex-1">
|
<div class="flex gap-x-0.5">
|
||||||
<SubgraphBreadcrumb />
|
<div class="min-w-0 flex-1">
|
||||||
|
<SubgraphBreadcrumb />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mx-1 flex flex-col items-end gap-1">
|
||||||
|
<div
|
||||||
|
class="actionbar-container relative pointer-events-auto flex h-12 items-center overflow-hidden rounded-lg border border-interface-stroke px-2 shadow-interface"
|
||||||
|
>
|
||||||
|
<ActionBarButtons />
|
||||||
|
<!-- Support for legacy topbar elements attached by custom scripts, hidden if no elements present -->
|
||||||
|
<div
|
||||||
|
ref="legacyCommandsContainerRef"
|
||||||
|
class="[&:not(:has(*>*:not(:empty)))]:hidden"
|
||||||
|
></div>
|
||||||
|
<ComfyActionbar />
|
||||||
|
<IconButton
|
||||||
|
v-tooltip.bottom="cancelJobTooltipConfig"
|
||||||
|
type="transparent"
|
||||||
|
size="sm"
|
||||||
|
class="mr-2 bg-destructive-background text-base-foreground transition-colors duration-200 ease-in-out hover:bg-destructive-background-hover focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-destructive-background"
|
||||||
|
:disabled="isExecutionIdle"
|
||||||
|
:aria-label="t('menu.interrupt')"
|
||||||
|
@click="cancelCurrentJob"
|
||||||
|
>
|
||||||
|
<i class="icon-[lucide--x] size-4" />
|
||||||
|
</IconButton>
|
||||||
|
<IconTextButton
|
||||||
|
v-tooltip.bottom="queueHistoryTooltipConfig"
|
||||||
|
size="sm"
|
||||||
|
type="secondary"
|
||||||
|
icon-position="right"
|
||||||
|
class="mr-2 h-8 border-0 px-3 text-sm font-medium text-base-foreground cursor-pointer"
|
||||||
|
:aria-pressed="isQueueOverlayExpanded"
|
||||||
|
:aria-label="queueToggleLabel"
|
||||||
|
:label="queueToggleLabel"
|
||||||
|
@click="toggleQueueOverlay"
|
||||||
|
>
|
||||||
|
<template #icon>
|
||||||
|
<i class="icon-[lucide--chevron-down] size-4" />
|
||||||
|
</template>
|
||||||
|
</IconTextButton>
|
||||||
|
<CurrentUserButton v-if="isLoggedIn" class="shrink-0" />
|
||||||
|
<LoginButton v-else-if="isDesktop" />
|
||||||
|
<IconButton
|
||||||
|
v-if="!isRightSidePanelOpen"
|
||||||
|
v-tooltip.bottom="rightSidePanelTooltipConfig"
|
||||||
|
type="transparent"
|
||||||
|
size="sm"
|
||||||
|
class="mr-2 text-base-foreground transition-colors duration-200 ease-in-out bg-secondary-background hover:bg-secondary-background-hover focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary-background"
|
||||||
|
:aria-label="t('rightSidePanel.togglePanel')"
|
||||||
|
@click="rightSidePanelStore.togglePanel"
|
||||||
|
>
|
||||||
|
<i class="icon-[lucide--panel-right] size-4" />
|
||||||
|
</IconButton>
|
||||||
|
<QueueInlineProgress :hidden="isQueueOverlayExpanded" />
|
||||||
|
</div>
|
||||||
|
<QueueProgressOverlay
|
||||||
|
v-model:expanded="isQueueOverlayExpanded"
|
||||||
|
:menu-hovered="isTopMenuHovered"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mx-1 flex flex-col items-end gap-1">
|
<QueueInlineProgressSummary class="pr-1" :hidden="isQueueOverlayExpanded" />
|
||||||
<div
|
|
||||||
class="actionbar-container relative pointer-events-auto flex h-12 items-center overflow-hidden rounded-lg border border-interface-stroke px-2 shadow-interface"
|
|
||||||
>
|
|
||||||
<ActionBarButtons />
|
|
||||||
<!-- Support for legacy topbar elements attached by custom scripts, hidden if no elements present -->
|
|
||||||
<div
|
|
||||||
ref="legacyCommandsContainerRef"
|
|
||||||
class="[&:not(:has(*>*:not(:empty)))]:hidden"
|
|
||||||
></div>
|
|
||||||
<ComfyActionbar />
|
|
||||||
<IconButton
|
|
||||||
v-tooltip.bottom="cancelJobTooltipConfig"
|
|
||||||
type="transparent"
|
|
||||||
size="sm"
|
|
||||||
class="mr-2 bg-destructive-background text-base-foreground transition-colors duration-200 ease-in-out hover:bg-destructive-background-hover focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-destructive-background"
|
|
||||||
:disabled="isExecutionIdle"
|
|
||||||
:aria-label="t('menu.interrupt')"
|
|
||||||
@click="cancelCurrentJob"
|
|
||||||
>
|
|
||||||
<i class="icon-[lucide--x] size-4" />
|
|
||||||
</IconButton>
|
|
||||||
<IconTextButton
|
|
||||||
v-tooltip.bottom="queueHistoryTooltipConfig"
|
|
||||||
size="sm"
|
|
||||||
type="secondary"
|
|
||||||
icon-position="right"
|
|
||||||
class="mr-2 h-8 border-0 px-3 text-sm font-medium text-base-foreground cursor-pointer"
|
|
||||||
:aria-pressed="isQueueOverlayExpanded"
|
|
||||||
:aria-label="queueToggleLabel"
|
|
||||||
:label="queueToggleLabel"
|
|
||||||
@click="toggleQueueOverlay"
|
|
||||||
>
|
|
||||||
<template #icon>
|
|
||||||
<i class="icon-[lucide--chevron-down] size-4" />
|
|
||||||
</template>
|
|
||||||
</IconTextButton>
|
|
||||||
<CurrentUserButton v-if="isLoggedIn" class="shrink-0" />
|
|
||||||
<LoginButton v-else-if="isDesktop" />
|
|
||||||
<IconButton
|
|
||||||
v-if="!isRightSidePanelOpen"
|
|
||||||
v-tooltip.bottom="rightSidePanelTooltipConfig"
|
|
||||||
type="transparent"
|
|
||||||
size="sm"
|
|
||||||
class="mr-2 text-base-foreground transition-colors duration-200 ease-in-out bg-secondary-background hover:bg-secondary-background-hover focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary-background"
|
|
||||||
:aria-label="t('rightSidePanel.togglePanel')"
|
|
||||||
@click="rightSidePanelStore.togglePanel"
|
|
||||||
>
|
|
||||||
<i class="icon-[lucide--panel-right] size-4" />
|
|
||||||
</IconButton>
|
|
||||||
<QueueInlineProgress :hidden="isQueueOverlayExpanded" />
|
|
||||||
</div>
|
|
||||||
<QueueProgressOverlay
|
|
||||||
v-model:expanded="isQueueOverlayExpanded"
|
|
||||||
:menu-hovered="isTopMenuHovered"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -79,6 +83,7 @@ import SubgraphBreadcrumb from '@/components/breadcrumb/SubgraphBreadcrumb.vue'
|
|||||||
import IconButton from '@/components/button/IconButton.vue'
|
import IconButton from '@/components/button/IconButton.vue'
|
||||||
import IconTextButton from '@/components/button/IconTextButton.vue'
|
import IconTextButton from '@/components/button/IconTextButton.vue'
|
||||||
import QueueInlineProgress from '@/components/queue/QueueInlineProgress.vue'
|
import QueueInlineProgress from '@/components/queue/QueueInlineProgress.vue'
|
||||||
|
import QueueInlineProgressSummary from '@/components/queue/QueueInlineProgressSummary.vue'
|
||||||
import QueueProgressOverlay from '@/components/queue/QueueProgressOverlay.vue'
|
import QueueProgressOverlay from '@/components/queue/QueueProgressOverlay.vue'
|
||||||
import ActionBarButtons from '@/components/topbar/ActionBarButtons.vue'
|
import ActionBarButtons from '@/components/topbar/ActionBarButtons.vue'
|
||||||
import CurrentUserButton from '@/components/topbar/CurrentUserButton.vue'
|
import CurrentUserButton from '@/components/topbar/CurrentUserButton.vue'
|
||||||
|
|||||||
169
src/components/queue/QueueInlineProgressSummary.stories.ts
Normal file
169
src/components/queue/QueueInlineProgressSummary.stories.ts
Normal file
@@ -0,0 +1,169 @@
|
|||||||
|
import type { Meta, StoryObj } from '@storybook/vue3-vite'
|
||||||
|
|
||||||
|
import QueueInlineProgressSummary from './QueueInlineProgressSummary.vue'
|
||||||
|
import { useExecutionStore } from '@/stores/executionStore'
|
||||||
|
|
||||||
|
type SeedOptions = {
|
||||||
|
promptId: string
|
||||||
|
nodes: Record<string, boolean>
|
||||||
|
runningNodeId?: string
|
||||||
|
runningNodeTitle?: string
|
||||||
|
runningNodeType?: string
|
||||||
|
currentValue?: number
|
||||||
|
currentMax?: number
|
||||||
|
}
|
||||||
|
|
||||||
|
function resetExecutionStore() {
|
||||||
|
const exec = useExecutionStore()
|
||||||
|
exec.activePromptId = null
|
||||||
|
exec.queuedPrompts = {}
|
||||||
|
exec.nodeProgressStates = {}
|
||||||
|
exec.nodeProgressStatesByPrompt = {}
|
||||||
|
exec._executingNodeProgress = null
|
||||||
|
exec.lastExecutionError = null
|
||||||
|
exec.lastNodeErrors = null
|
||||||
|
exec.initializingPromptIds = new Set()
|
||||||
|
exec.promptIdToWorkflowId = new Map()
|
||||||
|
}
|
||||||
|
|
||||||
|
function seedExecutionState({
|
||||||
|
promptId,
|
||||||
|
nodes,
|
||||||
|
runningNodeId,
|
||||||
|
runningNodeTitle,
|
||||||
|
runningNodeType,
|
||||||
|
currentValue = 0,
|
||||||
|
currentMax = 100
|
||||||
|
}: SeedOptions) {
|
||||||
|
resetExecutionStore()
|
||||||
|
|
||||||
|
const exec = useExecutionStore()
|
||||||
|
const workflow = runningNodeId
|
||||||
|
? ({
|
||||||
|
changeTracker: {
|
||||||
|
activeState: {
|
||||||
|
nodes: Object.keys(nodes).map((id) => ({
|
||||||
|
id,
|
||||||
|
title:
|
||||||
|
id === runningNodeId ? (runningNodeTitle ?? '') : `Node ${id}`,
|
||||||
|
type: id === runningNodeId ? (runningNodeType ?? 'Node') : 'Node'
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} as any)
|
||||||
|
: undefined
|
||||||
|
|
||||||
|
exec.activePromptId = promptId
|
||||||
|
exec.queuedPrompts = {
|
||||||
|
[promptId]: {
|
||||||
|
nodes,
|
||||||
|
...(workflow ? { workflow } : {})
|
||||||
|
}
|
||||||
|
} as any
|
||||||
|
|
||||||
|
const nodeProgress = runningNodeId
|
||||||
|
? {
|
||||||
|
[runningNodeId]: {
|
||||||
|
value: currentValue,
|
||||||
|
max: currentMax,
|
||||||
|
state: 'running',
|
||||||
|
node_id: runningNodeId,
|
||||||
|
prompt_id: promptId
|
||||||
|
}
|
||||||
|
}
|
||||||
|
: {}
|
||||||
|
|
||||||
|
exec.nodeProgressStates = nodeProgress as any
|
||||||
|
exec.nodeProgressStatesByPrompt = runningNodeId
|
||||||
|
? ({ [promptId]: nodeProgress } as any)
|
||||||
|
: {}
|
||||||
|
exec._executingNodeProgress = runningNodeId
|
||||||
|
? ({
|
||||||
|
value: currentValue,
|
||||||
|
max: currentMax,
|
||||||
|
prompt_id: promptId,
|
||||||
|
node: runningNodeId
|
||||||
|
} as any)
|
||||||
|
: null
|
||||||
|
}
|
||||||
|
|
||||||
|
const meta: Meta<typeof QueueInlineProgressSummary> = {
|
||||||
|
title: 'Queue/QueueInlineProgressSummary',
|
||||||
|
component: QueueInlineProgressSummary,
|
||||||
|
parameters: {
|
||||||
|
layout: 'padded',
|
||||||
|
backgrounds: {
|
||||||
|
default: 'light'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default meta
|
||||||
|
type Story = StoryObj<typeof meta>
|
||||||
|
|
||||||
|
export const RunningKSampler: Story = {
|
||||||
|
render: () => ({
|
||||||
|
components: { QueueInlineProgressSummary },
|
||||||
|
setup() {
|
||||||
|
seedExecutionState({
|
||||||
|
promptId: 'prompt-running',
|
||||||
|
nodes: { '1': true, '2': false, '3': false, '4': true },
|
||||||
|
runningNodeId: '2',
|
||||||
|
runningNodeTitle: 'KSampler',
|
||||||
|
runningNodeType: 'KSampler',
|
||||||
|
currentValue: 12,
|
||||||
|
currentMax: 100
|
||||||
|
})
|
||||||
|
|
||||||
|
return {}
|
||||||
|
},
|
||||||
|
template: `
|
||||||
|
<div style="background: var(--color-surface-primary); width: 420px; padding: 12px;">
|
||||||
|
<QueueInlineProgressSummary />
|
||||||
|
</div>
|
||||||
|
`
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export const RunningWithFallbackName: Story = {
|
||||||
|
render: () => ({
|
||||||
|
components: { QueueInlineProgressSummary },
|
||||||
|
setup() {
|
||||||
|
seedExecutionState({
|
||||||
|
promptId: 'prompt-fallback',
|
||||||
|
nodes: { '10': true, '11': true, '12': false, '13': true },
|
||||||
|
runningNodeId: '12',
|
||||||
|
runningNodeTitle: '',
|
||||||
|
runningNodeType: 'custom_node',
|
||||||
|
currentValue: 78,
|
||||||
|
currentMax: 100
|
||||||
|
})
|
||||||
|
|
||||||
|
return {}
|
||||||
|
},
|
||||||
|
template: `
|
||||||
|
<div style="background: var(--color-surface-primary); width: 420px; padding: 12px;">
|
||||||
|
<QueueInlineProgressSummary />
|
||||||
|
</div>
|
||||||
|
`
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ProgressWithoutCurrentNode: Story = {
|
||||||
|
render: () => ({
|
||||||
|
components: { QueueInlineProgressSummary },
|
||||||
|
setup() {
|
||||||
|
seedExecutionState({
|
||||||
|
promptId: 'prompt-progress-only',
|
||||||
|
nodes: { '21': true, '22': true, '23': true, '24': false }
|
||||||
|
})
|
||||||
|
|
||||||
|
return {}
|
||||||
|
},
|
||||||
|
template: `
|
||||||
|
<div style="background: var(--color-surface-primary); width: 420px; padding: 12px;">
|
||||||
|
<QueueInlineProgressSummary />
|
||||||
|
</div>
|
||||||
|
`
|
||||||
|
})
|
||||||
|
}
|
||||||
64
src/components/queue/QueueInlineProgressSummary.vue
Normal file
64
src/components/queue/QueueInlineProgressSummary.vue
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
<template>
|
||||||
|
<div v-if="shouldShow" class="flex justify-end">
|
||||||
|
<div
|
||||||
|
class="flex items-center gap-4 whitespace-nowrap text-[0.75rem] leading-[normal] drop-shadow-[1px_1px_8px_rgba(0,0,0,0.4)]"
|
||||||
|
aria-hidden="true"
|
||||||
|
>
|
||||||
|
<div class="flex items-center gap-1 text-base-foreground">
|
||||||
|
<span class="font-normal">{{ totalLabel }}:</span>
|
||||||
|
<span class="w-[5ch] shrink-0 text-right font-bold tabular-nums">
|
||||||
|
{{ totalPercentFormatted }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex items-center gap-1 text-muted-foreground">
|
||||||
|
<span
|
||||||
|
class="w-[16ch] shrink-0 truncate text-right"
|
||||||
|
:title="currentNodeLabel"
|
||||||
|
>
|
||||||
|
{{ currentNodeLabel }}:
|
||||||
|
</span>
|
||||||
|
<span class="w-[5ch] shrink-0 text-right tabular-nums">
|
||||||
|
{{ currentNodePercentFormatted }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { computed } from 'vue'
|
||||||
|
import { useI18n } from 'vue-i18n'
|
||||||
|
|
||||||
|
import { useCurrentNodeName } from '@/composables/queue/useCurrentNodeName'
|
||||||
|
import { useQueueProgress } from '@/composables/queue/useQueueProgress'
|
||||||
|
import { useExecutionStore } from '@/stores/executionStore'
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
hidden?: boolean
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const { t } = useI18n()
|
||||||
|
const executionStore = useExecutionStore()
|
||||||
|
const { currentNodeName } = useCurrentNodeName()
|
||||||
|
const {
|
||||||
|
totalPercent,
|
||||||
|
totalPercentFormatted,
|
||||||
|
currentNodePercent,
|
||||||
|
currentNodePercentFormatted
|
||||||
|
} = useQueueProgress()
|
||||||
|
|
||||||
|
const totalLabel = computed<string>(() =>
|
||||||
|
t('sideToolbar.queueProgressOverlay.inlineTotalLabel')
|
||||||
|
)
|
||||||
|
|
||||||
|
const currentNodeLabel = computed<string>(() => currentNodeName.value)
|
||||||
|
|
||||||
|
const shouldShow = computed(
|
||||||
|
() =>
|
||||||
|
!props.hidden &&
|
||||||
|
(!executionStore.isIdle ||
|
||||||
|
totalPercent.value > 0 ||
|
||||||
|
currentNodePercent.value > 0)
|
||||||
|
)
|
||||||
|
</script>
|
||||||
23
src/composables/queue/useCurrentNodeName.ts
Normal file
23
src/composables/queue/useCurrentNodeName.ts
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
import { computed } from 'vue'
|
||||||
|
import { useI18n } from 'vue-i18n'
|
||||||
|
|
||||||
|
import { st } from '@/i18n'
|
||||||
|
import { useExecutionStore } from '@/stores/executionStore'
|
||||||
|
import { normalizeI18nKey } from '@/utils/formatUtil'
|
||||||
|
|
||||||
|
export function useCurrentNodeName() {
|
||||||
|
const { t } = useI18n()
|
||||||
|
const executionStore = useExecutionStore()
|
||||||
|
|
||||||
|
const currentNodeName = computed(() => {
|
||||||
|
const node = executionStore.executingNode
|
||||||
|
if (!node) return t('g.emDash')
|
||||||
|
const title = (node.title ?? '').toString().trim()
|
||||||
|
if (title) return title
|
||||||
|
const nodeType = (node.type ?? '').toString().trim() || t('g.untitled')
|
||||||
|
const key = `nodeDefs.${normalizeI18nKey(nodeType)}.display_name`
|
||||||
|
return st(key, nodeType)
|
||||||
|
})
|
||||||
|
|
||||||
|
return { currentNodeName }
|
||||||
|
}
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
import { computed, onUnmounted, ref, watch } from 'vue'
|
import { computed, onUnmounted, ref, watch } from 'vue'
|
||||||
import { useI18n } from 'vue-i18n'
|
import { useI18n } from 'vue-i18n'
|
||||||
|
|
||||||
|
import { useCurrentNodeName } from '@/composables/queue/useCurrentNodeName'
|
||||||
import { useQueueProgress } from '@/composables/queue/useQueueProgress'
|
import { useQueueProgress } from '@/composables/queue/useQueueProgress'
|
||||||
import { st } from '@/i18n'
|
|
||||||
import { isCloud } from '@/platform/distribution/types'
|
import { isCloud } from '@/platform/distribution/types'
|
||||||
import { useWorkflowStore } from '@/platform/workflow/management/stores/workflowStore'
|
import { useWorkflowStore } from '@/platform/workflow/management/stores/workflowStore'
|
||||||
import { useExecutionStore } from '@/stores/executionStore'
|
import { useExecutionStore } from '@/stores/executionStore'
|
||||||
@@ -16,7 +16,6 @@ import {
|
|||||||
isToday,
|
isToday,
|
||||||
isYesterday
|
isYesterday
|
||||||
} from '@/utils/dateTimeUtil'
|
} from '@/utils/dateTimeUtil'
|
||||||
import { normalizeI18nKey } from '@/utils/formatUtil'
|
|
||||||
import { buildJobDisplay } from '@/utils/queueDisplay'
|
import { buildJobDisplay } from '@/utils/queueDisplay'
|
||||||
import { jobStateFromTask } from '@/utils/queueUtil'
|
import { jobStateFromTask } from '@/utils/queueUtil'
|
||||||
|
|
||||||
@@ -168,6 +167,7 @@ export function useJobList() {
|
|||||||
})
|
})
|
||||||
|
|
||||||
const { totalPercent, currentNodePercent } = useQueueProgress()
|
const { totalPercent, currentNodePercent } = useQueueProgress()
|
||||||
|
const { currentNodeName } = useCurrentNodeName()
|
||||||
|
|
||||||
const relativeTimeFormatter = computed(() => {
|
const relativeTimeFormatter = computed(() => {
|
||||||
const localeValue = locale.value
|
const localeValue = locale.value
|
||||||
@@ -183,16 +183,6 @@ export function useJobList() {
|
|||||||
const isJobInitializing = (promptId: string | number | undefined) =>
|
const isJobInitializing = (promptId: string | number | undefined) =>
|
||||||
executionStore.isPromptInitializing(promptId)
|
executionStore.isPromptInitializing(promptId)
|
||||||
|
|
||||||
const currentNodeName = computed(() => {
|
|
||||||
const node = executionStore.executingNode
|
|
||||||
if (!node) return t('g.emDash')
|
|
||||||
const title = (node.title ?? '').toString().trim()
|
|
||||||
if (title) return title
|
|
||||||
const nodeType = (node.type ?? '').toString().trim() || t('g.untitled')
|
|
||||||
const key = `nodeDefs.${normalizeI18nKey(nodeType)}.display_name`
|
|
||||||
return st(key, nodeType)
|
|
||||||
})
|
|
||||||
|
|
||||||
const selectedJobTab = ref<JobTab>('All')
|
const selectedJobTab = ref<JobTab>('All')
|
||||||
const selectedWorkflowFilter = ref<'all' | 'current'>('all')
|
const selectedWorkflowFilter = ref<'all' | 'current'>('all')
|
||||||
const selectedSortMode = ref<JobSortMode>('mostRecent')
|
const selectedSortMode = ref<JobSortMode>('mostRecent')
|
||||||
|
|||||||
@@ -697,6 +697,8 @@
|
|||||||
"total": "Total: {percent}",
|
"total": "Total: {percent}",
|
||||||
"colonPercent": ": {percent}",
|
"colonPercent": ": {percent}",
|
||||||
"currentNode": "Current node:",
|
"currentNode": "Current node:",
|
||||||
|
"inlineTotalLabel": "Total",
|
||||||
|
"inlineCurrentNodeLabel": "Current node",
|
||||||
"viewAllJobs": "View all jobs",
|
"viewAllJobs": "View all jobs",
|
||||||
"running": "running",
|
"running": "running",
|
||||||
"preview": "Preview",
|
"preview": "Preview",
|
||||||
|
|||||||
Reference in New Issue
Block a user