Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
95d4305241 |
@@ -4,6 +4,7 @@ import { ListTodo, SearchIcon } from 'lucide-react'
|
|||||||
import { toast } from 'sonner'
|
import { toast } from 'sonner'
|
||||||
|
|
||||||
import { Button } from '@evobgp/ui/components/button'
|
import { Button } from '@evobgp/ui/components/button'
|
||||||
|
import { Badge } from '@/components/reui/badge'
|
||||||
import { DataGridMutedCell, DataGridNameCell } from '@/components/data-grid-cell'
|
import { DataGridMutedCell, DataGridNameCell } from '@/components/data-grid-cell'
|
||||||
import { StatusBadge } from '@/components/status-badge'
|
import { StatusBadge } from '@/components/status-badge'
|
||||||
import { DataGridColumnHeader } from '@/components/reui/data-grid/data-grid-column-header'
|
import { DataGridColumnHeader } from '@/components/reui/data-grid/data-grid-column-header'
|
||||||
@@ -14,7 +15,7 @@ import {
|
|||||||
type DataGridColumnDef,
|
type DataGridColumnDef,
|
||||||
} from '@/components/reui-kit'
|
} from '@/components/reui-kit'
|
||||||
import { apiMutate } from '@/lib/api-client'
|
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 { JobRow } from '@/types/api'
|
||||||
import type { QueryClient } from '@tanstack/react-query'
|
import type { QueryClient } from '@tanstack/react-query'
|
||||||
|
|
||||||
@@ -71,18 +72,25 @@ export function OperationsJobsGrid({
|
|||||||
{
|
{
|
||||||
accessorKey: 'kind',
|
accessorKey: 'kind',
|
||||||
header: ({ column }) => <DataGridColumnHeader column={column} title="Вид" />,
|
header: ({ column }) => <DataGridColumnHeader column={column} title="Вид" />,
|
||||||
cell: ({ row }) => (
|
cell: ({ row }) => {
|
||||||
|
const trigger = jobTriggerRu(row.original.meta?.trigger)
|
||||||
|
return (
|
||||||
<DataGridNameCell
|
<DataGridNameCell
|
||||||
icon={ListTodo}
|
icon={ListTodo}
|
||||||
title={jobKindRu(row.original.kind)}
|
title={
|
||||||
subtitle={
|
<span className="flex min-w-0 items-center gap-1.5">
|
||||||
row.original.meta?.module_id
|
<span className="truncate">{jobKindRu(row.original.kind)}</span>
|
||||||
? (nameById.get(String(row.original.meta.module_id)) ??
|
{trigger ? (
|
||||||
String(row.original.meta.module_id))
|
<Badge variant="outline" size="sm" className="shrink-0 font-normal">
|
||||||
: undefined
|
{trigger}
|
||||||
|
</Badge>
|
||||||
|
) : null}
|
||||||
|
</span>
|
||||||
}
|
}
|
||||||
|
subtitle={jobSubtitleRu(row.original.kind, row.original.meta, nameById)}
|
||||||
/>
|
/>
|
||||||
),
|
)
|
||||||
|
},
|
||||||
meta: { headerTitle: 'Вид' },
|
meta: { headerTitle: 'Вид' },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -156,10 +164,9 @@ export function OperationsJobsGrid({
|
|||||||
onClearFilters={() => setFilterQuery(createTextFilterQuery('search'))}
|
onClearFilters={() => setFilterQuery(createTextFilterQuery('search'))}
|
||||||
getFilterFieldValue={(item, field) => {
|
getFilterFieldValue={(item, field) => {
|
||||||
if (field !== 'search') return undefined
|
if (field !== 'search') return undefined
|
||||||
const moduleName = item.meta?.module_id
|
const subtitle = jobSubtitleRu(item.kind, item.meta, nameById)
|
||||||
? (nameById.get(String(item.meta.module_id)) ?? String(item.meta.module_id))
|
const trigger = jobTriggerRu(item.meta?.trigger)
|
||||||
: ''
|
return `${jobKindRu(item.kind)} ${item.status} ${item.job_id} ${subtitle ?? ''} ${trigger ?? ''}`
|
||||||
return `${jobKindRu(item.kind)} ${item.status} ${item.job_id} ${moduleName}`
|
|
||||||
}}
|
}}
|
||||||
columns={columns}
|
columns={columns}
|
||||||
data={items}
|
data={items}
|
||||||
|
|||||||
@@ -6,12 +6,14 @@ import { ScheduleJobsGrid } from './schedule-jobs-grid'
|
|||||||
/** Jobs ResourcePage with status tabs (data-grid-filtering-2). */
|
/** Jobs ResourcePage with status tabs (data-grid-filtering-2). */
|
||||||
export function ScheduleJobsCard({
|
export function ScheduleJobsCard({
|
||||||
jobs,
|
jobs,
|
||||||
|
nameById,
|
||||||
isLoading,
|
isLoading,
|
||||||
isError,
|
isError,
|
||||||
error,
|
error,
|
||||||
onRetry,
|
onRetry,
|
||||||
}: {
|
}: {
|
||||||
jobs: JobRow[]
|
jobs: JobRow[]
|
||||||
|
nameById?: Map<string, string>
|
||||||
isLoading: boolean
|
isLoading: boolean
|
||||||
isError: boolean
|
isError: boolean
|
||||||
error: unknown
|
error: unknown
|
||||||
@@ -27,7 +29,11 @@ export function ScheduleJobsCard({
|
|||||||
onRetry={onRetry}
|
onRetry={onRetry}
|
||||||
>
|
>
|
||||||
{(items) => (
|
{(items) => (
|
||||||
<ScheduleJobsGrid items={items} isLoading={isLoading && items.length > 0} />
|
<ScheduleJobsGrid
|
||||||
|
items={items}
|
||||||
|
nameById={nameById}
|
||||||
|
isLoading={isLoading && items.length > 0}
|
||||||
|
/>
|
||||||
)}
|
)}
|
||||||
</QueryState>
|
</QueryState>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -10,7 +10,8 @@ import {
|
|||||||
createTextFilterQuery,
|
createTextFilterQuery,
|
||||||
type DataGridColumnDef,
|
type DataGridColumnDef,
|
||||||
} from '@/components/reui-kit'
|
} 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'
|
import type { JobRow } from '@/types/api'
|
||||||
|
|
||||||
const FAILED_STATUSES = ['failed', 'error', 'cancelled']
|
const FAILED_STATUSES = ['failed', 'error', 'cancelled']
|
||||||
@@ -33,9 +34,11 @@ function tabFilter(item: JobRow, tabId: string): boolean {
|
|||||||
|
|
||||||
export function ScheduleJobsGrid({
|
export function ScheduleJobsGrid({
|
||||||
items,
|
items,
|
||||||
|
nameById = new Map<string, string>(),
|
||||||
isLoading = false,
|
isLoading = false,
|
||||||
}: {
|
}: {
|
||||||
items: JobRow[]
|
items: JobRow[]
|
||||||
|
nameById?: Map<string, string>
|
||||||
isLoading?: boolean
|
isLoading?: boolean
|
||||||
}) {
|
}) {
|
||||||
const [filterQuery, setFilterQuery] = useState<FilterQuery>(() =>
|
const [filterQuery, setFilterQuery] = useState<FilterQuery>(() =>
|
||||||
@@ -64,9 +67,25 @@ export function ScheduleJobsGrid({
|
|||||||
{
|
{
|
||||||
accessorKey: 'kind',
|
accessorKey: 'kind',
|
||||||
header: ({ column }) => <DataGridColumnHeader column={column} title="Вид" />,
|
header: ({ column }) => <DataGridColumnHeader column={column} title="Вид" />,
|
||||||
cell: ({ row }) => (
|
cell: ({ row }) => {
|
||||||
<DataGridNameCell icon={ListTodo} title={jobKindRu(row.original.kind)} />
|
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: 'Вид' },
|
meta: { headerTitle: 'Вид' },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -119,7 +138,7 @@ export function ScheduleJobsGrid({
|
|||||||
meta: { headerTitle: 'Ошибка' },
|
meta: { headerTitle: 'Ошибка' },
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
[],
|
[nameById],
|
||||||
)
|
)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -134,7 +153,9 @@ export function ScheduleJobsGrid({
|
|||||||
onClearFilters={() => setFilterQuery(createTextFilterQuery('search'))}
|
onClearFilters={() => setFilterQuery(createTextFilterQuery('search'))}
|
||||||
getFilterFieldValue={(item, field) => {
|
getFilterFieldValue={(item, field) => {
|
||||||
if (field !== 'search') return undefined
|
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}
|
columns={columns}
|
||||||
data={items}
|
data={items}
|
||||||
|
|||||||
@@ -58,6 +58,54 @@ export function jobKindRu(kind: string): string {
|
|||||||
return JOB_KIND_RU[kind] ?? kind
|
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> = {
|
const JOB_STATUS_RU: Record<string, string> = {
|
||||||
queued: 'В очереди',
|
queued: 'В очереди',
|
||||||
running: 'Выполняется',
|
running: 'Выполняется',
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import { KpiStatGrid, type KpiStatItem } from '@/components/reui-kit'
|
|||||||
import { ScheduleJobsCard } from '@/components/schedule/schedule-jobs-card'
|
import { ScheduleJobsCard } from '@/components/schedule/schedule-jobs-card'
|
||||||
import { ScheduleModulesGrid } from '@/components/schedule/schedule-modules-grid'
|
import { ScheduleModulesGrid } from '@/components/schedule/schedule-modules-grid'
|
||||||
import { apiMutate } from '@/lib/api-client'
|
import { apiMutate } from '@/lib/api-client'
|
||||||
|
import { moduleNameById } from '@/queries/overview'
|
||||||
import { modulesListQueryOptions } from '@/queries/modules'
|
import { modulesListQueryOptions } from '@/queries/modules'
|
||||||
import { operationsJobsQueryOptions } from '@/queries/operations'
|
import { operationsJobsQueryOptions } from '@/queries/operations'
|
||||||
|
|
||||||
@@ -117,6 +118,7 @@ function ScheduleComponent() {
|
|||||||
|
|
||||||
<ScheduleJobsCard
|
<ScheduleJobsCard
|
||||||
jobs={jobs}
|
jobs={jobs}
|
||||||
|
nameById={moduleNameById(modules)}
|
||||||
isLoading={jobsQ.isLoading}
|
isLoading={jobsQ.isLoading}
|
||||||
isError={jobsQ.isError}
|
isError={jobsQ.isError}
|
||||||
error={jobsQ.error}
|
error={jobsQ.error}
|
||||||
|
|||||||
@@ -448,7 +448,10 @@ func (s *Server) handleModuleRefresh(w http.ResponseWriter, r *http.Request) {
|
|||||||
idemPtr = &idem
|
idemPtr = &idem
|
||||||
}
|
}
|
||||||
mid := mod.ID
|
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 {
|
if err != nil {
|
||||||
writeInternalError(w, "internal", err)
|
writeInternalError(w, "internal", err)
|
||||||
return
|
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{
|
j, _, err := s.jobs.Enqueue(a.TenantID, jobs.KindDeployApply, idemPtr, nil, map[string]any{
|
||||||
"revision_id": revID,
|
"revision_id": revID,
|
||||||
"strategy": body.Strategy,
|
"strategy": body.Strategy,
|
||||||
|
"trigger": "api",
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
writeInternalError(w, "internal", err)
|
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{
|
j, _, err := s.jobs.Enqueue(a.TenantID, jobs.KindDeployApply, idemPtr, nil, map[string]any{
|
||||||
"revision_id": revID,
|
"revision_id": revID,
|
||||||
"speaker_id": spkID,
|
"speaker_id": spkID,
|
||||||
|
"trigger": "api",
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
writeInternalError(w, "internal", err)
|
writeInternalError(w, "internal", err)
|
||||||
|
|||||||
@@ -436,9 +436,11 @@ func (w *Worker) enqueueDeployAllSpeakers(j *Job, tenantID, revID string) {
|
|||||||
if revID == "" {
|
if revID == "" {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
applyJob, _, err := w.Registry.Enqueue(tenantID, KindDeployApply, nil, nil, map[string]any{
|
meta := map[string]any{"revision_id": revID}
|
||||||
"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 {
|
if err != nil {
|
||||||
j.mergeMeta(map[string]any{"deploy_apply_enqueue_error": err.Error()})
|
j.mergeMeta(map[string]any{"deploy_apply_enqueue_error": err.Error()})
|
||||||
return
|
return
|
||||||
|
|||||||
Reference in New Issue
Block a user