pull in master branch

This commit is contained in:
Christie Molloy 2020-09-28 17:09:34 -04:00
commit 8a513e3ecf
62 changed files with 10184 additions and 8077 deletions

View file

@ -1,38 +1,38 @@
{
"env": {
"browser": true,
"es6": true
},
"extends": [
"eslint:recommended",
"plugin:react/recommended",
"plugin:@typescript-eslint/eslint-recommended"
],
"globals": {
"Atomics": "readonly",
"SharedArrayBuffer": "readonly"
},
"parser": "@typescript-eslint/parser",
"parserOptions": {
"ecmaFeatures": {
"jsx": true
},
"ecmaVersion": 2018,
"sourceType": "module"
},
"plugins": [
"react",
"@typescript-eslint"
],
"rules": {
"no-unused-vars": "off",
"@typescript-eslint/no-unused-vars": [
"error"
]
},
"settings": {
"react": {
"version": "detect"
}
}
"env": {
"browser": true,
"es6": true
},
"extends": [
"eslint:recommended",
"plugin:react/recommended",
"plugin:@typescript-eslint/eslint-recommended"
],
"globals": {
"Atomics": "readonly",
"SharedArrayBuffer": "readonly"
},
"parser": "@typescript-eslint/parser",
"parserOptions": {
"ecmaFeatures": {
"jsx": true
},
"ecmaVersion": 2018,
"sourceType": "module"
},
"plugins": [
"react",
"@typescript-eslint"
],
"rules": {
"no-unused-vars": "off",
"@typescript-eslint/no-unused-vars": [
"error"
]
},
"settings": {
"react": {
"version": "detect"
}
}
}

View file

@ -8,7 +8,7 @@ module.exports = function (grunt) {
copy: {
main: {
cwd: "./node_modules/@patternfly/patternfly/assets/",
src: "**/*",
src: ["**/*", '!**/fontawesome/**'],
dest: "public/assets/",
expand: true,
},

View file

@ -16,10 +16,10 @@
"build-storybook": "build-storybook -s public"
},
"dependencies": {
"@patternfly/patternfly": "^4.31.6",
"@patternfly/react-core": "^4.40.4",
"@patternfly/react-icons": "^4.7.2",
"@patternfly/react-table": "^4.15.5",
"@patternfly/patternfly": "^4.42.2",
"@patternfly/react-core": "4.50.2",
"@patternfly/react-icons": "4.7.6",
"@patternfly/react-table": "4.16.20",
"file-saver": "^2.0.2",
"i18next": "^19.6.2",
"keycloak-js": "^11.0.0",
@ -33,8 +33,8 @@
"@babel/core": "^7.10.5",
"@babel/preset-typescript": "^7.10.4",
"@snowpack/app-scripts-react": "^1.10.0",
"@snowpack/plugin-postcss": "^1.0.2",
"@snowpack/plugin-webpack": "^2.0.6",
"@snowpack/plugin-postcss": "1.0.4",
"@snowpack/plugin-webpack": "2.0.12",
"@storybook/addon-actions": "^6.0.21",
"@storybook/addon-essentials": "^6.0.21",
"@storybook/addon-info": "^5.3.21",
@ -73,7 +73,7 @@
"prettier": "^2.0.5",
"react-is": "^16.13.1",
"react-scripts": "^3.4.1",
"snowpack": "^2.10.0",
"snowpack": "2.11.1",
"typescript": "^3.8.3"
}
}

View file

@ -1,47 +1,47 @@
{
"clientId": "security-admin-console-v2",
"rootUrl": "http://localhost:8080/",
"adminUrl": "http://localhost:8080/",
"surrogateAuthRequired": false,
"enabled": true,
"alwaysDisplayInConsole": false,
"clientAuthenticatorType": "client-secret",
"redirectUris": [
"http://localhost:8080/*"
],
"webOrigins": [
"http://localhost:8080"
],
"notBefore": 0,
"bearerOnly": false,
"consentRequired": false,
"standardFlowEnabled": true,
"implicitFlowEnabled": false,
"directAccessGrantsEnabled": true,
"serviceAccountsEnabled": false,
"publicClient": true,
"frontchannelLogout": false,
"protocol": "openid-connect",
"attributes": {},
"authenticationFlowBindingOverrides": {},
"fullScopeAllowed": true,
"nodeReRegistrationTimeout": -1,
"defaultClientScopes": [
"web-origins",
"role_list",
"roles",
"profile",
"email"
],
"optionalClientScopes": [
"address",
"phone",
"offline_access",
"microprofile-jwt"
],
"access": {
"view": true,
"configure": true,
"manage": true
}
"clientId": "security-admin-console-v2",
"rootUrl": "http://localhost:8080/",
"adminUrl": "http://localhost:8080/",
"surrogateAuthRequired": false,
"enabled": true,
"alwaysDisplayInConsole": false,
"clientAuthenticatorType": "client-secret",
"redirectUris": [
"http://localhost:8080/*"
],
"webOrigins": [
"http://localhost:8080"
],
"notBefore": 0,
"bearerOnly": false,
"consentRequired": false,
"standardFlowEnabled": true,
"implicitFlowEnabled": false,
"directAccessGrantsEnabled": true,
"serviceAccountsEnabled": false,
"publicClient": true,
"frontchannelLogout": false,
"protocol": "openid-connect",
"attributes": {},
"authenticationFlowBindingOverrides": {},
"fullScopeAllowed": true,
"nodeReRegistrationTimeout": -1,
"defaultClientScopes": [
"web-origins",
"role_list",
"roles",
"profile",
"email"
],
"optionalClientScopes": [
"address",
"phone",
"offline_access",
"microprofile-jwt"
],
"access": {
"view": true,
"configure": true,
"manage": true
}
}

View file

