Clients(authorization -> evaluate): Adds expandable results table (#2004)
This commit is contained in:
parent
e8e64798ea
commit
fa0e162c0b
5 changed files with 455 additions and 7 deletions
|
@ -12,6 +12,11 @@ import {
|
||||||
Switch,
|
Switch,
|
||||||
ExpandableSection,
|
ExpandableSection,
|
||||||
TextInput,
|
TextInput,
|
||||||
|
ButtonVariant,
|
||||||
|
InputGroup,
|
||||||
|
Toolbar,
|
||||||
|
ToolbarGroup,
|
||||||
|
ToolbarItem,
|
||||||
} from "@patternfly/react-core";
|
} from "@patternfly/react-core";
|
||||||
import { Controller, useFormContext } from "react-hook-form";
|
import { Controller, useFormContext } from "react-hook-form";
|
||||||
|
|
||||||
|
@ -25,10 +30,16 @@ import type ResourceEvaluation from "@keycloak/keycloak-admin-client/lib/defs/re
|
||||||
import { useRealm } from "../../context/realm-context/RealmContext";
|
import { useRealm } from "../../context/realm-context/RealmContext";
|
||||||
import { AttributeInput } from "../../components/attribute-input/AttributeInput";
|
import { AttributeInput } from "../../components/attribute-input/AttributeInput";
|
||||||
import { defaultContextAttributes } from "../utils";
|
import { defaultContextAttributes } from "../utils";
|
||||||
|
import type EvaluationResultRepresentation from "@keycloak/keycloak-admin-client/lib/defs/evaluationResultRepresentation";
|
||||||
import type ResourceRepresentation from "@keycloak/keycloak-admin-client/lib/defs/resourceRepresentation";
|
import type ResourceRepresentation from "@keycloak/keycloak-admin-client/lib/defs/resourceRepresentation";
|
||||||
import { useParams } from "react-router-dom";
|
import { useParams } from "react-router-dom";
|
||||||
import type ScopeRepresentation from "@keycloak/keycloak-admin-client/lib/defs/scopeRepresentation";
|
import type ScopeRepresentation from "@keycloak/keycloak-admin-client/lib/defs/scopeRepresentation";
|
||||||
import type { KeyValueType } from "../../components/attribute-form/attribute-convert";
|
import type { KeyValueType } from "../../components/attribute-form/attribute-convert";
|
||||||
|
import { TableComposable, Th, Thead, Tr } from "@patternfly/react-table";
|
||||||
|
import "./auth-evaluate.css";
|
||||||
|
import { AuthorizationEvaluateResource } from "./AuthorizationEvaluateResource";
|
||||||
|
import { SearchIcon } from "@patternfly/react-icons";
|
||||||
|
import { ListEmptyState } from "../../components/list-empty-state/ListEmptyState";
|
||||||
|
|
||||||
interface EvaluateFormInputs
|
interface EvaluateFormInputs
|
||||||
extends Omit<ResourceEvaluation, "context" | "resources"> {
|
extends Omit<ResourceEvaluation, "context" | "resources"> {
|
||||||
|
@ -68,12 +79,14 @@ export type AttributeForm = Omit<
|
||||||
resources?: KeyValueType[];
|
resources?: KeyValueType[];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type Props = ClientSettingsProps & EvaluationResultRepresentation;
|
||||||
|
|
||||||
export const AuthorizationEvaluate = ({
|
export const AuthorizationEvaluate = ({
|
||||||
clients,
|
clients,
|
||||||
clientRoles,
|
clientRoles,
|
||||||
clientName,
|
clientName,
|
||||||
users,
|
users,
|
||||||
}: ClientSettingsProps) => {
|
}: Props) => {
|
||||||
const form = useFormContext<EvaluateFormInputs>();
|
const form = useFormContext<EvaluateFormInputs>();
|
||||||
const { control, reset, trigger } = form;
|
const { control, reset, trigger } = form;
|
||||||
const { t } = useTranslation("clients");
|
const { t } = useTranslation("clients");
|
||||||
|
@ -92,6 +105,28 @@ export const AuthorizationEvaluate = ({
|
||||||
const [scopes, setScopes] = useState<ScopeRepresentation[]>([]);
|
const [scopes, setScopes] = useState<ScopeRepresentation[]>([]);
|
||||||
const [selectedClient, setSelectedClient] = useState<ClientRepresentation>();
|
const [selectedClient, setSelectedClient] = useState<ClientRepresentation>();
|
||||||
const [selectedUser, setSelectedUser] = useState<UserRepresentation>();
|
const [selectedUser, setSelectedUser] = useState<UserRepresentation>();
|
||||||
|
const [evaluateResults, setEvaluateResults] = useState<
|
||||||
|
EvaluationResultRepresentation[]
|
||||||
|
>([]);
|
||||||
|
const [showEvaluateResults, setShowEvaluateResults] = useState(false);
|
||||||
|
const [searchVal, setSearchVal] = useState("");
|
||||||
|
const [filteredResources, setFilteredResources] = useState<
|
||||||
|
EvaluationResultRepresentation[]
|
||||||
|
>([]);
|
||||||
|
const [filterDropdownOpen, setFilterDropdownOpen] = useState(false);
|
||||||
|
const [key, setKey] = useState(0);
|
||||||
|
|
||||||
|
const refresh = () => {
|
||||||
|
setKey(new Date().getTime());
|
||||||
|
};
|
||||||
|
|
||||||
|
const FilterType = {
|
||||||
|
allResults: t("allResults"),
|
||||||
|
resultPermit: t("resultPermit"),
|
||||||
|
resultDeny: t("resultDeny"),
|
||||||
|
};
|
||||||
|
|
||||||
|
const [filterType, setFilterType] = useState(FilterType.allResults);
|
||||||
|
|
||||||
useFetch(
|
useFetch(
|
||||||
async () =>
|
async () =>
|
||||||
|
@ -107,7 +142,7 @@ export const AuthorizationEvaluate = ({
|
||||||
setResources(resources);
|
setResources(resources);
|
||||||
setScopes(scopes);
|
setScopes(scopes);
|
||||||
},
|
},
|
||||||
[]
|
[key, filterType]
|
||||||
);
|
);
|
||||||
|
|
||||||
const evaluate = async () => {
|
const evaluate = async () => {
|
||||||
|
@ -131,13 +166,175 @@ export const AuthorizationEvaluate = ({
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
return adminClient.clients.evaluateResource(
|
const evaluation = await adminClient.clients.evaluateResource(
|
||||||
{ id: clientId!, realm: realm.realm },
|
{ id: clientId!, realm: realm.realm },
|
||||||
resEval
|
resEval
|
||||||
);
|
);
|
||||||
|
|
||||||
|
setEvaluateResults(evaluation.results);
|
||||||
|
setShowEvaluateResults(true);
|
||||||
|
return evaluateResults;
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
const onSearch = () => {
|
||||||
|
if (searchVal !== "") {
|
||||||
|
setSearchVal(searchVal);
|
||||||
|
const filtered = evaluateResults.filter((resource) =>
|
||||||
|
resource.resource?.name?.includes(searchVal)
|
||||||
|
);
|
||||||
|
setFilteredResources(filtered);
|
||||||
|
} else {
|
||||||
|
setSearchVal("");
|
||||||
|
setFilteredResources(evaluateResults);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleKeyDown = (e: any) => {
|
||||||
|
if (e.key === "Enter") {
|
||||||
|
onSearch();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleInputChange = (value: string) => {
|
||||||
|
setSearchVal(value);
|
||||||
|
};
|
||||||
|
|
||||||
|
const noEvaluatedData = evaluateResults.length === 0;
|
||||||
|
const noFilteredData = filteredResources.length === 0;
|
||||||
|
|
||||||
|
const options = [
|
||||||
|
<SelectOption
|
||||||
|
key={1}
|
||||||
|
data-testid="all-results-option"
|
||||||
|
value={FilterType.allResults}
|
||||||
|
isPlaceholder
|
||||||
|
/>,
|
||||||
|
<SelectOption
|
||||||
|
data-testid="result-permit-option"
|
||||||
|
key={2}
|
||||||
|
value={FilterType.resultPermit}
|
||||||
|
/>,
|
||||||
|
<SelectOption
|
||||||
|
data-testid="result-deny-option"
|
||||||
|
key={3}
|
||||||
|
value={FilterType.resultDeny}
|
||||||
|
/>,
|
||||||
|
];
|
||||||
|
|
||||||
|
return showEvaluateResults ? (
|
||||||
|
<PageSection>
|
||||||
|
<Toolbar>
|
||||||
|
<ToolbarGroup className="providers-toolbar">
|
||||||
|
<ToolbarItem>
|
||||||
|
<InputGroup>
|
||||||
|
<TextInput
|
||||||
|
name={"inputGroupName"}
|
||||||
|
id={"inputGroupName"}
|
||||||
|
type="search"
|
||||||
|
aria-label={t("common:search")}
|
||||||
|
placeholder={t("common:search")}
|
||||||
|
onChange={handleInputChange}
|
||||||
|
onKeyDown={handleKeyDown}
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
variant={ButtonVariant.control}
|
||||||
|
aria-label={t("common:search")}
|
||||||
|
onClick={() => onSearch()}
|
||||||
|
>
|
||||||
|
<SearchIcon />
|
||||||
|
</Button>
|
||||||
|
</InputGroup>
|
||||||
|
</ToolbarItem>
|
||||||
|
<ToolbarItem>
|
||||||
|
<Select
|
||||||
|
width={300}
|
||||||
|
data-testid="filter-type-select"
|
||||||
|
isOpen={filterDropdownOpen}
|
||||||
|
className="kc-filter-type-select"
|
||||||
|
variant={SelectVariant.single}
|
||||||
|
onToggle={() => setFilterDropdownOpen(!filterDropdownOpen)}
|
||||||
|
onSelect={(_, value) => {
|
||||||
|
if (value === FilterType.allResults) {
|
||||||
|
setFilterType(FilterType.allResults);
|
||||||
|
} else if (value === FilterType.resultPermit) {
|
||||||
|
const filterPermit = evaluateResults.filter(
|
||||||
|
(resource) => resource.status === "PERMIT"
|
||||||
|
);
|
||||||
|
setFilteredResources(filterPermit);
|
||||||
|
setFilterType(FilterType.resultPermit);
|
||||||
|
|
||||||
|
refresh();
|
||||||
|
} else if (value === FilterType.resultDeny) {
|
||||||
|
const filterDeny = evaluateResults.filter(
|
||||||
|
(resource) => resource.status === "DENY"
|
||||||
|
);
|
||||||
|
setFilterType(FilterType.resultDeny);
|
||||||
|
setFilteredResources(filterDeny);
|
||||||
|
refresh();
|
||||||
|
}
|
||||||
|
setFilterDropdownOpen(false);
|
||||||
|
}}
|
||||||
|
selections={filterType}
|
||||||
|
>
|
||||||
|
{options}
|
||||||
|
</Select>
|
||||||
|
</ToolbarItem>
|
||||||
|
</ToolbarGroup>
|
||||||
|
</Toolbar>
|
||||||
|
{!noEvaluatedData && !noFilteredData && (
|
||||||
|
<TableComposable aria-label={t("evaluationResults")}>
|
||||||
|
<Thead>
|
||||||
|
<Tr>
|
||||||
|
<Th />
|
||||||
|
<Th>{t("resource")}</Th>
|
||||||
|
<Th>{t("overallResults")}</Th>
|
||||||
|
<Th>{t("scopes")}</Th>
|
||||||
|
<Th />
|
||||||
|
</Tr>
|
||||||
|
</Thead>
|
||||||
|
{(filterType == FilterType.allResults
|
||||||
|
? evaluateResults
|
||||||
|
: filteredResources
|
||||||
|
).map((resource, rowIndex) => (
|
||||||
|
<AuthorizationEvaluateResource
|
||||||
|
key={rowIndex}
|
||||||
|
rowIndex={rowIndex}
|
||||||
|
resource={resource}
|
||||||
|
evaluateResults={evaluateResults}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</TableComposable>
|
||||||
|
)}
|
||||||
|
{noEvaluatedData ||
|
||||||
|
(noFilteredData && (
|
||||||
|
<ListEmptyState
|
||||||
|
isSearchVariant
|
||||||
|
message={t("common:noSearchResults")}
|
||||||
|
instructions={t("common:noSearchResultsInstructions")}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
<ActionGroup className="kc-evaluated-options">
|
||||||
|
<Button
|
||||||
|
data-testid="authorization-eval"
|
||||||
|
id="back-btn"
|
||||||
|
onClick={() => setShowEvaluateResults(false)}
|
||||||
|
>
|
||||||
|
{t("common:back")}
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
data-testid="authorization-reevaluate"
|
||||||
|
id="reevaluate-btn"
|
||||||
|
variant="secondary"
|
||||||
|
onClick={() => evaluate()}
|
||||||
|
>
|
||||||
|
{t("clients:reevaluate")}
|
||||||
|
</Button>
|
||||||
|
<Button data-testid="authorization-revert" variant="secondary">
|
||||||
|
{t("showAuthData")}
|
||||||
|
</Button>
|
||||||
|
</ActionGroup>
|
||||||
|
</PageSection>
|
||||||
|
) : (
|
||||||
<PageSection>
|
<PageSection>
|
||||||
<FormPanel
|
<FormPanel
|
||||||
className="kc-identity-information"
|
className="kc-identity-information"
|
||||||
|
@ -332,7 +529,7 @@ export const AuthorizationEvaluate = ({
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
helperTextInvalid={t("common:required")}
|
helperTextInvalid={t("common:required")}
|
||||||
fieldId={name!}
|
fieldId="resourcesAndAuthScopes"
|
||||||
>
|
>
|
||||||
<AttributeInput
|
<AttributeInput
|
||||||
selectableValues={resources.map<AttributeType>((item) => ({
|
selectableValues={resources.map<AttributeType>((item) => ({
|
||||||
|
@ -428,7 +625,7 @@ export const AuthorizationEvaluate = ({
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
helperTextInvalid={t("common:required")}
|
helperTextInvalid={t("common:required")}
|
||||||
fieldId={name!}
|
fieldId="contextualAttributes"
|
||||||
>
|
>
|
||||||
<AttributeInput
|
<AttributeInput
|
||||||
selectableValues={defaultContextAttributes}
|
selectableValues={defaultContextAttributes}
|
||||||
|
@ -439,7 +636,12 @@ export const AuthorizationEvaluate = ({
|
||||||
</ExpandableSection>
|
</ExpandableSection>
|
||||||
</FormAccess>
|
</FormAccess>
|
||||||
<ActionGroup>
|
<ActionGroup>
|
||||||
<Button data-testid="authorization-eval" onClick={() => evaluate()}>
|
<Button
|
||||||
|
data-testid="authorization-eval"
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
||||||
|
isDisabled={form.getValues().resources?.every((e) => e.key === "")}
|
||||||
|
onClick={() => evaluate()}
|
||||||
|
>
|
||||||
{t("evaluate")}
|
{t("evaluate")}
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
|
|
93
src/clients/authorization/AuthorizationEvaluateResource.tsx
Normal file
93
src/clients/authorization/AuthorizationEvaluateResource.tsx
Normal file
|
@ -0,0 +1,93 @@
|
||||||
|
import React, { useState } from "react";
|
||||||
|
import {
|
||||||
|
ExpandableRowContent,
|
||||||
|
TableComposable,
|
||||||
|
Tbody,
|
||||||
|
Td,
|
||||||
|
Th,
|
||||||
|
Thead,
|
||||||
|
Tr,
|
||||||
|
} from "@patternfly/react-table";
|
||||||
|
import { DescriptionList } from "@patternfly/react-core/dist/esm/components";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { AuthorizationEvaluateResourcePolicies } from "./AuthorizationEvaluateResourcePolicies";
|
||||||
|
import type EvaluationResultRepresentation from "@keycloak/keycloak-admin-client/lib/defs/evaluationResultRepresentation";
|
||||||
|
import type PolicyResultRepresentation from "@keycloak/keycloak-admin-client/lib/defs/policyResultRepresentation";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
rowIndex: number;
|
||||||
|
resource: EvaluationResultRepresentation;
|
||||||
|
evaluateResults: any;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const AuthorizationEvaluateResource = ({
|
||||||
|
rowIndex,
|
||||||
|
resource,
|
||||||
|
evaluateResults,
|
||||||
|
}: Props) => {
|
||||||
|
const [expanded, setExpanded] = useState<boolean>(false);
|
||||||
|
const { t } = useTranslation("clients");
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Tbody isExpanded={expanded}>
|
||||||
|
<Tr>
|
||||||
|
<Td
|
||||||
|
expand={{
|
||||||
|
rowIndex,
|
||||||
|
isExpanded: expanded,
|
||||||
|
onToggle: () => setExpanded((prev) => !prev),
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Td data-testid={`name-column-${resource.resource}`}>
|
||||||
|
{resource.resource?.name}
|
||||||
|
</Td>
|
||||||
|
<Td id={resource.status?.toLowerCase()}>
|
||||||
|
{t(`${resource.status?.toLowerCase()}`)}
|
||||||
|
</Td>
|
||||||
|
<Td>
|
||||||
|
{resource.allowedScopes?.length
|
||||||
|
? resource.allowedScopes.map((item) => item.name)
|
||||||
|
: "-"}
|
||||||
|
</Td>
|
||||||
|
</Tr>
|
||||||
|
<Tr key={`child-${resource.resource}`} isExpanded={expanded}>
|
||||||
|
<Td />
|
||||||
|
<Td colSpan={5}>
|
||||||
|
<ExpandableRowContent>
|
||||||
|
{expanded && (
|
||||||
|
<DescriptionList
|
||||||
|
isHorizontal
|
||||||
|
className="keycloak_resource_details"
|
||||||
|
>
|
||||||
|
<TableComposable aria-label={t("evaluationResults")}>
|
||||||
|
<Thead>
|
||||||
|
<Tr>
|
||||||
|
<Th />
|
||||||
|
<Th>{t("permission")}</Th>
|
||||||
|
<Th>{t("results")}</Th>
|
||||||
|
<Th>{t("decisionStrategy")}</Th>
|
||||||
|
<Th>{t("grantedScopes")}</Th>
|
||||||
|
<Th>{t("deniedScopes")}</Th>
|
||||||
|
<Th />
|
||||||
|
</Tr>
|
||||||
|
</Thead>
|
||||||
|
{Object.values(evaluateResults[rowIndex].policies).map(
|
||||||
|
(outerPolicy, idx) => (
|
||||||
|
<AuthorizationEvaluateResourcePolicies
|
||||||
|
key={idx}
|
||||||
|
idx={idx}
|
||||||
|
rowIndex={rowIndex}
|
||||||
|
outerPolicy={outerPolicy as PolicyResultRepresentation}
|
||||||
|
resource={resource}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
)}
|
||||||
|
</TableComposable>
|
||||||
|
</DescriptionList>
|
||||||
|
)}
|
||||||
|
</ExpandableRowContent>
|
||||||
|
</Td>
|
||||||
|
</Tr>
|
||||||
|
</Tbody>
|
||||||
|
);
|
||||||
|
};
|
|
@ -0,0 +1,114 @@
|
||||||
|
import React, { useState } from "react";
|
||||||
|
import {
|
||||||
|
DescriptionList,
|
||||||
|
TextContent,
|
||||||
|
TextList,
|
||||||
|
TextListItem,
|
||||||
|
capitalize,
|
||||||
|
} from "@patternfly/react-core";
|
||||||
|
import { Tbody, Tr, Td, ExpandableRowContent } from "@patternfly/react-table";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import type PolicyResultRepresentation from "@keycloak/keycloak-admin-client/lib/defs/policyResultRepresentation";
|
||||||
|
import type EvaluationResultRepresentation from "@keycloak/keycloak-admin-client/lib/defs/evaluationResultRepresentation";
|
||||||
|
import { DecisionEffect } from "@keycloak/keycloak-admin-client/lib/defs/policyRepresentation";
|
||||||
|
import { Link, useParams } from "react-router-dom";
|
||||||
|
import { toPermissionDetails } from "../routes/PermissionDetails";
|
||||||
|
import { toPolicyDetails } from "../routes/PolicyDetails";
|
||||||
|
import { useRealm } from "../../context/realm-context/RealmContext";
|
||||||
|
import type { ClientParams } from "../routes/Client";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
idx: number;
|
||||||
|
rowIndex: number;
|
||||||
|
outerPolicy: PolicyResultRepresentation;
|
||||||
|
resource: EvaluationResultRepresentation;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const AuthorizationEvaluateResourcePolicies = ({
|
||||||
|
idx,
|
||||||
|
rowIndex,
|
||||||
|
outerPolicy,
|
||||||
|
resource,
|
||||||
|
}: Props) => {
|
||||||
|
const [expanded, setExpanded] = useState<boolean>(false);
|
||||||
|
const { t } = useTranslation("clients");
|
||||||
|
const { realm } = useRealm();
|
||||||
|
const { clientId } = useParams<ClientParams>();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Tbody key={idx} isExpanded={expanded}>
|
||||||
|
<Tr>
|
||||||
|
<Td
|
||||||
|
expand={{
|
||||||
|
rowIndex,
|
||||||
|
isExpanded: expanded,
|
||||||
|
onToggle: () => setExpanded((prev) => !prev),
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Td data-testid={`name-column-${resource.resource}`}>
|
||||||
|
<Link
|
||||||
|
to={toPermissionDetails({
|
||||||
|
realm,
|
||||||
|
id: clientId,
|
||||||
|
permissionType: outerPolicy.policy?.type!,
|
||||||
|
permissionId: outerPolicy.policy?.id!,
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
{outerPolicy.policy?.name}
|
||||||
|
</Link>
|
||||||
|
</Td>
|
||||||
|
<Td id={outerPolicy.status?.toLowerCase()}>
|
||||||
|
{t(outerPolicy.status?.toLowerCase() as string)}
|
||||||
|
</Td>
|
||||||
|
<Td>{t(`${outerPolicy.policy?.decisionStrategy?.toLowerCase()}`)}</Td>
|
||||||
|
<Td>
|
||||||
|
{outerPolicy.status === DecisionEffect.Permit
|
||||||
|
? resource.policies?.[rowIndex].scopes?.join(", ")
|
||||||
|
: "-"}
|
||||||
|
</Td>
|
||||||
|
<Td>
|
||||||
|
{outerPolicy.status === DecisionEffect.Deny &&
|
||||||
|
resource.policies?.[rowIndex]?.scopes?.length
|
||||||
|
? resource.policies[rowIndex].scopes?.join(", ")
|
||||||
|
: "-"}
|
||||||
|
</Td>
|
||||||
|
</Tr>
|
||||||
|
<Tr key={`child-${resource.resource}`} isExpanded={expanded}>
|
||||||
|
<Td />
|
||||||
|
<Td colSpan={5}>
|
||||||
|
{expanded && (
|
||||||
|
<ExpandableRowContent>
|
||||||
|
<DescriptionList
|
||||||
|
isHorizontal
|
||||||
|
className="keycloak_resource_details"
|
||||||
|
>
|
||||||
|
<TextContent>
|
||||||
|
<TextList>
|
||||||
|
{outerPolicy.associatedPolicies?.map((item) => (
|
||||||
|
<TextListItem key="policyDetails">
|
||||||
|
<Link
|
||||||
|
to={toPolicyDetails({
|
||||||
|
realm,
|
||||||
|
id: clientId,
|
||||||
|
policyType: item.policy?.type!,
|
||||||
|
policyId: item.policy?.id!,
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
{item.policy?.name}
|
||||||
|
</Link>
|
||||||
|
|
||||||
|
{t("votedToStatus", {
|
||||||
|
status: capitalize(item.status as string),
|
||||||
|
})}
|
||||||
|
</TextListItem>
|
||||||
|
))}
|
||||||
|
</TextList>
|
||||||
|
</TextContent>
|
||||||
|
</DescriptionList>
|
||||||
|
</ExpandableRowContent>
|
||||||
|
)}
|
||||||
|
</Td>
|
||||||
|
</Tr>
|
||||||
|
</Tbody>
|
||||||
|
);
|
||||||
|
};
|
23
src/clients/authorization/auth-evaluate.css
Normal file
23
src/clients/authorization/auth-evaluate.css
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
td#permit {
|
||||||
|
color: var(--pf-global--success-color--100);
|
||||||
|
font-weight: bold;;
|
||||||
|
}
|
||||||
|
|
||||||
|
td#deny {
|
||||||
|
color: var(--pf-global--danger-color--100);
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.kc-evaluated-options {
|
||||||
|
padding-top: 300px
|
||||||
|
}
|
||||||
|
|
||||||
|
button#back-btn {
|
||||||
|
margin-right: var(--pf-global--spacer--md);
|
||||||
|
}
|
||||||
|
|
||||||
|
button#reevaluate-btn {
|
||||||
|
margin-right: var(--pf-global--spacer--md);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
@ -44,6 +44,22 @@ export default {
|
||||||
selectAUser: "Select a user",
|
selectAUser: "Select a user",
|
||||||
client: "Client",
|
client: "Client",
|
||||||
evaluate: "Evaluate",
|
evaluate: "Evaluate",
|
||||||
|
reevaluate: "Re-evaluate",
|
||||||
|
showAuthData: "Show authorization data",
|
||||||
|
results: "Results",
|
||||||
|
allResults: "All results",
|
||||||
|
resultPermit: "Result-Permit",
|
||||||
|
resultDeny: "Result-Deny",
|
||||||
|
permit: "Permit",
|
||||||
|
deny: "Deny",
|
||||||
|
unanimous: "Unanimous",
|
||||||
|
affirmative: "Affirmative",
|
||||||
|
consensus: "Consensus",
|
||||||
|
votedToStatus: " voted to {{status}}",
|
||||||
|
overallResults: "Overall Results",
|
||||||
|
grantedScopes: "Granted scopes",
|
||||||
|
deniedScopes: "Denied scopes",
|
||||||
|
permission: "Permission",
|
||||||
lastEvaluation: "Last Evaluation",
|
lastEvaluation: "Last Evaluation",
|
||||||
resourcesAndAuthScopes: "Resources and Authentication Scopes",
|
resourcesAndAuthScopes: "Resources and Authentication Scopes",
|
||||||
authScopes: "Authorization scopes",
|
authScopes: "Authorization scopes",
|
||||||
|
|
Loading…
Reference in a new issue