Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Stage users #181

Merged
merged 1 commit into from
Nov 15, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions src/components/BulkSelectorUsersPrep.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,9 @@ const BulkSelectorPrep = (props: PropsToBulkSelectorPrep) => {
isSelecting = true,
selectableUsersList: User[]
) => {
if (selectableUsersList.length === 0) {
return;
}
props.usersData.changeSelectedUserNames(
isSelecting ? selectableUsersList.map((r) => r.uid) : []
);
Expand Down
14 changes: 11 additions & 3 deletions src/components/Form/PrincipalAliasMultiTextBox.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ import {
ErrorResult,
useAddPrincipalAliasMutation,
useRemovePrincipalAliasMutation,
useAddStagePrincipalAliasMutation,
useRemoveStagePrincipalAliasMutation,
} from "src/services/rpc";
// Layouts
import SecondaryButton from "../layouts/SecondaryButton";
Expand All @@ -25,16 +27,22 @@ interface PrincipalAliasMultiTextBoxProps {
ipaObject: Record<string, unknown>;
metadata: Metadata;
onRefresh: () => void;
from: "active-users" | "stage-users" | "preserved-users";
}

const PrincipalAliasMultiTextBox = (props: PrincipalAliasMultiTextBoxProps) => {
// Alerts to show in the UI
const alerts = useAlerts();

// RTK hooks
const [addPrincipalAlias] = useAddPrincipalAliasMutation();
const [removePrincipalAlias] = useRemovePrincipalAliasMutation();

let [addPrincipalAlias] = useAddPrincipalAliasMutation();
if (props.from === "stage-users") {
[addPrincipalAlias] = useAddStagePrincipalAliasMutation();
}
let [removePrincipalAlias] = useRemovePrincipalAliasMutation();
if (props.from === "stage-users") {
[removePrincipalAlias] = useRemoveStagePrincipalAliasMutation();
}
// 'krbprincipalname' value from ipaObject
const krbprincipalname = props.ipaObject["krbprincipalname"] as string[];

Expand Down
13 changes: 10 additions & 3 deletions src/components/UserSettings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,11 @@ import UsersMailingAddress from "src/components/UsersSections/UsersMailingAddres
import UsersEmployeeInfo from "src/components/UsersSections/UsersEmployeeInfo";
import UsersAttributesSMB from "src/components/UsersSections/UsersAttributesSMB";
// RPC
import { ErrorResult, useSaveUserMutation } from "src/services/rpc";
import {
ErrorResult,
useSaveUserMutation,
useSaveStageUserMutation,
} from "src/services/rpc";
// Hooks
import useAlerts from "src/hooks/useAlerts";

Expand Down Expand Up @@ -67,8 +71,11 @@ const UserSettings = (props: PropsToUserSettings) => {
// Alerts to show in the UI
const alerts = useAlerts();

// RTK hook: save user
const [saveUser] = useSaveUserMutation();
// RTK hook: save user (acive/preserved and stage)
let [saveUser] = useSaveUserMutation();
if (props.from === "stage-users") {
[saveUser] = useSaveStageUserMutation();
}

// Kebab
const [isKebabOpen, setIsKebabOpen] = useState(false);
Expand Down
7 changes: 7 additions & 0 deletions src/components/UsersSections/UsersAccountSettings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,12 @@ interface PropsToUsersAccountSettings {
from: "active-users" | "stage-users" | "preserved-users";
}

// Generic data to pass to the Textbox adder
interface ElementData {
id: string | number;
element: string;
}

const UsersAccountSettings = (props: PropsToUsersAccountSettings) => {
// TODO: Handle the `has_password` variable (boolean) by another Ipa component
const [password] = useState("");
Expand Down Expand Up @@ -185,6 +191,7 @@ const UsersAccountSettings = (props: PropsToUsersAccountSettings) => {
ipaObject={ipaObject}
metadata={props.metadata}
onRefresh={props.onRefresh}
from={props.from}
/>
</FormGroup>
<FormGroup
Expand Down
252 changes: 252 additions & 0 deletions src/components/modals/ActivateStageUsers.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,252 @@
import React, { useState } from "react";
// PatternFly
import {
Button,
Checkbox,
Text,
TextContent,
TextVariants,
} from "@patternfly/react-core";
// Layouts
import ModalWithFormLayout from "src/components/layouts/ModalWithFormLayout";
// Tables
import UsersDisplayTable from "src/components/tables/UsersDisplayTable";
// Redux
import { useAppDispatch } from "src/store/hooks";
import { removeUser as removeStageUser } from "src/store/Identity/stageUsers-slice";
// RPC
import {
Command,
BatchRPCResponse,
useBatchMutCommandMutation,
} from "src/services/rpc";
import { FetchBaseQueryError } from "@reduxjs/toolkit/dist/query";
import { SerializedError } from "@reduxjs/toolkit";
// Modals
import ErrorModal from "./ErrorModal";
// Data types
import { ErrorData } from "src/utils/datatypes/globalDataTypes";
// Hooks
import useAlerts from "src/hooks/useAlerts";

interface SelectedUsersData {
selectedUsers: string[];
updateSelectedUsers: (newSelectedUsers: string[]) => void;
}

export interface PropsToActivateUsers {
show: boolean;
handleModalToggle: () => void;
selectedUsersData: SelectedUsersData;
onRefresh?: () => void;
onOpenDeleteModal?: () => void;
onCloseDeleteModal?: () => void;
}

const ActivateStageUsers = (props: PropsToActivateUsers) => {
// Set dispatch (Redux)
const dispatch = useAppDispatch();

// Alerts
const alerts = useAlerts();

// Define 'executeUserStageCommand' to activate user data to IPA server
const [executeUserActivateCommand] = useBatchMutCommandMutation();

const [noMembersChecked, setNoMembers] = useState<boolean>(false);

// List of fields
const fields = [
{
id: "question-text",
pfComponent: (
<TextContent>
<Text component={TextVariants.p}>
Are you sure you want to activate the selected stage users?
</Text>
</TextContent>
),
},
{
id: "activate-users-table",
pfComponent: (
<UsersDisplayTable
usersToDisplay={props.selectedUsersData.selectedUsers}
from={"stage-users"}
/>
),
},
{
id: "no-members",
pfComponent: (
<Checkbox
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the purpose of this checkbox? This is not in the current WebUI either.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh yeah, this is an option you can use with the CLI when activating users. Figured if the CLI can do it, then so should the UI.

[vagrant@server ~]$ ipa stageuser-activate --help
Usage: ipa [global-options] stageuser-activate LOGIN [options]

Activate a stage user.
Options:
  -h, --help    show this help message and exit
  --all         Retrieve and print all attributes from the server. Affects
                command output.
  --raw         Print entries as stored on the server. Only affects output
                format.
  --no-members  Suppress processing of membership attributes.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, didn't know about this. I think it is a nice feature to keep then :)

label="Suppress processing of membership attributes"
isChecked={noMembersChecked}
onChange={() => {
setNoMembers(!noMembersChecked);
}}
id="no-members-checkbox"
name="no-members"
/>
),
},
];

// Close modal
const closeModal = () => {
props.handleModalToggle();
};

// Handle API error data
const [isModalErrorOpen, setIsModalErrorOpen] = useState(false);
const [errorTitle, setErrorTitle] = useState("");
const [errorMessage, setErrorMessage] = useState("");

const closeAndCleanErrorParameters = () => {
setIsModalErrorOpen(false);
setErrorTitle("");
setErrorMessage("");
};

const onCloseErrorModal = () => {
closeAndCleanErrorParameters();
};

const errorModalActions = [
<Button key="cancel" variant="link" onClick={onCloseErrorModal}>
OK
</Button>,
];

const handleAPIError = (error: FetchBaseQueryError | SerializedError) => {
if ("code" in error) {
setErrorTitle("IPA error " + error.code + ": " + error.name);
if (error.message !== undefined) {
setErrorMessage(error.message);
}
} else if ("data" in error) {
const errorData = error.data as ErrorData;
const errorCode = errorData.code as string;
const errorName = errorData.name as string;
const errorMessage = errorData.error as string;

setErrorTitle("IPA error " + errorCode + ": " + errorName);
setErrorMessage(errorMessage);
}
setIsModalErrorOpen(true);
};

// Stage user
const activateUsers = () => {
// Prepare users params
const uidsToActivatePayload: Command[] = [];

props.selectedUsersData.selectedUsers.map((uid) => {
const payloadItem = {
method: "stageuser_activate",
params: [uid, { no_members: noMembersChecked }],
} as Command;
uidsToActivatePayload.push(payloadItem);
});

// [API call] activate elements
executeUserActivateCommand(uidsToActivatePayload).then((response) => {
if ("data" in response) {
const data = response.data as BatchRPCResponse;
const result = data.result;
const error = data.error as FetchBaseQueryError | SerializedError;

if (result) {
if ("error" in result.results[0] && result.results[0].error) {
const errorData = {
code: result.results[0].error_code,
name: result.results[0].error_name,
error: result.results[0].error,
} as ErrorData;

const error = {
status: "CUSTOM_ERROR",
data: errorData,
} as FetchBaseQueryError;

// Handle error
handleAPIError(error);
} else {
// Update data from Redux
props.selectedUsersData.selectedUsers.map((user) => {
dispatch(removeStageUser(user[0]));
});

// Reset selected values
props.selectedUsersData.updateSelectedUsers([]);

// Refresh data
if (props.onRefresh !== undefined) {
props.onRefresh();
}

// Show alert: success
alerts.addAlert(
"activate-users-success",
"Users activated",
"success"
);

closeModal();
}
} else if (error) {
// Handle error
handleAPIError(error);
}
}
});
};

// Set the Modal and Action buttons for 'Stage' option
const modalStageActions: JSX.Element[] = [
<Button
key="stage-users"
variant="primary"
onClick={activateUsers}
form="stage-users-modal"
>
Activate
</Button>,
<Button key="cancel-stage-user" variant="link" onClick={closeModal}>
Cancel
</Button>,
];

const modalActivate: JSX.Element = (
<ModalWithFormLayout
variantType="medium"
modalPosition="top"
offPosition="76px"
title="Activate Stage User"
formId="stage-user-activate-modal"
fields={fields}
show={props.show}
onClose={closeModal}
actions={modalStageActions}
/>
);

// Render 'ActivateStageUsers'
return (
<>
<alerts.ManagedAlerts />
{modalActivate}
{isModalErrorOpen && (
<ErrorModal
title={errorTitle}
isOpen={isModalErrorOpen}
onClose={onCloseErrorModal}
actions={errorModalActions}
errorMessage={errorMessage}
/>
)}
</>
);
};

export default ActivateStageUsers;
Loading