Added DirectPV mode to Operator console (#2203)
Signed-off-by: Benjamin Perez <benjamin@bexsoft.net>
This commit is contained in:
4
CREDITS
4
CREDITS
@@ -7137,8 +7137,8 @@ SOFTWARE.
|
||||
|
||||
================================================================
|
||||
|
||||
github.com/minio/direct-csi
|
||||
https://github.com/minio/direct-csi
|
||||
github.com/minio/directpv
|
||||
https://github.com/minio/directpv
|
||||
----------------------------------------------------------------
|
||||
GNU AFFERO GENERAL PUBLIC LICENSE
|
||||
Version 3, 19 November 2007
|
||||
|
||||
@@ -37,6 +37,9 @@ import (
|
||||
// swagger:model loginDetails
|
||||
type LoginDetails struct {
|
||||
|
||||
// is direct p v
|
||||
IsDirectPV bool `json:"isDirectPV,omitempty"`
|
||||
|
||||
// login strategy
|
||||
// Enum: [form redirect service-account redirect-service-account]
|
||||
LoginStrategy string `json:"loginStrategy,omitempty"`
|
||||
|
||||
@@ -37,6 +37,9 @@ import (
|
||||
// swagger:model operatorSessionResponse
|
||||
type OperatorSessionResponse struct {
|
||||
|
||||
// direct p v
|
||||
DirectPV bool `json:"directPV,omitempty"`
|
||||
|
||||
// features
|
||||
Features []string `json:"features"`
|
||||
|
||||
|
||||
@@ -69,3 +69,10 @@ func getK8sSAToken() string {
|
||||
func getMarketplace() string {
|
||||
return env.Get(ConsoleMarketplace, "")
|
||||
}
|
||||
|
||||
// Get DirectPVMode
|
||||
func getDirectPVEnabled() bool {
|
||||
currentMode := env.Get(DirectPVMode, "off")
|
||||
|
||||
return currentMode == "on"
|
||||
}
|
||||
|
||||
@@ -25,6 +25,9 @@ const (
|
||||
prometheusPath = "prometheus.io/path"
|
||||
prometheusPort = "prometheus.io/port"
|
||||
prometheusScrape = "prometheus.io/scrape"
|
||||
|
||||
// Constants for DirectPV
|
||||
DirectPVMode = "DIRECTPV_MODE"
|
||||
)
|
||||
|
||||
// Image versions
|
||||
|
||||
@@ -3415,6 +3415,9 @@ func init() {
|
||||
"loginDetails": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"isDirectPV": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"loginStrategy": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
@@ -3648,6 +3651,9 @@ func init() {
|
||||
"operatorSessionResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"directPV": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"features": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
@@ -9174,6 +9180,9 @@ func init() {
|
||||
"loginDetails": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"isDirectPV": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"loginStrategy": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
@@ -9363,6 +9372,9 @@ func init() {
|
||||
"operatorSessionResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"directPV": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"features": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
|
||||
@@ -118,6 +118,7 @@ func getLoginDetailsResponse(params authApi.LoginDetailParams) (*models.LoginDet
|
||||
loginDetails := &models.LoginDetails{
|
||||
LoginStrategy: loginStrategy,
|
||||
Redirect: redirectURL,
|
||||
IsDirectPV: getDirectPVEnabled(),
|
||||
}
|
||||
return loginDetails, nil
|
||||
}
|
||||
|
||||
@@ -52,6 +52,7 @@ func getSessionResponse(session *models.Principal, params authApi.SessionCheckPa
|
||||
Operator: true,
|
||||
Permissions: map[string][]string{},
|
||||
Features: getListOfOperatorFeatures(),
|
||||
DirectPV: getDirectPVEnabled(),
|
||||
}
|
||||
return sessionResp, nil
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@ import { ErrorResponseHandler } from "./common/types";
|
||||
import { ReplicationSite } from "./screens/Console/Configurations/SiteReplication/SiteReplication";
|
||||
import { useSelector } from "react-redux";
|
||||
import {
|
||||
directPVMode,
|
||||
globalSetDistributedSetup,
|
||||
operatorMode,
|
||||
selOpMode,
|
||||
@@ -63,6 +64,7 @@ const ProtectedRoute = ({ Component }: ProtectedRouteProps) => {
|
||||
// check for tenants presence, that indicates we are in operator mode
|
||||
if (res.operator) {
|
||||
dispatch(operatorMode(true));
|
||||
dispatch(directPVMode(!!res.directPV));
|
||||
document.title = "MinIO Operator";
|
||||
}
|
||||
})
|
||||
|
||||
@@ -212,6 +212,11 @@ export const IAM_PAGES = {
|
||||
"/namespaces/:tenantNamespace/tenants/:tenantName/events",
|
||||
NAMESPACE_TENANT_CSR: "/namespaces/:tenantNamespace/tenants/:tenantName/csr",
|
||||
OPERATOR_MARKETPLACE: "/marketplace",
|
||||
|
||||
/* DirectPV */
|
||||
DIRECTPV_STORAGE: "/storage",
|
||||
DIRECTPV_DRIVES: "/drives",
|
||||
DIRECTPV_VOLUMES: "/volumes",
|
||||
};
|
||||
|
||||
// roles
|
||||
|
||||
56
portal-ui/src/icons/DirectPVLogo.tsx
Normal file
56
portal-ui/src/icons/DirectPVLogo.tsx
Normal file
@@ -0,0 +1,56 @@
|
||||
// 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, { SVGProps } from "react";
|
||||
|
||||
const DirectPVLogo = (props: SVGProps<SVGSVGElement>) => {
|
||||
return (
|
||||
<svg
|
||||
version="1.1"
|
||||
id="Layer_1"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
{...props}
|
||||
className={`min-icon`}
|
||||
fill={"currentcolor"}
|
||||
viewBox="0 0 416.5 121.19"
|
||||
>
|
||||
<rect id="Rectangle_2" x="60.81" y=".48" width="10.5" height="30.9" />
|
||||
<path
|
||||
id="Path_1"
|
||||
d="M48.11,.98L26.81,13.98c-.3,.2-.7,.2-1,0L4.51,.98c-.5-.3-1-.4-1.5-.4h0C1.41,.58,.11,1.88,.11,3.48V31.48H10.61v-13.4c0-.6,.5-1,1.1-1,.2,0,.4,.1,.5,.2l11.9,7.3c1.2,.7,2.6,.7,3.8,0l12.6-7.4c.5-.3,1.1-.1,1.4,.4,.1,.2,.1,.3,.1,.5v13.4h10.5V3.48c0-1.6-1.3-2.9-2.9-2.9h0c-.5-.1-1,.1-1.5,.4Z"
|
||||
/>
|
||||
<path
|
||||
id="Path_2"
|
||||
d="M123.61,.48h-10.6V14.58c0,.6-.5,1-1,1-.2,0-.3,0-.5-.1L83.91,.88c-.4-.2-.9-.3-1.4-.3h0c-1.6,0-2.9,1.3-2.9,2.9V31.48h10.5v-14.1c0-.6,.5-1,1.1-1,.2,0,.3,0,.5,.1l27.6,14.7c.4,.2,.9,.3,1.4,.3h0c1.6,0,2.9-1.3,2.9-2.9V.48Z"
|
||||
/>
|
||||
<path id="Path_3" d="M131.81,31.5V.5h4.8V31.4l-4.8,.1Z" />
|
||||
<path
|
||||
id="Path_4"
|
||||
d="M165.01,32c-13,0-22.2-6.2-22.2-16S152.11,0,165.01,0s22.2,6.2,22.2,16-9.1,16-22.2,16Zm0-27.9c-9.6,0-17.1,4.2-17.1,11.9s7.4,11.9,17.1,11.9,17.1-4.2,17.1-11.9-7.5-11.9-17.1-11.9Z"
|
||||
/>
|
||||
<path d="M0,50.99H26c11.3,0,20.3,3.1,26.9,9.4,6.6,6.3,10,14.6,10,25.2s-3.3,18.9-10,25.2c-6.6,6.3-15.6,9.4-26.9,9.4H0V50.99Zm26,7.5H7.9v54.2H26c9.2,0,16.2-2.4,21.1-7.3s7.4-11.5,7.4-19.8-2.5-14.9-7.4-19.8c-4.8-4.8-11.9-7.3-21.1-7.3Z" />
|
||||
<path d="M81.3,51.59c1.4,0,2.7,.5,3.7,1.5s1.5,2.2,1.5,3.7-.5,2.7-1.5,3.7-2.2,1.5-3.7,1.5-2.7-.5-3.7-1.5-1.5-2.2-1.5-3.7,.5-2.7,1.5-3.7c1-1,2.2-1.5,3.7-1.5Zm3.7,21.8v46.8h-7.4v-46.8h7.4Z" />
|
||||
<path d="M123.4,72.49c3.4,0,6.3,.5,8.6,1.6l-1.8,7.3c-2.3-1.3-5-1.9-8.1-1.9-3.7,0-6.7,1.3-9,4s-3.5,6.2-3.5,10.6v26.2h-7.4v-46.9h7.3v6.6c1.6-2.4,3.6-4.3,6-5.6s5.1-1.9,7.9-1.9Z" />
|
||||
<path d="M160.9,72.49c6.4,0,11.6,2.3,15.7,6.8,4.1,4.5,6.2,10.3,6.2,17.4,0,1,0,1.9-.1,2.8h-37.5c.5,4.9,2.3,8.6,5.3,11.3,3.1,2.7,6.8,4,11.2,4,5.6,0,10.5-2,14.8-5.9l4,5c-5.1,4.9-11.5,7.3-19.2,7.3-6.9,0-12.6-2.2-17-6.7s-6.6-10.3-6.6-17.6,2.2-12.9,6.6-17.5c4.4-4.7,9.9-6.9,16.6-6.9Zm-.1,6.5c-4.2,0-7.7,1.3-10.4,4-2.7,2.6-4.4,6.1-5,10.4h30c-.5-4.4-2-7.9-4.7-10.5-2.6-2.6-6-3.9-9.9-3.9Z" />
|
||||
<path d="M217.2,72.49c3.5,0,6.7,.6,9.7,1.9s5.5,3.1,7.6,5.5l-4.7,5c-3.7-3.6-8-5.4-12.8-5.4s-8.6,1.6-11.8,4.9c-3.1,3.3-4.7,7.4-4.7,12.4s1.6,9.2,4.7,12.5c3.1,3.3,7.1,4.9,11.8,4.9s9.2-1.8,12.9-5.5l4.6,5c-2.1,2.4-4.7,4.2-7.7,5.5s-6.2,1.9-9.7,1.9c-7.1,0-12.9-2.3-17.4-6.9s-6.8-10.4-6.8-17.4,2.3-12.8,6.8-17.4c4.6-4.6,10.4-6.9,17.5-6.9Z" />
|
||||
<path d="M279.6,73.39v6.7h-20.2v23.6c0,3.5,.8,6.2,2.5,7.9,1.7,1.7,3.9,2.6,6.8,2.6,3.5,0,6.8-1.1,9.8-3.3l3.5,5.6c-4.1,3.1-8.7,4.6-13.9,4.6s-9.1-1.4-11.9-4.3-4.2-7.2-4.2-13v-23.7h-10.2v-6.7h10.2v-14.9h7.4v14.9h20.2Z" />
|
||||
<path d="M320,92.39h-18.2v27.8h-8V50.99h26.1c7.7,0,13.7,1.8,18,5.5s6.4,8.7,6.4,15.2-2.1,11.5-6.4,15.2c-4.2,3.6-10.2,5.5-17.9,5.5Zm-.2-33.9h-17.9v26.4h17.9c5.4,0,9.4-1.1,12.2-3.3,2.8-2.2,4.2-5.5,4.2-9.9s-1.4-7.7-4.2-9.9-6.9-3.3-12.2-3.3Z" />
|
||||
<path d="M416.5,50.99l-27.7,69.1h-9.8l-27.7-69.1h8.4l24.3,61.2,24.3-61.2h8.2Z" />
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
|
||||
export default DirectPVLogo;
|
||||
@@ -39,7 +39,7 @@ import { useSelector } from "react-redux";
|
||||
import useApi from "./Common/Hooks/useApi";
|
||||
import { Bucket, BucketList } from "./Buckets/types";
|
||||
import { selFeatures } from "./consoleSlice";
|
||||
import { selOpMode } from "../../systemSlice";
|
||||
import { selOpMode, selDirectPVMode } from "../../systemSlice";
|
||||
|
||||
const useStyles = makeStyles((theme: Theme) => ({
|
||||
resultItem: {
|
||||
@@ -128,6 +128,7 @@ const KBarStateChangeMonitor = ({
|
||||
|
||||
const CommandBar = () => {
|
||||
const operatorMode = useSelector(selOpMode);
|
||||
const directPVMode = useSelector(selDirectPVMode);
|
||||
const features = useSelector(selFeatures);
|
||||
const navigate = useNavigate();
|
||||
|
||||
@@ -148,6 +149,7 @@ const CommandBar = () => {
|
||||
const initialActions: Action[] = routesAsKbarActions(
|
||||
features,
|
||||
operatorMode,
|
||||
directPVMode,
|
||||
buckets,
|
||||
navigate
|
||||
);
|
||||
|
||||
@@ -24,12 +24,13 @@ import IconButton from "@mui/material/IconButton";
|
||||
import { AppState, useAppDispatch } from "../../../../store";
|
||||
import OperatorLogo from "../../../../icons/OperatorLogo";
|
||||
import ConsoleLogo from "../../../../icons/ConsoleLogo";
|
||||
import DirectPVLogo from "../../../../icons/DirectPVLogo";
|
||||
|
||||
import { CircleIcon, ObjectManagerIcon } from "../../../../icons";
|
||||
import { Box } from "@mui/material";
|
||||
import { toggleList } from "../../ObjectBrowser/objectBrowserSlice";
|
||||
import { selFeatures } from "../../consoleSlice";
|
||||
import { selOpMode } from "../../../../systemSlice";
|
||||
import { selDirectPVMode, selOpMode } from "../../../../systemSlice";
|
||||
|
||||
const styles = (theme: Theme) =>
|
||||
createStyles({
|
||||
@@ -107,6 +108,7 @@ const PageHeader = ({
|
||||
(state: AppState) => state.system.sidebarOpen
|
||||
);
|
||||
const operatorMode = useSelector(selOpMode);
|
||||
const directPVMode = useSelector(selDirectPVMode);
|
||||
const managerObjects = useSelector(
|
||||
(state: AppState) => state.objectBrowser.objectManager.objectsToManage
|
||||
);
|
||||
@@ -151,7 +153,13 @@ const PageHeader = ({
|
||||
>
|
||||
{!sidebarOpen && (
|
||||
<div className={classes.logo}>
|
||||
{operatorMode ? <OperatorLogo /> : <ConsoleLogo />}
|
||||
{!operatorMode && !directPVMode ? (
|
||||
<ConsoleLogo />
|
||||
) : (
|
||||
<Fragment>
|
||||
{directPVMode ? <DirectPVLogo /> : <OperatorLogo />}
|
||||
</Fragment>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
<Box
|
||||
|
||||
@@ -52,6 +52,7 @@ import EditPool from "./Tenants/TenantDetails/Pools/EditPool/EditPool";
|
||||
import ComponentsScreen from "./Common/ComponentsScreen";
|
||||
import {
|
||||
menuOpen,
|
||||
selDirectPVMode,
|
||||
selDistSet,
|
||||
selOpMode,
|
||||
serverIsLoading,
|
||||
@@ -145,6 +146,12 @@ const AddReplicationSites = React.lazy(
|
||||
() => import("./Configurations/SiteReplication/AddReplicationSites")
|
||||
);
|
||||
|
||||
const StoragePVCs = React.lazy(() => import("./Storage/StoragePVCs"));
|
||||
|
||||
const DirectPVDrives = React.lazy(() => import("./DirectPV/DirectPVDrives"));
|
||||
|
||||
const DirectPVVolumes = React.lazy(() => import("./DirectPV/DirectPVVolumes"));
|
||||
|
||||
const styles = (theme: Theme) =>
|
||||
createStyles({
|
||||
root: {
|
||||
@@ -187,6 +194,7 @@ const Console = ({ classes }: IConsoleProps) => {
|
||||
const features = useSelector(selFeatures);
|
||||
const distributedSetup = useSelector(selDistSet);
|
||||
const operatorMode = useSelector(selOpMode);
|
||||
const directPVMode = useSelector(selDirectPVMode);
|
||||
const snackBarMessage = useSelector(
|
||||
(state: AppState) => state.system.snackBar
|
||||
);
|
||||
@@ -461,9 +469,38 @@ const Console = ({ classes }: IConsoleProps) => {
|
||||
},
|
||||
];
|
||||
|
||||
const allowedRoutes = (
|
||||
operatorMode ? operatorConsoleRoutes : consoleAdminRoutes
|
||||
).filter((route: any) =>
|
||||
const directPVRoutes: IRouteRule[] = [
|
||||
{
|
||||
component: StoragePVCs,
|
||||
path: IAM_PAGES.DIRECTPV_STORAGE,
|
||||
forceDisplay: true,
|
||||
},
|
||||
{
|
||||
component: DirectPVDrives,
|
||||
path: IAM_PAGES.DIRECTPV_DRIVES,
|
||||
forceDisplay: true,
|
||||
},
|
||||
{
|
||||
component: DirectPVVolumes,
|
||||
path: IAM_PAGES.DIRECTPV_VOLUMES,
|
||||
forceDisplay: true,
|
||||
},
|
||||
{
|
||||
component: License,
|
||||
path: IAM_PAGES.LICENSE,
|
||||
forceDisplay: true,
|
||||
},
|
||||
];
|
||||
|
||||
let routes = consoleAdminRoutes;
|
||||
|
||||
if (directPVMode) {
|
||||
routes = directPVRoutes;
|
||||
} else if (operatorMode) {
|
||||
routes = operatorConsoleRoutes;
|
||||
}
|
||||
|
||||
const allowedRoutes = routes.filter((route: any) =>
|
||||
obOnly
|
||||
? route.path.includes("buckets")
|
||||
: (route.forceDisplay ||
|
||||
|
||||
347
portal-ui/src/screens/Console/DirectPV/DirectPVDrives.tsx
Normal file
347
portal-ui/src/screens/Console/DirectPV/DirectPVDrives.tsx
Normal file
@@ -0,0 +1,347 @@
|
||||
// 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 { Grid, InputAdornment, TextField } from "@mui/material";
|
||||
import get from "lodash/get";
|
||||
import GroupIcon from "@mui/icons-material/Group";
|
||||
import { AddIcon, StorageIcon } from "../../../icons";
|
||||
import {
|
||||
actionsTray,
|
||||
containerForHeader,
|
||||
searchField,
|
||||
} from "../Common/FormComponents/common/styleLibrary";
|
||||
import {
|
||||
IDirectPVDrives,
|
||||
IDirectPVFormatResItem,
|
||||
IDrivesResponse,
|
||||
} from "./types";
|
||||
import { niceBytes } from "../../../common/utils";
|
||||
import { ErrorResponseHandler } from "../../../common/types";
|
||||
import api from "../../../common/api";
|
||||
import TableWrapper from "../Common/TableWrapper/TableWrapper";
|
||||
import RefreshIcon from "../../../icons/RefreshIcon";
|
||||
import SearchIcon from "../../../icons/SearchIcon";
|
||||
import BoxIconButton from "../Common/BoxIconButton/BoxIconButton";
|
||||
import HelpBox from "../../../common/HelpBox";
|
||||
import BoxButton from "../Common/BoxButton/BoxButton";
|
||||
|
||||
import withSuspense from "../Common/Components/withSuspense";
|
||||
import PageHeader from "../Common/PageHeader/PageHeader";
|
||||
import PageLayout from "../Common/Layout/PageLayout";
|
||||
|
||||
const FormatDrives = withSuspense(React.lazy(() => import("./FormatDrives")));
|
||||
const FormatErrorsResult = withSuspense(
|
||||
React.lazy(() => import("./FormatErrorsResult"))
|
||||
);
|
||||
|
||||
interface IDirectPVMain {
|
||||
classes: any;
|
||||
}
|
||||
|
||||
const styles = (theme: Theme) =>
|
||||
createStyles({
|
||||
tableWrapper: {
|
||||
height: "calc(100vh - 275px)",
|
||||
},
|
||||
linkItem: {
|
||||
display: "default",
|
||||
color: theme.palette.info.main,
|
||||
textDecoration: "none",
|
||||
"&:hover": {
|
||||
textDecoration: "underline",
|
||||
color: "#000",
|
||||
},
|
||||
},
|
||||
...actionsTray,
|
||||
...searchField,
|
||||
...containerForHeader(theme.spacing(4)),
|
||||
});
|
||||
|
||||
const DirectPVMain = ({ classes }: IDirectPVMain) => {
|
||||
const [records, setRecords] = useState<IDirectPVDrives[]>([]);
|
||||
const [filter, setFilter] = useState("");
|
||||
const [checkedDrives, setCheckedDrives] = useState<string[]>([]);
|
||||
const [loading, setLoading] = useState<boolean>(true);
|
||||
const [formatOpen, setFormatOpen] = useState<boolean>(false);
|
||||
const [formatAll, setFormatAll] = useState<boolean>(false);
|
||||
const [formatErrorsResult, setFormatErrorsResult] = useState<
|
||||
IDirectPVFormatResItem[]
|
||||
>([]);
|
||||
const [formatErrorsOpen, setFormatErrorsOpen] = useState<boolean>(false);
|
||||
const [drivesToFormat, setDrivesToFormat] = useState<string[]>([]);
|
||||
const [notAvailable, setNotAvailable] = useState<boolean>(true);
|
||||
|
||||
useEffect(() => {
|
||||
if (loading) {
|
||||
api
|
||||
.invoke("GET", "/api/v1/directpv/drives")
|
||||
.then((res: IDrivesResponse) => {
|
||||
let drives: IDirectPVDrives[] = get(res, "drives", []);
|
||||
|
||||
if (!drives) {
|
||||
drives = [];
|
||||
}
|
||||
|
||||
drives = drives.map((item) => {
|
||||
const newItem = { ...item };
|
||||
newItem.joinName = `${newItem.node}:${newItem.drive}`;
|
||||
|
||||
return newItem;
|
||||
});
|
||||
|
||||
drives.sort((d1, d2) => {
|
||||
if (d1.drive > d2.drive) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (d1.drive < d2.drive) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
});
|
||||
|
||||
setRecords(drives);
|
||||
setLoading(false);
|
||||
setNotAvailable(false);
|
||||
})
|
||||
.catch((err: ErrorResponseHandler) => {
|
||||
setLoading(false);
|
||||
setNotAvailable(true);
|
||||
});
|
||||
}
|
||||
}, [loading, notAvailable]);
|
||||
|
||||
const formatAllDrives = () => {
|
||||
const allDrives = records.map((item) => {
|
||||
return `${item.node}:${item.drive}`;
|
||||
});
|
||||
setFormatAll(true);
|
||||
setDrivesToFormat(allDrives);
|
||||
setFormatOpen(true);
|
||||
};
|
||||
|
||||
const formatSingleUnit = (driveID: string) => {
|
||||
const selectedUnit = [driveID];
|
||||
setDrivesToFormat(selectedUnit);
|
||||
setFormatAll(false);
|
||||
setFormatOpen(true);
|
||||
};
|
||||
|
||||
const formatSelectedDrives = () => {
|
||||
if (checkedDrives.length > 0) {
|
||||
setDrivesToFormat(checkedDrives);
|
||||
setFormatAll(false);
|
||||
setFormatOpen(true);
|
||||
}
|
||||
};
|
||||
|
||||
const selectionChanged = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const targetD = e.target;
|
||||
const value = targetD.value;
|
||||
const checked = targetD.checked;
|
||||
|
||||
let elements: string[] = [...checkedDrives]; // We clone the checkedDrives array
|
||||
|
||||
if (checked) {
|
||||
// If the user has checked this field we need to push this to checkedDrivesList
|
||||
elements.push(value);
|
||||
} else {
|
||||
// User has unchecked this field, we need to remove it from the list
|
||||
elements = elements.filter((element) => element !== value);
|
||||
}
|
||||
|
||||
setCheckedDrives(elements);
|
||||
|
||||
return elements;
|
||||
};
|
||||
|
||||
const closeFormatModal = (
|
||||
refresh: boolean,
|
||||
errorsList: IDirectPVFormatResItem[]
|
||||
) => {
|
||||
setFormatOpen(false);
|
||||
if (refresh) {
|
||||
// Errors are present, we trigger the modal box to show these changes.
|
||||
if (errorsList && errorsList.length > 0) {
|
||||
setFormatErrorsResult(errorsList);
|
||||
setFormatErrorsOpen(true);
|
||||
}
|
||||
setLoading(true);
|
||||
setCheckedDrives([]);
|
||||
}
|
||||
};
|
||||
|
||||
const tableActions = [
|
||||
{
|
||||
type: "format",
|
||||
onClick: formatSingleUnit,
|
||||
sendOnlyId: true,
|
||||
},
|
||||
];
|
||||
|
||||
const filteredRecords: IDirectPVDrives[] = records.filter((elementItem) =>
|
||||
elementItem.drive.includes(filter)
|
||||
);
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
{formatOpen && (
|
||||
<FormatDrives
|
||||
closeFormatModalAndRefresh={closeFormatModal}
|
||||
deleteOpen={formatOpen}
|
||||
allDrives={formatAll}
|
||||
drivesToFormat={drivesToFormat}
|
||||
/>
|
||||
)}
|
||||
|
||||
{formatErrorsOpen && (
|
||||
<FormatErrorsResult
|
||||
errorsList={formatErrorsResult}
|
||||
open={formatErrorsOpen}
|
||||
onCloseFormatErrorsList={() => {
|
||||
setFormatErrorsOpen(false);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
<PageHeader label="Local Drives" />
|
||||
<PageLayout>
|
||||
<Grid item xs={12} className={classes.actionsTray}>
|
||||
<TextField
|
||||
placeholder="Search Drives"
|
||||
className={classes.searchField}
|
||||
id="search-resource"
|
||||
label=""
|
||||
InputProps={{
|
||||
disableUnderline: true,
|
||||
startAdornment: (
|
||||
<InputAdornment position="start">
|
||||
<SearchIcon />
|
||||
</InputAdornment>
|
||||
),
|
||||
}}
|
||||
onChange={(e) => {
|
||||
setFilter(e.target.value);
|
||||
}}
|
||||
disabled={notAvailable}
|
||||
variant="standard"
|
||||
/>
|
||||
<BoxIconButton
|
||||
color="primary"
|
||||
aria-label="Refresh Tenant List"
|
||||
onClick={() => {
|
||||
setLoading(true);
|
||||
}}
|
||||
disabled={notAvailable}
|
||||
size="large"
|
||||
>
|
||||
<RefreshIcon />
|
||||
</BoxIconButton>
|
||||
<BoxButton
|
||||
variant="contained"
|
||||
color="primary"
|
||||
disabled={checkedDrives.length <= 0 || notAvailable}
|
||||
onClick={formatSelectedDrives}
|
||||
label={"Format Selected Drives"}
|
||||
>
|
||||
<GroupIcon />
|
||||
</BoxButton>
|
||||
<BoxButton
|
||||
variant="contained"
|
||||
color="primary"
|
||||
label={"Format All Drives"}
|
||||
onClick={formatAllDrives}
|
||||
disabled={notAvailable}
|
||||
>
|
||||
<AddIcon />
|
||||
</BoxButton>
|
||||
</Grid>
|
||||
|
||||
<Grid item xs={12}>
|
||||
{notAvailable && !loading ? (
|
||||
<HelpBox
|
||||
title={"Leverage locally attached drives"}
|
||||
iconComponent={<StorageIcon />}
|
||||
help={
|
||||
<Fragment>
|
||||
We can automatically provision persistent volumes (PVs) on top
|
||||
locally attached drives on your Kubernetes nodes by leveraging
|
||||
DirectPV.
|
||||
<br />
|
||||
<br />
|
||||
For more information{" "}
|
||||
<a
|
||||
href="https://github.com/minio/directpv"
|
||||
rel="noreferrer"
|
||||
target="_blank"
|
||||
className={classes.linkItem}
|
||||
>
|
||||
Visit DirectPV Documentation
|
||||
</a>
|
||||
</Fragment>
|
||||
}
|
||||
/>
|
||||
) : (
|
||||
<TableWrapper
|
||||
itemActions={tableActions}
|
||||
columns={[
|
||||
{
|
||||
label: "Drive",
|
||||
elementKey: "drive",
|
||||
},
|
||||
{
|
||||
label: "Capacity",
|
||||
elementKey: "capacity",
|
||||
renderFunction: niceBytes,
|
||||
},
|
||||
{
|
||||
label: "Allocated",
|
||||
elementKey: "allocated",
|
||||
renderFunction: niceBytes,
|
||||
},
|
||||
{
|
||||
label: "Volumes",
|
||||
elementKey: "volumes",
|
||||
},
|
||||
{
|
||||
label: "Node",
|
||||
elementKey: "node",
|
||||
},
|
||||
{
|
||||
label: "Status",
|
||||
elementKey: "status",
|
||||
},
|
||||
]}
|
||||
onSelect={selectionChanged}
|
||||
selectedItems={checkedDrives}
|
||||
isLoading={loading}
|
||||
records={filteredRecords}
|
||||
customPaperHeight={classes.tableWrapper}
|
||||
entityName="Drives"
|
||||
idField="joinName"
|
||||
/>
|
||||
)}
|
||||
</Grid>
|
||||
</PageLayout>
|
||||
</Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
export default withStyles(styles)(DirectPVMain);
|
||||
163
portal-ui/src/screens/Console/DirectPV/DirectPVVolumes.tsx
Normal file
163
portal-ui/src/screens/Console/DirectPV/DirectPVVolumes.tsx
Normal file
@@ -0,0 +1,163 @@
|
||||
// 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 get from "lodash/get";
|
||||
import { useSelector } from "react-redux";
|
||||
import { Theme } from "@mui/material/styles";
|
||||
import createStyles from "@mui/styles/createStyles";
|
||||
import withStyles from "@mui/styles/withStyles";
|
||||
import { Grid, InputAdornment, TextField } from "@mui/material";
|
||||
import { AppState, useAppDispatch } from "../../../store";
|
||||
import {
|
||||
actionsTray,
|
||||
containerForHeader,
|
||||
searchField,
|
||||
} from "../Common/FormComponents/common/styleLibrary";
|
||||
import { IDirectPVVolumes, IVolumesResponse } from "./types";
|
||||
import { niceBytes } from "../../../common/utils";
|
||||
import { ErrorResponseHandler } from "../../../common/types";
|
||||
import { setErrorSnackMessage } from "../../../systemSlice";
|
||||
import api from "../../../common/api";
|
||||
import TableWrapper from "../Common/TableWrapper/TableWrapper";
|
||||
import SearchIcon from "../../../icons/SearchIcon";
|
||||
import PageHeader from "../Common/PageHeader/PageHeader";
|
||||
import PageLayout from "../Common/Layout/PageLayout";
|
||||
|
||||
interface IDirectPVVolumesProps {
|
||||
classes: any;
|
||||
}
|
||||
|
||||
const styles = (theme: Theme) =>
|
||||
createStyles({
|
||||
tableWrapper: {
|
||||
height: "calc(100vh - 267px)",
|
||||
},
|
||||
...actionsTray,
|
||||
...searchField,
|
||||
...containerForHeader(theme.spacing(4)),
|
||||
});
|
||||
|
||||
const DirectPVVolumes = ({ classes }: IDirectPVVolumesProps) => {
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const selectedDrive = useSelector(
|
||||
(state: AppState) => state.directPV.selectedDrive
|
||||
);
|
||||
|
||||
const [records, setRecords] = useState<IDirectPVVolumes[]>([]);
|
||||
const [filter, setFilter] = useState("");
|
||||
const [loading, setLoading] = useState<boolean>(true);
|
||||
|
||||
useEffect(() => {
|
||||
if (loading) {
|
||||
api
|
||||
.invoke("GET", `/api/v1/directpv/volumes?drives=${selectedDrive}`)
|
||||
.then((res: IVolumesResponse) => {
|
||||
let volumes = get(res, "volumes", []);
|
||||
|
||||
if (!volumes) {
|
||||
volumes = [];
|
||||
}
|
||||
|
||||
volumes.sort((d1, d2) => {
|
||||
if (d1.volume > d2.volume) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (d1.volume < d2.volume) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
});
|
||||
|
||||
setRecords(volumes);
|
||||
setLoading(false);
|
||||
})
|
||||
.catch((err: ErrorResponseHandler) => {
|
||||
setLoading(false);
|
||||
dispatch(setErrorSnackMessage(err));
|
||||
});
|
||||
}
|
||||
}, [loading, selectedDrive, dispatch]);
|
||||
|
||||
const filteredRecords: IDirectPVVolumes[] = records.filter((elementItem) =>
|
||||
elementItem.drive.includes(filter)
|
||||
);
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<PageHeader label="Volumes" />
|
||||
<PageLayout>
|
||||
<Grid item xs={12} className={classes.actionsTray}>
|
||||
<TextField
|
||||
placeholder="Search Volumes"
|
||||
className={classes.searchField}
|
||||
id="search-resource"
|
||||
label=""
|
||||
InputProps={{
|
||||
disableUnderline: true,
|
||||
startAdornment: (
|
||||
<InputAdornment position="start">
|
||||
<SearchIcon />
|
||||
</InputAdornment>
|
||||
),
|
||||
}}
|
||||
onChange={(e) => {
|
||||
setFilter(e.target.value);
|
||||
}}
|
||||
variant="standard"
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<br />
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<TableWrapper
|
||||
itemActions={[]}
|
||||
columns={[
|
||||
{
|
||||
label: "Volume",
|
||||
elementKey: "volume",
|
||||
},
|
||||
{
|
||||
label: "Capacity",
|
||||
elementKey: "capacity",
|
||||
renderFunction: niceBytes,
|
||||
},
|
||||
{
|
||||
label: "Node",
|
||||
elementKey: "node",
|
||||
},
|
||||
{
|
||||
label: "Drive",
|
||||
elementKey: "drive",
|
||||
},
|
||||
]}
|
||||
isLoading={loading}
|
||||
records={filteredRecords}
|
||||
entityName="Volumes"
|
||||
idField="volume"
|
||||
customPaperHeight={classes.tableWrapper}
|
||||
/>
|
||||
</Grid>
|
||||
</PageLayout>
|
||||
</Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
export default withStyles(styles)(DirectPVVolumes);
|
||||
143
portal-ui/src/screens/Console/DirectPV/FormatDrives.tsx
Normal file
143
portal-ui/src/screens/Console/DirectPV/FormatDrives.tsx
Normal file
@@ -0,0 +1,143 @@
|
||||
// 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 { DialogContentText, Grid, LinearProgress } from "@mui/material";
|
||||
import { IDirectPVFormatResItem, IDirectPVFormatResult } from "./types";
|
||||
import { ErrorResponseHandler } from "../../../common/types";
|
||||
import api from "../../../common/api";
|
||||
import InputBoxWrapper from "../Common/FormComponents/InputBoxWrapper/InputBoxWrapper";
|
||||
import PredefinedList from "../Common/FormComponents/PredefinedList/PredefinedList";
|
||||
import FormSwitchWrapper from "../Common/FormComponents/FormSwitchWrapper/FormSwitchWrapper";
|
||||
import ConfirmDialog from "../Common/ModalWrapper/ConfirmDialog";
|
||||
import { FormatDrivesIcon } from "../../../icons";
|
||||
import { setErrorSnackMessage } from "../../../systemSlice";
|
||||
import { useAppDispatch } from "../../../store";
|
||||
|
||||
interface IFormatAllDrivesProps {
|
||||
closeFormatModalAndRefresh: (
|
||||
refresh: boolean,
|
||||
formatIssuesList: IDirectPVFormatResItem[]
|
||||
) => void;
|
||||
deleteOpen: boolean;
|
||||
allDrives: boolean;
|
||||
drivesToFormat: string[];
|
||||
}
|
||||
|
||||
const FormatDrives = ({
|
||||
closeFormatModalAndRefresh,
|
||||
deleteOpen,
|
||||
allDrives,
|
||||
drivesToFormat,
|
||||
}: IFormatAllDrivesProps) => {
|
||||
const dispatch = useAppDispatch();
|
||||
const [deleteLoading, setDeleteLoading] = useState<boolean>(false);
|
||||
const [formatAll, setFormatAll] = useState<string>("");
|
||||
const [force, setForce] = useState<boolean>(false);
|
||||
|
||||
const removeRecord = () => {
|
||||
if (deleteLoading) {
|
||||
return;
|
||||
}
|
||||
setDeleteLoading(true);
|
||||
api
|
||||
.invoke("POST", `/api/v1/directpv/drives/format`, {
|
||||
drives: drivesToFormat,
|
||||
force,
|
||||
})
|
||||
.then((res: IDirectPVFormatResult) => {
|
||||
setDeleteLoading(false);
|
||||
closeFormatModalAndRefresh(true, res.formatIssuesList);
|
||||
})
|
||||
.catch((err: ErrorResponseHandler) => {
|
||||
setDeleteLoading(false);
|
||||
dispatch(setErrorSnackMessage(err));
|
||||
});
|
||||
};
|
||||
return (
|
||||
<ConfirmDialog
|
||||
title={`Format ${allDrives ? "All " : ""} Drives`}
|
||||
confirmText={`Format Drive${
|
||||
drivesToFormat.length > 1 || allDrives ? "s" : ""
|
||||
}`}
|
||||
confirmButtonProps={{
|
||||
disabled: formatAll !== "YES, PROCEED",
|
||||
}}
|
||||
isOpen={deleteOpen}
|
||||
isLoading={deleteLoading}
|
||||
onConfirm={removeRecord}
|
||||
onClose={() => {
|
||||
closeFormatModalAndRefresh(false, []);
|
||||
}}
|
||||
titleIcon={<FormatDrivesIcon />}
|
||||
confirmationContent={
|
||||
<React.Fragment>
|
||||
<DialogContentText>
|
||||
{!allDrives && (
|
||||
<Fragment>
|
||||
<PredefinedList
|
||||
label={`Selected Drive${
|
||||
drivesToFormat.length > 1 ? "s" : ""
|
||||
}`}
|
||||
content={drivesToFormat.join(", ")}
|
||||
/>
|
||||
<br />
|
||||
</Fragment>
|
||||
)}
|
||||
<Grid item xs={12}>
|
||||
<FormSwitchWrapper
|
||||
value="force"
|
||||
id="force"
|
||||
name="force"
|
||||
checked={force}
|
||||
onChange={(event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setForce(event.target.checked);
|
||||
}}
|
||||
label={"Force Format"}
|
||||
indicatorLabels={["Yes", "No"]}
|
||||
/>
|
||||
</Grid>
|
||||
Are you sure you want to format{" "}
|
||||
{allDrives ? <strong>All</strong> : "the selected"} drive
|
||||
{drivesToFormat.length > 1 || allDrives ? "s" : ""}?.
|
||||
<br />
|
||||
<br />
|
||||
<strong>
|
||||
All information contained will be erased and cannot be recovered
|
||||
</strong>
|
||||
<br />
|
||||
<br />
|
||||
To continue please type <b>YES, PROCEED</b> in the box.
|
||||
<Grid item xs={12}>
|
||||
<InputBoxWrapper
|
||||
id="format-confirm"
|
||||
name="format-confirm"
|
||||
onChange={(event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setFormatAll(event.target.value);
|
||||
}}
|
||||
label=""
|
||||
value={formatAll}
|
||||
/>
|
||||
</Grid>
|
||||
</DialogContentText>
|
||||
{deleteLoading && <LinearProgress />}
|
||||
</React.Fragment>
|
||||
}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default FormatDrives;
|
||||
122
portal-ui/src/screens/Console/DirectPV/FormatErrorsResult.tsx
Normal file
122
portal-ui/src/screens/Console/DirectPV/FormatErrorsResult.tsx
Normal file
@@ -0,0 +1,122 @@
|
||||
// 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 { Button, Grid, Theme } from "@mui/material";
|
||||
import createStyles from "@mui/styles/createStyles";
|
||||
import withStyles from "@mui/styles/withStyles";
|
||||
import React from "react";
|
||||
import ModalWrapper from "../Common/ModalWrapper/ModalWrapper";
|
||||
import TableWrapper from "../Common/TableWrapper/TableWrapper";
|
||||
import { IDirectPVFormatResItem } from "./types";
|
||||
import { modalStyleUtils } from "../Common/FormComponents/common/styleLibrary";
|
||||
import { DriveFormatErrorsIcon } from "../../../icons";
|
||||
import { encodeURLString } from "../../../common/utils";
|
||||
|
||||
interface IFormatErrorsProps {
|
||||
open: boolean;
|
||||
onCloseFormatErrorsList: () => void;
|
||||
errorsList: IDirectPVFormatResItem[];
|
||||
classes: any;
|
||||
}
|
||||
|
||||
const styles = (theme: Theme) =>
|
||||
createStyles({
|
||||
buttonContainer: {
|
||||
textAlign: "right",
|
||||
},
|
||||
errorsList: {
|
||||
height: "calc(100vh - 280px)",
|
||||
},
|
||||
...modalStyleUtils,
|
||||
});
|
||||
|
||||
const download = (filename: string, text: string) => {
|
||||
let element = document.createElement("a");
|
||||
element.setAttribute(
|
||||
"href",
|
||||
"data:application/json;charset=utf-8," + encodeURLString(text)
|
||||
);
|
||||
element.setAttribute("download", filename);
|
||||
|
||||
element.style.display = "none";
|
||||
document.body.appendChild(element);
|
||||
|
||||
element.click();
|
||||
|
||||
document.body.removeChild(element);
|
||||
};
|
||||
|
||||
const FormatErrorsResult = ({
|
||||
open,
|
||||
onCloseFormatErrorsList,
|
||||
errorsList,
|
||||
classes,
|
||||
}: IFormatErrorsProps) => {
|
||||
return (
|
||||
<ModalWrapper
|
||||
modalOpen={open}
|
||||
title={"Format Errors"}
|
||||
onClose={onCloseFormatErrorsList}
|
||||
titleIcon={<DriveFormatErrorsIcon />}
|
||||
>
|
||||
<Grid container>
|
||||
<Grid item xs={12} className={classes.modalFormScrollable}>
|
||||
There were some issues trying to format the selected CSI Drives,
|
||||
please fix the issues and try again.
|
||||
<br />
|
||||
<TableWrapper
|
||||
columns={[
|
||||
{
|
||||
label: "Node",
|
||||
elementKey: "node",
|
||||
},
|
||||
{ label: "Drive", elementKey: "drive" },
|
||||
{ label: "Message", elementKey: "error" },
|
||||
]}
|
||||
entityName="Format Errors"
|
||||
idField="drive"
|
||||
records={errorsList}
|
||||
isLoading={false}
|
||||
customPaperHeight={classes.errorsList}
|
||||
textSelectable
|
||||
noBackground
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12} className={classes.modalButtonBar}>
|
||||
<Button
|
||||
color="primary"
|
||||
variant="outlined"
|
||||
onClick={() => {
|
||||
download("csiFormatErrors.json", JSON.stringify([...errorsList]));
|
||||
}}
|
||||
>
|
||||
Download
|
||||
</Button>
|
||||
<Button
|
||||
onClick={onCloseFormatErrorsList}
|
||||
color="primary"
|
||||
variant="contained"
|
||||
autoFocus
|
||||
>
|
||||
Done
|
||||
</Button>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</ModalWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
export default withStyles(styles)(FormatErrorsResult);
|
||||
40
portal-ui/src/screens/Console/DirectPV/directPVSlice.ts
Normal file
40
portal-ui/src/screens/Console/DirectPV/directPVSlice.ts
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 { createSlice, PayloadAction } from "@reduxjs/toolkit";
|
||||
|
||||
export interface IDirectPVReducer {
|
||||
selectedDrive: string;
|
||||
}
|
||||
|
||||
const initialState: IDirectPVReducer = {
|
||||
selectedDrive: "",
|
||||
};
|
||||
export const directPVSlice = createSlice({
|
||||
name: "directPV",
|
||||
initialState,
|
||||
reducers: {
|
||||
selectDrive: (state, action: PayloadAction<string>) => {
|
||||
if (action.payload !== "") {
|
||||
state.selectedDrive = action.payload;
|
||||
}
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
export const { selectDrive } = directPVSlice.actions;
|
||||
|
||||
export default directPVSlice.reducer;
|
||||
50
portal-ui/src/screens/Console/DirectPV/types.ts
Normal file
50
portal-ui/src/screens/Console/DirectPV/types.ts
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/>.
|
||||
|
||||
export interface IDirectPVDrives {
|
||||
joinName: string;
|
||||
drive: string;
|
||||
capacity: string;
|
||||
allocated: string;
|
||||
volumes: number;
|
||||
node: string;
|
||||
status: "Available" | "Unavailable" | "InUse" | "Ready" | "Terminating";
|
||||
}
|
||||
|
||||
export interface IDirectPVVolumes {
|
||||
volume: string;
|
||||
capacity: string;
|
||||
node: string;
|
||||
drive: string;
|
||||
}
|
||||
|
||||
export interface IDrivesResponse {
|
||||
drives: IDirectPVDrives[];
|
||||
}
|
||||
|
||||
export interface IVolumesResponse {
|
||||
volumes: IDirectPVVolumes[];
|
||||
}
|
||||
|
||||
export interface IDirectPVFormatResult {
|
||||
formatIssuesList: IDirectPVFormatResItem[];
|
||||
}
|
||||
|
||||
export interface IDirectPVFormatResItem {
|
||||
node: string;
|
||||
drive: string;
|
||||
error: string;
|
||||
}
|
||||
@@ -31,7 +31,12 @@ import api from "../../../common/api";
|
||||
import MenuToggle from "./MenuToggle";
|
||||
import ConsoleMenuList from "./ConsoleMenuList";
|
||||
import { validRoutes } from "../valid-routes";
|
||||
import { menuOpen, selOpMode, userLogged } from "../../../systemSlice";
|
||||
import {
|
||||
menuOpen,
|
||||
selDirectPVMode,
|
||||
selOpMode,
|
||||
userLogged,
|
||||
} from "../../../systemSlice";
|
||||
import { resetSession, selFeatures } from "../consoleSlice";
|
||||
|
||||
const drawerWidth = 250;
|
||||
@@ -97,6 +102,7 @@ const Menu = ({ classes }: IMenuProps) => {
|
||||
(state: AppState) => state.system.sidebarOpen
|
||||
);
|
||||
const operatorMode = useSelector(selOpMode);
|
||||
const directPVMode = useSelector(selDirectPVMode);
|
||||
|
||||
const logout = () => {
|
||||
const deleteSession = () => {
|
||||
@@ -117,7 +123,7 @@ const Menu = ({ classes }: IMenuProps) => {
|
||||
deleteSession();
|
||||
});
|
||||
};
|
||||
const allowedMenuItems = validRoutes(features, operatorMode);
|
||||
const allowedMenuItems = validRoutes(features, operatorMode, directPVMode);
|
||||
|
||||
return (
|
||||
<Drawer
|
||||
|
||||
@@ -16,13 +16,19 @@
|
||||
|
||||
import React, { Fragment, Suspense, useEffect } from "react";
|
||||
import OperatorLogo from "../../../icons/OperatorLogo";
|
||||
import DirectPVLogo from "../../../icons/DirectPVLogo";
|
||||
|
||||
import { LoginMinIOLogo, VersionIcon } from "../../../icons";
|
||||
import { Box, IconButton } from "@mui/material";
|
||||
import MenuIcon from "@mui/icons-material/Menu";
|
||||
import LicensedConsoleLogo from "../Common/Components/LicensedConsoleLogo";
|
||||
import { useSelector } from "react-redux";
|
||||
import useApi from "../Common/Hooks/useApi";
|
||||
import { setLicenseInfo } from "../../../systemSlice";
|
||||
import {
|
||||
selDirectPVMode,
|
||||
selOpMode,
|
||||
setLicenseInfo,
|
||||
} from "../../../systemSlice";
|
||||
import { AppState, useAppDispatch } from "../../../store";
|
||||
import MenuToggleIcon from "../../../icons/MenuToggleIcon";
|
||||
|
||||
@@ -38,9 +44,9 @@ const MenuToggle = ({ isOpen, onToggle }: MenuToggleProps) => {
|
||||
const licenseInfo = useSelector(
|
||||
(state: AppState) => state?.system?.licenseInfo
|
||||
);
|
||||
const operatorMode = useSelector(
|
||||
(state: AppState) => state.system.operatorMode
|
||||
);
|
||||
const operatorMode = useSelector(selOpMode);
|
||||
|
||||
const directPVMode = useSelector(selDirectPVMode);
|
||||
|
||||
const [isLicenseLoading, invokeLicenseInfoApi] = useApi(
|
||||
(res: any) => {
|
||||
@@ -107,9 +113,7 @@ const MenuToggle = ({ isOpen, onToggle }: MenuToggleProps) => {
|
||||
>
|
||||
{isOpen ? (
|
||||
<div className={`logo ${stateClsName}`}>
|
||||
{operatorMode ? (
|
||||
<OperatorLogo />
|
||||
) : (
|
||||
{!operatorMode && !directPVMode ? (
|
||||
<Fragment>
|
||||
<div
|
||||
style={{ marginLeft: "4px", width: 100, textAlign: "right" }}
|
||||
@@ -122,6 +126,10 @@ const MenuToggle = ({ isOpen, onToggle }: MenuToggleProps) => {
|
||||
/>
|
||||
</div>
|
||||
</Fragment>
|
||||
) : (
|
||||
<Fragment>
|
||||
{directPVMode ? <DirectPVLogo /> : <OperatorLogo />}
|
||||
</Fragment>
|
||||
)}
|
||||
</div>
|
||||
) : (
|
||||
|
||||
167
portal-ui/src/screens/Console/Storage/StoragePVCs.tsx
Normal file
167
portal-ui/src/screens/Console/Storage/StoragePVCs.tsx
Normal file
@@ -0,0 +1,167 @@
|
||||
// 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 get from "lodash/get";
|
||||
import { Theme } from "@mui/material/styles";
|
||||
import { Grid } from "@mui/material";
|
||||
import createStyles from "@mui/styles/createStyles";
|
||||
import withStyles from "@mui/styles/withStyles";
|
||||
import {
|
||||
actionsTray,
|
||||
containerForHeader,
|
||||
searchField,
|
||||
} from "../Common/FormComponents/common/styleLibrary";
|
||||
import { ErrorResponseHandler } from "../../../common/types";
|
||||
import { useAppDispatch } from "../../../store";
|
||||
import { setErrorSnackMessage } from "../../../systemSlice";
|
||||
import { IPVCsResponse, IStoragePVCs } from "./types";
|
||||
import api from "../../../common/api";
|
||||
import TableWrapper from "../Common/TableWrapper/TableWrapper";
|
||||
import DeletePVC from "../Tenants/TenantDetails/DeletePVC";
|
||||
import PageHeader from "../Common/PageHeader/PageHeader";
|
||||
import PageLayout from "../Common/Layout/PageLayout";
|
||||
import SearchBox from "../Common/SearchBox";
|
||||
|
||||
interface IStorageVolumesProps {
|
||||
classes: any;
|
||||
}
|
||||
|
||||
const styles = (theme: Theme) =>
|
||||
createStyles({
|
||||
tableWrapper: {
|
||||
height: "calc(100vh - 150px)",
|
||||
},
|
||||
...actionsTray,
|
||||
...searchField,
|
||||
...containerForHeader(theme.spacing(4)),
|
||||
});
|
||||
|
||||
const StorageVolumes = ({ classes }: IStorageVolumesProps) => {
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const [records, setRecords] = useState<IStoragePVCs[]>([]);
|
||||
const [filter, setFilter] = useState("");
|
||||
const [loading, setLoading] = useState<boolean>(true);
|
||||
const [selectedPVC, setSelectedPVC] = useState<any>(null);
|
||||
const [deleteOpen, setDeleteOpen] = useState<boolean>(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (loading) {
|
||||
api
|
||||
.invoke("GET", `/api/v1/list-pvcs`)
|
||||
.then((res: IPVCsResponse) => {
|
||||
let volumes = get(res, "pvcs", []);
|
||||
setRecords(volumes ? volumes : []);
|
||||
setLoading(false);
|
||||
})
|
||||
.catch((err: ErrorResponseHandler) => {
|
||||
setLoading(false);
|
||||
dispatch(setErrorSnackMessage(err));
|
||||
});
|
||||
}
|
||||
}, [loading, dispatch]);
|
||||
|
||||
const filteredRecords: IStoragePVCs[] = records.filter((elementItem) =>
|
||||
elementItem.name.toLowerCase().includes(filter.toLowerCase())
|
||||
);
|
||||
|
||||
const confirmDeletePVC = (pvcItem: IStoragePVCs) => {
|
||||
const delPvc = {
|
||||
...pvcItem,
|
||||
tenant: pvcItem.tenant,
|
||||
namespace: pvcItem.namespace,
|
||||
};
|
||||
setSelectedPVC(delPvc);
|
||||
setDeleteOpen(true);
|
||||
};
|
||||
|
||||
const tableActions = [{ type: "delete", onClick: confirmDeletePVC }];
|
||||
|
||||
const closeDeleteModalAndRefresh = (reloadData: boolean) => {
|
||||
setDeleteOpen(false);
|
||||
setLoading(true);
|
||||
};
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
{deleteOpen && (
|
||||
<DeletePVC
|
||||
deleteOpen={deleteOpen}
|
||||
selectedPVC={selectedPVC}
|
||||
closeDeleteModalAndRefresh={closeDeleteModalAndRefresh}
|
||||
/>
|
||||
)}
|
||||
<PageHeader
|
||||
label="Persistent Volumes Claims"
|
||||
middleComponent={
|
||||
<SearchBox
|
||||
placeholder={"Search Volumes (PVCs)"}
|
||||
onChange={(val) => {
|
||||
setFilter(val);
|
||||
}}
|
||||
value={filter}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<PageLayout>
|
||||
<Grid item xs={12}>
|
||||
<TableWrapper
|
||||
itemActions={tableActions}
|
||||
columns={[
|
||||
{
|
||||
label: "Name",
|
||||
elementKey: "name",
|
||||
},
|
||||
{
|
||||
label: "Namespace",
|
||||
elementKey: "namespace",
|
||||
width: 90,
|
||||
},
|
||||
{
|
||||
label: "Status",
|
||||
elementKey: "status",
|
||||
width: 120,
|
||||
},
|
||||
{
|
||||
label: "Tenant",
|
||||
renderFullObject: true,
|
||||
renderFunction: (record: any) =>
|
||||
`${record.namespace}/${record.tenant}`,
|
||||
},
|
||||
{
|
||||
label: "Capacity",
|
||||
elementKey: "capacity",
|
||||
width: 90,
|
||||
},
|
||||
{
|
||||
label: "Storage Class",
|
||||
elementKey: "storageClass",
|
||||
},
|
||||
]}
|
||||
isLoading={loading}
|
||||
records={filteredRecords}
|
||||
entityName="PVCs"
|
||||
idField="name"
|
||||
customPaperHeight={classes.tableWrapper}
|
||||
/>
|
||||
</Grid>
|
||||
</PageLayout>
|
||||
</Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
export default withStyles(styles)(StorageVolumes);
|
||||
@@ -23,11 +23,12 @@ import { Bucket } from "./Buckets/types";
|
||||
export const routesAsKbarActions = (
|
||||
features: string[] | null,
|
||||
operatorMode: boolean,
|
||||
directPVMode: boolean,
|
||||
buckets: Bucket[],
|
||||
navigate: (url: string) => void
|
||||
) => {
|
||||
const initialActions: Action[] = [];
|
||||
const allowedMenuItems = validRoutes(features, operatorMode);
|
||||
const allowedMenuItems = validRoutes(features, operatorMode, directPVMode);
|
||||
for (const i of allowedMenuItems) {
|
||||
if (i.children && i.children.length > 0) {
|
||||
for (const childI of i.children) {
|
||||
|
||||
@@ -28,6 +28,7 @@ export interface ISessionResponse {
|
||||
status: string;
|
||||
features: string[];
|
||||
operator: boolean;
|
||||
directPV?: boolean;
|
||||
distributedMode: boolean;
|
||||
permissions: ISessionPermissions;
|
||||
allowResources: IAllowResources[] | null;
|
||||
|
||||
@@ -46,10 +46,12 @@ import { hasPermission } from "../../common/SecureComponent";
|
||||
import WatchIcon from "../../icons/WatchIcon";
|
||||
import RegisterMenuIcon from "../../icons/SidebarMenus/RegisterMenuIcon";
|
||||
import {
|
||||
ClustersIcon,
|
||||
DocumentationIcon,
|
||||
LambdaIcon,
|
||||
LicenseIcon,
|
||||
RecoverIcon,
|
||||
StorageIcon,
|
||||
TenantsOutlineIcon,
|
||||
TiersIcon,
|
||||
} from "../../icons";
|
||||
@@ -59,7 +61,8 @@ import LicenseBadge from "./Menu/LicenseBadge";
|
||||
|
||||
export const validRoutes = (
|
||||
features: string[] | null | undefined,
|
||||
operatorMode: boolean
|
||||
operatorMode: boolean,
|
||||
directPVMode: boolean
|
||||
) => {
|
||||
const ldapIsEnabled = (features && features.includes("ldap-idp")) || false;
|
||||
let consoleMenus: IMenuItem[] = [
|
||||
@@ -311,35 +314,101 @@ export const validRoutes = (
|
||||
},
|
||||
];
|
||||
|
||||
const allowedItems = (operatorMode ? operatorMenus : consoleMenus).filter(
|
||||
(item: IMenuItem) => {
|
||||
if (item.children && item.children.length > 0) {
|
||||
const c = item.children?.filter((childItem: IMenuItem) => {
|
||||
return (
|
||||
((childItem.customPermissionFnc
|
||||
? childItem.customPermissionFnc()
|
||||
: hasPermission(
|
||||
CONSOLE_UI_RESOURCE,
|
||||
IAM_PAGES_PERMISSIONS[childItem.to ?? ""]
|
||||
)) ||
|
||||
childItem.forceDisplay) &&
|
||||
!childItem.fsHidden
|
||||
);
|
||||
});
|
||||
return c.length > 0;
|
||||
}
|
||||
let directPVMenus: IMenuItem[] = [
|
||||
{
|
||||
group: "Storage",
|
||||
type: "item",
|
||||
id: "StoragePVCs",
|
||||
component: NavLink,
|
||||
to: IAM_PAGES.DIRECTPV_STORAGE,
|
||||
name: "PVCs",
|
||||
icon: ClustersIcon,
|
||||
forceDisplay: true,
|
||||
},
|
||||
{
|
||||
name: "Drives",
|
||||
type: "item",
|
||||
id: "drives",
|
||||
component: NavLink,
|
||||
icon: DrivesMenuIcon,
|
||||
to: IAM_PAGES.DIRECTPV_DRIVES,
|
||||
forceDisplay: true,
|
||||
},
|
||||
{
|
||||
name: "Volumes",
|
||||
type: "item",
|
||||
id: "volumes",
|
||||
component: NavLink,
|
||||
icon: StorageIcon,
|
||||
to: IAM_PAGES.DIRECTPV_VOLUMES,
|
||||
forceDisplay: true,
|
||||
},
|
||||
{
|
||||
group: "DirectPV",
|
||||
type: "item",
|
||||
id: "License",
|
||||
component: NavLink,
|
||||
to: IAM_PAGES.LICENSE,
|
||||
name: "License",
|
||||
icon: LicenseIcon,
|
||||
forceDisplay: true,
|
||||
},
|
||||
{
|
||||
group: "DirectPV",
|
||||
type: "item",
|
||||
id: "Documentation",
|
||||
component: NavLink,
|
||||
to: IAM_PAGES.DOCUMENTATION,
|
||||
name: "Documentation",
|
||||
icon: DocumentationIcon,
|
||||
forceDisplay: true,
|
||||
onClick: (
|
||||
e:
|
||||
| React.MouseEvent<HTMLLIElement>
|
||||
| React.MouseEvent<HTMLAnchorElement>
|
||||
| React.MouseEvent<HTMLDivElement>
|
||||
) => {
|
||||
e.preventDefault();
|
||||
window.open("https://docs.min.io/?ref=op", "_blank");
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
const res =
|
||||
((item.customPermissionFnc
|
||||
? item.customPermissionFnc()
|
||||
: hasPermission(
|
||||
CONSOLE_UI_RESOURCE,
|
||||
IAM_PAGES_PERMISSIONS[item.to ?? ""]
|
||||
)) ||
|
||||
item.forceDisplay) &&
|
||||
!item.fsHidden;
|
||||
return res;
|
||||
let menus = consoleMenus;
|
||||
|
||||
if (directPVMode) {
|
||||
menus = directPVMenus;
|
||||
} else if (operatorMode) {
|
||||
menus = operatorMenus;
|
||||
}
|
||||
|
||||
const allowedItems = menus.filter((item: IMenuItem) => {
|
||||
if (item.children && item.children.length > 0) {
|
||||
const c = item.children?.filter((childItem: IMenuItem) => {
|
||||
return (
|
||||
((childItem.customPermissionFnc
|
||||
? childItem.customPermissionFnc()
|
||||
: hasPermission(
|
||||
CONSOLE_UI_RESOURCE,
|
||||
IAM_PAGES_PERMISSIONS[childItem.to ?? ""]
|
||||
)) ||
|
||||
childItem.forceDisplay) &&
|
||||
!childItem.fsHidden
|
||||
);
|
||||
});
|
||||
return c.length > 0;
|
||||
}
|
||||
);
|
||||
|
||||
const res =
|
||||
((item.customPermissionFnc
|
||||
? item.customPermissionFnc()
|
||||
: hasPermission(
|
||||
CONSOLE_UI_RESOURCE,
|
||||
IAM_PAGES_PERMISSIONS[item.to ?? ""]
|
||||
)) ||
|
||||
item.forceDisplay) &&
|
||||
!item.fsHidden;
|
||||
return res;
|
||||
});
|
||||
return allowedItems;
|
||||
};
|
||||
|
||||
@@ -50,6 +50,7 @@ import {
|
||||
import { resetForm, setJwt } from "./loginSlice";
|
||||
import StrategyForm from "./StrategyForm";
|
||||
import { LoginField } from "./LoginField";
|
||||
import DirectPVLogo from "../../icons/DirectPVLogo";
|
||||
|
||||
const useStyles = makeStyles((theme: Theme) =>
|
||||
createStyles({
|
||||
@@ -260,6 +261,8 @@ const Login = () => {
|
||||
);
|
||||
const navigateTo = useSelector((state: AppState) => state.login.navigateTo);
|
||||
|
||||
const directPVMode = useSelector((state: AppState) => state.login.isDirectPV);
|
||||
|
||||
const isOperator =
|
||||
loginStrategy.loginStrategy === loginStrategyType.serviceAccount ||
|
||||
loginStrategy.loginStrategy === loginStrategyType.redirectServiceAccount;
|
||||
@@ -396,7 +399,13 @@ const Login = () => {
|
||||
);
|
||||
}
|
||||
|
||||
const consoleText = isOperator ? <OperatorLogo /> : <ConsoleLogo />;
|
||||
let modeLogo = <ConsoleLogo />;
|
||||
|
||||
if (directPVMode) {
|
||||
modeLogo = <DirectPVLogo />;
|
||||
} else if (isOperator) {
|
||||
modeLogo = <OperatorLogo />;
|
||||
}
|
||||
|
||||
const hyperLink = isOperator
|
||||
? "https://docs.min.io/minio/k8s/operator-console/operator-console.html?ref=con"
|
||||
@@ -432,7 +441,7 @@ const Login = () => {
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Box className={classes.iconLogo}>{consoleText}</Box>
|
||||
<Box className={classes.iconLogo}>{modeLogo}</Box>
|
||||
<Box
|
||||
style={{
|
||||
font: "normal normal normal 20px/24px Lato",
|
||||
|
||||
@@ -37,6 +37,7 @@ export interface LoginState {
|
||||
|
||||
latestMinIOVersion: string;
|
||||
loadingVersion: boolean;
|
||||
isDirectPV: boolean;
|
||||
|
||||
navigateTo: string;
|
||||
}
|
||||
@@ -55,6 +56,7 @@ const initialState: LoginState = {
|
||||
loadingFetchConfiguration: true,
|
||||
latestMinIOVersion: "",
|
||||
loadingVersion: true,
|
||||
isDirectPV: false,
|
||||
|
||||
navigateTo: "",
|
||||
};
|
||||
@@ -107,6 +109,7 @@ export const loginSlice = createSlice({
|
||||
state.loadingFetchConfiguration = false;
|
||||
if (action.payload) {
|
||||
state.loginStrategy = action.payload;
|
||||
state.isDirectPV = !!action.payload.isDirectPV;
|
||||
}
|
||||
})
|
||||
.addCase(doLoginAsync.pending, (state, action) => {
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
export interface ILoginDetails {
|
||||
loginStrategy: loginStrategyType;
|
||||
redirect: string;
|
||||
isDirectPV?: boolean;
|
||||
}
|
||||
|
||||
export enum loginStrategyType {
|
||||
|
||||
@@ -35,6 +35,7 @@ import editPoolReducer from "./screens/Console/Tenants/TenantDetails/Pools/EditP
|
||||
import editTenantMonitoringReducer from "./screens/Console/Tenants/TenantDetails/tenantMonitoringSlice";
|
||||
import editTenantAuditLoggingReducer from "./screens/Console/Tenants/TenantDetails/tenantAuditLogSlice";
|
||||
import editTenantSecurityContextReducer from "./screens/Console/Tenants/tenantSecurityContextSlice";
|
||||
import directPVReducer from "./screens/Console/DirectPV/directPVSlice";
|
||||
|
||||
const rootReducer = combineReducers({
|
||||
system: systemReducer,
|
||||
@@ -57,6 +58,7 @@ const rootReducer = combineReducers({
|
||||
editTenantMonitoring: editTenantMonitoringReducer,
|
||||
editTenantLogging: editTenantAuditLoggingReducer,
|
||||
editTenantSecurityContext: editTenantSecurityContextReducer,
|
||||
directPV: directPVReducer,
|
||||
});
|
||||
|
||||
export const store = configureStore({
|
||||
|
||||
@@ -29,6 +29,7 @@ export interface SystemState {
|
||||
loggedIn: boolean;
|
||||
showMarketplace: boolean;
|
||||
operatorMode: boolean;
|
||||
directPVMode: boolean;
|
||||
sidebarOpen: boolean;
|
||||
session: string;
|
||||
userName: string;
|
||||
@@ -48,6 +49,7 @@ const initialState: SystemState = {
|
||||
loggedIn: false,
|
||||
showMarketplace: false,
|
||||
operatorMode: false,
|
||||
directPVMode: false,
|
||||
session: "",
|
||||
userName: "",
|
||||
sidebarOpen: initSideBarOpen,
|
||||
@@ -83,6 +85,9 @@ export const systemSlice = createSlice({
|
||||
operatorMode: (state, action: PayloadAction<boolean>) => {
|
||||
state.operatorMode = action.payload;
|
||||
},
|
||||
directPVMode: (state, action: PayloadAction<boolean>) => {
|
||||
state.directPVMode = action.payload;
|
||||
},
|
||||
menuOpen: (state, action: PayloadAction<boolean>) => {
|
||||
// persist preference to local storage
|
||||
localStorage.setItem(
|
||||
@@ -154,6 +159,7 @@ export const {
|
||||
userLogged,
|
||||
showMarketplace,
|
||||
operatorMode,
|
||||
directPVMode,
|
||||
menuOpen,
|
||||
setServerNeedsRestart,
|
||||
serverIsLoading,
|
||||
@@ -171,6 +177,7 @@ export const {
|
||||
export const selDistSet = (state: AppState) => state.system.distributedSetup;
|
||||
export const selSiteRep = (state: AppState) => state.system.siteReplicationInfo;
|
||||
export const selOpMode = (state: AppState) => state.system.operatorMode;
|
||||
export const selDirectPVMode = (state: AppState) => state.system.directPVMode;
|
||||
export const selShowMarketplace = (state: AppState) =>
|
||||
state.system.showMarketplace;
|
||||
|
||||
|
||||
@@ -1501,6 +1501,8 @@ definitions:
|
||||
enum: [ form, redirect, service-account, redirect-service-account ]
|
||||
redirect:
|
||||
type: string
|
||||
isDirectPV:
|
||||
type: boolean
|
||||
loginRequest:
|
||||
type: object
|
||||
properties:
|
||||
@@ -1544,6 +1546,8 @@ definitions:
|
||||
enum: [ ok ]
|
||||
operator:
|
||||
type: boolean
|
||||
directPV:
|
||||
type: boolean
|
||||
permissions:
|
||||
type: object
|
||||
additionalProperties:
|
||||
|
||||
Reference in New Issue
Block a user