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>
This commit is contained in:
Daniel Valdivia
2023-01-25 13:18:41 -08:00
committed by GitHub
parent b34b05f059
commit 3a0e4c4f8c
7 changed files with 148 additions and 70 deletions

View File

@@ -15,10 +15,11 @@
name="theme-color"
/>
<meta content="MinIO Console" name="description" />
<meta name="minio-license" content="apgl" />
<!--
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/
-->
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/
-->
<link href="%PUBLIC_URL%/styles/root-styles.css" rel="stylesheet" />
<link
href="%PUBLIC_URL%/apple-icon-180x180.png"
@@ -50,14 +51,14 @@
rel="mask-icon"
/>
<!--
Notice the use of %PUBLIC_URL% in the tags above.
It will be replaced with the URL of the `public` folder during the build.
Only files inside the `public` folder can be referenced from the HTML.
Notice the use of %PUBLIC_URL% in the tags above.
It will be replaced with the URL of the `public` folder during the build.
Only files inside the `public` folder can be referenced from the HTML.
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`.
-->
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`.
-->
<title>MinIO Console</title>
</head>
<body>

40
portal-ui/src/config.ts Normal file
View File

@@ -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 <http://www.gnu.org/licenses/>.
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;
};

View File

@@ -14,21 +14,14 @@
// 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, 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 (
<Box
@@ -151,14 +111,7 @@ const MenuToggle = ({ isOpen, onToggle }: MenuToggleProps) => {
<Fragment>
<ApplicationLogo
applicationName={"console"}
subVariant={
logoPlan as
| "AGPL"
| "simple"
| "standard"
| "enterprise"
| undefined
}
subVariant={logoPlan}
inverse
/>
</Fragment>

View File

@@ -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 = () => {
<Fragment>
<MainError />
<LoginWrapper
logoProps={{ applicationName: modeLogo }}
logoProps={{ applicationName: modeLogo, subVariant: logoVar }}
form={loginComponent}
formFooter={
<Fragment>

View File

@@ -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)
}

View File

@@ -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("<meta name=\"minio-license\" content=\"%s\" />", InstanceLicensePlan.String())
indexPageStr = strings.Replace(indexPageStr, "<meta name=\"minio-license\" content=\"apgl\"/>", 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, "/") {

63
restapi/license.go Normal file
View File

@@ -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 <http://www.gnu.org/licenses/>.
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
}
}