Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
95d4305241 |
@@ -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 }) => <DataGridColumnHeader column={column} title="Вид" />,
|
||||
cell: ({ row }) => (
|
||||
<DataGridNameCell
|
||||
icon={ListTodo}
|
||||
title={jobKindRu(row.original.kind)}
|
||||
subtitle={
|
||||
row.original.meta?.module_id
|
||||
? (nameById.get(String(row.original.meta.module_id)) ??
|
||||
String(row.original.meta.module_id))
|
||||
: undefined
|
||||
}
|
||||
/>
|
||||
),
|
||||
cell: ({ row }) => {
|
||||
const trigger = jobTriggerRu(row.original.meta?.trigger)
|
||||
return (
|
||||
<DataGridNameCell
|
||||
icon={ListTodo}
|
||||
title={
|
||||
<span className="flex min-w-0 items-center gap-1.5">
|
||||
<span className="truncate">{jobKindRu(row.original.kind)}</span>
|
||||
{trigger ? (
|
||||
<Badge variant="outline" size="sm" className="shrink-0 font-normal">
|
||||
{trigger}
|
||||
</Badge>
|
||||
) : null}
|
||||
</span>
|
||||
}
|
||||
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}
|
||||
|
||||
@@ -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<string, string>
|
||||
isLoading: boolean
|
||||
isError: boolean
|
||||
error: unknown
|
||||
@@ -27,7 +29,11 @@ export function ScheduleJobsCard({
|
||||
onRetry={onRetry}
|
||||
>
|
||||
{(items) => (
|
||||
<ScheduleJobsGrid items={items} isLoading={isLoading && items.length > 0} />
|
||||
<ScheduleJobsGrid
|
||||
items={items}
|
||||
nameById={nameById}
|
||||
isLoading={isLoading && items.length > 0}
|
||||
/>
|
||||
)}
|
||||
</QueryState>
|
||||
)
|
||||
|
||||
@@ -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<string, string>(),
|
||||
isLoading = false,
|
||||
}: {
|
||||
items: JobRow[]
|
||||
nameById?: Map<string, string>
|
||||
isLoading?: boolean
|
||||
}) {
|
||||
const [filterQuery, setFilterQuery] = useState<FilterQuery>(() =>
|
||||
@@ -64,9 +67,25 @@ export function ScheduleJobsGrid({
|
||||
{
|
||||
accessorKey: 'kind',
|
||||
header: ({ column }) => <DataGridColumnHeader column={column} title="Вид" />,
|
||||
cell: ({ row }) => (
|
||||
<DataGridNameCell icon={ListTodo} title={jobKindRu(row.original.kind)} />
|
||||
),
|
||||
cell: ({ row }) => {
|
||||
const trigger = jobTriggerRu(row.original.meta?.trigger)
|
||||
return (
|
||||
<DataGridNameCell
|
||||
icon={ListTodo}
|
||||
title={
|
||||
<span className="flex min-w-0 items-center gap-1.5">
|
||||
<span className="truncate">{jobKindRu(row.original.kind)}</span>
|
||||
{trigger ? (
|
||||
<Badge variant="outline" size="sm" className="shrink-0 font-normal">
|
||||
{trigger}
|
||||
</Badge>
|
||||
) : null}
|
||||
</span>
|
||||
}
|
||||
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}
|
||||
|
||||
@@ -58,6 +58,54 @@ export function jobKindRu(kind: string): string {
|
||||
return JOB_KIND_RU[kind] ?? kind
|
||||
}
|
||||
|
||||
const JOB_TRIGGER_RU: Record<string, string> = {
|
||||
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<string, unknown> | undefined,
|
||||
nameById: Map<string, string>,
|
||||
): 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<string, unknown>).message
|
||||
if (typeof message === 'string' && message !== '') return message
|
||||
}
|
||||
return undefined
|
||||
}
|
||||
default:
|
||||
return undefined
|
||||
}
|
||||
}
|
||||
|
||||
const JOB_STATUS_RU: Record<string, string> = {
|
||||
queued: 'В очереди',
|
||||
running: 'Выполняется',
|
||||
|
||||
@@ -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() {
|
||||
|
||||
<ScheduleJobsCard
|
||||
jobs={jobs}
|
||||
nameById={moduleNameById(modules)}
|
||||
isLoading={jobsQ.isLoading}
|
||||
isError={jobsQ.isError}
|
||||
error={jobsQ.error}
|
||||
|
||||
@@ -448,7 +448,10 @@ func (s *Server) handleModuleRefresh(w http.ResponseWriter, r *http.Request) {
|
||||
idemPtr = &idem
|
||||
}
|
||||
mid := mod.ID
|
||||
j, _, err := s.jobs.Enqueue(a.TenantID, jobs.KindModuleRefresh, idemPtr, &mid, map[string]any{"module_id": moduleID})
|
||||
j, _, err := s.jobs.Enqueue(a.TenantID, jobs.KindModuleRefresh, idemPtr, &mid, map[string]any{
|
||||
"module_id": moduleID,
|
||||
"trigger": "api",
|
||||
})
|
||||
if err != nil {
|
||||
writeInternalError(w, "internal", err)
|
||||
return
|
||||
@@ -751,6 +754,7 @@ func (s *Server) handleApply(w http.ResponseWriter, r *http.Request) {
|
||||
j, _, err := s.jobs.Enqueue(a.TenantID, jobs.KindDeployApply, idemPtr, nil, map[string]any{
|
||||
"revision_id": revID,
|
||||
"strategy": body.Strategy,
|
||||
"trigger": "api",
|
||||
})
|
||||
if err != nil {
|
||||
writeInternalError(w, "internal", err)
|
||||
@@ -801,6 +805,7 @@ func (s *Server) handleSpeakerApply(w http.ResponseWriter, r *http.Request) {
|
||||
j, _, err := s.jobs.Enqueue(a.TenantID, jobs.KindDeployApply, idemPtr, nil, map[string]any{
|
||||
"revision_id": revID,
|
||||
"speaker_id": spkID,
|
||||
"trigger": "api",
|
||||
})
|
||||
if err != nil {
|
||||
writeInternalError(w, "internal", err)
|
||||
|
||||
@@ -436,9 +436,11 @@ func (w *Worker) enqueueDeployAllSpeakers(j *Job, tenantID, revID string) {
|
||||
if revID == "" {
|
||||
return
|
||||
}
|
||||
applyJob, _, err := w.Registry.Enqueue(tenantID, KindDeployApply, nil, nil, map[string]any{
|
||||
"revision_id": revID,
|
||||
})
|
||||
meta := map[string]any{"revision_id": revID}
|
||||
if tr := strings.TrimSpace(j.metaString("trigger")); tr != "" {
|
||||
meta["trigger"] = tr
|
||||
}
|
||||
applyJob, _, err := w.Registry.Enqueue(tenantID, KindDeployApply, nil, nil, meta)
|
||||
if err != nil {
|
||||
j.mergeMeta(map[string]any{"deploy_apply_enqueue_error": err.Error()})
|
||||
return
|
||||
|
||||
Reference in New Issue
Block a user