diff --git a/portal-ui/package.json b/portal-ui/package.json
index cb73237a8..f7860a8c2 100644
--- a/portal-ui/package.json
+++ b/portal-ui/package.json
@@ -10,7 +10,7 @@
"local-storage-fallback": "^4.1.1",
"lodash": "^4.17.21",
"luxon": "^3.4.3",
- "mds": "https://github.com/minio/mds.git#v0.12.1",
+ "mds": "https://github.com/minio/mds.git#v0.12.2",
"react": "^18.1.0",
"react-component-export-image": "^1.0.6",
"react-copy-to-clipboard": "^5.0.2",
diff --git a/portal-ui/src/StyleHandler.tsx b/portal-ui/src/StyleHandler.tsx
index 5cf2fa4c2..90e0e84ae 100644
--- a/portal-ui/src/StyleHandler.tsx
+++ b/portal-ui/src/StyleHandler.tsx
@@ -31,6 +31,7 @@ const StyleHandler = ({ children }: IStyleHandler) => {
const colorVariants = useSelector(
(state: AppState) => state.system.overrideStyles,
);
+ const darkMode = useSelector((state: AppState) => state.system.darkMode);
let thm = undefined;
@@ -38,11 +39,12 @@ const StyleHandler = ({ children }: IStyleHandler) => {
thm = generateOverrideTheme(colorVariants);
}
- // ThemeHandler is needed for MDS components theming. Eventually we will remove Theme Provider & use only mds themes.
return (
- {children}
+
+ {children}
+
);
};
diff --git a/portal-ui/src/screens/Console/Common/DarkModeActivator/DarkModeActivator.tsx b/portal-ui/src/screens/Console/Common/DarkModeActivator/DarkModeActivator.tsx
new file mode 100644
index 000000000..2d4131c6d
--- /dev/null
+++ b/portal-ui/src/screens/Console/Common/DarkModeActivator/DarkModeActivator.tsx
@@ -0,0 +1,48 @@
+// This file is part of MinIO Console Server
+// Copyright (c) 2023 MinIO, Inc.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see .
+
+import React from "react";
+import { Button, DarkModeIcon } from "mds";
+import TooltipWrapper from "../TooltipWrapper/TooltipWrapper";
+import { useSelector } from "react-redux";
+import { AppState, useAppDispatch } from "../../../../store";
+import { setDarkMode } from "../../../../systemSlice";
+import { storeDarkMode } from "../../../../utils/stylesUtils";
+
+const DarkModeActivator = () => {
+ const dispatch = useAppDispatch();
+
+ const darkMode = useSelector((state: AppState) => state.system.darkMode);
+
+ const darkModeActivator = () => {
+ const currentStatus = !!darkMode;
+
+ dispatch(setDarkMode(!currentStatus));
+ storeDarkMode(!currentStatus ? "on" : "off");
+ };
+
+ return (
+
+ }
+ onClick={darkModeActivator}
+ />
+
+ );
+};
+
+export default DarkModeActivator;
diff --git a/portal-ui/src/screens/Console/Common/PageHeaderWrapper/PageHeaderWrapper.tsx b/portal-ui/src/screens/Console/Common/PageHeaderWrapper/PageHeaderWrapper.tsx
index 516fa5802..a524147a4 100644
--- a/portal-ui/src/screens/Console/Common/PageHeaderWrapper/PageHeaderWrapper.tsx
+++ b/portal-ui/src/screens/Console/Common/PageHeaderWrapper/PageHeaderWrapper.tsx
@@ -17,6 +17,7 @@
import React, { Fragment } from "react";
import { PageHeader } from "mds";
import ObjectManagerButton from "../ObjectManager/ObjectManagerButton";
+import DarkModeActivator from "../DarkModeActivator/DarkModeActivator";
interface IPageHeaderWrapper {
label: React.ReactNode;
@@ -35,6 +36,7 @@ const PageHeaderWrapper = ({
actions={
{actions}
+
}
diff --git a/portal-ui/src/screens/Console/License/LicensePlans.tsx b/portal-ui/src/screens/Console/License/LicensePlans.tsx
index 8980c3a84..d5d4eb65d 100644
--- a/portal-ui/src/screens/Console/License/LicensePlans.tsx
+++ b/portal-ui/src/screens/Console/License/LicensePlans.tsx
@@ -36,6 +36,8 @@ import {
PAID_PLANS,
STANDARD_PLAN_FEATURES,
} from "./utils";
+import styled from "styled-components";
+import get from "lodash/get";
interface IRegisterStatus {
activateProductModal: any;
@@ -45,6 +47,252 @@ interface IRegisterStatus {
setActivateProductModal: any;
}
+const PlanListContainer = styled.div(({ theme }) => ({
+ display: "grid",
+
+ margin: "0 1.5rem 0 1.5rem",
+
+ gridTemplateColumns: "1fr 1fr 1fr 1fr",
+
+ [`@media (max-width: ${breakPoints.sm}px)`]: {
+ gridTemplateColumns: "1fr 1fr 1fr",
+ },
+
+ "&.paid-plans-only": {
+ display: "grid",
+ gridTemplateColumns: "1fr 1fr 1fr",
+ },
+
+ "& .features-col": {
+ flex: 1,
+ minWidth: "260px",
+
+ "@media (max-width: 600px)": {
+ display: "none",
+ },
+ },
+
+ "& .xs-only": {
+ display: "none",
+ },
+
+ "& .button-box": {
+ display: "flex",
+ alignItems: "center",
+ justifyContent: "center",
+ padding: "5px 0px 25px 0px",
+ borderLeft: `1px solid ${get(theme, "borderColor", "#EAEAEA")}`,
+ },
+ "& .plan-header": {
+ height: "99px",
+ borderBottom: `1px solid ${get(theme, "borderColor", "#EAEAEA")}`,
+ },
+ "& .feature-title": {
+ height: "25px",
+ paddingLeft: "26px",
+ fontSize: "14px",
+
+ background: get(theme, "signalColors.disabled", "#E5E5E5"),
+ color: get(theme, "signalColors.main", "#07193E"),
+
+ "@media (max-width: 600px)": {
+ "& .feature-title-info .xs-only": {
+ display: "block",
+ },
+ },
+ },
+ "& .feature-name": {
+ minHeight: "60px",
+ padding: "5px",
+ borderBottom: `1px solid ${get(theme, "borderColor", "#EAEAEA")}`,
+ display: "flex",
+ alignItems: "center",
+ paddingLeft: "26px",
+ fontSize: "14px",
+ },
+ "& .feature-item": {
+ display: "flex",
+ flexFlow: "column",
+ alignItems: "center",
+ justifyContent: "center",
+ minHeight: "60px",
+ padding: "0 15px 0 15px",
+ borderBottom: `1px solid ${get(theme, "borderColor", "#EAEAEA")}`,
+ borderLeft: `1px solid ${get(theme, "borderColor", "#EAEAEA")}`,
+ fontSize: "14px",
+ "& .link-text": {
+ color: "#2781B0",
+ cursor: "pointer",
+ textDecoration: "underline",
+ },
+
+ "&.icon-yes": {
+ width: "15px",
+ height: "15px",
+ },
+ },
+
+ "& .feature-item-info": {
+ flex: 1,
+ display: "flex",
+ flexFlow: "column",
+ alignItems: "center",
+ justifyContent: "space-around",
+ textAlign: "center",
+
+ "@media (max-width: 600px)": {
+ justifyContent: "space-evenly",
+ width: "100%",
+ "& .xs-only": {
+ display: "block",
+ },
+ "& .plan-feature": {
+ textAlign: "center",
+ paddingRight: "10px",
+ },
+ },
+ },
+
+ "& .plan-col": {
+ minWidth: "260px",
+ flex: 1,
+ },
+
+ "& .active-plan-col": {
+ background: `${get(
+ theme,
+ "boxBackground",
+ "#FDFDFD",
+ )} 0% 0% no-repeat padding-box`,
+ boxShadow: " 0px 3px 20px #00000038",
+
+ "& .plan-header": {
+ backgroundColor: get(theme, "signalColors.info", "#2781B0"),
+ },
+
+ "& .feature-title": {
+ background: get(theme, "signalColors.disabled", "#E5E5E5"),
+ color: get(theme, "fontColor", "#000"),
+ },
+ },
+}));
+
+const PlanHeaderContainer = styled.div(({ theme }) => ({
+ display: "flex",
+ alignItems: "flex-start",
+ justifyContent: "center",
+ flexFlow: "column",
+ borderLeft: `1px solid ${get(theme, "borderColor", "#EAEAEA")}`,
+ borderBottom: "0px !important",
+ "& .plan-header": {
+ display: "flex",
+ alignItems: "center",
+ justifyContent: "center",
+ flexFlow: "column",
+ },
+
+ "& .title-block": {
+ display: "flex",
+ alignItems: "center",
+ flexFlow: "column",
+ width: "100%",
+ "& .title-main": {
+ display: "flex",
+ alignItems: "center",
+ justifyContent: "center",
+ flex: 1,
+ },
+ "& .iconContainer": {
+ "& .min-icon": {
+ minWidth: 140,
+ width: "100%",
+ maxHeight: 55,
+ height: "100%",
+ },
+ },
+ },
+
+ "& .open-source": {
+ fontSize: "14px",
+ display: "flex",
+ marginBottom: "5px",
+ alignItems: "center",
+ "& .min-icon": {
+ marginRight: "8px",
+ height: "12px",
+ width: "12px",
+ },
+ },
+
+ "& .cur-plan-text": {
+ fontSize: "12px",
+ textTransform: "uppercase",
+ },
+
+ "@media (max-width: 600px)": {
+ cursor: "pointer",
+ "& .title-block": {
+ "& .title": {
+ fontSize: "14px",
+ fontWeight: 600,
+ },
+ },
+ },
+
+ "&.active, &.active.xs-active": {
+ color: "#ffffff",
+ position: "relative",
+
+ "& .min-icon": {
+ fill: "#ffffff",
+ },
+
+ "&:before": {
+ content: "' '",
+ position: "absolute",
+ width: "100%",
+ height: "18px",
+ backgroundColor: get(theme, "signalColors.info", "#2781B0"),
+ display: "block",
+ top: -16,
+ },
+ "& .iconContainer": {
+ "& .min-icon": {
+ marginTop: "-12px",
+ },
+ },
+ },
+ "&.active": {
+ backgroundColor: get(theme, "signalColors.info", "#2781B0"),
+ color: "#ffffff",
+ },
+ "&.xs-active": {
+ background: "#eaeaea",
+ },
+}));
+
+const ListContainer = styled.div(({ theme }) => ({
+ border: `1px solid ${get(theme, "borderColor", "#EAEAEA")}`,
+ borderTop: "0px",
+ marginBottom: "45px",
+ "&::-webkit-scrollbar": {
+ width: "5px",
+ height: "5px",
+ },
+ "&::-webkit-scrollbar-track": {
+ background: "#F0F0F0",
+ borderRadius: 0,
+ boxShadow: "inset 0px 0px 0px 0px #F0F0F0",
+ },
+ "&::-webkit-scrollbar-thumb": {
+ background: "#777474",
+ borderRadius: 0,
+ },
+ "&::-webkit-scrollbar-thumb:hover": {
+ background: "#5A6375",
+ },
+}));
+
const PlanHeader = ({
isActive,
isXsViewActive,
@@ -61,7 +309,7 @@ const PlanHeader = ({
}) => {
const plan = title.toLowerCase();
return (
- {
onClick && onClick(plan);
}}
- sx={{
- display: "flex",
- alignItems: "flex-start",
- justifyContent: "center",
- flexFlow: "column",
- borderLeft: "1px solid #eaeaea",
- borderBottom: "0px !important",
- "& .plan-header": {
- display: "flex",
- alignItems: "center",
- justifyContent: "center",
- flexFlow: "column",
- },
-
- "& .title-block": {
- display: "flex",
- alignItems: "center",
- flexFlow: "column",
- width: "100%",
- "& .title-main": {
- display: "flex",
- alignItems: "center",
- justifyContent: "center",
- flex: 1,
- },
- "& .iconContainer": {
- "& .min-icon": {
- minWidth: 140,
- width: "100%",
- maxHeight: 55,
- height: "100%",
- },
- },
- },
-
- "& .open-source": {
- fontSize: "14px",
- display: "flex",
- marginBottom: "5px",
- alignItems: "center",
- "& .min-icon": {
- marginRight: "8px",
- height: "12px",
- width: "12px",
- },
- },
-
- "& .cur-plan-text": {
- fontSize: "12px",
- textTransform: "uppercase",
- },
-
- "@media (max-width: 600px)": {
- cursor: "pointer",
- "& .title-block": {
- "& .title": {
- fontSize: "14px",
- fontWeight: 600,
- },
- },
- },
-
- "&.active, &.active.xs-active": {
- color: "#ffffff",
- position: "relative",
-
- "& .min-icon": {
- fill: "#ffffff",
- },
-
- "&:before": {
- content: "' '",
- position: "absolute",
- width: "100%",
- height: "18px",
- backgroundColor: "#2781B0",
- display: "block",
- top: -16,
- },
- "& .iconContainer": {
- "& .min-icon": {
- marginTop: "-12px",
- },
- },
- },
- "&.active": {
- background: "#2781B0",
- color: "#ffffff",
- },
- "&.xs-active": {
- background: "#eaeaea",
- },
- }}
>
{children}
-
+
);
};
@@ -344,29 +499,7 @@ const LicensePlans = ({ licenseInfo }: IRegisterStatus) => {
const featureList = FEATURE_ITEMS;
return (
-
+
{
borderBottom: "8px solid rgb(6 48 83)",
}}
/>
-
+
{featureList.map((fi) => {
const featureTitleRow = fi.featureTitleRow;
@@ -732,8 +740,8 @@ const LicensePlans = ({ licenseInfo }: IRegisterStatus) => {
)}
-
-
+
+
);
};
diff --git a/portal-ui/src/screens/LoginPage/loginThunks.ts b/portal-ui/src/screens/LoginPage/loginThunks.ts
index 2e8cdeb6c..ad6782899 100644
--- a/portal-ui/src/screens/LoginPage/loginThunks.ts
+++ b/portal-ui/src/screens/LoginPage/loginThunks.ts
@@ -16,12 +16,17 @@
import { createAsyncThunk } from "@reduxjs/toolkit";
import { AppState } from "../../store";
-import { setErrorSnackMessage, userLogged } from "../../systemSlice";
+import {
+ setDarkMode,
+ setErrorSnackMessage,
+ userLogged,
+} from "../../systemSlice";
import { setNavigateTo } from "./loginSlice";
import { getTargetPath } from "./Login";
import { api } from "api";
import { ApiError, LoginRequest } from "api/consoleApi";
import { errorToHandler } from "api/errors";
+import { isDarkModeOn } from "../../utils/stylesUtils";
export const doLoginAsync = createAsyncThunk(
"login/doLoginAsync",
@@ -47,10 +52,13 @@ export const doLoginAsync = createAsyncThunk(
return api.login
.login(payload)
.then((res) => {
+ const darkModeEnabled = isDarkModeOn(); // If null, then we set the dark mode as disabled per requirement. If configuration al ready set, then we establish this configuration
+
// We set the state in redux
dispatch(userLogged(true));
localStorage.setItem("userLoggedIn", accessKey);
dispatch(setNavigateTo(getTargetPath()));
+ dispatch(setDarkMode(!!darkModeEnabled));
})
.catch(async (res) => {
const err = (await res.json()) as ApiError;
diff --git a/portal-ui/src/systemSlice.ts b/portal-ui/src/systemSlice.ts
index c4da657f7..abefbd4f3 100644
--- a/portal-ui/src/systemSlice.ts
+++ b/portal-ui/src/systemSlice.ts
@@ -18,6 +18,7 @@ import { snackBarMessage, SRInfoStateType } from "./types";
import { ErrorResponseHandler, IEmbeddedCustomStyles } from "./common/types";
import { AppState } from "./store";
import { SubnetInfo } from "./screens/Console/License/types";
+import { isDarkModeOn } from "./utils/stylesUtils";
// determine whether we have the sidebar state stored on localstorage
const initSideBarOpen = localStorage.getItem("sidebarOpen")
@@ -45,6 +46,7 @@ export interface SystemState {
helpName: string;
helpTabName: string;
locationPath: string;
+ darkMode: boolean;
}
const initialState: SystemState = {
@@ -76,6 +78,7 @@ const initialState: SystemState = {
helpName: "help",
helpTabName: "docs",
locationPath: "",
+ darkMode: isDarkModeOn(),
};
export const systemSlice = createSlice({
@@ -174,6 +177,9 @@ export const systemSlice = createSlice({
setLocationPath: (state, action: PayloadAction) => {
state.locationPath = action.payload;
},
+ setDarkMode: (state, action: PayloadAction) => {
+ state.darkMode = action.payload;
+ },
resetSystem: () => {
return initialState;
},
@@ -203,6 +209,7 @@ export const {
setHelpName,
setHelpTabName,
setLocationPath,
+ setDarkMode,
} = systemSlice.actions;
export const selDistSet = (state: AppState) => state.system.distributedSetup;
diff --git a/portal-ui/src/utils/stylesUtils.ts b/portal-ui/src/utils/stylesUtils.ts
index 66090a4bb..41836367a 100644
--- a/portal-ui/src/utils/stylesUtils.ts
+++ b/portal-ui/src/utils/stylesUtils.ts
@@ -15,6 +15,7 @@
// along with this program. If not, see .
import { IEmbeddedCustomStyles } from "../common/types";
+import get from "lodash/get";
export const getOverrideColorVariants: (
customStyles: string,
@@ -279,3 +280,18 @@ export const generateOverrideTheme = (overrideVars: IEmbeddedCustomStyles) => {
return retVal;
};
+
+export const isDarkModeOn = () => {
+ const darkMode = localStorage.getItem("dark-mode");
+
+ if (!darkMode) {
+ const systemDarkMode = window.matchMedia("(prefers-color-scheme: dark)");
+ return get(systemDarkMode, "matches", false);
+ }
+
+ return darkMode === "on";
+};
+
+export const storeDarkMode = (mode: "on" | "off") => {
+ localStorage.setItem("dark-mode", mode);
+};
diff --git a/portal-ui/yarn.lock b/portal-ui/yarn.lock
index cd8ad4898..ed9839f5e 100644
--- a/portal-ui/yarn.lock
+++ b/portal-ui/yarn.lock
@@ -8038,9 +8038,9 @@ mdn-data@2.0.4:
resolved "https://registry.yarnpkg.com/mdn-data/-/mdn-data-2.0.4.tgz#699b3c38ac6f1d728091a64650b65d388502fd5b"
integrity sha512-iV3XNKw06j5Q7mi6h+9vbx23Tv7JkjEVgKHW4pimwyDGWm0OIQntJJ+u1C6mg6mK1EaTv42XQ7w76yuzH7M2cA==
-"mds@https://github.com/minio/mds.git#v0.12.1":
- version "0.12.1"
- resolved "https://github.com/minio/mds.git#4c4b18d022eed7c120a044709e677b25bc4500d7"
+"mds@https://github.com/minio/mds.git#v0.12.2":
+ version "0.12.2"
+ resolved "https://github.com/minio/mds.git#82b14ea9544079a24db31e5cff477e3fc56c4e39"
dependencies:
"@types/styled-components" "^5.1.30"
"@uiw/react-textarea-code-editor" "^2.1.9"