Main Pages — RPC Service File¶
Part of: Main Pages guide See also: Overview | Routing & Conventions
Create src/services/rpcMyEntities.ts with RTK Query endpoints. This file defines how the WebUI communicates with the FreeIPA JSON-RPC API.
FreeIPA API reference: https://freeipa.readthedocs.io/en/latest/api/commands.html. Each entity typically provides a standard set of commands following the pattern <entity>_<action> (e.g. host_find, host_show, host_add, host_del, host_mod).
Important: rpc.ts vs rpc<Entity>.ts¶
All entity-specific endpoints must be defined in a dedicated src/services/rpc<Entity>.ts file, never in src/services/rpc.ts. The base rpc.ts file is reserved for shared/generic infrastructure only:
The base
createApidefinition and tag typesGeneric payloads (
GenericPayload) and shared types (Command,BatchRPCResponse, etc.)The generic
useGettingGenericQueryhook (for initial data loading)Shared helper functions (
getCommand,getBatchCommand)
Do NOT modify rpc.ts when adding a new entity. Specifically:
Do NOT add new
entryTypevalues toGenericPayloadoruseSearchEntriesMutation— define a dedicated search mutation in the entity’srpc<Entity>.tsfile insteadOnly add a cache tag to
tagTypesif the entity-specific service file usesprovidesTags/invalidatesTags
Entity-specific queries, mutations, and hooks are defined using api.injectEndpoints() in the entity’s own file (e.g. rpcRoles.ts, rpcTrusts.ts, rpcSelinuxUserMaps.ts).
Minimum Endpoints for a Main Page¶
A main page needs at least:
A query for initial data loading — can use the generic
useGettingGenericQuerywrapper from the entity’s service fileA search mutation for explicit search submissions — must be defined in
rpc<Entity>.tsusing the two-step find+show pattern
Template¶
// src/services/rpcMyEntities.ts
import {
api,
Command,
getBatchCommand,
getCommand,
BatchRPCResponse,
FindRPCResponse,
} from "./rpc";
import { API_VERSION_BACKUP } from "../utils/utils";
import { MyEntity } from "../utils/datatypes/globalDataTypes";
import { apiToMyEntity } from "src/utils/myEntitiesUtils";
/**
* MyEntity-related endpoints
*
* API commands:
* - myentity_find: https://freeipa.readthedocs.io/en/latest/api/myentity_find.html
* - myentity_show: https://freeipa.readthedocs.io/en/latest/api/myentity_show.html
* - myentity_add: https://freeipa.readthedocs.io/en/latest/api/myentity_add.html
* - myentity_del: https://freeipa.readthedocs.io/en/latest/api/myentity_del.html
* - myentity_mod: https://freeipa.readthedocs.io/en/latest/api/myentity_mod.html
*/
interface MyEntitiesFullDataPayload {
searchValue: string;
apiVersion?: string;
sizelimit: number;
startIdx: number;
stopIdx: number;
}
export interface MyEntityAddPayload {
cn: string; // primary key
description?: string;
// Add fields matching the IPA `myentity_add` command parameters...
}
const extendedApi = api.injectEndpoints({
endpoints: (build) => ({
/**
* List entities: two-step find+show pattern
* 1. Call `myentity_find` with pkey_only to get IDs
* 2. Batch call `myentity_show` for the paginated slice
*/
getMyEntitiesFullData: build.query<BatchRPCResponse, MyEntitiesFullDataPayload>({
async queryFn(payloadData, _queryApi, _extraOptions, fetchWithBQ) {
const { searchValue, apiVersion, sizelimit, startIdx, stopIdx } = payloadData;
const apiVersionUsed = apiVersion || API_VERSION_BACKUP;
// Step 1: Find IDs
const findCommand: Command = {
method: "myentity_find",
params: [[searchValue], { pkey_only: true, sizelimit, version: apiVersionUsed }],
};
const findResult = await fetchWithBQ(getCommand(findCommand));
if (findResult.error) return { error: findResult.error };
const findResponse = findResult.data as FindRPCResponse;
const totalCount = findResponse.result.result.length;
const ids: string[] = [];
for (let i = startIdx; i < totalCount && i < stopIdx; i++) {
const item = findResponse.result.result[i] as Record<string, unknown>;
ids.push((item.cn as string[])[0]); // Use the entity's PK field
}
// Step 2: Batch show
const showCommands: Command[] = ids.map((id) => ({
method: "myentity_show",
params: [[id], {}],
}));
const showResult = await fetchWithBQ(getBatchCommand(showCommands, apiVersionUsed));
const response = showResult.data as BatchRPCResponse;
if (response) {
response.result.totalCount = totalCount;
}
return { data: response };
},
}),
/**
* Search entities (mutation variant for explicit search submit)
*/
searchMyEntitiesEntries: build.mutation<BatchRPCResponse, MyEntitiesFullDataPayload>({
async queryFn(payloadData, _queryApi, _extraOptions, fetchWithBQ) {
// Same two-step pattern as the query above
// ...
},
}),
/**
* Add a new entity via `myentity_add`
*/
addMyEntity: build.mutation<FindRPCResponse, MyEntityAddPayload>({
query: (payload) => {
const params: Record<string, unknown> = {
version: API_VERSION_BACKUP,
};
if (payload.description) params.description = payload.description;
// Map additional optional fields...
return getCommand({
method: "myentity_add",
params: [[payload.cn], params],
});
},
}),
/**
* Delete entities via batch `myentity_del`
*/
deleteMyEntities: build.mutation<BatchRPCResponse, string[]>({
query: (payload) => {
const commands: Command[] = payload.map((id) => ({
method: "myentity_del",
params: [[id], {}],
}));
return getBatchCommand(commands, API_VERSION_BACKUP);
},
}),
/**
* Show a single entity via `myentity_show` (for detail/settings pages)
*/
myEntityShow: build.query<FindRPCResponse, string>({
query: (entityId) => {
return getCommand({
method: "myentity_show",
params: [[entityId], { all: true, rights: true }],
});
},
}),
/**
* Modify an entity via `myentity_mod` (for settings pages)
*/
myEntityMod: build.mutation<FindRPCResponse, Record<string, unknown>>({
query: (payload) => {
const { cn, ...rest } = payload;
return getCommand({
method: "myentity_mod",
params: [[cn], { ...rest, all: true, rights: true, version: API_VERSION_BACKUP }],
});
},
}),
}),
overrideExisting: false,
});
// Export the generated hooks
export const {
useGetMyEntitiesFullDataQuery,
useSearchMyEntitiesEntriesMutation,
useAddMyEntityMutation,
useDeleteMyEntitiesMutation,
useMyEntityShowQuery,
useMyEntityModMutation,
} = extendedApi;
FreeIPA API Command Naming Convention¶
Most IPA entities follow a standard set of commands. Replace <entity> with the IPA object name (e.g. host, hbacrule, trust, dnszone):
Command |
Purpose |
Used by |
|---|---|---|
|
Search/list entries |
Main page (list query) |
|
Get single entry details |
Main page (batch show) + Settings page |
|
Create a new entry |
Add modal |
|
Delete entries |
Delete modal |
|
Modify an entry |
Settings page save |
|
Enable an entry |
Enable/Disable modal (optional) |
|
Disable an entry |
Enable/Disable modal (optional) |
Full command list: https://freeipa.readthedocs.io/en/latest/api/commands.html
Individual command docs: https://freeipa.readthedocs.io/en/latest/api/<entity>_<action>.html
Existing RPC Service Examples¶
Entity |
Service file |
Key hooks |
|---|---|---|
Roles |
|
|
Trusts |
|
|
DNS Zones |
|
|
Hosts |
|
|
HBAC Rules |
|
|