diff --git a/apps/web/src/components/operations/operations-jobs-grid.tsx b/apps/web/src/components/operations/operations-jobs-grid.tsx
index e8d549a..b84abee 100644
--- a/apps/web/src/components/operations/operations-jobs-grid.tsx
+++ b/apps/web/src/components/operations/operations-jobs-grid.tsx
@@ -4,6 +4,7 @@ import { ListTodo, SearchIcon } from 'lucide-react'
import { toast } from 'sonner'
import { Button } from '@evobgp/ui/components/button'
+import { Badge } from '@/components/reui/badge'
import { DataGridMutedCell, DataGridNameCell } from '@/components/data-grid-cell'
import { StatusBadge } from '@/components/status-badge'
import { DataGridColumnHeader } from '@/components/reui/data-grid/data-grid-column-header'
@@ -14,7 +15,7 @@ import {
type DataGridColumnDef,
} from '@/components/reui-kit'
import { apiMutate } from '@/lib/api-client'
-import { jobKindRu } from '@/lib/ui-labels'
+import { jobKindRu, jobSubtitleRu, jobTriggerRu } from '@/lib/ui-labels'
import type { JobRow } from '@/types/api'
import type { QueryClient } from '@tanstack/react-query'
@@ -71,18 +72,25 @@ export function OperationsJobsGrid({
{
accessorKey: 'kind',
header: ({ column }) => ,
- cell: ({ row }) => (
-
- ),
+ cell: ({ row }) => {
+ const trigger = jobTriggerRu(row.original.meta?.trigger)
+ return (
+
+ {jobKindRu(row.original.kind)}
+ {trigger ? (
+
+ {trigger}
+
+ ) : null}
+
+ }
+ subtitle={jobSubtitleRu(row.original.kind, row.original.meta, nameById)}
+ />
+ )
+ },
meta: { headerTitle: 'Вид' },
},
{
@@ -156,10 +164,9 @@ export function OperationsJobsGrid({
onClearFilters={() => setFilterQuery(createTextFilterQuery('search'))}
getFilterFieldValue={(item, field) => {
if (field !== 'search') return undefined
- const moduleName = item.meta?.module_id
- ? (nameById.get(String(item.meta.module_id)) ?? String(item.meta.module_id))
- : ''
- return `${jobKindRu(item.kind)} ${item.status} ${item.job_id} ${moduleName}`
+ const subtitle = jobSubtitleRu(item.kind, item.meta, nameById)
+ const trigger = jobTriggerRu(item.meta?.trigger)
+ return `${jobKindRu(item.kind)} ${item.status} ${item.job_id} ${subtitle ?? ''} ${trigger ?? ''}`
}}
columns={columns}
data={items}
diff --git a/apps/web/src/components/schedule/schedule-jobs-card.tsx b/apps/web/src/components/schedule/schedule-jobs-card.tsx
index bc8bb4b..326b58a 100644
--- a/apps/web/src/components/schedule/schedule-jobs-card.tsx
+++ b/apps/web/src/components/schedule/schedule-jobs-card.tsx
@@ -6,12 +6,14 @@ import { ScheduleJobsGrid } from './schedule-jobs-grid'
/** Jobs ResourcePage with status tabs (data-grid-filtering-2). */
export function ScheduleJobsCard({
jobs,
+ nameById,
isLoading,
isError,
error,
onRetry,
}: {
jobs: JobRow[]
+ nameById?: Map
isLoading: boolean
isError: boolean
error: unknown
@@ -27,7 +29,11 @@ export function ScheduleJobsCard({
onRetry={onRetry}
>
{(items) => (
- 0} />
+ 0}
+ />
)}
)
diff --git a/apps/web/src/components/schedule/schedule-jobs-grid.tsx b/apps/web/src/components/schedule/schedule-jobs-grid.tsx
index 377f5bc..6f6c370 100644
--- a/apps/web/src/components/schedule/schedule-jobs-grid.tsx
+++ b/apps/web/src/components/schedule/schedule-jobs-grid.tsx
@@ -10,7 +10,8 @@ import {
createTextFilterQuery,
type DataGridColumnDef,
} from '@/components/reui-kit'
-import { isRefreshJobKind, jobKindRu } from '@/lib/ui-labels'
+import { Badge } from '@/components/reui/badge'
+import { isRefreshJobKind, jobKindRu, jobSubtitleRu, jobTriggerRu } from '@/lib/ui-labels'
import type { JobRow } from '@/types/api'
const FAILED_STATUSES = ['failed', 'error', 'cancelled']
@@ -33,9 +34,11 @@ function tabFilter(item: JobRow, tabId: string): boolean {
export function ScheduleJobsGrid({
items,
+ nameById = new Map(),
isLoading = false,
}: {
items: JobRow[]
+ nameById?: Map
isLoading?: boolean
}) {
const [filterQuery, setFilterQuery] = useState(() =>
@@ -64,9 +67,25 @@ export function ScheduleJobsGrid({
{
accessorKey: 'kind',
header: ({ column }) => ,
- cell: ({ row }) => (
-
- ),
+ cell: ({ row }) => {
+ const trigger = jobTriggerRu(row.original.meta?.trigger)
+ return (
+
+ {jobKindRu(row.original.kind)}
+ {trigger ? (
+
+ {trigger}
+
+ ) : null}
+
+ }
+ subtitle={jobSubtitleRu(row.original.kind, row.original.meta, nameById)}
+ />
+ )
+ },
meta: { headerTitle: 'Вид' },
},
{
@@ -119,7 +138,7 @@ export function ScheduleJobsGrid({
meta: { headerTitle: 'Ошибка' },
},
],
- [],
+ [nameById],
)
return (
@@ -134,7 +153,9 @@ export function ScheduleJobsGrid({
onClearFilters={() => setFilterQuery(createTextFilterQuery('search'))}
getFilterFieldValue={(item, field) => {
if (field !== 'search') return undefined
- return `${jobKindRu(item.kind)} ${item.status} ${item.error ?? ''} ${item.job_id}`
+ const subtitle = jobSubtitleRu(item.kind, item.meta, nameById)
+ const trigger = jobTriggerRu(item.meta?.trigger)
+ return `${jobKindRu(item.kind)} ${item.status} ${item.error ?? ''} ${item.job_id} ${subtitle ?? ''} ${trigger ?? ''}`
}}
columns={columns}
data={items}
diff --git a/apps/web/src/lib/ui-labels.ts b/apps/web/src/lib/ui-labels.ts
index 8e62c5a..92904fa 100644
--- a/apps/web/src/lib/ui-labels.ts
+++ b/apps/web/src/lib/ui-labels.ts
@@ -58,6 +58,54 @@ export function jobKindRu(kind: string): string {
return JOB_KIND_RU[kind] ?? kind
}
+const JOB_TRIGGER_RU: Record = {
+ scheduler: 'по расписанию',
+ api: 'вручную',
+ peer_patch: 'правка пиров',
+}
+
+export function jobTriggerRu(trigger: unknown): string | undefined {
+ if (typeof trigger !== 'string' || trigger === '') return undefined
+ const known = JOB_TRIGGER_RU[trigger]
+ if (known) return known
+ if (/_(create|patch|delete|import_csv)$/.test(trigger)) return 'после правки данных'
+ return undefined
+}
+
+export function jobSubtitleRu(
+ kind: string,
+ meta: Record | undefined,
+ nameById: Map,
+): string | undefined {
+ if (!meta) return undefined
+ switch (kind) {
+ case 'tenant_refresh': {
+ const ids = Array.isArray(meta.module_ids)
+ ? meta.module_ids.map(String).filter((id) => id !== '')
+ : []
+ if (ids.length === 0) return undefined
+ const names = ids.map((id) => nameById.get(id) ?? id)
+ if (names.length <= 3) return names.join(', ')
+ return `${names.slice(0, 2).join(', ')} +${names.length - 2} ещё`
+ }
+ case 'module_refresh': {
+ const id = typeof meta.module_id === 'string' ? meta.module_id : ''
+ if (id === '') return undefined
+ return nameById.get(id) ?? id
+ }
+ case 'deploy_apply': {
+ const summary = meta.apply_summary
+ if (summary && typeof summary === 'object') {
+ const message = (summary as Record).message
+ if (typeof message === 'string' && message !== '') return message
+ }
+ return undefined
+ }
+ default:
+ return undefined
+ }
+}
+
const JOB_STATUS_RU: Record = {
queued: 'В очереди',
running: 'Выполняется',
diff --git a/apps/web/src/routes/_auth/schedule.tsx b/apps/web/src/routes/_auth/schedule.tsx
index 53e3153..5ce5aab 100644
--- a/apps/web/src/routes/_auth/schedule.tsx
+++ b/apps/web/src/routes/_auth/schedule.tsx
@@ -11,6 +11,7 @@ import { KpiStatGrid, type KpiStatItem } from '@/components/reui-kit'
import { ScheduleJobsCard } from '@/components/schedule/schedule-jobs-card'
import { ScheduleModulesGrid } from '@/components/schedule/schedule-modules-grid'
import { apiMutate } from '@/lib/api-client'
+import { moduleNameById } from '@/queries/overview'
import { modulesListQueryOptions } from '@/queries/modules'
import { operationsJobsQueryOptions } from '@/queries/operations'
@@ -117,6 +118,7 @@ function ScheduleComponent() {