Add KMS UI (#2377)
Adds components to interact with KMS server connected to minio
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
37
portal-ui/src/icons/SidebarMenus/EncryptionIcon.tsx
Normal file
37
portal-ui/src/icons/SidebarMenus/EncryptionIcon.tsx
Normal file
@@ -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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
import * as React from "react";
|
||||
import { SVGProps } from "react";
|
||||
|
||||
const EncryptionIcon = (props: SVGProps<SVGSVGElement>) => (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="255.209"
|
||||
height="255.209"
|
||||
viewBox="0 0 255.209 255.209"
|
||||
className={`min-icon`}
|
||||
fill={"currentcolor"}
|
||||
{...props}
|
||||
>
|
||||
<path
|
||||
id="KMS"
|
||||
d="M175.664,255.209V228.695H79.546v26.515H46.4V228.695H3a3,3,0,0,1-3-3V3A3,3,0,0,1,3,0H252.21a3,3,0,0,1,3,3V225.694a3,3,0,0,1-3,3h-43.4v26.515ZM23.2,29.83V198.865a9.954,9.954,0,0,0,9.943,9.943H222.065a9.954,9.954,0,0,0,9.943-9.943V29.83a9.954,9.954,0,0,0-9.943-9.943H33.144A9.954,9.954,0,0,0,23.2,29.83ZM222.065,198.866h0Zm-188.921,0V29.83H222.065V198.865H33.144ZM69.224,88.258a26.52,26.52,0,1,0,34.909,34.375h33.071a2,2,0,0,0,2-2V104.747a2,2,0,0,0-2-2H104.134A26.545,26.545,0,0,0,69.224,88.258ZM59.659,112.69a19.886,19.886,0,1,1,19.886,19.886A19.887,19.887,0,0,1,59.659,112.69Z"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
|
||||
export default EncryptionIcon;
|
||||
38
portal-ui/src/icons/SidebarMenus/EncryptionStatusIcon.tsx
Normal file
38
portal-ui/src/icons/SidebarMenus/EncryptionStatusIcon.tsx
Normal file
@@ -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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
import * as React from "react";
|
||||
import { SVGProps } from "react";
|
||||
|
||||
const EncryptionStatusIcon = (props: SVGProps<SVGSVGElement>) => (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="256"
|
||||
height="162.281"
|
||||
viewBox="0 0 256 162.281"
|
||||
className={`min-icon`}
|
||||
fill={"currentcolor"}
|
||||
{...props}
|
||||
>
|
||||
<path
|
||||
id="KMS-status"
|
||||
d="M-13110.45-17976.135a8.3,8.3,0,0,1-7.6-4.979l-30.661-70.426h-41.776a8.3,8.3,0,0,1-8.292-8.3,8.3,8.3,0,0,1,8.292-8.3h47.211a8.289,8.289,0,0,1,7.6,4.98l23.306,53.533,32.412-122.619a8.3,8.3,0,0,1,8.017-6.178h.074a8.293,8.293,0,0,1,7.978,6.336l23.061,94.307,25.367-45.307a8.267,8.267,0,0,1,7.232-4.254c.136,0,.276,0,.416.01a8.315,8.315,0,0,1,7.189,4.979l20.733,47.732h28.818a8.292,8.292,0,0,1,8.293,8.287,8.294,8.294,0,0,1-8.293,8.3h-34.254a8.273,8.273,0,0,1-7.6-4.988l-16.239-37.379-27.48,49.107a8.274,8.274,0,0,1-7.233,4.244,9.94,9.94,0,0,1-1.12-.07,8.309,8.309,0,0,1-6.936-6.258l-20.317-83.1-30.171,114.166a8.3,8.3,0,0,1-7.387,6.152C-13110.021-17976.143-13110.24-17976.135-13110.45-17976.135Z"
|
||||
transform="translate(13198.776 18138.416)"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
|
||||
export default EncryptionStatusIcon;
|
||||
@@ -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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
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<string>("");
|
||||
|
||||
const onConfirmAdd = () => {
|
||||
invokeAddApi("POST", "/api/v1/kms/keys/", { key: keyName });
|
||||
};
|
||||
|
||||
return (
|
||||
<ConfirmDialog
|
||||
title={""}
|
||||
confirmText={"Create"}
|
||||
isOpen={addOpen}
|
||||
isLoading={addLoading}
|
||||
onConfirm={onConfirmAdd}
|
||||
onClose={onClose}
|
||||
confirmButtonProps={{
|
||||
disabled: keyName.indexOf(" ") !== -1 || keyName === "" || addLoading,
|
||||
variant: "callAction",
|
||||
}}
|
||||
confirmationContent={
|
||||
<DialogContentText>
|
||||
<KMSHelpBox
|
||||
helpText={"Create Key"}
|
||||
contents={[
|
||||
"Create a new cryptographic key in the Key Management Service server connected to MINIO.",
|
||||
]}
|
||||
/>
|
||||
|
||||
<Grid item xs={12} marginTop={3}>
|
||||
<InputBoxWrapper
|
||||
id="key-name"
|
||||
name="key-name"
|
||||
label="Key Name"
|
||||
autoFocus={true}
|
||||
value={keyName}
|
||||
error={
|
||||
keyName.indexOf(" ") !== -1
|
||||
? "Key name cannot contain spaces"
|
||||
: ""
|
||||
}
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setKeyName(e.target.value);
|
||||
}}
|
||||
/>
|
||||
</Grid>
|
||||
</DialogContentText>
|
||||
}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default AddKeyModal;
|
||||
@@ -14,10 +14,10 @@
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
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<boolean>(false);
|
||||
const [kmsKeyID, setKmsKeyID] = useState<string>("");
|
||||
const [encryptionType, setEncryptionType] = useState<string>("disabled");
|
||||
const [keys, setKeys] = useState<[]>([]);
|
||||
const [loadingKeys, setLoadingKeys] = useState<boolean>(false);
|
||||
const [addOpen, setAddOpen] = useState<boolean>(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 (
|
||||
<ModalWrapper
|
||||
modalOpen={open}
|
||||
onClose={() => {
|
||||
closeModalAndRefresh();
|
||||
}}
|
||||
title="Enable Bucket Encryption"
|
||||
titleIcon={<BucketEncryptionIcon />}
|
||||
>
|
||||
<form
|
||||
noValidate
|
||||
autoComplete="off"
|
||||
onSubmit={(e: React.FormEvent<HTMLFormElement>) => {
|
||||
enableBucketEncryption(e);
|
||||
<Fragment>
|
||||
{addOpen && (
|
||||
<AddKeyModal
|
||||
addOpen={addOpen}
|
||||
closeAddModalAndRefresh={(refresh: boolean) => {
|
||||
setAddOpen(false);
|
||||
setLoadingKeys(true);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
||||
<ModalWrapper
|
||||
modalOpen={open}
|
||||
onClose={() => {
|
||||
closeModalAndRefresh();
|
||||
}}
|
||||
title="Enable Bucket Encryption"
|
||||
titleIcon={<BucketEncryptionIcon />}
|
||||
>
|
||||
<Grid container>
|
||||
<Grid item xs={12} className={classes.modalFormScrollable}>
|
||||
<Grid item xs={12} className={classes.formFieldRow}>
|
||||
<SelectWrapper
|
||||
onChange={(e: SelectChangeEvent<string>) => {
|
||||
setEncryptionType(e.target.value as string);
|
||||
<form
|
||||
noValidate
|
||||
autoComplete="off"
|
||||
onSubmit={(e: React.FormEvent<HTMLFormElement>) => {
|
||||
enableBucketEncryption(e);
|
||||
}}
|
||||
>
|
||||
<Grid container>
|
||||
<Grid item xs={12} className={classes.modalFormScrollable}>
|
||||
<Grid item xs={12} className={classes.formFieldRow}>
|
||||
<SelectWrapper
|
||||
onChange={(e: SelectChangeEvent<string>) => {
|
||||
setEncryptionType(e.target.value as string);
|
||||
}}
|
||||
id="select-encryption-type"
|
||||
name="select-encryption-type"
|
||||
label={"Encryption Type"}
|
||||
value={encryptionType}
|
||||
options={[
|
||||
{
|
||||
label: "Disabled",
|
||||
value: "disabled",
|
||||
},
|
||||
{
|
||||
label: "SSE-S3",
|
||||
value: "sse-s3",
|
||||
},
|
||||
{
|
||||
label: "SSE-KMS",
|
||||
value: "sse-kms",
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</Grid>
|
||||
|
||||
{encryptionType === "sse-kms" && (
|
||||
<Grid
|
||||
item
|
||||
xs={12}
|
||||
className={classes.formFieldRow}
|
||||
display={"flex"}
|
||||
>
|
||||
<SelectWrapper
|
||||
onChange={(e: SelectChangeEvent<string>) => {
|
||||
setKmsKeyID(e.target.value);
|
||||
}}
|
||||
id="select-kms-key-id"
|
||||
name="select-kms-key-id"
|
||||
label={"KMS Key ID"}
|
||||
value={kmsKeyID}
|
||||
options={keys.map((key: any) => {
|
||||
return {
|
||||
label: key.name,
|
||||
value: key.name,
|
||||
};
|
||||
})}
|
||||
/>
|
||||
<Grid marginLeft={1}>
|
||||
<SecureComponent
|
||||
scopes={[IAM_SCOPES.KMS_IMPORT_KEY]}
|
||||
resource={CONSOLE_UI_RESOURCE}
|
||||
errorProps={{ disabled: true }}
|
||||
>
|
||||
<TooltipWrapper tooltip={"Add key"}>
|
||||
<Button
|
||||
id={"import-key"}
|
||||
variant={"regular"}
|
||||
icon={<AddIcon />}
|
||||
onClick={(e) => {
|
||||
setAddOpen(true);
|
||||
e.preventDefault();
|
||||
}}
|
||||
/>
|
||||
</TooltipWrapper>
|
||||
</SecureComponent>
|
||||
</Grid>
|
||||
</Grid>
|
||||
)}
|
||||
</Grid>
|
||||
<Grid item xs={12} className={classes.modalButtonBar}>
|
||||
<Button
|
||||
id={"cancel"}
|
||||
type="submit"
|
||||
variant="regular"
|
||||
onClick={() => {
|
||||
closeModalAndRefresh();
|
||||
}}
|
||||
id="select-encryption-type"
|
||||
name="select-encryption-type"
|
||||
label={"Encryption Type"}
|
||||
value={encryptionType}
|
||||
options={[
|
||||
{
|
||||
label: "Disabled",
|
||||
value: "disabled",
|
||||
},
|
||||
{
|
||||
label: "SSE-S3",
|
||||
value: "sse-s3",
|
||||
},
|
||||
{
|
||||
label: "SSE-KMS",
|
||||
value: "sse-kms",
|
||||
},
|
||||
]}
|
||||
disabled={loading}
|
||||
label={"Cancel"}
|
||||
/>
|
||||
<Button
|
||||
id={"save"}
|
||||
type="submit"
|
||||
variant="callAction"
|
||||
disabled={loading}
|
||||
label={"Save"}
|
||||
/>
|
||||
</Grid>
|
||||
|
||||
{encryptionType === "sse-kms" && (
|
||||
<Grid item xs={12} className={classes.formFieldRow}>
|
||||
<InputBoxWrapper
|
||||
id="kms-key-id"
|
||||
name="kms-key-id"
|
||||
label="KMS Key ID"
|
||||
value={kmsKeyID}
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setKmsKeyID(e.target.value);
|
||||
}}
|
||||
/>
|
||||
{loading && (
|
||||
<Grid item xs={12}>
|
||||
<LinearProgress />
|
||||
</Grid>
|
||||
)}
|
||||
</Grid>
|
||||
<Grid item xs={12} className={classes.modalButtonBar}>
|
||||
<Button
|
||||
id={"cancel"}
|
||||
type="submit"
|
||||
variant="regular"
|
||||
onClick={() => {
|
||||
closeModalAndRefresh();
|
||||
}}
|
||||
disabled={loading}
|
||||
label={"Cancel"}
|
||||
/>
|
||||
<Button
|
||||
id={"save"}
|
||||
type="submit"
|
||||
variant="callAction"
|
||||
disabled={loading}
|
||||
label={"Save"}
|
||||
/>
|
||||
</Grid>
|
||||
{loading && (
|
||||
<Grid item xs={12}>
|
||||
<LinearProgress />
|
||||
</Grid>
|
||||
)}
|
||||
</Grid>
|
||||
</form>
|
||||
</ModalWrapper>
|
||||
</form>
|
||||
</ModalWrapper>
|
||||
</Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
import React, { useState } from "react";
|
||||
import React, { ClipboardEvent, useState } from "react";
|
||||
import {
|
||||
Grid,
|
||||
IconButton,
|
||||
@@ -44,6 +44,7 @@ interface InputBoxProps {
|
||||
onChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
|
||||
onKeyPress?: (e: any) => void;
|
||||
onFocus?: () => void;
|
||||
onPaste?: (e: ClipboardEvent<HTMLInputElement>) => 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<boolean>(false);
|
||||
@@ -216,6 +218,7 @@ const InputBoxWrapper = ({
|
||||
className={classes.inputRebase}
|
||||
onKeyPress={onKeyPress}
|
||||
onFocus={onFocus}
|
||||
onPaste={onPaste}
|
||||
/>
|
||||
{inputBoxWrapperIcon && (
|
||||
<div
|
||||
|
||||
@@ -172,6 +172,8 @@ const DirectPVDrives = React.lazy(() => 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<boolean>(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[] = [
|
||||
|
||||
50
portal-ui/src/screens/Console/KMS/AddKey.tsx
Normal file
50
portal-ui/src/screens/Console/KMS/AddKey.tsx
Normal file
@@ -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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
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 (
|
||||
<Fragment>
|
||||
<Grid item xs={12}>
|
||||
<PageHeader
|
||||
label={<BackLink to={IAM_PAGES.KMS_KEYS} label={"Keys"} />}
|
||||
/>
|
||||
<AddKeyForm onError={onError} onSuccess={onSuccess} />
|
||||
</Grid>
|
||||
</Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
export default AddKey;
|
||||
125
portal-ui/src/screens/Console/KMS/AddKeyForm.tsx
Normal file
125
portal-ui/src/screens/Console/KMS/AddKeyForm.tsx
Normal file
@@ -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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
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<string>("");
|
||||
|
||||
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 (
|
||||
<PageLayout>
|
||||
<FormLayout
|
||||
title={"Create Key"}
|
||||
icon={<AddAccessRuleIcon />}
|
||||
helpbox={
|
||||
<KMSHelpBox
|
||||
helpText={"Encryption Key"}
|
||||
contents={[
|
||||
"Create a new cryptographic key in the Key Management Service server connected to MINIO.",
|
||||
]}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<form
|
||||
noValidate
|
||||
autoComplete="off"
|
||||
onSubmit={(e: React.FormEvent<HTMLFormElement>) => {
|
||||
addRecord(e);
|
||||
}}
|
||||
>
|
||||
<Grid container item spacing={1}>
|
||||
<Grid item xs={12}>
|
||||
<InputBoxWrapper
|
||||
id="key-name"
|
||||
name="key-name"
|
||||
label="Key Name"
|
||||
autoFocus={true}
|
||||
value={keyName}
|
||||
error={validateKeyName(keyName)}
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setKeyName(e.target.value);
|
||||
}}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12} textAlign={"right"}>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "flex-end",
|
||||
marginTop: "20px",
|
||||
gap: "15px",
|
||||
}}
|
||||
>
|
||||
<Button
|
||||
id={"clear"}
|
||||
type="button"
|
||||
variant="regular"
|
||||
onClick={resetForm}
|
||||
label={"Clear"}
|
||||
/>
|
||||
|
||||
<Button
|
||||
id={"save-key"}
|
||||
type="submit"
|
||||
variant="callAction"
|
||||
color="primary"
|
||||
disabled={loading || !validSave}
|
||||
label={"Save"}
|
||||
/>
|
||||
</Box>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</form>
|
||||
</FormLayout>
|
||||
</PageLayout>
|
||||
);
|
||||
};
|
||||
|
||||
export default AddKeyForm;
|
||||
102
portal-ui/src/screens/Console/KMS/DeleteKMSModal.tsx
Normal file
102
portal-ui/src/screens/Console/KMS/DeleteKMSModal.tsx
Normal file
@@ -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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
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 (
|
||||
<ConfirmDialog
|
||||
title={`Delete ${element}`}
|
||||
confirmText={"Delete"}
|
||||
isOpen={deleteOpen}
|
||||
titleIcon={<ConfirmDeleteIcon />}
|
||||
isLoading={deleteLoading}
|
||||
onConfirm={onConfirmDelete}
|
||||
onClose={onClose}
|
||||
confirmButtonProps={{
|
||||
disabled: retypeKey !== selectedItem || deleteLoading,
|
||||
}}
|
||||
confirmationContent={
|
||||
<DialogContentText>
|
||||
<Grid item xs={12}>
|
||||
<WarningMessage
|
||||
title={"WARNING"}
|
||||
label={
|
||||
"Please note that this is a dangerous operation. Once a key has been deleted all data that has been encrypted with it cannot be decrypted anymore, and therefore, is lost."
|
||||
}
|
||||
/>
|
||||
</Grid>
|
||||
To continue please type <b>{selectedItem}</b> in the box.
|
||||
<Grid item xs={12}>
|
||||
<InputBoxWrapper
|
||||
id="retype-key"
|
||||
name="retype-key"
|
||||
onChange={(event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setRetypeKey(event.target.value);
|
||||
}}
|
||||
onPaste={(e) => e.preventDefault()}
|
||||
label=""
|
||||
value={retypeKey}
|
||||
/>
|
||||
</Grid>
|
||||
</DialogContentText>
|
||||
}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default DeleteKMSModal;
|
||||
157
portal-ui/src/screens/Console/KMS/ImportKey.tsx
Normal file
157
portal-ui/src/screens/Console/KMS/ImportKey.tsx
Normal file
@@ -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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
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<string>("");
|
||||
const [keyContent, setKeyContent] = useState<string>(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 (
|
||||
<Fragment>
|
||||
<Grid item xs={12}>
|
||||
<PageHeader
|
||||
label={<BackLink to={IAM_PAGES.KMS_KEYS} label={"Keys"} />}
|
||||
/>
|
||||
<PageLayout>
|
||||
<FormLayout
|
||||
title={"Import Key"}
|
||||
icon={<AddAccessRuleIcon />}
|
||||
helpbox={
|
||||
<KMSHelpBox
|
||||
helpText={"Encryption Key"}
|
||||
contents={[
|
||||
"Import a cryptographic key in the Key Management Service server connected to MINIO.",
|
||||
]}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<form
|
||||
noValidate
|
||||
autoComplete="off"
|
||||
onSubmit={(e: React.FormEvent<HTMLFormElement>) => {
|
||||
importRecord(e);
|
||||
}}
|
||||
>
|
||||
<Grid container item spacing={1}>
|
||||
<Grid item xs={12}>
|
||||
<InputBoxWrapper
|
||||
id="key-name"
|
||||
name="key-name"
|
||||
label="Key Name"
|
||||
autoFocus={true}
|
||||
value={keyName}
|
||||
error={validateKeyName(keyName)}
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setKeyName(e.target.value);
|
||||
}}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<CodeMirrorWrapper
|
||||
label={"Set key Content"}
|
||||
value={keyContent}
|
||||
onBeforeChange={(editor, data, value) => {
|
||||
setKeyContent(value);
|
||||
}}
|
||||
editorHeight={"350px"}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12} textAlign={"right"}>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "flex-end",
|
||||
marginTop: "20px",
|
||||
gap: "15px",
|
||||
}}
|
||||
>
|
||||
<Button
|
||||
id={"clear"}
|
||||
type="button"
|
||||
variant="regular"
|
||||
onClick={resetForm}
|
||||
label={"Clear"}
|
||||
/>
|
||||
|
||||
<Button
|
||||
id={"import-key"}
|
||||
type="submit"
|
||||
variant="callAction"
|
||||
color="primary"
|
||||
disabled={loading || !validSave}
|
||||
label={"Import"}
|
||||
/>
|
||||
</Box>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</form>
|
||||
</FormLayout>
|
||||
</PageLayout>
|
||||
</Grid>
|
||||
</Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
export default ImportKey;
|
||||
51
portal-ui/src/screens/Console/KMS/KMSHelpbox.tsx
Normal file
51
portal-ui/src/screens/Console/KMS/KMSHelpbox.tsx
Normal file
@@ -0,0 +1,51 @@
|
||||
import React from "react";
|
||||
|
||||
import { Box } from "@mui/material";
|
||||
import { HelpIconFilled } from "mds";
|
||||
|
||||
interface IKMSHelpBoxProps {
|
||||
helpText: string;
|
||||
contents: string[];
|
||||
}
|
||||
|
||||
const KMSHelpBox = ({ helpText, contents }: IKMSHelpBoxProps) => {
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
flex: 1,
|
||||
border: "1px solid #eaeaea",
|
||||
borderRadius: "2px",
|
||||
display: "flex",
|
||||
flexFlow: "column",
|
||||
padding: "20px",
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
fontSize: "16px",
|
||||
fontWeight: 600,
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
marginBottom: "16px",
|
||||
paddingBottom: "20px",
|
||||
|
||||
"& .min-icon": {
|
||||
height: "21px",
|
||||
width: "21px",
|
||||
marginRight: "15px",
|
||||
},
|
||||
}}
|
||||
>
|
||||
<HelpIconFilled />
|
||||
<div>{helpText}</div>
|
||||
</Box>
|
||||
<Box sx={{ fontSize: "14px", marginBottom: "15px" }}>
|
||||
{contents.map((content) => (
|
||||
<Box sx={{ paddingBottom: "20px" }}>{content}</Box>
|
||||
))}
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default KMSHelpBox;
|
||||
40
portal-ui/src/screens/Console/KMS/KMSRoutes.tsx
Normal file
40
portal-ui/src/screens/Console/KMS/KMSRoutes.tsx
Normal file
@@ -0,0 +1,40 @@
|
||||
// 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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
import React from "react";
|
||||
import { Route, Routes } from "react-router-dom";
|
||||
|
||||
import withSuspense from "../Common/Components/withSuspense";
|
||||
import NotFoundPage from "../../NotFoundPage";
|
||||
|
||||
const Status = withSuspense(React.lazy(() => import("./Status")));
|
||||
const ListKeys = withSuspense(React.lazy(() => import("./ListKeys")));
|
||||
const AddKey = withSuspense(React.lazy(() => import("./AddKey")));
|
||||
const ImportKey = withSuspense(React.lazy(() => import("./ImportKey")));
|
||||
|
||||
const KMSRoutes = () => {
|
||||
return (
|
||||
<Routes>
|
||||
<Route path={"status"} element={<Status />} />
|
||||
<Route path={"keys"} element={<ListKeys />} />
|
||||
<Route path={"add-key"} element={<AddKey />} />
|
||||
<Route path={"import-key"} element={<ImportKey />} />
|
||||
<Route path={"*"} element={<NotFoundPage />} />
|
||||
</Routes>
|
||||
);
|
||||
};
|
||||
|
||||
export default KMSRoutes;
|
||||
242
portal-ui/src/screens/Console/KMS/ListKeys.tsx
Normal file
242
portal-ui/src/screens/Console/KMS/ListKeys.tsx
Normal file
@@ -0,0 +1,242 @@
|
||||
// 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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
import { Grid, Theme } from "@mui/material";
|
||||
import { createStyles, withStyles } from "@mui/styles";
|
||||
import { AddIcon, Button, RefreshIcon, UploadIcon } from "mds";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import api from "../../../common/api";
|
||||
import {
|
||||
hasPermission,
|
||||
SecureComponent,
|
||||
} from "../../../common/SecureComponent";
|
||||
import {
|
||||
CONSOLE_UI_RESOURCE,
|
||||
IAM_PAGES,
|
||||
IAM_SCOPES,
|
||||
} from "../../../common/SecureComponent/permissions";
|
||||
import { ErrorResponseHandler } from "../../../common/types";
|
||||
import { useAppDispatch } from "../../../store";
|
||||
import { setErrorSnackMessage } from "../../../systemSlice";
|
||||
import withSuspense from "../Common/Components/withSuspense";
|
||||
import {
|
||||
containerForHeader,
|
||||
searchField,
|
||||
} from "../Common/FormComponents/common/styleLibrary";
|
||||
import PageLayout from "../Common/Layout/PageLayout";
|
||||
import PageHeader from "../Common/PageHeader/PageHeader";
|
||||
import SearchBox from "../Common/SearchBox";
|
||||
import TableWrapper from "../Common/TableWrapper/TableWrapper";
|
||||
import TooltipWrapper from "../Common/TooltipWrapper/TooltipWrapper";
|
||||
|
||||
const DeleteKMSModal = withSuspense(
|
||||
React.lazy(() => import("./DeleteKMSModal"))
|
||||
);
|
||||
|
||||
const styles = (theme: Theme) =>
|
||||
createStyles({
|
||||
...searchField,
|
||||
...containerForHeader(theme.spacing(4)),
|
||||
});
|
||||
|
||||
interface IKeysProps {
|
||||
classes: any;
|
||||
}
|
||||
|
||||
const ListKeys = ({ classes }: IKeysProps) => {
|
||||
const dispatch = useAppDispatch();
|
||||
const navigate = useNavigate();
|
||||
|
||||
const [filter, setFilter] = useState<string>("");
|
||||
const [deleteOpen, setDeleteOpen] = useState<boolean>(false);
|
||||
const [selectedKey, setSelectedKey] = useState<string>("");
|
||||
const [loading, setLoading] = useState<boolean>(false);
|
||||
const [records, setRecords] = useState<[]>([]);
|
||||
|
||||
const deleteKey = hasPermission(CONSOLE_UI_RESOURCE, [
|
||||
IAM_SCOPES.KMS_DELETE_KEY,
|
||||
]);
|
||||
|
||||
const displayKeys = hasPermission(CONSOLE_UI_RESOURCE, [
|
||||
IAM_SCOPES.KMS_LIST_KEYS,
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
fetchRecords();
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
setLoading(true);
|
||||
}, [filter]);
|
||||
|
||||
useEffect(() => {
|
||||
if (loading) {
|
||||
if (displayKeys) {
|
||||
let pattern = filter.trim() === "" ? "*" : filter.trim();
|
||||
api
|
||||
.invoke("GET", `/api/v1/kms/keys?pattern=${pattern}`)
|
||||
.then((res) => {
|
||||
setLoading(false);
|
||||
setRecords(res.results);
|
||||
})
|
||||
.catch((err: ErrorResponseHandler) => {
|
||||
setLoading(false);
|
||||
dispatch(setErrorSnackMessage(err));
|
||||
});
|
||||
} else {
|
||||
setLoading(false);
|
||||
}
|
||||
}
|
||||
}, [loading, setLoading, setRecords, dispatch, displayKeys, filter]);
|
||||
|
||||
const fetchRecords = () => {
|
||||
setLoading(true);
|
||||
};
|
||||
|
||||
const confirmDeleteKey = (key: string) => {
|
||||
setDeleteOpen(true);
|
||||
setSelectedKey(key);
|
||||
};
|
||||
|
||||
const closeDeleteModalAndRefresh = (refresh: boolean) => {
|
||||
setDeleteOpen(false);
|
||||
|
||||
if (refresh) {
|
||||
fetchRecords();
|
||||
}
|
||||
};
|
||||
|
||||
const tableActions = [
|
||||
{
|
||||
type: "delete",
|
||||
onClick: confirmDeleteKey,
|
||||
sendOnlyId: true,
|
||||
disableButtonFunction: () => !deleteKey,
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
{deleteOpen && (
|
||||
<DeleteKMSModal
|
||||
deleteOpen={deleteOpen}
|
||||
selectedItem={selectedKey}
|
||||
endpoint={"/api/v1/kms/keys/"}
|
||||
element={"Key"}
|
||||
closeDeleteModalAndRefresh={closeDeleteModalAndRefresh}
|
||||
/>
|
||||
)}
|
||||
<PageHeader label="Key Management Service Keys" />
|
||||
<PageLayout className={classes.pageContainer}>
|
||||
<Grid container spacing={1}>
|
||||
<Grid
|
||||
item
|
||||
xs={12}
|
||||
display={"flex"}
|
||||
alignItems={"center"}
|
||||
justifyContent={"flex-end"}
|
||||
sx={{
|
||||
"& button": {
|
||||
marginLeft: "8px",
|
||||
},
|
||||
}}
|
||||
>
|
||||
<SecureComponent
|
||||
scopes={[IAM_SCOPES.KMS_LIST_KEYS]}
|
||||
resource={CONSOLE_UI_RESOURCE}
|
||||
errorProps={{ disabled: true }}
|
||||
>
|
||||
<SearchBox
|
||||
onChange={setFilter}
|
||||
placeholder="Search Keys with pattern"
|
||||
value={filter}
|
||||
/>
|
||||
</SecureComponent>
|
||||
|
||||
<SecureComponent
|
||||
scopes={[IAM_SCOPES.KMS_LIST_KEYS]}
|
||||
resource={CONSOLE_UI_RESOURCE}
|
||||
errorProps={{ disabled: true }}
|
||||
>
|
||||
<TooltipWrapper tooltip={"Refresh"}>
|
||||
<Button
|
||||
id={"refresh-keys"}
|
||||
variant="regular"
|
||||
icon={<RefreshIcon />}
|
||||
onClick={() => setLoading(true)}
|
||||
/>
|
||||
</TooltipWrapper>
|
||||
</SecureComponent>
|
||||
<SecureComponent
|
||||
scopes={[IAM_SCOPES.KMS_IMPORT_KEY]}
|
||||
resource={CONSOLE_UI_RESOURCE}
|
||||
errorProps={{ disabled: true }}
|
||||
>
|
||||
<TooltipWrapper tooltip={"Import Key"}>
|
||||
<Button
|
||||
id={"import-key"}
|
||||
variant={"regular"}
|
||||
icon={<UploadIcon />}
|
||||
onClick={() => {
|
||||
navigate(IAM_PAGES.KMS_KEYS_IMPORT);
|
||||
}}
|
||||
/>
|
||||
</TooltipWrapper>
|
||||
</SecureComponent>
|
||||
<SecureComponent
|
||||
scopes={[IAM_SCOPES.KMS_CREATE_KEY]}
|
||||
resource={CONSOLE_UI_RESOURCE}
|
||||
errorProps={{ disabled: true }}
|
||||
>
|
||||
<TooltipWrapper tooltip={"Create Key"}>
|
||||
<Button
|
||||
id={"create-key"}
|
||||
label={"Create Key"}
|
||||
variant={"callAction"}
|
||||
icon={<AddIcon />}
|
||||
onClick={() => navigate(IAM_PAGES.KMS_KEYS_ADD)}
|
||||
/>
|
||||
</TooltipWrapper>
|
||||
</SecureComponent>
|
||||
</Grid>
|
||||
<Grid item xs={12} className={classes.tableBlock}>
|
||||
<SecureComponent
|
||||
scopes={[IAM_SCOPES.KMS_LIST_KEYS]}
|
||||
resource={CONSOLE_UI_RESOURCE}
|
||||
errorProps={{ disabled: true }}
|
||||
>
|
||||
<TableWrapper
|
||||
itemActions={tableActions}
|
||||
columns={[
|
||||
{ label: "Name", elementKey: "name" },
|
||||
{ label: "Created By", elementKey: "createdBy" },
|
||||
{ label: "Created At", elementKey: "createdAt" },
|
||||
]}
|
||||
isLoading={loading}
|
||||
records={records}
|
||||
entityName="Keys"
|
||||
idField="name"
|
||||
/>
|
||||
</SecureComponent>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</PageLayout>
|
||||
</React.Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
export default withStyles(styles)(ListKeys);
|
||||
435
portal-ui/src/screens/Console/KMS/Status.tsx
Normal file
435
portal-ui/src/screens/Console/KMS/Status.tsx
Normal file
@@ -0,0 +1,435 @@
|
||||
// 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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
import React, { Fragment, useEffect, useState } from "react";
|
||||
import { Theme } from "@mui/material/styles";
|
||||
import createStyles from "@mui/styles/createStyles";
|
||||
import withStyles from "@mui/styles/withStyles";
|
||||
import { Box, Grid } from "@mui/material";
|
||||
import PageHeader from "../Common/PageHeader/PageHeader";
|
||||
import PageLayout from "../Common/Layout/PageLayout";
|
||||
import api from "../../../common/api";
|
||||
|
||||
import { ErrorResponseHandler } from "../../../common/types";
|
||||
import { hasPermission } from "../../../common/SecureComponent";
|
||||
import {
|
||||
CONSOLE_UI_RESOURCE,
|
||||
IAM_SCOPES,
|
||||
} from "../../../common/SecureComponent/permissions";
|
||||
|
||||
import { setErrorSnackMessage } from "../../../systemSlice";
|
||||
import { useAppDispatch } from "../../../store";
|
||||
import Tabs from "@mui/material/Tabs";
|
||||
import Tab from "@mui/material/Tab";
|
||||
import { TabPanel } from "../../shared/tabs";
|
||||
|
||||
import LabelValuePair from "../Common/UsageBarWrapper/LabelValuePair";
|
||||
import SectionTitle from "../Common/SectionTitle";
|
||||
import LabelWithIcon from "../Buckets/BucketDetails/SummaryItems/LabelWithIcon";
|
||||
import {
|
||||
Bar,
|
||||
BarChart,
|
||||
CartesianGrid,
|
||||
Legend,
|
||||
Line,
|
||||
LineChart,
|
||||
Tooltip,
|
||||
XAxis,
|
||||
YAxis,
|
||||
} from "recharts";
|
||||
import { DisabledIcon, EnabledIcon } from "mds";
|
||||
|
||||
const styles = (theme: Theme) => createStyles({});
|
||||
|
||||
const Status = () => {
|
||||
const dispatch = useAppDispatch();
|
||||
const [curTab, setCurTab] = useState<number>(0);
|
||||
|
||||
const [status, setStatus] = useState<any | null>(null);
|
||||
const [loadingStatus, setLoadingStatus] = useState<boolean>(true);
|
||||
const [metrics, setMetrics] = useState<any | null>(null);
|
||||
const [loadingMetrics, setLoadingMetrics] = useState<boolean>(true);
|
||||
const [apis, setAPIs] = useState<any | null>(null);
|
||||
const [loadingAPIs, setLoadingAPIs] = useState<boolean>(true);
|
||||
const [version, setVersion] = useState<any | null>(null);
|
||||
const [loadingVersion, setLoadingVersion] = useState<boolean>(true);
|
||||
|
||||
const displayStatus = hasPermission(CONSOLE_UI_RESOURCE, [
|
||||
IAM_SCOPES.KMS_STATUS,
|
||||
]);
|
||||
const displayMetrics = hasPermission(CONSOLE_UI_RESOURCE, [
|
||||
IAM_SCOPES.KMS_METRICS,
|
||||
]);
|
||||
const displayAPIs = hasPermission(CONSOLE_UI_RESOURCE, [IAM_SCOPES.KMS_APIS]);
|
||||
const displayVersion = hasPermission(CONSOLE_UI_RESOURCE, [
|
||||
IAM_SCOPES.KMS_Version,
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
setLoadingStatus(true);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const loadMetrics = () => {
|
||||
if (displayMetrics) {
|
||||
api
|
||||
.invoke("GET", `/api/v1/kms/metrics`)
|
||||
.then((result: any) => {
|
||||
if (result) {
|
||||
setMetrics(result);
|
||||
}
|
||||
setLoadingMetrics(false);
|
||||
})
|
||||
.catch((err: ErrorResponseHandler) => {
|
||||
dispatch(setErrorSnackMessage(err));
|
||||
setLoadingMetrics(false);
|
||||
});
|
||||
} else {
|
||||
setLoadingMetrics(false);
|
||||
}
|
||||
};
|
||||
|
||||
const loadAPIs = () => {
|
||||
if (displayAPIs) {
|
||||
api
|
||||
.invoke("GET", `/api/v1/kms/apis`)
|
||||
.then((result: any) => {
|
||||
if (result) {
|
||||
setAPIs(result);
|
||||
}
|
||||
setLoadingAPIs(false);
|
||||
})
|
||||
.catch((err: ErrorResponseHandler) => {
|
||||
dispatch(setErrorSnackMessage(err));
|
||||
setLoadingAPIs(false);
|
||||
});
|
||||
} else {
|
||||
setLoadingAPIs(false);
|
||||
}
|
||||
};
|
||||
|
||||
const loadVersion = () => {
|
||||
if (displayVersion) {
|
||||
api
|
||||
.invoke("GET", `/api/v1/kms/version`)
|
||||
.then((result: any) => {
|
||||
if (result) {
|
||||
setVersion(result);
|
||||
}
|
||||
setLoadingVersion(false);
|
||||
})
|
||||
.catch((err: ErrorResponseHandler) => {
|
||||
dispatch(setErrorSnackMessage(err));
|
||||
setLoadingVersion(false);
|
||||
});
|
||||
} else {
|
||||
setLoadingVersion(false);
|
||||
}
|
||||
};
|
||||
|
||||
const loadStatus = () => {
|
||||
if (displayStatus) {
|
||||
api
|
||||
.invoke("GET", `/api/v1/kms/status`)
|
||||
.then((result: any) => {
|
||||
if (result) {
|
||||
setStatus(result);
|
||||
}
|
||||
setLoadingStatus(false);
|
||||
})
|
||||
.catch((err: ErrorResponseHandler) => {
|
||||
dispatch(setErrorSnackMessage(err));
|
||||
setLoadingStatus(false);
|
||||
});
|
||||
} else {
|
||||
setLoadingStatus(false);
|
||||
}
|
||||
};
|
||||
|
||||
if (loadingStatus) {
|
||||
loadStatus();
|
||||
}
|
||||
if (loadingMetrics) {
|
||||
loadMetrics();
|
||||
}
|
||||
if (loadingAPIs) {
|
||||
loadAPIs();
|
||||
}
|
||||
if (loadingVersion) {
|
||||
loadVersion();
|
||||
}
|
||||
}, [
|
||||
dispatch,
|
||||
displayStatus,
|
||||
loadingStatus,
|
||||
displayMetrics,
|
||||
loadingMetrics,
|
||||
displayAPIs,
|
||||
loadingAPIs,
|
||||
displayVersion,
|
||||
loadingVersion,
|
||||
]);
|
||||
|
||||
const statusPanel = (
|
||||
<Fragment>
|
||||
<SectionTitle>Status</SectionTitle>
|
||||
<br />
|
||||
{status && (
|
||||
<Grid container spacing={1}>
|
||||
<Grid item xs={12}>
|
||||
<Box
|
||||
sx={{
|
||||
display: "grid",
|
||||
gridTemplateColumns: { xs: "1fr", sm: "2fr 1fr" },
|
||||
gridAutoFlow: { xs: "dense", sm: "row" },
|
||||
gap: 2,
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
display: "grid",
|
||||
gridTemplateColumns: { xs: "1fr", sm: "2fr 1fr" },
|
||||
gridAutoFlow: { xs: "dense", sm: "row" },
|
||||
gap: 2,
|
||||
}}
|
||||
>
|
||||
<LabelValuePair label={"Name:"} value={status.name} />
|
||||
{version && (
|
||||
<LabelValuePair label={"Version:"} value={version.version} />
|
||||
)}
|
||||
<LabelValuePair
|
||||
label={"Default Key ID:"}
|
||||
value={status.defaultKeyID}
|
||||
/>
|
||||
<LabelValuePair
|
||||
label={"Key Management Service Endpoints:"}
|
||||
value={
|
||||
<Fragment>
|
||||
{status.endpoints.map((e: any, i: number) => (
|
||||
<LabelWithIcon
|
||||
key={i}
|
||||
icon={
|
||||
e.status === "online" ? (
|
||||
<EnabledIcon />
|
||||
) : (
|
||||
<DisabledIcon />
|
||||
)
|
||||
}
|
||||
label={e.url}
|
||||
/>
|
||||
))}
|
||||
</Fragment>
|
||||
}
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
</Grid>
|
||||
</Grid>
|
||||
)}
|
||||
</Fragment>
|
||||
);
|
||||
|
||||
const apisPanel = (
|
||||
<Fragment>
|
||||
<SectionTitle>Supported API endpoints</SectionTitle>
|
||||
<br />
|
||||
{apis && (
|
||||
<Grid container spacing={1}>
|
||||
<Grid item xs={12}>
|
||||
<LabelValuePair
|
||||
label={""}
|
||||
value={
|
||||
<Box
|
||||
sx={{
|
||||
display: "grid",
|
||||
gridTemplateColumns: { xs: "1fr", sm: "2fr 1fr" },
|
||||
gridAutoFlow: { xs: "dense", sm: "row" },
|
||||
gap: 2,
|
||||
}}
|
||||
>
|
||||
{apis.results.map((e: any, i: number) => (
|
||||
<LabelWithIcon
|
||||
key={i}
|
||||
icon={<EnabledIcon />}
|
||||
label={`${e.path} - ${e.method}`}
|
||||
/>
|
||||
))}
|
||||
</Box>
|
||||
}
|
||||
/>
|
||||
</Grid>
|
||||
</Grid>
|
||||
)}
|
||||
</Fragment>
|
||||
);
|
||||
|
||||
const getAPIRequestsData = () => {
|
||||
return [
|
||||
{ label: "Success", success: metrics.requestOK },
|
||||
{ label: "Failures", failures: metrics.requestFail },
|
||||
{ label: "Errors", errors: metrics.requestErr },
|
||||
{ label: "Active", active: metrics.requestActive },
|
||||
];
|
||||
};
|
||||
|
||||
const getEventsData = () => {
|
||||
return [
|
||||
{ label: "Audit", audit: metrics.auditEvents },
|
||||
{ label: "Errors", errors: metrics.errorEvents },
|
||||
];
|
||||
};
|
||||
|
||||
const getHistogramData = () => {
|
||||
return metrics.latencyHistogram.map((h: any) => {
|
||||
return {
|
||||
...h,
|
||||
duration: `${h.duration / 1000000}ms`,
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
const metricsPanel = (
|
||||
<Fragment>
|
||||
{metrics && (
|
||||
<Fragment>
|
||||
<h3>API Requests</h3>
|
||||
<BarChart width={730} height={250} data={getAPIRequestsData()}>
|
||||
<CartesianGrid strokeDasharray="3 3" />
|
||||
<XAxis dataKey="label" />
|
||||
<YAxis />
|
||||
<Tooltip />
|
||||
<Legend />
|
||||
<Bar dataKey="success" fill="green" />
|
||||
<Bar dataKey="failures" fill="red" />
|
||||
<Bar dataKey="errors" fill="black" />
|
||||
<Bar dataKey="active" fill="#8884d8" />
|
||||
</BarChart>
|
||||
|
||||
<h3>Events</h3>
|
||||
<BarChart width={730} height={250} data={getEventsData()}>
|
||||
<CartesianGrid strokeDasharray="3 3" />
|
||||
<XAxis dataKey="label" />
|
||||
<YAxis />
|
||||
<Tooltip />
|
||||
<Legend />
|
||||
<Bar dataKey="audit" fill="green" />
|
||||
<Bar dataKey="errors" fill="black" />
|
||||
</BarChart>
|
||||
<h3>Latency Histogram</h3>
|
||||
{metrics.latencyHistogram && (
|
||||
<LineChart
|
||||
width={730}
|
||||
height={250}
|
||||
data={getHistogramData()}
|
||||
margin={{ top: 5, right: 30, left: 20, bottom: 5 }}
|
||||
>
|
||||
<CartesianGrid strokeDasharray="3 3" />
|
||||
<XAxis dataKey="duration" />
|
||||
<YAxis />
|
||||
<Tooltip />
|
||||
<Legend />
|
||||
<Line
|
||||
type="monotone"
|
||||
dataKey="total"
|
||||
stroke="#8884d8"
|
||||
name={"Requests that took T ms or less"}
|
||||
/>
|
||||
</LineChart>
|
||||
)}
|
||||
</Fragment>
|
||||
)}
|
||||
</Fragment>
|
||||
);
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<PageHeader label="Key Management Service" actions={<React.Fragment />} />
|
||||
|
||||
<PageLayout>
|
||||
<Tabs
|
||||
value={curTab}
|
||||
onChange={(e: React.ChangeEvent<{}>, newValue: number) => {
|
||||
setCurTab(newValue);
|
||||
}}
|
||||
indicatorColor="primary"
|
||||
textColor="primary"
|
||||
aria-label="cluster-tabs"
|
||||
variant="scrollable"
|
||||
scrollButtons="auto"
|
||||
>
|
||||
<Tab
|
||||
label="Status"
|
||||
id="simple-tab-0"
|
||||
aria-controls="simple-tabpanel-0"
|
||||
/>
|
||||
<Tab
|
||||
label="APIs"
|
||||
id="simple-tab-1"
|
||||
aria-controls="simple-tabpanel-1"
|
||||
/>
|
||||
<Tab
|
||||
label="Metrics"
|
||||
id="simple-tab-2"
|
||||
aria-controls="simple-tabpanel-2"
|
||||
onClick={() => {}}
|
||||
/>
|
||||
</Tabs>
|
||||
|
||||
<TabPanel index={0} value={curTab}>
|
||||
<Box
|
||||
sx={{
|
||||
border: "1px solid #eaeaea",
|
||||
borderRadius: "2px",
|
||||
display: "flex",
|
||||
flexFlow: "column",
|
||||
padding: "43px",
|
||||
}}
|
||||
>
|
||||
{statusPanel}
|
||||
</Box>
|
||||
</TabPanel>
|
||||
<TabPanel index={1} value={curTab}>
|
||||
<Box
|
||||
sx={{
|
||||
border: "1px solid #eaeaea",
|
||||
borderRadius: "2px",
|
||||
display: "flex",
|
||||
flexFlow: "column",
|
||||
padding: "43px",
|
||||
}}
|
||||
>
|
||||
{apisPanel}
|
||||
</Box>
|
||||
</TabPanel>
|
||||
<TabPanel index={2} value={curTab}>
|
||||
<Box
|
||||
sx={{
|
||||
border: "1px solid #eaeaea",
|
||||
borderRadius: "2px",
|
||||
display: "flex",
|
||||
flexFlow: "column",
|
||||
padding: "43px",
|
||||
}}
|
||||
>
|
||||
{metricsPanel}
|
||||
</Box>
|
||||
</TabPanel>
|
||||
</PageLayout>
|
||||
</Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
export default withStyles(styles)(Status);
|
||||
@@ -58,6 +58,8 @@ import {
|
||||
import { hasPermission } from "../../common/SecureComponent";
|
||||
import React from "react";
|
||||
import LicenseBadge from "./Menu/LicenseBadge";
|
||||
import EncryptionIcon from "../../icons/SidebarMenus/EncryptionIcon";
|
||||
import EncryptionStatusIcon from "../../icons/SidebarMenus/EncryptionStatusIcon";
|
||||
import { LockOpen, Login } from "@mui/icons-material";
|
||||
|
||||
export const validRoutes = (
|
||||
@@ -66,6 +68,7 @@ export const validRoutes = (
|
||||
directPVMode: boolean
|
||||
) => {
|
||||
const ldapIsEnabled = (features && features.includes("ldap-idp")) || false;
|
||||
const kmsIsEnabled = (features && features.includes("kms")) || false;
|
||||
let consoleMenus: IMenuItem[] = [
|
||||
{
|
||||
group: "User",
|
||||
@@ -216,6 +219,14 @@ export const validRoutes = (
|
||||
icon: DrivesMenuIcon,
|
||||
component: NavLink,
|
||||
},
|
||||
{
|
||||
name: "Encryption",
|
||||
id: "monitorEncryption",
|
||||
to: IAM_PAGES.KMS_STATUS,
|
||||
icon: EncryptionStatusIcon,
|
||||
component: NavLink,
|
||||
fsHidden: !kmsIsEnabled,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
@@ -242,6 +253,15 @@ export const validRoutes = (
|
||||
icon: RecoverIcon,
|
||||
id: "sitereplication",
|
||||
},
|
||||
{
|
||||
group: "Administrator",
|
||||
component: NavLink,
|
||||
to: IAM_PAGES.KMS_KEYS,
|
||||
name: "Encryption",
|
||||
icon: EncryptionIcon,
|
||||
id: "encryption",
|
||||
fsHidden: !kmsIsEnabled,
|
||||
},
|
||||
{
|
||||
group: "Administrator",
|
||||
component: NavLink,
|
||||
|
||||
@@ -20,6 +20,7 @@ package restapi
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"sort"
|
||||
|
||||
"github.com/go-openapi/runtime/middleware"
|
||||
"github.com/minio/console/models"
|
||||
@@ -120,7 +121,7 @@ func kmsMetrics(ctx context.Context, minioClient MinioAdmin) (*models.KmsMetrics
|
||||
RequestActive: &metrics.RequestActive,
|
||||
AuditEvents: &metrics.AuditEvents,
|
||||
ErrorEvents: &metrics.ErrorEvents,
|
||||
LatencyHistogram: nil,
|
||||
LatencyHistogram: parseHistogram(metrics.LatencyHistogram),
|
||||
Uptime: &metrics.UpTime,
|
||||
Cpus: &metrics.CPUs,
|
||||
UsableCPUs: &metrics.UsableCPUs,
|
||||
@@ -131,6 +132,17 @@ func kmsMetrics(ctx context.Context, minioClient MinioAdmin) (*models.KmsMetrics
|
||||
}, nil
|
||||
}
|
||||
|
||||
func parseHistogram(histogram map[int64]int64) (records []*models.KmsLatencyHistogram) {
|
||||
for duration, total := range histogram {
|
||||
records = append(records, &models.KmsLatencyHistogram{Duration: duration, Total: total})
|
||||
}
|
||||
cp := func(i, j int) bool {
|
||||
return records[i].Duration < records[j].Duration
|
||||
}
|
||||
sort.Slice(records, cp)
|
||||
return records
|
||||
}
|
||||
|
||||
func GetKMSAPIsResponse(session *models.Principal, params kmsAPI.KMSAPIsParams) (*models.KmsAPIsResponse, *models.Error) {
|
||||
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
|
||||
defer cancel()
|
||||
|
||||
@@ -6249,6 +6249,9 @@ func init() {
|
||||
"properties": {
|
||||
"duration": {
|
||||
"type": "integer"
|
||||
},
|
||||
"total": {
|
||||
"type": "integer"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -6319,7 +6322,10 @@ func init() {
|
||||
"type": "integer"
|
||||
},
|
||||
"latencyHistogram": {
|
||||
"$ref": "#/definitions/kmsLatencyHistogram"
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/kmsLatencyHistogram"
|
||||
}
|
||||
},
|
||||
"requestActive": {
|
||||
"type": "integer"
|
||||
@@ -14817,6 +14823,9 @@ func init() {
|
||||
"properties": {
|
||||
"duration": {
|
||||
"type": "integer"
|
||||
},
|
||||
"total": {
|
||||
"type": "integer"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -14887,7 +14896,10 @@ func init() {
|
||||
"type": "integer"
|
||||
},
|
||||
"latencyHistogram": {
|
||||
"$ref": "#/definitions/kmsLatencyHistogram"
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/kmsLatencyHistogram"
|
||||
}
|
||||
},
|
||||
"requestActive": {
|
||||
"type": "integer"
|
||||
|
||||
@@ -5677,7 +5677,9 @@ definitions:
|
||||
errorEvents:
|
||||
type: integer
|
||||
latencyHistogram:
|
||||
$ref: "#/definitions/kmsLatencyHistogram"
|
||||
type: array
|
||||
items:
|
||||
$ref: "#/definitions/kmsLatencyHistogram"
|
||||
uptime:
|
||||
type: integer
|
||||
cpus:
|
||||
@@ -5697,6 +5699,9 @@ definitions:
|
||||
properties:
|
||||
duration:
|
||||
type: integer
|
||||
total:
|
||||
type: integer
|
||||
|
||||
kmsAPIsResponse:
|
||||
type: object
|
||||
properties:
|
||||
|
||||
Reference in New Issue
Block a user