@ -1,12 +1,14 @@
import React from "react";
import { Page, PageSection } from "@patternfly/react-core";
import { Page } from "@patternfly/react-core";
import { BrowserRouter as Router, Route, Switch } from "react-router-dom";
import { Header } from "./PageHeader";
import { PageNav } from "./PageNav";
import { Help } from "./components/help-enabler/HelpHeader";
import { BrowserRouter as Router, Route, Switch } from "react-router-dom";
import { NewRealmForm } from "./realm/add/NewRealmForm";
import { NewClientForm } from "./clients/add/NewClientForm";
import { NewClientScopeForm } from "./client-scopes/add/NewClientScopeForm";
import { ImportForm } from "./clients/import/ImportForm";
import { ClientsSection } from "./clients/ClientsSection";
import { ClientScopesSection } from "./client-scopes/ClientScopesSection";
@ -36,7 +38,7 @@ export const App = () => {
<Route exact path="/clients" component={ClientsSection}></Route>
<Route
exact
path="/client-settings"
path="/client-settings/:id"
component={ClientSettings}
></Route>
<Route exact path="/add-client" component={NewClientForm}></Route>
@ -47,6 +49,11 @@ export const App = () => {
path="/client-scopes"
component={ClientScopesSection}
></Route>
<Route
exact
path="/add-client-scopes"
component={NewClientScopeForm}
></Route>
<Route
exact
path="/realm-roles"

View file

@ -0,0 +1,58 @@
import React from "react";
import { useTranslation } from "react-i18next";
import {
Table,
TableBody,
TableHeader,
TableVariant,
} from "@patternfly/react-table";
import { ClientScopeRepresentation } from "./models/client-scope";
type ClientScopeListProps = {
clientScopes: ClientScopeRepresentation[];
};
export const ClientScopeList = ({ clientScopes }: ClientScopeListProps) => {
const { t } = useTranslation("client-scopes");
const columns: (keyof ClientScopeRepresentation)[] = [
"name",
"description",
"protocol",
];
const data = clientScopes.map((c) => {
return { cells: columns.map((col) => c[col]) };
});
return (
<>
<Table
variant={TableVariant.compact}
cells={[
{ title: t("name") },
{ title: t("description") },
{
title: t("protocol"),
},
]}
rows={data}
actions={[
{
title: t("common:export"),
onClick: () => {},
},
{
title: t("common:delete"),
onClick: () => {},
},
]}
aria-label={t("clientScopeList")}
>
<TableHeader />
<TableBody />
</Table>
</>
);
};

View file

@ -1,10 +1,53 @@
import { PageSection } from "@patternfly/react-core";
import React from "react";
import React, { useContext, useState } from "react";
import { Button, PageSection } from "@patternfly/react-core";
import { useTranslation } from "react-i18next";
import { useHistory } from "react-router-dom";
import { RealmContext } from "../components/realm-context/RealmContext";
import { HttpClientContext } from "../http-service/HttpClientContext";
import { ClientRepresentation } from "../realm/models/Realm";
import { DataLoader } from "../components/data-loader/DataLoader";
import { TableToolbar } from "../components/table-toolbar/TableToolbar";
import { ClientScopeList } from "./ClientScopesList";
export const ClientScopesSection = () => {
const { t } = useTranslation("client-scopes");
const history = useHistory();
const [max, setMax] = useState(10);
const [first, setFirst] = useState(0);
const httpClient = useContext(HttpClientContext)!;
const { realm } = useContext(RealmContext);
const loader = async () => {
return await httpClient
.doGet(`/admin/realms/${realm}/client-scopes`, { params: { first, max } })
.then((r) => r.data as ClientRepresentation[]);
};
return (
<>
<PageSection variant="light">The Client Scopes Page</PageSection>
</>
<PageSection variant="light">
<DataLoader loader={loader}>
{(scopes) => (
<TableToolbar
count={scopes!.length}
first={first}
max={max}
onNextClick={setFirst}
onPreviousClick={setFirst}
onPerPageSelect={(first, max) => {
setFirst(first);
setMax(max);
}}
toolbarItem={
<Button onClick={() => history.push("/add-client-scopes")}>
{t("createClientScope")}
</Button>
}
>
<ClientScopeList clientScopes={scopes} />
</TableToolbar>
)}
</DataLoader>
</PageSection>
);
};

View file

@ -0,0 +1,485 @@
[
{
"id": "3507ed12-d8b0-455c-b91a-62a6765ecf0f",
"name": "address",
"description": "OpenID Connect built-in scope: address",
"protocol": "openid-connect",
"attributes": {
"include.in.token.scope": "true",
"display.on.consent.screen": "true",
"consent.screen.text": "${addressScopeConsentText}"
},
"protocolMappers": [
{
"id": "b0582082-abab-4c63-b3b7-a92afe6b3436",
"name": "address",
"protocol": "openid-connect",
"protocolMapper": "oidc-address-mapper",
"consentRequired": false,
"config": {
"user.attribute.formatted": "formatted",
"user.attribute.country": "country",
"user.attribute.postal_code": "postal_code",
"userinfo.token.claim": "true",
"user.attribute.street": "street",
"id.token.claim": "true",
"user.attribute.region": "region",
"access.token.claim": "true",
"user.attribute.locality": "locality"
}
}
]
},
{
"id": "eb8c7985-5459-45a9-ace5-2959ce0fd1c9",
"name": "email",
"description": "OpenID Connect built-in scope: email",
"protocol": "openid-connect",
"attributes": {
"include.in.token.scope": "true",
"display.on.consent.screen": "true",
"consent.screen.text": "${emailScopeConsentText}"
},
"protocolMappers": [
{
"id": "348dfe5c-26e6-43e8-bc80-b7db9f842f24",
"name": "email",
"protocol": "openid-connect",
"protocolMapper": "oidc-usermodel-property-mapper",
"consentRequired": false,
"config": {
"userinfo.token.claim": "true",
"user.attribute": "email",
"id.token.claim": "true",
"access.token.claim": "true",
"claim.name": "email",
"jsonType.label": "String"
}
},
{
"id": "bfe77908-4ca3-40ea-b5be-75bea87f5bb1",
"name": "email verified",
"protocol": "openid-connect",
"protocolMapper": "oidc-usermodel-property-mapper",
"consentRequired": false,
"config": {
"userinfo.token.claim": "true",
"user.attribute": "emailVerified",
"id.token.claim": "true",
"access.token.claim": "true",
"claim.name": "email_verified",
"jsonType.label": "boolean"
}
}
]
},
{
"id": "e604d76d-20ec-4d80-acee-1885af201568",
"name": "microprofile-jwt",
"description": "Microprofile - JWT built-in scope",
"protocol": "openid-connect",
"attributes": {
"include.in.token.scope": "true",
"display.on.consent.screen": "false"
},
"protocolMappers": [
{
"id": "63a71cf3-df7c-4a81-a23f-d3ba62801c72",
"name": "upn",
"protocol": "openid-connect",
"protocolMapper": "oidc-usermodel-property-mapper",
"consentRequired": false,
"config": {
"userinfo.token.claim": "true",
"user.attribute": "username",
"id.token.claim": "true",
"access.token.claim": "true",
"claim.name": "upn",
"jsonType.label": "String"
}
},
{
"id": "5eb3444b-8e96-4267-9afc-20abd56613aa",
"name": "groups",
"protocol": "openid-connect",
"protocolMapper": "oidc-usermodel-realm-role-mapper",
"consentRequired": false,
"config": {
"multivalued": "true",
"user.attribute": "foo",
"id.token.claim": "true",
"access.token.claim": "true",
"claim.name": "groups",
"jsonType.label": "String"
}
}
]
},
{
"id": "2cdac876-a8ce-4cde-8bcb-00e28804ec91",
"name": "offline_access",
"description": "OpenID Connect built-in scope: offline_access",
"protocol": "openid-connect",
"attributes": {
"consent.screen.text": "${offlineAccessScopeConsentText}",
"display.on.consent.screen": "true"
}
},
{
"id": "3db88729-214e-4c71-8fac-ee744279538b",
"name": "phone",
"description": "OpenID Connect built-in scope: phone",
"protocol": "openid-connect",
"attributes": {
"include.in.token.scope": "true",
"display.on.consent.screen": "true",
"consent.screen.text": "${phoneScopeConsentText}"
},
"protocolMappers": [
{
"id": "00ca4abc-fc26-4273-9d77-d7a793f38976",
"name": "phone number verified",
"protocol": "openid-connect",
"protocolMapper": "oidc-usermodel-attribute-mapper",
"consentRequired": false,
"config": {
"userinfo.token.claim": "true",
"user.attribute": "phoneNumberVerified",
"id.token.claim": "true",
"access.token.claim": "true",
"claim.name": "phone_number_verified",
"jsonType.label": "boolean"
}
},
{
"id": "98885779-b84e-4565-bc1b-a0c703f03be0",
"name": "phone number",
"protocol": "openid-connect",
"protocolMapper": "oidc-usermodel-attribute-mapper",
"consentRequired": false,
"config": {
"userinfo.token.claim": "true",
"user.attribute": "phoneNumber",
"id.token.claim": "true",
"access.token.claim": "true",
"claim.name": "phone_number",
"jsonType.label": "String"
}
}
]
},
{
"id": "82aca51c-22b4-4156-93a9-3ed33ec2adcc",
"name": "profile",
"description": "OpenID Connect built-in scope: profile",
"protocol": "openid-connect",
"attributes": {
"include.in.token.scope": "true",
"display.on.consent.screen": "true",
"consent.screen.text": "${profileScopeConsentText}"
},
"protocolMappers": [
{
"id": "d974a079-8416-4dea-9e49-76dab694e836",
"name": "full name",
"protocol": "openid-connect",
"protocolMapper": "oidc-full-name-mapper",
"consentRequired": false,
"config": {
"id.token.claim": "true",
"access.token.claim": "true",
"userinfo.token.claim": "true"
}
},
{
"id": "2b0e5ec3-cc38-44c4-8851-98c0e3e3f60d",
"name": "birthdate",
"protocol": "openid-connect",
"protocolMapper": "oidc-usermodel-attribute-mapper",
"consentRequired": false,
"config": {
"userinfo.token.claim": "true",
"user.attribute": "birthdate",
"id.token.claim": "true",
"access.token.claim": "true",
"claim.name": "birthdate",
"jsonType.label": "String"
}
},
{
"id": "feef3c77-5a8e-4f22-94c8-fc606eb8dad0",
"name": "website",
"protocol": "openid-connect",
"protocolMapper": "oidc-usermodel-attribute-mapper",
"consentRequired": false,
"config": {
"userinfo.token.claim": "true",
"user.attribute": "website",
"id.token.claim": "true",
"access.token.claim": "true",
"claim.name": "website",
"jsonType.label": "String"
}
},
{
"id": "e0340530-efde-4bdf-8399-c98b994e3c4f",
"name": "nickname",
"protocol": "openid-connect",
"protocolMapper": "oidc-usermodel-attribute-mapper",
"consentRequired": false,
"config": {
"userinfo.token.claim": "true",
"user.attribute": "nickname",
"id.token.claim": "true",
"access.token.claim": "true",
"claim.name": "nickname",
"jsonType.label": "String"
}
},
{
"id": "bef63a97-20a4-4595-9e31-881273af8b47",
"name": "locale",
"protocol": "openid-connect",
"protocolMapper": "oidc-usermodel-attribute-mapper",
"consentRequired": false,
"config": {
"userinfo.token.claim": "true",
"user.attribute": "locale",
"id.token.claim": "true",
"access.token.claim": "true",
"claim.name": "locale",
"jsonType.label": "String"
}
},
{
"id": "3a4e571b-9ee4-4553-8a54-dcf0ab757b39",
"name": "picture",
"protocol": "openid-connect",
"protocolMapper": "oidc-usermodel-attribute-mapper",
"consentRequired": false,
"config": {
"userinfo.token.claim": "true",
"user.attribute": "picture",
"id.token.claim": "true",
"access.token.claim": "true",
"claim.name": "picture",
"jsonType.label": "String"
}
},
{
"id": "d0c55da1-f814-4bfe-a311-b34ddd7ee2fb",
"name": "updated at",
"protocol": "openid-connect",
"protocolMapper": "oidc-usermodel-attribute-mapper",
"consentRequired": false,
"config": {
"userinfo.token.claim": "true",
"user.attribute": "updatedAt",
"id.token.claim": "true",
"access.token.claim": "true",
"claim.name": "updated_at",
"jsonType.label": "String"
}
},
{
"id": "392fa527-96e9-41a5-8fa4-6deb1f3916a5",
"name": "middle name",
"protocol": "openid-connect",
"protocolMapper": "oidc-usermodel-attribute-mapper",
"consentRequired": false,
"config": {
"userinfo.token.claim": "true",
"user.attribute": "middleName",
"id.token.claim": "true",
"access.token.claim": "true",
"claim.name": "middle_name",
"jsonType.label": "String"
}
},
{
"id": "042e6e1e-f041-432f-88bc-79421366fb99",
"name": "gender",
"protocol": "openid-connect",
"protocolMapper": "oidc-usermodel-attribute-mapper",
"consentRequired": false,
"config": {
"userinfo.token.claim": "true",
"user.attribute": "gender",
"id.token.claim": "true",
"access.token.claim": "true",
"claim.name": "gender",
"jsonType.label": "String"
}
},
{
"id": "e269f729-2eca-4ff0-9caf-3baa4f6188c5",
"name": "zoneinfo",
"protocol": "openid-connect",
"protocolMapper": "oidc-usermodel-attribute-mapper",
"consentRequired": false,
"config": {
"userinfo.token.claim": "true",
"user.attribute": "zoneinfo",
"id.token.claim": "true",
"access.token.claim": "true",
"claim.name": "zoneinfo",
"jsonType.label": "String"
}
},
{
"id": "b3929aa6-6acf-4b13-9d23-ee459926feef",
"name": "given name",
"protocol": "openid-connect",
"protocolMapper": "oidc-usermodel-property-mapper",
"consentRequired": false,
"config": {
"userinfo.token.claim": "true",
"user.attribute": "firstName",
"id.token.claim": "true",
"access.token.claim": "true",
"claim.name": "given_name",
"jsonType.label": "String"
}
},
{
"id": "877d4b97-2520-40f7-9e58-cd99560a4637",
"name": "profile",
"protocol": "openid-connect",
"protocolMapper": "oidc-usermodel-attribute-mapper",
"consentRequired": false,
"config": {
"userinfo.token.claim": "true",
"user.attribute": "profile",
"id.token.claim": "true",
"access.token.claim": "true",
"claim.name": "profile",
"jsonType.label": "String"
}
},
{
"id": "f6aab00d-4b15-4ef3-a037-50d8a6c047ff",
"name": "family name",
"protocol": "openid-connect",
"protocolMapper": "oidc-usermodel-property-mapper",
"consentRequired": false,
"config": {
"userinfo.token.claim": "true",
"user.attribute": "lastName",
"id.token.claim": "true",
"access.token.claim": "true",
"claim.name": "family_name",
"jsonType.label": "String"
}
},
{
"id": "52483504-1da0-4645-8df0-d7ec36bf835a",
"name": "username",
"protocol": "openid-connect",
"protocolMapper": "oidc-usermodel-property-mapper",
"consentRequired": false,
"config": {
"userinfo.token.claim": "true",
"user.attribute": "username",
"id.token.claim": "true",
"access.token.claim": "true",
"claim.name": "preferred_username",
"jsonType.label": "String"
}
}
]
},
{
"id": "dc401683-7876-4a01-a670-73deae0a10c2",
"name": "role_list",
"description": "SAML role list",
"protocol": "saml",
"attributes": {
"consent.screen.text": "${samlRoleListScopeConsentText}",
"display.on.consent.screen": "true"
},
"protocolMappers": [
{
"id": "4903f029-ca74-4447-b9ac-cf7799f2391c",
"name": "role list",
"protocol": "saml",
"protocolMapper": "saml-role-list-mapper",
"consentRequired": false,
"config": {
"single": "false",
"attribute.nameformat": "Basic",
"attribute.name": "Role"
}
}
]
},
{
"id": "715eec20-9d2b-45cf-b2c3-fd11aae96b63",
"name": "roles",
"description": "OpenID Connect scope for add user roles to the access token",
"protocol": "openid-connect",
"attributes": {
"include.in.token.scope": "false",
"display.on.consent.screen": "true",
"consent.screen.text": "${rolesScopeConsentText}"
},
"protocolMappers": [
{
"id": "d1464021-822d-41d8-8195-d8962fe70f61",
"name": "audience resolve",
"protocol": "openid-connect",
"protocolMapper": "oidc-audience-resolve-mapper",
"consentRequired": false,
"config": {}
},
{
"id": "b85d197d-f195-4dcd-a873-77ee4ec9fcea",
"name": "realm roles",
"protocol": "openid-connect",
"protocolMapper": "oidc-usermodel-realm-role-mapper",
"consentRequired": false,
"config": {
"user.attribute": "foo",
"access.token.claim": "true",
"claim.name": "realm_access.roles",
"jsonType.label": "String",
"multivalued": "true"
}
},
{
"id": "ef5b5c95-5236-41f1-ab9b-3e4213abbe76",
"name": "client roles",
"protocol": "openid-connect",
"protocolMapper": "oidc-usermodel-client-role-mapper",
"consentRequired": false,
"config": {
"user.attribute": "foo",
"access.token.claim": "true",
"claim.name": "resource_access.${client_id}.roles",
"jsonType.label": "String",
"multivalued": "true"
}
}
]
},
{
"id": "30b4d89f-bfd9-45d4-b71f-01dd0f64da57",
"name": "web-origins",
"description": "OpenID Connect scope for add allowed web origins to the access token",
"protocol": "openid-connect",
"attributes": {
"include.in.token.scope": "false",
"display.on.consent.screen": "false",
"consent.screen.text": ""
},
"protocolMappers": [
{
"id": "76f254c5-dc78-4048-abc9-c9de9d55f5a4",
"name": "allowed web origins",
"protocol": "openid-connect",
"protocolMapper": "oidc-allowed-origins-mapper",
"consentRequired": false,
"config": {}
}
]
}
]

View file

@ -0,0 +1,181 @@
import React, { useContext, useState } from "react";
import {
ActionGroup,
AlertVariant,
Button,
Form,
FormGroup,
PageSection,
Select,
SelectVariant,
Switch,
TextInput,
} from "@patternfly/react-core";
import { useTranslation } from "react-i18next";
import { Controller, useForm } from "react-hook-form";
import { ClientScopeRepresentation } from "../models/client-scope";
import { HelpItem } from "../../components/help-enabler/HelpItem";
import { HttpClientContext } from "../../http-service/HttpClientContext";
import { RealmContext } from "../../components/realm-context/RealmContext";
import { useAlerts } from "../../components/alert/Alerts";
export const NewClientScopeForm = () => {
const { t } = useTranslation("client-scopes");
const { register, control, handleSubmit } = useForm<
ClientScopeRepresentation
>();
const httpClient = useContext(HttpClientContext)!;
const { realm } = useContext(RealmContext);
const [open, isOpen] = useState(false);
const [add, Alerts] = useAlerts();
const save = async (clientScopes: ClientScopeRepresentation) => {
try {
const keyValues = Object.keys(clientScopes.attributes!).map((key) => {
const newKey = key.replace(/_/g, ".");
return { [newKey]: clientScopes.attributes![key] };
});
clientScopes.attributes = Object.assign({}, ...keyValues);
await httpClient.doPost(
`/admin/realms/${realm}/client-scopes`,
clientScopes
);
add(t("createClientScopeSuccess"), AlertVariant.success);
} catch (error) {
add(`${t("createClientScopeError")} '${error}'`, AlertVariant.danger);
}
};
return (
<PageSection variant="light">
<Alerts />
<Form isHorizontal onSubmit={handleSubmit(save)}>
<FormGroup
label={
<>
{t("name")} <HelpItem item="clientScope.name" />
</>
}
fieldId="kc-name"
isRequired
>
<TextInput
ref={register({ required: true })}
type="text"
id="kc-name"
name="name"
/>
</FormGroup>
<FormGroup
label={
<>
{t("description")} <HelpItem item="clientScope.description" />
</>
}
fieldId="kc-description"
>
<TextInput
ref={register}
type="text"
id="kc-description"
name="description"
/>
</FormGroup>
<FormGroup
label={
<>
{t("protocol")} <HelpItem item="clientScope.protocol" />
</>
}
fieldId="kc-protocol"
>
<Controller
name="protocol"
defaultValue=""
control={control}
render={({ onChange, value }) => (
<Select
id="kc-protocol"
required
onToggle={() => isOpen(!open)}
onSelect={(_, value, isPlaceholder) => {
onChange(isPlaceholder ? "" : (value as string));
isOpen(false);
}}
selections={value}
variant={SelectVariant.single}
aria-label="Select Encryption type"
isOpen={open}
></Select>
)}
/>
</FormGroup>
<FormGroup
label={
<>
{t("displayOnConsentScreen")}{" "}
<HelpItem item="clientScope.displayOnConsentScreen" />
</>
}
fieldId="kc-display.on.consent.screen"
>
<Controller
name="attributes.display_on_consent_screen"
control={control}
defaultValue={false}
render={({ onChange, value }) => (
<Switch
id="kc-display.on.consent.screen"
label={t("common:on")}
labelOff={t("common:off")}
isChecked={value}
onChange={onChange}
/>
)}
/>
</FormGroup>
<FormGroup
label={
<>
{t("consentScreenText")}{" "}
<HelpItem item="clientScope.consentScreenText" />
</>
}
fieldId="kc-consent-screen-text"
>
<TextInput
ref={register}
type="text"
id="kc-consent-screen-text"
name="attributes.consent_screen_text"
/>
</FormGroup>
<FormGroup
label={
<>
{t("guiOrder")} <HelpItem item="clientScope.guiOrder" />
</>
}
fieldId="kc-gui-order"
>
<TextInput
ref={register}
type="number"
id="kc-gui-order"
name="attributes.gui_order"
/>
</FormGroup>
<ActionGroup>
<Button variant="primary" type="submit">
{t("common:save")}
</Button>
<Button variant="link">{t("common:cancel")}</Button>
</ActionGroup>
</Form>
</PageSection>
);
};

View file

@ -0,0 +1,14 @@
{
"client-scopes": {
"createClientScope": "Create client scope",
"clientScopeList": "List of client scopes",
"name": "Name",
"description": "Description",
"protocol": "Protocol",
"createClientScopeSuccess": "Client scope created",
"createClientScopeError": "Could not create client scope:",
"displayOnConsentScreen": "Display on consent screen",
"consentScreenText": "Consent screen text",
"guiOrder": "GUI Order"
}
}

View file

@ -0,0 +1,24 @@
/**
* https://www.keycloak.org/docs-api/4.1/rest-api/#_protocolmapperrepresentation
*/
export interface ProtocolMapperRepresentation {
config?: Record<string, any>;
id?: string;
name?: string;
protocol?: string;
protocolMapper?: string;
}
/**
* https://www.keycloak.org/docs-api/6.0/rest-api/index.html#_clientscoperepresentation
*/
export interface ClientScopeRepresentation {
attributes?: Record<string, any>;
description?: string;
id?: string;
name?: string;
protocol?: string;
protocolMappers?: ProtocolMapperRepresentation[];
}

View file

@ -1,21 +1,26 @@
import React from "react";
import { FormGroup, TextInput } from "@patternfly/react-core";
import { FieldElement, ValidationRules, Ref } from "react-hook-form";
import { UseFormMethods } from "react-hook-form";
import { useTranslation } from "react-i18next";
type ClientDescriptionProps = {
register<TFieldElement extends FieldElement>(
rules?: ValidationRules
): (ref: (TFieldElement & Ref) | null) => void;
form: UseFormMethods;
};
export const ClientDescription = ({ register }: ClientDescriptionProps) => {
export const ClientDescription = ({ form }: ClientDescriptionProps) => {
const { t } = useTranslation("clients");
const { register, errors } = form;
return (
<>
<FormGroup label={t("clientID")} fieldId="kc-client-id">
<FormGroup
label={t("clientID")}
fieldId="kc-client-id"
helperTextInvalid={t("common:required")}
validated={errors.clientId ? "error" : "default"}
isRequired
>
<TextInput
ref={register()}
ref={register({ required: true })}
type="text"
id="kc-client-id"
name="clientId"

View file

@ -36,16 +36,13 @@ export const ClientList = ({ baseUrl, clients }: ClientListProps) => {
const { realm } = useContext(RealmContext);
const [add, Alerts] = useAlerts();
const convertClientId = (clientId: string) =>
clientId.substring(0, clientId.indexOf("#"));
const enabled = (): IFormatter => (data?: IFormatterValueType) => {
const field = data!.toString();
const value = convertClientId(field);
return field.indexOf("true") !== -1 ? (
<Link to="client-settings">{value}</Link>
) : (
<Link to="client-settings">
{value} <Badge isRead>Disabled</Badge>
const [id, clientId, disabled] = field.split("#");
return (
<Link to={`client-settings/${id}`}>
{clientId}
{disabled !== "true" && <Badge isRead>Disabled</Badge>}
</Link>
);
};
@ -75,13 +72,13 @@ export const ClientList = ({ baseUrl, clients }: ClientListProps) => {
};
const data = clients!
.map((r) => {
r.clientId = r.clientId + "#" + r.enabled;
r.baseUrl = replaceBaseUrl(r);
return r;
.map((client) => {
client.clientId = `${client.id}#${client.clientId}#${client.enabled}`;
client.baseUrl = replaceBaseUrl(client);
return client;
})
.map((c) => {
return { cells: columns.map((col) => c[col]), client: c };
.map((column) => {
return { cells: columns.map((col) => column[col]), client: column };
});
return (
<>
@ -103,7 +100,8 @@ export const ClientList = ({ baseUrl, clients }: ClientListProps) => {
title: t("common:export"),
onClick: (_, rowId) => {
const clientCopy = JSON.parse(JSON.stringify(data[rowId].client));
clientCopy.clientId = convertClientId(clientCopy.clientId);
const [, orgClientId] = clientCopy.clientId.split("#");
clientCopy.clientId = orgClientId;
delete clientCopy.id;
if (clientCopy.protocolMappers) {

View file

@ -1,45 +1,72 @@
import React, { useState, FormEvent } from "react";
import React, { useContext, useEffect } from "react";
import { useTranslation } from "react-i18next";
import {
FormGroup,
TextInput,
Form,
Dropdown,
DropdownToggle,
DropdownItem,
Switch,
TextArea,
PageSection,
ActionGroup,
Button,
AlertVariant,
} from "@patternfly/react-core";
import { useForm } from "react-hook-form";
import { useParams } from "react-router-dom";
import { Controller, useForm } from "react-hook-form";
import { ScrollForm } from "../components/scroll-form/ScrollForm";
import { ClientDescription } from "./ClientDescription";
import { ClientRepresentation } from "./models/client-model";
import { CapabilityConfig } from "./add/CapabilityConfig";
import { RealmContext } from "../components/realm-context/RealmContext";
import { HttpClientContext } from "../http-service/HttpClientContext";
import { ClientRepresentation } from "../realm/models/Realm";
import {
convertToMultiline,
MultiLineInput,
toValue,
} from "../components/multi-line-input/MultiLineInput";
import { useAlerts } from "../components/alert/Alerts";
type ClientSettingsProps = {
client: ClientRepresentation;
};
export const ClientSettings = ({ client: clientInit }: ClientSettingsProps) => {
export const ClientSettings = () => {
const { t } = useTranslation("clients");
const [client, setClient] = useState({ ...clientInit });
const form = useForm();
const onChange = (
value: string | boolean,
event: FormEvent<HTMLInputElement>
) => {
const target = event.target;
const name = (target as HTMLInputElement).name;
const httpClient = useContext(HttpClientContext)!;
const { realm } = useContext(RealmContext);
const [addAlert, Alerts] = useAlerts();
setClient({
...client,
[name]: value,
});
const { id } = useParams<{ id: string }>();
const form = useForm();
const url = `/admin/realms/${realm}/clients/${id}`;
useEffect(() => {
(async () => {
const fetchedClient = await httpClient.doGet<ClientRepresentation>(url);
if (fetchedClient.data) {
Object.entries(fetchedClient.data).map((entry) => {
if (entry[0] !== "redirectUris") {
form.setValue(entry[0], entry[1]);
} else if (entry[1] && entry[1].length > 0) {
form.setValue(entry[0], convertToMultiline(entry[1]));
}
});
}
})();
}, []);
const save = async () => {
if (await form.trigger()) {
const redirectUris = toValue(form.getValues()["redirectUris"]);
try {
httpClient.doPut(url, { ...form.getValues(), redirectUris });
addAlert(t("clientSaveSuccess"), AlertVariant.success);
} catch (error) {
addAlert(`${t("clientSaveError")} '${error}'`, AlertVariant.danger);
}
}
};
return (
<PageSection>
<Alerts />
<ScrollForm
sections={[
t("capabilityConfig"),
@ -48,11 +75,9 @@ export const ClientSettings = ({ client: clientInit }: ClientSettingsProps) => {
t("loginSettings"),
]}
>
<CapabilityConfig form={form} />
<Form isHorizontal>
<CapabilityConfig form={form} />
</Form>
<Form isHorizontal>
<ClientDescription register={form.register} />
<ClientDescription form={form} />
</Form>
<Form isHorizontal>
<FormGroup label={t("rootUrl")} fieldId="kc-root-url">
@ -60,64 +85,55 @@ export const ClientSettings = ({ client: clientInit }: ClientSettingsProps) => {
type="text"
id="kc-root-url"
name="rootUrl"
value={client.rootUrl}
onChange={onChange}
ref={form.register}
/>
</FormGroup>
<FormGroup label={t("validRedirectUri")} fieldId="kc-redirect">
<TextInput
type="text"
id="kc-redirect"
name="redirectUris"
onChange={onChange}
/>
<MultiLineInput form={form} name="redirectUris" />
</FormGroup>
<FormGroup label={t("homeURL")} fieldId="kc-home-url">
<TextInput
type="text"
id="kc-home-url"
name="baseUrl"
value={client.baseUrl}
onChange={onChange}
ref={form.register}
/>
</FormGroup>
</Form>
<Form isHorizontal>
<FormGroup label={t("loginTheme")} fieldId="kc-login-theme">
<Dropdown
id="kc-login-theme"
toggle={
<DropdownToggle id="toggle-id" onToggle={() => {}}>
{t("loginTheme")}
</DropdownToggle>
}
dropdownItems={[
<DropdownItem key="link">Link</DropdownItem>,
<DropdownItem key="action" component="button" />,
]}
/>
</FormGroup>
<FormGroup label={t("consentRequired")} fieldId="kc-consent">
<Switch
id="kc-consent"
<Controller
name="consentRequired"
label={t("common:on")}
labelOff={t("common:off")}
isChecked={client.consentRequired}
onChange={onChange}
defaultValue={false}
control={form.control}
render={({ onChange, value }) => (
<Switch
id="kc-consent"
label={t("common:on")}
labelOff={t("common:off")}
isChecked={value}
onChange={onChange}
/>
)}
/>
</FormGroup>
<FormGroup
label={t("displayOnClient")}
fieldId="kc-display-on-client"
>
<Switch
id="kc-display-on-client"
<Controller
name="alwaysDisplayInConsole"
label={t("common:on")}
labelOff={t("common:off")}
isChecked={client.alwaysDisplayInConsole}
onChange={onChange}
defaultValue={false}
control={form.control}
render={({ onChange, value }) => (
<Switch
id="kc-display-on-client"
label={t("common:on")}
labelOff={t("common:off")}
isChecked={value}
onChange={onChange}
/>
)}
/>
</FormGroup>
<FormGroup
@ -127,9 +143,15 @@ export const ClientSettings = ({ client: clientInit }: ClientSettingsProps) => {
<TextArea
id="kc-consent-screen-text"
name="consentText"
//value={client.protocolMappers![0].consentText}
ref={form.register}
/>
</FormGroup>
<ActionGroup>
<Button variant="primary" onClick={() => save()}>
{t("common:save")}
</Button>
<Button variant="link">{t("common:cancel")}</Button>
</ActionGroup>
</Form>
</ScrollForm>
</PageSection>

View file

@ -10,6 +10,7 @@ import { HttpClientContext } from "../http-service/HttpClientContext";
import { KeycloakContext } from "../auth/KeycloakContext";
import { ClientRepresentation } from "./models/client-model";
import { RealmContext } from "../components/realm-context/RealmContext";
import { ViewHeader } from "../components/view-header/ViewHeader";
export const ClientsSection = () => {
const { t } = useTranslation("clients");
@ -28,40 +29,48 @@ export const ClientsSection = () => {
};
return (
<PageSection variant="light">
<DataLoader loader={loader}>
{(clients) => (
<TableToolbar
count={clients!.length}
first={first}
max={max}
onNextClick={setFirst}
onPreviousClick={setFirst}
onPerPageSelect={(f, m) => {
setFirst(f);
setMax(m);
}}
toolbarItem={
<>
<Button onClick={() => history.push("/add-client")}>
{t("createClient")}
</Button>
<Button
onClick={() => history.push("/import-client")}
variant="link"
>
{t("importClient")}
</Button>
</>
}
>
<ClientList
clients={clients}
baseUrl={keycloak!.authServerUrl()!}
/>
</TableToolbar>
)}
</DataLoader>
</PageSection>
<>
<ViewHeader
titleKey="clients:clientList"
subKey="clients:clientsExplain"
/>
<PageSection variant="light">
<DataLoader loader={loader}>
{(clients) => (
<TableToolbar
count={clients!.length}
first={first}
max={max}
onNextClick={setFirst}
onPreviousClick={setFirst}
onPerPageSelect={(first, max) => {
setFirst(first);
setMax(max);
}}
inputGroupName="clientsToolbarTextInput"
inputGroupPlaceholder={t("Search for client")}
toolbarItem={
<>
<Button onClick={() => history.push("/add-client")}>
{t("createClient")}
</Button>
<Button
onClick={() => history.push("/import-client")}
variant="link"
>
{t("importClient")}
</Button>
</>
}
>
<ClientList
clients={clients}
baseUrl={keycloak!.authServerUrl()!}
/>
</TableToolbar>
)}
</DataLoader>
</PageSection>
</>
);
};

View file

@ -8,7 +8,7 @@ Object {
<table
aria-label="clientList"
class="pf-c-table pf-m-grid-md pf-m-compact"
data-ouia-component-id="0"
data-ouia-component-id="OUIA-Generated-Table-1"
data-ouia-component-type="PF4/Table"
data-ouia-safe="true"
role="grid"
@ -61,7 +61,7 @@ Object {
>
<tr
class=""
data-ouia-component-id="1"
data-ouia-component-id="OUIA-Generated-TableRow-1"
data-ouia-component-type="PF4/TableRow"
data-ouia-safe="true"
>
@ -71,7 +71,7 @@ Object {
data-label="clientID"
>
<a
href="/client-settings"
href="/client-settings/767756c2-21f8-431c-9f4b-edf30654d653"
>
account
</a>
@ -96,24 +96,31 @@ Object {
data-label="homeURL"
>
<a
aria-disabled="false"
class="pf-c-button pf-m-link"
data-ouia-component-id="OUIA-Generated-Button-link-1"
data-ouia-component-type="PF4/Button"
data-ouia-safe="true"
href="http://blog.nerdin.chrealms/master/account/"
>
http://blog.nerdin.chrealms/master/account/
<svg
aria-hidden="true"
fill="currentColor"
height="1em"
role="img"
style="vertical-align: -0.125em;"
viewBox="0 0 512 512"
width="1em"
<span
class="pf-c-button__icon pf-m-end"
>
<path
d="M432,320H400a16,16,0,0,0-16,16V448H64V128H208a16,16,0,0,0,16-16V80a16,16,0,0,0-16-16H48A48,48,0,0,0,0,112V464a48,48,0,0,0,48,48H400a48,48,0,0,0,48-48V336A16,16,0,0,0,432,320ZM488,0h-128c-21.37,0-32.05,25.91-17,41l35.73,35.73L135,320.37a24,24,0,0,0,0,34L157.67,377a24,24,0,0,0,34,0L435.28,133.32,471,169c15,15,41,4.5,41-17V24A24,24,0,0,0,488,0Z"
transform=""
/>
</svg>
<svg
aria-hidden="true"
fill="currentColor"
height="1em"
role="img"
style="vertical-align: -0.125em;"
viewBox="0 0 512 512"
width="1em"
>
<path
d="M432,320H400a16,16,0,0,0-16,16V448H64V128H208a16,16,0,0,0,16-16V80a16,16,0,0,0-16-16H48A48,48,0,0,0,0,112V464a48,48,0,0,0,48,48H400a48,48,0,0,0,48-48V336A16,16,0,0,0,432,320ZM488,0h-128c-21.37,0-32.05,25.91-17,41l35.73,35.73L135,320.37a24,24,0,0,0,0,34L157.67,377a24,24,0,0,0,34,0L435.28,133.32,471,169c15,15,41,4.5,41-17V24A24,24,0,0,0,488,0Z"
/>
</svg>
</span>
</a>
</td>
<td
@ -122,7 +129,7 @@ Object {
>
<div
class="pf-c-dropdown pf-m-align-right"
data-ouia-component-id="2"
data-ouia-component-id="OUIA-Generated-Dropdown-1"
data-ouia-component-type="PF4/Dropdown"
data-ouia-safe="true"
>
@ -145,7 +152,6 @@ Object {
>
<path
d="M96 184c39.8 0 72 32.2 72 72s-32.2 72-72 72-72-32.2-72-72 32.2-72 72-72zM24 80c0 39.8 32.2 72 72 72s72-32.2 72-72S135.8 8 96 8 24 40.2 24 80zm0 352c0 39.8 32.2 72 72 72s72-32.2 72-72-32.2-72-72-72-72 32.2-72 72z"
transform=""
/>
</svg>
</button>
@ -154,7 +160,7 @@ Object {
</tr>
<tr
class=""
data-ouia-component-id="3"
data-ouia-component-id="OUIA-Generated-TableRow-2"
data-ouia-component-type="PF4/TableRow"
data-ouia-safe="true"
>
@ -164,7 +170,7 @@ Object {
data-label="clientID"
>
<a
href="/client-settings"
href="/client-settings/337dc87b-e08d-409e-aaac-6ab7df4b925b"
>
account-console
</a>
@ -189,24 +195,31 @@ Object {
data-label="homeURL"
>
<a
aria-disabled="false"
class="pf-c-button pf-m-link"
data-ouia-component-id="OUIA-Generated-Button-link-2"
data-ouia-component-type="PF4/Button"
data-ouia-safe="true"
href="http://blog.nerdin.chrealms/master/account/"
>
http://blog.nerdin.chrealms/master/account/
<svg
aria-hidden="true"
fill="currentColor"
height="1em"
role="img"
style="vertical-align: -0.125em;"
viewBox="0 0 512 512"
width="1em"
<span
class="pf-c-button__icon pf-m-end"
>
<path
d="M432,320H400a16,16,0,0,0-16,16V448H64V128H208a16,16,0,0,0,16-16V80a16,16,0,0,0-16-16H48A48,48,0,0,0,0,112V464a48,48,0,0,0,48,48H400a48,48,0,0,0,48-48V336A16,16,0,0,0,432,320ZM488,0h-128c-21.37,0-32.05,25.91-17,41l35.73,35.73L135,320.37a24,24,0,0,0,0,34L157.67,377a24,24,0,0,0,34,0L435.28,133.32,471,169c15,15,41,4.5,41-17V24A24,24,0,0,0,488,0Z"
transform=""
/>
</svg>
<svg
aria-hidden="true"
fill="currentColor"
height="1em"
role="img"
style="vertical-align: -0.125em;"
viewBox="0 0 512 512"
width="1em"
>
<path
d="M432,320H400a16,16,0,0,0-16,16V448H64V128H208a16,16,0,0,0,16-16V80a16,16,0,0,0-16-16H48A48,48,0,0,0,0,112V464a48,48,0,0,0,48,48H400a48,48,0,0,0,48-48V336A16,16,0,0,0,432,320ZM488,0h-128c-21.37,0-32.05,25.91-17,41l35.73,35.73L135,320.37a24,24,0,0,0,0,34L157.67,377a24,24,0,0,0,34,0L435.28,133.32,471,169c15,15,41,4.5,41-17V24A24,24,0,0,0,488,0Z"
/>
</svg>
</span>
</a>
</td>
<td
@ -215,7 +228,7 @@ Object {
>
<div
class="pf-c-dropdown pf-m-align-right"
data-ouia-component-id="4"
data-ouia-component-id="OUIA-Generated-Dropdown-2"
data-ouia-component-type="PF4/Dropdown"
data-ouia-safe="true"
>
@ -238,7 +251,6 @@ Object {
>
<path
d="M96 184c39.8 0 72 32.2 72 72s-32.2 72-72 72-72-32.2-72-72 32.2-72 72-72zM24 80c0 39.8 32.2 72 72 72s72-32.2 72-72S135.8 8 96 8 24 40.2 24 80zm0 352c0 39.8 32.2 72 72 72s72-32.2 72-72-32.2-72-72-72-72 32.2-72 72z"
transform=""
/>
</svg>
</button>
@ -247,7 +259,7 @@ Object {
</tr>
<tr
class=""
data-ouia-component-id="5"
data-ouia-component-id="OUIA-Generated-TableRow-3"
data-ouia-component-type="PF4/TableRow"
data-ouia-safe="true"
>
@ -257,7 +269,7 @@ Object {
data-label="clientID"
>
<a
href="/client-settings"
href="/client-settings/60d59afe-7926-4c22-b829-798125793ef5"
>
admin-cli
</a>
@ -289,7 +301,7 @@ Object {
>
<div
class="pf-c-dropdown pf-m-align-right"
data-ouia-component-id="6"
data-ouia-component-id="OUIA-Generated-Dropdown-3"
data-ouia-component-type="PF4/Dropdown"
data-ouia-safe="true"
>
@ -312,7 +324,6 @@ Object {
>
<path
d="M96 184c39.8 0 72 32.2 72 72s-32.2 72-72 72-72-32.2-72-72 32.2-72 72-72zM24 80c0 39.8 32.2 72 72 72s72-32.2 72-72S135.8 8 96 8 24 40.2 24 80zm0 352c0 39.8 32.2 72 72 72s72-32.2 72-72-32.2-72-72-72-72 32.2-72 72z"
transform=""
/>
</svg>
</button>
@ -321,7 +332,7 @@ Object {
</tr>
<tr
class=""
data-ouia-component-id="7"
data-ouia-component-id="OUIA-Generated-TableRow-4"
data-ouia-component-type="PF4/TableRow"
data-ouia-safe="true"
>
@ -331,7 +342,7 @@ Object {
data-label="clientID"
>
<a
href="/client-settings"
href="/client-settings/c2d74093-2b8c-4ecb-870f-c7358ff48237"
>
broker
</a>
@ -363,7 +374,7 @@ Object {
>
<div
class="pf-c-dropdown pf-m-align-right"
data-ouia-component-id="8"
data-ouia-component-id="OUIA-Generated-Dropdown-4"
data-ouia-component-type="PF4/Dropdown"
data-ouia-safe="true"
>
@ -386,7 +397,6 @@ Object {
>
<path
d="M96 184c39.8 0 72 32.2 72 72s-32.2 72-72 72-72-32.2-72-72 32.2-72 72-72zM24 80c0 39.8 32.2 72 72 72s72-32.2 72-72S135.8 8 96 8 24 40.2 24 80zm0 352c0 39.8 32.2 72 72 72s72-32.2 72-72-32.2-72-72-72-72 32.2-72 72z"
transform=""
/>
</svg>
</button>
@ -395,7 +405,7 @@ Object {
</tr>
<tr
class=""
data-ouia-component-id="9"
data-ouia-component-id="OUIA-Generated-TableRow-5"
data-ouia-component-type="PF4/TableRow"
data-ouia-safe="true"
>
@ -405,7 +415,7 @@ Object {
data-label="clientID"
>
<a
href="/client-settings"
href="/client-settings/66135023-e667-4864-b1f3-f87e805fabc2"
>
master-realm
</a>
@ -435,7 +445,7 @@ Object {
>
<div
class="pf-c-dropdown pf-m-align-right"
data-ouia-component-id="10"
data-ouia-component-id="OUIA-Generated-Dropdown-5"
data-ouia-component-type="PF4/Dropdown"
data-ouia-safe="true"
>
@ -458,7 +468,6 @@ Object {
>
<path
d="M96 184c39.8 0 72 32.2 72 72s-32.2 72-72 72-72-32.2-72-72 32.2-72 72-72zM24 80c0 39.8 32.2 72 72 72s72-32.2 72-72S135.8 8 96 8 24 40.2 24 80zm0 352c0 39.8 32.2 72 72 72s72-32.2 72-72-32.2-72-72-72-72 32.2-72 72z"
transform=""
/>
</svg>
</button>
@ -467,7 +476,7 @@ Object {
</tr>
<tr
class=""
data-ouia-component-id="11"
data-ouia-component-id="OUIA-Generated-TableRow-6"
data-ouia-component-type="PF4/TableRow"
data-ouia-safe="true"
>
@ -477,7 +486,7 @@ Object {
data-label="clientID"
>
<a
href="/client-settings"
href="/client-settings/324f4182-d302-44f8-ac8a-149eaa29dc90"
>
new
</a>
@ -502,24 +511,31 @@ Object {
data-label="homeURL"
>
<a
aria-disabled="false"
class="pf-c-button pf-m-link"
data-ouia-component-id="OUIA-Generated-Button-link-3"
data-ouia-component-type="PF4/Button"
data-ouia-safe="true"
href="http://localhost:8080/"
>
http://localhost:8080/
<svg
aria-hidden="true"
fill="currentColor"
height="1em"
role="img"
style="vertical-align: -0.125em;"
viewBox="0 0 512 512"
width="1em"
<span
class="pf-c-button__icon pf-m-end"
>
<path
d="M432,320H400a16,16,0,0,0-16,16V448H64V128H208a16,16,0,0,0,16-16V80a16,16,0,0,0-16-16H48A48,48,0,0,0,0,112V464a48,48,0,0,0,48,48H400a48,48,0,0,0,48-48V336A16,16,0,0,0,432,320ZM488,0h-128c-21.37,0-32.05,25.91-17,41l35.73,35.73L135,320.37a24,24,0,0,0,0,34L157.67,377a24,24,0,0,0,34,0L435.28,133.32,471,169c15,15,41,4.5,41-17V24A24,24,0,0,0,488,0Z"
transform=""
/>
</svg>
<svg
aria-hidden="true"
fill="currentColor"
height="1em"
role="img"
style="vertical-align: -0.125em;"
viewBox="0 0 512 512"
width="1em"
>
<path
d="M432,320H400a16,16,0,0,0-16,16V448H64V128H208a16,16,0,0,0,16-16V80a16,16,0,0,0-16-16H48A48,48,0,0,0,0,112V464a48,48,0,0,0,48,48H400a48,48,0,0,0,48-48V336A16,16,0,0,0,432,320ZM488,0h-128c-21.37,0-32.05,25.91-17,41l35.73,35.73L135,320.37a24,24,0,0,0,0,34L157.67,377a24,24,0,0,0,34,0L435.28,133.32,471,169c15,15,41,4.5,41-17V24A24,24,0,0,0,488,0Z"
/>
</svg>
</span>
</a>
</td>
<td
@ -528,7 +544,7 @@ Object {
>
<div
class="pf-c-dropdown pf-m-align-right"
data-ouia-component-id="12"
data-ouia-component-id="OUIA-Generated-Dropdown-6"
data-ouia-component-type="PF4/Dropdown"
data-ouia-safe="true"
>
@ -551,7 +567,6 @@ Object {
>
<path
d="M96 184c39.8 0 72 32.2 72 72s-32.2 72-72 72-72-32.2-72-72 32.2-72 72-72zM24 80c0 39.8 32.2 72 72 72s72-32.2 72-72S135.8 8 96 8 24 40.2 24 80zm0 352c0 39.8 32.2 72 72 72s72-32.2 72-72-32.2-72-72-72-72 32.2-72 72z"
transform=""
/>
</svg>
</button>
@ -560,7 +575,7 @@ Object {
</tr>
<tr
class=""
data-ouia-component-id="13"
data-ouia-component-id="OUIA-Generated-TableRow-7"
data-ouia-component-type="PF4/TableRow"
data-ouia-safe="true"
>
@ -570,7 +585,7 @@ Object {
data-label="clientID"
>
<a
href="/client-settings"
href="/client-settings/fb45882b-4d85-4f40-920e-6a68298d36d0"
>
photoz-realm
</a>
@ -600,7 +615,7 @@ Object {
>
<div
class="pf-c-dropdown pf-m-align-right"
data-ouia-component-id="14"
data-ouia-component-id="OUIA-Generated-Dropdown-7"
data-ouia-component-type="PF4/Dropdown"
data-ouia-safe="true"
>
@ -623,7 +638,6 @@ Object {
>
<path
d="M96 184c39.8 0 72 32.2 72 72s-32.2 72-72 72-72-32.2-72-72 32.2-72 72-72zM24 80c0 39.8 32.2 72 72 72s72-32.2 72-72S135.8 8 96 8 24 40.2 24 80zm0 352c0 39.8 32.2 72 72 72s72-32.2 72-72-32.2-72-72-72-72 32.2-72 72z"
transform=""
/>
</svg>
</button>
@ -632,7 +646,7 @@ Object {
</tr>
<tr
class=""
data-ouia-component-id="15"
data-ouia-component-id="OUIA-Generated-TableRow-8"
data-ouia-component-type="PF4/TableRow"
data-ouia-safe="true"
>
@ -642,7 +656,7 @@ Object {
data-label="clientID"
>
<a
href="/client-settings"
href="/client-settings/9ed60e41-d794-4046-842f-3247bf32f5ce"
>
security-admin-console
</a>
@ -667,24 +681,31 @@ Object {
data-label="homeURL"
>
<a
aria-disabled="false"
class="pf-c-button pf-m-link"
data-ouia-component-id="OUIA-Generated-Button-link-4"
data-ouia-component-type="PF4/Button"
data-ouia-safe="true"
href="http://blog.nerdin.chadmin/master/console/"
>
http://blog.nerdin.chadmin/master/console/
<svg
aria-hidden="true"
fill="currentColor"
height="1em"
role="img"
style="vertical-align: -0.125em;"
viewBox="0 0 512 512"
width="1em"
<span
class="pf-c-button__icon pf-m-end"
>
<path
d="M432,320H400a16,16,0,0,0-16,16V448H64V128H208a16,16,0,0,0,16-16V80a16,16,0,0,0-16-16H48A48,48,0,0,0,0,112V464a48,48,0,0,0,48,48H400a48,48,0,0,0,48-48V336A16,16,0,0,0,432,320ZM488,0h-128c-21.37,0-32.05,25.91-17,41l35.73,35.73L135,320.37a24,24,0,0,0,0,34L157.67,377a24,24,0,0,0,34,0L435.28,133.32,471,169c15,15,41,4.5,41-17V24A24,24,0,0,0,488,0Z"
transform=""
/>
</svg>
<svg
aria-hidden="true"
fill="currentColor"
height="1em"
role="img"
style="vertical-align: -0.125em;"
viewBox="0 0 512 512"
width="1em"
>
<path
d="M432,320H400a16,16,0,0,0-16,16V448H64V128H208a16,16,0,0,0,16-16V80a16,16,0,0,0-16-16H48A48,48,0,0,0,0,112V464a48,48,0,0,0,48,48H400a48,48,0,0,0,48-48V336A16,16,0,0,0,432,320ZM488,0h-128c-21.37,0-32.05,25.91-17,41l35.73,35.73L135,320.37a24,24,0,0,0,0,34L157.67,377a24,24,0,0,0,34,0L435.28,133.32,471,169c15,15,41,4.5,41-17V24A24,24,0,0,0,488,0Z"
/>
</svg>
</span>
</a>
</td>
<td
@ -693,7 +714,7 @@ Object {
>
<div
class="pf-c-dropdown pf-m-align-right"
data-ouia-component-id="16"
data-ouia-component-id="OUIA-Generated-Dropdown-8"
data-ouia-component-type="PF4/Dropdown"
data-ouia-safe="true"
>
@ -716,7 +737,6 @@ Object {
>
<path
d="M96 184c39.8 0 72 32.2 72 72s-32.2 72-72 72-72-32.2-72-72 32.2-72 72-72zM24 80c0 39.8 32.2 72 72 72s72-32.2 72-72S135.8 8 96 8 24 40.2 24 80zm0 352c0 39.8 32.2 72 72 72s72-32.2 72-72-32.2-72-72-72-72 32.2-72 72z"
transform=""
/>
</svg>
</button>
@ -736,7 +756,7 @@ Object {
<table
aria-label="clientList"
class="pf-c-table pf-m-grid-md pf-m-compact"
data-ouia-component-id="0"
data-ouia-component-id="OUIA-Generated-Table-1"
data-ouia-component-type="PF4/Table"
data-ouia-safe="true"
role="grid"
@ -789,7 +809,7 @@ Object {
>
<tr
class=""
data-ouia-component-id="1"
data-ouia-component-id="OUIA-Generated-TableRow-1"
data-ouia-component-type="PF4/TableRow"
data-ouia-safe="true"
>
@ -799,7 +819,7 @@ Object {
data-label="clientID"
>
<a
href="/client-settings"
href="/client-settings/767756c2-21f8-431c-9f4b-edf30654d653"
>
account
</a>
@ -824,24 +844,31 @@ Object {
data-label="homeURL"
>
<a
aria-disabled="false"
class="pf-c-button pf-m-link"
data-ouia-component-id="OUIA-Generated-Button-link-1"
data-ouia-component-type="PF4/Button"
data-ouia-safe="true"
href="http://blog.nerdin.chrealms/master/account/"
>
http://blog.nerdin.chrealms/master/account/
<svg
aria-hidden="true"
fill="currentColor"
height="1em"
role="img"
style="vertical-align: -0.125em;"
viewBox="0 0 512 512"
width="1em"
<span
class="pf-c-button__icon pf-m-end"
>
<path
d="M432,320H400a16,16,0,0,0-16,16V448H64V128H208a16,16,0,0,0,16-16V80a16,16,0,0,0-16-16H48A48,48,0,0,0,0,112V464a48,48,0,0,0,48,48H400a48,48,0,0,0,48-48V336A16,16,0,0,0,432,320ZM488,0h-128c-21.37,0-32.05,25.91-17,41l35.73,35.73L135,320.37a24,24,0,0,0,0,34L157.67,377a24,24,0,0,0,34,0L435.28,133.32,471,169c15,15,41,4.5,41-17V24A24,24,0,0,0,488,0Z"
transform=""
/>
</svg>
<svg
aria-hidden="true"
fill="currentColor"
height="1em"
role="img"
style="vertical-align: -0.125em;"
viewBox="0 0 512 512"
width="1em"
>
<path
d="M432,320H400a16,16,0,0,0-16,16V448H64V128H208a16,16,0,0,0,16-16V80a16,16,0,0,0-16-16H48A48,48,0,0,0,0,112V464a48,48,0,0,0,48,48H400a48,48,0,0,0,48-48V336A16,16,0,0,0,432,320ZM488,0h-128c-21.37,0-32.05,25.91-17,41l35.73,35.73L135,320.37a24,24,0,0,0,0,34L157.67,377a24,24,0,0,0,34,0L435.28,133.32,471,169c15,15,41,4.5,41-17V24A24,24,0,0,0,488,0Z"
/>
</svg>
</span>
</a>
</td>
<td
@ -850,7 +877,7 @@ Object {
>
<div
class="pf-c-dropdown pf-m-align-right"
data-ouia-component-id="2"
data-ouia-component-id="OUIA-Generated-Dropdown-1"
data-ouia-component-type="PF4/Dropdown"
data-ouia-safe="true"
>
@ -873,7 +900,6 @@ Object {
>
<path
d="M96 184c39.8 0 72 32.2 72 72s-32.2 72-72 72-72-32.2-72-72 32.2-72 72-72zM24 80c0 39.8 32.2 72 72 72s72-32.2 72-72S135.8 8 96 8 24 40.2 24 80zm0 352c0 39.8 32.2 72 72 72s72-32.2 72-72-32.2-72-72-72-72 32.2-72 72z"
transform=""
/>
</svg>
</button>
@ -882,7 +908,7 @@ Object {
</tr>
<tr
class=""
data-ouia-component-id="3"
data-ouia-component-id="OUIA-Generated-TableRow-2"
data-ouia-component-type="PF4/TableRow"
data-ouia-safe="true"
>
@ -892,7 +918,7 @@ Object {
data-label="clientID"
>
<a
href="/client-settings"
href="/client-settings/337dc87b-e08d-409e-aaac-6ab7df4b925b"
>
account-console
</a>
@ -917,24 +943,31 @@ Object {
data-label="homeURL"
>
<a
aria-disabled="false"
class="pf-c-button pf-m-link"
data-ouia-component-id="OUIA-Generated-Button-link-2"
data-ouia-component-type="PF4/Button"
data-ouia-safe="true"
href="http://blog.nerdin.chrealms/master/account/"
>
http://blog.nerdin.chrealms/master/account/
<svg
aria-hidden="true"
fill="currentColor"
height="1em"
role="img"
style="vertical-align: -0.125em;"
viewBox="0 0 512 512"
width="1em"
<span
class="pf-c-button__icon pf-m-end"
>
<path
d="M432,320H400a16,16,0,0,0-16,16V448H64V128H208a16,16,0,0,0,16-16V80a16,16,0,0,0-16-16H48A48,48,0,0,0,0,112V464a48,48,0,0,0,48,48H400a48,48,0,0,0,48-48V336A16,16,0,0,0,432,320ZM488,0h-128c-21.37,0-32.05,25.91-17,41l35.73,35.73L135,320.37a24,24,0,0,0,0,34L157.67,377a24,24,0,0,0,34,0L435.28,133.32,471,169c15,15,41,4.5,41-17V24A24,24,0,0,0,488,0Z"
transform=""
/>
</svg>
<svg
aria-hidden="true"
fill="currentColor"
height="1em"
role="img"
style="vertical-align: -0.125em;"
viewBox="0 0 512 512"
width="1em"
>
<path
d="M432,320H400a16,16,0,0,0-16,16V448H64V128H208a16,16,0,0,0,16-16V80a16,16,0,0,0-16-16H48A48,48,0,0,0,0,112V464a48,48,0,0,0,48,48H400a48,48,0,0,0,48-48V336A16,16,0,0,0,432,320ZM488,0h-128c-21.37,0-32.05,25.91-17,41l35.73,35.73L135,320.37a24,24,0,0,0,0,34L157.67,377a24,24,0,0,0,34,0L435.28,133.32,471,169c15,15,41,4.5,41-17V24A24,24,0,0,0,488,0Z"
/>
</svg>
</span>
</a>
</td>
<td
@ -943,7 +976,7 @@ Object {
>
<div
class="pf-c-dropdown pf-m-align-right"
data-ouia-component-id="4"
data-ouia-component-id="OUIA-Generated-Dropdown-2"
data-ouia-component-type="PF4/Dropdown"
data-ouia-safe="true"
>
@ -966,7 +999,6 @@ Object {
>
<path
d="M96 184c39.8 0 72 32.2 72 72s-32.2 72-72 72-72-32.2-72-72 32.2-72 72-72zM24 80c0 39.8 32.2 72 72 72s72-32.2 72-72S135.8 8 96 8 24 40.2 24 80zm0 352c0 39.8 32.2 72 72 72s72-32.2 72-72-32.2-72-72-72-72 32.2-72 72z"
transform=""
/>
</svg>
</button>
@ -975,7 +1007,7 @@ Object {
</tr>
<tr
class=""
data-ouia-component-id="5"
data-ouia-component-id="OUIA-Generated-TableRow-3"
data-ouia-component-type="PF4/TableRow"
data-ouia-safe="true"
>
@ -985,7 +1017,7 @@ Object {
data-label="clientID"
>
<a
href="/client-settings"
href="/client-settings/60d59afe-7926-4c22-b829-798125793ef5"
>
admin-cli
</a>
@ -1017,7 +1049,7 @@ Object {
>
<div
class="pf-c-dropdown pf-m-align-right"
data-ouia-component-id="6"
data-ouia-component-id="OUIA-Generated-Dropdown-3"
data-ouia-component-type="PF4/Dropdown"
data-ouia-safe="true"
>
@ -1040,7 +1072,6 @@ Object {
>
<path
d="M96 184c39.8 0 72 32.2 72 72s-32.2 72-72 72-72-32.2-72-72 32.2-72 72-72zM24 80c0 39.8 32.2 72 72 72s72-32.2 72-72S135.8 8 96 8 24 40.2 24 80zm0 352c0 39.8 32.2 72 72 72s72-32.2 72-72-32.2-72-72-72-72 32.2-72 72z"
transform=""
/>
</svg>
</button>
@ -1049,7 +1080,7 @@ Object {
</tr>
<tr
class=""
data-ouia-component-id="7"
data-ouia-component-id="OUIA-Generated-TableRow-4"
data-ouia-component-type="PF4/TableRow"
data-ouia-safe="true"
>
@ -1059,7 +1090,7 @@ Object {
data-label="clientID"
>
<a
href="/client-settings"
href="/client-settings/c2d74093-2b8c-4ecb-870f-c7358ff48237"
>
broker
</a>
@ -1091,7 +1122,7 @@ Object {
>
<div
class="pf-c-dropdown pf-m-align-right"
data-ouia-component-id="8"
data-ouia-component-id="OUIA-Generated-Dropdown-4"
data-ouia-component-type="PF4/Dropdown"
data-ouia-safe="true"
>
@ -1114,7 +1145,6 @@ Object {
>
<path
d="M96 184c39.8 0 72 32.2 72 72s-32.2 72-72 72-72-32.2-72-72 32.2-72 72-72zM24 80c0 39.8 32.2 72 72 72s72-32.2 72-72S135.8 8 96 8 24 40.2 24 80zm0 352c0 39.8 32.2 72 72 72s72-32.2 72-72-32.2-72-72-72-72 32.2-72 72z"
transform=""
/>
</svg>
</button>
@ -1123,7 +1153,7 @@ Object {
</tr>
<tr
class=""
data-ouia-component-id="9"
data-ouia-component-id="OUIA-Generated-TableRow-5"
data-ouia-component-type="PF4/TableRow"
data-ouia-safe="true"
>
@ -1133,7 +1163,7 @@ Object {
data-label="clientID"
>
<a
href="/client-settings"
href="/client-settings/66135023-e667-4864-b1f3-f87e805fabc2"
>
master-realm
</a>
@ -1163,7 +1193,7 @@ Object {
>
<div
class="pf-c-dropdown pf-m-align-right"
data-ouia-component-id="10"
data-ouia-component-id="OUIA-Generated-Dropdown-5"
data-ouia-component-type="PF4/Dropdown"
data-ouia-safe="true"
>
@ -1186,7 +1216,6 @@ Object {
>
<path
d="M96 184c39.8 0 72 32.2 72 72s-32.2 72-72 72-72-32.2-72-72 32.2-72 72-72zM24 80c0 39.8 32.2 72 72 72s72-32.2 72-72S135.8 8 96 8 24 40.2 24 80zm0 352c0 39.8 32.2 72 72 72s72-32.2 72-72-32.2-72-72-72-72 32.2-72 72z"
transform=""
/>
</svg>
</button>
@ -1195,7 +1224,7 @@ Object {
</tr>
<tr
class=""
data-ouia-component-id="11"
data-ouia-component-id="OUIA-Generated-TableRow-6"
data-ouia-component-type="PF4/TableRow"
data-ouia-safe="true"
>
@ -1205,7 +1234,7 @@ Object {
data-label="clientID"
>
<a
href="/client-settings"
href="/client-settings/324f4182-d302-44f8-ac8a-149eaa29dc90"
>
new
</a>
@ -1230,24 +1259,31 @@ Object {
data-label="homeURL"
>
<a
aria-disabled="false"
class="pf-c-button pf-m-link"
data-ouia-component-id="OUIA-Generated-Button-link-3"
data-ouia-component-type="PF4/Button"
data-ouia-safe="true"
href="http://localhost:8080/"
>
http://localhost:8080/
<svg
aria-hidden="true"
fill="currentColor"
height="1em"
role="img"
style="vertical-align: -0.125em;"
viewBox="0 0 512 512"
width="1em"
<span
class="pf-c-button__icon pf-m-end"
>
<path
d="M432,320H400a16,16,0,0,0-16,16V448H64V128H208a16,16,0,0,0,16-16V80a16,16,0,0,0-16-16H48A48,48,0,0,0,0,112V464a48,48,0,0,0,48,48H400a48,48,0,0,0,48-48V336A16,16,0,0,0,432,320ZM488,0h-128c-21.37,0-32.05,25.91-17,41l35.73,35.73L135,320.37a24,24,0,0,0,0,34L157.67,377a24,24,0,0,0,34,0L435.28,133.32,471,169c15,15,41,4.5,41-17V24A24,24,0,0,0,488,0Z"
transform=""
/>
</svg>
<svg
aria-hidden="true"
fill="currentColor"
height="1em"
role="img"
style="vertical-align: -0.125em;"
viewBox="0 0 512 512"
width="1em"
>
<path
d="M432,320H400a16,16,0,0,0-16,16V448H64V128H208a16,16,0,0,0,16-16V80a16,16,0,0,0-16-16H48A48,48,0,0,0,0,112V464a48,48,0,0,0,48,48H400a48,48,0,0,0,48-48V336A16,16,0,0,0,432,320ZM488,0h-128c-21.37,0-32.05,25.91-17,41l35.73,35.73L135,320.37a24,24,0,0,0,0,34L157.67,377a24,24,0,0,0,34,0L435.28,133.32,471,169c15,15,41,4.5,41-17V24A24,24,0,0,0,488,0Z"
/>
</svg>
</span>
</a>
</td>
<td
@ -1256,7 +1292,7 @@ Object {
>
<div
class="pf-c-dropdown pf-m-align-right"
data-ouia-component-id="12"
data-ouia-component-id="OUIA-Generated-Dropdown-6"
data-ouia-component-type="PF4/Dropdown"
data-ouia-safe="true"
>
@ -1279,7 +1315,6 @@ Object {
>
<path
d="M96 184c39.8 0 72 32.2 72 72s-32.2 72-72 72-72-32.2-72-72 32.2-72 72-72zM24 80c0 39.8 32.2 72 72 72s72-32.2 72-72S135.8 8 96 8 24 40.2 24 80zm0 352c0 39.8 32.2 72 72 72s72-32.2 72-72-32.2-72-72-72-72 32.2-72 72z"
transform=""
/>
</svg>
</button>
@ -1288,7 +1323,7 @@ Object {
</tr>
<tr
class=""
data-ouia-component-id="13"
data-ouia-component-id="OUIA-Generated-TableRow-7"
data-ouia-component-type="PF4/TableRow"
data-ouia-safe="true"
>
@ -1298,7 +1333,7 @@ Object {
data-label="clientID"
>
<a
href="/client-settings"
href="/client-settings/fb45882b-4d85-4f40-920e-6a68298d36d0"
>
photoz-realm
</a>
@ -1328,7 +1363,7 @@ Object {
>
<div
class="pf-c-dropdown pf-m-align-right"
data-ouia-component-id="14"
data-ouia-component-id="OUIA-Generated-Dropdown-7"
data-ouia-component-type="PF4/Dropdown"
data-ouia-safe="true"
>
@ -1351,7 +1386,6 @@ Object {
>
<path
d="M96 184c39.8 0 72 32.2 72 72s-32.2 72-72 72-72-32.2-72-72 32.2-72 72-72zM24 80c0 39.8 32.2 72 72 72s72-32.2 72-72S135.8 8 96 8 24 40.2 24 80zm0 352c0 39.8 32.2 72 72 72s72-32.2 72-72-32.2-72-72-72-72 32.2-72 72z"
transform=""
/>
</svg>
</button>
@ -1360,7 +1394,7 @@ Object {
</tr>
<tr
class=""
data-ouia-component-id="15"
data-ouia-component-id="OUIA-Generated-TableRow-8"
data-ouia-component-type="PF4/TableRow"
data-ouia-safe="true"
>
@ -1370,7 +1404,7 @@ Object {
data-label="clientID"
>
<a
href="/client-settings"
href="/client-settings/9ed60e41-d794-4046-842f-3247bf32f5ce"
>
security-admin-console
</a>
@ -1395,24 +1429,31 @@ Object {
data-label="homeURL"
>
<a
aria-disabled="false"
class="pf-c-button pf-m-link"
data-ouia-component-id="OUIA-Generated-Button-link-4"
data-ouia-component-type="PF4/Button"
data-ouia-safe="true"
href="http://blog.nerdin.chadmin/master/console/"
>
http://blog.nerdin.chadmin/master/console/
<svg
aria-hidden="true"
fill="currentColor"
height="1em"
role="img"
style="vertical-align: -0.125em;"
viewBox="0 0 512 512"
width="1em"
<span
class="pf-c-button__icon pf-m-end"
>
<path
d="M432,320H400a16,16,0,0,0-16,16V448H64V128H208a16,16,0,0,0,16-16V80a16,16,0,0,0-16-16H48A48,48,0,0,0,0,112V464a48,48,0,0,0,48,48H400a48,48,0,0,0,48-48V336A16,16,0,0,0,432,320ZM488,0h-128c-21.37,0-32.05,25.91-17,41l35.73,35.73L135,320.37a24,24,0,0,0,0,34L157.67,377a24,24,0,0,0,34,0L435.28,133.32,471,169c15,15,41,4.5,41-17V24A24,24,0,0,0,488,0Z"
transform=""
/>
</svg>
<svg
aria-hidden="true"
fill="currentColor"
height="1em"
role="img"
style="vertical-align: -0.125em;"
viewBox="0 0 512 512"
width="1em"
>
<path
d="M432,320H400a16,16,0,0,0-16,16V448H64V128H208a16,16,0,0,0,16-16V80a16,16,0,0,0-16-16H48A48,48,0,0,0,0,112V464a48,48,0,0,0,48,48H400a48,48,0,0,0,48-48V336A16,16,0,0,0,432,320ZM488,0h-128c-21.37,0-32.05,25.91-17,41l35.73,35.73L135,320.37a24,24,0,0,0,0,34L157.67,377a24,24,0,0,0,34,0L435.28,133.32,471,169c15,15,41,4.5,41-17V24A24,24,0,0,0,488,0Z"
/>
</svg>
</span>
</a>
</td>
<td
@ -1421,7 +1462,7 @@ Object {
>
<div
class="pf-c-dropdown pf-m-align-right"
data-ouia-component-id="16"
data-ouia-component-id="OUIA-Generated-Dropdown-8"
data-ouia-component-type="PF4/Dropdown"
data-ouia-safe="true"
>
@ -1444,7 +1485,6 @@ Object {
>
<path
d="M96 184c39.8 0 72 32.2 72 72s-32.2 72-72 72-72-32.2-72-72 32.2-72 72-72zM24 80c0 39.8 32.2 72 72 72s72-32.2 72-72S135.8 8 96 8 24 40.2 24 80zm0 352c0 39.8 32.2 72 72 72s72-32.2 72-72-32.2-72-72-72-72 32.2-72 72z"
transform=""
/>
</svg>
</button>

View file

@ -21,6 +21,7 @@ export const CapabilityConfig = ({ form }: CapabilityConfigProps) => {
<FormGroup label={t("clientAuthentication")} fieldId="kc-authentication">
<Controller
name="publicClient"
defaultValue={false}
control={form.control}
render={({ onChange, value }) => (
<Switch
@ -37,6 +38,7 @@ export const CapabilityConfig = ({ form }: CapabilityConfigProps) => {
<FormGroup label={t("clientAuthorization")} fieldId="kc-authorization">
<Controller
name="authorizationServicesEnabled"
defaultValue={false}
control={form.control}
render={({ onChange, value }) => (
<Switch
@ -52,9 +54,10 @@ export const CapabilityConfig = ({ form }: CapabilityConfigProps) => {
</FormGroup>
<FormGroup label={t("authenticationFlow")} fieldId="kc-flow">
<Grid>
<GridItem span={6}>
<GridItem lg={4} sm={6}>
<Controller
name="standardFlowEnabled"
defaultValue={false}
control={form.control}
render={({ onChange, value }) => (
<Checkbox
@ -67,9 +70,10 @@ export const CapabilityConfig = ({ form }: CapabilityConfigProps) => {
)}
/>
</GridItem>
<GridItem span={6}>
<GridItem lg={8} sm={6}>
<Controller
name="directAccessGrantsEnabled"
defaultValue={false}
control={form.control}
render={({ onChange, value }) => (
<Checkbox
@ -82,9 +86,10 @@ export const CapabilityConfig = ({ form }: CapabilityConfigProps) => {
)}
/>
</GridItem>
<GridItem span={6}>
<GridItem lg={4} sm={6}>
<Controller
name="implicitFlowEnabled"
defaultValue={false}
control={form.control}
render={({ onChange, value }) => (
<Checkbox
@ -97,9 +102,10 @@ export const CapabilityConfig = ({ form }: CapabilityConfigProps) => {
)}
/>
</GridItem>
<GridItem span={6}>
<GridItem lg={8} sm={6}>
<Controller
name="serviceAccountsEnabled"
defaultValue={false}
control={form.control}
render={({ onChange, value }) => (
<Checkbox

View file

@ -78,7 +78,7 @@ export const GeneralSettings = ({ form }: GeneralSettingsProps) => {
)}
/>
</FormGroup>
<ClientDescription register={register} />
<ClientDescription form={form} />
</Form>
);
};

View file

@ -1,10 +1,7 @@
import React, { useState, useContext } from "react";
import { useHistory } from "react-router-dom";
import {
Text,
PageSection,
TextContent,
Divider,
Wizard,
AlertVariant,
WizardFooter,
@ -20,6 +17,7 @@ import { CapabilityConfig } from "./CapabilityConfig";
import { ClientRepresentation } from "../models/client-model";
import { useAlerts } from "../../components/alert/Alerts";
import { RealmContext } from "../../components/realm-context/RealmContext";
import { ViewHeader } from "../../components/view-header/ViewHeader";
export const NewClientForm = () => {
const { t } = useTranslation("clients");
@ -96,12 +94,10 @@ export const NewClientForm = () => {
return (
<>
<Alerts />
<PageSection variant="light">
<TextContent>
<Text component="h1">{title}</Text>
</TextContent>
</PageSection>
<Divider />
<ViewHeader
titleKey="clients:createClient"
subKey="clients:clientsExplain"
/>
<PageSection variant="light">
<Wizard
onClose={() => history.push("/clients")}

File diff suppressed because it is too large Load diff

View file

@ -1,9 +1,6 @@
import React, { useContext } from "react";
import {
PageSection,
Text,
TextContent,
Divider,
Form,
FormGroup,
TextInput,
@ -19,16 +16,15 @@ import { ClientDescription } from "../ClientDescription";
import { HttpClientContext } from "../../http-service/HttpClientContext";
import { JsonFileUpload } from "../../components/json-file-upload/JsonFileUpload";
import { useAlerts } from "../../components/alert/Alerts";
import { AlertPanel } from "../../components/alert/AlertPanel";
import { RealmContext } from "../../components/realm-context/RealmContext";
import { ViewHeader } from "../../components/view-header/ViewHeader";
export const ImportForm = () => {
const { t } = useTranslation("clients");
const httpClient = useContext(HttpClientContext)!;
const { realm } = useContext(RealmContext);
const { register, handleSubmit, errors, setValue } = useForm<
ClientRepresentation
>();
const form = useForm<ClientRepresentation>();
const { register, handleSubmit, setValue } = form;
const [add, Alerts] = useAlerts();
@ -57,17 +53,14 @@ export const ImportForm = () => {
return (
<>
<Alerts />
<PageSection variant="light">
<TextContent>
<Text component="h1">{t("importClient")}</Text>
{t("clientsExplain")}
</TextContent>
</PageSection>
<Divider />
<ViewHeader
titleKey="clients:importClient"
subKey="clients:clientsExplain"
/>
<PageSection variant="light">
<Form isHorizontal onSubmit={handleSubmit(save)}>
<JsonFileUpload id="realm-file" onChange={handleFileChange} />
<ClientDescription register={register} />
<ClientDescription form={form} />
<FormGroup label={t("type")} fieldId="kc-type">
<TextInput
type="text"

View file

@ -14,7 +14,9 @@
"capabilityConfig": "Capability config",
"clientsExplain": "Clients are applications and services that can request authentication of a user",
"clientImportError": "Could not import client",
"clientImportSuccess": "Client imported successful",
"clientSaveSuccess": "Client successfully updated",
"clientSaveError": "Client could not be updated:",
"clientImportSuccess": "Client imported successfully",
"clientDeletedSuccess": "The client has been deleted",
"clientDeleteError": "Could not delete client:",
"clientAuthentication": "Client authentication",
@ -31,6 +33,7 @@
"rootUrl": "Root URL",
"validRedirectUri": "Valid redirect URIs",
"loginTheme": "Login theme",
"consentRequired": "Consent required"
"consentRequired": "Consent required",
"searchForClient": "Search for client"
}
}

View file

@ -5,6 +5,7 @@
"save": "Save",
"cancel": "Cancel",
"continue": "Continue",
"delete": "Delete",
"next": "Next",
"back": "Back",
@ -13,6 +14,8 @@
"clearFile": "Clear this file",
"on": "On",
"off":"Off",
"enabled": "Enabled",
"disabled": "Disabled",
"signOut": "Sign out",
"manageAccount": "Manage account",

View file

@ -35,7 +35,7 @@ exports[`remove alert after timeout: cleared alert 1`] = `
aria-disabled={false}
aria-label={null}
className="pf-c-button pf-m-primary"
data-ouia-component-id={0}
data-ouia-component-id="OUIA-Generated-Button-primary-1"
data-ouia-component-type="PF4/Button"
data-ouia-safe={true}
disabled={false}
@ -69,7 +69,7 @@ exports[`remove alert after timeout: with alert 1`] = `
aria-label="Default Alert"
aria-live="polite"
class="pf-c-alert"
data-ouia-component-id="1"
data-ouia-component-id="OUIA-Generated-Alert-default-1"
data-ouia-component-type="PF4/Alert"
data-ouia-safe="true"
>
@ -82,12 +82,11 @@ exports[`remove alert after timeout: with alert 1`] = `
height="1em"
role="img"
style="vertical-align: -0.125em;"
viewBox="0 64 896 1024"
viewBox="0 0 896 1024"
width="1em"
>
<path
d="M320.060356,128 L575.938356,128 C575.938356,57.3 518.639356,-0.001 448.000356,-0.001 L448.000356,-0.001 C377.361356,-0.001 320.060356,57.3 320.060356,128 L320.060356,128 Z M64.2023555,192 C25.9623555,192 0.202355518,223.199 -2.84217094e-13,256 L-2.84217094e-13,256 C-0.0976444824,271.1 5.22235552,286.54 17.2223555,299.419 L17.2223555,299.419 C55.8623555,340.94 128.161356,403.399 128.161356,608 L128.161356,608 C128.161356,763.4 237.121356,887.8 384.040356,918.32 L384.040356,918.32 L384.040356,960 C384.040356,995.339 412.679356,1024 448.000356,1024 L448.000356,1024 C483.319356,1024 511.959356,995.339 511.959356,960 L511.959356,960 L511.959356,918.32 C658.878356,887.8 767.837356,763.4 767.837356,608 L767.837356,608 C767.837356,403.4 840.138356,340.94 878.778356,299.42 L878.778356,299.42 C890.777356,286.52 896.096356,271.1 895.999684,256 L895.999684,256 C895.777356,223.2 870.037356,192 831.798356,192 L831.798356,192 L64.2023555,192 Z"
transform="rotate(180 0 512) scale(-1 1)"
d="M448,0 C465.333333,0 480.333333,6.33333333 493,19 C505.666667,31.6666667 512,46.6666667 512,64 L512,106 L514.23,106.45 C587.89,121.39 648.48,157.24 696,214 C744,271.333333 768,338.666667 768,416 C768,500 780,568.666667 804,622 C818.666667,652.666667 841.333333,684 872,716 C873.773676,718.829136 875.780658,721.505113 878,724 C890,737.333333 896,752.333333 896,769 C896,785.666667 890,800.333333 878,813 C866,825.666667 850.666667,832 832,832 L63.3,832 C44.9533333,831.84 29.8533333,825.506667 18,813 C6,800.333333 0,785.666667 0,769 C0,752.333333 6,737.333333 18,724 L24,716 L25.06,714.9 C55.1933333,683.28 77.5066667,652.313333 92,622 C116,568.666667 128,500 128,416 C128,338.666667 152,271.333333 200,214 C248,156.666667 309.333333,120.666667 384,106 L384,63.31 C384.166667,46.27 390.5,31.5 403,19 C415.666667,6.33333333 430.666667,0 448,0 Z M576,896 L576,897.08 C575.74,932.6 563.073333,962.573333 538,987 C512.666667,1011.66667 482.666667,1024 448,1024 C413.333333,1024 383.333333,1011.66667 358,987 C332.666667,962.333333 320,932 320,896 L576,896 Z"
/>
</svg>
</div>
@ -106,7 +105,7 @@ exports[`remove alert after timeout: with alert 1`] = `
aria-disabled="false"
aria-label="Close alert: Hello"
class="pf-c-button pf-m-plain"
data-ouia-component-id="2"
data-ouia-component-id="OUIA-Generated-Button-plain-1"
data-ouia-component-type="PF4/Button"
data-ouia-safe="true"
title="Hello"
@ -123,7 +122,6 @@ exports[`remove alert after timeout: with alert 1`] = `
>
<path
d="M242.72 256l100.07-100.07c12.28-12.28 12.28-32.19 0-44.48l-22.24-22.24c-12.28-12.28-32.19-12.28-44.48 0L176 189.28 75.93 89.21c-12.28-12.28-32.19-12.28-44.48 0L9.21 111.45c-12.28 12.28-12.28 32.19 0 44.48L109.28 256 9.21 356.07c-12.28 12.28-12.28 32.19 0 44.48l22.24 22.24c12.28 12.28 32.2 12.28 44.48 0L176 322.72l100.07 100.07c12.28 12.28 32.2 12.28 44.48 0l22.24-22.24c12.28-12.28 12.28-32.19 0-44.48L242.72 256z"
transform=""
/>
</svg>
</button>
@ -152,7 +150,7 @@ exports[`remove alert after timeout: with alert 1`] = `
aria-disabled={false}
aria-label={null}
className="pf-c-button pf-m-primary"
data-ouia-component-id={0}
data-ouia-component-id="OUIA-Generated-Button-primary-1"
data-ouia-component-type="PF4/Button"
data-ouia-safe={true}
disabled={false}

View file

@ -0,0 +1,94 @@
import React, { ReactElement, ReactNode, useState } from "react";
import {
Button,
ButtonVariant,
Modal,
ModalVariant,
} from "@patternfly/react-core";
import { useTranslation } from "react-i18next";
export const useConfirmDialog = (
props: ConfirmDialogProps
): [() => void, () => ReactElement] => {
const [show, setShow] = useState(false);
function toggleDialog() {
setShow((show) => !show);
}
const Dialog = () => (
<ConfirmDialogModal
key="confirmDialog"
{...props}
open={show}
toggleDialog={toggleDialog}
/>
);
return [toggleDialog, Dialog];
};
export interface ConfirmDialogModalProps extends ConfirmDialogProps {
open: boolean;
toggleDialog: () => void;
}
export type ConfirmDialogProps = {
titleKey: string;
messageKey?: string;
cancelButtonLabel?: string;
continueButtonLabel?: string;
continueButtonVariant?: ButtonVariant;
onConfirm: () => void;
onCancel?: () => void;
children?: ReactNode;
};
export const ConfirmDialogModal = ({
titleKey,
messageKey,
cancelButtonLabel,
continueButtonLabel,
continueButtonVariant,
onConfirm,
onCancel,
children,
open = true,
toggleDialog,
}: ConfirmDialogModalProps) => {
const { t } = useTranslation();
return (
<Modal
title={t(titleKey)}
isOpen={open}
onClose={toggleDialog}
variant={ModalVariant.small}
actions={[
<Button
id="modal-confirm"
key="confirm"
variant={continueButtonVariant || ButtonVariant.primary}
onClick={() => {
onConfirm();
toggleDialog();
}}
>
{t(continueButtonLabel || "common:continue")}
</Button>,
<Button
id="modal-cancel"
key="cancel"
variant={ButtonVariant.secondary}
onClick={() => {
if (onCancel) onCancel();
toggleDialog();
}}
>
{t(cancelButtonLabel || "common:cancel")}
</Button>,
]}
>
{!messageKey && children}
{messageKey && t(messageKey)}
</Modal>
);
};

View file

@ -0,0 +1,36 @@
import React from "react";
import { mount } from "enzyme";
import { useConfirmDialog } from "../ConfirmDialog";
describe("Confirmation dialog", () => {
it("renders simple confirm dialog", () => {
const onConfirm = jest.fn();
const Test = () => {
const [toggle, ConfirmDialog] = useConfirmDialog({
titleKey: "Delete app02?",
messageKey:
"If you delete this client, all associated data will be removed.",
continueButtonLabel: "Delete",
onConfirm: onConfirm,
});
return (
<>
<button id="show" onClick={toggle}>
Show
</button>
<ConfirmDialog />
</>
);
};
const simple = mount(<Test />);
simple.find("#show").simulate("click");
expect(simple).toMatchSnapshot();
const button = simple.find("#modal-confirm").find("button");
expect(button).not.toBeNull();
button!.simulate("click");
expect(onConfirm).toBeCalled();
});
});

View file

@ -0,0 +1,356 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Confirmation dialog renders simple confirm dialog 1`] = `
<Test>
<button
id="show"
onClick={[Function]}
>
Show
</button>
<Dialog>
<ConfirmDialogModal
continueButtonLabel="Delete"
key="confirmDialog"
messageKey="If you delete this client, all associated data will be removed."
onConfirm={[MockFunction]}
open={true}
titleKey="Delete app02?"
toggleDialog={[Function]}
>
<Modal
actions={
Array [
<Button
id="modal-confirm"
onClick={[Function]}
variant="primary"
>
Delete
</Button>,
<Button
id="modal-cancel"
onClick={[Function]}
variant="secondary"
>
cancel
</Button>,
]
}
appendTo={[Function]}
aria-describedby=""
aria-label=""
aria-labelledby=""
className=""
hasNoBodyWrapper={false}
isOpen={true}
onClose={[Function]}
ouiaSafe={true}
showClose={true}
title="Delete app02?"
variant="small"
>
<Portal
containerInfo={
<div>
<div
class="pf-c-backdrop"
>
<div
class="pf-l-bullseye"
>
<div
aria-describedby="pf-modal-part-3"
aria-labelledby="pf-modal-part-2"
aria-modal="true"
class="pf-c-modal-box pf-m-sm"
data-ouia-component-id="OUIA-Generated-Modal-small-2"
data-ouia-component-type="PF4/ModalContent"
data-ouia-safe="true"
id="pf-modal-part-1"
role="dialog"
>
<button
aria-disabled="false"
aria-label="Close"
class="pf-c-button pf-m-plain"
data-ouia-component-id="OUIA-Generated-Button-plain-1"
data-ouia-component-type="PF4/Button"
data-ouia-safe="true"
type="button"
>
<svg
aria-hidden="true"
fill="currentColor"
height="1em"
role="img"
style="vertical-align: -0.125em;"
viewBox="0 0 352 512"
width="1em"
>
<path
d="M242.72 256l100.07-100.07c12.28-12.28 12.28-32.19 0-44.48l-22.24-22.24c-12.28-12.28-32.19-12.28-44.48 0L176 189.28 75.93 89.21c-12.28-12.28-32.19-12.28-44.48 0L9.21 111.45c-12.28 12.28-12.28 32.19 0 44.48L109.28 256 9.21 356.07c-12.28 12.28-12.28 32.19 0 44.48l22.24 22.24c12.28 12.28 32.2 12.28 44.48 0L176 322.72l100.07 100.07c12.28 12.28 32.2 12.28 44.48 0l22.24-22.24c12.28-12.28 12.28-32.19 0-44.48L242.72 256z"
/>
</svg>
</button>
<header
class="pf-c-modal-box__header"
>
<h1
class="pf-c-modal-box__title pf-c-modal-box__title"
id="pf-modal-part-2"
>
Delete app02?
</h1>
</header>
<div
class="pf-c-modal-box__body"
id="pf-modal-part-3"
>
If you delete this client, all associated data will be removed.
</div>
<footer
class="pf-c-modal-box__footer"
>
<button
aria-disabled="false"
class="pf-c-button pf-m-primary"
data-ouia-component-id="OUIA-Generated-Button-primary-1"
data-ouia-component-type="PF4/Button"
data-ouia-safe="true"
id="modal-confirm"
type="button"
>
Delete
</button>
<button
aria-disabled="false"
class="pf-c-button pf-m-secondary"
data-ouia-component-id="OUIA-Generated-Button-secondary-1"
data-ouia-component-type="PF4/Button"
data-ouia-safe="true"
id="modal-cancel"
type="button"
>
cancel
</button>
</footer>
</div>
</div>
</div>
</div>
}
>
<ModalContent
actions={
Array [
<Button
id="modal-confirm"
onClick={[Function]}
variant="primary"
>
Delete
</Button>,
<Button
id="modal-cancel"
onClick={[Function]}
variant="secondary"
>
cancel
</Button>,
]
}
aria-describedby=""
aria-label=""
aria-labelledby=""
boxId="pf-modal-part-1"
className=""
descriptorId="pf-modal-part-3"
hasNoBodyWrapper={false}
isOpen={true}
labelId="pf-modal-part-2"
onClose={[Function]}
ouiaId="OUIA-Generated-Modal-small-2"
ouiaSafe={true}
showClose={true}
title="Delete app02?"
variant="small"
>
<Backdrop>
<div
className="pf-c-backdrop"
>
<FocusTrap
active={true}
className="pf-l-bullseye"
focusTrapOptions={
Object {
"clickOutsideDeactivates": true,
}
}
paused={false}
>
<div
className="pf-l-bullseye"
>
<ModalBox
aria-describedby="pf-modal-part-3"
aria-label=""
aria-labelledby="pf-modal-part-2"
className=""
data-ouia-component-id="OUIA-Generated-Modal-small-2"
data-ouia-component-type="PF4/ModalContent"
data-ouia-safe={true}
id="pf-modal-part-1"
style={Object {}}
variant="small"
>
<div
aria-describedby="pf-modal-part-3"
aria-label={null}
aria-labelledby="pf-modal-part-2"
aria-modal="true"
className="pf-c-modal-box pf-m-sm"
data-ouia-component-id="OUIA-Generated-Modal-small-2"
data-ouia-component-type="PF4/ModalContent"
data-ouia-safe={true}
id="pf-modal-part-1"
role="dialog"
style={Object {}}
>
<ModalBoxCloseButton
onClose={[Function]}
>
<Button
aria-label="Close"
className=""
onClick={[Function]}
variant="plain"
>
<button
aria-disabled={false}
aria-label="Close"
className="pf-c-button pf-m-plain"
data-ouia-component-id="OUIA-Generated-Button-plain-1"
data-ouia-component-type="PF4/Button"
data-ouia-safe={true}
disabled={false}
onClick={[Function]}
type="button"
>
<TimesIcon
color="currentColor"
noVerticalAlign={false}
size="sm"
>
<svg
aria-hidden={true}
aria-labelledby={null}
fill="currentColor"
height="1em"
role="img"
style={
Object {
"verticalAlign": "-0.125em",
}
}
viewBox="0 0 352 512"
width="1em"
>
<path
d="M242.72 256l100.07-100.07c12.28-12.28 12.28-32.19 0-44.48l-22.24-22.24c-12.28-12.28-32.19-12.28-44.48 0L176 189.28 75.93 89.21c-12.28-12.28-32.19-12.28-44.48 0L9.21 111.45c-12.28 12.28-12.28 32.19 0 44.48L109.28 256 9.21 356.07c-12.28 12.28-12.28 32.19 0 44.48l22.24 22.24c12.28 12.28 32.2 12.28 44.48 0L176 322.72l100.07 100.07c12.28 12.28 32.2 12.28 44.48 0l22.24-22.24c12.28-12.28 12.28-32.19 0-44.48L242.72 256z"
/>
</svg>
</TimesIcon>
</button>
</Button>
</ModalBoxCloseButton>
<ModalBoxHeader>
<header
className="pf-c-modal-box__header"
>
<ModalBoxTitle
className="pf-c-modal-box__title"
id="pf-modal-part-2"
title="Delete app02?"
>
<h1
className="pf-c-modal-box__title pf-c-modal-box__title"
id="pf-modal-part-2"
>
Delete app02?
</h1>
</ModalBoxTitle>
</header>
</ModalBoxHeader>
<ModalBoxBody
id="pf-modal-part-3"
>
<div
className="pf-c-modal-box__body"
id="pf-modal-part-3"
>
If you delete this client, all associated data will be removed.
</div>
</ModalBoxBody>
<ModalBoxFooter>
<footer
className="pf-c-modal-box__footer"
>
<Button
id="modal-confirm"
key="confirm"
onClick={[Function]}
variant="primary"
>
<button
aria-disabled={false}
aria-label={null}
className="pf-c-button pf-m-primary"
data-ouia-component-id="OUIA-Generated-Button-primary-1"
data-ouia-component-type="PF4/Button"
data-ouia-safe={true}
disabled={false}
id="modal-confirm"
onClick={[Function]}
type="button"
>
Delete
</button>
</Button>
<Button
id="modal-cancel"
key="cancel"
onClick={[Function]}
variant="secondary"
>
<button
aria-disabled={false}
aria-label={null}
className="pf-c-button pf-m-secondary"
data-ouia-component-id="OUIA-Generated-Button-secondary-1"
data-ouia-component-type="PF4/Button"
data-ouia-safe={true}
disabled={false}
id="modal-cancel"
onClick={[Function]}
type="button"
>
cancel
</button>
</Button>
</footer>
</ModalBoxFooter>
</div>
</ModalBox>
</div>
</FocusTrap>
</div>
</Backdrop>
</ModalContent>
</Portal>
</Modal>
</ConfirmDialogModal>
</Dialog>
</Test>
`;

View file

@ -1,15 +1,18 @@
import React from "react";
import { ExternalLinkAltIcon } from "@patternfly/react-icons";
import { Button, ButtonProps } from "@patternfly/react-core";
export const ExternalLink = ({
title,
href,
...rest
}: React.HTMLProps<HTMLAnchorElement>) => {
export const ExternalLink = ({ title, href, ...rest }: ButtonProps) => {
return (
<a href={href} {...rest}>
{title ? title : href}{" "}
{href?.startsWith("http") && <ExternalLinkAltIcon />}
</a>
<Button
variant="link"
icon={href?.startsWith("http") && <ExternalLinkAltIcon />}
iconPosition="right"
component="a"
href={href}
{...rest}
>
{title ? title : href}
</Button>
);
};

View file

@ -14,4 +14,18 @@ describe("<ExternalLink />", () => {
);
expect(comp.asFragment()).toMatchSnapshot();
});
it("render with internal url", () => {
const comp = render(
<ExternalLink href="/application/home/" title="Application page" />
);
expect(comp.asFragment()).toMatchSnapshot();
});
it("render as application", () => {
const comp = render(
<ExternalLink href="/application/main" title="Application link" />
);
expect(comp.asFragment()).toMatchSnapshot();
});
});

View file

@ -1,25 +1,63 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`<ExternalLink /> render as application 1`] = `
<DocumentFragment>
<a
aria-disabled="false"
class="pf-c-button pf-m-link"
data-ouia-component-id="OUIA-Generated-Button-link-4"
data-ouia-component-type="PF4/Button"
data-ouia-safe="true"
href="/application/main"
>
Application link
</a>
</DocumentFragment>
`;
exports[`<ExternalLink /> render with internal url 1`] = `
<DocumentFragment>
<a
aria-disabled="false"
class="pf-c-button pf-m-link"
data-ouia-component-id="OUIA-Generated-Button-link-3"
data-ouia-component-type="PF4/Button"
data-ouia-safe="true"
href="/application/home/"
>
Application page
</a>
</DocumentFragment>
`;
exports[`<ExternalLink /> render with link 1`] = `
<DocumentFragment>
<a
aria-disabled="false"
class="pf-c-button pf-m-link"
data-ouia-component-id="OUIA-Generated-Button-link-1"
data-ouia-component-type="PF4/Button"
data-ouia-safe="true"
href="http://hello.nl/"
>
http://hello.nl/
<svg
aria-hidden="true"
fill="currentColor"
height="1em"
role="img"
style="vertical-align: -0.125em;"
viewBox="0 0 512 512"
width="1em"
http://hello.nl/
<span
class="pf-c-button__icon pf-m-end"
>
<path
d="M432,320H400a16,16,0,0,0-16,16V448H64V128H208a16,16,0,0,0,16-16V80a16,16,0,0,0-16-16H48A48,48,0,0,0,0,112V464a48,48,0,0,0,48,48H400a48,48,0,0,0,48-48V336A16,16,0,0,0,432,320ZM488,0h-128c-21.37,0-32.05,25.91-17,41l35.73,35.73L135,320.37a24,24,0,0,0,0,34L157.67,377a24,24,0,0,0,34,0L435.28,133.32,471,169c15,15,41,4.5,41-17V24A24,24,0,0,0,488,0Z"
transform=""
/>
</svg>
<svg
aria-hidden="true"
fill="currentColor"
height="1em"
role="img"
style="vertical-align: -0.125em;"
viewBox="0 0 512 512"
width="1em"
>
<path
d="M432,320H400a16,16,0,0,0-16,16V448H64V128H208a16,16,0,0,0,16-16V80a16,16,0,0,0-16-16H48A48,48,0,0,0,0,112V464a48,48,0,0,0,48,48H400a48,48,0,0,0,48-48V336A16,16,0,0,0,432,320ZM488,0h-128c-21.37,0-32.05,25.91-17,41l35.73,35.73L135,320.37a24,24,0,0,0,0,34L157.67,377a24,24,0,0,0,34,0L435.28,133.32,471,169c15,15,41,4.5,41-17V24A24,24,0,0,0,488,0Z"
/>
</svg>
</span>
</a>
</DocumentFragment>
`;
@ -27,23 +65,31 @@ exports[`<ExternalLink /> render with link 1`] = `
exports[`<ExternalLink /> render with link and title 1`] = `
<DocumentFragment>
<a
aria-disabled="false"
class="pf-c-button pf-m-link"
data-ouia-component-id="OUIA-Generated-Button-link-2"
data-ouia-component-type="PF4/Button"
data-ouia-safe="true"
href="http://hello.nl/"
>
Link to Hello
<svg
aria-hidden="true"
fill="currentColor"
height="1em"
role="img"
style="vertical-align: -0.125em;"
viewBox="0 0 512 512"
width="1em"
Link to Hello
<span
class="pf-c-button__icon pf-m-end"
>
<path
d="M432,320H400a16,16,0,0,0-16,16V448H64V128H208a16,16,0,0,0,16-16V80a16,16,0,0,0-16-16H48A48,48,0,0,0,0,112V464a48,48,0,0,0,48,48H400a48,48,0,0,0,48-48V336A16,16,0,0,0,432,320ZM488,0h-128c-21.37,0-32.05,25.91-17,41l35.73,35.73L135,320.37a24,24,0,0,0,0,34L157.67,377a24,24,0,0,0,34,0L435.28,133.32,471,169c15,15,41,4.5,41-17V24A24,24,0,0,0,488,0Z"
transform=""
/>
</svg>
<svg
aria-hidden="true"
fill="currentColor"
height="1em"
role="img"
style="vertical-align: -0.125em;"
viewBox="0 0 512 512"
width="1em"
>
<path
d="M432,320H400a16,16,0,0,0-16,16V448H64V128H208a16,16,0,0,0,16-16V80a16,16,0,0,0-16-16H48A48,48,0,0,0,0,112V464a48,48,0,0,0,48,48H400a48,48,0,0,0,48-48V336A16,16,0,0,0,432,320ZM488,0h-128c-21.37,0-32.05,25.91-17,41l35.73,35.73L135,320.37a24,24,0,0,0,0,34L157.67,377a24,24,0,0,0,34,0L435.28,133.32,471,169c15,15,41,4.5,41-17V24A24,24,0,0,0,488,0Z"
/>
</svg>
</span>
</a>
</DocumentFragment>
`;

View file

@ -1,7 +1,8 @@
import React from "react";
import React, { useContext } from "react";
import { Tooltip } from "@patternfly/react-core";
import { HelpIcon } from "@patternfly/react-icons";
import { useTranslation } from "react-i18next";
import { HelpContext } from "./HelpHeader";
type HelpItemProps = {
item: string;
@ -9,11 +10,16 @@ type HelpItemProps = {
export const HelpItem = ({ item }: HelpItemProps) => {
const { t } = useTranslation();
const { enabled } = useContext(HelpContext);
return (
<Tooltip position="right" content={t(`help:${item}`)}>
<span id={item} data-testid={item}>
<HelpIcon />
</span>
</Tooltip>
<>
{enabled && (
<Tooltip position="right" content={t(`help:${item}`)}>
<span id={item} data-testid={item}>
<HelpIcon />
</span>
</Tooltip>
)}
</>
);
};

View file

@ -4,7 +4,7 @@ exports[`<HelpHeader /> enable help 1`] = `
<DocumentFragment>
<div
class="pf-c-dropdown pf-m-align-right pf-m-expanded"
data-ouia-component-id="5"
data-ouia-component-id="OUIA-Generated-Dropdown-3"
data-ouia-component-type="PF4/Dropdown"
data-ouia-safe="true"
>
@ -23,12 +23,11 @@ exports[`<HelpHeader /> enable help 1`] = `
height="1em"
role="img"
style="vertical-align: -0.125em;"
viewBox="0 64 1024 1024"
viewBox="0 0 1024 1024"
width="1em"
>
<path
d="M512.059-73.143c-282.338 0-512.059 229.673-512.059 512.025 0 282.235 229.721 511.975 512.059 511.975 282.281 0 511.941-229.735 511.941-511.975 0.005-282.352-229.659-512.025-511.941-512.025zM512.059 826.523c-213.826 0-387.728-173.856-387.728-387.643 0-213.89 173.904-387.694 387.728-387.694 213.717 0 387.671 173.803 387.671 387.694 0.005 213.785-173.957 387.643-387.671 387.643zM585.143 164.571v109.714c0 4.951-1.808 9.237-5.429 12.857s-7.906 5.429-12.857 5.429h-109.714c-4.953 0-9.239-1.808-12.857-5.429s-5.429-7.906-5.429-12.857v-109.714c0-4.951 1.81-9.237 5.429-12.857s7.904-5.429 12.857-5.429h109.714c4.951 0 9.237 1.808 12.857 5.429s5.429 7.906 5.429 12.857zM521.616 365.714c118.343 0 214.286 88.965 214.286 187.429s-95.943 178.286-214.286 178.286c-173.045 0-208.091-93.714-213.963-171.113 0-7.936 6.729-9.838 15.872-9.838s108.073 0 113.143 0c6.096 0 14.475 0.633 17.554 10.571 0 48.857 135.968 54.953 135.968-7.904 0-31.506-29.717-63.817-68.571-66.194s-82.286-7.607-82.286-54.199c0-13.022 0-25.673 0-44.693 0-19.015 9.717-22.343 27.431-22.343s54.853-0.002 54.853-0.002z"
transform="rotate(180 0 512) scale(-1 1)"
d="M521.3,576 C627.5,576 713.7,502 713.7,413.7 C713.7,325.4 627.6,253.6 521.3,253.6 C366,253.6 334.5,337.7 329.2,407.2 C329.2,414.3 335.2,416 343.5,416 L445,416 C450.5,416 458,415.5 460.8,406.5 C460.8,362.6 582.9,357.1 582.9,413.6 C582.9,441.9 556.2,470.9 521.3,473 C486.4,475.1 447.3,479.8 447.3,521.7 L447.3,553.8 C447.3,570.8 456.1,576 472,576 C487.9,576 521.3,576 521.3,576 M575.3,751.3 L575.3,655.3 C575.313862,651.055109 573.620137,646.982962 570.6,644 C567.638831,640.947672 563.552355,639.247987 559.3,639.29884 L463.3,639.29884 C459.055109,639.286138 454.982962,640.979863 452,644 C448.947672,646.961169 447.247987,651.047645 447.29884,655.3 L447.29884,751.3 C447.286138,755.544891 448.979863,759.617038 452,762.6 C454.961169,765.652328 459.047645,767.352013 463.3,767.30116 L559.3,767.30116 C563.544891,767.313862 567.617038,765.620137 570.6,762.6 C573.659349,759.643612 575.360354,755.553963 575.3,751.3 M512,896 C300.2,896 128,723.9 128,512 C128,300.3 300.2,128 512,128 C723.8,128 896,300.2 896,512 C896,723.8 723.7,896 512,896 M512.1,0 C229.7,0 0,229.8 0,512 C0,794.2 229.8,1024 512.1,1024 C794.4,1024 1024,794.3 1024,512 C1024,229.7 794.4,0 512.1,0"
/>
</svg>
</span>
@ -69,7 +68,6 @@ exports[`<HelpHeader /> enable help 1`] = `
>
<path
d="M432,320H400a16,16,0,0,0-16,16V448H64V128H208a16,16,0,0,0,16-16V80a16,16,0,0,0-16-16H48A48,48,0,0,0,0,112V464a48,48,0,0,0,48,48H400a48,48,0,0,0,48-48V336A16,16,0,0,0,432,320ZM488,0h-128c-21.37,0-32.05,25.91-17,41l35.73,35.73L135,320.37a24,24,0,0,0,0,34L157.67,377a24,24,0,0,0,34,0L435.28,133.32,471,169c15,15,41,4.5,41-17V24A24,24,0,0,0,488,0Z"
transform=""
/>
</svg>
</div>
@ -101,7 +99,7 @@ exports[`<HelpHeader /> enable help 1`] = `
>
<label
class="pf-c-switch"
data-ouia-component-id="6"
data-ouia-component-id="OUIA-Generated-Switch-2"
data-ouia-component-type="PF4/Switch"
data-ouia-safe="true"
for="enableHelp"
@ -130,7 +128,6 @@ exports[`<HelpHeader /> enable help 1`] = `
>
<path
d="M173.898 439.404l-166.4-166.4c-9.997-9.997-9.997-26.206 0-36.204l36.203-36.204c9.997-9.998 26.207-9.998 36.204 0L192 312.69 432.095 72.596c9.997-9.997 26.207-9.997 36.204 0l36.203 36.204c9.997 9.997 9.997 26.206 0 36.204l-294.4 294.401c-9.998 9.997-26.207 9.997-36.204-.001z"
transform=""
/>
</svg>
</div>
@ -160,7 +157,7 @@ exports[`<HelpHeader /> open dropdown 1`] = `
<DocumentFragment>
<div
class="pf-c-dropdown pf-m-align-right pf-m-expanded"
data-ouia-component-id="2"
data-ouia-component-id="OUIA-Generated-Dropdown-2"
data-ouia-component-type="PF4/Dropdown"
data-ouia-safe="true"
>
@ -179,12 +176,11 @@ exports[`<HelpHeader /> open dropdown 1`] = `
height="1em"
role="img"
style="vertical-align: -0.125em;"
viewBox="0 64 1024 1024"
viewBox="0 0 1024 1024"
width="1em"
>
<path
d="M512.059-73.143c-282.338 0-512.059 229.673-512.059 512.025 0 282.235 229.721 511.975 512.059 511.975 282.281 0 511.941-229.735 511.941-511.975 0.005-282.352-229.659-512.025-511.941-512.025zM512.059 826.523c-213.826 0-387.728-173.856-387.728-387.643 0-213.89 173.904-387.694 387.728-387.694 213.717 0 387.671 173.803 387.671 387.694 0.005 213.785-173.957 387.643-387.671 387.643zM585.143 164.571v109.714c0 4.951-1.808 9.237-5.429 12.857s-7.906 5.429-12.857 5.429h-109.714c-4.953 0-9.239-1.808-12.857-5.429s-5.429-7.906-5.429-12.857v-109.714c0-4.951 1.81-9.237 5.429-12.857s7.904-5.429 12.857-5.429h109.714c4.951 0 9.237 1.808 12.857 5.429s5.429 7.906 5.429 12.857zM521.616 365.714c118.343 0 214.286 88.965 214.286 187.429s-95.943 178.286-214.286 178.286c-173.045 0-208.091-93.714-213.963-171.113 0-7.936 6.729-9.838 15.872-9.838s108.073 0 113.143 0c6.096 0 14.475 0.633 17.554 10.571 0 48.857 135.968 54.953 135.968-7.904 0-31.506-29.717-63.817-68.571-66.194s-82.286-7.607-82.286-54.199c0-13.022 0-25.673 0-44.693 0-19.015 9.717-22.343 27.431-22.343s54.853-0.002 54.853-0.002z"
transform="rotate(180 0 512) scale(-1 1)"
d="M521.3,576 C627.5,576 713.7,502 713.7,413.7 C713.7,325.4 627.6,253.6 521.3,253.6 C366,253.6 334.5,337.7 329.2,407.2 C329.2,414.3 335.2,416 343.5,416 L445,416 C450.5,416 458,415.5 460.8,406.5 C460.8,362.6 582.9,357.1 582.9,413.6 C582.9,441.9 556.2,470.9 521.3,473 C486.4,475.1 447.3,479.8 447.3,521.7 L447.3,553.8 C447.3,570.8 456.1,576 472,576 C487.9,576 521.3,576 521.3,576 M575.3,751.3 L575.3,655.3 C575.313862,651.055109 573.620137,646.982962 570.6,644 C567.638831,640.947672 563.552355,639.247987 559.3,639.29884 L463.3,639.29884 C459.055109,639.286138 454.982962,640.979863 452,644 C448.947672,646.961169 447.247987,651.047645 447.29884,655.3 L447.29884,751.3 C447.286138,755.544891 448.979863,759.617038 452,762.6 C454.961169,765.652328 459.047645,767.352013 463.3,767.30116 L559.3,767.30116 C563.544891,767.313862 567.617038,765.620137 570.6,762.6 C573.659349,759.643612 575.360354,755.553963 575.3,751.3 M512,896 C300.2,896 128,723.9 128,512 C128,300.3 300.2,128 512,128 C723.8,128 896,300.2 896,512 C896,723.8 723.7,896 512,896 M512.1,0 C229.7,0 0,229.8 0,512 C0,794.2 229.8,1024 512.1,1024 C794.4,1024 1024,794.3 1024,512 C1024,229.7 794.4,0 512.1,0"
/>
</svg>
</span>
@ -225,7 +221,6 @@ exports[`<HelpHeader /> open dropdown 1`] = `
>
<path
d="M432,320H400a16,16,0,0,0-16,16V448H64V128H208a16,16,0,0,0,16-16V80a16,16,0,0,0-16-16H48A48,48,0,0,0,0,112V464a48,48,0,0,0,48,48H400a48,48,0,0,0,48-48V336A16,16,0,0,0,432,320ZM488,0h-128c-21.37,0-32.05,25.91-17,41l35.73,35.73L135,320.37a24,24,0,0,0,0,34L157.67,377a24,24,0,0,0,34,0L435.28,133.32,471,169c15,15,41,4.5,41-17V24A24,24,0,0,0,488,0Z"
transform=""
/>
</svg>
</div>
@ -257,7 +252,7 @@ exports[`<HelpHeader /> open dropdown 1`] = `
>
<label
class="pf-c-switch"
data-ouia-component-id="3"
data-ouia-component-id="OUIA-Generated-Switch-1"
data-ouia-component-type="PF4/Switch"
data-ouia-safe="true"
for="enableHelp"
@ -286,7 +281,6 @@ exports[`<HelpHeader /> open dropdown 1`] = `
>
<path
d="M173.898 439.404l-166.4-166.4c-9.997-9.997-9.997-26.206 0-36.204l36.203-36.204c9.997-9.998 26.207-9.998 36.204 0L192 312.69 432.095 72.596c9.997-9.997 26.207-9.997 36.204 0l36.203 36.204c9.997 9.997 9.997 26.206 0 36.204l-294.4 294.401c-9.998 9.997-26.207 9.997-36.204-.001z"
transform=""
/>
</svg>
</div>
@ -311,7 +305,7 @@ exports[`<HelpHeader /> render 1`] = `
<DocumentFragment>
<div
class="pf-c-dropdown pf-m-align-right"
data-ouia-component-id="0"
data-ouia-component-id="OUIA-Generated-Dropdown-1"
data-ouia-component-type="PF4/Dropdown"
data-ouia-safe="true"
>
@ -330,12 +324,11 @@ exports[`<HelpHeader /> render 1`] = `
height="1em"
role="img"
style="vertical-align: -0.125em;"
viewBox="0 64 1024 1024"
viewBox="0 0 1024 1024"
width="1em"
>
<path
d="M512.059-73.143c-282.338 0-512.059 229.673-512.059 512.025 0 282.235 229.721 511.975 512.059 511.975 282.281 0 511.941-229.735 511.941-511.975 0.005-282.352-229.659-512.025-511.941-512.025zM512.059 826.523c-213.826 0-387.728-173.856-387.728-387.643 0-213.89 173.904-387.694 387.728-387.694 213.717 0 387.671 173.803 387.671 387.694 0.005 213.785-173.957 387.643-387.671 387.643zM585.143 164.571v109.714c0 4.951-1.808 9.237-5.429 12.857s-7.906 5.429-12.857 5.429h-109.714c-4.953 0-9.239-1.808-12.857-5.429s-5.429-7.906-5.429-12.857v-109.714c0-4.951 1.81-9.237 5.429-12.857s7.904-5.429 12.857-5.429h109.714c4.951 0 9.237 1.808 12.857 5.429s5.429 7.906 5.429 12.857zM521.616 365.714c118.343 0 214.286 88.965 214.286 187.429s-95.943 178.286-214.286 178.286c-173.045 0-208.091-93.714-213.963-171.113 0-7.936 6.729-9.838 15.872-9.838s108.073 0 113.143 0c6.096 0 14.475 0.633 17.554 10.571 0 48.857 135.968 54.953 135.968-7.904 0-31.506-29.717-63.817-68.571-66.194s-82.286-7.607-82.286-54.199c0-13.022 0-25.673 0-44.693 0-19.015 9.717-22.343 27.431-22.343s54.853-0.002 54.853-0.002z"
transform="rotate(180 0 512) scale(-1 1)"
d="M521.3,576 C627.5,576 713.7,502 713.7,413.7 C713.7,325.4 627.6,253.6 521.3,253.6 C366,253.6 334.5,337.7 329.2,407.2 C329.2,414.3 335.2,416 343.5,416 L445,416 C450.5,416 458,415.5 460.8,406.5 C460.8,362.6 582.9,357.1 582.9,413.6 C582.9,441.9 556.2,470.9 521.3,473 C486.4,475.1 447.3,479.8 447.3,521.7 L447.3,553.8 C447.3,570.8 456.1,576 472,576 C487.9,576 521.3,576 521.3,576 M575.3,751.3 L575.3,655.3 C575.313862,651.055109 573.620137,646.982962 570.6,644 C567.638831,640.947672 563.552355,639.247987 559.3,639.29884 L463.3,639.29884 C459.055109,639.286138 454.982962,640.979863 452,644 C448.947672,646.961169 447.247987,651.047645 447.29884,655.3 L447.29884,751.3 C447.286138,755.544891 448.979863,759.617038 452,762.6 C454.961169,765.652328 459.047645,767.352013 463.3,767.30116 L559.3,767.30116 C563.544891,767.313862 567.617038,765.620137 570.6,762.6 C573.659349,759.643612 575.360354,755.553963 575.3,751.3 M512,896 C300.2,896 128,723.9 128,512 C128,300.3 300.2,128 512,128 C723.8,128 896,300.2 896,512 C896,723.8 723.7,896 512,896 M512.1,0 C229.7,0 0,229.8 0,512 C0,794.2 229.8,1024 512.1,1024 C794.4,1024 1024,794.3 1024,512 C1024,229.7 794.4,0 512.1,0"
/>
</svg>
</span>

View file

@ -13,12 +13,11 @@ exports[`<HelpItem /> render 1`] = `
height="1em"
role="img"
style="vertical-align: -0.125em;"
viewBox="0 64 1024 1024"
viewBox="0 0 1024 1024"
width="1em"
>
<path
d="M512.059-73.143c-282.338 0-512.059 229.673-512.059 512.025 0 282.235 229.721 511.975 512.059 511.975 282.281 0 511.941-229.735 511.941-511.975 0.005-282.352-229.659-512.025-511.941-512.025zM512.059 826.523c-213.826 0-387.728-173.856-387.728-387.643 0-213.89 173.904-387.694 387.728-387.694 213.717 0 387.671 173.803 387.671 387.694 0.005 213.785-173.957 387.643-387.671 387.643zM585.143 164.571v109.714c0 4.951-1.808 9.237-5.429 12.857s-7.906 5.429-12.857 5.429h-109.714c-4.953 0-9.239-1.808-12.857-5.429s-5.429-7.906-5.429-12.857v-109.714c0-4.951 1.81-9.237 5.429-12.857s7.904-5.429 12.857-5.429h109.714c4.951 0 9.237 1.808 12.857 5.429s5.429 7.906 5.429 12.857zM521.616 365.714c118.343 0 214.286 88.965 214.286 187.429s-95.943 178.286-214.286 178.286c-173.045 0-208.091-93.714-213.963-171.113 0-7.936 6.729-9.838 15.872-9.838s108.073 0 113.143 0c6.096 0 14.475 0.633 17.554 10.571 0 48.857 135.968 54.953 135.968-7.904 0-31.506-29.717-63.817-68.571-66.194s-82.286-7.607-82.286-54.199c0-13.022 0-25.673 0-44.693 0-19.015 9.717-22.343 27.431-22.343s54.853-0.002 54.853-0.002z"
transform="rotate(180 0 512) scale(-1 1)"
d="M521.3,576 C627.5,576 713.7,502 713.7,413.7 C713.7,325.4 627.6,253.6 521.3,253.6 C366,253.6 334.5,337.7 329.2,407.2 C329.2,414.3 335.2,416 343.5,416 L445,416 C450.5,416 458,415.5 460.8,406.5 C460.8,362.6 582.9,357.1 582.9,413.6 C582.9,441.9 556.2,470.9 521.3,473 C486.4,475.1 447.3,479.8 447.3,521.7 L447.3,553.8 C447.3,570.8 456.1,576 472,576 C487.9,576 521.3,576 521.3,576 M575.3,751.3 L575.3,655.3 C575.313862,651.055109 573.620137,646.982962 570.6,644 C567.638831,640.947672 563.552355,639.247987 559.3,639.29884 L463.3,639.29884 C459.055109,639.286138 454.982962,640.979863 452,644 C448.947672,646.961169 447.247987,651.047645 447.29884,655.3 L447.29884,751.3 C447.286138,755.544891 448.979863,759.617038 452,762.6 C454.961169,765.652328 459.047645,767.352013 463.3,767.30116 L559.3,767.30116 C563.544891,767.313862 567.617038,765.620137 570.6,762.6 C573.659349,759.643612 575.360354,755.553963 575.3,751.3 M512,896 C300.2,896 128,723.9 128,512 C128,300.3 300.2,128 512,128 C723.8,128 896,300.2 896,512 C896,723.8 723.7,896 512,896 M512.1,0 C229.7,0 0,229.8 0,512 C0,794.2 229.8,1024 512.1,1024 C794.4,1024 1024,794.3 1024,512 C1024,229.7 794.4,0 512.1,0"
/>
</svg>
</span>

View file

@ -118,6 +118,7 @@ exports[`<JsonFileUpload /> render 1`] = `
id="test-filename"
innerRef={null}
isDisabled={false}
isLeftTruncated={false}
isReadOnly={true}
isRequired={false}
name="test-filename"
@ -135,7 +136,9 @@ exports[`<JsonFileUpload /> render 1`] = `
disabled={false}
id="test-filename"
name="test-filename"
onBlur={[Function]}
onChange={[Function]}
onFocus={[Function]}
placeholder="Drag a file here or browse to upload"
readOnly={true}
required={false}
@ -155,7 +158,7 @@ exports[`<JsonFileUpload /> render 1`] = `
aria-disabled={false}
aria-label={null}
className="pf-c-button pf-m-control"
data-ouia-component-id={0}
data-ouia-component-id="OUIA-Generated-Button-control-1"
data-ouia-component-type="PF4/Button"
data-ouia-safe={true}
disabled={false}
@ -176,7 +179,7 @@ exports[`<JsonFileUpload /> render 1`] = `
aria-disabled={true}
aria-label={null}
className="pf-c-button pf-m-control pf-m-disabled"
data-ouia-component-id={1}
data-ouia-component-id="OUIA-Generated-Button-control-2"
data-ouia-component-type="PF4/Button"
data-ouia-safe={true}
disabled={true}
@ -370,6 +373,7 @@ exports[`<JsonFileUpload /> upload file 1`] = `
id="upload-filename"
innerRef={null}
isDisabled={false}
isLeftTruncated={false}
isReadOnly={true}
isRequired={false}
name="upload-filename"
@ -387,7 +391,9 @@ exports[`<JsonFileUpload /> upload file 1`] = `
disabled={false}
id="upload-filename"
name="upload-filename"
onBlur={[Function]}
onChange={[Function]}
onFocus={[Function]}
placeholder="Drag a file here or browse to upload"
readOnly={true}
required={false}
@ -407,7 +413,7 @@ exports[`<JsonFileUpload /> upload file 1`] = `
aria-disabled={false}
aria-label={null}
className="pf-c-button pf-m-control"
data-ouia-component-id={4}
data-ouia-component-id="OUIA-Generated-Button-control-3"
data-ouia-component-type="PF4/Button"
data-ouia-safe={true}
disabled={false}
@ -428,7 +434,7 @@ exports[`<JsonFileUpload /> upload file 1`] = `
aria-disabled={true}
aria-label={null}
className="pf-c-button pf-m-control pf-m-disabled"
data-ouia-component-id={5}
data-ouia-component-id="OUIA-Generated-Button-control-4"
data-ouia-component-type="PF4/Button"
data-ouia-safe={true}
disabled={true}

View file

@ -20,7 +20,6 @@ exports[`<ListEmptyState /> render 1`] = `
>
<path
d="M256 8C119 8 8 119 8 256s111 248 248 248 248-111 248-248S393 8 256 8zm144 276c0 6.6-5.4 12-12 12h-92v92c0 6.6-5.4 12-12 12h-56c-6.6 0-12-5.4-12-12v-92h-92c-6.6 0-12-5.4-12-12v-56c0-6.6 5.4-12 12-12h92v-92c0-6.6 5.4-12 12-12h56c6.6 0 12 5.4 12 12v92h92c6.6 0 12 5.4 12 12v56z"
transform=""
/>
</svg>
<h4
@ -36,7 +35,7 @@ exports[`<ListEmptyState /> render 1`] = `
<button
aria-disabled="false"
class="pf-c-button pf-m-primary"
data-ouia-component-id="0"
data-ouia-component-id="OUIA-Generated-Button-primary-1"
data-ouia-component-type="PF4/Button"
data-ouia-safe="true"
type="button"

View file

@ -0,0 +1,78 @@
import React, { useEffect } from "react";
import { useFieldArray, UseFormMethods } from "react-hook-form";
import {
TextInput,
Split,
SplitItem,
Button,
ButtonVariant,
} from "@patternfly/react-core";
import { MinusIcon, PlusIcon } from "@patternfly/react-icons";
type MultiLine = {
value: string;
};
export function convertToMultiline(fields: string[]): MultiLine[] {
return fields.map((field) => {
return { value: field };
});
}
export function toValue(formValue: MultiLine[]): string[] {
return formValue.map((field) => field.value);
}
export type MultiLineInputProps = {
form: UseFormMethods;
name: string;
};
export const MultiLineInput = ({ name, form }: MultiLineInputProps) => {
const { register, control } = form;
const { fields, append, remove } = useFieldArray({
name,
control,
});
useEffect(() => {
form.reset({
[name]: [{ value: "" }],
});
}, []);
return (
<>
{fields.map(({ id, value }, index) => (
<Split key={id}>
<SplitItem>
<TextInput
id={id}
ref={register()}
name={`${name}[${index}].value`}
defaultValue={value}
/>
</SplitItem>
<SplitItem>
{index === fields.length - 1 && (
<Button
variant={ButtonVariant.link}
onClick={() => append({})}
tabIndex={-1}
>
<PlusIcon />
</Button>
)}
{index !== fields.length - 1 && (
<Button
variant={ButtonVariant.link}
onClick={() => remove(index)}
tabIndex={-1}
>
<MinusIcon />
</Button>
)}
</SplitItem>
</Split>
))}
</>
);
};

View file

@ -1,4 +1,4 @@
import React, { useState, useContext } from "react";
import React, { useState, useContext, useEffect } from "react";
import { useHistory } from "react-router-dom";
import {
@ -34,14 +34,19 @@ export const RealmSelector = ({ realmList }: RealmSelectorProps) => {
realmName.charAt(0).toUpperCase() + realmName.slice(1);
const RealmText = ({ value }: { value: string }) => (
<Split>
<Split className="keycloak__realm_selector__list-item-split">
<SplitItem isFilled>{toUpperCase(value)}</SplitItem>
<SplitItem>{value === realm && <CheckIcon />}</SplitItem>
</Split>
);
const AddRealm = () => (
<Button component="div" isBlock onClick={() => history.push("/add-realm")}>
const AddRealm = ({ className }: { className?: string }) => (
<Button
component="div"
isBlock
onClick={() => history.push("/add-realm")}
className={className}
>
Create Realm
</Button>
);
@ -56,6 +61,10 @@ export const RealmSelector = ({ realmList }: RealmSelectorProps) => {
setFilteredItems(filtered || []);
};
useEffect(() => {
onFilter();
}, [search]);
const dropdownItems = realmList.map((r) => (
<DropdownItem
key={r.id}

View file

@ -79,7 +79,7 @@ exports[`renders realm selector 1`] = `
>
<div
className="pf-c-dropdown keycloak__realm_selector__dropdown"
data-ouia-component-id={0}
data-ouia-component-id="OUIA-Generated-Dropdown-1"
data-ouia-component-type="PF4/Dropdown"
data-ouia-safe={true}
id="realm-select"
@ -98,7 +98,7 @@ exports[`renders realm selector 1`] = `
Object {
"current": <div
class="pf-c-dropdown pf-m-expanded keycloak__realm_selector__dropdown"
data-ouia-component-id="1"
data-ouia-component-id="OUIA-Generated-Dropdown-1"
data-ouia-component-type="PF4/Dropdown"
data-ouia-safe="true"
id="realm-select"
@ -129,7 +129,6 @@ exports[`renders realm selector 1`] = `
>
<path
d="M31.3 192h257.3c17.8 0 26.7 21.5 14.1 34.1L174.1 354.8c-7.8 7.8-20.5 7.8-28.3 0L17.2 226.1C4.6 213.5 13.5 192 31.3 192z"
transform=""
/>
</svg>
</span>
@ -148,7 +147,7 @@ exports[`renders realm selector 1`] = `
tabindex="-1"
>
<div
class="pf-l-split"
class="pf-l-split keycloak__realm_selector__list-item-split"
>
<div
class="pf-l-split__item pf-m-fill"
@ -176,7 +175,7 @@ exports[`renders realm selector 1`] = `
<div
aria-disabled="false"
class="pf-c-button pf-m-primary pf-m-block"
data-ouia-component-id="2"
data-ouia-component-id="OUIA-Generated-Button-primary-1"
data-ouia-component-type="PF4/Button"
data-ouia-safe="true"
>
@ -207,7 +206,7 @@ exports[`renders realm selector 1`] = `
Object {
"current": <div
class="pf-c-dropdown pf-m-expanded keycloak__realm_selector__dropdown"
data-ouia-component-id="1"
data-ouia-component-id="OUIA-Generated-Dropdown-1"
data-ouia-component-type="PF4/Dropdown"
data-ouia-safe="true"
id="realm-select"
@ -238,7 +237,6 @@ exports[`renders realm selector 1`] = `
>
<path
d="M31.3 192h257.3c17.8 0 26.7 21.5 14.1 34.1L174.1 354.8c-7.8 7.8-20.5 7.8-28.3 0L17.2 226.1C4.6 213.5 13.5 192 31.3 192z"
transform=""
/>
</svg>
</span>
@ -257,7 +255,7 @@ exports[`renders realm selector 1`] = `
tabindex="-1"
>
<div
class="pf-l-split"
class="pf-l-split keycloak__realm_selector__list-item-split"
>
<div
class="pf-l-split__item pf-m-fill"
@ -285,7 +283,7 @@ exports[`renders realm selector 1`] = `
<div
aria-disabled="false"
class="pf-c-button pf-m-primary pf-m-block"
data-ouia-component-id="2"
data-ouia-component-id="OUIA-Generated-Button-primary-1"
data-ouia-component-type="PF4/Button"
data-ouia-safe="true"
>
@ -337,7 +335,6 @@ exports[`renders realm selector 1`] = `
>
<path
d="M31.3 192h257.3c17.8 0 26.7 21.5 14.1 34.1L174.1 354.8c-7.8 7.8-20.5 7.8-28.3 0L17.2 226.1C4.6 213.5 13.5 192 31.3 192z"
transform=""
/>
</svg>
</CaretDownIcon>

View file

@ -26,6 +26,18 @@
width: 100%;
}
.keycloak__realm_selector__list-item-split {
width: 100%;
text-align: left;
}
/* the last child is the realm selector button, and this is the only way to style the li around it */
.keycloak__realm_selector__context_selector li:last-child {
position: sticky;
bottom: 0;
background-color: var(--pf-c-context-selector__menu--BackgroundColor);
}
.keycloak__page_nav__nav_item__realm-selector {
margin-top: var(--pf-c-nav__link--PaddingTop);
padding-right: var(--pf-c-nav__link--PaddingRight);

View file

@ -7,9 +7,11 @@ import {
InputGroup,
TextInput,
Button,
ButtonVariant,
Pagination,
} from "@patternfly/react-core";
import { SearchIcon } from "@patternfly/react-icons";
import { useTranslation } from "react-i18next";
type TableToolbarProps = {
count: number;
@ -20,6 +22,12 @@ type TableToolbarProps = {
onPerPageSelect: (max: number, first: number) => void;
toolbarItem?: React.ReactNode;
children: React.ReactNode;
inputGroupName?: string;
inputGroupPlaceholder?: string;
inputGroupOnChange?: (
newInput: string,
event: React.FormEvent<HTMLInputElement>
) => void;
};
export const TableToolbar = ({
@ -31,7 +39,11 @@ export const TableToolbar = ({
onPerPageSelect,
toolbarItem,
children,
inputGroupName,
inputGroupPlaceholder,
inputGroupOnChange,
}: TableToolbarProps) => {
const { t } = useTranslation("groups");
const page = first / max;
const pagination = (variant: "top" | "bottom" = "top") => (
<Pagination
@ -55,15 +67,29 @@ export const TableToolbar = ({
<>
<Toolbar>
<ToolbarContent>
<ToolbarItem>
<InputGroup>
<TextInput type="text" aria-label="search for client criteria" />
<Button variant="control" aria-label="search for client">
<SearchIcon />
</Button>
</InputGroup>
</ToolbarItem>
{toolbarItem && <ToolbarItem>{toolbarItem}</ToolbarItem>}
<React.Fragment>
{inputGroupName && (
<ToolbarItem>
<InputGroup>
<TextInput
name={inputGroupName}
id={inputGroupName}
type="search"
aria-label={t("Search")}
placeholder={inputGroupPlaceholder}
onChange={inputGroupOnChange}
/>
<Button
variant={ButtonVariant.control}
aria-label={t("Search")}
>
<SearchIcon />
</Button>
</InputGroup>
</ToolbarItem>
)}
</React.Fragment>
{toolbarItem}
<ToolbarItem variant="pagination">{pagination()}</ToolbarItem>
</ToolbarContent>
</Toolbar>

View file

@ -0,0 +1,108 @@
import React, { ReactElement, useContext, useState } from "react";
import {
Text,
PageSection,
TextContent,
Divider,
Level,
LevelItem,
Switch,
Toolbar,
ToolbarContent,
ToolbarItem,
Badge,
Select,
} from "@patternfly/react-core";
import { HelpContext } from "../help-enabler/HelpHeader";
import { useTranslation } from "react-i18next";
export type ViewHeaderProps = {
titleKey: string;
badge?: string;
subKey: string;
selectItems?: ReactElement[];
isEnabled?: boolean;
onSelect?: (value: string) => void;
onToggle?: (value: boolean) => void;
};
export const ViewHeader = ({
titleKey,
badge,
subKey,
selectItems,
isEnabled,
onSelect,
onToggle,
}: ViewHeaderProps) => {
const { t } = useTranslation();
const { enabled } = useContext(HelpContext);
const [open, setOpen] = useState(false);
const [checked, setChecked] = useState(isEnabled);
return (
<>
<PageSection variant="light">
<Level hasGutter>
<LevelItem>
<Level>
<LevelItem>
<TextContent className="pf-u-mr-sm">
<Text component="h1">{t(titleKey)}</Text>
</TextContent>
</LevelItem>
{badge && (
<LevelItem>
<Badge>{badge}</Badge>
</LevelItem>
)}
</Level>
</LevelItem>
<LevelItem></LevelItem>
{selectItems && (
<LevelItem>
<Toolbar>
<ToolbarContent>
<ToolbarItem>
<Switch
id={`${titleKey}-switch`}
label={t("common:enabled")}
labelOff={t("common:disabled")}
className="pf-u-mr-lg"
isChecked={checked}
onChange={(value) => {
if (onToggle) {
onToggle(value);
}
setChecked(value);
}}
/>
</ToolbarItem>
<ToolbarItem>
<Select
isOpen={open}
onToggle={() => setOpen(!open)}
onSelect={(_, value) => {
if (onSelect) {
onSelect(value as string);
}
setOpen(false);
}}
>
{selectItems}
</Select>
</ToolbarItem>
</ToolbarContent>
</Toolbar>
</LevelItem>
)}
</Level>
{enabled && (
<TextContent>
<Text>{t(subKey)}</Text>
</TextContent>
)}
</PageSection>
<Divider />
</>
);
};

View file

@ -11,10 +11,11 @@ import {
import { useTranslation } from "react-i18next";
import { HttpClientContext } from "../http-service/HttpClientContext";
import { RealmContext } from "../components/realm-context/RealmContext";
import { useAlerts } from "../components/alert/Alerts";
export const GroupsCreateModal = ({isCreateModalOpen, handleModalToggle}) => {
const { t } = useTranslation("group");
const { t } = useTranslation("groups");
const httpClient = useContext(HttpClientContext)!;
const { realm } = useContext(RealmContext);
const [ nameValue, setNameValue ] = useState("");
@ -46,7 +47,7 @@ export const GroupsCreateModal = ({isCreateModalOpen, handleModalToggle}) => {
]}
>
<Form isHorizontal>
<FormGroup label={t("rootUrl")} fieldId="kc-root-url">
<FormGroup label={t("name")} fieldId="kc-root-url">
<TextInput
type="text"
id="create-group-name"

View file

@ -1,35 +1,106 @@
import React from "react";
import React, { useState, useEffect, useContext } from "react";
import {
Table,
TableHeader,
TableBody,
TableVariant,
} from "@patternfly/react-table";
import { GroupRepresentation } from "./models/groups";
import { Button } from "@patternfly/react-core";
import { useTranslation } from "react-i18next";
import { GroupRepresentation } from "./models/groups";
import { UsersIcon } from "@patternfly/react-icons";
import { HttpClientContext } from "../http-service/HttpClientContext";
type GroupsListProps = {
list: GroupRepresentation[];
list?: GroupRepresentation[];
};
export const GroupsList = ({ list }: GroupsListProps) => {
const { t } = useTranslation("group");
const columns: (keyof GroupRepresentation)[] = ["name"];
const { t } = useTranslation("groups");
const httpClient = useContext(HttpClientContext)!;
const columnGroupName: keyof GroupRepresentation = "name";
const columnGroupNumber: keyof GroupRepresentation = "membersLength";
const [formattedData, setFormattedData] = useState([
{ cells: [<Button key="0">Test</Button>], selected: false },
]);
const data = list.map((c) => {
return { cells: columns.map((col) => c[col]) };
});
const formatData = (data: GroupRepresentation[]) =>
data.map((group: { [key: string]: any }, index) => {
const groupName = group[columnGroupName];
const groupNumber = group[columnGroupNumber];
return {
cells: [
<Button variant="link" key={index}>
{groupName}
</Button>,
<div className="keycloak-admin--groups__member-count" key={index}>
<UsersIcon />
{groupNumber}
</div>,
],
selected: false,
};
});
useEffect(() => {
setFormattedData(formatData(list!));
}, [list]);
function onSelect(
event: React.FormEvent<HTMLInputElement>,
isSelected: boolean,
rowId: number
) {
let localRow;
if (rowId === undefined) {
localRow = formattedData.map((row: { [key: string]: any }) => {
row.selected = isSelected;
return row;
});
} else {
localRow = [...formattedData];
localRow[rowId].selected = isSelected;
setFormattedData(localRow);
}
}
// Delete individual rows using the action in the table
function onDelete(rowIndex: number) {
const localFilteredData = [...list!];
httpClient.doDelete(
`/admin/realms/master/groups/${localFilteredData[rowIndex].id}`
);
// TO DO update the state
}
const tableHeader = [{ title: t("groupName") }, { title: t("members") }];
const actions = [
{
title: t("moveTo"),
onClick: () => console.log("TO DO: Add move to functionality"),
},
{
title: t("common:Delete"),
onClick: () => onDelete,
},
];
console.log(list);
return (
<Table
aria-label="Simple Table"
variant={TableVariant.compact}
cells={[{ title: t("Name") }]}
rows={data}
>
<TableHeader />
<TableBody />
</Table>
<React.Fragment>
{formattedData && (
<Table
actions={actions}
variant={TableVariant.compact}
onSelect={onSelect}
canSelectAll={false}
aria-label={t("tableOfGroups")}
cells={tableHeader}
rows={formattedData}
>
<TableHeader />
<TableBody />
</Table>
)}
</React.Fragment>
);
};

View file

@ -0,0 +1,10 @@
.keycloak-admin--groups__member-count {
display: flex;
align-items: center;
}
.keycloak-admin--groups__member-count svg {
color: var(--pf-global--Color--200);
font-size: var(--pf-global--FontSize--md);
margin-right: var(--pf-global--spacer--sm);
}

View file

@ -1,21 +1,40 @@
import React, { useContext, useState } from "react";
import React, { useContext, useState, useEffect } from "react";
import { useTranslation } from "react-i18next";
import { useHistory } from "react-router-dom";
import { Button, PageSection } from "@patternfly/react-core";
import { HttpClientContext } from "../http-service/HttpClientContext";
import { GroupsList } from "./GroupsList";
import { GroupsCreateModal } from "./GroupsCreateModal";
import { DataLoader } from "../components/data-loader/DataLoader";
import { GroupRepresentation } from "./models/groups";
import {
ServerGroupsArrayRepresentation,
ServerGroupMembersRepresentation,
} from "./models/server-info";
import { TableToolbar } from "../components/table-toolbar/TableToolbar";
import {
Button,
Divider,
Dropdown,
DropdownItem,
KebabToggle,
PageSection,
PageSectionVariants,
Title,
TitleSizes,
ToolbarItem,
} from "@patternfly/react-core";
import "./GroupsSection.css";
export const GroupsSection = () => {
const { t } = useTranslation("groups");
const history = useHistory();
const httpClient = useContext(HttpClientContext)!;
const [rawData, setRawData] = useState([{}]);
const [filteredData, setFilteredData] = useState([{}]);
const [max, setMax] = useState(10);
const [first, setFirst] = useState(0);
const [isKebabOpen, setIsKebabOpen] = useState(false);
const columnID: keyof GroupRepresentation = "id";
const membersLength: keyof GroupRepresentation = "membersLength";
const columnGroupName: keyof GroupRepresentation = "name";
const [isCreateModalOpen, setIsCreateModalOpen] = useState(false);
@ -24,41 +43,113 @@ export const GroupsSection = () => {
};
const loader = async () => {
return await httpClient
.doGet("/admin/realms/master/groups", { params: { first, max } })
.then((r) => r.data as GroupRepresentation[]);
const groups = await httpClient.doGet<ServerGroupsArrayRepresentation[]>(
"/admin/realms/master/groups",
{ params: { first, max } }
);
const groupsData = groups.data!;
const getMembers = async (id: number) => {
const response = await httpClient.doGet<
ServerGroupMembersRepresentation[]
>(`/admin/realms/master/groups/${id}/members`);
const responseData = response.data!;
return responseData.length;
};
const memberPromises = groupsData.map((group: { [key: string]: any }) =>
getMembers(group[columnID])
);
const memberData = await Promise.all(memberPromises);
const updatedObject = groupsData.map(
(group: { [key: string]: any }, i: number) => {
const object = Object.assign({}, group);
object[membersLength] = memberData[i];
return object;
}
);
return updatedObject;
};
useEffect(() => {
loader().then((data: GroupRepresentation[]) => {
data && setRawData(data);
setFilteredData(data);
});
}, []);
// Filter groups
const filterGroups = (newInput: string) => {
const localRowData: object[] = [];
rawData.forEach(function (obj: { [key: string]: string }) {
const groupName = obj[columnGroupName];
if (groupName.toLowerCase().includes(newInput.toLowerCase())) {
localRowData.push(obj);
}
});
setFilteredData(localRowData);
};
// Kebab delete action
const onKebabToggle = (isOpen: boolean) => {
setIsKebabOpen(isOpen);
};
const onKebabSelect = () => {
setIsKebabOpen(!isKebabOpen);
};
return (
<>
<PageSection variant="light">
<DataLoader loader={loader}>
{(groups) => (
<TableToolbar
count={groups!.length}
first={first}
max={max}
onNextClick={setFirst}
onPreviousClick={setFirst}
onPerPageSelect={(f, m) => {
setFirst(f);
setMax(m);
}}
toolbarItem={
<>
<Button onClick={() => handleModalToggle()}>
{t("Create group")}
</Button>
</>
}
>
<GroupsList list={groups} />
</TableToolbar>
<React.Fragment>
<PageSection variant={PageSectionVariants.light}>
<Title headingLevel="h3" size={TitleSizes["2xl"]}>
{t("groups")}
</Title>
</PageSection>
<Divider />
<PageSection variant={PageSectionVariants.light}>
<TableToolbar
count={10}
first={first}
max={max}
onNextClick={setFirst}
onPreviousClick={setFirst}
onPerPageSelect={(f, m) => {
setFirst(f);
setMax(m);
}}
inputGroupName="groupsToolbarTextInput"
inputGroupPlaceholder="Search groups"
inputGroupOnChange={filterGroups}
toolbarItem={
<>
<ToolbarItem>
<Button variant="primary" onClick={() => handleModalToggle()}>
{t("createGroup")}
</Button>
</ToolbarItem>
<ToolbarItem>
<Dropdown
onSelect={onKebabSelect}
toggle={<KebabToggle onToggle={onKebabToggle} />}
isOpen={isKebabOpen}
isPlain
dropdownItems={[
<DropdownItem key="action" component="button">
{t("delete")}
</DropdownItem>,
]}
/>
</ToolbarItem>
</>
}
>
{rawData && filteredData && (
<GroupsList list={filteredData ? filteredData : rawData} />
)}
</DataLoader>
</TableToolbar>
<GroupsCreateModal isCreateModalOpen={isCreateModalOpen} handleModalToggle={handleModalToggle}/>
</PageSection>
</>
</React.Fragment>
);
};

View file

@ -0,0 +1,26 @@
[
{
"name": "IT-1",
"groupNumber": 732
},
{
"name": "IT-2",
"groupNumber": 532
},
{
"name": "IT-3",
"groupNumber": 43
},
{
"name": "3scale-group",
"groupNumber": 732
},
{
"name": "Fuse-group",
"groupNumber": 532
},
{
"name": "Apicurio-group",
"groupNumber": 43
}
]

15
src/groups/messages.json Normal file
View file

@ -0,0 +1,15 @@
{
"groups": {
"groups": "Groups",
"createGroup": "Create group",
"groupName": "Group name",
"searchForGroups": "Search for groups",
"searchGroups": "Search groups",
"search": "Search",
"members": "Members",
"moveTo": "Move to",
"delete": "Delete",
"tableOfGroups": "Table of groups",
"name": "Name"
}
}

View file

@ -7,4 +7,7 @@ export interface GroupRepresentation {
clientRoles?: { [index: string]: string[] };
subGroups?: GroupRepresentation[];
access?: { [index: string]: boolean };
groupNumber?: number;
membersLength?: number;
list?: [];
}

View file

@ -0,0 +1,15 @@
export interface ServerGroupsRepresentation {
id?: number;
name?: string;
path?: string;
subGroups?: [];
}
// TO DO: Update this to represent the data that is returned
export interface ServerGroupMembersRepresentation {
data?: [];
}
export interface ServerGroupsArrayRepresentation {
groups: { [index: string]: ServerGroupsRepresentation[] };
}

View file

@ -1,5 +1,13 @@
{
"help": {
"storybook": "Sometimes you need some help and it's nice when the app does that"
"storybook": "Sometimes you need some help and it's nice when the app does that",
"clientScope": {
"name": "Name of the client scope. Must be unique in the realm. Name should not contain space characters as it is used as value of scope parameter",
"description": "Description of the client scope",
"protocol": "Which SSO protocol configuration is being supplied by this client scope",
"displayOnConsentScreen": "If on, and this client scope is added to some client with consent required, the text specified by 'Consent Screen Text' will be displayed on consent screen. If off, this client scope will not be displayed on the consent screen",
"consentScreenText": "Text that will be shown on the consent screen when this client scope is added to some client with consent required. Defaults to name of client scope if it is not filled",
"guiOrder": "Specify order of the provider in GUI (such as in Consent page) as integer"
}
}
}

View file

@ -4,15 +4,24 @@ import { initReactI18next } from "react-i18next";
import common from "./common-messages.json";
import clients from "./clients/messages.json";
import clientScopes from "./client-scopes/messages.json";
import groups from "./groups/messages.json";
import realm from "./realm/messages.json";
import roles from "./realm-roles/messages.json";
import help from "./help.json";
const initOptions = {
ns: ["common", "help", "clients", "realm", "roles"],
defaultNS: "common",
resources: {
en: { ...common, ...help, ...clients, ...realm, ...roles },
en: {
...common,
...help,
...clients,
...clientScopes,
...groups,
...realm,
...roles,
},
},
lng: "en",
fallbackLng: "en",

View file

@ -1,77 +1,77 @@
[
{
"name":"Admin",
"composite":true,
"description": "Lorem ipsum dolor sit amet"
},
{
"name":"Author",
"composite":false,
"description": "Lorem ipsum dolor sit amet"
},
{
"name":"Billing",
"composite":true,
"description": "Lorem ipsum dolor sit"
},
{
"name":"Contributor",
"composite":true,
"description": "Lorem ipsum dolor sit, consecte"
},
{
"name":"Editor",
"composite":true,
"description": "Lorem ipsum dolor sit amet"
},
{
"name":"Engineer",
"composite":true,
"description": "Lorem ipsum dolor sit amet"
},
{
"name":"Member",
"composite":false,
"description": "Lorem ipsum dolor sit amet"
},
{
"name":"Moderator",
"composite":true,
"description": "Lorem ipsum dolor sit amet"
},
{
"name":"Owner",
"composite":true,
"description": "Lorem ipsum dolor sit amet"
},
{
"name":"Reader",
"composite":true,
"description": "Lorem ipsum dolor sit amet"
},
{
"name":"Subscriber",
"composite":true,
"description": "Lorem ipsum dolor sit "
},
{
"name":"Teenager",
"composite":true,
"description": "Lorem ipsum dolor sit amet, consecte occaecat"
},
{
"name":"User",
"composite":true,
"description": "Lorem ipsum dolor sit amet, consecte"
},
{
"name":"Writer",
"composite":true,
"description": "Lorem ipsum dolor"
},
{
"name":"Zara",
"composite":true,
"description": "Lorem ipsum dolor sit amet"
}
]
{
"name":"Admin",
"composite":true,
"description": "Lorem ipsum dolor sit amet"
},
{
"name":"Author",
"composite":false,
"description": "Lorem ipsum dolor sit amet"
},
{
"name":"Billing",
"composite":true,
"description": "Lorem ipsum dolor sit"
},
{
"name":"Contributor",
"composite":true,
"description": "Lorem ipsum dolor sit, consecte"
},
{
"name":"Editor",
"composite":true,
"description": "Lorem ipsum dolor sit amet"
},
{
"name":"Engineer",
"composite":true,
"description": "Lorem ipsum dolor sit amet"
},
{
"name":"Member",
"composite":false,
"description": "Lorem ipsum dolor sit amet"
},
{
"name":"Moderator",
"composite":true,
"description": "Lorem ipsum dolor sit amet"
},
{
"name":"Owner",
"composite":true,
"description": "Lorem ipsum dolor sit amet"
},
{
"name":"Reader",
"composite":true,
"description": "Lorem ipsum dolor sit amet"
},
{
"name":"Subscriber",
"composite":true,
"description": "Lorem ipsum dolor sit "
},
{
"name":"Teenager",
"composite":true,
"description": "Lorem ipsum dolor sit amet, consecte occaecat"
},
{
"name":"User",
"composite":true,
"description": "Lorem ipsum dolor sit amet, consecte"
},
{
"name":"Writer",
"composite":true,
"description": "Lorem ipsum dolor"
},
{
"name":"Zara",
"composite":true,
"description": "Lorem ipsum dolor sit amet"
}
]

View file

@ -1,22 +1,21 @@
{
"roles": {
"createRole": "Create role",
"importRole": "Import role",
"roleID": "Role ID",
"type": "Type",
"homeURL": "Home URL",
"roleExplain": "Realm-level roles are a global namespace to define your roles.",
"roleName": "Role name",
"composite": "Composite",
"description": "Description",
"roleList": "Role list",
"generalSettings": "General Settings",
"capabilityConfig": "Capability config",
"roleImportError": "Could not import role",
"roleImportSuccess": "Role imported succeful",
"roleDeletedSucess": "The role has been deleted",
"roleDeleteError": "Could not delete role:",
"roleAuthentication": "Role authentication"
}
"roles": {
"createRole": "Create role",
"importRole": "Import role",
"roleID": "Role ID",
"type": "Type",
"homeURL": "Home URL",
"roleExplain": "Realm-level roles are a global namespace to define your roles.",
"roleName": "Role name",
"composite": "Composite",
"description": "Description",
"roleList": "Role list",
"generalSettings": "General Settings",
"capabilityConfig": "Capability config",
"roleImportError": "Could not import role",
"roleImportSuccess": "Role imported succeful",
"roleDeletedSucess": "The role has been deleted",
"roleDeleteError": "Could not delete role:",
"roleAuthentication": "Role authentication"
}
}

View file

@ -1,16 +1,13 @@
import React, { useState, FormEvent, useContext } from "react";
import React, { useContext } from "react";
import { useTranslation } from "react-i18next";
import {
Text,
PageSection,
TextContent,
FormGroup,
Form,
TextInput,
Switch,
ActionGroup,
Button,
Divider,
AlertVariant,
} from "@patternfly/react-core";
@ -19,6 +16,7 @@ import { RealmRepresentation } from "../models/Realm";
import { HttpClientContext } from "../../http-service/HttpClientContext";
import { useAlerts } from "../../components/alert/Alerts";
import { useForm, Controller } from "react-hook-form";
import { ViewHeader } from "../../components/view-header/ViewHeader";
export const NewRealmForm = () => {
const { t } = useTranslation("realm");
@ -50,12 +48,7 @@ export const NewRealmForm = () => {
return (
<>
<Alerts />
<PageSection variant="light">
<TextContent>
<Text component="h1">Create Realm</Text>
</TextContent>
</PageSection>
<Divider />
<ViewHeader titleKey="realm:createRealm" subKey="realm:realmExplain" />
<PageSection variant="light">
<Form isHorizontal onSubmit={handleSubmit(save)}>
<JsonFileUpload id="kc-realm-filename" onChange={handleFileChange} />

View file

@ -5,7 +5,8 @@
"enabled":"Enabled",
"create":"Create",
"createRealm": "Create realm",
"realmExplain": "A realm manages a set of users, credentials, roles, and groups. A user belongs to and logs into a realm. Realms are isolated from one another and can only manage and authenticate the users that they control.",
"noRealmRoles": "No realm roles",
"emptyStateText": "There aren't any realm roles in this realm. Create a realm role to get started."
}
}
}

View file

@ -1,3 +1,5 @@
import { ClientScopeRepresentation } from "../../client-scopes/models/client-scope";
export interface RealmRepresentation {
id: string;
realm: string;
@ -231,15 +233,6 @@ export interface ClientRepresentation {
origin: string;
}
export interface ClientScopeRepresentation {
id: string;
name: string;
description: string;
protocol: string;
attributes: { [index: string]: string };
protocolMappers: ProtocolMapperRepresentation[];
}
export interface UserFederationProviderRepresentation {
id: string;
displayName: string;

View file

@ -0,0 +1,67 @@
import React from "react";
import {
TextContent,
Text,
TextVariants,
ButtonVariant,
} from "@patternfly/react-core";
import { Meta, Story } from "@storybook/react";
import { action } from "@storybook/addon-actions";
import {
ConfirmDialogModal,
ConfirmDialogModalProps,
useConfirmDialog,
} from "../components/confirm-dialog/ConfirmDialog";
export default {
title: "Confirmation Dialog",
component: ConfirmDialogModal,
} as Meta;
const Template: Story<ConfirmDialogModalProps> = (args) => (
<ConfirmDialogModal {...args} />
);
export const Simple = Template.bind({});
Simple.args = {
titleKey: "Delete app02?",
messageKey: "If you delete this client, all associated data will be removed.",
continueButtonLabel: "Delete",
continueButtonVariant: ButtonVariant.danger,
};
export const Children = Template.bind({});
Children.args = {
titleKey: "Children as content!",
continueButtonVariant: ButtonVariant.primary,
children: (
<>
<TextContent>
<Text component={TextVariants.h3}>Hello World</Text>
</TextContent>
<p>Example of some other patternfly components.</p>
</>
),
};
const Test = () => {
const [toggle, Dialog] = useConfirmDialog({
titleKey: "Delete app02?",
messageKey:
"If you delete this client, all associated data will be removed.",
continueButtonLabel: "Delete",
onConfirm: action("confirm"),
onCancel: action("cancel"),
});
return (
<>
<button id="show" onClick={toggle}>
Show
</button>
<Dialog />
</>
);
};
export const Api = () => <Test />;

View file

@ -1,21 +0,0 @@
import React from "react";
import { Meta, Story } from "@storybook/react";
import {
ConfirmationDialog,
ConfirmationDialogProps,
} from "../components/confirmation-dialog/ConfirmationDialog";
export default {
title: "Confirmation dailog",
component: ConfirmationDialog,
parameters: { actions: { argTypesRegex: "^on.*" } },
} as Meta;
const Template: Story<ConfirmationDialogProps> = (args) => (
<ConfirmationDialog {...args} />
);
export const Dialog = Template.bind({});
Dialog.args = {
onConfirm: () => "Confirm",
};

View file

@ -1,15 +1,14 @@
import React from "react";
import { Meta, Story } from "@storybook/react";
import { ExternalLink } from "../components/external-link/ExternalLink";
import { ButtonProps } from "@patternfly/react-core";
export default {
title: "External link",
component: ExternalLink,
} as Meta;
const Template: Story<React.HTMLProps<HTMLAnchorElement>> = (args) => (
<ExternalLink {...args} />
);
const Template: Story<ButtonProps> = (args) => <ExternalLink {...args} />;
export const WithTitle = Template.bind({});
WithTitle.args = {
@ -21,3 +20,16 @@ export const WithoutTitle = Template.bind({});
WithoutTitle.args = {
href: "http://some-other-link.nl/super",
};
export const ApplicationLink = Template.bind({});
ApplicationLink.args = {
title: "Application link",
href: "/application/main",
};
export const DisabledLink = Template.bind({});
DisabledLink.args = {
title: "Disabled link",
href: "http://some-other-link.nl/super",
isAriaDisabled: true,
};

View file

@ -0,0 +1,37 @@
import React from "react";
import { Meta, Story } from "@storybook/react";
import { action } from "@storybook/addon-actions";
import { useForm } from "react-hook-form";
import { Button } from "@patternfly/react-core";
import {
MultiLineInput,
MultiLineInputProps,
toValue,
} from "../components/multi-line-input/MultiLineInput";
export default {
title: "MultiLineInput component",
component: MultiLineInput,
} as Meta;
const Template: Story<MultiLineInputProps> = (args) => {
const form = useForm();
return (
<form
onSubmit={form.handleSubmit((data) => {
action("submit")(toValue(data.items));
})}
>
<MultiLineInput {...args} form={form} />
<br />
<br />
<Button type="submit">Submit</Button>
</form>
);
};
export const View = Template.bind({});
View.args = {
name: "items",
};

View file

@ -0,0 +1,39 @@
import React from "react";
import { Meta, Story } from "@storybook/react";
import { Page, SelectOption } from "@patternfly/react-core";
import {
ViewHeader,
ViewHeaderProps,
} from "../components/view-header/ViewHeader";
export default {
title: "View Header",
component: ViewHeader,
} as Meta;
const Template: Story<ViewHeaderProps> = (args) => (
<Page>
<ViewHeader {...args} />
</Page>
);
export const Extended = Template.bind({});
Extended.args = {
titleKey: "This is the title",
badge: "badge",
subKey: "This is the description.",
selectItems: [
<SelectOption key="first" value="first-item">
First item
</SelectOption>,
<SelectOption key="second" value="second-item">
Second item
</SelectOption>,
],
};
export const Simple = Template.bind({});
Simple.args = {
titleKey: "Title simple",
subKey: "Some lengthy description about what this is about.",
};

233
yarn.lock
View file

@ -2839,56 +2839,51 @@
dependencies:
mkdirp "^1.0.4"
"@open-wc/webpack-import-meta-loader@^0.4.7":
version "0.4.7"
resolved "https://registry.yarnpkg.com/@open-wc/webpack-import-meta-loader/-/webpack-import-meta-loader-0.4.7.tgz#d8212640a386a66bf06a2a4c101936467839b5a1"
integrity sha512-F3d1EHRckk2+ZpgEEAgVITp8BU9DYLBhKOhNMREeQ1BwILRIhrt+V1bebpnd0Mz595jzd7Yh1wSibLsXibkCpg==
"@patternfly/patternfly@4.42.2", "@patternfly/patternfly@^4.42.2":
version "4.42.2"
resolved "https://registry.yarnpkg.com/@patternfly/patternfly/-/patternfly-4.42.2.tgz#236d87bd85f00cb7a16d0c2956638ecedc3fa6ef"
integrity sha512-VLDhNko4D09sKcnzWEzMr8T8z9btqAYpuK0ntWMsAwi+/C9XsKyaxPioxuEsm7PeuW6OU0neEzSDYMSUnwrMBQ==
"@patternfly/patternfly@4.31.6", "@patternfly/patternfly@^4.31.6":
version "4.31.6"
resolved "https://registry.yarnpkg.com/@patternfly/patternfly/-/patternfly-4.31.6.tgz#ef9919df610171760cd19920a904ca9b09a74593"
integrity sha512-gp8tpbE4Z6C1PIQwNiWMjO5XSr/UGjXs4InL/zmxgZbToyizUxsudwJyCObtdvDNoN57ZJp0gYWYy0tIuwEyMA==
"@patternfly/react-core@^4.40.4":
version "4.40.4"
resolved "https://registry.yarnpkg.com/@patternfly/react-core/-/react-core-4.40.4.tgz#e4409f89327e2fcdcd07a08833c0149e6f2f6966"
integrity sha512-NQuUgIVEty7BBNJMJAVRXejOGRGpRQwgQ8Rw/J/JlgkhtOrCSFX5cEbpAXMXLYWkJrz0++XfRK/FQMoQbvS2hQ==
"@patternfly/react-core@4.50.2", "@patternfly/react-core@^4.50.2":
version "4.50.2"
resolved "https://registry.yarnpkg.com/@patternfly/react-core/-/react-core-4.50.2.tgz#b66d9bd8804994af70c2d80ce43a0dee6b90de5c"
integrity sha512-EAzrgsNivoYJb+Zk0YGKgr24J4qqY8dnPt96NJKhEcSRmVeqJEvL6Uhd1xJF1hRKQ5Bzrn/Lp66C+u5fgmnUBw==
dependencies:
"@patternfly/react-icons" "^4.7.2"
"@patternfly/react-styles" "^4.7.2"
"@patternfly/react-tokens" "^4.9.4"
"@patternfly/react-icons" "^4.7.6"
"@patternfly/react-styles" "^4.7.5"
"@patternfly/react-tokens" "^4.9.8"
focus-trap "4.0.2"
react-dropzone "9.0.0"
tippy.js "5.1.2"
tslib "^1.11.1"
"@patternfly/react-icons@^4.7.2":
version "4.7.2"
resolved "https://registry.yarnpkg.com/@patternfly/react-icons/-/react-icons-4.7.2.tgz#f4ad252cb5682bd95da474ce9ce6ddf7fb3a1ac1"
integrity sha512-r1yCVHxUtRSblo8VwfaUM0d49z4eToZXAI0VzOnfKPRgSmGZrn6l8soQgDDtyQsSDr534Qvm55y/qLrlR9JCnw==
"@patternfly/react-icons@4.7.6", "@patternfly/react-icons@^4.7.6":
version "4.7.6"
resolved "https://registry.yarnpkg.com/@patternfly/react-icons/-/react-icons-4.7.6.tgz#d5f19192912284fc334abb034bc08967b4245f22"
integrity sha512-B1+gVqe4zS+xQGrQh09z846rWxS6JNAs7PpQJYPEJ1SLzLOwy5wEaOK67im9dD6niEJifJqCcbacCNtVZjlWag==
"@patternfly/react-styles@^4.7.2":
version "4.7.2"
resolved "https://registry.yarnpkg.com/@patternfly/react-styles/-/react-styles-4.7.2.tgz#6671a243401ef55adddcb0e0922f5f5f4eea840e"
integrity sha512-r3zyrt1mXcqdXaEq+otl1cGsN0Ou1k8uIJSY+4EGe2A5jLGbX3vBTwUrpPKLB6tUdNL+mZriFf+3oKhWbVZDkw==
"@patternfly/react-styles@^4.7.5":
version "4.7.5"
resolved "https://registry.yarnpkg.com/@patternfly/react-styles/-/react-styles-4.7.5.tgz#b5d7161c0c75b54974b7a4db5a69ab30f6dbcb45"
integrity sha512-xom9hI2QzztT5pxByTFj2h3E0s4zD/+wVVLqvugl98cy8bCNpfo97OftfDCxBSV0MuhFDMs8/zr9QJUcq/O8Lw==
"@patternfly/react-table@^4.15.5":
version "4.15.5"
resolved "https://registry.yarnpkg.com/@patternfly/react-table/-/react-table-4.15.5.tgz#7fc3fcd37a6fd4dca00cc32d24c76199ee41a7f1"
integrity sha512-GlyKrEDMY+yLvczj5rWpNKcUp90Ib7alKV9JK8rVLOpTsukQ0QplXxYFsnIrombcaw2V54XVdflZGjsB0GoHEw==
"@patternfly/react-table@4.16.20":
version "4.16.20"
resolved "https://registry.yarnpkg.com/@patternfly/react-table/-/react-table-4.16.20.tgz#43e1f855e6cde5fb39b7ce1a6e63c989abadf242"
integrity sha512-EBxTnlmEMUcIoL8r1i4sBGB2y1IY1ym9zc47TniC+huC+jWDgxLy2866N/GA+GHtuBT3aPR/0WtziWCD/To8QQ==
dependencies:
"@patternfly/patternfly" "4.31.6"
"@patternfly/react-core" "^4.40.4"
"@patternfly/react-icons" "^4.7.2"
"@patternfly/react-styles" "^4.7.2"
"@patternfly/react-tokens" "^4.9.4"
"@patternfly/patternfly" "4.42.2"
"@patternfly/react-core" "^4.50.2"
"@patternfly/react-icons" "^4.7.6"
"@patternfly/react-styles" "^4.7.5"
"@patternfly/react-tokens" "^4.9.8"
lodash "^4.17.19"
tslib "^1.11.1"
"@patternfly/react-tokens@^4.9.4":
version "4.9.4"
resolved "https://registry.yarnpkg.com/@patternfly/react-tokens/-/react-tokens-4.9.4.tgz#71ea3c33045fb29bcc8d98f2c0f07bfcdc89a12c"
integrity sha512-AJpcAvzWXvfThT2mx24rV7OJSHvZnIsOP1bVrXiubpFAJhi/Suq+LGe/lTPUnuSXaflwyDBRZDXWWmJb4yaWqg==
"@patternfly/react-tokens@^4.9.8":
version "4.9.8"
resolved "https://registry.yarnpkg.com/@patternfly/react-tokens/-/react-tokens-4.9.8.tgz#260079b01359bcaf23875890e412f3c6e87e0202"
integrity sha512-HBbtQHlWl3/B9KrS1fI3/k6sXDowfTsmw0zKuHXWjG552l0ApOrDmzh120Q7m6cvvkFv/Cw6XRwtJIDZBCEiyg==
"@reach/router@^1.2.1", "@reach/router@^1.3.3":
version "1.3.4"
@ -2948,6 +2943,14 @@
is-module "^1.0.0"
resolve "^1.17.0"
"@rollup/plugin-replace@^2.3.3":
version "2.3.3"
resolved "https://registry.yarnpkg.com/@rollup/plugin-replace/-/plugin-replace-2.3.3.tgz#cd6bae39444de119f5d905322b91ebd4078562e7"
integrity sha512-XPmVXZ7IlaoWaJLkSCDaa0Y6uVo5XQYHhiMFzOd5qSv5rE+t/UJToPIOE56flKIxBFQI27ONsxb7dqHnwSsjKQ==
dependencies:
"@rollup/pluginutils" "^3.0.8"
magic-string "^0.25.5"
"@rollup/pluginutils@^3.0.4", "@rollup/pluginutils@^3.1.0":
version "3.1.0"
resolved "https://registry.yarnpkg.com/@rollup/pluginutils/-/pluginutils-3.1.0.tgz#706b4524ee6dc8b103b3c995533e5ad680c02b9b"
@ -3002,10 +3005,10 @@
"@babel/core" "^7.10.5"
workerpool "^6.0.0"
"@snowpack/plugin-build-script@^2.0.6":
version "2.0.6"
resolved "https://registry.yarnpkg.com/@snowpack/plugin-build-script/-/plugin-build-script-2.0.6.tgz#95c85cc6c4c53763a91a2d170633918ecd0d3cc8"
integrity sha512-qtvXQq54MaBYJrCTa+DaK/KzUXYBkRukaGzIdhpqW/xzOknCjpufiQIy0VUzxwRQk0OlLC6/h3sV1hSMsoTVJw==
"@snowpack/plugin-build-script@^2.0.7":
version "2.0.7"
resolved "https://registry.yarnpkg.com/@snowpack/plugin-build-script/-/plugin-build-script-2.0.7.tgz#1d133030ceac51efce471b66622d1b7320d5ab9f"
integrity sha512-f1fZN0Blsfk25oMBf62wvhHm6v7DUG0W85+JFKzYI6SHOIHrYL37o9xaJX7RJpn2kEKCRymWsQo4PclQdnL77w==
dependencies:
execa "^4.0.3"
npm-run-path "^4.0.1"
@ -3018,10 +3021,10 @@
dotenv "^8.2.0"
dotenv-expand "^5.1.0"
"@snowpack/plugin-postcss@^1.0.2":
version "1.0.2"
resolved "https://registry.yarnpkg.com/@snowpack/plugin-postcss/-/plugin-postcss-1.0.2.tgz#902eacf40ecd465be52137b01dbbfc773902be6c"
integrity sha512-DRRBe2uySvuQ9AEeOnJEtZEcjS8h9g3f8idGHohVcU0G1Anff+MQgEEZ2sFgrFuERVFaqoFTz3av3ArBkT5G8A==
"@snowpack/plugin-postcss@1.0.4":
version "1.0.4"
resolved "https://registry.yarnpkg.com/@snowpack/plugin-postcss/-/plugin-postcss-1.0.4.tgz#59a7e40bfb225525b4894d06d20d1205f476af0c"
integrity sha512-7znEgujAa1IECR63tx50PPjZkH3DlxIECLZVMngi1/RgRZlKAqWxkORH0y8RtrxLmszHb/TnCwRY0dK5/zJGGQ==
"@snowpack/plugin-react-refresh@^2.1.0":
version "2.1.0"
@ -3030,30 +3033,31 @@
dependencies:
react-refresh "^0.8.3"
"@snowpack/plugin-run-script@^2.1.1":
version "2.1.1"
resolved "https://registry.yarnpkg.com/@snowpack/plugin-run-script/-/plugin-run-script-2.1.1.tgz#f0f71cab5e0a482dfceb3306ae3e7d9cab859746"
integrity sha512-ZLOcu6n+eLDNxTxKlKK/+smn9PWr/epUKGPw8tBHr3qdKQ64WNpCVr0jnafanzWpzQWD8k92J8POfskKMuEI5Q==
"@snowpack/plugin-run-script@^2.1.2":
version "2.1.2"
resolved "https://registry.yarnpkg.com/@snowpack/plugin-run-script/-/plugin-run-script-2.1.2.tgz#51a620f99cc8ba4a23d3fc5c748f3c16aada7ad4"
integrity sha512-120uF0GKpEY/jHRuOZnxH8siLPXDSKB44Fy9d+zMWybsVvkIvV7pqcT9T9ojnth6QRB+f39tPaiqt+Ej/ByBjg==
dependencies:
execa "^4.0.3"
npm-run-path "^4.0.1"
"@snowpack/plugin-webpack@^2.0.6":
version "2.0.6"
resolved "https://registry.yarnpkg.com/@snowpack/plugin-webpack/-/plugin-webpack-2.0.6.tgz#8d9e206c5fd1ec829413368584470395eb52bd29"
integrity sha512-XbiE9+QLEz7sJchuQP1nu6JCwC/jXstBOE4Kwnx1w2ulTRnN4bFWLHIy4ZCh9zQp3/XH9xinC6+hmJHScbnrfg==
"@snowpack/plugin-webpack@2.0.12":
version "2.0.12"
resolved "https://registry.yarnpkg.com/@snowpack/plugin-webpack/-/plugin-webpack-2.0.12.tgz#44907f36542a35d326032108dcf9379e74bb0324"
integrity sha512-kX7Jfgz5V/vwgtsrnGQNOguKqrGsEHmLAnHnTQ2WRKS7i90F1zb4UUoWqLgrH1cYdD1cdAhlfsXnl9ex3iI+7w==
dependencies:
"@open-wc/webpack-import-meta-loader" "^0.4.7"
babel-loader "^8.1.0"
core-js "^3.5.0"
css-loader "^3.5.3"
file-loader "^6.0.0"
glob "^7.1.6"
html-minifier "^4.0.0"
jsdom "^16.2.2"
mini-css-extract-plugin "^0.9.0"
optimize-css-assets-webpack-plugin "^5.0.3"
terser-webpack-plugin "^3.0.1"
webpack "^4.43.0"
webpack-manifest-plugin "^2.2.0"
"@storybook/addon-actions@6.0.21", "@storybook/addon-actions@^6.0.21":
version "6.0.21"
@ -6527,6 +6531,14 @@ callsites@^3.0.0:
resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73"
integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==
camel-case@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/camel-case/-/camel-case-3.0.0.tgz#ca3c3688a4e9cf3a4cda777dc4dcbc713249cf73"
integrity sha1-yjw2iKTpzzpM2nd9xNy8cTJJz3M=
dependencies:
no-case "^2.2.0"
upper-case "^1.1.1"
camel-case@^4.1.1:
version "4.1.1"
resolved "https://registry.yarnpkg.com/camel-case/-/camel-case-4.1.1.tgz#1fc41c854f00e2f7d0139dfeba1542d6896fe547"
@ -6774,7 +6786,7 @@ classnames@^2.2.5:
resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.2.6.tgz#43935bffdd291f326dad0a205309b38d00f650ce"
integrity sha512-JR/iSQOSt+LQIWwrwEzJ9uk0xfN3mTVYMwt1Ir5mUcSN6pU+V4zQFFaJsclJbPuAUQH+yfWef6tm7l1quW3C8Q==
clean-css@^4.2.3:
clean-css@^4.2.1, clean-css@^4.2.3:
version "4.2.3"
resolved "https://registry.yarnpkg.com/clean-css/-/clean-css-4.2.3.tgz#507b5de7d97b48ee53d84adb0160ff6216380f78"
integrity sha512-VcMWDN54ZN/DS+g58HYL5/n4Zrqe8vHJpGA8KdgUXFU4fuP/aHNw8eld9SyEIyabIMJX/0RaY/fplOo5hYLSFA==
@ -8397,10 +8409,10 @@ es-get-iterator@^1.0.2:
is-string "^1.0.5"
isarray "^2.0.5"
es-module-lexer@^0.3.17:
version "0.3.19"
resolved "https://registry.yarnpkg.com/es-module-lexer/-/es-module-lexer-0.3.19.tgz#91065ea884f436a2e140e5006b1aaf154ab23f71"
integrity sha512-EMav8JgMd66ltDkVTVjknEyjxwjnFg4dfoTJD2T8yH9qunGK3kMKsIqDDSEWied2bOdNm2zTOUcZaKTScjWFiQ==
es-module-lexer@^0.3.24:
version "0.3.25"
resolved "https://registry.yarnpkg.com/es-module-lexer/-/es-module-lexer-0.3.25.tgz#24a1abcb9c5dc96923a8e42be033b801f788de06"
integrity sha512-H9VoFD5H9zEfiOX2LeTWDwMvAbLqcAyA2PIb40TOAvGpScOjit02oTGWgIh+M0rx2eJOKyJVM9wtpKFVgnyC3A==
es-to-primitive@^1.2.1:
version "1.2.1"
@ -8447,10 +8459,10 @@ es6-symbol@^3.1.1, es6-symbol@~3.1.3:
d "^1.0.1"
ext "^1.1.2"
esbuild@^0.6.11:
version "0.6.28"
resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.6.28.tgz#b14fc7a2a04d13c416f75024ac5c449f4248edc4"
integrity sha512-VU1QBpzUiuPdrmt6oN1Xd/w/xurSqvsrIUFKPIV9K25Fedqx0Zb9NaBtPlFXawM5vt0dxsbpKJxgylmPz1GlyQ==
esbuild@^0.6.28:
version "0.6.34"
resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.6.34.tgz#76565a60e006f45d5f273b6e59e61ed0816551f5"
integrity sha512-InRdL/Q96pUucPqovJzvuLhquZr6jOn81FDVwFjCKz1rYKIm9OdOC+7Fs4vr6x48vKBl5LzKgtjU39BUpO636A==
escalade@^3.0.1:
version "3.0.1"
@ -8812,6 +8824,11 @@ eventemitter3@^4.0.0:
resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-4.0.4.tgz#b5463ace635a083d018bdc7c917b4c5f10a85384"
integrity sha512-rlaVLnVxtxvoyLsQQFBx53YmXHDxRIzzTLbdfxqi4yocpSjAxXwkU0cScM5JgSKMqEhrZpnvQ2D9gjylR0AimQ==
eventemitter3@^4.0.4:
version "4.0.7"
resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-4.0.7.tgz#2de9b68f6528d5644ef5c59526a1b4a07306169f"
integrity sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==
events@^3.0.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/events/-/events-3.1.0.tgz#84279af1b34cb75aa88bf5ff291f6d0bd9b31a59"
@ -10308,6 +10325,19 @@ html-minifier-terser@^5.0.1:
relateurl "^0.2.7"
terser "^4.6.3"
html-minifier@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/html-minifier/-/html-minifier-4.0.0.tgz#cca9aad8bce1175e02e17a8c33e46d8988889f56"
integrity sha512-aoGxanpFPLg7MkIl/DDFYtb0iWz7jMFGqFhvEDZga6/4QTjneiD8I/NXL1x5aaoCp7FSIT6h/OhykDdPsbtMig==
dependencies:
camel-case "^3.0.0"
clean-css "^4.2.1"
commander "^2.19.0"
he "^1.2.0"
param-case "^2.1.1"
relateurl "^0.2.7"
uglify-js "^3.5.1"
html-parse-stringify2@2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/html-parse-stringify2/-/html-parse-stringify2-2.0.1.tgz#dc5670b7292ca158b7bc916c9a6735ac8872834a"
@ -11214,6 +11244,11 @@ isarray@^2.0.5:
resolved "https://registry.yarnpkg.com/isarray/-/isarray-2.0.5.tgz#8af1e4c1221244cc62459faf38940d4e644a5723"
integrity sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==
isbinaryfile@^4.0.6:
version "4.0.6"
resolved "https://registry.yarnpkg.com/isbinaryfile/-/isbinaryfile-4.0.6.tgz#edcb62b224e2b4710830b67498c8e4e5a4d2610b"
integrity sha512-ORrEy+SNVqUhrCaal4hA4fBzhggQQ+BaLntyPOdoEiwlKZW9BZiJXjg3RMiruE4tPEI3pyVPpySHQF/dKWperg==
isexe@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10"
@ -12500,7 +12535,7 @@ kleur@^3.0.3:
resolved "https://registry.yarnpkg.com/kleur/-/kleur-3.0.3.tgz#a79c9ecc86ee1ce3fa6206d1216c501f147fc07e"
integrity sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==
kleur@^4.1.0:
kleur@^4.1.1:
version "4.1.1"
resolved "https://registry.yarnpkg.com/kleur/-/kleur-4.1.1.tgz#80b49dd7d1afeba41b8dcdf4ecfff9252205fc52"
integrity sha512-BsNhM6T/yTWFG580CRnYhT3LfUuPK7Hwrm+W2H0G8lK/nogalP5Nsrh/cHjxVVkzl0sFm7z8b8rNcZCfKxeoxA==
@ -12785,6 +12820,11 @@ loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.2.0, loose-envify@^1.3
dependencies:
js-tokens "^3.0.0 || ^4.0.0"
lower-case@^1.1.1:
version "1.1.4"
resolved "https://registry.yarnpkg.com/lower-case/-/lower-case-1.1.4.tgz#9a2cabd1b9e8e0ae993a4bf7d5875c39c42e8eac"
integrity sha1-miyr0bno4K6ZOkv31YdcOcQujqw=
lower-case@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/lower-case/-/lower-case-2.0.1.tgz#39eeb36e396115cc05e29422eaea9e692c9408c7"
@ -13393,6 +13433,13 @@ nice-try@^1.0.4:
resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366"
integrity sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==
no-case@^2.2.0:
version "2.3.2"
resolved "https://registry.yarnpkg.com/no-case/-/no-case-2.3.2.tgz#60b813396be39b3f1288a4c1ed5d1e7d28b464ac"
integrity sha512-rmTZ9kz+f3rCvK2TD1Ue/oZlns7OGoIWP4fc3llxxRXlOkHKoWPPWJOfFYpITabSow43QJbRIoHQXtt10VldyQ==
dependencies:
lower-case "^1.1.1"
no-case@^3.0.3:
version "3.0.3"
resolved "https://registry.yarnpkg.com/no-case/-/no-case-3.0.3.tgz#c21b434c1ffe48b39087e86cfb4d2582e9df18f8"
@ -13985,12 +14032,12 @@ p-map@^4.0.0:
dependencies:
aggregate-error "^3.0.0"
p-queue@^6.2.1:
version "6.4.0"
resolved "https://registry.yarnpkg.com/p-queue/-/p-queue-6.4.0.tgz#5050b379393ea1814d6f9613a654f687d92c0466"
integrity sha512-X7ddxxiQ+bLR/CUt3/BVKrGcJDNxBr0pEEFKHHB6vTPWNUhgDv36GpIH18RmGM3YGPpBT+JWGjDDqsVGuF0ERw==
p-queue@^6.6.1:
version "6.6.1"
resolved "https://registry.yarnpkg.com/p-queue/-/p-queue-6.6.1.tgz#578891ada028a61371ec2692b26614d1b7d2b10a"
integrity sha512-miQiSxLYPYBxGkrldecZC18OTLjdUqnlRebGzPRiVxB8mco7usCmm7hFuxiTvp93K18JnLtE4KMMycjAu/cQQg==
dependencies:
eventemitter3 "^4.0.0"
eventemitter3 "^4.0.4"
p-timeout "^3.1.0"
p-reduce@^1.0.0:
@ -14036,6 +14083,13 @@ parallel-transform@^1.1.0:
inherits "^2.0.3"
readable-stream "^2.1.5"
param-case@^2.1.1:
version "2.1.1"
resolved "https://registry.yarnpkg.com/param-case/-/param-case-2.1.1.tgz#df94fd8cf6531ecf75e6bef9a0858fbc72be2247"
integrity sha1-35T9jPZTHs915r75oIWPvHK+Ikc=
dependencies:
no-case "^2.2.0"
param-case@^3.0.3:
version "3.0.3"
resolved "https://registry.yarnpkg.com/param-case/-/param-case-3.0.3.tgz#4be41f8399eff621c56eebb829a5e451d9801238"
@ -15183,7 +15237,12 @@ prepend-http@^1.0.0:
resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-1.0.4.tgz#d4f4562b0ce3696e41ac52d0e002e57a635dc6dc"
integrity sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw=
prettier@^2.0.5, prettier@~2.0.5:
prettier@^2.0.5:
version "2.1.2"
resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.1.2.tgz#3050700dae2e4c8b67c4c3f666cdb8af405e1ce5"
integrity sha512-16c7K+x4qVlJg9rEbXl7HEGmQyZlG4R9AgP+oHKRMsMsuk8s+ATStlf1NpDqyBI1HpVyfjLOeMhH2LvuNvV5Vg==
prettier@~2.0.5:
version "2.0.5"
resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.0.5.tgz#d6d56282455243f2f92cc1716692c08aa31522d4"
integrity sha512-7PtVymN48hGcO4fGjybyBSIWDsLU4H4XlvOHfq91pz9kkGlonzwTfYkaIEwiRg/dAJF9YlbsduBAgtYLi+8cFg==
@ -17035,10 +17094,10 @@ snapdragon@^0.8.1:
source-map-resolve "^0.5.0"
use "^3.1.0"
snowpack@^2.10.0:
version "2.10.0"
resolved "https://registry.yarnpkg.com/snowpack/-/snowpack-2.10.0.tgz#143b78e4a25f5bf604042d59628acb213acb6807"
integrity sha512-kZCGWV8cH+CaRcHe+vZEcbbfVqTlKqshHDKUIj7yM5al61WnoDXrFfK6RSDOW3BzxYTiuIyuyULCkWicdIzTaw==
snowpack@2.11.1:
version "2.11.1"
resolved "https://registry.yarnpkg.com/snowpack/-/snowpack-2.11.1.tgz#8565bed98806162bc1b001d3796c8863b18a2084"
integrity sha512-trEBfCRUaPLaoMZdyDyLdzQfQGy2tOrr0gGeSV8AVjos3m412s2mWsfhYEGCT1AE/t0mrGMhj7ccVz5fh5yOnQ==
dependencies:
"@babel/plugin-syntax-import-meta" "^7.10.4"
"@rollup/plugin-alias" "^3.0.1"
@ -17046,8 +17105,9 @@ snowpack@^2.10.0:
"@rollup/plugin-inject" "^4.0.2"
"@rollup/plugin-json" "^4.0.0"
"@rollup/plugin-node-resolve" "^9.0.0"
"@snowpack/plugin-build-script" "^2.0.6"
"@snowpack/plugin-run-script" "^2.1.1"
"@rollup/plugin-replace" "^2.3.3"
"@snowpack/plugin-build-script" "^2.0.7"
"@snowpack/plugin-run-script" "^2.1.2"
cacache "^15.0.0"
cachedir "^2.3.0"
chokidar "^3.4.0"
@ -17056,8 +17116,8 @@ snowpack@^2.10.0:
css-modules-loader-core "^1.1.0"
deepmerge "^4.2.2"
detect-port "^1.3.0"
es-module-lexer "^0.3.17"
esbuild "^0.6.11"
es-module-lexer "^0.3.24"
esbuild "^0.6.28"
etag "^1.8.1"
execa "^4.0.3"
find-cache-dir "^3.3.1"
@ -17066,13 +17126,14 @@ snowpack@^2.10.0:
got "^11.1.4"
http-proxy "^1.18.1"
is-builtin-module "^3.0.0"
isbinaryfile "^4.0.6"
jsonschema "^1.2.5"
kleur "^4.1.0"
kleur "^4.1.1"
mime-types "^2.1.26"
mkdirp "^1.0.3"
npm-run-path "^4.0.1"
open "^7.0.4"
p-queue "^6.2.1"
p-queue "^6.6.1"
resolve-from "^5.0.0"
rimraf "^3.0.0"
rollup "^2.23.0"
@ -18149,6 +18210,11 @@ ua-parser-js@^0.7.18:
resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.21.tgz#853cf9ce93f642f67174273cc34565ae6f308777"
integrity sha512-+O8/qh/Qj8CgC6eYBVBykMrNtp5Gebn4dlGD/kKXVkJNDwyrAwSIqwz8CDf+tsAIWVycKcku6gIXJ0qwx/ZXaQ==
uglify-js@^3.5.1:
version "3.10.4"
resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.10.4.tgz#dd680f5687bc0d7a93b14a3482d16db6eba2bfbb"
integrity sha512-kBFT3U4Dcj4/pJ52vfjCSfyLyvG9VYYuGYPmrPvAxRw/i7xHiT4VvCev+uiEMcEEiu6UNB6KgWmGtSUYIWScbw==
unbzip2-stream@^1.0.9:
version "1.4.3"
resolved "https://registry.yarnpkg.com/unbzip2-stream/-/unbzip2-stream-1.4.3.tgz#b0da04c4371311df771cdc215e87f2130991ace7"
@ -18343,6 +18409,11 @@ upath@^1.1.1:
resolved "https://registry.yarnpkg.com/upath/-/upath-1.2.0.tgz#8f66dbcd55a883acdae4408af8b035a5044c1894"
integrity sha512-aZwGpamFO61g3OlfT7OQCHqhGnW43ieH9WZeP7QxN/G/jS4jfqUkZxoryvJgVPEcrl5NL/ggHsSmLMHuH64Lhg==
upper-case@^1.1.1:
version "1.1.3"
resolved "https://registry.yarnpkg.com/upper-case/-/upper-case-1.1.3.tgz#f6b4501c2ec4cdd26ba78be7222961de77621598"
integrity sha1-9rRQHC7EzdJrp4vnIilh3ndiFZg=
uri-js@^4.2.2:
version "4.2.2"
resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.2.2.tgz#94c540e1ff772956e2299507c010aea6c8838eb0"
@ -18745,7 +18816,7 @@ webpack-log@^2.0.0:
ansi-colors "^3.0.0"
uuid "^3.3.2"
webpack-manifest-plugin@2.2.0:
webpack-manifest-plugin@2.2.0, webpack-manifest-plugin@^2.2.0:
version "2.2.0"
resolved "https://registry.yarnpkg.com/webpack-manifest-plugin/-/webpack-manifest-plugin-2.2.0.tgz#19ca69b435b0baec7e29fbe90fb4015de2de4f16"
integrity sha512-9S6YyKKKh/Oz/eryM1RyLVDVmy3NSPV0JXMRhZ18fJsq+AwGxUY34X54VNwkzYcEmEkDwNxuEOboCZEebJXBAQ==