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

[Active users][Settings][Kebab] Add 'OTP token' option #219

Merged
merged 2 commits into from
Jan 17, 2024
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
9 changes: 9 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
"@patternfly/react-core": "^5.1.2",
"@patternfly/react-table": "^5.1.2",
"@reduxjs/toolkit": "^1.8.6",
"qrcode.react": "^3.1.0",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"react-redux": "^8.0.4",
Expand Down
94 changes: 94 additions & 0 deletions src/components/Form/DateTimeSelector.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
import React from "react";
pvoborni marked this conversation as resolved.
Show resolved Hide resolved
// PatternFly
import {
DatePicker,
InputGroup,
TimePicker,
isValidDate,
yyyyMMddFormat,
} from "@patternfly/react-core";
// Utils
import {
parseFullDateStringToUTCFormat,
toGeneralizedTime,
} from "src/utils/utils";

interface PropsToDateTimeSelector {
datetime: Date | null;
onChange?: (timeValue: Date) => void;
name: string;
ariaLabel?: string;
isDisabled?: boolean;
}

const hhMMFormat = (date: Date) => {
const hours = date.getHours().toString().padStart(2, "0");
const minutes = date.getMinutes().toString().padStart(2, "0");

return hours + ":" + minutes;
};

function cloneDate(date: Date): Date {
return parseFullDateStringToUTCFormat(toGeneralizedTime(date)) as Date;
}

const DateTimeSelector = (props: PropsToDateTimeSelector) => {
// On change date handler
const onDateChange = (
_event: React.FormEvent<HTMLInputElement>,
inputDate: string,
newFromDate: Date | undefined
) => {
let updatedFromDate = new Date();
if (props.datetime && isValidDate(props.datetime)) {
updatedFromDate = cloneDate(props.datetime);
}

if (!newFromDate || !isValidDate(newFromDate)) return;

updatedFromDate.setFullYear(newFromDate.getFullYear());
updatedFromDate.setMonth(newFromDate.getMonth());
updatedFromDate.setDate(newFromDate.getDate());

if (props.onChange) {
props.onChange(updatedFromDate);
}
};

// On change time handler
const onTimeChange = (_event, time, hour, minute) => {
let updatedFromDate = new Date();
if (props.datetime && isValidDate(props.datetime)) {
updatedFromDate = cloneDate(props.datetime);
}
updatedFromDate.setHours(hour);
updatedFromDate.setMinutes(minute);
if (props.onChange) {
props.onChange(updatedFromDate);
}
};

return (
<InputGroup>
<DatePicker
name={props.name}
value={props.datetime ? yyyyMMddFormat(props.datetime) : ""}
onChange={onDateChange}
aria-label={props.ariaLabel || props.name}
placeholder="YYYY-MM-DD"
isDisabled={props.isDisabled || false}
/>
<TimePicker
name={props.name}
time={props.datetime ? hhMMFormat(props.datetime) : ""}
aria-label={props.ariaLabel || props.name}
onChange={onTimeChange}
placeholder="HH:MM"
is24Hour={true}
isDisabled={props.isDisabled || false}
/>
</InputGroup>
);
};

