Adds functionality to delete realm role (#160)
* added function to refresh the dataloader data * add Realm Roles page add section for no realm roles update role-model, fixed UI to match designs fix paths add storybook demo and role actions kebab fix build and clean up fix formatting fix lint fix test and update snapshot update snapshot after rebase PR feedback from Stan add back pf addons localize NoRealmRoles component changes to PR suggested by Sarah adds new role form rename fields cleaning up address PR feedback from Stan add logic to delete realm role format rebase with erik branch resolves merge conflicts fix check types error fix invalid state fix check-types fix ts error * fix format * remove log stmt * address PR feedback from Erik and Sarah * remove utility class * address Erik comment * fix formatting * fix bug and address PR feedback * move msg * fix lint * fix format * delete details file (on another branch) Co-authored-by: Erik Jan de Wit <erikjan.dewit@gmail.com>
This commit is contained in:
parent
35584207c8
commit
caec08f95f
8 changed files with 137 additions and 61 deletions
|
@ -69,7 +69,7 @@ export const PageNav: React.FunctionComponent = () => {
|
||||||
<NavGroup title={t("manage")}>
|
<NavGroup title={t("manage")}>
|
||||||
{makeNavItem("clients", "clients")}
|
{makeNavItem("clients", "clients")}
|
||||||
{makeNavItem("clientScopes", "client-scopes")}
|
{makeNavItem("clientScopes", "client-scopes")}
|
||||||
{makeNavItem("realmRoles", "realm-roles")}
|
{makeNavItem("realmRoles", "roles")}
|
||||||
{makeNavItem("users", "users")}
|
{makeNavItem("users", "users")}
|
||||||
{makeNavItem("groups", "groups")}
|
{makeNavItem("groups", "groups")}
|
||||||
{makeNavItem("sessions", "sessions")}
|
{makeNavItem("sessions", "sessions")}
|
||||||
|
|
|
@ -1,46 +1,69 @@
|
||||||
import React, { useContext } from "react";
|
import React, { useContext, useEffect, useState } from "react";
|
||||||
import { useHistory } from "react-router-dom";
|
import { useHistory } from "react-router-dom";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { Button, PageSection } from "@patternfly/react-core";
|
import { Bullseye, Button, PageSection, Spinner } from "@patternfly/react-core";
|
||||||
|
|
||||||
import { DataLoader } from "../components/data-loader/DataLoader";
|
|
||||||
import { TableToolbar } from "../components/table-toolbar/TableToolbar";
|
|
||||||
import { HttpClientContext } from "../context/http-service/HttpClientContext";
|
import { HttpClientContext } from "../context/http-service/HttpClientContext";
|
||||||
import { RoleRepresentation } from "../model/role-model";
|
import { RoleRepresentation } from "../model/role-model";
|
||||||
import { RolesList } from "./RoleList";
|
import { RolesList } from "./RoleList";
|
||||||
import { RealmContext } from "../context/realm-context/RealmContext";
|
import { RealmContext } from "../context/realm-context/RealmContext";
|
||||||
import { ViewHeader } from "../components/view-header/ViewHeader";
|
import { ViewHeader } from "../components/view-header/ViewHeader";
|
||||||
|
import { PaginatingTableToolbar } from "../components/table-toolbar/PaginatingTableToolbar";
|
||||||
|
|
||||||
export const RealmRolesSection = () => {
|
export const RealmRolesSection = () => {
|
||||||
|
const [max, setMax] = useState(10);
|
||||||
|
const [first, setFirst] = useState(0);
|
||||||
const { t } = useTranslation("roles");
|
const { t } = useTranslation("roles");
|
||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
const httpClient = useContext(HttpClientContext)!;
|
const httpClient = useContext(HttpClientContext)!;
|
||||||
|
const [roles, setRoles] = useState<RoleRepresentation[]>();
|
||||||
const { realm } = useContext(RealmContext);
|
const { realm } = useContext(RealmContext);
|
||||||
|
|
||||||
const loader = async () => {
|
const loader = async () => {
|
||||||
|
const params: { [name: string]: string | number } = { first, max };
|
||||||
|
|
||||||
const result = await httpClient.doGet<RoleRepresentation[]>(
|
const result = await httpClient.doGet<RoleRepresentation[]>(
|
||||||
`/admin/realms/${realm}/roles`
|
`/admin/realms/${realm}/roles`,
|
||||||
|
{ params: params }
|
||||||
);
|
);
|
||||||
return result.data;
|
setRoles(result.data);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
loader();
|
||||||
|
}, [first, max]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<ViewHeader titleKey="roles:title" subKey="roles:roleExplain" />
|
<ViewHeader titleKey="roles:title" subKey="roles:roleExplain" />
|
||||||
<PageSection padding={{ default: "noPadding" }}>
|
<PageSection variant="light">
|
||||||
<TableToolbar
|
{!roles && (
|
||||||
toolbarItem={
|
<Bullseye>
|
||||||
<>
|
<Spinner />
|
||||||
<Button onClick={() => history.push("/add-role")}>
|
</Bullseye>
|
||||||
{t("createRole")}
|
)}
|
||||||
</Button>
|
{roles && (
|
||||||
</>
|
<PaginatingTableToolbar
|
||||||
}
|
count={roles!.length}
|
||||||
>
|
first={first}
|
||||||
<DataLoader loader={loader}>
|
max={max}
|
||||||
{(roles) => <RolesList roles={roles.data} />}
|
onNextClick={setFirst}
|
||||||
</DataLoader>
|
onPreviousClick={setFirst}
|
||||||
</TableToolbar>
|
onPerPageSelect={(first, max) => {
|
||||||
|
setFirst(first);
|
||||||
|
setMax(max);
|
||||||
|
}}
|
||||||
|
toolbarItem={
|
||||||
|
<>
|
||||||
|
<Button onClick={() => history.push("/add-role")}>
|
||||||
|
{t("createRole")}
|
||||||
|
</Button>
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<RolesList roles={roles} refresh={loader} />
|
||||||
|
</PaginatingTableToolbar>
|
||||||
|
)}
|
||||||
</PageSection>
|
</PageSection>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import React from "react";
|
import React, { useContext, useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
@ -12,9 +12,15 @@ import {
|
||||||
|
|
||||||
import { ExternalLink } from "../components/external-link/ExternalLink";
|
import { ExternalLink } from "../components/external-link/ExternalLink";
|
||||||
import { RoleRepresentation } from "../model/role-model";
|
import { RoleRepresentation } from "../model/role-model";
|
||||||
|
import { AlertVariant, ButtonVariant } from "@patternfly/react-core";
|
||||||
|
import { HttpClientContext } from "../context/http-service/HttpClientContext";
|
||||||
|
import { useAlerts } from "../components/alert/Alerts";
|
||||||
|
import { RealmContext } from "../context/realm-context/RealmContext";
|
||||||
|
import { useConfirmDialog } from "../components/confirm-dialog/ConfirmDialog";
|
||||||
|
|
||||||
type RolesListProps = {
|
type RolesListProps = {
|
||||||
roles?: RoleRepresentation[];
|
roles?: RoleRepresentation[];
|
||||||
|
refresh: () => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
const columns: (keyof RoleRepresentation)[] = [
|
const columns: (keyof RoleRepresentation)[] = [
|
||||||
|
@ -23,8 +29,12 @@ const columns: (keyof RoleRepresentation)[] = [
|
||||||
"description",
|
"description",
|
||||||
];
|
];
|
||||||
|
|
||||||
export const RolesList = ({ roles }: RolesListProps) => {
|
export const RolesList = ({ roles, refresh }: RolesListProps) => {
|
||||||
const { t } = useTranslation("roles");
|
const { t } = useTranslation("roles");
|
||||||
|
const httpClient = useContext(HttpClientContext)!;
|
||||||
|
const { realm } = useContext(RealmContext);
|
||||||
|
const { addAlert } = useAlerts();
|
||||||
|
const [selectedRowId, setSelectedRowId] = useState(-1);
|
||||||
|
|
||||||
const emptyFormatter = (): IFormatter => (data?: IFormatterValueType) => {
|
const emptyFormatter = (): IFormatter => (data?: IFormatterValueType) => {
|
||||||
return data ? data : "—";
|
return data ? data : "—";
|
||||||
|
@ -43,37 +53,66 @@ export const RolesList = ({ roles }: RolesListProps) => {
|
||||||
? boolVal.charAt(0).toUpperCase() + boolVal.slice(1)
|
? boolVal.charAt(0).toUpperCase() + boolVal.slice(1)
|
||||||
: undefined) as string;
|
: undefined) as string;
|
||||||
};
|
};
|
||||||
|
const data = roles!.map((column) => {
|
||||||
const data = roles!.map((c) => {
|
return { cells: columns.map((col) => column[col]), role: column };
|
||||||
return { cells: columns.map((col) => c[col]) };
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
let selectedRoleName;
|
||||||
|
if (selectedRowId === data.length) {
|
||||||
|
selectedRoleName = data[selectedRowId - 1].role.name;
|
||||||
|
} else if (selectedRowId != -1) {
|
||||||
|
selectedRoleName = data[selectedRowId].role.name;
|
||||||
|
}
|
||||||
|
|
||||||
|
const [toggleDeleteDialog, DeleteConfirm] = useConfirmDialog({
|
||||||
|
titleKey: "roles:roleDeleteConfirm",
|
||||||
|
messageKey: t("roles:roleDeleteConfirmDialog", { selectedRoleName }),
|
||||||
|
continueButtonLabel: "common:delete",
|
||||||
|
continueButtonVariant: ButtonVariant.danger,
|
||||||
|
onConfirm: async () => {
|
||||||
|
try {
|
||||||
|
await httpClient.doDelete(
|
||||||
|
`/admin/realms/${realm}/roles/${data[selectedRowId].role.name}`
|
||||||
|
);
|
||||||
|
refresh();
|
||||||
|
addAlert(t("roleDeletedSuccess"), AlertVariant.success);
|
||||||
|
} catch (error) {
|
||||||
|
addAlert(`${t("roleDeleteError")} ${error}`, AlertVariant.danger);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Table
|
<>
|
||||||
variant={TableVariant.compact}
|
<DeleteConfirm />
|
||||||
cells={[
|
<Table
|
||||||
{
|
variant={TableVariant.compact}
|
||||||
title: t("roleName"),
|
cells={[
|
||||||
cellFormatters: [externalLink(), emptyFormatter()],
|
{
|
||||||
},
|
title: t("roleName"),
|
||||||
{
|
cellFormatters: [externalLink(), emptyFormatter()],
|
||||||
title: t("composite"),
|
},
|
||||||
cellFormatters: [boolFormatter(), emptyFormatter()],
|
{
|
||||||
},
|
title: t("composite"),
|
||||||
{ title: t("description"), cellFormatters: [emptyFormatter()] },
|
cellFormatters: [boolFormatter(), emptyFormatter()],
|
||||||
]}
|
},
|
||||||
rows={data}
|
{ title: t("description"), cellFormatters: [emptyFormatter()] },
|
||||||
actions={[
|
]}
|
||||||
{
|
rows={data}
|
||||||
title: t("common:Export"),
|
actions={[
|
||||||
},
|
{
|
||||||
{
|
title: t("common:Delete"),
|
||||||
title: t("common:Delete"),
|
onClick: (_, rowId) => {
|
||||||
},
|
setSelectedRowId(rowId);
|
||||||
]}
|
toggleDeleteDialog();
|
||||||
aria-label="Roles list"
|
},
|
||||||
>
|
},
|
||||||
<TableHeader />
|
]}
|
||||||
<TableBody />
|
aria-label="Roles list"
|
||||||
</Table>
|
>
|
||||||
|
<TableHeader />
|
||||||
|
<TableBody />
|
||||||
|
</Table>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -18,7 +18,7 @@ import {
|
||||||
import { RoleRepresentation } from "../../model/role-model";
|
import { RoleRepresentation } from "../../model/role-model";
|
||||||
import { HttpClientContext } from "../../context/http-service/HttpClientContext";
|
import { HttpClientContext } from "../../context/http-service/HttpClientContext";
|
||||||
import { useAlerts } from "../../components/alert/Alerts";
|
import { useAlerts } from "../../components/alert/Alerts";
|
||||||
import { Controller, useForm } from "react-hook-form";
|
import { Controller, useForm, FieldErrors } from "react-hook-form";
|
||||||
import { RealmContext } from "../../context/realm-context/RealmContext";
|
import { RealmContext } from "../../context/realm-context/RealmContext";
|
||||||
|
|
||||||
export const NewRoleForm = () => {
|
export const NewRoleForm = () => {
|
||||||
|
@ -40,6 +40,8 @@ export const NewRoleForm = () => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
console.log(errors);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<PageSection variant="light">
|
<PageSection variant="light">
|
||||||
|
@ -56,16 +58,18 @@ export const NewRoleForm = () => {
|
||||||
type="text"
|
type="text"
|
||||||
id="kc-role-name"
|
id="kc-role-name"
|
||||||
name="name"
|
name="name"
|
||||||
ref={register()}
|
ref={register({ required: true })}
|
||||||
/>
|
/>
|
||||||
</FormGroup>
|
</FormGroup>
|
||||||
<FormGroup
|
<FormGroup
|
||||||
label={t("description")}
|
label={t("description")}
|
||||||
fieldId="kc-role-description"
|
fieldId="kc-role-description"
|
||||||
helperTextInvalid="Max length 255"
|
|
||||||
validated={
|
validated={
|
||||||
errors ? ValidatedOptions.error : ValidatedOptions.default
|
Object.keys(errors).length != 0
|
||||||
|
? ValidatedOptions.error
|
||||||
|
: ValidatedOptions.default
|
||||||
}
|
}
|
||||||
|
helperTextInvalid={"Max length 255"}
|
||||||
>
|
>
|
||||||
<Controller
|
<Controller
|
||||||
name="description"
|
name="description"
|
||||||
|
@ -75,6 +79,12 @@ export const NewRoleForm = () => {
|
||||||
render={({ onChange, value }) => (
|
render={({ onChange, value }) => (
|
||||||
<TextArea
|
<TextArea
|
||||||
type="text"
|
type="text"
|
||||||
|
validated={
|
||||||
|
errors.description &&
|
||||||
|
errors.description.type === "maxLength"
|
||||||
|
? "error"
|
||||||
|
: "default"
|
||||||
|
}
|
||||||
id="kc-role-description"
|
id="kc-role-description"
|
||||||
value={value}
|
value={value}
|
||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
|
|
|
@ -17,7 +17,9 @@
|
||||||
"roleCreated": "Role created",
|
"roleCreated": "Role created",
|
||||||
"roleCreateError": "Could not create role:",
|
"roleCreateError": "Could not create role:",
|
||||||
"roleImportSuccess": "Role import successful",
|
"roleImportSuccess": "Role import successful",
|
||||||
"roleDeletedSucess": "The role has been deleted",
|
"roleDeleteConfirm": "Delete role?",
|
||||||
|
"roleDeleteConfirmDialog": "This action will permanently delete the role {{selectedRoleName}} and cannot be undone.",
|
||||||
|
"roleDeletedSuccess": "The role has been deleted",
|
||||||
"roleDeleteError": "Could not delete role:",
|
"roleDeleteError": "Could not delete role:",
|
||||||
"roleAuthentication": "Role authentication"
|
"roleAuthentication": "Role authentication"
|
||||||
}
|
}
|
||||||
|
|
|
@ -60,7 +60,7 @@ export const NewRealmForm = () => {
|
||||||
type="text"
|
type="text"
|
||||||
id="kc-realm-name"
|
id="kc-realm-name"
|
||||||
name="realm"
|
name="realm"
|
||||||
ref={register()}
|
ref={register({ required: true })}
|
||||||
/>
|
/>
|
||||||
</FormGroup>
|
</FormGroup>
|
||||||
<FormGroup label={t("enabled")} fieldId="kc-realm-enabled-switch">
|
<FormGroup label={t("enabled")} fieldId="kc-realm-enabled-switch">
|
||||||
|
|
|
@ -55,7 +55,7 @@ export const routes = (t: TFunction) => [
|
||||||
breadcrumb: t("client-scopes:createClientScope"),
|
breadcrumb: t("client-scopes:createClientScope"),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "/realm-roles",
|
path: "/roles",
|
||||||
component: RealmRolesSection,
|
component: RealmRolesSection,
|
||||||
breadcrumb: t("roles:roleList"),
|
breadcrumb: t("roles:roleList"),
|
||||||
},
|
},
|
||||||
|
|
|
@ -8,4 +8,6 @@ export default {
|
||||||
component: RolesList,
|
component: RolesList,
|
||||||
} as Meta;
|
} as Meta;
|
||||||
|
|
||||||
export const RolesListExample = () => <RolesList roles={rolesMock} />;
|
export const RolesListExample = () => (
|
||||||
|
<RolesList roles={rolesMock} refresh={() => {}} />
|
||||||
|
);
|
||||||
|
|
Loading…
Reference in a new issue