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:
@@ -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
40
portal-ui/src/config.ts
Normal 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;
|
||||
};
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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
63
restapi/license.go
Normal 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
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user