diff --git a/models/kms_latency_histogram.go b/models/kms_latency_histogram.go
index 07b6dc21d..ea90d2dd6 100644
--- a/models/kms_latency_histogram.go
+++ b/models/kms_latency_histogram.go
@@ -36,6 +36,9 @@ type KmsLatencyHistogram struct {
// duration
Duration int64 `json:"duration,omitempty"`
+
+ // total
+ Total int64 `json:"total,omitempty"`
}
// Validate validates this kms latency histogram
diff --git a/models/kms_metrics_response.go b/models/kms_metrics_response.go
index 67da3ad53..e73fe2035 100644
--- a/models/kms_metrics_response.go
+++ b/models/kms_metrics_response.go
@@ -24,6 +24,7 @@ package models
import (
"context"
+ "strconv"
"github.com/go-openapi/errors"
"github.com/go-openapi/strfmt"
@@ -57,7 +58,7 @@ type KmsMetricsResponse struct {
// latency histogram
// Required: true
- LatencyHistogram *KmsLatencyHistogram `json:"latencyHistogram"`
+ LatencyHistogram []*KmsLatencyHistogram `json:"latencyHistogram"`
// request active
// Required: true
@@ -196,15 +197,22 @@ func (m *KmsMetricsResponse) validateLatencyHistogram(formats strfmt.Registry) e
return err
}
- if m.LatencyHistogram != nil {
- if err := m.LatencyHistogram.Validate(formats); err != nil {
- if ve, ok := err.(*errors.Validation); ok {
- return ve.ValidateName("latencyHistogram")
- } else if ce, ok := err.(*errors.CompositeError); ok {
- return ce.ValidateName("latencyHistogram")
- }
- return err
+ for i := 0; i < len(m.LatencyHistogram); i++ {
+ if swag.IsZero(m.LatencyHistogram[i]) { // not required
+ continue
}
+
+ if m.LatencyHistogram[i] != nil {
+ if err := m.LatencyHistogram[i].Validate(formats); err != nil {
+ if ve, ok := err.(*errors.Validation); ok {
+ return ve.ValidateName("latencyHistogram" + "." + strconv.Itoa(i))
+ } else if ce, ok := err.(*errors.CompositeError); ok {
+ return ce.ValidateName("latencyHistogram" + "." + strconv.Itoa(i))
+ }
+ return err
+ }
+ }
+
}
return nil
@@ -298,15 +306,19 @@ func (m *KmsMetricsResponse) ContextValidate(ctx context.Context, formats strfmt
func (m *KmsMetricsResponse) contextValidateLatencyHistogram(ctx context.Context, formats strfmt.Registry) error {
- if m.LatencyHistogram != nil {
- if err := m.LatencyHistogram.ContextValidate(ctx, formats); err != nil {
- if ve, ok := err.(*errors.Validation); ok {
- return ve.ValidateName("latencyHistogram")
- } else if ce, ok := err.(*errors.CompositeError); ok {
- return ce.ValidateName("latencyHistogram")
+ for i := 0; i < len(m.LatencyHistogram); i++ {
+
+ if m.LatencyHistogram[i] != nil {
+ if err := m.LatencyHistogram[i].ContextValidate(ctx, formats); err != nil {
+ if ve, ok := err.(*errors.Validation); ok {
+ return ve.ValidateName("latencyHistogram" + "." + strconv.Itoa(i))
+ } else if ce, ok := err.(*errors.CompositeError); ok {
+ return ce.ValidateName("latencyHistogram" + "." + strconv.Itoa(i))
+ }
+ return err
}
- return err
}
+
}
return nil
diff --git a/portal-ui/src/common/SecureComponent/permissions.ts b/portal-ui/src/common/SecureComponent/permissions.ts
index 9d05c9d30..e0c179c72 100644
--- a/portal-ui/src/common/SecureComponent/permissions.ts
+++ b/portal-ui/src/common/SecureComponent/permissions.ts
@@ -113,6 +113,26 @@ export const IAM_SCOPES = {
ADMIN_INSPECT_DATA: "admin:InspectData",
S3_ALL_ACTIONS: "s3:*",
ADMIN_ALL_ACTIONS: "admin:*",
+ KMS_ALL_ACTIONS: "kms:*",
+ KMS_STATUS: "kms:Status",
+ KMS_METRICS: "kms:Metrics",
+ KMS_APIS: "kms:API",
+ KMS_Version: "kms:Version",
+ KMS_CREATE_KEY: "kms:CreateKey",
+ KMS_DELETE_KEY: "kms:DeleteKey",
+ KMS_LIST_KEYS: "kms:ListKeys",
+ KMS_IMPORT_KEY: "kms:ImportKey",
+ KMS_KEY_STATUS: "kms:KeyStatus",
+ KMS_DESCRIBE_POLICY: "kms:DescribePolicy",
+ KMS_ASSIGN_POLICY: "kms:AssignPolicy",
+ KMS_DELETE_POLICY: "kms:DeletePolicy",
+ KMS_SET_POLICY: "kms:SetPolicy",
+ KMS_GET_POLICY: "kms:GetPolicy",
+ KMS_LIST_POLICIES: "kms:ListPolicies",
+ KMS_DESCRIBE_IDENTITY: "kms:DescribeIdentity",
+ KMS_DESCRIBE_SELF_IDENTITY: "kms:DescribeSelfIdentity",
+ KMS_DELETE_IDENTITY: "kms:DeleteIdentity",
+ KMS_LIST_IDENTITIES: "kms:ListIdentities",
};
export const IAM_PAGES = {
@@ -160,6 +180,13 @@ export const IAM_PAGES = {
/* Health */
HEALTH: "/health",
+ /* KMS */
+ KMS: "/kms",
+ KMS_STATUS: "/kms/status",
+ KMS_KEYS: "/kms/keys",
+ KMS_KEYS_ADD: "/kms/add-key/",
+ KMS_KEYS_IMPORT: "/kms/import-key/",
+
/* Support */
TOOLS: "/support",
REGISTER_SUPPORT: "/support/register",
@@ -454,6 +481,24 @@ export const IAM_PAGES_PERMISSIONS = {
IAM_SCOPES.ADMIN_SERVER_INFO,
IAM_SCOPES.ADMIN_CONFIG_UPDATE,
],
+ [IAM_PAGES.KMS]: [IAM_SCOPES.KMS_ALL_ACTIONS],
+ [IAM_PAGES.KMS_STATUS]: [IAM_SCOPES.KMS_ALL_ACTIONS, IAM_SCOPES.KMS_STATUS],
+ [IAM_PAGES.KMS_KEYS]: [
+ IAM_SCOPES.KMS_ALL_ACTIONS,
+ IAM_SCOPES.KMS_CREATE_KEY,
+ IAM_SCOPES.KMS_DELETE_KEY,
+ IAM_SCOPES.KMS_LIST_KEYS,
+ IAM_SCOPES.KMS_IMPORT_KEY,
+ IAM_SCOPES.KMS_KEY_STATUS,
+ ],
+ [IAM_PAGES.KMS_KEYS_ADD]: [
+ IAM_SCOPES.KMS_ALL_ACTIONS,
+ IAM_SCOPES.KMS_CREATE_KEY,
+ ],
+ [IAM_PAGES.KMS_KEYS_IMPORT]: [
+ IAM_SCOPES.KMS_ALL_ACTIONS,
+ IAM_SCOPES.KMS_IMPORT_KEY,
+ ],
[IAM_PAGES.IDP_LDAP_CONFIGURATIONS]: [
IAM_SCOPES.ADMIN_ALL_ACTIONS,
IAM_SCOPES.ADMIN_CONFIG_UPDATE,
diff --git a/portal-ui/src/icons/SidebarMenus/EncryptionIcon.tsx b/portal-ui/src/icons/SidebarMenus/EncryptionIcon.tsx
new file mode 100644
index 000000000..d51a353aa
--- /dev/null
+++ b/portal-ui/src/icons/SidebarMenus/EncryptionIcon.tsx
@@ -0,0 +1,37 @@
+// This file is part of MinIO Console Server
+// Copyright (c) 2022 MinIO, Inc.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see .
+
+import * as React from "react";
+import { SVGProps } from "react";
+
+const EncryptionIcon = (props: SVGProps) => (
+
+);
+
+export default EncryptionIcon;
diff --git a/portal-ui/src/icons/SidebarMenus/EncryptionStatusIcon.tsx b/portal-ui/src/icons/SidebarMenus/EncryptionStatusIcon.tsx
new file mode 100644
index 000000000..866c41f13
--- /dev/null
+++ b/portal-ui/src/icons/SidebarMenus/EncryptionStatusIcon.tsx
@@ -0,0 +1,38 @@
+// This file is part of MinIO Console Server
+// Copyright (c) 2022 MinIO, Inc.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see .
+
+import * as React from "react";
+import { SVGProps } from "react";
+
+const EncryptionStatusIcon = (props: SVGProps) => (
+
+);
+
+export default EncryptionStatusIcon;
diff --git a/portal-ui/src/screens/Console/Buckets/BucketDetails/AddKeyModal.tsx b/portal-ui/src/screens/Console/Buckets/BucketDetails/AddKeyModal.tsx
new file mode 100644
index 000000000..e01367074
--- /dev/null
+++ b/portal-ui/src/screens/Console/Buckets/BucketDetails/AddKeyModal.tsx
@@ -0,0 +1,95 @@
+// This file is part of MinIO Console Server
+// Copyright (c) 2022 MinIO, Inc.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see .
+
+import { DialogContentText, Grid } from "@mui/material";
+import React, { useState } from "react";
+import { ErrorResponseHandler } from "../../../../common/types";
+import { useAppDispatch } from "../../../../store";
+import { setErrorSnackMessage } from "../../../../systemSlice";
+import InputBoxWrapper from "../../Common/FormComponents/InputBoxWrapper/InputBoxWrapper";
+import useApi from "../../Common/Hooks/useApi";
+import ConfirmDialog from "../../Common/ModalWrapper/ConfirmDialog";
+import KMSHelpBox from "../../KMS/KMSHelpbox";
+
+interface IAddKeyModalProps {
+ closeAddModalAndRefresh: (refresh: boolean) => void;
+ addOpen: boolean;
+}
+
+const AddKeyModal = ({
+ closeAddModalAndRefresh,
+ addOpen,
+}: IAddKeyModalProps) => {
+ const dispatch = useAppDispatch();
+ const onAddSuccess = () => closeAddModalAndRefresh(true);
+ const onAddError = (err: ErrorResponseHandler) => {
+ closeAddModalAndRefresh(false);
+ dispatch(setErrorSnackMessage(err));
+ };
+ const onClose = () => closeAddModalAndRefresh(false);
+
+ const [addLoading, invokeAddApi] = useApi(onAddSuccess, onAddError);
+ const [keyName, setKeyName] = useState("");
+
+ const onConfirmAdd = () => {
+ invokeAddApi("POST", "/api/v1/kms/keys/", { key: keyName });
+ };
+
+ return (
+
+
+
+
+ ) => {
+ setKeyName(e.target.value);
+ }}
+ />
+
+
+ }
+ />
+ );
+};
+
+export default AddKeyModal;
diff --git a/portal-ui/src/screens/Console/Buckets/BucketDetails/EnableBucketEncryption.tsx b/portal-ui/src/screens/Console/Buckets/BucketDetails/EnableBucketEncryption.tsx
index bddeb26a6..fa2dfd287 100644
--- a/portal-ui/src/screens/Console/Buckets/BucketDetails/EnableBucketEncryption.tsx
+++ b/portal-ui/src/screens/Console/Buckets/BucketDetails/EnableBucketEncryption.tsx
@@ -14,10 +14,10 @@
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see .
-import React, { useEffect, useState } from "react";
+import React, { Fragment, useEffect, useState } from "react";
import Grid from "@mui/material/Grid";
import { LinearProgress, SelectChangeEvent } from "@mui/material";
-import { Button } from "mds";
+import { AddIcon, Button } from "mds";
import { Theme } from "@mui/material/styles";
import createStyles from "@mui/styles/createStyles";
import withStyles from "@mui/styles/withStyles";
@@ -29,12 +29,18 @@ import { BucketEncryptionInfo } from "../types";
import { ErrorResponseHandler } from "../../../../common/types";
import api from "../../../../common/api";
import ModalWrapper from "../../Common/ModalWrapper/ModalWrapper";
-import InputBoxWrapper from "../../Common/FormComponents/InputBoxWrapper/InputBoxWrapper";
import SelectWrapper from "../../Common/FormComponents/SelectWrapper/SelectWrapper";
import { BucketEncryptionIcon } from "mds";
import { setModalErrorSnackMessage } from "../../../../systemSlice";
import { useAppDispatch } from "../../../../store";
+import {
+ CONSOLE_UI_RESOURCE,
+ IAM_SCOPES,
+} from "../../../../common/SecureComponent/permissions";
+import { SecureComponent } from "../../../../common/SecureComponent";
+import TooltipWrapper from "../../Common/TooltipWrapper/TooltipWrapper";
+import AddKeyModal from "./AddKeyModal";
const styles = (theme: Theme) =>
createStyles({
@@ -62,6 +68,9 @@ const EnableBucketEncryption = ({
const [loading, setLoading] = useState(false);
const [kmsKeyID, setKmsKeyID] = useState("");
const [encryptionType, setEncryptionType] = useState("disabled");
+ const [keys, setKeys] = useState<[]>([]);
+ const [loadingKeys, setLoadingKeys] = useState(false);
+ const [addOpen, setAddOpen] = useState(false);
useEffect(() => {
if (encryptionCfg) {
@@ -74,6 +83,21 @@ const EnableBucketEncryption = ({
}
}, [encryptionCfg]);
+ useEffect(() => {
+ if (encryptionType === "sse-kms") {
+ api
+ .invoke("GET", `/api/v1/kms/keys`)
+ .then((res: any) => {
+ setKeys(res.results);
+ setLoadingKeys(false);
+ })
+ .catch((err: ErrorResponseHandler) => {
+ setLoadingKeys(false);
+ dispatch(setModalErrorSnackMessage(err));
+ });
+ }
+ }, [encryptionType, loadingKeys, dispatch]);
+
const enableBucketEncryption = (event: React.FormEvent) => {
event.preventDefault();
if (loading) {
@@ -108,90 +132,132 @@ const EnableBucketEncryption = ({
};
return (
- {
- closeModalAndRefresh();
- }}
- title="Enable Bucket Encryption"
- titleIcon={}
- >
-
+
+
);
};
diff --git a/portal-ui/src/screens/Console/Common/FormComponents/InputBoxWrapper/InputBoxWrapper.tsx b/portal-ui/src/screens/Console/Common/FormComponents/InputBoxWrapper/InputBoxWrapper.tsx
index d8f3237ef..70de46b4d 100644
--- a/portal-ui/src/screens/Console/Common/FormComponents/InputBoxWrapper/InputBoxWrapper.tsx
+++ b/portal-ui/src/screens/Console/Common/FormComponents/InputBoxWrapper/InputBoxWrapper.tsx
@@ -13,7 +13,7 @@
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see .
-import React, { useState } from "react";
+import React, { ClipboardEvent, useState } from "react";
import {
Grid,
IconButton,
@@ -44,6 +44,7 @@ interface InputBoxProps {
onChange: (e: React.ChangeEvent) => void;
onKeyPress?: (e: any) => void;
onFocus?: () => void;
+ onPaste?: (e: ClipboardEvent) => void;
value: string | boolean;
id: string;
name: string;
@@ -137,6 +138,7 @@ const InputBoxWrapper = ({
className = "",
onKeyPress,
onFocus,
+ onPaste,
}: InputBoxProps) => {
let inputProps: any = { "data-index": index, ...extraInputProps };
const [toggleTextInput, setToggleTextInput] = useState(false);
@@ -216,6 +218,7 @@ const InputBoxWrapper = ({
className={classes.inputRebase}
onKeyPress={onKeyPress}
onFocus={onFocus}
+ onPaste={onPaste}
/>
{inputBoxWrapperIcon && (
import("./DirectPV/DirectPVDrives"));
const DirectPVVolumes = React.lazy(() => import("./DirectPV/DirectPVVolumes"));
+const KMSRoutes = React.lazy(() => import("./KMS/KMSRoutes"));
+
const styles = (theme: Theme) =>
createStyles({
root: {
@@ -236,6 +238,7 @@ const Console = ({ classes }: IConsoleProps) => {
const [openSnackbar, setOpenSnackbar] = useState(false);
const ldapIsEnabled = (features && features.includes("ldap-idp")) || false;
+ const kmsIsEnabled = (features && features.includes("kms")) || false;
const obOnly = !!features?.includes("object-browser-only");
const restartServer = () => {
@@ -475,6 +478,11 @@ const Console = ({ classes }: IConsoleProps) => {
path: IAM_PAGES.LICENSE,
forceDisplay: true,
},
+ {
+ component: KMSRoutes,
+ path: IAM_PAGES.KMS,
+ fsHidden: !kmsIsEnabled,
+ },
];
const operatorConsoleRoutes: IRouteRule[] = [
diff --git a/portal-ui/src/screens/Console/KMS/AddKey.tsx b/portal-ui/src/screens/Console/KMS/AddKey.tsx
new file mode 100644
index 000000000..eca2335e6
--- /dev/null
+++ b/portal-ui/src/screens/Console/KMS/AddKey.tsx
@@ -0,0 +1,50 @@
+// This file is part of MinIO Console Server
+// Copyright (c) 2022 MinIO, Inc.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see .
+
+import React, { Fragment } from "react";
+
+import Grid from "@mui/material/Grid";
+import PageHeader from "../Common/PageHeader/PageHeader";
+import BackLink from "../../../common/BackLink";
+import { IAM_PAGES } from "../../../common/SecureComponent/permissions";
+import { ErrorResponseHandler } from "../../../common/types";
+import { setErrorSnackMessage } from "../../../systemSlice";
+import { useNavigate } from "react-router-dom";
+import { useAppDispatch } from "../../../store";
+import AddKeyForm from "./AddKeyForm";
+
+const AddKey = () => {
+ const dispatch = useAppDispatch();
+ const navigate = useNavigate();
+
+ const onSuccess = () => navigate(`${IAM_PAGES.KMS_KEYS}`);
+
+ const onError = (err: ErrorResponseHandler) =>
+ dispatch(setErrorSnackMessage(err));
+
+ return (
+
+
+ }
+ />
+
+
+
+ );
+};
+
+export default AddKey;
diff --git a/portal-ui/src/screens/Console/KMS/AddKeyForm.tsx b/portal-ui/src/screens/Console/KMS/AddKeyForm.tsx
new file mode 100644
index 000000000..9758a18a9
--- /dev/null
+++ b/portal-ui/src/screens/Console/KMS/AddKeyForm.tsx
@@ -0,0 +1,125 @@
+// This file is part of MinIO Console Server
+// Copyright (c) 2022 MinIO, Inc.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see .
+
+import React, { useState } from "react";
+import { Box } from "@mui/material";
+
+import Grid from "@mui/material/Grid";
+import { AddAccessRuleIcon, Button } from "mds";
+import PageLayout from "../Common/Layout/PageLayout";
+import InputBoxWrapper from "../Common/FormComponents/InputBoxWrapper/InputBoxWrapper";
+import { ErrorResponseHandler } from "../../../common/types";
+import FormLayout from "../Common/FormLayout";
+import useApi from "../Common/Hooks/useApi";
+import KMSHelpBox from "./KMSHelpbox";
+
+interface IAddKeyFormProps {
+ onSuccess: () => void;
+ onError: (err: ErrorResponseHandler) => void;
+}
+
+const AddKeyForm = ({ onSuccess, onError }: IAddKeyFormProps) => {
+ const [loading, invokeApi] = useApi(onSuccess, onError);
+ const [keyName, setKeyName] = useState("");
+
+ const addRecord = (event: React.FormEvent) => {
+ event.preventDefault();
+ invokeApi("POST", "/api/v1/kms/keys/", { key: keyName });
+ };
+
+ const resetForm = () => {
+ setKeyName("");
+ };
+
+ const validateKeyName = (keyName: string) => {
+ if (keyName.indexOf(" ") !== -1) {
+ return "Key name cannot contain spaces";
+ } else return "";
+ };
+
+ const validSave = keyName.trim() !== "" && keyName.indexOf(" ") === -1;
+
+ return (
+
+ }
+ helpbox={
+
+ }
+ >
+
+
+
+ );
+};
+
+export default AddKeyForm;
diff --git a/portal-ui/src/screens/Console/KMS/DeleteKMSModal.tsx b/portal-ui/src/screens/Console/KMS/DeleteKMSModal.tsx
new file mode 100644
index 000000000..9d02e802c
--- /dev/null
+++ b/portal-ui/src/screens/Console/KMS/DeleteKMSModal.tsx
@@ -0,0 +1,102 @@
+// This file is part of MinIO Console Server
+// Copyright (c) 2022 MinIO, Inc.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see .
+
+import React, { useState } from "react";
+
+import { DialogContentText, Grid } from "@mui/material";
+import { ErrorResponseHandler } from "../../../common/types";
+import useApi from "../Common/Hooks/useApi";
+import ConfirmDialog from "../Common/ModalWrapper/ConfirmDialog";
+import { setErrorSnackMessage } from "../../../systemSlice";
+import { useAppDispatch } from "../../../store";
+import WarningMessage from "../Common/WarningMessage/WarningMessage";
+import InputBoxWrapper from "../Common/FormComponents/InputBoxWrapper/InputBoxWrapper";
+import { ConfirmDeleteIcon } from "mds";
+
+interface IDeleteKMSModalProps {
+ closeDeleteModalAndRefresh: (refresh: boolean) => void;
+ deleteOpen: boolean;
+ selectedItem: string;
+ endpoint: string;
+ element: string;
+}
+
+const DeleteKMSModal = ({
+ closeDeleteModalAndRefresh,
+ deleteOpen,
+ selectedItem,
+ endpoint,
+ element,
+}: IDeleteKMSModalProps) => {
+ const dispatch = useAppDispatch();
+ const onDelSuccess = () => closeDeleteModalAndRefresh(true);
+ const onDelError = (err: ErrorResponseHandler) =>
+ dispatch(setErrorSnackMessage(err));
+ const onClose = () => closeDeleteModalAndRefresh(false);
+
+ const [deleteLoading, invokeDeleteApi] = useApi(onDelSuccess, onDelError);
+ const [retypeKey, setRetypeKey] = useState("");
+
+ if (!selectedItem) {
+ return null;
+ }
+
+ const onConfirmDelete = () => {
+ invokeDeleteApi("DELETE", `${endpoint}${selectedItem}`);
+ };
+
+ return (
+ }
+ isLoading={deleteLoading}
+ onConfirm={onConfirmDelete}
+ onClose={onClose}
+ confirmButtonProps={{
+ disabled: retypeKey !== selectedItem || deleteLoading,
+ }}
+ confirmationContent={
+
+
+
+
+ To continue please type {selectedItem} in the box.
+
+ ) => {
+ setRetypeKey(event.target.value);
+ }}
+ onPaste={(e) => e.preventDefault()}
+ label=""
+ value={retypeKey}
+ />
+
+
+ }
+ />
+ );
+};
+
+export default DeleteKMSModal;
diff --git a/portal-ui/src/screens/Console/KMS/ImportKey.tsx b/portal-ui/src/screens/Console/KMS/ImportKey.tsx
new file mode 100644
index 000000000..01c409682
--- /dev/null
+++ b/portal-ui/src/screens/Console/KMS/ImportKey.tsx
@@ -0,0 +1,157 @@
+// This file is part of MinIO Console Server
+// Copyright (c) 2022 MinIO, Inc.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see .
+
+import React, { Fragment, useState } from "react";
+import { Box } from "@mui/material";
+
+import Grid from "@mui/material/Grid";
+import { AddAccessRuleIcon, Button } from "mds";
+import PageHeader from "../Common/PageHeader/PageHeader";
+import PageLayout from "../Common/Layout/PageLayout";
+import InputBoxWrapper from "../Common/FormComponents/InputBoxWrapper/InputBoxWrapper";
+import BackLink from "../../../common/BackLink";
+import { IAM_PAGES } from "../../../common/SecureComponent/permissions";
+import { ErrorResponseHandler } from "../../../common/types";
+import FormLayout from "../Common/FormLayout";
+import { setErrorSnackMessage } from "../../../systemSlice";
+import { useNavigate } from "react-router-dom";
+import { useAppDispatch } from "../../../store";
+import useApi from "../Common/Hooks/useApi";
+import KMSHelpBox from "./KMSHelpbox";
+import CodeMirrorWrapper from "../Common/FormComponents/CodeMirrorWrapper/CodeMirrorWrapper";
+
+export const emptyContent = '{\n "bytes": ""\n}';
+
+const ImportKey = () => {
+ const dispatch = useAppDispatch();
+ const navigate = useNavigate();
+
+ const onSuccess = () => navigate(`${IAM_PAGES.KMS_KEYS}`);
+
+ const onError = (err: ErrorResponseHandler) =>
+ dispatch(setErrorSnackMessage(err));
+
+ const [loading, invokeApi] = useApi(onSuccess, onError);
+ const [keyName, setKeyName] = useState("");
+ const [keyContent, setKeyContent] = useState(emptyContent);
+
+ const importRecord = (event: React.FormEvent) => {
+ event.preventDefault();
+ let data = JSON.parse(keyContent);
+ invokeApi("POST", `/api/v1/kms/keys/${keyName}/import`, data);
+ };
+
+ const resetForm = () => {
+ setKeyName("");
+ setKeyContent("");
+ };
+
+ const validateKeyName = (keyName: string) => {
+ if (keyName.indexOf(" ") !== -1) {
+ return "Key name cannot contain spaces";
+ } else return "";
+ };
+
+ const validSave = keyName.trim() !== "" && keyName.indexOf(" ") === -1;
+
+ return (
+
+
+ }
+ />
+
+ }
+ helpbox={
+
+ }
+ >
+