diff --git a/package.json b/package.json
index 8b8ac3cb0d..a1ba96bbe1 100644
--- a/package.json
+++ b/package.json
@@ -20,6 +20,7 @@
"@patternfly/react-core": "^4.40.4",
"@patternfly/react-icons": "^4.7.2",
"@patternfly/react-table": "^4.15.5",
+ "file-saver": "^2.0.2",
"i18next": "^19.6.2",
"i18next-http-backend": "^1.0.18",
"keycloak-js": "^11.0.0",
@@ -47,6 +48,7 @@
"@testing-library/react": "^10.4.6",
"@types/dot": "^1.1.4",
"@types/enzyme": "^3.10.5",
+ "@types/file-saver": "^2.0.1",
"@types/jest": "^26.0.4",
"@types/react": "^16.9.23",
"@types/react-dom": "^16.9.5",
diff --git a/src/clients/ClientList.test.tsx b/src/clients/ClientList.test.tsx
index 6542b08191..96ed3ddd49 100644
--- a/src/clients/ClientList.test.tsx
+++ b/src/clients/ClientList.test.tsx
@@ -3,10 +3,14 @@ import { render } from "@testing-library/react";
import clientMock from "./mock-clients.json";
import { ClientList } from "./ClientList";
+import { I18nextProvider } from "react-i18next";
+import { i18n } from "../i18n";
test("renders ClientList", () => {
const { getByText } = render(
-
+
+
+
);
const headerElement = getByText(/Client ID/i);
expect(headerElement).toBeInTheDocument();
diff --git a/src/clients/ClientList.tsx b/src/clients/ClientList.tsx
index 3dfe10eb31..afba0f4ab3 100644
--- a/src/clients/ClientList.tsx
+++ b/src/clients/ClientList.tsx
@@ -1,4 +1,4 @@
-import React from "react";
+import React, { useContext } from "react";
import {
Table,
TableBody,
@@ -7,10 +7,15 @@ import {
IFormatter,
IFormatterValueType,
} from "@patternfly/react-table";
-import { Badge } from "@patternfly/react-core";
+import { Badge, AlertVariant } from "@patternfly/react-core";
+import { saveAs } from "file-saver";
import { ExternalLink } from "../components/external-link/ExternalLink";
import { ClientRepresentation } from "../model/client-model";
+import { HttpClientContext } from "../http-service/HttpClientContext";
+import { useAlerts } from "../components/alert/Alerts";
+import { AlertPanel } from "../components/alert/AlertPanel";
+import { useTranslation } from "react-i18next";
type ClientListProps = {
clients?: ClientRepresentation[];
@@ -25,9 +30,15 @@ const columns: (keyof ClientRepresentation)[] = [
];
export const ClientList = ({ baseUrl, clients }: ClientListProps) => {
+ const httpClient = useContext(HttpClientContext)!;
+ const { t } = useTranslation();
+ const [add, alerts, hide] = useAlerts();
+
+ const convertClientId = (clientId: string) =>
+ clientId.substring(0, clientId.indexOf("#"));
const enabled = (): IFormatter => (data?: IFormatterValueType) => {
const field = data!.toString();
- const value = field.substring(0, field.indexOf("#"));
+ const value = convertClientId(field);
return field.indexOf("true") !== -1 ? (
<>{value}>
) : (
@@ -61,25 +72,67 @@ export const ClientList = ({ baseUrl, clients }: ClientListProps) => {
return r;
})
.map((c) => {
- return { cells: columns.map((col) => c[col]) };
+ return { cells: columns.map((col) => c[col]), client: c };
});
return (
-
+ <>
+
+ {
+ const clientCopy = JSON.parse(JSON.stringify(data[rowId].client));
+ clientCopy.clientId = convertClientId(clientCopy.clientId);
+ delete clientCopy.id;
+
+ if (clientCopy.protocolMappers) {
+ for (let i = 0; i < clientCopy.protocolMappers.length; i++) {
+ delete clientCopy.protocolMappers[i].id;
+ }
+ }
+
+ saveAs(
+ new Blob([JSON.stringify(clientCopy, null, 2)], {
+ type: "application/json",
+ }),
+ clientCopy.clientId + ".json"
+ );
+ },
+ },
+ {
+ title: t("Delete"),
+ onClick: (_, rowId) => {
+ try {
+ httpClient.doDelete(
+ `/admin/realms/master/clients/${data[rowId].client.id}`
+ );
+ add(t("The client has been deleted"), AlertVariant.success);
+ } catch (error) {
+ add(
+ `${t("Could not delete client:")} ${error}`,
+ AlertVariant.danger
+ );
+ }
+ },
+ },
+ ]}
+ aria-label="Client list"
+ >
+
+
+
+ >
);
};
diff --git a/src/http-service/http-client.ts b/src/http-service/http-client.ts
index 88c7e3de4f..fe21d628a1 100644
--- a/src/http-service/http-client.ts
+++ b/src/http-service/http-client.ts
@@ -74,10 +74,13 @@ export class HttpClient {
await this.makeConfig(config)
);
- try {
- response.data = await response.json();
- } catch (e) {
- console.warn(e);
+ const contentType = response.headers.get("content-type");
+ if (contentType && contentType.indexOf("application/json") !== -1) {
+ try {
+ response.data = await response.json();
+ } catch (e) {
+ console.warn(e);
+ }
}
if (!response.ok) {
diff --git a/yarn.lock b/yarn.lock
index 79540cbff0..e0caedf71b 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -4124,6 +4124,11 @@
resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.39.tgz#e177e699ee1b8c22d23174caaa7422644389509f"
integrity sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw==
+"@types/file-saver@^2.0.1":
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/@types/file-saver/-/file-saver-2.0.1.tgz#e18eb8b069e442f7b956d313f4fadd3ef887354e"
+ integrity sha512-g1QUuhYVVAamfCifK7oB7G3aIl4BbOyzDOqVyUfEr4tfBKrXfeH+M+Tg7HKCXSrbzxYdhyCP7z9WbKo0R2hBCw==
+
"@types/glob-base@^0.3.0":
version "0.3.0"
resolved "https://registry.yarnpkg.com/@types/glob-base/-/glob-base-0.3.0.tgz#a581d688347e10e50dd7c17d6f2880a10354319d"
@@ -9165,6 +9170,11 @@ file-loader@^6.0.0:
loader-utils "^2.0.0"
schema-utils "^2.7.1"
+file-saver@^2.0.2:
+ version "2.0.2"
+ resolved "https://registry.yarnpkg.com/file-saver/-/file-saver-2.0.2.tgz#06d6e728a9ea2df2cce2f8d9e84dfcdc338ec17a"
+ integrity sha512-Wz3c3XQ5xroCxd1G8b7yL0Ehkf0TC9oYC6buPFkNnU9EnaPlifeAFCyCh+iewXTyFRcg0a6j3J7FmJsIhlhBdw==
+
file-selector@^0.1.8:
version "0.1.12"
resolved "https://registry.yarnpkg.com/file-selector/-/file-selector-0.1.12.tgz#fe726547be219a787a9dcc640575a04a032b1fd0"