Merge pull request #406 from edewit/issue-404

removed bookmarable sub tab
This commit is contained in:
mfrances17 2021-03-03 09:28:50 -05:00 committed by GitHub
commit 1a9131db7f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 80 additions and 76 deletions

View file

@ -21,7 +21,7 @@ export const ClientScopesSection = () => {
const ClientScopeDetailLink = (clientScope: ClientScopeRepresentation) => ( const ClientScopeDetailLink = (clientScope: ClientScopeRepresentation) => (
<> <>
<Link key={clientScope.id} to={`${url}/${clientScope.id}`}> <Link key={clientScope.id} to={`${url}/${clientScope.id}/settings`}>
{clientScope.name} {clientScope.name}
</Link> </Link>
</> </>

View file

@ -42,7 +42,7 @@ export const RoleMappingForm = () => {
const { t } = useTranslation("client-scopes"); const { t } = useTranslation("client-scopes");
const { register, handleSubmit, control, errors } = useForm(); const { register, handleSubmit, control, errors } = useForm();
const { clientScopeId } = useParams<{ clientScopeId: string }>(); const { id } = useParams<{ id: string }>();
const [roleOpen, setRoleOpen] = useState(false); const [roleOpen, setRoleOpen] = useState(false);
@ -101,10 +101,7 @@ export const RoleMappingForm = () => {
const save = async (mapping: ProtocolMapperRepresentation) => { const save = async (mapping: ProtocolMapperRepresentation) => {
try { try {
await adminClient.clientScopes.addProtocolMapper( await adminClient.clientScopes.addProtocolMapper({ id }, mapping);
{ id: clientScopeId },
mapping
);
addAlert(t("mapperCreateSuccess")); addAlert(t("mapperCreateSuccess"));
} catch (error) { } catch (error) {
addAlert(t("mapperCreateError", error)); addAlert(t("mapperCreateError", error));
@ -227,7 +224,7 @@ export const RoleMappingForm = () => {
placeholderText={t("selectASourceOfRoles")} placeholderText={t("selectASourceOfRoles")}
isGrouped isGrouped
onFilter={(evt) => { onFilter={(evt) => {
const textInput = evt.target.value; const textInput = evt?.target.value || "";
if (textInput === "") { if (textInput === "") {
return createSelectGroup(clients); return createSelectGroup(clients);
} else { } else {

View file

@ -59,7 +59,7 @@ export const MapperList = ({ clientScope, refresh }: MapperListProps) => {
const [filter, setFilter] = useState(clientScope.protocolMappers); const [filter, setFilter] = useState(clientScope.protocolMappers);
const toggleAddMapperDialog = (buildIn: boolean) => { const toggleAddMapperDialog = (buildIn: boolean) => {
if (buildIn) { if (buildIn) {
setFilter(mapperList); setFilter(mapperList || []);
} else { } else {
setFilter(undefined); setFilter(undefined);
} }
@ -123,9 +123,7 @@ export const MapperList = ({ clientScope, refresh }: MapperListProps) => {
cells: { cells: {
name: ( name: (
<> <>
<Link to={`${url}/${clientScope.id}/${mapper.id}`}> <Link to={`${url}/${mapper.id}`}>{mapper.name}</Link>
{mapper.name}
</Link>
</> </>
), ),
category: mapperType.category, category: mapperType.category,

View file

@ -37,8 +37,8 @@ import { convertFormValuesToObject, convertToFormValues } from "../../util";
import { FormAccess } from "../../components/form-access/FormAccess"; import { FormAccess } from "../../components/form-access/FormAccess";
type Params = { type Params = {
scopeId: string;
id: string; id: string;
mapperId: string;
}; };
export const MappingDetails = () => { export const MappingDetails = () => {
@ -47,7 +47,7 @@ export const MappingDetails = () => {
const handleError = useErrorHandler(); const handleError = useErrorHandler();
const { addAlert } = useAlerts(); const { addAlert } = useAlerts();
const { scopeId, id } = useParams<Params>(); const { id, mapperId } = useParams<Params>();
const { register, errors, setValue, control, handleSubmit } = useForm(); const { register, errors, setValue, control, handleSubmit } = useForm();
const [mapping, setMapping] = useState<ProtocolMapperRepresentation>(); const [mapping, setMapping] = useState<ProtocolMapperRepresentation>();
const [typeOpen, setTypeOpen] = useState(false); const [typeOpen, setTypeOpen] = useState(false);
@ -63,10 +63,10 @@ export const MappingDetails = () => {
useEffect(() => { useEffect(() => {
return asyncStateFetch( return asyncStateFetch(
async () => { async () => {
if (id.match(isGuid)) { if (mapperId.match(isGuid)) {
const data = await adminClient.clientScopes.findProtocolMapper({ const data = await adminClient.clientScopes.findProtocolMapper({
id: scopeId, id,
mapperId: id, mapperId,
}); });
if (data) { if (data) {
Object.entries(data).map((entry) => { Object.entries(data).map((entry) => {
@ -83,9 +83,19 @@ export const MappingDetails = () => {
mapping: data, mapping: data,
}; };
} else { } else {
const scope = await adminClient.clientScopes.findOne({ id: scopeId }); const scope = await adminClient.clientScopes.findOne({ id });
const protocolMappers = serverInfo.protocolMapperTypes![
scope.protocol!
];
const mapping = protocolMappers.find(
(mapper) => mapper.id === mapperId
)!;
return { return {
mapping: { protocol: scope.protocol, protocolMapper: id }, mapping: {
name: mapping.name,
protocol: scope.protocol,
protocolMapper: mapperId,
},
}; };
} }
}, },
@ -105,11 +115,11 @@ export const MappingDetails = () => {
onConfirm: async () => { onConfirm: async () => {
try { try {
await adminClient.clientScopes.delClientScopeMappings( await adminClient.clientScopes.delClientScopeMappings(
{ client: scopeId, id }, { client: id, id: mapperId },
[] []
); );
addAlert(t("mappingDeletedSuccess"), AlertVariant.success); addAlert(t("mappingDeletedSuccess"), AlertVariant.success);
history.push(`${url}/${scopeId}`); history.push(`${url}/${id}`);
} catch (error) { } catch (error) {
addAlert(t("mappingDeletedError", { error }), AlertVariant.danger); addAlert(t("mappingDeletedError", { error }), AlertVariant.danger);
} }
@ -119,15 +129,15 @@ export const MappingDetails = () => {
const save = async (formMapping: ProtocolMapperRepresentation) => { const save = async (formMapping: ProtocolMapperRepresentation) => {
const config = convertFormValuesToObject(formMapping.config); const config = convertFormValuesToObject(formMapping.config);
const map = { ...mapping, ...formMapping, config }; const map = { ...mapping, ...formMapping, config };
const key = id.match(isGuid) ? "Updated" : "Created"; const key = mapperId.match(isGuid) ? "Updated" : "Created";
try { try {
if (id.match(isGuid)) { if (mapperId.match(isGuid)) {
await adminClient.clientScopes.updateProtocolMapper( await adminClient.clientScopes.updateProtocolMapper(
{ id: scopeId, mapperId: id }, { id, mapperId },
map map
); );
} else { } else {
await adminClient.clientScopes.addProtocolMapper({ id: scopeId }, map); await adminClient.clientScopes.addProtocolMapper({ id }, map);
} }
addAlert(t(`mapping${key}Success`), AlertVariant.success); addAlert(t(`mapping${key}Success`), AlertVariant.success);
} catch (error) { } catch (error) {
@ -140,10 +150,10 @@ export const MappingDetails = () => {
<DeleteConfirm /> <DeleteConfirm />
<ViewHeader <ViewHeader
titleKey={mapping ? mapping.name! : t("addMapper")} titleKey={mapping ? mapping.name! : t("addMapper")}
subKey={id.match(isGuid) ? id : ""} subKey={mapperId.match(isGuid) ? mapperId : ""}
badge={mapping?.protocol} badge={mapping?.protocol}
dropdownItems={ dropdownItems={
id.match(isGuid) mapperId.match(isGuid)
? [ ? [
<DropdownItem <DropdownItem
key="delete" key="delete"
@ -163,7 +173,7 @@ export const MappingDetails = () => {
role="manage-clients" role="manage-clients"
> >
<> <>
{!id.match(isGuid) && ( {!mapperId.match(isGuid) && (
<FormGroup <FormGroup
label={t("common:name")} label={t("common:name")}
labelIcon={ labelIcon={

View file

@ -6,6 +6,7 @@ import {
PageSection, PageSection,
Spinner, Spinner,
Tab, Tab,
Tabs,
TabTitleText, TabTitleText,
} from "@patternfly/react-core"; } from "@patternfly/react-core";
import { useParams } from "react-router-dom"; import { useParams } from "react-router-dom";
@ -113,6 +114,7 @@ export const ClientDetails = () => {
const { addAlert } = useAlerts(); const { addAlert } = useAlerts();
const [downloadDialogOpen, setDownloadDialogOpen] = useState(false); const [downloadDialogOpen, setDownloadDialogOpen] = useState(false);
const toggleDownloadDialog = () => setDownloadDialogOpen(!downloadDialogOpen); const toggleDownloadDialog = () => setDownloadDialogOpen(!downloadDialogOpen);
const [activeTab2, setActiveTab2] = useState(30);
const form = useForm<ClientForm>(); const form = useForm<ClientForm>();
const publicClient = useWatch({ const publicClient = useWatch({
@ -121,12 +123,12 @@ export const ClientDetails = () => {
defaultValue: false, defaultValue: false,
}); });
const { id } = useParams<{ id: string }>(); const { clientId } = useParams<{ clientId: string }>();
const [client, setClient] = useState<ClientRepresentation>(); const [client, setClient] = useState<ClientRepresentation>();
const loader = async () => { const loader = async () => {
const roles = await adminClient.clients.listRoles({ id }); const roles = await adminClient.clients.listRoles({ id: clientId });
return _.sortBy(roles, (role) => role.name?.toUpperCase()); return _.sortBy(roles, (role) => role.name?.toUpperCase());
}; };
@ -137,7 +139,7 @@ export const ClientDetails = () => {
continueButtonVariant: ButtonVariant.danger, continueButtonVariant: ButtonVariant.danger,
onConfirm: async () => { onConfirm: async () => {
try { try {
await adminClient.clients.del({ id }); await adminClient.clients.del({ id: clientId });
addAlert(t("clientDeletedSuccess"), AlertVariant.success); addAlert(t("clientDeletedSuccess"), AlertVariant.success);
} catch (error) { } catch (error) {
addAlert(`${t("clientDeleteError")} ${error}`, AlertVariant.danger); addAlert(`${t("clientDeleteError")} ${error}`, AlertVariant.danger);
@ -162,14 +164,14 @@ export const ClientDetails = () => {
useEffect(() => { useEffect(() => {
return asyncStateFetch( return asyncStateFetch(
() => adminClient.clients.findOne({ id }), () => adminClient.clients.findOne({ id: clientId }),
(fetchedClient) => { (fetchedClient) => {
setClient(fetchedClient); setClient(fetchedClient);
setupForm(fetchedClient); setupForm(fetchedClient);
}, },
handleError handleError
); );
}, [id]); }, [clientId]);
const save = async () => { const save = async () => {
if (await form.trigger()) { if (await form.trigger()) {
@ -187,7 +189,7 @@ export const ClientDetails = () => {
webOrigins, webOrigins,
attributes, attributes,
}; };
await adminClient.clients.update({ id }, newClient); await adminClient.clients.update({ id: clientId }, newClient);
setupForm(newClient); setupForm(newClient);
setClient(newClient); setClient(newClient);
addAlert(t("clientSaveSuccess"), AlertVariant.success); addAlert(t("clientSaveSuccess"), AlertVariant.success);
@ -244,7 +246,7 @@ export const ClientDetails = () => {
eventKey="credentials" eventKey="credentials"
title={<TabTitleText>{t("credentials")}</TabTitleText>} title={<TabTitleText>{t("credentials")}</TabTitleText>}
> >
<Credentials clientId={id} save={save} /> <Credentials clientId={clientId} save={save} />
</Tab> </Tab>
)} )}
<Tab <Tab
@ -259,22 +261,32 @@ export const ClientDetails = () => {
eventKey="clientScopes" eventKey="clientScopes"
title={<TabTitleText>{t("clientScopes")}</TabTitleText>} title={<TabTitleText>{t("clientScopes")}</TabTitleText>}
> >
<KeycloakTabs paramName="subtab" isSecondary> <Tabs
activeKey={activeTab2}
isSecondary
onSelect={(_, key) => setActiveTab2(key as number)}
>
<Tab <Tab
id="setup" id="setup"
eventKey="setup" eventKey={30}
title={<TabTitleText>{t("setup")}</TabTitleText>} title={<TabTitleText>{t("setup")}</TabTitleText>}
> >
<ClientScopes clientId={id} protocol={client!.protocol!} /> <ClientScopes
clientId={clientId}
protocol={client!.protocol!}
/>
</Tab> </Tab>
<Tab <Tab
id="evaluate" id="evaluate"
eventKey="evaluate" eventKey={31}
title={<TabTitleText>{t("evaluate")}</TabTitleText>} title={<TabTitleText>{t("evaluate")}</TabTitleText>}
> >
<EvaluateScopes clientId={id} protocol={client!.protocol!} /> <EvaluateScopes
clientId={clientId}
protocol={client!.protocol!}
/>
</Tab> </Tab>
</KeycloakTabs> </Tabs>
</Tab> </Tab>
{client!.serviceAccountsEnabled && ( {client!.serviceAccountsEnabled && (
<Tab <Tab
@ -282,7 +294,7 @@ export const ClientDetails = () => {
eventKey="serviceAccount" eventKey="serviceAccount"
title={<TabTitleText>{t("serviceAccount")}</TabTitleText>} title={<TabTitleText>{t("serviceAccount")}</TabTitleText>}
> >
<ServiceAccount clientId={id} /> <ServiceAccount clientId={clientId} />
</Tab> </Tab>
)} )}
<Tab <Tab

View file

@ -66,7 +66,7 @@ export const ClientsSection = () => {
const ClientDetailLink = (client: ClientRepresentation) => ( const ClientDetailLink = (client: ClientRepresentation) => (
<> <>
<Link key={client.id} to={`${url}/${client.id}`}> <Link key={client.id} to={`${url}/${client.id}/settings`}>
{client.clientId} {client.clientId}
{!client.enabled && ( {!client.enabled && (
<Badge isRead className="pf-u-ml-sm"> <Badge isRead className="pf-u-ml-sm">

View file

@ -44,7 +44,7 @@ export const NewClientForm = () => {
try { try {
const newClient = await adminClient.clients.create({ ...client }); const newClient = await adminClient.clients.create({ ...client });
addAlert(t("createSuccess"), AlertVariant.success); addAlert(t("createSuccess"), AlertVariant.success);
history.push(`/${realm}/clients/${newClient.id}`); history.push(`/${realm}/clients/${newClient.id}/settings`);
} catch (error) { } catch (error) {
addAlert(t("createError", { error }), AlertVariant.danger); addAlert(t("createError", { error }), AlertVariant.danger);
} }

View file

@ -6,7 +6,7 @@ import { MemoryRouter } from "react-router-dom";
describe("BreadCrumbs tests", () => { describe("BreadCrumbs tests", () => {
it("couple of crumbs", () => { it("couple of crumbs", () => {
const crumbs = mount( const crumbs = mount(
<MemoryRouter initialEntries={["/master/clients/1234"]}> <MemoryRouter initialEntries={["/master/clients/1234/settings"]}>
<PageBreadCrumbs /> <PageBreadCrumbs />
</MemoryRouter> </MemoryRouter>
); );

View file

@ -136,7 +136,7 @@ exports[`BreadCrumbs tests couple of crumbs 1`] = `
</AngleRightIcon> </AngleRightIcon>
</span> </span>
<span <span
key="/master/clients/1234" key="/master/clients/1234/settings"
> >
Client details Client details
</span> </span>

View file

@ -15,8 +15,6 @@ const createUrl = (
const value = params[key]; const value = params[key];
if (url.indexOf(key) !== -1) { if (url.indexOf(key) !== -1) {
url = url.replace(new RegExp(`:${key}\\??`), value || ""); url = url.replace(new RegExp(`:${key}\\??`), value || "");
} else {
url += `/${value}`;
} }
} }
return url; return url;
@ -37,13 +35,13 @@ export const KeycloakTabs = ({
(isValidElement<TabProps>(firstTab) && firstTab.props.eventKey) || (isValidElement<TabProps>(firstTab) && firstTab.props.eventKey) ||
""; "";
const pathIndex = match.path.indexOf(paramName) + paramName.length;
const path = match.path.substr(0, pathIndex);
return ( return (
<Tabs <Tabs
activeKey={tab} activeKey={tab}
onSelect={(_, key) => onSelect={(_, key) =>
history.push( history.push(createUrl(path, { ...params, [paramName]: key as string }))
createUrl(match.path, { ...params, [paramName]: key as string })
)
} }
{...rest} {...rest}
> >

View file

@ -22,6 +22,15 @@ type RolesListProps = {
) => Promise<RoleRepresentation[]>; ) => Promise<RoleRepresentation[]>;
}; };
const RoleLink = ({ role }: { role: RoleRepresentation }) => {
const { url } = useRouteMatch();
return (
<Link key={role.id} to={`${url}/${role.id}/details`}>
{role.name}
</Link>
);
};
export const RolesList = ({ export const RolesList = ({
loader, loader,
paginated = true, paginated = true,
@ -37,9 +46,7 @@ export const RolesList = ({
const RoleDetailLink = (role: RoleRepresentation) => ( const RoleDetailLink = (role: RoleRepresentation) => (
<> <>
<Link key={role.id} to={`${url}/${role.id}/details`}> <RoleLink role={role} />
{role.name}
</Link>
</> </>
); );

View file

@ -59,12 +59,6 @@ export const routes: RoutesFn = (t: TFunction) => [
breadcrumb: t("clients:importClient"), breadcrumb: t("clients:importClient"),
access: "manage-clients", access: "manage-clients",
}, },
{
path: "/:realm/clients/:id",
component: ClientDetails,
breadcrumb: t("clients:clientSettings"),
access: "view-clients",
},
{ {
path: "/:realm/clients/:clientId/roles/add-role", path: "/:realm/clients/:clientId/roles/add-role",
component: RealmRoleTabs, component: RealmRoleTabs,
@ -84,9 +78,9 @@ export const routes: RoutesFn = (t: TFunction) => [
access: "view-realm", access: "view-realm",
}, },
{ {
path: "/:realm/clients/:id/:tab?/:subtab?", path: "/:realm/clients/:clientId/:tab",
component: ClientDetails, component: ClientDetails,
breadcrumb: null, breadcrumb: t("clients:clientSettings"),
access: "view-clients", access: "view-clients",
}, },
{ {
@ -96,31 +90,19 @@ export const routes: RoutesFn = (t: TFunction) => [
access: "manage-clients", access: "manage-clients",
}, },
{ {
path: "/:realm/client-scopes/:id", path: "/:realm/client-scopes/:id/mappers/oidc-role-name-mapper",
component: ClientScopeForm,
breadcrumb: t("client-scopes:clientScopeDetails"),
access: "view-clients",
},
{
path: "/:realm/client-scopes/:id/:tab",
component: ClientScopeForm,
breadcrumb: null,
access: "view-clients",
},
{
path: "/:realm/client-scopes/:scopeId/oidc-role-name-mapper",
component: RoleMappingForm, component: RoleMappingForm,
breadcrumb: t("client-scopes:mappingDetails"), breadcrumb: t("client-scopes:mappingDetails"),
access: "view-clients", access: "view-clients",
}, },
{ {
path: "/:realm/client-scopes/:scopeId/:id", path: "/:realm/client-scopes/:id/mappers/:mapperId",
component: MappingDetails, component: MappingDetails,
breadcrumb: t("client-scopes:mappingDetails"), breadcrumb: t("client-scopes:mappingDetails"),
access: "view-clients", access: "view-clients",
}, },
{ {
path: "/:realm/client-scopes/:id", path: "/:realm/client-scopes/:id/:tab",
component: ClientScopeForm, component: ClientScopeForm,
breadcrumb: t("client-scopes:clientScopeDetails"), breadcrumb: t("client-scopes:clientScopeDetails"),
access: "view-clients", access: "view-clients",