export default DateTimeSelector;
123 changes: 16 additions & 107 deletions src/components/Form/IpaCalendar.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,4 @@
import React from "react";
// PatternFly
import {
DatePicker,
InputGroup,
TimePicker,
isValidDate,
InputGroupItem,
} from "@patternfly/react-core";
// Utils
import {
parseFullDateStringToUTCFormat,
Expand All @@ -20,6 +12,8 @@ import {
updateIpaObject,
} from "src/utils/ipaObjectUtils";
import { ParamMetadata } from "src/utils/datatypes/globalDataTypes";
// Components
import DateTimeSelector from "./DateTimeSelector";

export interface DateParam {
__datetime__: string;
Expand Down Expand Up @@ -62,113 +56,28 @@ function getParamPropertiesDateTime(
};
}

function cloneDate(date: Date): Date {
return parseFullDateStringToUTCFormat(toGeneralizedTime(date)) as Date;
}

const IpaCalendar = (props: IPAParamDefinition) => {
const { readOnly, value } = getParamPropertiesDateTime(props);

// On change date handler
const onDateChange = (
_event: React.FormEvent<HTMLInputElement>,
inputDate: string,
newFromDate: Date | undefined
) => {
if (newFromDate !== undefined) {
if (
isValidDate(newFromDate) &&
inputDate === yyyyMMddFormat(newFromDate)
) {
if (value) {
newFromDate.setHours(value.getHours());
newFromDate.setMinutes(value.getMinutes());
}
// Update 'ipaObject' with the new date
// - Parse to generalized format (to return to the "user_mod" API call)
if (props.ipaObject !== undefined && props.onChange !== undefined) {
const LDAPDate: string = toGeneralizedTime(newFromDate);
updateIpaObject(
props.ipaObject,
props.onChange,
LDAPDate,
props.name
);
}
}
}
};

// On change time handler
const onTimeChange = (_event, time, hour, minute) => {
// Assume inital data is null
// If the date is empty, create a new one with the current time
let updatedFromDate: Date = new Date();
if (value && isValidDate(value)) {
updatedFromDate = cloneDate(value);
}
updatedFromDate.setHours(hour);
updatedFromDate.setMinutes(minute);

// Update 'ipaObject' with the new date
// - Parse to generalized format (to return to the "user_mod" API call)
const onDateChange = (date: Date) => {
if (props.ipaObject !== undefined && props.onChange !== undefined) {
const LDAPDate = toGeneralizedTime(updatedFromDate);
updateIpaObject(props.ipaObject, props.onChange, LDAPDate, props.name);
updateIpaObject(
props.ipaObject,
props.onChange,
toGeneralizedTime(date),
props.name
);
}
};

// Parse the current date into 'YYYY-MM-DD' format
const yyyyMMddFormat = (date: Date | null | undefined): string => {
if (date === undefined || date === null) return "";

// This convertion is needed to prevent any Date data type issues
const dt = new Date(date);
const year = dt.getFullYear();
const month = dt.getMonth() + 1;
const day = dt.getDate().toString().padStart(2, "0");

const res = year.toString() + "-" + month.toString() + "-" + day;
return res;
};

// Parse the current date into 'HH:MM' format
const hhMMFormat = (date: Date | null | undefined): string => {
if (date === undefined || date === null) return "";

// This convertion is needed to prevent any Date data type issues
const dt = new Date(date);
const hours = dt.getHours().toString().padStart(2, "0");
const minutes = dt.getMinutes().toString().padStart(2, "0");

const res = hours + ":" + minutes;
return res;
};

return (
<InputGroup>
<InputGroupItem>
<DatePicker
name={"add-date-" + props.name}
value={yyyyMMddFormat(value)}
onChange={onDateChange}
aria-label="Kerberos principal expiration date"
placeholder="YYYY-MM-DD"
isDisabled={readOnly}
/>
</InputGroupItem>
<InputGroupItem>
<TimePicker
name={"add-time-" + props.name}
time={hhMMFormat(value)}
aria-label="Kerberos principal expiration time"
onChange={onTimeChange}
placeholder="HH:MM"
is24Hour={true}
isDisabled={readOnly}
/>
</InputGroupItem>
</InputGroup>
<DateTimeSelector
datetime={value}
onChange={onDateChange}
name={props.name}
ariaLabel="Kerberos principal expiration date"
isDisabled={readOnly}
/>
);
};

Expand Down
22 changes: 21 additions & 1 deletion src/components/UserSettings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ import RebuildAutoMembership from "./modals/RebuildAutoMembership";
import UnlockUser from "./modals/UnlockUser";
import ResetPassword from "./modals/ResetPassword";
import IssueNewCertificate from "./modals/IssueNewCertificate";
import AddOtpToken from "./modals/AddOtpToken";

export interface PropsToUserSettings {
originalUser: Partial<User>;
Expand Down Expand Up @@ -173,6 +174,12 @@ const UserSettings = (props: PropsToUserSettings) => {
setIsNewCertificateModalOpen(false);
};

// 'Add OTP token' option
const [isAddOtpTokenModalOpen, setIsAddOtpTokenModalOpen] = useState(false);
const onCloseAddOtpTokenModal = () => {
setIsAddOtpTokenModalOpen(false);
};

// Kebab
const [isKebabOpen, setIsKebabOpen] = useState(false);

Expand Down Expand Up @@ -207,7 +214,12 @@ const UserSettings = (props: PropsToUserSettings) => {
>
Unlock
</DropdownItem>,
<DropdownItem key="add otp token">Add OTP token</DropdownItem>,
<DropdownItem
key="add otp token"
onClick={() => setIsAddOtpTokenModalOpen(true)}
>
Add OTP token
</DropdownItem>,
<DropdownItem
key="rebuild auto membership"
onClick={() => setIsRebuildAutoMembershipModalOpen(true)}
Expand Down Expand Up @@ -523,6 +535,14 @@ const UserSettings = (props: PropsToUserSettings) => {
showPrincipalFields={false}
onRefresh={props.onRefresh}
/>
{props.user.uid !== undefined && (
<AddOtpToken
uid={props.user.uid}
isOpen={isAddOtpTokenModalOpen}
setIsOpen={setIsAddOtpTokenModalOpen}
onClose={onCloseAddOtpTokenModal}
/>
)}
</>
);
};
Expand Down
51 changes: 51 additions & 0 deletions src/components/layouts/HelperTextWithIcon.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import React from "react";
// PatternFly
import { HelperText, HelperTextItem } from "@patternfly/react-core";
// Icons
import {
InfoIcon,
QuestionIcon,
ExclamationIcon,
CheckIcon,
TimesIcon,
} from "@patternfly/react-icons";

type IconType = "info" | "question" | "warning" | "success" | "error";

interface PropsToHelperTextWithIcon {
message: string | React.ReactNode;
type?: IconType;
}

const getIcon = (type?: IconType) => {
switch (type) {
case "info":
return <InfoIcon />;
case "question":
return <QuestionIcon />;
case "warning":
return <ExclamationIcon />;
case "success":
return <CheckIcon />;
case "error":
return <TimesIcon />;
default:
return <InfoIcon />;
}
};

const HelperTextWithIcon = (props: PropsToHelperTextWithIcon) => {
return (
<HelperText>
{!props.type ? (
<HelperTextItem>{props.message}</HelperTextItem>
) : (
<HelperTextItem icon={getIcon(props.type)}>
{props.message}
</HelperTextItem>
)}
</HelperText>
);
};

export default HelperTextWithIcon;
Loading
Loading