{props.columnNames.map((columnName, idx) => (
{element[columnName]}
diff --git a/src/pages/Hosts/Hosts.tsx b/src/pages/Hosts/Hosts.tsx
index 6a2b351b..a09e2f9f 100644
--- a/src/pages/Hosts/Hosts.tsx
+++ b/src/pages/Hosts/Hosts.tsx
@@ -54,7 +54,7 @@ import {
useGettingHostQuery,
useGetDNSZonesQuery,
useAutoMemberRebuildHostsMutation,
- HostsPayload,
+ GenericPayload,
} from "../../services/rpc";
const Hosts = () => {
@@ -134,7 +134,7 @@ const Hosts = () => {
apiVersion: apiVersion || API_VERSION_BACKUP,
startIdx: firstHostIdx,
stopIdx: lastHostIdx,
- } as HostsPayload);
+ } as GenericPayload);
const {
data: batchResponse,
diff --git a/src/pages/Hosts/HostsTable.tsx b/src/pages/Hosts/HostsTable.tsx
index bf78cef3..7954418a 100644
--- a/src/pages/Hosts/HostsTable.tsx
+++ b/src/pages/Hosts/HostsTable.tsx
@@ -2,13 +2,13 @@ import React, { useEffect, useState } from "react";
// PatternFly
import { Td, Th, ThProps, Tr } from "@patternfly/react-table";
// Tables
-import TableLayout from "src/components/layouts/TableLayout";
+import TableLayout from "../../components/layouts/TableLayout";
// Data types
-import { Host } from "src/utils/datatypes/globalDataTypes";
+import { Host } from "../../utils/datatypes/globalDataTypes";
// Layouts
-import SkeletonOnTableLayout from "src/components/layouts/Skeleton/SkeletonOnTableLayout";
+import SkeletonOnTableLayout from "../../components/layouts/Skeleton/SkeletonOnTableLayout";
// Navigation
-import { URL_PREFIX } from "src/navigation/NavRoutes";
+import { URL_PREFIX } from "../../navigation/NavRoutes";
// React Router DOM
import { Link } from "react-router-dom";
@@ -99,22 +99,22 @@ const HostsTable = (props: PropsToTable) => {
// Since OnSort specifies sorted columns by index, we need sortable values for our object by column index.
const getSortableRowValues = (host: Host): (string | number)[] => {
- const { fqdn, description } = host;
+ const { fqdn, description, enrolledby } = host;
let descriptionString = "";
if (description !== undefined) {
descriptionString = description[0];
}
- return [fqdn, descriptionString];
+ return [fqdn, descriptionString, enrolledby ? enrolledby : ""];
};
let sortedHosts = [...shownHostsList];
if (activeSortIndex !== null) {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
sortedHosts = shownHostsList.sort((a, b) => {
- const aValue = getSortableRowValues(a)[activeSortIndex];
- const bValue = getSortableRowValues(b)[activeSortIndex];
+ let aValue = getSortableRowValues(a)[activeSortIndex];
+ let bValue = getSortableRowValues(b)[activeSortIndex];
if (typeof aValue === "number") {
// Numeric sort
if (activeSortDirection === "asc") {
@@ -123,6 +123,10 @@ const HostsTable = (props: PropsToTable) => {
return (bValue as number) - (aValue as number);
} else {
// String sort
+ if (aValue.constructor === Array) {
+ aValue = aValue[0];
+ bValue = bValue[0];
+ }
if (activeSortDirection === "asc") {
return (aValue as string).localeCompare(bValue as string);
}
diff --git a/src/pages/Services/Services.tsx b/src/pages/Services/Services.tsx
index 3c0a0007..7a9b93c9 100644
--- a/src/pages/Services/Services.tsx
+++ b/src/pages/Services/Services.tsx
@@ -1,4 +1,4 @@
-import React, { useState } from "react";
+import React, { useEffect, useState } from "react";
// PatternFly
import {
Page,
@@ -12,37 +12,64 @@ import {
OuterScrollContainer,
} from "@patternfly/react-table";
// Layouts
-import TitleLayout from "src/components/layouts/TitleLayout";
+import TitleLayout from "../../components/layouts/TitleLayout";
import ToolbarLayout, {
ToolbarItem,
-} from "src/components/layouts/ToolbarLayout";
-import SearchInputLayout from "src/components/layouts/SearchInputLayout";
-import SecondaryButton from "src/components/layouts/SecondaryButton";
-import HelpTextWithIconLayout from "src/components/layouts/HelpTextWithIconLayout";
+} from "../../components/layouts/ToolbarLayout";
+import SearchInputLayout from "../../components/layouts/SearchInputLayout";
+import SecondaryButton from "../../components/layouts/SecondaryButton";
+import HelpTextWithIconLayout from "../../components/layouts/HelpTextWithIconLayout";
// Components
-import BulkSelectorServicesPrep from "src/components/BulkSelectorServicesPrep";
-import PaginationPrep from "src/components/PaginationPrep";
+import BulkSelectorServicesPrep from "../../components/BulkSelectorServicesPrep";
+import PaginationPrep from "../../components/PaginationPrep";
// Tables
import ServicesTable from "./ServicesTable";
// Redux
-import { useAppSelector } from "src/store/hooks";
+import { useAppDispatch, useAppSelector } from "../../store/hooks";
+import { updateServicesList } from "../../store/Identity/services-slice";
// Data types
-import { Service } from "src/utils/datatypes/globalDataTypes";
+import { Host, Service } from "../../utils/datatypes/globalDataTypes";
// Utils
-import { isServiceSelectable } from "src/utils/utils";
+import { API_VERSION_BACKUP, isServiceSelectable } from "../../utils/utils";
// Icons
import OutlinedQuestionCircleIcon from "@patternfly/react-icons/dist/esm/icons/outlined-question-circle-icon";
// Modals
-import AddService from "src/components/modals/AddService";
-import DeleteServices from "src/components/modals/DeleteServices";
+import AddService from "../../components/modals/AddService";
+import DeleteServices from "../../components/modals/DeleteServices";
+// Hooks
+import { useAlerts } from "../../hooks/useAlerts";
+// Errors
+import useApiError from "../../hooks/useApiError";
+import GlobalErrors from "../../components/errors/GlobalErrors";
+import ModalErrors from "../../components/errors/ModalErrors";
+// RPC client
+import {
+ useGetHostsListQuery,
+ useGettingServicesQuery,
+ GenericPayload,
+} from "../../services/rpc";
const Services = () => {
// Initialize services list (Redux)
- const servicesList = useAppSelector((state) => state.services.servicesList);
+ const [servicesList, setServicesList] = useState([]);
+
+ // Dispatch (Redux)
+ const dispatch = useAppDispatch();
+
+ // Retrieve API version from environment data
+ const apiVersion = useAppSelector(
+ (state) => state.global.environment.api_version
+ ) as string;
+
+ // Alerts to show in the UI
+ const alerts = useAlerts();
+
+ // Handle API calls errors
+ const globalErrors = useApiError([]);
+ const modalErrors = useApiError([]);
// Selected services state
const [selectedServices, setSelectedServices] = useState([]);
-
const updateSelectedServices = (newSelectedServices: string[]) => {
setSelectedServices(newSelectedServices);
};
@@ -50,14 +77,12 @@ const Services = () => {
// 'Delete' button state
const [isDeleteButtonDisabled, setIsDeleteButtonDisabled] =
useState(true);
-
const updateIsDeleteButtonDisabled = (value: boolean) => {
setIsDeleteButtonDisabled(value);
};
// If some entries have been deleted, restore the selectedServices list
const [isDeletion, setIsDeletion] = useState(false);
-
const updateIsDeletion = (value: boolean) => {
setIsDeletion(value);
};
@@ -65,32 +90,22 @@ const Services = () => {
// Elements selected (per page)
// - This will help to calculate the remaining elements on a specific page (bulk selector)
const [selectedPerPage, setSelectedPerPage] = useState(0);
-
const updateSelectedPerPage = (selected: number) => {
setSelectedPerPage(selected);
};
// Pagination
const [page, setPage] = useState(1);
-
const updatePage = (newPage: number) => {
setPage(newPage);
};
-
const [perPage, setPerPage] = useState(15);
-
const updatePerPage = (newSetPerPage: number) => {
setPerPage(newSetPerPage);
};
-
- // Services displayed on the first page
- const [shownServicesList, setShownServicesList] = useState(
- servicesList.slice(0, perPage)
- );
-
- const updateShownServicesList = (newShownServicesList: Service[]) => {
- setShownServicesList(newShownServicesList);
- };
+ // Page indexes
+ const firstServiceIdx = (page - 1) * perPage;
+ const lastServiceIdx = page * perPage;
// Filter (Input search)
const [searchValue, setSearchValue] = React.useState("");
@@ -99,25 +114,10 @@ const Services = () => {
setSearchValue(value);
};
- // Show table rows
- const [showTableRows, setShowTableRows] = useState(false);
-
const updateShowTableRows = (value: boolean) => {
setShowTableRows(value);
};
- // Refresh displayed elements every time elements list changes (from Redux or somewhere else)
- React.useEffect(() => {
- updatePage(1);
- if (showTableRows) updateShowTableRows(false);
- setTimeout(() => {
- updateShownServicesList(servicesList.slice(0, perPage));
- updateShowTableRows(true);
- // Reset 'selectedPerPage'
- updateSelectedPerPage(0);
- }, 1000);
- }, [servicesList]);
-
// Modals functionality
const [showAddModal, setShowAddModal] = useState(false);
const [showDeleteModal, setShowDeleteModal] = useState(false);
@@ -125,6 +125,9 @@ const Services = () => {
const onAddClickHandler = () => {
setShowAddModal(true);
};
+ const onCloseAddModal = () => {
+ setShowAddModal(false);
+ };
const onAddModalToggle = () => {
setShowAddModal(!showAddModal);
};
@@ -136,6 +139,39 @@ const Services = () => {
setShowDeleteModal(!showDeleteModal);
};
+ // Get a list of the hosts
+ const [hostsList, setHostsList] = useState([]);
+ const hostDataResponse = useGetHostsListQuery();
+
+ useEffect(() => {
+ if (hostDataResponse === undefined || hostDataResponse.isFetching) {
+ return;
+ }
+ if (hostDataResponse.isSuccess && hostDataResponse.data) {
+ const hostsListResult = hostDataResponse.data.result.result;
+ const hostCount = hostDataResponse.data.result.count;
+ const hosts: Host[] = [];
+ for (let i = 0; i < hostCount; i++) {
+ const hostObj = hostsListResult[i] as Host;
+ hosts.push(hostObj);
+ }
+ setHostsList(hosts.map((hostName) => hostName.fqdn));
+ }
+
+ // API response: Error
+ if (
+ !hostDataResponse.isLoading &&
+ hostDataResponse.isError &&
+ hostDataResponse.error !== undefined
+ ) {
+ alerts.addAlert(
+ "get-host-list-error",
+ "Failed to get host list",
+ "danger"
+ );
+ }
+ }, [hostDataResponse]);
+
// Table-related shared functionality
// - Selectable checkboxes on table
const selectableServicesTable = servicesList.filter(isServiceSelectable); // elements per Table
@@ -147,17 +183,105 @@ const Services = () => {
setSelectedServiceIds(selectedServiceIds);
};
+ // Button disabled due to error
+ const [isDisabledDueError, setIsDisabledDueError] = useState(false);
+
// - Helper method to set the selected services from the table
const setServiceSelected = (service: Service, isSelecting = true) =>
setSelectedServiceIds((prevSelected) => {
const otherSelectedServiceIds = prevSelected.filter(
- (r) => r !== service.id
+ (r) => r !== service.krbcanonicalname
);
return isSelecting && isServiceSelectable(service)
- ? [...otherSelectedServiceIds, service.id]
+ ? [...otherSelectedServiceIds, service.krbcanonicalname]
: otherSelectedServiceIds;
});
+ // Derived states - what we get from API
+ const servicesDataResponse = useGettingServicesQuery({
+ searchValue: "",
+ sizeLimit: 0,
+ apiVersion: apiVersion || API_VERSION_BACKUP,
+ startIdx: firstServiceIdx,
+ stopIdx: lastServiceIdx,
+ } as GenericPayload);
+
+ const {
+ data: batchResponse,
+ isLoading: isBatchLoading,
+ error: batchError,
+ } = servicesDataResponse;
+
+ // Handle data when the API call is finished
+ useEffect(() => {
+ if (servicesDataResponse.isFetching) {
+ setShowTableRows(false);
+ // Reset selected users on refresh
+ setSelectedServices([]);
+ setSelectedServiceIds([]);
+ globalErrors.clear();
+ setIsDisabledDueError(false);
+ return;
+ }
+
+ // API response: Success
+ if (
+ servicesDataResponse.isSuccess &&
+ servicesDataResponse.data &&
+ batchResponse !== undefined
+ ) {
+ const servicesListResult = batchResponse.result.results;
+ const servicesListSize = batchResponse.result.count;
+ const servicesList: Service[] = [];
+
+ for (let i = 0; i < servicesListSize; i++) {
+ servicesList.push(servicesListResult[i].result);
+ }
+
+ // Update 'Hosts' slice data
+ dispatch(updateServicesList(servicesList));
+ setServicesList(servicesList);
+ // Show table elements
+ setShowTableRows(true);
+ }
+
+ // API response: Error
+ if (
+ !servicesDataResponse.isLoading &&
+ servicesDataResponse.isError &&
+ servicesDataResponse.error !== undefined
+ ) {
+ setIsDisabledDueError(true);
+ globalErrors.addError(
+ batchError,
+ "Error when loading data",
+ "error-batch-hosts"
+ );
+ }
+ }, [servicesDataResponse]);
+
+ // Show table rows
+ const [showTableRows, setShowTableRows] = useState(!isBatchLoading);
+
+ // Show table rows only when data is fully retrieved
+ useEffect(() => {
+ if (showTableRows !== !isBatchLoading) {
+ setShowTableRows(!isBatchLoading);
+ }
+ }, [isBatchLoading]);
+
+ // Refresh button handling
+ const refreshServicesData = () => {
+ // Hide table
+ setShowTableRows(false);
+
+ // Reset selected hosts on refresh
+ setSelectedServices([]);
+ setSelectedServiceIds([]);
+
+ servicesDataResponse.refetch();
+ };
+
// Data wrappers
// - 'SearchInputLayout'
const searchValueData = {
@@ -192,7 +316,7 @@ const Services = () => {
showTableRows,
updateShowTableRows,
updateSelectedPerPage,
- updateShownElementsList: updateShownServicesList,
+ updateShownElementsList: setHostsList,
};
// - 'ServicesTable'
@@ -229,7 +353,7 @@ const Services = () => {
element: (
{
},
{
key: 3,
- element: Refresh,
+ element: (
+
+ Refresh
+
+ ),
},
{
key: 4,
@@ -271,7 +402,10 @@ const Services = () => {
{
key: 5,
element: (
-
+
Add
),
@@ -311,6 +445,7 @@ const Services = () => {
// Render component
return (
+
@@ -327,15 +462,19 @@ const Services = () => {
-
+ {batchError !== undefined && batchError ? (
+
+ ) : (
+
+ )}
@@ -348,12 +487,21 @@ const Services = () => {
className="pf-v5-u-pb-0 pf-v5-u-pr-md"
/>
-
+
+
);
diff --git a/src/pages/Services/ServicesManagedBy.tsx b/src/pages/Services/ServicesManagedBy.tsx
index 429d41cb..bea7835b 100644
--- a/src/pages/Services/ServicesManagedBy.tsx
+++ b/src/pages/Services/ServicesManagedBy.tsx
@@ -52,7 +52,7 @@ const ServicesManagedBy = (props: PropsToServicesManagedBy) => {
// List is current elements on the list (Dummy data)
const [hostsList, setHostsList] = useState(
- getFullHostInformation([props.service.host])
+ getFullHostInformation([props.service.krbcanonicalname])
);
// Some data is updated when any group list is altered
@@ -221,7 +221,7 @@ const ServicesManagedBy = (props: PropsToServicesManagedBy) => {
const tabData = {
tabName: "Hosts",
- elementName: props.service.host,
+ elementName: props.service.krbcanonicalname,
};
// - MemberOfDeleteModal
@@ -237,7 +237,7 @@ const ServicesManagedBy = (props: PropsToServicesManagedBy) => {
const deleteTabData = {
tabName: "Hosts",
- elementName: props.service.host,
+ elementName: props.service.krbcanonicalname,
};
return (
diff --git a/src/pages/Services/ServicesMemberOf.tsx b/src/pages/Services/ServicesMemberOf.tsx
index e31d1c17..53d328a7 100644
--- a/src/pages/Services/ServicesMemberOf.tsx
+++ b/src/pages/Services/ServicesMemberOf.tsx
@@ -246,7 +246,7 @@ const ServicesMemberOf = (props: PropsToServicesMemberOf) => {
const tabData = {
tabName,
- userName: props.service.id,
+ userName: props.service.krbcanonicalname,
};
// - MemberOfDeleteModal
diff --git a/src/pages/Services/ServicesTable.tsx b/src/pages/Services/ServicesTable.tsx
index 3d3bedbd..96c13ea9 100644
--- a/src/pages/Services/ServicesTable.tsx
+++ b/src/pages/Services/ServicesTable.tsx
@@ -67,7 +67,7 @@ const ServicesTable = (props: PropsToTable) => {
"i"
);
}
- return service.id.search(input) >= 0;
+ return service.krbcanonicalname.search(input) >= 0;
};
const filteredShownServices =
@@ -87,16 +87,16 @@ const ServicesTable = (props: PropsToTable) => {
// Since OnSort specifies sorted columns by index, we need sortable values for our object by column index.
const getSortableRowValues = (service: Service): (string | number)[] => {
- const { id } = service;
- return [id];
+ const { krbcanonicalname } = service;
+ return [krbcanonicalname];
};
let sortedServices = [...shownServicesList];
if (activeSortIndex !== null) {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
sortedServices = shownServicesList.sort((a, b) => {
- const aValue = getSortableRowValues(a)[activeSortIndex];
- const bValue = getSortableRowValues(b)[activeSortIndex];
+ let aValue = getSortableRowValues(a)[activeSortIndex][0];
+ let bValue = getSortableRowValues(b)[activeSortIndex][0];
if (typeof aValue === "number") {
// Numeric sort
if (activeSortDirection === "asc") {
@@ -105,6 +105,10 @@ const ServicesTable = (props: PropsToTable) => {
return (bValue as number) - (aValue as number);
} else {
// String sort
+ if (aValue.constructor === Array) {
+ aValue = aValue[0];
+ bValue = bValue[0];
+ }
if (activeSortDirection === "asc") {
return (aValue as string).localeCompare(bValue as string);
}
@@ -129,7 +133,7 @@ const ServicesTable = (props: PropsToTable) => {
});
const isServiceSelected = (service: Service) =>
- props.servicesData.selectedServiceIds.includes(service.id);
+ props.servicesData.selectedServiceIds.includes(service.krbcanonicalname);
// To allow shift+click to select/deselect multiple rows
const [recentSelectedRowIndex, setRecentSelectedRowIndex] = useState<
@@ -173,14 +177,14 @@ const ServicesTable = (props: PropsToTable) => {
// Update serviceIdsSelected array
let serviceIdsSelectedArray = props.servicesData.selectedServiceIds;
if (isSelecting) {
- serviceIdsSelectedArray.push(service.id);
+ serviceIdsSelectedArray.push(service.krbcanonicalname);
// Increment the elements selected per page (++)
props.paginationData.updateSelectedPerPage(
props.paginationData.selectedPerPage + 1
);
} else {
serviceIdsSelectedArray = serviceIdsSelectedArray.filter(
- (serviceId) => serviceId !== service.id
+ (serviceId) => serviceId !== service.krbcanonicalname
);
// Decrement the elements selected per page (--)
props.paginationData.updateSelectedPerPage(
@@ -243,7 +247,7 @@ const ServicesTable = (props: PropsToTable) => {
);
const body = filteredShownServices.map((service, rowIndex) => (
-
+
{
/>
|
- {service.id}
+ {service.krbcanonicalname}
|
diff --git a/src/pages/Services/ServicesTabs.tsx b/src/pages/Services/ServicesTabs.tsx
index 66b75498..19ebc14e 100644
--- a/src/pages/Services/ServicesTabs.tsx
+++ b/src/pages/Services/ServicesTabs.tsx
@@ -51,13 +51,13 @@ const ServicesTabs = () => {
diff --git a/src/services/rpc.ts b/src/services/rpc.ts
index fe63cbf0..327eee94 100644
--- a/src/services/rpc.ts
+++ b/src/services/rpc.ts
@@ -18,11 +18,14 @@ import {
Metadata,
PwPolicy,
RadiusServer,
+ servicesType,
UIDType,
User,
+ Service,
} from "../utils/datatypes/globalDataTypes";
-import { apiToUser } from "../utils/userUtils";
import { apiToHost } from "../utils/hostUtils";
+import { apiToUser } from "../utils/userUtils";
+import { apiToService } from "../utils/serviceUtils";
import { apiToPwPolicy } from "../utils/pwPolicyUtils";
import { apiToKrbPolicy } from "../utils/krbPolicyUtils";
@@ -137,12 +140,16 @@ export interface UsersPayload {
stopIdx: number;
}
-export interface HostsPayload {
+// Convert to BasicPayload (use for Hosts services, etc)
+export interface GenericPayload {
+ method: string;
searchValue: string;
sizeLimit: number;
apiVersion: string;
startIdx: number;
stopIdx: number;
+ objName?: string;
+ objAttr?: string;
}
export interface HostAddPayload {
@@ -154,6 +161,12 @@ export interface HostAddPayload {
description?: string;
}
+export interface ServiceAddPayload {
+ service: string;
+ skip_host_check: boolean;
+ force: boolean; // skip DNS check
+}
+
// Body data to perform the calls
export const getCommand = (commandData: Command) => {
const payloadWithParams = {
@@ -212,6 +225,7 @@ export const api = createApi({
"CertProfile",
"DNSZones",
"FullHost",
+ "FullService",
],
endpoints: (build) => ({
simpleCommand: build.query({
@@ -231,91 +245,6 @@ export const api = createApi({
query: (payloadData: Command[], apiVersion?: string) =>
getBatchCommand(payloadData, apiVersion || API_VERSION_BACKUP),
}),
- gettingUser: build.query({
- async queryFn(payloadData, _queryApi, _extraOptions, fetchWithBQ) {
- const {
- searchValue,
- sizeLimit,
- apiVersion,
- userType,
- startIdx,
- stopIdx,
- } = payloadData;
-
- // 1ST CALL - GETTING ALL UIDS
- if (apiVersion === undefined) {
- return {
- error: {
- status: "CUSTOM_ERROR",
- data: "",
- error: "API version not available",
- } as FetchBaseQueryError,
- };
- }
-
- // Prepare search parameters
- const params = {
- pkey_only: true,
- sizelimit: sizeLimit,
- version: apiVersion,
- };
- let method = "user_find";
- let show_method = "user_show";
- if (userType === "stage") {
- method = "stageuser_find";
- show_method = "stageuser_show";
- } else if (userType === "preserved") {
- params["preserved"] = true;
- }
-
- // Prepare payload
- const payloadDataUids: Command = {
- method: method,
- params: [[searchValue], params],
- };
-
- // Make call using 'fetchWithBQ'
- const getUidsResult = await fetchWithBQ(getCommand(payloadDataUids));
- // Return possible errors
- if (getUidsResult.error) {
- return { error: getUidsResult.error as FetchBaseQueryError };
- }
- // If no error: cast and assign 'uids'
- const uidResponseData = getUidsResult.data as FindRPCResponse;
-
- const uids: string[] = [];
- const itemsCount = uidResponseData.result.result.length as number;
- for (let i = startIdx; i < itemsCount && i < stopIdx; i++) {
- const userId = uidResponseData.result.result[i] as UIDType;
- const { uid } = userId;
- uids.push(uid[0] as string);
- }
-
- // 2ND CALL - GET PARTIAL USERS INFO
- // Prepare payload
- const options = { no_members: true };
- const payloadUserDataBatch: Command[] = uids.map((uid) => ({
- method: show_method,
- params: [[uid], options],
- }));
-
- // Make call using 'fetchWithBQ'
- const partialUsersInfoResult = await fetchWithBQ(
- getBatchCommand(payloadUserDataBatch as Command[], apiVersion)
- );
-
- const response = partialUsersInfoResult.data as BatchRPCResponse;
- response.result.totalCount = itemsCount;
-
- // Return results
- return partialUsersInfoResult.data
- ? { data: response }
- : {
- error:
- partialUsersInfoResult.error as unknown as FetchBaseQueryError,
- };
- },
- }),
getObjectMetadata: build.query({
query: () => {
return getCommand({
@@ -461,6 +390,24 @@ export const api = createApi({
},
providesTags: ["FullHost"],
}),
+ getServicesFullData: build.query({
+ query: (serviceName: string) => {
+ // Prepare search parameters
+ const params = {
+ all: true,
+ rights: true,
+ version: API_VERSION_BACKUP,
+ };
+ return getCommand({
+ method: "service_show",
+ params: [serviceName, params],
+ });
+ },
+ transformResponse: (response: FindRPCResponse): Service => {
+ return apiToService(response.result.result);
+ },
+ providesTags: ["FullService"],
+ }),
saveUser: build.mutation>({
query: (user) => {
const params = {
@@ -687,11 +634,28 @@ export const api = createApi({
response.result.result as unknown as User[],
providesTags: ["ActiveUsers"],
}),
- // Hosts
- gettingHost: build.query({
+ // Basic find/show query: Hosts, Services, ...
+ gettingGeneric: build.query({
async queryFn(payloadData, _queryApi, _extraOptions, fetchWithBQ) {
- const { searchValue, sizeLimit, apiVersion, startIdx, stopIdx } =
- payloadData;
+ const {
+ searchValue,
+ sizeLimit,
+ apiVersion,
+ startIdx,
+ stopIdx,
+ objAttr,
+ } = payloadData;
+ let objName = payloadData.objName;
+
+ if (objAttr === undefined || objName === undefined) {
+ return {
+ error: {
+ status: "CUSTOM_ERROR",
+ data: "",
+ error: "Missing required param",
+ } as FetchBaseQueryError,
+ };
+ }
if (apiVersion === undefined) {
return {
@@ -708,12 +672,16 @@ export const api = createApi({
pkey_only: true,
sizelimit: sizeLimit,
version: apiVersion,
- all: true,
};
+ if (objName === "preserved") {
+ params["preserved"] = true;
+ objName = "user";
+ }
+
// Prepare payload
const payloadDataIds: Command = {
- method: "host_find",
+ method: objName + "_find",
params: [[searchValue], params],
};
@@ -724,36 +692,47 @@ export const api = createApi({
return { error: getGroupIDsResult.error as FetchBaseQueryError };
}
// If no error: cast and assign 'uids'
- const hostIdResponseData = getGroupIDsResult.data as FindRPCResponse;
-
- const fqdns: string[] = [];
- const itemsCount = hostIdResponseData.result.result.length as number;
+ const idResponseData = getGroupIDsResult.data as FindRPCResponse;
+ const ids: string[] = [];
+ const itemsCount = idResponseData.result.result.length as number;
for (let i = startIdx; i < itemsCount && i < stopIdx; i++) {
- const hostId = hostIdResponseData.result.result[i] as fqdnType;
- const { fqdn } = hostId;
- fqdns.push(fqdn[0] as string);
+ let id;
+ if (objName === "host") {
+ id = idResponseData.result.result[i] as fqdnType;
+ } else if (objName === "service") {
+ id = idResponseData.result.result[i] as servicesType;
+ } else if (objName === "user" || objName === "stage") {
+ id = idResponseData.result.result[i] as UIDType;
+ } else {
+ // Unknown, should never happen
+ return {
+ error: {
+ status: "CUSTOM_ERROR",
+ data: "",
+ error: "Unknown object name " + objName,
+ } as FetchBaseQueryError,
+ };
+ }
+ ids.push(id[objAttr][0] as string);
}
- // 2ND CALL - GET PARTIAL HOSTS INFO
+ // 2ND CALL - GET PARTIAL INFO
// Prepare payload
- const payloadHostDataBatch: Command[] = fqdns.map((fqdn) => ({
- method: "host_show",
- params: [[fqdn], { no_members: true }],
+ const payloadDataBatch: Command[] = ids.map((name) => ({
+ method: objName + "_show",
+ params: [[name], {}],
}));
// Make call using 'fetchWithBQ'
- const partialHostsInfoResult = await fetchWithBQ(
- getBatchCommand(payloadHostDataBatch as Command[], apiVersion)
+ const partialInfoResult = await fetchWithBQ(
+ getBatchCommand(payloadDataBatch as Command[], apiVersion)
);
// Return results
- const response = partialHostsInfoResult.data as BatchRPCResponse;
- response.result.totalCount = itemsCount;
- return partialHostsInfoResult.data
- ? { data: response }
+ return partialInfoResult.data
+ ? { data: partialInfoResult.data as BatchRPCResponse }
: {
- error:
- partialHostsInfoResult.error as unknown as FetchBaseQueryError,
+ error: partialInfoResult.error as unknown as FetchBaseQueryError,
};
},
}),
@@ -871,6 +850,22 @@ export const api = createApi({
});
},
}),
+ addService: build.mutation({
+ query: (payloadData) => {
+ const params = [
+ [payloadData["service"]],
+ {
+ version: API_VERSION_BACKUP,
+ force: payloadData["force"],
+ skip_host_check: payloadData["skip_host_check"],
+ },
+ ];
+ return getCommand({
+ method: "service_add",
+ params: params,
+ });
+ },
+ }),
getCertProfile: build.query({
query: () => {
return getCommand({
@@ -948,10 +943,23 @@ export const api = createApi({
return getBatchCommand(hostsToDeletePayload, API_VERSION_BACKUP);
},
}),
- getDNSZones: build.query({
- query() {
+ removeServices: build.mutation({
+ query: (services) => {
+ const servicesToDeletePayload: Command[] = [];
+ services.map((service) => {
+ const payloadItem = {
+ method: "service_del",
+ params: [[service], {}],
+ } as Command;
+ servicesToDeletePayload.push(payloadItem);
+ });
+ return getBatchCommand(servicesToDeletePayload, API_VERSION_BACKUP);
+ },
+ }),
+ getGenericList: build.query({
+ query(objName) {
return getCommand({
- method: "dnszone_find",
+ method: objName + "_find",
params: [[], { version: API_VERSION_BACKUP }],
});
},
@@ -959,20 +967,31 @@ export const api = createApi({
}),
});
-// Wrappers for active, preserved, and stage users
+// Wrappers
+export const useGetDNSZonesQuery = () => {
+ return useGetGenericListQuery("dnszone");
+};
+
+export const useGetHostsListQuery = () => {
+ return useGetGenericListQuery("host");
+};
+
export const useGettingActiveUserQuery = (payloadData) => {
- payloadData["userType"] = "user";
- return useGettingUserQuery(payloadData);
+ payloadData["objName"] = "user";
+ payloadData["objAttr"] = "uid";
+ return useGettingGenericQuery(payloadData);
};
export const useGettingStageUserQuery = (payloadData) => {
- payloadData["userType"] = "stage";
- return useGettingUserQuery(payloadData);
+ payloadData["objName"] = "stageuser";
+ payloadData["objAttr"] = "uid";
+ return useGettingGenericQuery(payloadData);
};
export const useGettingPreservedUserQuery = (payloadData) => {
- payloadData["userType"] = "preserved";
- return useGettingUserQuery(payloadData);
+ payloadData["objName"] = "preserved";
+ payloadData["objAttr"] = "uid";
+ return useGettingGenericQuery(payloadData);
};
export const useGetUsersFullQuery = (userId: string) => {
@@ -994,12 +1013,23 @@ export const useGetStageUsersFullQuery = (userId: string) => {
return useGetGenericUsersFullDataQuery(query_args);
};
+export const useGettingHostQuery = (payloadData) => {
+ payloadData["objName"] = "host";
+ payloadData["objAttr"] = "fqdn";
+ return useGettingGenericQuery(payloadData);
+};
+
+export const useGettingServicesQuery = (payloadData) => {
+ payloadData["objName"] = "service";
+ payloadData["objAttr"] = "krbprincipalname";
+ return useGettingGenericQuery(payloadData);
+};
+
export const {
useSimpleCommandQuery,
useSimpleMutCommandMutation,
useBatchCommandQuery,
useBatchMutCommandMutation,
- useGettingUserQuery,
useGetObjectMetadataQuery,
useGetGenericUsersFullDataQuery,
useSaveUserMutation,
@@ -1020,7 +1050,6 @@ export const {
useRemoveHostPrincipalAliasMutation,
useAddHostPrincipalAliasMutation,
useGetActiveUsersQuery,
- useGettingHostQuery,
useAutoMemberRebuildHostsMutation,
useAutoMemberRebuildUsersMutation,
useEnableUserMutation,
@@ -1033,8 +1062,11 @@ export const {
useActivateUserMutation,
useRestoreUserMutation,
useAddHostMutation,
+ useAddServiceMutation,
useRemoveHostsMutation,
- useGetDNSZonesQuery,
useSaveHostMutation,
useGetHostsFullDataQuery,
+ useGettingGenericQuery,
+ useGetGenericListQuery,
+ useRemoveServicesMutation,
} = api;
diff --git a/src/store/Identity/services-slice.ts b/src/store/Identity/services-slice.ts
index 95032a42..bdd1794e 100644
--- a/src/store/Identity/services-slice.ts
+++ b/src/store/Identity/services-slice.ts
@@ -1,33 +1,31 @@
import { createSlice, PayloadAction } from "@reduxjs/toolkit";
-// User data (JSON file)
-import servicesJson from "./services.json";
// Data types
-import { Service } from "src/utils/datatypes/globalDataTypes";
+import { Service } from "../../utils/datatypes/globalDataTypes";
interface ServicesState {
servicesList: Service[];
}
const initialState: ServicesState = {
- servicesList: servicesJson,
+ servicesList: [],
};
const servicesSlice = createSlice({
name: "services",
initialState,
reducers: {
+ updateServicesList: (state, action: PayloadAction) => {
+ const updatedServicesList = action.payload;
+ state.servicesList = updatedServicesList;
+ },
addService: (state, action: PayloadAction) => {
const newService = action.payload;
- state.servicesList.push({
- id: newService.id,
- serviceType: newService.serviceType,
- host: newService.host,
- });
+ state.servicesList.push({ ...newService });
},
removeService: (state, action: PayloadAction) => {
const serviceId = action.payload;
const updatedServiceList = state.servicesList.filter(
- (service) => service.id !== serviceId
+ (service) => service.krbcanonicalname !== serviceId
);
// If not empty, replace list by new array
if (updatedServiceList) {
@@ -38,4 +36,5 @@ const servicesSlice = createSlice({
});
export default servicesSlice.reducer;
-export const { addService, removeService } = servicesSlice.actions;
+export const { updateServicesList, addService, removeService } =
+ servicesSlice.actions;
diff --git a/src/utils/datatypes/globalDataTypes.ts b/src/utils/datatypes/globalDataTypes.ts
index 0a21cca9..136e71ba 100644
--- a/src/utils/datatypes/globalDataTypes.ts
+++ b/src/utils/datatypes/globalDataTypes.ts
@@ -191,9 +191,17 @@ export interface HostGroup {
}
export interface Service {
- id: string;
serviceType: string;
- host: string;
+ krbcanonicalname: string;
+ krbprincipalname: string[];
+ krbprincipalauthind: string[];
+ sshpublickey: string[];
+ usercertificate: string[];
+ ipakrbauthzdata: string[]; // pac_type: MS-PAC, PAD, NONE
+ managedby_host: string[];
+ ipakrbrequirespreauth: boolean;
+ ipakrbokasdelegate: boolean;
+ ipakrboktoauthasdelegate: boolean;
}
// Errors
@@ -320,6 +328,11 @@ export interface fqdnType {
fqdn: string[];
}
+export interface servicesType {
+ dn: string;
+ krbprincipalname: string[];
+}
+
export interface CertProfile {
cn: string;
description: string;
diff --git a/src/utils/serviceUtils.tsx b/src/utils/serviceUtils.tsx
new file mode 100644
index 00000000..ef05c918
--- /dev/null
+++ b/src/utils/serviceUtils.tsx
@@ -0,0 +1,69 @@
+// Data types
+import { Service } from "../utils/datatypes/globalDataTypes";
+// Utils
+import { convertApiObj } from "../utils/ipaObjectUtils";
+
+// Parse the 'textInputField' data into expected data type
+// - TODO: Adapt it to work with many types of data
+export const asRecord = (
+ // property: string,
+ element: Partial,
+ onElementChange: (element: Partial) => void
+ // metadata: Metadata
+) => {
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ const ipaObject = element as Record;
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ function recordOnChange(ipaObject: Record) {
+ onElementChange(ipaObject as Service);
+ }
+
+ return { ipaObject, recordOnChange };
+};
+
+const simpleValues = new Set(["dn", "krbcanonicalname"]);
+const dateValues = new Set([]);
+
+export function apiToService(apiRecord: Record): Service {
+ const converted = convertApiObj(
+ apiRecord,
+ simpleValues,
+ dateValues
+ ) as Partial;
+ return partialServiceToService(converted) as Service;
+}
+
+// Determines whether a given property name is a simple value or is it multivalue (Array)
+// - Returns: boolean
+export const isSimpleValue = (propertyName) => {
+ return simpleValues.has(propertyName);
+};
+
+export function partialServiceToService(
+ partialService: Partial
+): Service {
+ return {
+ ...createEmptyService(),
+ ...partialService,
+ };
+}
+
+// Covert an partial Service object into a full Sservice object
+// (initializing the undefined params with default empty values)
+export function createEmptyService(): Service {
+ const service: Service = {
+ serviceType: "",
+ krbcanonicalname: "",
+ krbprincipalname: [],
+ krbprincipalauthind: [],
+ sshpublickey: [],
+ usercertificate: [],
+ ipakrbauthzdata: [],
+ managedby_host: [],
+ ipakrbrequirespreauth: false,
+ ipakrbokasdelegate: false,
+ ipakrboktoauthasdelegate: false,
+ };
+
+ return service;
+}
diff --git a/src/utils/utils.tsx b/src/utils/utils.tsx
index 41ebedb5..212a288a 100644
--- a/src/utils/utils.tsx
+++ b/src/utils/utils.tsx
@@ -31,7 +31,8 @@ export const isUserSelectable = (user: User) => user.uid !== "";
export const isHostSelectable = (host: Host) => host.fqdn != "";
// Determine whether a service is selectable or not
-export const isServiceSelectable = (service: Service) => service.id != "";
+export const isServiceSelectable = (service: Service) =>
+ service.krbcanonicalname != "";
// Write JSX error messages into 'apiErrorsJsx' array
export const apiErrorToJsXError = (
|