From 587ae5ce81e3af83022c2e79d0eeb7e5c5225595 Mon Sep 17 00:00:00 2001 From: Erik Jan de Wit Date: Tue, 6 Oct 2020 21:25:05 +0200 Subject: [PATCH 01/14] changed alerts to have a provider (#137) fixes: #132 --- src/App.tsx | 28 ++- src/client-scopes/add/NewClientScopeForm.tsx | 10 +- src/clients/ClientList.tsx | 10 +- src/clients/ClientSettings.tsx | 3 +- .../__snapshots__/ClientList.test.tsx.snap | 5 - src/clients/add/NewClientForm.tsx | 7 +- src/clients/import/ImportForm.tsx | 7 +- src/components/alert/Alerts.tsx | 32 ++- .../alert/__tests__/Alerts.test.tsx | 11 +- .../__snapshots__/Alerts.test.tsx.snap | 223 +++++++----------- src/realm-roles/add/NewRoleForm.tsx | 3 +- src/realm/add/NewRealmForm.tsx | 10 +- src/stories/AlertPanel.stories.tsx | 13 +- 13 files changed, 153 insertions(+), 209 deletions(-) diff --git a/src/App.tsx b/src/App.tsx index fdc2c55f22..abda61c00e 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -8,7 +8,7 @@ import { Help } from "./components/help-enabler/HelpHeader"; import { RealmContextProvider } from "./context/realm-context/RealmContext"; import { WhoAmIContextProvider } from "./context/whoami/WhoAmI"; - +import { AlertProvider } from "./components/alert/Alerts"; import { routes } from "./route-config"; import { PageBreadCrumbs } from "./components/bread-crumb/PageBreadCrumbs"; @@ -18,18 +18,20 @@ export const App = () => { - } - isManagedSidebar - sidebar={} - breadcrumb={} - > - - {routes(() => {}).map((route, i) => ( - - ))} - - + + } + isManagedSidebar + sidebar={} + breadcrumb={} + > + + {routes(() => {}).map((route, i) => ( + + ))} + + + diff --git a/src/client-scopes/add/NewClientScopeForm.tsx b/src/client-scopes/add/NewClientScopeForm.tsx index 54e1545f52..3c1f7044c5 100644 --- a/src/client-scopes/add/NewClientScopeForm.tsx +++ b/src/client-scopes/add/NewClientScopeForm.tsx @@ -30,7 +30,7 @@ export const NewClientScopeForm = () => { const { realm } = useContext(RealmContext); const [open, isOpen] = useState(false); - const [add, Alerts] = useAlerts(); + const { addAlert } = useAlerts(); const save = async (clientScopes: ClientScopeRepresentation) => { try { @@ -44,15 +44,17 @@ export const NewClientScopeForm = () => { `/admin/realms/${realm}/client-scopes`, clientScopes ); - add(t("createClientScopeSuccess"), AlertVariant.success); + addAlert(t("createClientScopeSuccess"), AlertVariant.success); } catch (error) { - add(`${t("createClientScopeError")} '${error}'`, AlertVariant.danger); + addAlert( + `${t("createClientScopeError")} '${error}'`, + AlertVariant.danger + ); } }; return ( -
{ const { t } = useTranslation("clients"); const httpClient = useContext(HttpClientContext)!; const { realm } = useContext(RealmContext); - const [add, Alerts] = useAlerts(); + const { addAlert } = useAlerts(); const emptyFormatter = (): IFormatter => (data?: IFormatterValueType) => { return data ? data : "—"; @@ -85,7 +85,6 @@ export const ClientList = ({ baseUrl, clients, refresh }: ClientListProps) => { }); return ( <> - { `/admin/realms/${realm}/clients/${data[rowId].client.id}` ); refresh(); - add(t("clientDeletedSuccess"), AlertVariant.success); + addAlert(t("clientDeletedSuccess"), AlertVariant.success); } catch (error) { - add(`${t("clientDeleteError")} ${error}`, AlertVariant.danger); + addAlert( + `${t("clientDeleteError")} ${error}`, + AlertVariant.danger + ); } }, }, diff --git a/src/clients/ClientSettings.tsx b/src/clients/ClientSettings.tsx index 0bfb2fd15d..462916333e 100644 --- a/src/clients/ClientSettings.tsx +++ b/src/clients/ClientSettings.tsx @@ -36,7 +36,7 @@ export const ClientSettings = () => { const { t } = useTranslation("clients"); const httpClient = useContext(HttpClientContext)!; const { realm } = useContext(RealmContext); - const [addAlert, Alerts] = useAlerts(); + const { addAlert } = useAlerts(); const { id } = useParams<{ id: string }>(); const [name, setName] = useState(""); @@ -139,7 +139,6 @@ export const ClientSettings = () => { }} /> -
-
-
    -
