From 12fb2ef34b7a47dd25e0f7616e8bf16dd3bb3496 Mon Sep 17 00:00:00 2001 From: Erik Jan de Wit Date: Fri, 6 Oct 2023 08:02:14 +0200 Subject: [PATCH 1/8] POC to see how we could extend the UI This is very crude and there are still open issues that need to be worked out Signed-off-by: Erik Jan de Wit --- js/apps/admin-ui/src/App.tsx | 28 +++--- js/apps/admin-ui/src/PageNav.tsx | 12 ++- js/apps/admin-ui/src/page/Page.tsx | 88 +++++++++++++++++++ js/apps/admin-ui/src/page/routes.tsx | 24 +++++ js/apps/admin-ui/src/routes.tsx | 2 + .../admin/ui/rest/test/AdminUiPage.java | 67 ++++++++++++++ ...k.services.ui.extend.UiPageProviderFactory | 1 + .../services/ui/extend/UiPageProvider.java | 10 +++ .../ui/extend/UiPageProviderFactory.java | 6 ++ .../services/ui/extend/UiPageSpi.java | 27 ++++++ .../services/org.keycloak.provider.Spi | 1 + 11 files changed, 251 insertions(+), 15 deletions(-) create mode 100644 js/apps/admin-ui/src/page/Page.tsx create mode 100644 js/apps/admin-ui/src/page/routes.tsx create mode 100644 rest/admin-ui-ext/src/main/java/org/keycloak/admin/ui/rest/test/AdminUiPage.java create mode 100644 rest/admin-ui-ext/src/main/resources/META-INF/services/org.keycloak.services.ui.extend.UiPageProviderFactory create mode 100644 server-spi-private/src/main/java/org/keycloak/services/ui/extend/UiPageProvider.java create mode 100644 server-spi-private/src/main/java/org/keycloak/services/ui/extend/UiPageProviderFactory.java create mode 100644 server-spi-private/src/main/java/org/keycloak/services/ui/extend/UiPageSpi.java diff --git a/js/apps/admin-ui/src/App.tsx b/js/apps/admin-ui/src/App.tsx index a17a9d25ebd..6787cb47b06 100644 --- a/js/apps/admin-ui/src/App.tsx +++ b/js/apps/admin-ui/src/App.tsx @@ -44,24 +44,24 @@ const AppContexts = ({ children }: PropsWithChildren) => ( export const App = () => { return ( - - } - isManagedSidebar - sidebar={} - breadcrumb={} - mainContainerId={mainPageContentId} - > - - + + + } + isManagedSidebar + sidebar={} + breadcrumb={} + mainContainerId={mainPageContentId} + > + }> - - - - + + + + ); }; diff --git a/js/apps/admin-ui/src/PageNav.tsx b/js/apps/admin-ui/src/PageNav.tsx index af280269544..7214b374603 100644 --- a/js/apps/admin-ui/src/PageNav.tsx +++ b/js/apps/admin-ui/src/PageNav.tsx @@ -9,10 +9,10 @@ import { import { FormEvent } from "react"; import { useTranslation } from "react-i18next"; import { NavLink, useMatch, useNavigate } from "react-router-dom"; - import { RealmSelector } from "./components/realm-selector/RealmSelector"; import { useAccess } from "./context/access/Access"; import { useRealm } from "./context/realm-context/RealmContext"; +import { useServerInfo } from "./context/server-info/ServerInfoProvider"; import { AddRealmRoute } from "./realm/routes/AddRealm"; import { routes } from "./routes"; @@ -56,6 +56,9 @@ const LeftNav = ({ title, path }: LeftNavProps) => { export const PageNav = () => { const { t } = useTranslation(); const { hasSomeAccess } = useAccess(); + const { componentTypes } = useServerInfo(); + const pages = + componentTypes?.["org.keycloak.services.ui.extend.UiPageProvider"]; const navigate = useNavigate(); @@ -116,6 +119,13 @@ export const PageNav = () => { + {pages?.map((p) => ( + + ))} )} diff --git a/js/apps/admin-ui/src/page/Page.tsx b/js/apps/admin-ui/src/page/Page.tsx new file mode 100644 index 00000000000..e003180ff8d --- /dev/null +++ b/js/apps/admin-ui/src/page/Page.tsx @@ -0,0 +1,88 @@ +import ComponentRepresentation from "@keycloak/keycloak-admin-client/lib/defs/componentRepresentation"; +import ComponentTypeRepresentation from "@keycloak/keycloak-admin-client/lib/defs/componentTypeRepresentation"; +import RealmRepresentation from "@keycloak/keycloak-admin-client/lib/defs/realmRepresentation"; +import { ActionGroup, Button, Form, PageSection } from "@patternfly/react-core"; +import { useState } from "react"; +import { FormProvider, useForm } from "react-hook-form"; +import { useTranslation } from "react-i18next"; +import { Link, useParams } from "react-router-dom"; +import { useAlerts } from "../components/alert/Alerts"; +import { adminClient } from "../admin-client"; +import { DynamicComponents } from "../components/dynamic/DynamicComponents"; +import { ViewHeader } from "../components/view-header/ViewHeader"; +import { useRealm } from "../context/realm-context/RealmContext"; +import { useServerInfo } from "../context/server-info/ServerInfoProvider"; +import { useFetch } from "../utils/useFetch"; + +export default function Page() { + const { t } = useTranslation(); + const { componentTypes } = useServerInfo(); + const pages = + componentTypes?.["org.keycloak.services.ui.extend.UiPageProvider"]; + + // Here the pageId should be used instead + const page = pages?.[0]; + const form = useForm(); + const { realm: realmName } = useRealm(); + const { id } = useParams<{ id: string }>(); + const [realm, setRealm] = useState(); + const { addAlert, addError } = useAlerts(); + + useFetch( + async () => + await Promise.all([ + adminClient.realms.findOne({ realm: realmName }), + id ? adminClient.components.findOne({ id }) : Promise.resolve(), + ]), + ([realm, data]) => { + setRealm(realm); + form.reset(data || {}); + }, + [], + ); + + //This doesn't work, we want a different way to know what endpoint to call + const onSubmit = async (component: ComponentRepresentation) => { + try { + const updatedComponent = { + ...component, + providerId: "admin-ui-page", + providerType: "org.keycloak.services.ui.extend.UiPageProvider", + parentId: realm?.id, + }; + if (id) { + await adminClient.components.update({ id }, updatedComponent); + } else { + await adminClient.components.create(updatedComponent); + } + addAlert("Successful saved / updated"); + } catch (error) { + addError("Error: {{error}}", error); + } + }; + + return ( + + + +
+ + + + + + + + +
+
+
+ ); +} diff --git a/js/apps/admin-ui/src/page/routes.tsx b/js/apps/admin-ui/src/page/routes.tsx new file mode 100644 index 00000000000..250d3ae138a --- /dev/null +++ b/js/apps/admin-ui/src/page/routes.tsx @@ -0,0 +1,24 @@ +import { Path, generatePath } from "react-router-dom"; +import type { AppRouteObject } from "../routes"; +import { lazy } from "react"; + +export type PageParams = { pageId: string }; + +const Page = lazy(() => import("./Page")); + +const PageRoute: AppRouteObject = { + path: "/:realm/page", //:pageId + element: , + breadcrumb: (t) => t("page"), + handle: { + access: "view-realm", + }, +}; + +const routes: AppRouteObject[] = [PageRoute]; + +export const toPage = (params: PageParams): Partial => ({ + pathname: generatePath(PageRoute.path, params), +}); + +export default routes; diff --git a/js/apps/admin-ui/src/routes.tsx b/js/apps/admin-ui/src/routes.tsx index f71b74cae90..077d04842a0 100644 --- a/js/apps/admin-ui/src/routes.tsx +++ b/js/apps/admin-ui/src/routes.tsx @@ -18,6 +18,7 @@ import realmRoutes from "./realm/routes"; import sessionRoutes from "./sessions/routes"; import userFederationRoutes from "./user-federation/routes"; import userRoutes from "./user/routes"; +import pageRoutes from "./page/routes"; export type AppRouteObjectHandle = { access: AccessType | AccessType[]; @@ -51,6 +52,7 @@ export const routes: AppRouteObject[] = [ ...userRoutes, ...groupsRoutes, ...dashboardRoutes, + ...pageRoutes, NotFoundRoute, ]; diff --git a/rest/admin-ui-ext/src/main/java/org/keycloak/admin/ui/rest/test/AdminUiPage.java b/rest/admin-ui-ext/src/main/java/org/keycloak/admin/ui/rest/test/AdminUiPage.java new file mode 100644 index 00000000000..b53fbfc4cde --- /dev/null +++ b/rest/admin-ui-ext/src/main/java/org/keycloak/admin/ui/rest/test/AdminUiPage.java @@ -0,0 +1,67 @@ +package org.keycloak.admin.ui.rest.test; + +import org.keycloak.Config; +import org.keycloak.models.KeycloakSession; +import org.keycloak.models.KeycloakSessionFactory; +import org.keycloak.provider.ProviderConfigProperty; +import org.keycloak.provider.ProviderConfigurationBuilder; +import org.keycloak.services.ui.extend.UiPageProvider; +import org.keycloak.services.ui.extend.UiPageProviderFactory; + +import java.util.List; + +/** + * Test implementation this is should be removed. + */ +public class AdminUiPage implements UiPageProvider, UiPageProviderFactory { + @Override + public UiPageProvider create(KeycloakSession session) { + return this; + } + + @Override + public void init(Config.Scope config) { + + } + + @Override + public void postInit(KeycloakSessionFactory factory) { + + } + + @Override + public void close() { + + } + + @Override + public String getId() { + return "admin-ui-page"; + } + + @Override + public String getHelpText() { + return "Who needs help"; + } + + @Override + public List getConfigProperties() { + return ProviderConfigurationBuilder.create() + .property() + .name("test") + .label("Test attribute") + .type(ProviderConfigProperty.MULTIVALUED_STRING_TYPE) + .helpText("Array of test values") + .add().property() + .name("other") + .label("Other") + .type(ProviderConfigProperty.STRING_TYPE) + .helpText("Other field that you can edit") + .add().property() + .name("switch") + .label("Switch") + .type(ProviderConfigProperty.BOOLEAN_TYPE) + .helpText("This will be a switch") + .add().build(); + } +} diff --git a/rest/admin-ui-ext/src/main/resources/META-INF/services/org.keycloak.services.ui.extend.UiPageProviderFactory b/rest/admin-ui-ext/src/main/resources/META-INF/services/org.keycloak.services.ui.extend.UiPageProviderFactory new file mode 100644 index 00000000000..b40398c57bd --- /dev/null +++ b/rest/admin-ui-ext/src/main/resources/META-INF/services/org.keycloak.services.ui.extend.UiPageProviderFactory @@ -0,0 +1 @@ +org.keycloak.admin.ui.rest.test.AdminUiPage \ No newline at end of file diff --git a/server-spi-private/src/main/java/org/keycloak/services/ui/extend/UiPageProvider.java b/server-spi-private/src/main/java/org/keycloak/services/ui/extend/UiPageProvider.java new file mode 100644 index 00000000000..3b825228055 --- /dev/null +++ b/server-spi-private/src/main/java/org/keycloak/services/ui/extend/UiPageProvider.java @@ -0,0 +1,10 @@ +package org.keycloak.services.ui.extend; + +import org.keycloak.provider.ConfiguredProvider; +import org.keycloak.provider.Provider; + +import java.util.List; + +public interface UiPageProvider extends Provider, ConfiguredProvider { + +} diff --git a/server-spi-private/src/main/java/org/keycloak/services/ui/extend/UiPageProviderFactory.java b/server-spi-private/src/main/java/org/keycloak/services/ui/extend/UiPageProviderFactory.java new file mode 100644 index 00000000000..a1bfadc0173 --- /dev/null +++ b/server-spi-private/src/main/java/org/keycloak/services/ui/extend/UiPageProviderFactory.java @@ -0,0 +1,6 @@ +package org.keycloak.services.ui.extend; + +import org.keycloak.provider.ProviderFactory; + +public interface UiPageProviderFactory extends ProviderFactory { +} diff --git a/server-spi-private/src/main/java/org/keycloak/services/ui/extend/UiPageSpi.java b/server-spi-private/src/main/java/org/keycloak/services/ui/extend/UiPageSpi.java new file mode 100644 index 00000000000..1923f6496d1 --- /dev/null +++ b/server-spi-private/src/main/java/org/keycloak/services/ui/extend/UiPageSpi.java @@ -0,0 +1,27 @@ +package org.keycloak.services.ui.extend; + +import org.keycloak.provider.Provider; +import org.keycloak.provider.ProviderFactory; +import org.keycloak.provider.Spi; + +public class UiPageSpi implements Spi { + @Override + public boolean isInternal() { + return true; + } + + @Override + public String getName() { + return "ui-page"; + } + + @Override + public Class getProviderClass() { + return UiPageProvider.class; + } + + @Override + public Class getProviderFactoryClass() { + return UiPageProviderFactory.class; + } +} diff --git a/server-spi-private/src/main/resources/META-INF/services/org.keycloak.provider.Spi b/server-spi-private/src/main/resources/META-INF/services/org.keycloak.provider.Spi index 3a1a5cd941d..2f86a59b409 100755 --- a/server-spi-private/src/main/resources/META-INF/services/org.keycloak.provider.Spi +++ b/server-spi-private/src/main/resources/META-INF/services/org.keycloak.provider.Spi @@ -38,6 +38,7 @@ org.keycloak.scripting.ScriptingSpi org.keycloak.services.managers.BruteForceProtectorSpi org.keycloak.services.resource.AccountResourceSpi org.keycloak.services.resource.RealmResourceSPI +org.keycloak.services.ui.extend.UiPageSpi org.keycloak.sessions.AuthenticationSessionSpi org.keycloak.sessions.StickySessionEncoderSpi org.keycloak.protocol.ClientInstallationSpi From 3f8b8c4238ac760104882ce267b2f4c3627bba2b Mon Sep 17 00:00:00 2001 From: Erik Jan de Wit Date: Wed, 18 Oct 2023 16:55:52 +0200 Subject: [PATCH 2/8] added saving option Signed-off-by: Erik Jan de Wit --- js/apps/admin-ui/src/page/Page.tsx | 6 +++++- .../org/keycloak/admin/ui/rest/test/AdminUiPage.java | 12 +++++++++++- .../services/ui/extend/UiPageProviderFactory.java | 9 ++++++++- 3 files changed, 24 insertions(+), 3 deletions(-) diff --git a/js/apps/admin-ui/src/page/Page.tsx b/js/apps/admin-ui/src/page/Page.tsx index e003180ff8d..182c6ddff65 100644 --- a/js/apps/admin-ui/src/page/Page.tsx +++ b/js/apps/admin-ui/src/page/Page.tsx @@ -41,8 +41,12 @@ export default function Page() { [], ); - //This doesn't work, we want a different way to know what endpoint to call const onSubmit = async (component: ComponentRepresentation) => { + if (component.config) + Object.entries(component.config).forEach( + ([key, value]) => + (component.config![key] = Array.isArray(value) ? value : [value]), + ); try { const updatedComponent = { ...component, diff --git a/rest/admin-ui-ext/src/main/java/org/keycloak/admin/ui/rest/test/AdminUiPage.java b/rest/admin-ui-ext/src/main/java/org/keycloak/admin/ui/rest/test/AdminUiPage.java index b53fbfc4cde..420fe4e2e55 100644 --- a/rest/admin-ui-ext/src/main/java/org/keycloak/admin/ui/rest/test/AdminUiPage.java +++ b/rest/admin-ui-ext/src/main/java/org/keycloak/admin/ui/rest/test/AdminUiPage.java @@ -1,19 +1,29 @@ package org.keycloak.admin.ui.rest.test; import org.keycloak.Config; +import org.keycloak.component.ComponentModel; +import org.keycloak.component.ComponentValidationException; import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSessionFactory; +import org.keycloak.models.RealmModel; import org.keycloak.provider.ProviderConfigProperty; import org.keycloak.provider.ProviderConfigurationBuilder; import org.keycloak.services.ui.extend.UiPageProvider; import org.keycloak.services.ui.extend.UiPageProviderFactory; import java.util.List; +import java.util.Map; /** * Test implementation this is should be removed. */ -public class AdminUiPage implements UiPageProvider, UiPageProviderFactory { +public class AdminUiPage implements UiPageProvider, UiPageProviderFactory { + + @Override + public void onCreate(KeycloakSession session, RealmModel realm, ComponentModel model) { + System.out.println("save model = " + model); + } + @Override public UiPageProvider create(KeycloakSession session) { return this; diff --git a/server-spi-private/src/main/java/org/keycloak/services/ui/extend/UiPageProviderFactory.java b/server-spi-private/src/main/java/org/keycloak/services/ui/extend/UiPageProviderFactory.java index a1bfadc0173..9016dc173fc 100644 --- a/server-spi-private/src/main/java/org/keycloak/services/ui/extend/UiPageProviderFactory.java +++ b/server-spi-private/src/main/java/org/keycloak/services/ui/extend/UiPageProviderFactory.java @@ -1,6 +1,13 @@ package org.keycloak.services.ui.extend; +import org.keycloak.component.ComponentFactory; +import org.keycloak.component.ComponentModel; +import org.keycloak.models.KeycloakSession; import org.keycloak.provider.ProviderFactory; -public interface UiPageProviderFactory extends ProviderFactory { +public interface UiPageProviderFactory extends ComponentFactory { + default + public T create(KeycloakSession session, ComponentModel model) { + return null; + } } From b23c9be42fe2052345fe824e410545dc794be79b Mon Sep 17 00:00:00 2001 From: Erik Jan de Wit Date: Wed, 25 Oct 2023 13:45:35 +0200 Subject: [PATCH 3/8] added list and recreate client form Signed-off-by: Erik Jan de Wit --- js/apps/admin-ui/src/page/Page.tsx | 25 ++-- js/apps/admin-ui/src/page/PageList.tsx | 139 ++++++++++++++++++ js/apps/admin-ui/src/page/routes.tsx | 27 +++- .../admin/ui/rest/test/AdminUiPage.java | 55 +++++-- .../ui/extend/UiPageProviderFactory.java | 3 +- 5 files changed, 216 insertions(+), 33 deletions(-) create mode 100644 js/apps/admin-ui/src/page/PageList.tsx diff --git a/js/apps/admin-ui/src/page/Page.tsx b/js/apps/admin-ui/src/page/Page.tsx index 182c6ddff65..b56d11ed748 100644 --- a/js/apps/admin-ui/src/page/Page.tsx +++ b/js/apps/admin-ui/src/page/Page.tsx @@ -6,25 +6,25 @@ import { useState } from "react"; import { FormProvider, useForm } from "react-hook-form"; import { useTranslation } from "react-i18next"; import { Link, useParams } from "react-router-dom"; -import { useAlerts } from "../components/alert/Alerts"; import { adminClient } from "../admin-client"; +import { useAlerts } from "../components/alert/Alerts"; import { DynamicComponents } from "../components/dynamic/DynamicComponents"; import { ViewHeader } from "../components/view-header/ViewHeader"; import { useRealm } from "../context/realm-context/RealmContext"; import { useServerInfo } from "../context/server-info/ServerInfoProvider"; import { useFetch } from "../utils/useFetch"; +import { PAGE_PROVIDER } from "./PageList"; +import { PageParams, toPage } from "./routes"; export default function Page() { const { t } = useTranslation(); const { componentTypes } = useServerInfo(); - const pages = - componentTypes?.["org.keycloak.services.ui.extend.UiPageProvider"]; + const pages = componentTypes?.[PAGE_PROVIDER]; + const { id, providerId } = useParams(); - // Here the pageId should be used instead - const page = pages?.[0]; + const page = pages?.find((p) => p.id === providerId); const form = useForm(); const { realm: realmName } = useRealm(); - const { id } = useParams<{ id: string }>(); const [realm, setRealm] = useState(); const { addAlert, addError } = useAlerts(); @@ -50,8 +50,8 @@ export default function Page() { try { const updatedComponent = { ...component, - providerId: "admin-ui-page", - providerType: "org.keycloak.services.ui.extend.UiPageProvider", + providerId, + providerType: PAGE_PROVIDER, parentId: realm?.id, }; if (id) { @@ -67,7 +67,7 @@ export default function Page() { return ( - +
@@ -80,7 +80,12 @@ export default function Page() { diff --git a/js/apps/admin-ui/src/page/PageList.tsx b/js/apps/admin-ui/src/page/PageList.tsx new file mode 100644 index 00000000000..a86eb635f38 --- /dev/null +++ b/js/apps/admin-ui/src/page/PageList.tsx @@ -0,0 +1,139 @@ +import ComponentRepresentation from "@keycloak/keycloak-admin-client/lib/defs/componentRepresentation"; +import type RealmRepresentation from "@keycloak/keycloak-admin-client/lib/defs/realmRepresentation"; +import type { ComponentQuery } from "@keycloak/keycloak-admin-client/lib/resources/components"; +import { + Button, + ButtonVariant, + PageSection, + ToolbarItem, +} from "@patternfly/react-core"; +import { useState } from "react"; +import { Link, useNavigate } from "react-router-dom"; +import { adminClient } from "../admin-client"; +import { KeycloakDataTable } from "../components/table-toolbar/KeycloakDataTable"; +import { ViewHeader } from "../components/view-header/ViewHeader"; +import { useRealm } from "../context/realm-context/RealmContext"; +import { useServerInfo } from "../context/server-info/ServerInfoProvider"; +import { useFetch } from "../utils/useFetch"; +import { toDetailPage } from "./routes"; +import { useTranslation } from "react-i18next"; +import { useConfirmDialog } from "../components/confirm-dialog/ConfirmDialog"; +import { useAlerts } from "../components/alert/Alerts"; +import { IRowData } from "@patternfly/react-table"; +import { ListEmptyState } from "../components/list-empty-state/ListEmptyState"; + +export const PAGE_PROVIDER = "org.keycloak.services.ui.extend.UiPageProvider"; + +const DetailLink = (obj: ComponentRepresentation) => { + const { realm } = useRealm(); + return ( + + {obj.id} + + ); +}; +export default function PageList() { + const { t } = useTranslation(); + const { addAlert, addError } = useAlerts(); + const navigate = useNavigate(); + const [key, setKey] = useState(0); + const refresh = () => setKey(key + 1); + + const { realm: realmName } = useRealm(); + const [realm, setRealm] = useState(); + const [selectedItem, setSelectedItem] = useState(); + const { componentTypes } = useServerInfo(); + const pages = componentTypes?.[PAGE_PROVIDER]; + + // Here the providerId should be used instead + const page = pages?.[0]!; + + useFetch( + async () => adminClient.realms.findOne({ realm: realmName }), + setRealm, + [], + ); + + const loader = async () => { + const params: ComponentQuery = { + parent: realm?.id, + type: PAGE_PROVIDER, + }; + return await adminClient.components.find({ ...params }); + }; + + const [toggleDeleteDialog, DeleteConfirm] = useConfirmDialog({ + titleKey: "itemDeleteConfirmTitle", + messageKey: "itemDeleteConfirm", + continueButtonLabel: "delete", + continueButtonVariant: ButtonVariant.danger, + onConfirm: async () => { + try { + await adminClient.components.del({ + id: selectedItem!.id!, + }); + addAlert(t("itemDeletedSuccess")); + refresh(); + } catch (error) { + addError("clientDeleteError", error); + } + }, + }); + + return ( + + + + + + + } + actionResolver={(item: IRowData) => [ + { + title: t("delete"), + onClick() { + setSelectedItem(item.data); + toggleDeleteDialog(); + }, + }, + ]} + searchPlaceholderKey="searchItem" + loader={loader} + columns={[ + { name: "id", cellRenderer: DetailLink }, + ...page.properties.slice(0, 3).map((p) => ({ + name: `config.${p.name}[0]`, + displayKey: p.name, + })), + ]} + ariaLabelKey="list" + emptyState={ + + navigate(toDetailPage({ realm: realmName, providerId: page.id })) + } + /> + } + /> + + ); +} diff --git a/js/apps/admin-ui/src/page/routes.tsx b/js/apps/admin-ui/src/page/routes.tsx index 250d3ae138a..1903e6b04c8 100644 --- a/js/apps/admin-ui/src/page/routes.tsx +++ b/js/apps/admin-ui/src/page/routes.tsx @@ -2,12 +2,23 @@ import { Path, generatePath } from "react-router-dom"; import type { AppRouteObject } from "../routes"; import { lazy } from "react"; -export type PageParams = { pageId: string }; +export type PageListParams = { realm: string; providerId: string }; +export type PageParams = { realm: string; providerId: string; id?: string }; +const PageList = lazy(() => import("./PageList")); const Page = lazy(() => import("./Page")); -const PageRoute: AppRouteObject = { - path: "/:realm/page", //:pageId +const PageListRoute: AppRouteObject = { + path: "/:realm/page", //:providerId + element: , + breadcrumb: (t) => t("page"), + handle: { + access: "view-realm", + }, +}; + +const PageDetailRoute: AppRouteObject = { + path: "/:realm/page/:providerId/:id?", element: , breadcrumb: (t) => t("page"), handle: { @@ -15,10 +26,14 @@ const PageRoute: AppRouteObject = { }, }; -const routes: AppRouteObject[] = [PageRoute]; +const routes: AppRouteObject[] = [PageListRoute, PageDetailRoute]; + +export const toPage = (params: PageListParams): Partial => ({ + pathname: generatePath(PageListRoute.path, params), +}); -export const toPage = (params: PageParams): Partial => ({ - pathname: generatePath(PageRoute.path, params), +export const toDetailPage = (params: PageParams): Partial => ({ + pathname: generatePath(PageDetailRoute.path, params), }); export default routes; diff --git a/rest/admin-ui-ext/src/main/java/org/keycloak/admin/ui/rest/test/AdminUiPage.java b/rest/admin-ui-ext/src/main/java/org/keycloak/admin/ui/rest/test/AdminUiPage.java index 420fe4e2e55..77d14d62270 100644 --- a/rest/admin-ui-ext/src/main/java/org/keycloak/admin/ui/rest/test/AdminUiPage.java +++ b/rest/admin-ui-ext/src/main/java/org/keycloak/admin/ui/rest/test/AdminUiPage.java @@ -2,7 +2,6 @@ import org.keycloak.Config; import org.keycloak.component.ComponentModel; -import org.keycloak.component.ComponentValidationException; import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSessionFactory; import org.keycloak.models.RealmModel; @@ -12,7 +11,6 @@ import org.keycloak.services.ui.extend.UiPageProviderFactory; import java.util.List; -import java.util.Map; /** * Test implementation this is should be removed. @@ -21,7 +19,7 @@ public class AdminUiPage implements UiPageProvider, UiPageProviderFactory getConfigProperties() { return ProviderConfigurationBuilder.create() .property() - .name("test") - .label("Test attribute") - .type(ProviderConfigProperty.MULTIVALUED_STRING_TYPE) - .helpText("Array of test values") + .name("clientId") + .label("Client ID") + .helpText("The client identifier registered with the identity provider.") + .type(ProviderConfigProperty.STRING_TYPE) + .add().property() + .name("name") + .label("Name") + .helpText("Specifies display name of the client. For example 'My Client'. Supports keys for localized values as well. For example: ${my_client}") + .type(ProviderConfigProperty.STRING_TYPE) .add().property() - .name("other") - .label("Other") + .name("description") + .label("Description") + .helpText("Specifies description of the client. For example 'My Client for TimeSheets'. Supports keys for localized values as well. For example: ${my_client_description}") .type(ProviderConfigProperty.STRING_TYPE) - .helpText("Other field that you can edit") .add().property() - .name("switch") - .label("Switch") + .name("alwaysDisplayInUI") + .label("Always display in UI") + .helpText("Always list this client in the Account UI, even if the user does not have an active session.") .type(ProviderConfigProperty.BOOLEAN_TYPE) - .helpText("This will be a switch") + .add().property() + .name("rootURL") + .label("Root URL") + .helpText("Always list this client in the Account UI, even if the user does not have an active session.") + .type(ProviderConfigProperty.STRING_TYPE) + .add().property() + .name("homeURL") + .label("Home URL") + .helpText("Default URL to use when the auth server needs to redirect or link back to the client.") + .type(ProviderConfigProperty.STRING_TYPE) + .add().property() + .name("validRedirectURIs") + .label("Valid redirect URIs") + .helpText("Default URL to use when the auth server needs to redirect or link back to the client.") + .type(ProviderConfigProperty.MULTIVALUED_STRING_TYPE) + .add().property() + .name("flow") + .label("Authentication flow") + .type(ProviderConfigProperty.MULTIVALUED_LIST_TYPE) + .options("Standard flow", "Implicit flow", "OAuth 2.0 Device Authorization Grant", "OIDC CIBA Grant", "Direct access grants", "Service accounts roles" + + ) .add().build(); } } diff --git a/server-spi-private/src/main/java/org/keycloak/services/ui/extend/UiPageProviderFactory.java b/server-spi-private/src/main/java/org/keycloak/services/ui/extend/UiPageProviderFactory.java index 9016dc173fc..cbd931642a2 100644 --- a/server-spi-private/src/main/java/org/keycloak/services/ui/extend/UiPageProviderFactory.java +++ b/server-spi-private/src/main/java/org/keycloak/services/ui/extend/UiPageProviderFactory.java @@ -6,8 +6,7 @@ import org.keycloak.provider.ProviderFactory; public interface UiPageProviderFactory extends ComponentFactory { - default - public T create(KeycloakSession session, ComponentModel model) { + default T create(KeycloakSession session, ComponentModel model) { return null; } } From d462e3752eb17a010fc0a3a2796407843fae9b8d Mon Sep 17 00:00:00 2001 From: Erik Jan de Wit Date: Fri, 27 Oct 2023 11:47:24 +0200 Subject: [PATCH 4/8] add tab ui Signed-off-by: Erik Jan de Wit --- .../admin/messages/messages_en.properties | 12 +++- .../admin/ui/rest/test/ThemeUiTab.java | 65 +++++++++++++++++++ ...ak.services.ui.extend.UiTabProviderFactory | 1 + .../services/ui/extend/UiTabProvider.java | 8 +++ .../ui/extend/UiTabProviderFactory.java | 11 ++++ .../keycloak/services/ui/extend/UiTabSpi.java | 27 ++++++++ .../services/org.keycloak.provider.Spi | 1 + 7 files changed, 124 insertions(+), 1 deletion(-) create mode 100644 rest/admin-ui-ext/src/main/java/org/keycloak/admin/ui/rest/test/ThemeUiTab.java create mode 100644 rest/admin-ui-ext/src/main/resources/META-INF/services/org.keycloak.services.ui.extend.UiTabProviderFactory create mode 100644 server-spi-private/src/main/java/org/keycloak/services/ui/extend/UiTabProvider.java create mode 100644 server-spi-private/src/main/java/org/keycloak/services/ui/extend/UiTabProviderFactory.java create mode 100644 server-spi-private/src/main/java/org/keycloak/services/ui/extend/UiTabSpi.java diff --git a/js/apps/admin-ui/maven-resources/theme/keycloak.v2/admin/messages/messages_en.properties b/js/apps/admin-ui/maven-resources/theme/keycloak.v2/admin/messages/messages_en.properties index f9b3d237593..86d952a7f0f 100644 --- a/js/apps/admin-ui/maven-resources/theme/keycloak.v2/admin/messages/messages_en.properties +++ b/js/apps/admin-ui/maven-resources/theme/keycloak.v2/admin/messages/messages_en.properties @@ -2986,4 +2986,14 @@ customValue=Custom value termsAndConditionsUserAttribute=Terms and conditions accepted timestamp realmOverridesDescription= Realm overrides allow you to specify translations that will take effect for the entire realm. These translations will override any translation specified by a theme. addTranslation=Add translation -effectiveMessageBundlesDescription=An effective message bundle is the set of translations for a given language, theme, and theme type. It also takes into account any realm overrides, which will take precedence. \ No newline at end of file +effectiveMessageBundlesDescription=An effective message bundle is the set of translations for a given language, theme, and theme type. It also takes into account any realm overrides, which will take precedence. +clientsClientScopesHelp=The scopes associated with this resource. +searchItem=Search item +createItem=Create item +itemDelete=Delete item +itemDeleteConfirm=Are you sure you want to permanently delete the item +itemDeleteConfirmTitle=Delete item? +itemDeletedSuccess=The item has been deleted +itemDeleteError=Could not delete item: {{error}} +noItems=There are no items +noItemsInstructions=You haven't created any items in this realm. Create a item to get started. \ No newline at end of file diff --git a/rest/admin-ui-ext/src/main/java/org/keycloak/admin/ui/rest/test/ThemeUiTab.java b/rest/admin-ui-ext/src/main/java/org/keycloak/admin/ui/rest/test/ThemeUiTab.java new file mode 100644 index 00000000000..56183a2fec5 --- /dev/null +++ b/rest/admin-ui-ext/src/main/java/org/keycloak/admin/ui/rest/test/ThemeUiTab.java @@ -0,0 +1,65 @@ +package org.keycloak.admin.ui.rest.test; + +import org.keycloak.Config; +import org.keycloak.component.ComponentModel; +import org.keycloak.models.KeycloakSession; +import org.keycloak.models.KeycloakSessionFactory; +import org.keycloak.provider.ProviderConfigProperty; +import org.keycloak.provider.ProviderConfigurationBuilder; +import org.keycloak.provider.ProviderFactory; +import org.keycloak.services.ui.extend.UiPageProvider; +import org.keycloak.services.ui.extend.UiTabProvider; +import org.keycloak.services.ui.extend.UiTabProviderFactory; +import org.keycloak.theme.Theme; +import org.keycloak.theme.ThemeProvider; +import org.keycloak.theme.ThemeProviderFactory; +import org.keycloak.theme.ThemeSelectorProvider; + +import java.io.IOException; +import java.util.List; + +public class ThemeUiTab implements UiPageProvider, UiTabProviderFactory { + + private KeycloakSession session; + + @Override + public String getId() { + return "Themes"; + } + + @Override + public String getHelpText() { + return null; + } + + @Override + public void init(Config.Scope config) { + } + + @Override + public void postInit(KeycloakSessionFactory factory) { + System.out.println("factory = " + factory); + } + + @Override + public void close() { + + } + + @Override + public List getConfigProperties() { + final ProviderConfigurationBuilder builder = ProviderConfigurationBuilder.create(); +// try { +// final Theme theme = session.theme().getTheme(Theme.Type.LOGIN.name(), Theme.Type.LOGIN); + builder.property() + .name("loginTheme") + .helpText("Select theme for login, OTP, grant, registration and forgot password pages.") + .add(); + //.options(theme.) + +// } catch (IOException e) { +// throw new RuntimeException(e); +// } + return builder.build(); + } +} diff --git a/rest/admin-ui-ext/src/main/resources/META-INF/services/org.keycloak.services.ui.extend.UiTabProviderFactory b/rest/admin-ui-ext/src/main/resources/META-INF/services/org.keycloak.services.ui.extend.UiTabProviderFactory new file mode 100644 index 00000000000..c6c57737378 --- /dev/null +++ b/rest/admin-ui-ext/src/main/resources/META-INF/services/org.keycloak.services.ui.extend.UiTabProviderFactory @@ -0,0 +1 @@ +org.keycloak.admin.ui.rest.test.ThemeUiTab \ No newline at end of file diff --git a/server-spi-private/src/main/java/org/keycloak/services/ui/extend/UiTabProvider.java b/server-spi-private/src/main/java/org/keycloak/services/ui/extend/UiTabProvider.java new file mode 100644 index 00000000000..480a8d04899 --- /dev/null +++ b/server-spi-private/src/main/java/org/keycloak/services/ui/extend/UiTabProvider.java @@ -0,0 +1,8 @@ +package org.keycloak.services.ui.extend; + +import org.keycloak.provider.ConfiguredProvider; +import org.keycloak.provider.Provider; + +public interface UiTabProvider extends Provider, ConfiguredProvider { + +} diff --git a/server-spi-private/src/main/java/org/keycloak/services/ui/extend/UiTabProviderFactory.java b/server-spi-private/src/main/java/org/keycloak/services/ui/extend/UiTabProviderFactory.java new file mode 100644 index 00000000000..f456e096b09 --- /dev/null +++ b/server-spi-private/src/main/java/org/keycloak/services/ui/extend/UiTabProviderFactory.java @@ -0,0 +1,11 @@ +package org.keycloak.services.ui.extend; + +import org.keycloak.component.ComponentFactory; +import org.keycloak.component.ComponentModel; +import org.keycloak.models.KeycloakSession; + +public interface UiTabProviderFactory extends ComponentFactory { + default T create(KeycloakSession session, ComponentModel model) { + return null; + } +} diff --git a/server-spi-private/src/main/java/org/keycloak/services/ui/extend/UiTabSpi.java b/server-spi-private/src/main/java/org/keycloak/services/ui/extend/UiTabSpi.java new file mode 100644 index 00000000000..4d73a4a11ea --- /dev/null +++ b/server-spi-private/src/main/java/org/keycloak/services/ui/extend/UiTabSpi.java @@ -0,0 +1,27 @@ +package org.keycloak.services.ui.extend; + +import org.keycloak.provider.Provider; +import org.keycloak.provider.ProviderFactory; +import org.keycloak.provider.Spi; + +public class UiTabSpi implements Spi { + @Override + public boolean isInternal() { + return true; + } + + @Override + public String getName() { + return "ui-tab"; + } + + @Override + public Class getProviderClass() { + return UiTabProvider.class; + } + + @Override + public Class getProviderFactoryClass() { + return UiTabProviderFactory.class; + } +} diff --git a/server-spi-private/src/main/resources/META-INF/services/org.keycloak.provider.Spi b/server-spi-private/src/main/resources/META-INF/services/org.keycloak.provider.Spi index 2f86a59b409..5d26788ba9e 100755 --- a/server-spi-private/src/main/resources/META-INF/services/org.keycloak.provider.Spi +++ b/server-spi-private/src/main/resources/META-INF/services/org.keycloak.provider.Spi @@ -39,6 +39,7 @@ org.keycloak.services.managers.BruteForceProtectorSpi org.keycloak.services.resource.AccountResourceSpi org.keycloak.services.resource.RealmResourceSPI org.keycloak.services.ui.extend.UiPageSpi +org.keycloak.services.ui.extend.UiTabSpi org.keycloak.sessions.AuthenticationSessionSpi org.keycloak.sessions.StickySessionEncoderSpi org.keycloak.protocol.ClientInstallationSpi From ade3257a916844d63d58553e62699edc262cf259 Mon Sep 17 00:00:00 2001 From: Erik Jan de Wit Date: Tue, 12 Dec 2023 16:47:23 +0100 Subject: [PATCH 5/8] integrate tabs Signed-off-by: Erik Jan de Wit --- js/apps/admin-ui/src/PageNav.tsx | 11 +- .../components/routable-tabs/RoutableTabs.tsx | 60 +++++++++- js/apps/admin-ui/src/page/Page.tsx | 89 ++------------- js/apps/admin-ui/src/page/PageHandler.tsx | 103 ++++++++++++++++++ js/apps/admin-ui/src/page/PageList.tsx | 19 ++-- js/apps/admin-ui/src/page/routes.tsx | 4 +- .../admin/ui/rest/test/ThemeUiTab.java | 19 +++- .../ui/extend/UiTabProviderFactory.java | 15 +++ 8 files changed, 220 insertions(+), 100 deletions(-) create mode 100644 js/apps/admin-ui/src/page/PageHandler.tsx diff --git a/js/apps/admin-ui/src/PageNav.tsx b/js/apps/admin-ui/src/PageNav.tsx index 7214b374603..57b51179642 100644 --- a/js/apps/admin-ui/src/PageNav.tsx +++ b/js/apps/admin-ui/src/PageNav.tsx @@ -13,19 +13,21 @@ import { RealmSelector } from "./components/realm-selector/RealmSelector"; import { useAccess } from "./context/access/Access"; import { useRealm } from "./context/realm-context/RealmContext"; import { useServerInfo } from "./context/server-info/ServerInfoProvider"; +import { toPage } from "./page/routes"; import { AddRealmRoute } from "./realm/routes/AddRealm"; import { routes } from "./routes"; import "./page-nav.css"; -type LeftNavProps = { title: string; path: string }; +type LeftNavProps = { title: string; path: string; id?: string }; -const LeftNav = ({ title, path }: LeftNavProps) => { +const LeftNav = ({ title, path, id }: LeftNavProps) => { const { t } = useTranslation(); const { hasAccess } = useAccess(); const { realm } = useRealm(); const route = routes.find( - (route) => route.path.replace(/\/:.+?(\?|(?:(?!\/).)*|$)/g, "") === path, + (route) => + route.path.replace(/\/:.+?(\?|(?:(?!\/).)*|$)/g, "") === id || path, ); const accessAllowed = @@ -123,7 +125,8 @@ export const PageNav = () => { ))} diff --git a/js/apps/admin-ui/src/components/routable-tabs/RoutableTabs.tsx b/js/apps/admin-ui/src/components/routable-tabs/RoutableTabs.tsx index e84dcd3712e..57e4fc6b166 100644 --- a/js/apps/admin-ui/src/components/routable-tabs/RoutableTabs.tsx +++ b/js/apps/admin-ui/src/components/routable-tabs/RoutableTabs.tsx @@ -1,4 +1,5 @@ import { + Tab, TabProps, Tabs, TabsComponent, @@ -6,11 +7,22 @@ import { } from "@patternfly/react-core"; import { Children, - isValidElement, JSXElementConstructor, + PropsWithChildren, ReactElement, + isValidElement, } from "react"; -import { Path, useHref, useLocation } from "react-router-dom"; +import { + Path, + generatePath, + matchPath, + useHref, + useLocation, + useParams, +} from "react-router-dom"; +import { useServerInfo } from "../../context/server-info/ServerInfoProvider"; +import { PageHandler } from "../../page/PageHandler"; +import { TAB_PROVIDER } from "../../page/PageList"; // TODO: Remove the custom 'children' props and type once the following issue has been resolved: // https://github.com/patternfly/patternfly-react/issues/6766 @@ -32,14 +44,31 @@ export const RoutableTabs = ({ ...otherProps }: RoutableTabsProps) => { const { pathname } = useLocation(); + const params = useParams(); + const { componentTypes } = useServerInfo(); + const tabs = componentTypes?.[TAB_PROVIDER] || []; + + const matchedTabs = tabs + .filter((tab) => matchPath({ path: tab.metadata.path }, pathname)) + .map((t) => ({ + ...t, + pathname: generatePath(t.metadata.path, { + ...params, + ...t.metadata.params, + }), + })); + // Extract all keys from matchedTabs + const matchedTabsKeys = matchedTabs.map((t) => t.pathname); - // Extract event keys from children. + // Extract event keys from children const eventKeys = Children.toArray(children) .filter((child): child is ChildElement => isValidElement(child)) .map((child) => child.props.eventKey.toString()); + const allKeys = [...eventKeys, ...matchedTabsKeys]; + // Determine if there is an exact match. - const exactMatch = eventKeys.find( + const exactMatch = allKeys.find( (eventKey) => eventKey === decodeURI(pathname), ); @@ -63,10 +92,33 @@ export const RoutableTabs = ({ {...otherProps} > {children} + {matchedTabs.map((t) => ( + + + + ))} ); }; +type DynamicTabProps = { + title: string; + eventKey: string; +}; + +const DynamicTab = ({ + children, + ...props +}: PropsWithChildren) => { + const href = useHref(props.eventKey); + + return ( + + {children} + + ); +}; + export const useRoutableTab = (to: Partial) => ({ eventKey: to.pathname ?? "", href: useHref(to), diff --git a/js/apps/admin-ui/src/page/Page.tsx b/js/apps/admin-ui/src/page/Page.tsx index b56d11ed748..30a80a26d67 100644 --- a/js/apps/admin-ui/src/page/Page.tsx +++ b/js/apps/admin-ui/src/page/Page.tsx @@ -1,20 +1,10 @@ -import ComponentRepresentation from "@keycloak/keycloak-admin-client/lib/defs/componentRepresentation"; -import ComponentTypeRepresentation from "@keycloak/keycloak-admin-client/lib/defs/componentTypeRepresentation"; -import RealmRepresentation from "@keycloak/keycloak-admin-client/lib/defs/realmRepresentation"; -import { ActionGroup, Button, Form, PageSection } from "@patternfly/react-core"; -import { useState } from "react"; -import { FormProvider, useForm } from "react-hook-form"; import { useTranslation } from "react-i18next"; -import { Link, useParams } from "react-router-dom"; -import { adminClient } from "../admin-client"; -import { useAlerts } from "../components/alert/Alerts"; -import { DynamicComponents } from "../components/dynamic/DynamicComponents"; +import { useParams } from "react-router-dom"; import { ViewHeader } from "../components/view-header/ViewHeader"; -import { useRealm } from "../context/realm-context/RealmContext"; import { useServerInfo } from "../context/server-info/ServerInfoProvider"; -import { useFetch } from "../utils/useFetch"; +import { PageHandler } from "./PageHandler"; import { PAGE_PROVIDER } from "./PageList"; -import { PageParams, toPage } from "./routes"; +import { PageParams } from "./routes"; export default function Page() { const { t } = useTranslation(); @@ -23,75 +13,14 @@ export default function Page() { const { id, providerId } = useParams(); const page = pages?.find((p) => p.id === providerId); - const form = useForm(); - const { realm: realmName } = useRealm(); - const [realm, setRealm] = useState(); - const { addAlert, addError } = useAlerts(); - - useFetch( - async () => - await Promise.all([ - adminClient.realms.findOne({ realm: realmName }), - id ? adminClient.components.findOne({ id }) : Promise.resolve(), - ]), - ([realm, data]) => { - setRealm(realm); - form.reset(data || {}); - }, - [], - ); - - const onSubmit = async (component: ComponentRepresentation) => { - if (component.config) - Object.entries(component.config).forEach( - ([key, value]) => - (component.config![key] = Array.isArray(value) ? value : [value]), - ); - try { - const updatedComponent = { - ...component, - providerId, - providerType: PAGE_PROVIDER, - parentId: realm?.id, - }; - if (id) { - await adminClient.components.update({ id }, updatedComponent); - } else { - await adminClient.components.create(updatedComponent); - } - addAlert("Successful saved / updated"); - } catch (error) { - addError("Error: {{error}}", error); - } - }; + if (!page) { + throw new Error(t("notFound")); + } return ( - + <> - - - - - - - - - - - - - + + ); } diff --git a/js/apps/admin-ui/src/page/PageHandler.tsx b/js/apps/admin-ui/src/page/PageHandler.tsx new file mode 100644 index 00000000000..ef9a5c2736b --- /dev/null +++ b/js/apps/admin-ui/src/page/PageHandler.tsx @@ -0,0 +1,103 @@ +import ComponentRepresentation from "@keycloak/keycloak-admin-client/lib/defs/componentRepresentation"; +import ComponentTypeRepresentation from "@keycloak/keycloak-admin-client/lib/defs/componentTypeRepresentation"; +import RealmRepresentation from "@keycloak/keycloak-admin-client/lib/defs/realmRepresentation"; +import { ActionGroup, Button, Form, PageSection } from "@patternfly/react-core"; +import { useState } from "react"; +import { FormProvider, useForm } from "react-hook-form"; +import { useTranslation } from "react-i18next"; +import { Link } from "react-router-dom"; +import { adminClient } from "../admin-client"; +import { useAlerts } from "../components/alert/Alerts"; +import { DynamicComponents } from "../components/dynamic/DynamicComponents"; +import { useRealm } from "../context/realm-context/RealmContext"; +import { useFetch } from "../utils/useFetch"; +import { PAGE_PROVIDER, TAB_PROVIDER } from "./PageList"; +import { toPage } from "./routes"; + +type PageHandlerProps = { + id?: string; + providerType: typeof TAB_PROVIDER | typeof PAGE_PROVIDER; + page: ComponentTypeRepresentation; +}; + +export const PageHandler = ({ + id: idAttribute, + providerType, + page: { id: providerId, ...page }, +}: PageHandlerProps) => { + const { t } = useTranslation(); + const form = useForm(); + const { realm: realmName } = useRealm(); + const [realm, setRealm] = useState(); + const { addAlert, addError } = useAlerts(); + const [id, setId] = useState(idAttribute); + + useFetch( + async () => + await Promise.all([ + adminClient.realms.findOne({ realm: realmName }), + id ? adminClient.components.findOne({ id }) : Promise.resolve(), + providerType === TAB_PROVIDER + ? adminClient.components.find({ type: TAB_PROVIDER }) + : Promise.resolve(), + ]), + ([realm, data, tabs]) => { + setRealm(realm); + const tab = (tabs || []).find((t) => t.providerId === providerId); + form.reset(data || tab || {}); + if (tab) setId(tab.id); + }, + [], + ); + + const onSubmit = async (component: ComponentRepresentation) => { + if (component.config) + Object.entries(component.config).forEach( + ([key, value]) => + (component.config![key] = Array.isArray(value) ? value : [value]), + ); + try { + const updatedComponent = { + ...component, + providerId, + providerType, + parentId: realm?.id, + }; + if (id) { + await adminClient.components.update({ id }, updatedComponent); + } else { + await adminClient.components.create(updatedComponent); + } + addAlert("Successful saved / updated"); + } catch (error) { + addError("Error: {{error}}", error); + } + }; + + return ( + +
+ + + + + + + + +
+
+ ); +}; diff --git a/js/apps/admin-ui/src/page/PageList.tsx b/js/apps/admin-ui/src/page/PageList.tsx index a86eb635f38..e23ec0351cf 100644 --- a/js/apps/admin-ui/src/page/PageList.tsx +++ b/js/apps/admin-ui/src/page/PageList.tsx @@ -7,22 +7,23 @@ import { PageSection, ToolbarItem, } from "@patternfly/react-core"; +import { IRowData } from "@patternfly/react-table"; import { useState } from "react"; -import { Link, useNavigate } from "react-router-dom"; +import { useTranslation } from "react-i18next"; +import { Link, useNavigate, useParams } from "react-router-dom"; import { adminClient } from "../admin-client"; +import { useAlerts } from "../components/alert/Alerts"; +import { useConfirmDialog } from "../components/confirm-dialog/ConfirmDialog"; +import { ListEmptyState } from "../components/list-empty-state/ListEmptyState"; import { KeycloakDataTable } from "../components/table-toolbar/KeycloakDataTable"; import { ViewHeader } from "../components/view-header/ViewHeader"; import { useRealm } from "../context/realm-context/RealmContext"; import { useServerInfo } from "../context/server-info/ServerInfoProvider"; import { useFetch } from "../utils/useFetch"; -import { toDetailPage } from "./routes"; -import { useTranslation } from "react-i18next"; -import { useConfirmDialog } from "../components/confirm-dialog/ConfirmDialog"; -import { useAlerts } from "../components/alert/Alerts"; -import { IRowData } from "@patternfly/react-table"; -import { ListEmptyState } from "../components/list-empty-state/ListEmptyState"; +import { PageListParams, toDetailPage } from "./routes"; export const PAGE_PROVIDER = "org.keycloak.services.ui.extend.UiPageProvider"; +export const TAB_PROVIDER = "org.keycloak.services.ui.extend.UiTabProvider"; const DetailLink = (obj: ComponentRepresentation) => { const { realm } = useRealm(); @@ -39,6 +40,7 @@ export default function PageList() { const { t } = useTranslation(); const { addAlert, addError } = useAlerts(); const navigate = useNavigate(); + const { providerId } = useParams(); const [key, setKey] = useState(0); const refresh = () => setKey(key + 1); @@ -48,8 +50,7 @@ export default function PageList() { const { componentTypes } = useServerInfo(); const pages = componentTypes?.[PAGE_PROVIDER]; - // Here the providerId should be used instead - const page = pages?.[0]!; + const page = pages?.find((p) => p.id === providerId)!; useFetch( async () => adminClient.realms.findOne({ realm: realmName }), diff --git a/js/apps/admin-ui/src/page/routes.tsx b/js/apps/admin-ui/src/page/routes.tsx index 1903e6b04c8..0ffb86e383d 100644 --- a/js/apps/admin-ui/src/page/routes.tsx +++ b/js/apps/admin-ui/src/page/routes.tsx @@ -2,14 +2,14 @@ import { Path, generatePath } from "react-router-dom"; import type { AppRouteObject } from "../routes"; import { lazy } from "react"; -export type PageListParams = { realm: string; providerId: string }; +export type PageListParams = { realm?: string; providerId: string }; export type PageParams = { realm: string; providerId: string; id?: string }; const PageList = lazy(() => import("./PageList")); const Page = lazy(() => import("./Page")); const PageListRoute: AppRouteObject = { - path: "/:realm/page", //:providerId + path: "/:realm?/page-section/:providerId", element: , breadcrumb: (t) => t("page"), handle: { diff --git a/rest/admin-ui-ext/src/main/java/org/keycloak/admin/ui/rest/test/ThemeUiTab.java b/rest/admin-ui-ext/src/main/java/org/keycloak/admin/ui/rest/test/ThemeUiTab.java index 56183a2fec5..1cf861b88ae 100644 --- a/rest/admin-ui-ext/src/main/java/org/keycloak/admin/ui/rest/test/ThemeUiTab.java +++ b/rest/admin-ui-ext/src/main/java/org/keycloak/admin/ui/rest/test/ThemeUiTab.java @@ -10,15 +10,18 @@ import org.keycloak.services.ui.extend.UiPageProvider; import org.keycloak.services.ui.extend.UiTabProvider; import org.keycloak.services.ui.extend.UiTabProviderFactory; +import org.keycloak.storage.user.ImportSynchronization; import org.keycloak.theme.Theme; import org.keycloak.theme.ThemeProvider; import org.keycloak.theme.ThemeProviderFactory; import org.keycloak.theme.ThemeSelectorProvider; import java.io.IOException; +import java.util.HashMap; import java.util.List; +import java.util.Map; -public class ThemeUiTab implements UiPageProvider, UiTabProviderFactory { +public class ThemeUiTab implements UiTabProvider, UiTabProviderFactory { private KeycloakSession session; @@ -53,7 +56,9 @@ public List getConfigProperties() { // final Theme theme = session.theme().getTheme(Theme.Type.LOGIN.name(), Theme.Type.LOGIN); builder.property() .name("loginTheme") + .label("Select a theme") .helpText("Select theme for login, OTP, grant, registration and forgot password pages.") + .type(ProviderConfigProperty.STRING_TYPE) .add(); //.options(theme.) @@ -62,4 +67,16 @@ public List getConfigProperties() { // } return builder.build(); } + + @Override + public String getPath() { + return "/:realm/clients/:clientId/:tab"; + } + + @Override + public Map getParams() { + Map params = new HashMap<>(); + params.put("tab", "theme"); + return params; + } } diff --git a/server-spi-private/src/main/java/org/keycloak/services/ui/extend/UiTabProviderFactory.java b/server-spi-private/src/main/java/org/keycloak/services/ui/extend/UiTabProviderFactory.java index f456e096b09..6587192081e 100644 --- a/server-spi-private/src/main/java/org/keycloak/services/ui/extend/UiTabProviderFactory.java +++ b/server-spi-private/src/main/java/org/keycloak/services/ui/extend/UiTabProviderFactory.java @@ -4,8 +4,23 @@ import org.keycloak.component.ComponentModel; import org.keycloak.models.KeycloakSession; +import java.util.HashMap; +import java.util.Map; + public interface UiTabProviderFactory extends ComponentFactory { default T create(KeycloakSession session, ComponentModel model) { return null; } + + @Override + default Map getTypeMetadata() { + Map metadata = new HashMap<>(); + metadata.put("path", getPath()); + metadata.put("params", getParams()); + return metadata; + } + + String getPath(); + + Map getParams(); } From 7899270589a52085fa2b0a6325db23bfe6683051 Mon Sep 17 00:00:00 2001 From: Erik Jan de Wit Date: Thu, 14 Dec 2023 14:17:37 +0100 Subject: [PATCH 6/8] remove examples Signed-off-by: Erik Jan de Wit --- .../admin/ui/rest/test/AdminUiPage.java | 102 ------------------ .../admin/ui/rest/test/ThemeUiTab.java | 82 -------------- 2 files changed, 184 deletions(-) delete mode 100644 rest/admin-ui-ext/src/main/java/org/keycloak/admin/ui/rest/test/AdminUiPage.java delete mode 100644 rest/admin-ui-ext/src/main/java/org/keycloak/admin/ui/rest/test/ThemeUiTab.java diff --git a/rest/admin-ui-ext/src/main/java/org/keycloak/admin/ui/rest/test/AdminUiPage.java b/rest/admin-ui-ext/src/main/java/org/keycloak/admin/ui/rest/test/AdminUiPage.java deleted file mode 100644 index 77d14d62270..00000000000 --- a/rest/admin-ui-ext/src/main/java/org/keycloak/admin/ui/rest/test/AdminUiPage.java +++ /dev/null @@ -1,102 +0,0 @@ -package org.keycloak.admin.ui.rest.test; - -import org.keycloak.Config; -import org.keycloak.component.ComponentModel; -import org.keycloak.models.KeycloakSession; -import org.keycloak.models.KeycloakSessionFactory; -import org.keycloak.models.RealmModel; -import org.keycloak.provider.ProviderConfigProperty; -import org.keycloak.provider.ProviderConfigurationBuilder; -import org.keycloak.services.ui.extend.UiPageProvider; -import org.keycloak.services.ui.extend.UiPageProviderFactory; - -import java.util.List; - -/** - * Test implementation this is should be removed. - */ -public class AdminUiPage implements UiPageProvider, UiPageProviderFactory { - - @Override - public void onCreate(KeycloakSession session, RealmModel realm, ComponentModel model) { - System.out.println("Extra logic on create"); - } - - @Override - public UiPageProvider create(KeycloakSession session) { - return this; - } - - @Override - public void init(Config.Scope config) { - - } - - @Override - public void postInit(KeycloakSessionFactory factory) { - - } - - @Override - public void close() { - - } - - @Override - public String getId() { - return "Clients"; - } - - @Override - public String getHelpText() { - return "Clients are applications and services that can request authentication of a user."; - } - - @Override - public List getConfigProperties() { - return ProviderConfigurationBuilder.create() - .property() - .name("clientId") - .label("Client ID") - .helpText("The client identifier registered with the identity provider.") - .type(ProviderConfigProperty.STRING_TYPE) - .add().property() - .name("name") - .label("Name") - .helpText("Specifies display name of the client. For example 'My Client'. Supports keys for localized values as well. For example: ${my_client}") - .type(ProviderConfigProperty.STRING_TYPE) - .add().property() - .name("description") - .label("Description") - .helpText("Specifies description of the client. For example 'My Client for TimeSheets'. Supports keys for localized values as well. For example: ${my_client_description}") - .type(ProviderConfigProperty.STRING_TYPE) - .add().property() - .name("alwaysDisplayInUI") - .label("Always display in UI") - .helpText("Always list this client in the Account UI, even if the user does not have an active session.") - .type(ProviderConfigProperty.BOOLEAN_TYPE) - .add().property() - .name("rootURL") - .label("Root URL") - .helpText("Always list this client in the Account UI, even if the user does not have an active session.") - .type(ProviderConfigProperty.STRING_TYPE) - .add().property() - .name("homeURL") - .label("Home URL") - .helpText("Default URL to use when the auth server needs to redirect or link back to the client.") - .type(ProviderConfigProperty.STRING_TYPE) - .add().property() - .name("validRedirectURIs") - .label("Valid redirect URIs") - .helpText("Default URL to use when the auth server needs to redirect or link back to the client.") - .type(ProviderConfigProperty.MULTIVALUED_STRING_TYPE) - .add().property() - .name("flow") - .label("Authentication flow") - .type(ProviderConfigProperty.MULTIVALUED_LIST_TYPE) - .options("Standard flow", "Implicit flow", "OAuth 2.0 Device Authorization Grant", "OIDC CIBA Grant", "Direct access grants", "Service accounts roles" - - ) - .add().build(); - } -} diff --git a/rest/admin-ui-ext/src/main/java/org/keycloak/admin/ui/rest/test/ThemeUiTab.java b/rest/admin-ui-ext/src/main/java/org/keycloak/admin/ui/rest/test/ThemeUiTab.java deleted file mode 100644 index 1cf861b88ae..00000000000 --- a/rest/admin-ui-ext/src/main/java/org/keycloak/admin/ui/rest/test/ThemeUiTab.java +++ /dev/null @@ -1,82 +0,0 @@ -package org.keycloak.admin.ui.rest.test; - -import org.keycloak.Config; -import org.keycloak.component.ComponentModel; -import org.keycloak.models.KeycloakSession; -import org.keycloak.models.KeycloakSessionFactory; -import org.keycloak.provider.ProviderConfigProperty; -import org.keycloak.provider.ProviderConfigurationBuilder; -import org.keycloak.provider.ProviderFactory; -import org.keycloak.services.ui.extend.UiPageProvider; -import org.keycloak.services.ui.extend.UiTabProvider; -import org.keycloak.services.ui.extend.UiTabProviderFactory; -import org.keycloak.storage.user.ImportSynchronization; -import org.keycloak.theme.Theme; -import org.keycloak.theme.ThemeProvider; -import org.keycloak.theme.ThemeProviderFactory; -import org.keycloak.theme.ThemeSelectorProvider; - -import java.io.IOException; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -public class ThemeUiTab implements UiTabProvider, UiTabProviderFactory { - - private KeycloakSession session; - - @Override - public String getId() { - return "Themes"; - } - - @Override - public String getHelpText() { - return null; - } - - @Override - public void init(Config.Scope config) { - } - - @Override - public void postInit(KeycloakSessionFactory factory) { - System.out.println("factory = " + factory); - } - - @Override - public void close() { - - } - - @Override - public List getConfigProperties() { - final ProviderConfigurationBuilder builder = ProviderConfigurationBuilder.create(); -// try { -// final Theme theme = session.theme().getTheme(Theme.Type.LOGIN.name(), Theme.Type.LOGIN); - builder.property() - .name("loginTheme") - .label("Select a theme") - .helpText("Select theme for login, OTP, grant, registration and forgot password pages.") - .type(ProviderConfigProperty.STRING_TYPE) - .add(); - //.options(theme.) - -// } catch (IOException e) { -// throw new RuntimeException(e); -// } - return builder.build(); - } - - @Override - public String getPath() { - return "/:realm/clients/:clientId/:tab"; - } - - @Override - public Map getParams() { - Map params = new HashMap<>(); - params.put("tab", "theme"); - return params; - } -} From 09af6e6683e212e6b59a0a76fa7f9c08f82f72df Mon Sep 17 00:00:00 2001 From: Erik Jan de Wit Date: Fri, 15 Dec 2023 11:05:11 +0100 Subject: [PATCH 7/8] fixed error messages Signed-off-by: Erik Jan de Wit --- .../admin/messages/messages_en.properties | 4 +- js/apps/admin-ui/src/App.tsx | 68 +++++++++---------- js/apps/admin-ui/src/PageNav.tsx | 2 +- js/apps/admin-ui/src/page/Page.tsx | 47 ++++++++++++- js/apps/admin-ui/src/page/PageHandler.tsx | 10 ++- js/apps/admin-ui/src/page/PageList.tsx | 4 +- ...k.services.ui.extend.UiPageProviderFactory | 1 - ...ak.services.ui.extend.UiTabProviderFactory | 1 - 8 files changed, 91 insertions(+), 46 deletions(-) delete mode 100644 rest/admin-ui-ext/src/main/resources/META-INF/services/org.keycloak.services.ui.extend.UiPageProviderFactory delete mode 100644 rest/admin-ui-ext/src/main/resources/META-INF/services/org.keycloak.services.ui.extend.UiTabProviderFactory diff --git a/js/apps/admin-ui/maven-resources/theme/keycloak.v2/admin/messages/messages_en.properties b/js/apps/admin-ui/maven-resources/theme/keycloak.v2/admin/messages/messages_en.properties index 86d952a7f0f..860ebf6c437 100644 --- a/js/apps/admin-ui/maven-resources/theme/keycloak.v2/admin/messages/messages_en.properties +++ b/js/apps/admin-ui/maven-resources/theme/keycloak.v2/admin/messages/messages_en.properties @@ -2996,4 +2996,6 @@ itemDeleteConfirmTitle=Delete item? itemDeletedSuccess=The item has been deleted itemDeleteError=Could not delete item: {{error}} noItems=There are no items -noItemsInstructions=You haven't created any items in this realm. Create a item to get started. \ No newline at end of file +noItemsInstructions=You haven't created any items in this realm. Create a item to get started. +itemSaveError=Error could not save item\! {{error}} +itemSaveSuccessful=Sucessful saved \ No newline at end of file diff --git a/js/apps/admin-ui/src/App.tsx b/js/apps/admin-ui/src/App.tsx index 6787cb47b06..af0202c3fa9 100644 --- a/js/apps/admin-ui/src/App.tsx +++ b/js/apps/admin-ui/src/App.tsx @@ -24,44 +24,44 @@ import { AuthWall } from "./root/AuthWall"; const AppContexts = ({ children }: PropsWithChildren) => ( - - - - - - - - {children} - - - - - - - + + + + + + + + + {children} + + + + + + + + ); export const App = () => { return ( - - - } - isManagedSidebar - sidebar={} - breadcrumb={} - mainContainerId={mainPageContentId} - > - - }> - - - - - - - - + + } + isManagedSidebar + sidebar={} + breadcrumb={} + mainContainerId={mainPageContentId} + > + + }> + + + + + + + ); }; diff --git a/js/apps/admin-ui/src/PageNav.tsx b/js/apps/admin-ui/src/PageNav.tsx index 57b51179642..eb169fd251d 100644 --- a/js/apps/admin-ui/src/PageNav.tsx +++ b/js/apps/admin-ui/src/PageNav.tsx @@ -27,7 +27,7 @@ const LeftNav = ({ title, path, id }: LeftNavProps) => { const { realm } = useRealm(); const route = routes.find( (route) => - route.path.replace(/\/:.+?(\?|(?:(?!\/).)*|$)/g, "") === id || path, + route.path.replace(/\/:.+?(\?|(?:(?!\/).)*|$)/g, "") === (id || path), ); const accessAllowed = diff --git a/js/apps/admin-ui/src/page/Page.tsx b/js/apps/admin-ui/src/page/Page.tsx index 30a80a26d67..b58be173557 100644 --- a/js/apps/admin-ui/src/page/Page.tsx +++ b/js/apps/admin-ui/src/page/Page.tsx @@ -1,25 +1,66 @@ +import { ButtonVariant, DropdownItem } from "@patternfly/react-core"; import { useTranslation } from "react-i18next"; -import { useParams } from "react-router-dom"; +import { useNavigate, useParams } from "react-router-dom"; +import { adminClient } from "../admin-client"; +import { useAlerts } from "../components/alert/Alerts"; +import { useConfirmDialog } from "../components/confirm-dialog/ConfirmDialog"; import { ViewHeader } from "../components/view-header/ViewHeader"; import { useServerInfo } from "../context/server-info/ServerInfoProvider"; import { PageHandler } from "./PageHandler"; import { PAGE_PROVIDER } from "./PageList"; -import { PageParams } from "./routes"; +import { PageParams, toPage } from "./routes"; +import { useRealm } from "../context/realm-context/RealmContext"; export default function Page() { const { t } = useTranslation(); const { componentTypes } = useServerInfo(); + const { realm } = useRealm(); const pages = componentTypes?.[PAGE_PROVIDER]; + const navigate = useNavigate(); const { id, providerId } = useParams(); + const { addAlert, addError } = useAlerts(); const page = pages?.find((p) => p.id === providerId); if (!page) { throw new Error(t("notFound")); } + const [toggleDeleteDialog, DeleteConfirm] = useConfirmDialog({ + titleKey: "itemDeleteConfirmTitle", + messageKey: "itemDeleteConfirm", + continueButtonLabel: "delete", + continueButtonVariant: ButtonVariant.danger, + onConfirm: async () => { + try { + await adminClient.components.del({ + id: id!, + }); + addAlert(t("itemDeletedSuccess")); + navigate(toPage({ realm, providerId: providerId! })); + } catch (error) { + addError("itemSaveError", error); + } + }, + }); return ( <> - + + toggleDeleteDialog()} + > + {t("delete")} + , + ] + : undefined + } + /> ); diff --git a/js/apps/admin-ui/src/page/PageHandler.tsx b/js/apps/admin-ui/src/page/PageHandler.tsx index ef9a5c2736b..1d4bfe8f7f5 100644 --- a/js/apps/admin-ui/src/page/PageHandler.tsx +++ b/js/apps/admin-ui/src/page/PageHandler.tsx @@ -68,15 +68,19 @@ export const PageHandler = ({ } else { await adminClient.components.create(updatedComponent); } - addAlert("Successful saved / updated"); + addAlert("itemSaveSuccessful"); } catch (error) { - addError("Error: {{error}}", error); + addError("itemSaveError", error); } }; return ( -
+ diff --git a/js/apps/admin-ui/src/page/PageList.tsx b/js/apps/admin-ui/src/page/PageList.tsx index e23ec0351cf..056a1deecec 100644 --- a/js/apps/admin-ui/src/page/PageList.tsx +++ b/js/apps/admin-ui/src/page/PageList.tsx @@ -79,7 +79,7 @@ export default function PageList() { addAlert(t("itemDeletedSuccess")); refresh(); } catch (error) { - addError("clientDeleteError", error); + addError("itemSaveError", error); } }, }); @@ -119,7 +119,7 @@ export default function PageList() { { name: "id", cellRenderer: DetailLink }, ...page.properties.slice(0, 3).map((p) => ({ name: `config.${p.name}[0]`, - displayKey: p.name, + displayKey: p.label, })), ]} ariaLabelKey="list" diff --git a/rest/admin-ui-ext/src/main/resources/META-INF/services/org.keycloak.services.ui.extend.UiPageProviderFactory b/rest/admin-ui-ext/src/main/resources/META-INF/services/org.keycloak.services.ui.extend.UiPageProviderFactory deleted file mode 100644 index b40398c57bd..00000000000 --- a/rest/admin-ui-ext/src/main/resources/META-INF/services/org.keycloak.services.ui.extend.UiPageProviderFactory +++ /dev/null @@ -1 +0,0 @@ -org.keycloak.admin.ui.rest.test.AdminUiPage \ No newline at end of file diff --git a/rest/admin-ui-ext/src/main/resources/META-INF/services/org.keycloak.services.ui.extend.UiTabProviderFactory b/rest/admin-ui-ext/src/main/resources/META-INF/services/org.keycloak.services.ui.extend.UiTabProviderFactory deleted file mode 100644 index c6c57737378..00000000000 --- a/rest/admin-ui-ext/src/main/resources/META-INF/services/org.keycloak.services.ui.extend.UiTabProviderFactory +++ /dev/null @@ -1 +0,0 @@ -org.keycloak.admin.ui.rest.test.ThemeUiTab \ No newline at end of file From 4d146d2b899f6a9d5d71cfdf2fea2a918a95e344 Mon Sep 17 00:00:00 2001 From: Erik Jan de Wit Date: Tue, 16 Jan 2024 15:21:05 +0100 Subject: [PATCH 8/8] added Feature for ui customization Signed-off-by: Erik Jan de Wit --- .../java/org/keycloak/common/Profile.java | 3 +++ .../java/org/keycloak/common/ProfileTest.java | 1 + ...ndDistTest.testBuildHelp.unix.approved.txt | 17 +++++++------- ...istTest.testBuildHelp.windows.approved.txt | 17 +++++++------- ...dDistTest.testExportHelp.unix.approved.txt | 17 +++++++------- ...stTest.testExportHelpAll.unix.approved.txt | 17 +++++++------- ...dDistTest.testImportHelp.unix.approved.txt | 17 +++++++------- ...stTest.testImportHelpAll.unix.approved.txt | 17 +++++++------- ...istTest.testStartDevHelp.unix.approved.txt | 17 +++++++------- ...Test.testStartDevHelp.windows.approved.txt | 23 ++++++++++--------- ...Test.testStartDevHelpAll.unix.approved.txt | 17 +++++++------- ...t.testStartDevHelpAll.windows.approved.txt | 23 ++++++++++--------- ...ndDistTest.testStartHelp.unix.approved.txt | 17 +++++++------- ...istTest.testStartHelp.windows.approved.txt | 23 ++++++++++--------- ...istTest.testStartHelpAll.unix.approved.txt | 17 +++++++------- ...Test.testStartHelpAll.windows.approved.txt | 23 ++++++++++--------- .../services/ui/extend/UiPageSpi.java | 6 +++++ .../keycloak/services/ui/extend/UiTabSpi.java | 6 +++++ 18 files changed, 154 insertions(+), 124 deletions(-) diff --git a/common/src/main/java/org/keycloak/common/Profile.java b/common/src/main/java/org/keycloak/common/Profile.java index 5b5a1ea9fdb..57f41ca7785 100755 --- a/common/src/main/java/org/keycloak/common/Profile.java +++ b/common/src/main/java/org/keycloak/common/Profile.java @@ -105,8 +105,11 @@ public enum Feature { MULTI_SITE("Multi-site support", Type.PREVIEW), OFFLINE_SESSION_PRELOADING("Offline session preloading", Type.DEPRECATED), + HOSTNAME_V1("Hostname Options V1", Type.DEFAULT), //HOSTNAME_V2("Hostname Options V2", Type.DEFAULT, 2), + + DECLARATIVE_UI("declarative ui spi", Type.EXPERIMENTAL), ; private final Type type; diff --git a/common/src/test/java/org/keycloak/common/ProfileTest.java b/common/src/test/java/org/keycloak/common/ProfileTest.java index d58c99e7fa1..0e09657edf9 100644 --- a/common/src/test/java/org/keycloak/common/ProfileTest.java +++ b/common/src/test/java/org/keycloak/common/ProfileTest.java @@ -78,6 +78,7 @@ public void checkDefaults() { Profile.Feature.DYNAMIC_SCOPES, Profile.Feature.DOCKER, Profile.Feature.MULTI_SITE, + Profile.Feature.DECLARATIVE_UI, Profile.Feature.RECOVERY_CODES, Profile.Feature.SCRIPTS, Profile.Feature.TOKEN_EXCHANGE, diff --git a/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testBuildHelp.unix.approved.txt b/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testBuildHelp.unix.approved.txt index 8b90686ac8d..c965b912fdd 100644 --- a/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testBuildHelp.unix.approved.txt +++ b/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testBuildHelp.unix.approved.txt @@ -64,20 +64,21 @@ Feature: --features Enables a set of one or more features. Possible values are: account-api[:v1], account2[:v1], account3[:v1], admin-api[:v1], admin-fine-grained-authz[:v1], admin2[:v1], authorization[:v1], ciba[:v1], client-policies[:v1], - client-secret-rotation[:v1], device-flow[:v1], docker[:v1], dpop[:v1], - dynamic-scopes[:v1], fips[:v1], hostname[:v1], impersonation[:v1], js-adapter - [:v1], kerberos[:v1], linkedin-oauth[:v1], multi-site[:v1], + client-secret-rotation[:v1], declarative-ui[:v1], device-flow[:v1], docker[: + v1], dpop[:v1], dynamic-scopes[:v1], fips[:v1], hostname[:v1], impersonation + [:v1], js-adapter[:v1], kerberos[:v1], linkedin-oauth[:v1], multi-site[:v1], offline-session-preloading[:v1], par[:v1], preview, recovery-codes[:v1], scripts[:v1], step-up-authentication[:v1], token-exchange[:v1], transient-users[:v1], update-email[:v1], web-authn[:v1]. --features-disabled Disables a set of one or more features. Possible values are: account-api, account2, account3, admin-api, admin-fine-grained-authz, admin2, - authorization, ciba, client-policies, client-secret-rotation, device-flow, - docker, dpop, dynamic-scopes, fips, impersonation, js-adapter, kerberos, - linkedin-oauth, multi-site, offline-session-preloading, par, preview, - recovery-codes, scripts, step-up-authentication, token-exchange, - transient-users, update-email, web-authn. + authorization, ciba, client-policies, client-secret-rotation, + declarative-ui, device-flow, docker, dpop, dynamic-scopes, fips, + impersonation, js-adapter, kerberos, linkedin-oauth, multi-site, + offline-session-preloading, par, preview, recovery-codes, scripts, + step-up-authentication, token-exchange, transient-users, update-email, + web-authn. HTTP(S): diff --git a/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testBuildHelp.windows.approved.txt b/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testBuildHelp.windows.approved.txt index ebcf0dbd3ab..c9cb3c5b9d8 100644 --- a/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testBuildHelp.windows.approved.txt +++ b/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testBuildHelp.windows.approved.txt @@ -64,20 +64,21 @@ Feature: --features Enables a set of one or more features. Possible values are: account-api[:v1], account2[:v1], account3[:v1], admin-api[:v1], admin-fine-grained-authz[:v1], admin2[:v1], authorization[:v1], ciba[:v1], client-policies[:v1], - client-secret-rotation[:v1], device-flow[:v1], docker[:v1], dpop[:v1], - dynamic-scopes[:v1], fips[:v1], hostname[:v1], impersonation[:v1], js-adapter - [:v1], kerberos[:v1], linkedin-oauth[:v1], multi-site[:v1], + client-secret-rotation[:v1], declarative-ui[:v1], device-flow[:v1], docker[: + v1], dpop[:v1], dynamic-scopes[:v1], fips[:v1], hostname[:v1], impersonation + [:v1], js-adapter[:v1], kerberos[:v1], linkedin-oauth[:v1], multi-site[:v1], offline-session-preloading[:v1], par[:v1], preview, recovery-codes[:v1], scripts[:v1], step-up-authentication[:v1], token-exchange[:v1], transient-users[:v1], update-email[:v1], web-authn[:v1]. --features-disabled Disables a set of one or more features. Possible values are: account-api, account2, account3, admin-api, admin-fine-grained-authz, admin2, - authorization, ciba, client-policies, client-secret-rotation, device-flow, - docker, dpop, dynamic-scopes, fips, impersonation, js-adapter, kerberos, - linkedin-oauth, multi-site, offline-session-preloading, par, preview, - recovery-codes, scripts, step-up-authentication, token-exchange, - transient-users, update-email, web-authn. + authorization, ciba, client-policies, client-secret-rotation, + declarative-ui, device-flow, docker, dpop, dynamic-scopes, fips, + impersonation, js-adapter, kerberos, linkedin-oauth, multi-site, + offline-session-preloading, par, preview, recovery-codes, scripts, + step-up-authentication, token-exchange, transient-users, update-email, + web-authn. HTTP(S): diff --git a/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testExportHelp.unix.approved.txt b/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testExportHelp.unix.approved.txt index 15146fadfa3..af3b7659cbb 100644 --- a/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testExportHelp.unix.approved.txt +++ b/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testExportHelp.unix.approved.txt @@ -59,20 +59,21 @@ Feature: --features Enables a set of one or more features. Possible values are: account-api[:v1], account2[:v1], account3[:v1], admin-api[:v1], admin-fine-grained-authz[:v1], admin2[:v1], authorization[:v1], ciba[:v1], client-policies[:v1], - client-secret-rotation[:v1], device-flow[:v1], docker[:v1], dpop[:v1], - dynamic-scopes[:v1], fips[:v1], hostname[:v1], impersonation[:v1], js-adapter - [:v1], kerberos[:v1], linkedin-oauth[:v1], multi-site[:v1], + client-secret-rotation[:v1], declarative-ui[:v1], device-flow[:v1], docker[: + v1], dpop[:v1], dynamic-scopes[:v1], fips[:v1], hostname[:v1], impersonation + [:v1], js-adapter[:v1], kerberos[:v1], linkedin-oauth[:v1], multi-site[:v1], offline-session-preloading[:v1], par[:v1], preview, recovery-codes[:v1], scripts[:v1], step-up-authentication[:v1], token-exchange[:v1], transient-users[:v1], update-email[:v1], web-authn[:v1]. --features-disabled Disables a set of one or more features. Possible values are: account-api, account2, account3, admin-api, admin-fine-grained-authz, admin2, - authorization, ciba, client-policies, client-secret-rotation, device-flow, - docker, dpop, dynamic-scopes, fips, impersonation, js-adapter, kerberos, - linkedin-oauth, multi-site, offline-session-preloading, par, preview, - recovery-codes, scripts, step-up-authentication, token-exchange, - transient-users, update-email, web-authn. + authorization, ciba, client-policies, client-secret-rotation, + declarative-ui, device-flow, docker, dpop, dynamic-scopes, fips, + impersonation, js-adapter, kerberos, linkedin-oauth, multi-site, + offline-session-preloading, par, preview, recovery-codes, scripts, + step-up-authentication, token-exchange, transient-users, update-email, + web-authn. Config: diff --git a/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testExportHelpAll.unix.approved.txt b/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testExportHelpAll.unix.approved.txt index 15146fadfa3..af3b7659cbb 100644 --- a/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testExportHelpAll.unix.approved.txt +++ b/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testExportHelpAll.unix.approved.txt @@ -59,20 +59,21 @@ Feature: --features Enables a set of one or more features. Possible values are: account-api[:v1], account2[:v1], account3[:v1], admin-api[:v1], admin-fine-grained-authz[:v1], admin2[:v1], authorization[:v1], ciba[:v1], client-policies[:v1], - client-secret-rotation[:v1], device-flow[:v1], docker[:v1], dpop[:v1], - dynamic-scopes[:v1], fips[:v1], hostname[:v1], impersonation[:v1], js-adapter - [:v1], kerberos[:v1], linkedin-oauth[:v1], multi-site[:v1], + client-secret-rotation[:v1], declarative-ui[:v1], device-flow[:v1], docker[: + v1], dpop[:v1], dynamic-scopes[:v1], fips[:v1], hostname[:v1], impersonation + [:v1], js-adapter[:v1], kerberos[:v1], linkedin-oauth[:v1], multi-site[:v1], offline-session-preloading[:v1], par[:v1], preview, recovery-codes[:v1], scripts[:v1], step-up-authentication[:v1], token-exchange[:v1], transient-users[:v1], update-email[:v1], web-authn[:v1]. --features-disabled Disables a set of one or more features. Possible values are: account-api, account2, account3, admin-api, admin-fine-grained-authz, admin2, - authorization, ciba, client-policies, client-secret-rotation, device-flow, - docker, dpop, dynamic-scopes, fips, impersonation, js-adapter, kerberos, - linkedin-oauth, multi-site, offline-session-preloading, par, preview, - recovery-codes, scripts, step-up-authentication, token-exchange, - transient-users, update-email, web-authn. + authorization, ciba, client-policies, client-secret-rotation, + declarative-ui, device-flow, docker, dpop, dynamic-scopes, fips, + impersonation, js-adapter, kerberos, linkedin-oauth, multi-site, + offline-session-preloading, par, preview, recovery-codes, scripts, + step-up-authentication, token-exchange, transient-users, update-email, + web-authn. Config: diff --git a/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testImportHelp.unix.approved.txt b/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testImportHelp.unix.approved.txt index 328527ca57b..ae700b66950 100644 --- a/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testImportHelp.unix.approved.txt +++ b/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testImportHelp.unix.approved.txt @@ -59,20 +59,21 @@ Feature: --features Enables a set of one or more features. Possible values are: account-api[:v1], account2[:v1], account3[:v1], admin-api[:v1], admin-fine-grained-authz[:v1], admin2[:v1], authorization[:v1], ciba[:v1], client-policies[:v1], - client-secret-rotation[:v1], device-flow[:v1], docker[:v1], dpop[:v1], - dynamic-scopes[:v1], fips[:v1], hostname[:v1], impersonation[:v1], js-adapter - [:v1], kerberos[:v1], linkedin-oauth[:v1], multi-site[:v1], + client-secret-rotation[:v1], declarative-ui[:v1], device-flow[:v1], docker[: + v1], dpop[:v1], dynamic-scopes[:v1], fips[:v1], hostname[:v1], impersonation + [:v1], js-adapter[:v1], kerberos[:v1], linkedin-oauth[:v1], multi-site[:v1], offline-session-preloading[:v1], par[:v1], preview, recovery-codes[:v1], scripts[:v1], step-up-authentication[:v1], token-exchange[:v1], transient-users[:v1], update-email[:v1], web-authn[:v1]. --features-disabled Disables a set of one or more features. Possible values are: account-api, account2, account3, admin-api, admin-fine-grained-authz, admin2, - authorization, ciba, client-policies, client-secret-rotation, device-flow, - docker, dpop, dynamic-scopes, fips, impersonation, js-adapter, kerberos, - linkedin-oauth, multi-site, offline-session-preloading, par, preview, - recovery-codes, scripts, step-up-authentication, token-exchange, - transient-users, update-email, web-authn. + authorization, ciba, client-policies, client-secret-rotation, + declarative-ui, device-flow, docker, dpop, dynamic-scopes, fips, + impersonation, js-adapter, kerberos, linkedin-oauth, multi-site, + offline-session-preloading, par, preview, recovery-codes, scripts, + step-up-authentication, token-exchange, transient-users, update-email, + web-authn. Config: diff --git a/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testImportHelpAll.unix.approved.txt b/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testImportHelpAll.unix.approved.txt index 328527ca57b..ae700b66950 100644 --- a/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testImportHelpAll.unix.approved.txt +++ b/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testImportHelpAll.unix.approved.txt @@ -59,20 +59,21 @@ Feature: --features Enables a set of one or more features. Possible values are: account-api[:v1], account2[:v1], account3[:v1], admin-api[:v1], admin-fine-grained-authz[:v1], admin2[:v1], authorization[:v1], ciba[:v1], client-policies[:v1], - client-secret-rotation[:v1], device-flow[:v1], docker[:v1], dpop[:v1], - dynamic-scopes[:v1], fips[:v1], hostname[:v1], impersonation[:v1], js-adapter - [:v1], kerberos[:v1], linkedin-oauth[:v1], multi-site[:v1], + client-secret-rotation[:v1], declarative-ui[:v1], device-flow[:v1], docker[: + v1], dpop[:v1], dynamic-scopes[:v1], fips[:v1], hostname[:v1], impersonation + [:v1], js-adapter[:v1], kerberos[:v1], linkedin-oauth[:v1], multi-site[:v1], offline-session-preloading[:v1], par[:v1], preview, recovery-codes[:v1], scripts[:v1], step-up-authentication[:v1], token-exchange[:v1], transient-users[:v1], update-email[:v1], web-authn[:v1]. --features-disabled Disables a set of one or more features. Possible values are: account-api, account2, account3, admin-api, admin-fine-grained-authz, admin2, - authorization, ciba, client-policies, client-secret-rotation, device-flow, - docker, dpop, dynamic-scopes, fips, impersonation, js-adapter, kerberos, - linkedin-oauth, multi-site, offline-session-preloading, par, preview, - recovery-codes, scripts, step-up-authentication, token-exchange, - transient-users, update-email, web-authn. + authorization, ciba, client-policies, client-secret-rotation, + declarative-ui, device-flow, docker, dpop, dynamic-scopes, fips, + impersonation, js-adapter, kerberos, linkedin-oauth, multi-site, + offline-session-preloading, par, preview, recovery-codes, scripts, + step-up-authentication, token-exchange, transient-users, update-email, + web-authn. Config: diff --git a/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testStartDevHelp.unix.approved.txt b/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testStartDevHelp.unix.approved.txt index 74ce2256c03..805a9729a71 100644 --- a/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testStartDevHelp.unix.approved.txt +++ b/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testStartDevHelp.unix.approved.txt @@ -89,20 +89,21 @@ Feature: --features Enables a set of one or more features. Possible values are: account-api[:v1], account2[:v1], account3[:v1], admin-api[:v1], admin-fine-grained-authz[:v1], admin2[:v1], authorization[:v1], ciba[:v1], client-policies[:v1], - client-secret-rotation[:v1], device-flow[:v1], docker[:v1], dpop[:v1], - dynamic-scopes[:v1], fips[:v1], hostname[:v1], impersonation[:v1], js-adapter - [:v1], kerberos[:v1], linkedin-oauth[:v1], multi-site[:v1], + client-secret-rotation[:v1], declarative-ui[:v1], device-flow[:v1], docker[: + v1], dpop[:v1], dynamic-scopes[:v1], fips[:v1], hostname[:v1], impersonation + [:v1], js-adapter[:v1], kerberos[:v1], linkedin-oauth[:v1], multi-site[:v1], offline-session-preloading[:v1], par[:v1], preview, recovery-codes[:v1], scripts[:v1], step-up-authentication[:v1], token-exchange[:v1], transient-users[:v1], update-email[:v1], web-authn[:v1]. --features-disabled Disables a set of one or more features. Possible values are: account-api, account2, account3, admin-api, admin-fine-grained-authz, admin2, - authorization, ciba, client-policies, client-secret-rotation, device-flow, - docker, dpop, dynamic-scopes, fips, impersonation, js-adapter, kerberos, - linkedin-oauth, multi-site, offline-session-preloading, par, preview, - recovery-codes, scripts, step-up-authentication, token-exchange, - transient-users, update-email, web-authn. + authorization, ciba, client-policies, client-secret-rotation, + declarative-ui, device-flow, docker, dpop, dynamic-scopes, fips, + impersonation, js-adapter, kerberos, linkedin-oauth, multi-site, + offline-session-preloading, par, preview, recovery-codes, scripts, + step-up-authentication, token-exchange, transient-users, update-email, + web-authn. Hostname: diff --git a/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testStartDevHelp.windows.approved.txt b/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testStartDevHelp.windows.approved.txt index 27044fc912b..bd1a3f37955 100644 --- a/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testStartDevHelp.windows.approved.txt +++ b/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testStartDevHelp.windows.approved.txt @@ -89,20 +89,21 @@ Feature: --features Enables a set of one or more features. Possible values are: account-api[:v1], account2[:v1], account3[:v1], admin-api[:v1], admin-fine-grained-authz[:v1], admin2[:v1], authorization[:v1], ciba[:v1], client-policies[:v1], - client-secret-rotation[:v1], device-flow[:v1], docker[:v1], dpop[:v1], - dynamic-scopes[:v1], fips[:v1], hostname[:v1],impersonation[:v1], js-adapter[:v1], kerberos - [:v1], linkedin-oauth[:v1], multi-site[:v1], offline-session-preloading[: - v1], par[:v1], preview, recovery-codes[:v1], scripts[:v1], - step-up-authentication[:v1], token-exchange[:v1], transient-users[:v1], - update-email[:v1], web-authn[:v1]. + client-secret-rotation[:v1], declarative-ui[:v1], device-flow[:v1], docker[: + v1], dpop[:v1], dynamic-scopes[:v1], fips[:v1], hostname[:v1], impersonation + [:v1], js-adapter[:v1], kerberos[:v1], linkedin-oauth[:v1], multi-site[:v1], + offline-session-preloading[:v1], par[:v1], preview, recovery-codes[:v1], + scripts[:v1], step-up-authentication[:v1], token-exchange[:v1], + transient-users[:v1], update-email[:v1], web-authn[:v1]. --features-disabled Disables a set of one or more features. Possible values are: account-api, account2, account3, admin-api, admin-fine-grained-authz, admin2, - authorization, ciba, client-policies, client-secret-rotation, device-flow, - docker, dpop, dynamic-scopes, fips, impersonation, js-adapter, kerberos, - linkedin-oauth, multi-site, offline-session-preloading, par, preview, - recovery-codes, scripts, step-up-authentication, token-exchange, - transient-users, update-email, web-authn. + authorization, ciba, client-policies, client-secret-rotation, + declarative-ui, device-flow, docker, dpop, dynamic-scopes, fips, + impersonation, js-adapter, kerberos, linkedin-oauth, multi-site, + offline-session-preloading, par, preview, recovery-codes, scripts, + step-up-authentication, token-exchange, transient-users, update-email, + web-authn. Hostname: diff --git a/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testStartDevHelpAll.unix.approved.txt b/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testStartDevHelpAll.unix.approved.txt index 74ce2256c03..805a9729a71 100644 --- a/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testStartDevHelpAll.unix.approved.txt +++ b/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testStartDevHelpAll.unix.approved.txt @@ -89,20 +89,21 @@ Feature: --features Enables a set of one or more features. Possible values are: account-api[:v1], account2[:v1], account3[:v1], admin-api[:v1], admin-fine-grained-authz[:v1], admin2[:v1], authorization[:v1], ciba[:v1], client-policies[:v1], - client-secret-rotation[:v1], device-flow[:v1], docker[:v1], dpop[:v1], - dynamic-scopes[:v1], fips[:v1], hostname[:v1], impersonation[:v1], js-adapter - [:v1], kerberos[:v1], linkedin-oauth[:v1], multi-site[:v1], + client-secret-rotation[:v1], declarative-ui[:v1], device-flow[:v1], docker[: + v1], dpop[:v1], dynamic-scopes[:v1], fips[:v1], hostname[:v1], impersonation + [:v1], js-adapter[:v1], kerberos[:v1], linkedin-oauth[:v1], multi-site[:v1], offline-session-preloading[:v1], par[:v1], preview, recovery-codes[:v1], scripts[:v1], step-up-authentication[:v1], token-exchange[:v1], transient-users[:v1], update-email[:v1], web-authn[:v1]. --features-disabled Disables a set of one or more features. Possible values are: account-api, account2, account3, admin-api, admin-fine-grained-authz, admin2, - authorization, ciba, client-policies, client-secret-rotation, device-flow, - docker, dpop, dynamic-scopes, fips, impersonation, js-adapter, kerberos, - linkedin-oauth, multi-site, offline-session-preloading, par, preview, - recovery-codes, scripts, step-up-authentication, token-exchange, - transient-users, update-email, web-authn. + authorization, ciba, client-policies, client-secret-rotation, + declarative-ui, device-flow, docker, dpop, dynamic-scopes, fips, + impersonation, js-adapter, kerberos, linkedin-oauth, multi-site, + offline-session-preloading, par, preview, recovery-codes, scripts, + step-up-authentication, token-exchange, transient-users, update-email, + web-authn. Hostname: diff --git a/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testStartDevHelpAll.windows.approved.txt b/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testStartDevHelpAll.windows.approved.txt index 27044fc912b..bd1a3f37955 100644 --- a/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testStartDevHelpAll.windows.approved.txt +++ b/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testStartDevHelpAll.windows.approved.txt @@ -89,20 +89,21 @@ Feature: --features Enables a set of one or more features. Possible values are: account-api[:v1], account2[:v1], account3[:v1], admin-api[:v1], admin-fine-grained-authz[:v1], admin2[:v1], authorization[:v1], ciba[:v1], client-policies[:v1], - client-secret-rotation[:v1], device-flow[:v1], docker[:v1], dpop[:v1], - dynamic-scopes[:v1], fips[:v1], hostname[:v1],impersonation[:v1], js-adapter[:v1], kerberos - [:v1], linkedin-oauth[:v1], multi-site[:v1], offline-session-preloading[: - v1], par[:v1], preview, recovery-codes[:v1], scripts[:v1], - step-up-authentication[:v1], token-exchange[:v1], transient-users[:v1], - update-email[:v1], web-authn[:v1]. + client-secret-rotation[:v1], declarative-ui[:v1], device-flow[:v1], docker[: + v1], dpop[:v1], dynamic-scopes[:v1], fips[:v1], hostname[:v1], impersonation + [:v1], js-adapter[:v1], kerberos[:v1], linkedin-oauth[:v1], multi-site[:v1], + offline-session-preloading[:v1], par[:v1], preview, recovery-codes[:v1], + scripts[:v1], step-up-authentication[:v1], token-exchange[:v1], + transient-users[:v1], update-email[:v1], web-authn[:v1]. --features-disabled Disables a set of one or more features. Possible values are: account-api, account2, account3, admin-api, admin-fine-grained-authz, admin2, - authorization, ciba, client-policies, client-secret-rotation, device-flow, - docker, dpop, dynamic-scopes, fips, impersonation, js-adapter, kerberos, - linkedin-oauth, multi-site, offline-session-preloading, par, preview, - recovery-codes, scripts, step-up-authentication, token-exchange, - transient-users, update-email, web-authn. + authorization, ciba, client-policies, client-secret-rotation, + declarative-ui, device-flow, docker, dpop, dynamic-scopes, fips, + impersonation, js-adapter, kerberos, linkedin-oauth, multi-site, + offline-session-preloading, par, preview, recovery-codes, scripts, + step-up-authentication, token-exchange, transient-users, update-email, + web-authn. Hostname: diff --git a/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testStartHelp.unix.approved.txt b/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testStartHelp.unix.approved.txt index 587c1701e95..31a64523ba7 100644 --- a/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testStartHelp.unix.approved.txt +++ b/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testStartHelp.unix.approved.txt @@ -90,20 +90,21 @@ Feature: --features Enables a set of one or more features. Possible values are: account-api[:v1], account2[:v1], account3[:v1], admin-api[:v1], admin-fine-grained-authz[:v1], admin2[:v1], authorization[:v1], ciba[:v1], client-policies[:v1], - client-secret-rotation[:v1], device-flow[:v1], docker[:v1], dpop[:v1], - dynamic-scopes[:v1], fips[:v1], hostname[:v1], impersonation[:v1], js-adapter - [:v1], kerberos[:v1], linkedin-oauth[:v1], multi-site[:v1], + client-secret-rotation[:v1], declarative-ui[:v1], device-flow[:v1], docker[: + v1], dpop[:v1], dynamic-scopes[:v1], fips[:v1], hostname[:v1], impersonation + [:v1], js-adapter[:v1], kerberos[:v1], linkedin-oauth[:v1], multi-site[:v1], offline-session-preloading[:v1], par[:v1], preview, recovery-codes[:v1], scripts[:v1], step-up-authentication[:v1], token-exchange[:v1], transient-users[:v1], update-email[:v1], web-authn[:v1]. --features-disabled Disables a set of one or more features. Possible values are: account-api, account2, account3, admin-api, admin-fine-grained-authz, admin2, - authorization, ciba, client-policies, client-secret-rotation, device-flow, - docker, dpop, dynamic-scopes, fips, impersonation, js-adapter, kerberos, - linkedin-oauth, multi-site, offline-session-preloading, par, preview, - recovery-codes, scripts, step-up-authentication, token-exchange, - transient-users, update-email, web-authn. + authorization, ciba, client-policies, client-secret-rotation, + declarative-ui, device-flow, docker, dpop, dynamic-scopes, fips, + impersonation, js-adapter, kerberos, linkedin-oauth, multi-site, + offline-session-preloading, par, preview, recovery-codes, scripts, + step-up-authentication, token-exchange, transient-users, update-email, + web-authn. Hostname: diff --git a/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testStartHelp.windows.approved.txt b/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testStartHelp.windows.approved.txt index 9ece031fc18..9c14fd37df7 100644 --- a/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testStartHelp.windows.approved.txt +++ b/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testStartHelp.windows.approved.txt @@ -90,20 +90,21 @@ Feature: --features Enables a set of one or more features. Possible values are: account-api[:v1], account2[:v1], account3[:v1], admin-api[:v1], admin-fine-grained-authz[:v1], admin2[:v1], authorization[:v1], ciba[:v1], client-policies[:v1], - client-secret-rotation[:v1], device-flow[:v1], docker[:v1], dpop[:v1], - dynamic-scopes[:v1], fips[:v1], hostname[:v1],impersonation[:v1], js-adapter[:v1], kerberos - [:v1], linkedin-oauth[:v1], multi-site[:v1], offline-session-preloading[: - v1], par[:v1], preview, recovery-codes[:v1], scripts[:v1], - step-up-authentication[:v1], token-exchange[:v1], transient-users[:v1], - update-email[:v1], web-authn[:v1]. + client-secret-rotation[:v1], declarative-ui[:v1], device-flow[:v1], docker[: + v1], dpop[:v1], dynamic-scopes[:v1], fips[:v1], hostname[:v1], impersonation + [:v1], js-adapter[:v1], kerberos[:v1], linkedin-oauth[:v1], multi-site[:v1], + offline-session-preloading[:v1], par[:v1], preview, recovery-codes[:v1], + scripts[:v1], step-up-authentication[:v1], token-exchange[:v1], + transient-users[:v1], update-email[:v1], web-authn[:v1]. --features-disabled Disables a set of one or more features. Possible values are: account-api, account2, account3, admin-api, admin-fine-grained-authz, admin2, - authorization, ciba, client-policies, client-secret-rotation, device-flow, - docker, dpop, dynamic-scopes, fips, impersonation, js-adapter, kerberos, - linkedin-oauth, multi-site, offline-session-preloading, par, preview, - recovery-codes, scripts, step-up-authentication, token-exchange, - transient-users, update-email, web-authn. + authorization, ciba, client-policies, client-secret-rotation, + declarative-ui, device-flow, docker, dpop, dynamic-scopes, fips, + impersonation, js-adapter, kerberos, linkedin-oauth, multi-site, + offline-session-preloading, par, preview, recovery-codes, scripts, + step-up-authentication, token-exchange, transient-users, update-email, + web-authn. Hostname: diff --git a/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testStartHelpAll.unix.approved.txt b/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testStartHelpAll.unix.approved.txt index 587c1701e95..31a64523ba7 100644 --- a/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testStartHelpAll.unix.approved.txt +++ b/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testStartHelpAll.unix.approved.txt @@ -90,20 +90,21 @@ Feature: --features Enables a set of one or more features. Possible values are: account-api[:v1], account2[:v1], account3[:v1], admin-api[:v1], admin-fine-grained-authz[:v1], admin2[:v1], authorization[:v1], ciba[:v1], client-policies[:v1], - client-secret-rotation[:v1], device-flow[:v1], docker[:v1], dpop[:v1], - dynamic-scopes[:v1], fips[:v1], hostname[:v1], impersonation[:v1], js-adapter - [:v1], kerberos[:v1], linkedin-oauth[:v1], multi-site[:v1], + client-secret-rotation[:v1], declarative-ui[:v1], device-flow[:v1], docker[: + v1], dpop[:v1], dynamic-scopes[:v1], fips[:v1], hostname[:v1], impersonation + [:v1], js-adapter[:v1], kerberos[:v1], linkedin-oauth[:v1], multi-site[:v1], offline-session-preloading[:v1], par[:v1], preview, recovery-codes[:v1], scripts[:v1], step-up-authentication[:v1], token-exchange[:v1], transient-users[:v1], update-email[:v1], web-authn[:v1]. --features-disabled Disables a set of one or more features. Possible values are: account-api, account2, account3, admin-api, admin-fine-grained-authz, admin2, - authorization, ciba, client-policies, client-secret-rotation, device-flow, - docker, dpop, dynamic-scopes, fips, impersonation, js-adapter, kerberos, - linkedin-oauth, multi-site, offline-session-preloading, par, preview, - recovery-codes, scripts, step-up-authentication, token-exchange, - transient-users, update-email, web-authn. + authorization, ciba, client-policies, client-secret-rotation, + declarative-ui, device-flow, docker, dpop, dynamic-scopes, fips, + impersonation, js-adapter, kerberos, linkedin-oauth, multi-site, + offline-session-preloading, par, preview, recovery-codes, scripts, + step-up-authentication, token-exchange, transient-users, update-email, + web-authn. Hostname: diff --git a/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testStartHelpAll.windows.approved.txt b/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testStartHelpAll.windows.approved.txt index 9ece031fc18..9c14fd37df7 100644 --- a/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testStartHelpAll.windows.approved.txt +++ b/quarkus/tests/integration/src/test/resources/org/keycloak/it/cli/dist/approvals/cli/help/HelpCommandDistTest.testStartHelpAll.windows.approved.txt @@ -90,20 +90,21 @@ Feature: --features Enables a set of one or more features. Possible values are: account-api[:v1], account2[:v1], account3[:v1], admin-api[:v1], admin-fine-grained-authz[:v1], admin2[:v1], authorization[:v1], ciba[:v1], client-policies[:v1], - client-secret-rotation[:v1], device-flow[:v1], docker[:v1], dpop[:v1], - dynamic-scopes[:v1], fips[:v1], hostname[:v1],impersonation[:v1], js-adapter[:v1], kerberos - [:v1], linkedin-oauth[:v1], multi-site[:v1], offline-session-preloading[: - v1], par[:v1], preview, recovery-codes[:v1], scripts[:v1], - step-up-authentication[:v1], token-exchange[:v1], transient-users[:v1], - update-email[:v1], web-authn[:v1]. + client-secret-rotation[:v1], declarative-ui[:v1], device-flow[:v1], docker[: + v1], dpop[:v1], dynamic-scopes[:v1], fips[:v1], hostname[:v1], impersonation + [:v1], js-adapter[:v1], kerberos[:v1], linkedin-oauth[:v1], multi-site[:v1], + offline-session-preloading[:v1], par[:v1], preview, recovery-codes[:v1], + scripts[:v1], step-up-authentication[:v1], token-exchange[:v1], + transient-users[:v1], update-email[:v1], web-authn[:v1]. --features-disabled Disables a set of one or more features. Possible values are: account-api, account2, account3, admin-api, admin-fine-grained-authz, admin2, - authorization, ciba, client-policies, client-secret-rotation, device-flow, - docker, dpop, dynamic-scopes, fips, impersonation, js-adapter, kerberos, - linkedin-oauth, multi-site, offline-session-preloading, par, preview, - recovery-codes, scripts, step-up-authentication, token-exchange, - transient-users, update-email, web-authn. + authorization, ciba, client-policies, client-secret-rotation, + declarative-ui, device-flow, docker, dpop, dynamic-scopes, fips, + impersonation, js-adapter, kerberos, linkedin-oauth, multi-site, + offline-session-preloading, par, preview, recovery-codes, scripts, + step-up-authentication, token-exchange, transient-users, update-email, + web-authn. Hostname: diff --git a/server-spi-private/src/main/java/org/keycloak/services/ui/extend/UiPageSpi.java b/server-spi-private/src/main/java/org/keycloak/services/ui/extend/UiPageSpi.java index 1923f6496d1..c1249cee10b 100644 --- a/server-spi-private/src/main/java/org/keycloak/services/ui/extend/UiPageSpi.java +++ b/server-spi-private/src/main/java/org/keycloak/services/ui/extend/UiPageSpi.java @@ -1,5 +1,6 @@ package org.keycloak.services.ui.extend; +import org.keycloak.common.Profile; import org.keycloak.provider.Provider; import org.keycloak.provider.ProviderFactory; import org.keycloak.provider.Spi; @@ -24,4 +25,9 @@ public Class getProviderClass() { public Class getProviderFactoryClass() { return UiPageProviderFactory.class; } + + @Override + public boolean isEnabled() { + return Profile.isFeatureEnabled(Profile.Feature.DECLARATIVE_UI); + } } diff --git a/server-spi-private/src/main/java/org/keycloak/services/ui/extend/UiTabSpi.java b/server-spi-private/src/main/java/org/keycloak/services/ui/extend/UiTabSpi.java index 4d73a4a11ea..bc97ef080d9 100644 --- a/server-spi-private/src/main/java/org/keycloak/services/ui/extend/UiTabSpi.java +++ b/server-spi-private/src/main/java/org/keycloak/services/ui/extend/UiTabSpi.java @@ -1,5 +1,6 @@ package org.keycloak.services.ui.extend; +import org.keycloak.common.Profile; import org.keycloak.provider.Provider; import org.keycloak.provider.ProviderFactory; import org.keycloak.provider.Spi; @@ -24,4 +25,9 @@ public Class getProviderClass() { public Class getProviderFactoryClass() { return UiTabProviderFactory.class; } + + @Override + public boolean isEnabled() { + return Profile.isFeatureEnabled(Profile.Feature.DECLARATIVE_UI); + } }