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 + } +}