, "container":
{ directAccessGrantsEnabled: false, standardFlowEnabled: false, }); - const [add, Alerts] = useAlerts(); + const { addAlert } = useAlerts(); const methods = useForm({ defaultValues: client }); const save = async () => { try { await httpClient.doPost(`/admin/realms/${realm}/clients`, client); - add("Client created", AlertVariant.success); + addAlert("Client created", AlertVariant.success); } catch (error) { - add(`Could not create client: '${error}'`, AlertVariant.danger); + addAlert(`Could not create client: '${error}'`, AlertVariant.danger); } }; @@ -93,7 +93,6 @@ export const NewClientForm = () => { const title = t("createClient"); return ( <> - { const form = useForm(); const { register, handleSubmit, setValue } = form; - const [add, Alerts] = useAlerts(); + const { addAlert } = useAlerts(); const handleFileChange = (value: string | File) => { const defaultClient = { @@ -45,14 +45,13 @@ export const ImportForm = () => { const save = async (client: ClientRepresentation) => { try { await httpClient.doPost(`/admin/realms/${realm}/clients`, client); - add(t("clientImportSuccess"), AlertVariant.success); + addAlert(t("clientImportSuccess"), AlertVariant.success); } catch (error) { - add(`${t("clientImportError")} '${error}'`, AlertVariant.danger); + addAlert(`${t("clientImportError")} '${error}'`, AlertVariant.danger); } }; return ( <> - void, - () => ReactElement, - (key: number) => void, - AlertType[] -] { +type AlertProps = { + addAlert: (message: string, variant?: AlertVariant) => void; +}; + +export const AlertContext = createContext({ + addAlert: () => {}, +}); + +export const useAlerts = () => useContext(AlertContext); + +export const AlertProvider = ({ children }: { children: ReactNode }) => { const [alerts, setAlerts] = useState([]); const createId = () => new Date().getTime(); @@ -15,7 +20,7 @@ export function useAlerts(): [ setAlerts((alerts) => [...alerts.filter((el) => el.key !== key)]); }; - const add = ( + const addAlert = ( message: string, variant: AlertVariant = AlertVariant.default ) => { @@ -24,7 +29,10 @@ export function useAlerts(): [ setTimeout(() => hideAlert(key), 8000); }; - const Panel = () => ; - - return [add, Panel, hideAlert, alerts]; -} + return ( + + + {children} + + ); +}; diff --git a/src/components/alert/__tests__/Alerts.test.tsx b/src/components/alert/__tests__/Alerts.test.tsx index 70e716f494..5a66b11296 100644 --- a/src/components/alert/__tests__/Alerts.test.tsx +++ b/src/components/alert/__tests__/Alerts.test.tsx @@ -4,17 +4,16 @@ import { mount } from "enzyme"; import { act } from "react-dom/test-utils"; import { AlertPanel } from "../AlertPanel"; -import { useAlerts } from "../Alerts"; +import { AlertProvider, useAlerts } from "../Alerts"; jest.useFakeTimers(); const WithButton = () => { - const [add, _, hide, alerts] = useAlerts(); + const { addAlert } = useAlerts(); return ( - <> - - - + + + ); }; diff --git a/src/components/alert/__tests__/__snapshots__/Alerts.test.tsx.snap b/src/components/alert/__tests__/__snapshots__/Alerts.test.tsx.snap index ef28412b6a..5f806bae2b 100644 --- a/src/components/alert/__tests__/__snapshots__/Alerts.test.tsx.snap +++ b/src/components/alert/__tests__/__snapshots__/Alerts.test.tsx.snap @@ -2,164 +2,101 @@ exports[`remove alert after timeout: cleared alert 1`] = ` - - + - -
    - - } + - +
      + + } > -
        - - - - - - + + + `; exports[`remove alert after timeout: with alert 1`] = ` - - + - -
          -
        • -
          -
          - -
          -

          - - Hello -

          -
          - -
          - -
          -
        • -
        - - } + - +
          + + } > -
            - - - - - - + + + `; diff --git a/src/realm-roles/add/NewRoleForm.tsx b/src/realm-roles/add/NewRoleForm.tsx index 495bd745bf..9d91b7694e 100644 --- a/src/realm-roles/add/NewRoleForm.tsx +++ b/src/realm-roles/add/NewRoleForm.tsx @@ -24,7 +24,7 @@ import { RealmContext } from "../../context/realm-context/RealmContext"; export const NewRoleForm = () => { const { t } = useTranslation("roles"); const httpClient = useContext(HttpClientContext)!; - const [addAlert, Alerts] = useAlerts(); + const { addAlert } = useAlerts(); const { realm } = useContext(RealmContext); const { register, control, errors, handleSubmit } = useForm< @@ -42,7 +42,6 @@ export const NewRoleForm = () => { return ( <> - {t("createRole")} diff --git a/src/realm/add/NewRealmForm.tsx b/src/realm/add/NewRealmForm.tsx index aab0203ce7..5b0594d9c0 100644 --- a/src/realm/add/NewRealmForm.tsx +++ b/src/realm/add/NewRealmForm.tsx @@ -21,7 +21,7 @@ import { ViewHeader } from "../../components/view-header/ViewHeader"; export const NewRealmForm = () => { const { t } = useTranslation("realm"); const httpClient = useContext(HttpClientContext)!; - const [add, Alerts] = useAlerts(); + const { addAlert } = useAlerts(); const { register, handleSubmit, setValue, control } = useForm< RealmRepresentation @@ -39,15 +39,17 @@ export const NewRealmForm = () => { const save = async (realm: RealmRepresentation) => { try { await httpClient.doPost("/admin/realms", realm); - add(t("Realm created"), AlertVariant.success); + addAlert(t("Realm created"), AlertVariant.success); } catch (error) { - add(`${t("Could not create realm:")} '${error}'`, AlertVariant.danger); + addAlert( + `${t("Could not create realm:")} '${error}'`, + AlertVariant.danger + ); } }; return ( <> - diff --git a/src/stories/AlertPanel.stories.tsx b/src/stories/AlertPanel.stories.tsx index e16874783c..d4bf79580f 100644 --- a/src/stories/AlertPanel.stories.tsx +++ b/src/stories/AlertPanel.stories.tsx @@ -3,7 +3,7 @@ import { AlertVariant, Button } from "@patternfly/react-core"; import { Meta } from "@storybook/react"; import { AlertPanel } from "../components/alert/AlertPanel"; -import { useAlerts } from "../components/alert/Alerts"; +import { AlertProvider, useAlerts } from "../components/alert/Alerts"; export default { title: "Alert Panel", @@ -17,11 +17,12 @@ export const Api = () => ( /> ); export const AddAlert = () => { - const [add, Alerts] = useAlerts(); + const { addAlert } = useAlerts(); return ( - <> - - - + + + ); }; From 908842542cce482e3c4a5e309830e1c66d0348ff Mon Sep 17 00:00:00 2001 From: Erik Jan de Wit Date: Tue, 6 Oct 2020 21:51:21 +0200 Subject: [PATCH 02/14] partial mocked download dialog (#140) * partial mocked download dialog * messages * added toggle * removed commened import --- src/clients/messages.json | 3 + src/common-messages.json | 1 + .../download-dialog/DownloadDialog.tsx | 180 ++++++++++++++++++ src/help.json | 4 + src/stories/DownloadDialog.stories.tsx | 28 +++ 5 files changed, 216 insertions(+) create mode 100644 src/components/download-dialog/DownloadDialog.tsx create mode 100644 src/stories/DownloadDialog.stories.tsx diff --git a/src/clients/messages.json b/src/clients/messages.json index c4c4ecb5a5..71f750000a 100644 --- a/src/clients/messages.json +++ b/src/clients/messages.json @@ -9,6 +9,9 @@ "homeURL": "Home URL", "description": "Description", "name": "Name", + "formatOption": "Format option", + "downloadAdaptorTitle": "Download adaptor configs", + "details": "Details", "clientList": "Client list", "clientSettings": "Client details", "generalSettings": "General Settings", diff --git a/src/common-messages.json b/src/common-messages.json index 437d08b41b..8a9a0618eb 100644 --- a/src/common-messages.json +++ b/src/common-messages.json @@ -12,6 +12,7 @@ "back": "Back", "export": "Export", "action": "Action", + "download": "Download", "resourceFile": "Resource file", "clearFile": "Clear this file", "on": "On", diff --git a/src/components/download-dialog/DownloadDialog.tsx b/src/components/download-dialog/DownloadDialog.tsx new file mode 100644 index 0000000000..0ac162ec61 --- /dev/null +++ b/src/components/download-dialog/DownloadDialog.tsx @@ -0,0 +1,180 @@ +import React, { useContext, useState, useEffect, ReactElement } from "react"; +import { + Alert, + AlertVariant, + Form, + FormGroup, + Select, + SelectOption, + SelectVariant, + Stack, + StackItem, + TextArea, +} from "@patternfly/react-core"; +import { ConfirmDialogModal } from "../confirm-dialog/ConfirmDialog"; +import { HttpClientContext } from "../../http-service/HttpClientContext"; +import { RealmContext } from "../realm-context/RealmContext"; +import { HelpItem } from "../help-enabler/HelpItem"; +import { useTranslation } from "react-i18next"; + +export type DownloadDialogProps = { + id: string; + protocol?: string; +}; + +type DownloadDialogModalProps = DownloadDialogProps & { + open: boolean; + toggleDialog: () => void; +}; + +const serverInfo = [ + { + id: "keycloak-oidc-jboss-subsystem-cli", + protocol: "openid-connect", + downloadOnly: false, + displayType: "Keycloak OIDC JBoss Subsystem CLI", + helpText: + "CLI script you must edit and apply to your client app server. This type of configuration is useful when you can't or don't want to crack open your WAR file.", + filename: "keycloak-oidc-subsystem.cli", + mediaType: "text/plain", + }, + { + id: "keycloak-oidc-jboss-subsystem", + protocol: "openid-connect", + downloadOnly: false, + displayType: "Keycloak OIDC JBoss Subsystem XML", + helpText: + "XML snippet you must edit and add to the Keycloak OIDC subsystem on your client app server. This type of configuration is useful when you can't or don't want to crack open your WAR file.", + filename: "keycloak-oidc-subsystem.xml", + mediaType: "application/xml", + }, + { + id: "keycloak-oidc-keycloak-json", + protocol: "openid-connect", + downloadOnly: false, + displayType: "Keycloak OIDC JSON", + helpText: + "keycloak.json file used by the Keycloak OIDC client adapter to configure clients. This must be saved to a keycloak.json file and put in your WEB-INF directory of your WAR file. You may also want to tweak this file after you download it.", + filename: "keycloak.json", + mediaType: "application/json", + }, +]; + +export const useDownloadDialog = ( + props: DownloadDialogProps +): [() => void, () => ReactElement] => { + const [show, setShow] = useState(false); + + function toggleDialog() { + setShow((show) => !show); + } + + const Dialog = () => ( + + ); + return [toggleDialog, Dialog]; +}; + +export const DownloadDialog = ({ + id, + open, + toggleDialog, + protocol = "openid-connect", +}: DownloadDialogModalProps) => { + const httpClient = useContext(HttpClientContext)!; + const { realm } = useContext(RealmContext); + const { t } = useTranslation("common"); + + const configFormats = serverInfo; //serverInfo.clientInstallations[protocol]; + const [selected, setSelected] = useState( + configFormats[configFormats.length - 1].id + ); + const [snippet, setSnippet] = useState(""); + const [openType, setOpenType] = useState(false); + + useEffect(() => { + (async () => { + const response = await httpClient.doGet( + `admin/${realm}/master/clients/${id}/installation/providers/${selected}` + ); + setSnippet(response.data!); + })(); + }, [selected]); + return ( + {}} + open={open} + toggleDialog={toggleDialog} + > + + + + + { + configFormats.find( + (configFormat) => configFormat.id === selected + )?.helpText + } + + + + } + > + + + + + } + > +