added save dialog
This commit is contained in:
parent
3399b16c7e
commit
3fe5d8383a
8 changed files with 255 additions and 42 deletions
|
@ -21,7 +21,7 @@ import ClientRepresentation from "keycloak-admin/lib/defs/clientRepresentation";
|
|||
import { formattedLinkTableCell } from "../components/external-link/FormattedLink";
|
||||
import { useConfirmDialog } from "../components/confirm-dialog/ConfirmDialog";
|
||||
import { KeycloakTabs } from "../components/keycloak-tabs/KeycloakTabs";
|
||||
import { InitialAccessTokenList } from "./InitialAccessTokenList";
|
||||
import { InitialAccessTokenList } from "./initial-access/InitialAccessTokenList";
|
||||
|
||||
export const ClientsSection = () => {
|
||||
const { t } = useTranslation("clients");
|
||||
|
|
|
@ -1,41 +0,0 @@
|
|||
import React from "react";
|
||||
import moment from "moment";
|
||||
|
||||
import { KeycloakDataTable } from "../components/table-toolbar/KeycloakDataTable";
|
||||
import { useAdminClient } from "../context/auth/AdminClient";
|
||||
import { useRealm } from "../context/realm-context/RealmContext";
|
||||
|
||||
export const InitialAccessTokenList = () => {
|
||||
const adminClient = useAdminClient();
|
||||
const { realm } = useRealm();
|
||||
const loader = async () =>
|
||||
await adminClient.realms.getClientsInitialAccess({ realm });
|
||||
|
||||
return (
|
||||
<KeycloakDataTable
|
||||
ariaLabelKey="clients:initialAccessToken"
|
||||
searchPlaceholderKey="clients:searchInitialAccessToken"
|
||||
loader={loader}
|
||||
columns={[
|
||||
{
|
||||
name: "id",
|
||||
},
|
||||
{
|
||||
name: "timestamp",
|
||||
cellRenderer: (row) => moment(row.timestamp * 1000).fromNow(),
|
||||
},
|
||||
{
|
||||
name: "expiration",
|
||||
cellRenderer: (row) =>
|
||||
moment(moment.now() - row.expiration * 1000).fromNow(),
|
||||
},
|
||||
{
|
||||
name: "count",
|
||||
},
|
||||
{
|
||||
name: "remainingCount",
|
||||
},
|
||||
]}
|
||||
/>
|
||||
);
|
||||
};
|
|
@ -4,6 +4,9 @@
|
|||
"adminURL": "URL to the admin interface of the client. Set this if the client supports the adapter REST API. This REST API allows the auth server to push revocation policies and other administrative tasks. Usually this is set to the base URL of the client.",
|
||||
"downloadType": "this is information about the download type",
|
||||
"details": "this is information about the details",
|
||||
"createToken": "An initial access token can only be used to create clients",
|
||||
"expiration": "Specifies how long the token should be valid",
|
||||
"count": "Specifies how many clients can be created using the token",
|
||||
"client-authenticator-type": "Client Authenticator used for authentication of this client against Keycloak server",
|
||||
"registration-access-token": "The registration access token provides access for clients to the client registration service.",
|
||||
"signature-algorithm": "JWA algorithm, which the client needs to use when signing a JWT for authentication. If left blank, the client is allowed to use any algorithm.",
|
||||
|
|
44
src/clients/initial-access/AccessTokenDialog.tsx
Normal file
44
src/clients/initial-access/AccessTokenDialog.tsx
Normal file
|
@ -0,0 +1,44 @@
|
|||
import React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import {
|
||||
Alert,
|
||||
AlertVariant,
|
||||
ClipboardCopy,
|
||||
Form,
|
||||
FormGroup,
|
||||
Modal,
|
||||
ModalVariant,
|
||||
} from "@patternfly/react-core";
|
||||
|
||||
type AccessTokenDialogProps = {
|
||||
token: string;
|
||||
toggleDialog: () => void;
|
||||
};
|
||||
|
||||
export const AccessTokenDialog = ({
|
||||
token,
|
||||
toggleDialog,
|
||||
}: AccessTokenDialogProps) => {
|
||||
const { t } = useTranslation("clients");
|
||||
return (
|
||||
<Modal
|
||||
title={t("initialAccessTokenDetails")}
|
||||
isOpen={true}
|
||||
onClose={toggleDialog}
|
||||
variant={ModalVariant.medium}
|
||||
>
|
||||
<Alert
|
||||
title={t("copyInitialAccessToken")}
|
||||
isInline
|
||||
variant={AlertVariant.warning}
|
||||
/>
|
||||
<Form className="pf-u-mt-md">
|
||||
<FormGroup label={t("initialAccessToken")} fieldId="initialAccessToken">
|
||||
<ClipboardCopy id="initialAccessToken" isReadOnly>
|
||||
{token}
|
||||
</ClipboardCopy>
|
||||
</FormGroup>
|
||||
</Form>
|
||||
</Modal>
|
||||
);
|
||||
};
|
129
src/clients/initial-access/CreateInitialAccessToken.tsx
Normal file
129
src/clients/initial-access/CreateInitialAccessToken.tsx
Normal file
|
@ -0,0 +1,129 @@
|
|||
import React, { FormEvent, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Controller, useForm } from "react-hook-form";
|
||||
import {
|
||||
ActionGroup,
|
||||
AlertVariant,
|
||||
Button,
|
||||
FormGroup,
|
||||
NumberInput,
|
||||
PageSection,
|
||||
} from "@patternfly/react-core";
|
||||
|
||||
import ClientInitialAccessPresentation from "keycloak-admin/lib/defs/clientInitialAccessPresentation";
|
||||
import { FormAccess } from "../../components/form-access/FormAccess";
|
||||
import { ViewHeader } from "../../components/view-header/ViewHeader";
|
||||
import { HelpItem } from "../../components/help-enabler/HelpItem";
|
||||
import { TimeSelector } from "../../components/time-selector/TimeSelector";
|
||||
import { useHistory } from "react-router-dom";
|
||||
import { useRealm } from "../../context/realm-context/RealmContext";
|
||||
import { useAdminClient } from "../../context/auth/AdminClient";
|
||||
import { useAlerts } from "../../components/alert/Alerts";
|
||||
import { AccessTokenDialog } from "./AccessTokenDialog";
|
||||
|
||||
export const CreateInitialAccessToken = () => {
|
||||
const { t } = useTranslation("clients");
|
||||
const { handleSubmit, control } = useForm();
|
||||
|
||||
const adminClient = useAdminClient();
|
||||
const { realm } = useRealm();
|
||||
const { addAlert } = useAlerts();
|
||||
|
||||
const history = useHistory();
|
||||
const [token, setToken] = useState("");
|
||||
|
||||
const save = async (clientToken: ClientInitialAccessPresentation) => {
|
||||
try {
|
||||
const access = await adminClient.realms.createClientsInitialAccess(
|
||||
{ realm },
|
||||
clientToken
|
||||
);
|
||||
setToken(access.token);
|
||||
} catch (error) {
|
||||
addAlert(t("tokenSaveError", { error }), AlertVariant.danger);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
{token && (
|
||||
<AccessTokenDialog token={token} toggleDialog={() => setToken("")} />
|
||||
)}
|
||||
<ViewHeader
|
||||
titleKey="clients:createToken"
|
||||
subKey="clients-help:createToken"
|
||||
/>
|
||||
<PageSection variant="light">
|
||||
<FormAccess
|
||||
isHorizontal
|
||||
role="create-client"
|
||||
onSubmit={handleSubmit(save)}
|
||||
>
|
||||
<FormGroup
|
||||
label={t("expiration")}
|
||||
fieldId="expiration"
|
||||
labelIcon={
|
||||
<HelpItem
|
||||
helpText="clients-help:expiration"
|
||||
forLabel={t("expiration")}
|
||||
forID="expiration"
|
||||
/>
|
||||
}
|
||||
>
|
||||
<Controller
|
||||
name="expiration"
|
||||
defaultValue=""
|
||||
control={control}
|
||||
render={({ onChange, value }) => (
|
||||
<TimeSelector value={value} onChange={onChange} />
|
||||
)}
|
||||
/>
|
||||
</FormGroup>
|
||||
<FormGroup
|
||||
label={t("count")}
|
||||
fieldId="count"
|
||||
labelIcon={
|
||||
<HelpItem
|
||||
helpText="clients-help:count"
|
||||
forLabel={t("count")}
|
||||
forID="count"
|
||||
/>
|
||||
}
|
||||
>
|
||||
<Controller
|
||||
name="count"
|
||||
defaultValue={1}
|
||||
control={control}
|
||||
render={({ onChange, value }) => (
|
||||
<NumberInput
|
||||
inputName="count"
|
||||
inputAriaLabel={t("count")}
|
||||
min={1}
|
||||
value={value}
|
||||
onPlus={() => onChange(value + 1)}
|
||||
onMinus={() => onChange(value - 1)}
|
||||
onChange={(event) =>
|
||||
onChange(Number((event.target as HTMLInputElement).value))
|
||||
}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</FormGroup>
|
||||
<ActionGroup>
|
||||
<Button variant="primary" type="submit">
|
||||
{t("common:save")}
|
||||
</Button>
|
||||
<Button
|
||||
variant="link"
|
||||
onClick={() =>
|
||||
history.push(`/${realm}/clients/initialAccessToken`)
|
||||
}
|
||||
>
|
||||
{t("common:cancel")}
|
||||
</Button>
|
||||
</ActionGroup>
|
||||
</FormAccess>
|
||||
</PageSection>
|
||||
</>
|
||||
);
|
||||
};
|
61
src/clients/initial-access/InitialAccessTokenList.tsx
Normal file
61
src/clients/initial-access/InitialAccessTokenList.tsx
Normal file
|
@ -0,0 +1,61 @@
|
|||
import React from "react";
|
||||
import { useHistory, useRouteMatch } from "react-router-dom";
|
||||
import moment from "moment";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Button } from "@patternfly/react-core";
|
||||
|
||||
import { KeycloakDataTable } from "../../components/table-toolbar/KeycloakDataTable";
|
||||
import { useAdminClient } from "../../context/auth/AdminClient";
|
||||
import { useRealm } from "../../context/realm-context/RealmContext";
|
||||
|
||||
export const InitialAccessTokenList = () => {
|
||||
const { t } = useTranslation("clients");
|
||||
const adminClient = useAdminClient();
|
||||
const { realm } = useRealm();
|
||||
|
||||
const history = useHistory();
|
||||
const { url } = useRouteMatch();
|
||||
|
||||
const loader = async () =>
|
||||
await adminClient.realms.getClientsInitialAccess({ realm });
|
||||
|
||||
return (
|
||||
<KeycloakDataTable
|
||||
ariaLabelKey="clients:initialAccessToken"
|
||||
searchPlaceholderKey="clients:searchInitialAccessToken"
|
||||
loader={loader}
|
||||
toolbarItem={
|
||||
<>
|
||||
<Button onClick={() => history.push(`${url}/create`)}>
|
||||
{t("common:create")}
|
||||
</Button>
|
||||
</>
|
||||
}
|
||||
columns={[
|
||||
{
|
||||
name: "id",
|
||||
displayKey: "clients:id",
|
||||
},
|
||||
{
|
||||
name: "timestamp",
|
||||
displayKey: "clients:timestamp",
|
||||
cellRenderer: (row) => moment(row.timestamp * 1000).format("LLL"),
|
||||
},
|
||||
{
|
||||
name: "expiration",
|
||||
displayKey: "clients:expires",
|
||||
cellRenderer: (row) =>
|
||||
moment(row.timestamp * 1000 + row.expiration * 1000).fromNow(),
|
||||
},
|
||||
{
|
||||
name: "count",
|
||||
displayKey: "clients:count",
|
||||
},
|
||||
{
|
||||
name: "remainingCount",
|
||||
displayKey: "clients:remainingCount",
|
||||
},
|
||||
]}
|
||||
/>
|
||||
);
|
||||
};
|
|
@ -74,6 +74,16 @@
|
|||
"disableConfirm": "If you disable this client, you cannot initiate a login or obtain access tokens.",
|
||||
"clientDeleteConfirm": "If you delete this client, all associated data will be removed.",
|
||||
"searchInitialAccessToken": "Search token",
|
||||
"createToken": "Create initial access token",
|
||||
"id": "ID",
|
||||
"timestamp": "Created date",
|
||||
"expirs": "Expires",
|
||||
"count": "Count",
|
||||
"remainingCount": "Remaining count",
|
||||
"expiration": "Expiration",
|
||||
"tokenSaveError": "Could not create initial access token {{error}}",
|
||||
"initialAccessTokenDetails": "Initial access token details",
|
||||
"copyInitialAccessToken": "Please copy and paste the initial access token before closing as it can not be retrieved later.",
|
||||
"clientAuthentication": "Client authentication",
|
||||
"authentication": "Authentication",
|
||||
"authenticationFlow": "Authentication flow",
|
||||
|
|
|
@ -27,6 +27,7 @@ import { UserFederationLdapSettings } from "./user-federation/UserFederationLdap
|
|||
import { RoleMappingForm } from "./client-scopes/add/RoleMappingForm";
|
||||
import { RealmRoleTabs } from "./realm-roles/RealmRoleTabs";
|
||||
import { SearchGroups } from "./groups/SearchGroups";
|
||||
import { CreateInitialAccessToken } from "./clients/initial-access/CreateInitialAccessToken";
|
||||
|
||||
export type RouteDef = BreadcrumbsRoute & {
|
||||
access: AccessType;
|
||||
|
@ -54,6 +55,12 @@ export const routes: RoutesFn = (t: TFunction) => [
|
|||
breadcrumb: null,
|
||||
access: "query-clients",
|
||||
},
|
||||
{
|
||||
path: "/:realm/clients/initialAccessToken/create",
|
||||
component: CreateInitialAccessToken,
|
||||
breadcrumb: t("clients:createToken"),
|
||||
access: "manage-clients",
|
||||
},
|
||||
{
|
||||
path: "/:realm/clients/add-client",
|
||||
component: NewClientForm,
|
||||
|
|
Loading…
Reference in a new issue