From 3a0e4c4f8c575ce316a5df0115a6503e53b480b8 Mon Sep 17 00:00:00 2001
From: Daniel Valdivia <18384552+dvaldivia@users.noreply.github.com>
Date: Wed, 25 Jan 2023 13:18:41 -0800
Subject: [PATCH] License Meta Tag (#2601)
Introduces a meta tag `minio-license` so that we can render the proper logo on login and menu
Signed-off-by: Daniel Valdivia <18384552+dvaldivia@users.noreply.github.com>
---
portal-ui/public/index.html | 21 ++++---
portal-ui/src/config.ts | 40 ++++++++++++
.../src/screens/Console/Menu/MenuToggle.tsx | 59 ++---------------
portal-ui/src/screens/LoginPage/LoginPage.tsx | 15 +++--
restapi/admin_subnet.go | 8 ++-
restapi/configure_console.go | 12 ++++
restapi/license.go | 63 +++++++++++++++++++
7 files changed, 148 insertions(+), 70 deletions(-)
create mode 100644 portal-ui/src/config.ts
create mode 100644 restapi/license.go
diff --git a/portal-ui/public/index.html b/portal-ui/public/index.html
index 5ed8e2c40..a16ca3d5f 100644
--- a/portal-ui/public/index.html
+++ b/portal-ui/public/index.html
@@ -15,10 +15,11 @@
name="theme-color"
/>
+
+ manifest.json provides metadata used when your web app is installed on a
+ user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
+-->
+ Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
+ work correctly both with client-side routing and a non-root public URL.
+ Learn how to configure a non-root public URL by running `npm run build`.
+-->
MinIO Console
diff --git a/portal-ui/src/config.ts b/portal-ui/src/config.ts
new file mode 100644
index 000000000..cd745e561
--- /dev/null
+++ b/portal-ui/src/config.ts
@@ -0,0 +1,40 @@
+// 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 .
+
+export const MinIOPlan =
+ (
+ document.head.querySelector(
+ "[name~=minio-license][content]"
+ ) as HTMLMetaElement
+ )?.content || "AGPL";
+
+type LogoVar = "simple" | "AGPL" | "standard" | "enterprise";
+
+export const getLogoVar = (): LogoVar => {
+ let logoVar: LogoVar = "AGPL";
+ switch (MinIOPlan) {
+ case "enterprise":
+ logoVar = "enterprise";
+ break;
+ case "STANDARD":
+ logoVar = "standard";
+ break;
+ default:
+ logoVar = "AGPL";
+ break;
+ }
+ return logoVar;
+};
diff --git a/portal-ui/src/screens/Console/Menu/MenuToggle.tsx b/portal-ui/src/screens/Console/Menu/MenuToggle.tsx
index c6c5768d8..00e88746e 100644
--- a/portal-ui/src/screens/Console/Menu/MenuToggle.tsx
+++ b/portal-ui/src/screens/Console/Menu/MenuToggle.tsx
@@ -14,21 +14,14 @@
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see .
-import React, { Fragment, Suspense, useEffect } from "react";
-import { ApplicationLogo } from "mds";
-
-import { VersionIcon } from "mds";
+import React, { Fragment, Suspense } from "react";
+import { ApplicationLogo, VersionIcon } from "mds";
import { Box, IconButton } from "@mui/material";
import MenuIcon from "@mui/icons-material/Menu";
import { useSelector } from "react-redux";
-import useApi from "../Common/Hooks/useApi";
-import {
- selDirectPVMode,
- selOpMode,
- setLicenseInfo,
-} from "../../../systemSlice";
-import { AppState, useAppDispatch } from "../../../store";
+import { selDirectPVMode, selOpMode } from "../../../systemSlice";
import TooltipWrapper from "../Common/TooltipWrapper/TooltipWrapper";
+import { getLogoVar } from "../../../config";
type MenuToggleProps = {
isOpen: boolean;
@@ -37,43 +30,10 @@ type MenuToggleProps = {
const MenuToggle = ({ isOpen, onToggle }: MenuToggleProps) => {
const stateClsName = isOpen ? "wide" : "mini";
- const dispatch = useAppDispatch();
-
- const licenseInfo = useSelector(
- (state: AppState) => state?.system?.licenseInfo
- );
const operatorMode = useSelector(selOpMode);
-
const directPVMode = useSelector(selDirectPVMode);
- const [isLicenseLoading, invokeLicenseInfoApi] = useApi(
- (res: any) => {
- dispatch(setLicenseInfo(res));
- },
- () => {
- dispatch(setLicenseInfo(null));
- }
- );
-
- //Get License info from SUBNET
- useEffect(() => {
- if (!operatorMode) {
- invokeLicenseInfoApi("GET", `/api/v1/subnet/info`);
- }
- // eslint-disable-next-line react-hooks/exhaustive-deps
- }, []);
-
- const { plan = "" } = licenseInfo || {};
-
- let logoPlan = "simple";
-
- if (!isLicenseLoading) {
- if (plan === "STANDARD" || plan === "ENTERPRISE") {
- logoPlan = plan.toLowerCase();
- } else {
- logoPlan = "AGPL";
- }
- }
+ let logoPlan = getLogoVar();
return (
{
diff --git a/portal-ui/src/screens/LoginPage/LoginPage.tsx b/portal-ui/src/screens/LoginPage/LoginPage.tsx
index 804db6e0e..76262435c 100644
--- a/portal-ui/src/screens/LoginPage/LoginPage.tsx
+++ b/portal-ui/src/screens/LoginPage/LoginPage.tsx
@@ -22,17 +22,22 @@ import {
MenuItem,
Select,
} from "@mui/material";
-import { Button, LoginWrapper } from "mds";
+import {
+ Button,
+ Loader,
+ LockIcon,
+ LoginWrapper,
+ LogoutIcon,
+ RefreshIcon,
+} from "mds";
import { Theme } from "@mui/material/styles";
import createStyles from "@mui/styles/createStyles";
import makeStyles from "@mui/styles/makeStyles";
import Grid from "@mui/material/Grid";
import { loginStrategyType, redirectRule } from "./types";
import MainError from "../Console/Common/MainError/MainError";
-import { LockIcon, LogoutIcon, RefreshIcon } from "mds";
import { spacingUtils } from "../Console/Common/FormComponents/common/styleLibrary";
import clsx from "clsx";
-import { Loader } from "mds";
import { AppState, useAppDispatch } from "../../store";
import { useSelector } from "react-redux";
import {
@@ -44,6 +49,7 @@ import { resetForm, setJwt } from "./loginSlice";
import StrategyForm from "./StrategyForm";
import { LoginField } from "./LoginField";
import { redirectRules } from "../../utils/sortFunctions";
+import { getLogoVar } from "../../config";
const useStyles = makeStyles((theme: Theme) =>
createStyles({
@@ -479,6 +485,7 @@ const Login = () => {
let modeLogo: "console" | "directpv" | "operator" | "kes" | "subnet" =
"console";
+ const logoVar = getLogoVar();
if (isDirectPV) {
modeLogo = "directpv";
@@ -496,7 +503,7 @@ const Login = () => {
diff --git a/restapi/admin_subnet.go b/restapi/admin_subnet.go
index 497f44736..a8c33a684 100644
--- a/restapi/admin_subnet.go
+++ b/restapi/admin_subnet.go
@@ -62,7 +62,7 @@ func registerSubnetHandlers(api *operations.ConsoleAPI) {
})
// Get subnet info
api.SubnetSubnetInfoHandler = subnetApi.SubnetInfoHandlerFunc(func(params subnetApi.SubnetInfoParams, session *models.Principal) middleware.Responder {
- resp, err := GetSubnetInfoResponse(session, params)
+ resp, err := GetSubnetInfoResponse(params)
if err != nil {
return subnetApi.NewSubnetInfoDefault(int(err.Code)).WithPayload(err)
}
@@ -86,6 +86,8 @@ func registerSubnetHandlers(api *operations.ConsoleAPI) {
})
}
+const EnvSubnetLicense = "CONSOLE_SUBNET_LICENSE"
+
func SubnetRegisterWithAPIKey(ctx context.Context, minioClient MinioAdmin, apiKey string) (bool, error) {
serverInfo, err := minioClient.serverInfo(ctx)
if err != nil {
@@ -316,14 +318,14 @@ func subnetRegisterResponse(ctx context.Context, minioClient MinioAdmin, params
return nil
}
-func GetSubnetInfoResponse(session *models.Principal, params subnetApi.SubnetInfoParams) (*models.License, *models.Error) {
+func GetSubnetInfoResponse(params subnetApi.SubnetInfoParams) (*models.License, *models.Error) {
ctx, cancel := context.WithCancel(params.HTTPRequest.Context())
defer cancel()
client := &xhttp.Client{
Client: GetConsoleHTTPClient(""),
}
- licenseInfo, err := subnet.ParseLicense(client, os.Getenv("CONSOLE_SUBNET_LICENSE"))
+ licenseInfo, err := subnet.ParseLicense(client, os.Getenv(EnvSubnetLicense))
if err != nil {
return nil, ErrorWithContext(ctx, err)
}
diff --git a/restapi/configure_console.go b/restapi/configure_console.go
index f058d822c..3b0993b63 100644
--- a/restapi/configure_console.go
+++ b/restapi/configure_console.go
@@ -164,6 +164,9 @@ func configureAPI(api *operations.ConsoleAPI) http.Handler {
api.ServerShutdown = func() {}
+ // do an initial subnet plan caching
+ fetchLicensePlan()
+
return setupGlobalMiddleware(api.Serve(setupMiddlewares))
}
@@ -408,6 +411,7 @@ func handleSPA(w http.ResponseWriter, r *http.Request) {
} else if getSubPath() != "/" {
indexPageBytes = replaceBaseInIndex(indexPageBytes, getSubPath())
}
+ indexPageBytes = replaceLicense(indexPageBytes)
mimeType := mimedb.TypeByExtension(filepath.Ext(r.URL.Path))
@@ -490,6 +494,14 @@ func replaceBaseInIndex(indexPageBytes []byte, basePath string) []byte {
return indexPageBytes
}
+func replaceLicense(indexPageBytes []byte) []byte {
+ indexPageStr := string(indexPageBytes)
+ newPlan := fmt.Sprintf("", InstanceLicensePlan.String())
+ indexPageStr = strings.Replace(indexPageStr, "", newPlan, 1)
+ indexPageBytes = []byte(indexPageStr)
+ return indexPageBytes
+}
+
func requestBounce(handler http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if strings.HasSuffix(r.URL.Path, "/") {
diff --git a/restapi/license.go b/restapi/license.go
new file mode 100644
index 000000000..89ca6ff4f
--- /dev/null
+++ b/restapi/license.go
@@ -0,0 +1,63 @@
+// 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 .
+
+package restapi
+
+import (
+ "os"
+
+ "github.com/minio/console/pkg/http"
+ "github.com/minio/console/pkg/subnet"
+)
+
+type SubnetPlan int
+
+const (
+ PlanAGPL SubnetPlan = iota
+ PlanStandard
+ PlanEnterprise
+)
+
+func (sp SubnetPlan) String() string {
+ switch sp {
+ case PlanStandard:
+ return "standard"
+ case PlanEnterprise:
+ return "enterprise"
+ default:
+ return "agpl"
+ }
+}
+
+var InstanceLicensePlan = PlanAGPL
+
+func fetchLicensePlan() {
+ client := &http.Client{
+ Client: GetConsoleHTTPClient(""),
+ }
+ licenseInfo, err := subnet.ParseLicense(client, os.Getenv(EnvSubnetLicense))
+ if err != nil {
+ return
+ }
+ switch licenseInfo.Plan {
+ case "STANDARD":
+ InstanceLicensePlan = PlanStandard
+ case "ENTERPRISE":
+ InstanceLicensePlan = PlanEnterprise
+ default:
+ InstanceLicensePlan = PlanAGPL
+ }
+}