diff --git a/ui/api/rebalance/actions.ts b/ui/api/rebalance/actions.ts new file mode 100644 index 000000000..5923e3d19 --- /dev/null +++ b/ui/api/rebalance/actions.ts @@ -0,0 +1,81 @@ +"use server"; +import { logger } from "@/utils/logger"; +import { + RebalanceResponse, + RebalanceResponseSchema, + RebalanceSchema, + RebalancesResponse, + RebalanceStatus, +} from "./schema"; +import { filterUndefinedFromObj } from "@/utils/filterUndefinedFromObj"; +import { getHeaders } from "@/api/api"; + +const log = logger.child({ module: "rebalance-api" }); + +export async function getRebalancesList( + kafkaId: string, + params: { + name?: string; + mode?: string; + status?: RebalanceStatus[]; + pageSize?: number; + pageCursor?: string; + sort?: string; + sortDir?: string; + }, +): Promise { + const sp = new URLSearchParams( + filterUndefinedFromObj({ + "fields[kafkaRebalances]": + "name,namespace,creationTimestamp,status,mode,brokers", + "filter[name]": params.name ? `like,*${params.name}*` : undefined, + "filter[status]": + params.status && params.status.length > 0 + ? `in,${params.status.join(",")}` + : undefined, + "filter[mode]": params.mode ? `like,*${params.mode}*` : undefined, + "page[size]": params.pageSize, + "page[after]": params.pageCursor, + sort: params.sort + ? (params.sortDir !== "asc" ? "-" : "") + params.sort + : undefined, + }), + ); + const rebalanceQuery = sp.toString(); + const url = `${process.env.BACKEND_URL}/api/kafkas/${kafkaId}/rebalances?${rebalanceQuery}`; + const res = await fetch(url, { + headers: await getHeaders(), + next: { + tags: ["rebalances"], + }, + }); + + log.debug({ url }, "getRebalanceList"); + const rawData = await res.json(); + log.trace({ url, rawData }, "getRebalanceList response"); + return RebalanceResponseSchema.parse(rawData); +} + +export async function getRebalanceDetails( + kafkaId: string, + rebalanceId: string, +): Promise { + const url = `${process.env.BACKEND_URL}/api/kafkas/${kafkaId}/rebalances/${rebalanceId}`; + const decodedRebalanceId = decodeURIComponent(rebalanceId); + const body = { + data: { + type: "kafkaRebalances", + id: decodedRebalanceId, + meta: {}, + attributes: {}, + }, + }; + log.debug({ url }, "Fetching rebalance details"); + const res = await fetch(url, { + headers: await getHeaders(), + method: "PATCH", + body: JSON.stringify(body), + }); + const rawData = await res.json(); + return RebalanceSchema.parse(rawData.data); +} diff --git a/ui/api/rebalance/schema.ts b/ui/api/rebalance/schema.ts new file mode 100644 index 000000000..e2e8a9328 --- /dev/null +++ b/ui/api/rebalance/schema.ts @@ -0,0 +1,92 @@ +import { z } from "zod"; + +const RebalanceStatusSchema = z.union([ + z.literal("New"), + z.literal("PendingProposal"), + z.literal("ProposalReady"), + z.literal("Rebalancing"), + z.literal("Stopped"), + z.literal("NotReady"), + z.literal("Ready"), + z.literal("ReconciliationPaused"), +]); + +const OptimizationResultSchema = z.object({ + numIntraBrokerReplicaMovements: z.number(), + numReplicaMovements: z.number(), + onDemandBalancednessScoreAfter: z.number(), + afterBeforeLoadConfigMap: z.string(), + intraBrokerDataToMoveMB: z.number(), + monitoredPartitionsPercentage: z.number(), + provisionRecommendation: z.string(), + excludedBrokersForReplicaMove: z.array(z.string()).nullable(), + excludedBrokersForLeadership: z.array(z.string()).nullable(), + provisionStatus: z.string(), + onDemandBalancednessScoreBefore: z.number(), + recentWindows: z.number(), + dataToMoveMB: z.number(), + excludedTopics: z.array(z.string()).nullable(), + numLeaderMovements: z.number(), +}); + +export const RebalanceSchema = z.object({ + id: z.string(), + type: z.literal("kafkaRebalances"), + meta: z + .object({ + autoApproval: z.boolean().optional(), + allowedActions: z.array(z.string()), + }) + .optional(), + attributes: z.object({ + name: z.string(), + namespace: z.string(), + creationTimestamp: z.string(), + status: RebalanceStatusSchema, + mode: z.string().optional(), + brokers: z.array(z.number()).nullable(), + sessionId: z.string().optional(), + optimizationResult: OptimizationResultSchema, + }), +}); + +const RebalancesListSchema = z.object({ + id: z.string(), + type: z.literal("kafkaRebalances"), + meta: z.object({ + page: z.object({ + cursor: z.string(), + }), + autoApproval: z.boolean(), + managed: z.boolean().optional(), + }), + attributes: RebalanceSchema.shape.attributes.pick({ + name: true, + status: true, + creationTimestamp: true, + mode: true, + brokers: true, + }), +}); + +export const RebalanceResponseSchema = z.object({ + meta: z.object({ + page: z.object({ + total: z.number(), + pageNumber: z.number().optional(), + }), + }), + links: z.object({ + first: z.string().nullable(), + prev: z.string().nullable(), + next: z.string().nullable(), + last: z.string().nullable(), + }), + data: z.array(RebalancesListSchema), +}); +export type RebalanceList = z.infer; +export type RebalancesResponse = z.infer; + +export type RebalanceResponse = z.infer; + +export type RebalanceStatus = z.infer;