License Page UI Updates (#1444)
* License Page UI Updates Signed-off-by: Daniel Valdivia <18384552+dvaldivia@users.noreply.github.com> * Lint Signed-off-by: Daniel Valdivia <18384552+dvaldivia@users.noreply.github.com>
This commit is contained in:
@@ -20,15 +20,9 @@ import (
|
||||
"errors"
|
||||
"log"
|
||||
|
||||
"github.com/minio/pkg/env"
|
||||
"github.com/minio/pkg/licverifier"
|
||||
)
|
||||
|
||||
// GetSubnetURL
|
||||
func GetSubnetURL() string {
|
||||
return env.Get(ConsoleSubnetURL, "https://subnet.min.io")
|
||||
}
|
||||
|
||||
// GetLicenseInfoFromJWT will return license metadata from a jwt string license
|
||||
func GetLicenseInfoFromJWT(license string, publicKeys []string) (*licverifier.LicenseInfo, error) {
|
||||
if license == "" {
|
||||
|
||||
@@ -18,10 +18,14 @@
|
||||
package subnet
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"github.com/minio/pkg/licverifier"
|
||||
|
||||
"github.com/minio/console/models"
|
||||
"github.com/minio/madmin-go"
|
||||
mc "github.com/minio/mc/cmd"
|
||||
@@ -81,7 +85,12 @@ func GetOrganizations(client cluster.HTTPClientI, token string) ([]*models.Subne
|
||||
return organizations, nil
|
||||
}
|
||||
|
||||
func Register(client cluster.HTTPClientI, admInfo madmin.InfoMessage, apiKey, token, accountID string) (string, error) {
|
||||
type LicenseTokenConfig struct {
|
||||
APIKey string
|
||||
License string
|
||||
}
|
||||
|
||||
func Register(client cluster.HTTPClientI, admInfo madmin.InfoMessage, apiKey, token, accountID string) (*LicenseTokenConfig, error) {
|
||||
var headers map[string]string
|
||||
regInfo := GetClusterRegInfo(admInfo)
|
||||
regURL := subnetRegisterURL()
|
||||
@@ -89,23 +98,71 @@ func Register(client cluster.HTTPClientI, admInfo madmin.InfoMessage, apiKey, to
|
||||
regURL += "?api_key=" + apiKey
|
||||
} else {
|
||||
if accountID == "" || token == "" {
|
||||
return "", errors.New("missing accountID or authentication token")
|
||||
return nil, errors.New("missing accountID or authentication token")
|
||||
}
|
||||
headers = subnetAuthHeaders(token)
|
||||
regURL += "?aid=" + accountID
|
||||
}
|
||||
regToken, err := GenerateRegToken(regInfo)
|
||||
if err != nil {
|
||||
return "", err
|
||||
return nil, err
|
||||
}
|
||||
reqPayload := mc.ClusterRegistrationReq{Token: regToken}
|
||||
resp, err := subnetPostReq(client, regURL, reqPayload, headers)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
respJSON := gjson.Parse(resp)
|
||||
subnetAPIKey := respJSON.Get("api_key").String()
|
||||
licenseJwt := respJSON.Get("license").String()
|
||||
|
||||
if subnetAPIKey != "" {
|
||||
return &LicenseTokenConfig{
|
||||
APIKey: subnetAPIKey,
|
||||
License: licenseJwt,
|
||||
}, nil
|
||||
}
|
||||
return nil, errors.New("subnet api key not found")
|
||||
}
|
||||
|
||||
const publicKey = "/downloads/license-pubkey.pem"
|
||||
|
||||
// downloadSubnetPublicKey will download the current subnet public key.
|
||||
func downloadSubnetPublicKey(client cluster.HTTPClientI) (string, error) {
|
||||
// Get the public key directly from Subnet
|
||||
url := fmt.Sprintf("%s%s", subnetBaseURL(), publicKey)
|
||||
resp, err := client.Get(url)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
subnetAPIKey := gjson.Parse(resp).Get("api_key").String()
|
||||
if subnetAPIKey != "" {
|
||||
return subnetAPIKey, nil
|
||||
defer resp.Body.Close()
|
||||
buf := new(bytes.Buffer)
|
||||
_, err = buf.ReadFrom(resp.Body)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return "", errors.New("subnet api key not found")
|
||||
return buf.String(), err
|
||||
}
|
||||
|
||||
// ParseLicense parses the license with the bundle public key and return it's information
|
||||
func ParseLicense(client cluster.HTTPClientI, license string) (*licverifier.LicenseInfo, error) {
|
||||
var publicKeys []string
|
||||
|
||||
subnetPubKey, err := downloadSubnetPublicKey(client)
|
||||
if err != nil {
|
||||
log.Print(err)
|
||||
// there was an issue getting the subnet public key
|
||||
// use hardcoded public keys instead
|
||||
publicKeys = OfflinePublicKeys
|
||||
} else {
|
||||
publicKeys = append(publicKeys, subnetPubKey)
|
||||
}
|
||||
|
||||
licenseInfo, err := GetLicenseInfoFromJWT(license, publicKeys)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return licenseInfo, nil
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
377
portal-ui/src/screens/Console/License/LicensePlans.tsx
Normal file
377
portal-ui/src/screens/Console/License/LicensePlans.tsx
Normal file
@@ -0,0 +1,377 @@
|
||||
// This file is part of MinIO Console Server
|
||||
// Copyright (c) 2022 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/>.
|
||||
|
||||
import React, { Fragment } from "react";
|
||||
import Grid from "@mui/material/Grid";
|
||||
import clsx from "clsx";
|
||||
import ActivationModal from "./ActivationModal";
|
||||
import { planButtons, planDetails, planItems } from "./utils";
|
||||
import CheckCircleIcon from "@mui/icons-material/CheckCircle";
|
||||
import Button from "@mui/material/Button";
|
||||
import { Theme } from "@mui/material/styles";
|
||||
import createStyles from "@mui/styles/createStyles";
|
||||
import { SubnetInfo } from "./types";
|
||||
import withStyles from "@mui/styles/withStyles";
|
||||
|
||||
const styles = (theme: Theme) =>
|
||||
createStyles({
|
||||
planItemsPadding: {
|
||||
border: "1px solid #EAEDEE",
|
||||
borderTop: 0,
|
||||
maxWidth: 1180,
|
||||
},
|
||||
planItemsBorder: {
|
||||
height: 7,
|
||||
backgroundColor: "#07193E",
|
||||
},
|
||||
|
||||
link: {
|
||||
textDecoration: "underline !important",
|
||||
color: theme.palette.info.main,
|
||||
},
|
||||
linkButton: {
|
||||
fontFamily: '"Lato", sans-serif',
|
||||
fontWeight: "normal",
|
||||
textTransform: "none",
|
||||
fontSize: "inherit",
|
||||
height: 0,
|
||||
padding: 0,
|
||||
margin: 0,
|
||||
},
|
||||
tableContainer: {
|
||||
marginLeft: 28,
|
||||
},
|
||||
detailsContainerBorder: {
|
||||
borderLeft: "1px solid #e2e2e2",
|
||||
},
|
||||
detailsTitle: {
|
||||
fontSize: 19,
|
||||
fontWeight: 700,
|
||||
marginBottom: 26,
|
||||
paddingTop: 18,
|
||||
lineHeight: 1,
|
||||
},
|
||||
currPlan: {
|
||||
color: "white",
|
||||
backgroundColor: "#4CCB92",
|
||||
},
|
||||
planHeader: {
|
||||
padding: 8,
|
||||
},
|
||||
detailsPrice: {
|
||||
fontSize: 13,
|
||||
fontWeight: 700,
|
||||
},
|
||||
detailsCapacityMax: {
|
||||
minHeight: 28,
|
||||
fontSize: 10,
|
||||
},
|
||||
itemContainer: {
|
||||
height: 36,
|
||||
"& .item:last-child": {
|
||||
borderRight: "1px solid #e5e5e5",
|
||||
},
|
||||
},
|
||||
itemContainerDetail: {
|
||||
height: 48,
|
||||
},
|
||||
item: {
|
||||
height: "100%",
|
||||
borderLeft: "1px solid #e5e5e5",
|
||||
textAlign: "center",
|
||||
fontSize: 10,
|
||||
fontWeight: 700,
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
alignContent: "center",
|
||||
borderTop: "1px solid #e5e5e5",
|
||||
},
|
||||
|
||||
itemFirst: {
|
||||
borderLeft: 0,
|
||||
borderRight: 0,
|
||||
},
|
||||
field: {
|
||||
textAlign: "left",
|
||||
fontWeight: 400,
|
||||
fontSize: 12,
|
||||
},
|
||||
checkIcon: {
|
||||
fontSize: 15,
|
||||
color: "#385973",
|
||||
},
|
||||
buttonContainer: {
|
||||
paddingTop: 8,
|
||||
paddingBottom: 24,
|
||||
height: "100%",
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
borderLeft: "1px solid #e2e2e2",
|
||||
},
|
||||
buttonContainerBlank: {
|
||||
border: 0,
|
||||
},
|
||||
buttonContainerHighlighted: {
|
||||
borderTop: 0,
|
||||
},
|
||||
button: {
|
||||
textTransform: "none",
|
||||
fontSize: 15,
|
||||
fontWeight: 700,
|
||||
},
|
||||
activateLink: {
|
||||
color: "#1C5A8D",
|
||||
fontWeight: "bold",
|
||||
clear: "both",
|
||||
background: "none",
|
||||
border: "none",
|
||||
textDecoration: "underline",
|
||||
cursor: "pointer",
|
||||
},
|
||||
currentPlanBG: {
|
||||
background: "#022A4A 0% 0% no-repeat padding-box",
|
||||
color: "#FFFFFF",
|
||||
borderTop: "1px solid #52687d",
|
||||
},
|
||||
});
|
||||
|
||||
interface IRegisterStatus {
|
||||
classes: any;
|
||||
activateProductModal: any;
|
||||
closeModalAndFetchLicenseInfo: any;
|
||||
licenseInfo: SubnetInfo | undefined;
|
||||
setLicenseModal: React.Dispatch<React.SetStateAction<boolean>>;
|
||||
operatorMode: boolean;
|
||||
currentPlanID: number;
|
||||
setActivateProductModal: any;
|
||||
}
|
||||
|
||||
const LicensePlans = ({
|
||||
classes,
|
||||
activateProductModal,
|
||||
closeModalAndFetchLicenseInfo,
|
||||
licenseInfo,
|
||||
setLicenseModal,
|
||||
operatorMode,
|
||||
currentPlanID,
|
||||
setActivateProductModal,
|
||||
}: IRegisterStatus) => {
|
||||
const planDetailsFiltered = planDetails.filter((item) => {
|
||||
if (licenseInfo) {
|
||||
if (item.title === "Community") {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
||||
const planButtonsFiltered = planButtons.filter((item) => {
|
||||
if (licenseInfo) {
|
||||
if (item.plan === "Community") {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
||||
const gridColWidth = licenseInfo ? 4 : 3;
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<Grid item xs={12}>
|
||||
<div className={classes.planItemsBorder} />
|
||||
</Grid>
|
||||
<Grid item xs={12} className={clsx(classes.planItemsPadding)}>
|
||||
<Grid container>
|
||||
<ActivationModal
|
||||
open={activateProductModal}
|
||||
closeModal={() => closeModalAndFetchLicenseInfo()}
|
||||
/>
|
||||
<Grid container item xs={12} className={classes.tableContainer}>
|
||||
<Grid container item xs={12}>
|
||||
<Grid item xs={gridColWidth} />
|
||||
{planDetailsFiltered.map((details: any) => {
|
||||
let currentPlan =
|
||||
(!licenseInfo && details.title === "Community") ||
|
||||
(licenseInfo &&
|
||||
licenseInfo.plan.toLowerCase() ===
|
||||
details.title.toLowerCase());
|
||||
return (
|
||||
<Grid
|
||||
key={details.id}
|
||||
container
|
||||
item
|
||||
xs={gridColWidth}
|
||||
justifyContent={"center"}
|
||||
className={clsx(classes.detailsContainerBorder, {
|
||||
[classes.currPlan]: currentPlan,
|
||||
})}
|
||||
>
|
||||
<Grid item xs={10} className={classes.planHeader}></Grid>
|
||||
<Grid item xs={10} className={classes.detailsTitle}>
|
||||
{details.title}
|
||||
{currentPlan && (
|
||||
<Fragment>
|
||||
<div style={{ fontSize: 10 }}>CURRENT PLAN</div>
|
||||
</Fragment>
|
||||
)}
|
||||
</Grid>
|
||||
<Grid item xs={10} className={classes.detailsPrice}>
|
||||
{details.price}
|
||||
</Grid>
|
||||
<Grid item xs={10} className={classes.detailsCapacityMax}>
|
||||
{details.capacityMax || ""}
|
||||
</Grid>
|
||||
</Grid>
|
||||
);
|
||||
})}
|
||||
</Grid>
|
||||
{planItems.map((item: any) => {
|
||||
return (
|
||||
<Grid
|
||||
key={item.id}
|
||||
container
|
||||
item
|
||||
xs={12}
|
||||
className={clsx(
|
||||
classes.itemContainer,
|
||||
item.communityDetail && classes.itemContainerDetail
|
||||
)}
|
||||
>
|
||||
<Grid
|
||||
item
|
||||
xs={gridColWidth}
|
||||
className={clsx(
|
||||
classes.item,
|
||||
classes.field,
|
||||
classes.itemFirst
|
||||
)}
|
||||
>
|
||||
{item.field}
|
||||
</Grid>
|
||||
{planDetailsFiltered.map((pd) => {
|
||||
return (
|
||||
<Fragment>
|
||||
<Grid
|
||||
container
|
||||
item
|
||||
xs={gridColWidth}
|
||||
className={clsx(classes.item)}
|
||||
>
|
||||
<Grid item xs={12}>
|
||||
{item.plans[pd.title].label === "N/A" ? (
|
||||
""
|
||||
) : item.plans[pd.title].label === "Yes" ? (
|
||||
<CheckCircleIcon className={classes.checkIcon} />
|
||||
) : (
|
||||
<Fragment>
|
||||
{item.plans[pd.title].link ? (
|
||||
<Button
|
||||
variant="text"
|
||||
color="primary"
|
||||
size="small"
|
||||
className={clsx(
|
||||
classes.link,
|
||||
classes.linkButton
|
||||
)}
|
||||
onClick={() => setLicenseModal(true)}
|
||||
>
|
||||
{item.plans[pd.title].label}
|
||||
</Button>
|
||||
) : (
|
||||
item.plans[pd.title].label
|
||||
)}
|
||||
</Fragment>
|
||||
)}
|
||||
</Grid>
|
||||
{item.plans[pd.title].detail !== undefined && (
|
||||
<Grid item xs={12}>
|
||||
{item.plans[pd.title].detail}
|
||||
</Grid>
|
||||
)}
|
||||
</Grid>
|
||||
</Fragment>
|
||||
);
|
||||
})}
|
||||
</Grid>
|
||||
);
|
||||
})}
|
||||
<Grid container item xs={12}>
|
||||
<Grid
|
||||
item
|
||||
xs={gridColWidth}
|
||||
className={clsx(
|
||||
classes.buttonContainer,
|
||||
classes.buttonContainerBlank
|
||||
)}
|
||||
/>
|
||||
{planButtonsFiltered.map((button: any, index: any) => {
|
||||
return (
|
||||
<Grid
|
||||
key={button.id}
|
||||
container
|
||||
item
|
||||
xs={gridColWidth}
|
||||
style={{ textAlign: "center" }}
|
||||
className={clsx(classes.buttonContainer, {
|
||||
[classes.buttonContainerHighlighted]:
|
||||
button.text === "Subscribe",
|
||||
})}
|
||||
>
|
||||
<Grid item xs={12}>
|
||||
<Button
|
||||
variant={
|
||||
button.text === "Join Slack"
|
||||
? "outlined"
|
||||
: "contained"
|
||||
}
|
||||
color="primary"
|
||||
className={clsx(classes.button)}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
href="#"
|
||||
disabled={
|
||||
licenseInfo &&
|
||||
licenseInfo.plan.toLowerCase() ===
|
||||
button.plan.toLowerCase()
|
||||
}
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
window.open(
|
||||
`${button.link}/?ref=${
|
||||
operatorMode ? "op" : "con"
|
||||
}`,
|
||||
"_blank"
|
||||
);
|
||||
}}
|
||||
>
|
||||
{currentPlanID !== index && index > 0
|
||||
? button.text2
|
||||
: button.text}
|
||||
</Button>
|
||||
</Grid>
|
||||
</Grid>
|
||||
);
|
||||
})}
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
export default withStyles(styles)(LicensePlans);
|
||||
@@ -14,12 +14,20 @@
|
||||
// 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 planDetails = [
|
||||
export interface IPlanDetails {
|
||||
id: number;
|
||||
title: string;
|
||||
price: string;
|
||||
capacityMax: string;
|
||||
capacityMin?: string;
|
||||
}
|
||||
|
||||
export const planDetails: IPlanDetails[] = [
|
||||
{
|
||||
id: 0,
|
||||
title: "Community",
|
||||
price: "N/A",
|
||||
capacityMax: "Open Source",
|
||||
price: "Open Source",
|
||||
capacityMax: "",
|
||||
},
|
||||
{
|
||||
id: 1,
|
||||
@@ -37,113 +45,221 @@ export const planDetails = [
|
||||
},
|
||||
];
|
||||
|
||||
export const planItems = [
|
||||
export interface IPlanItemValue {
|
||||
label: string;
|
||||
detail?: string;
|
||||
link?: boolean;
|
||||
}
|
||||
|
||||
export interface IPlanItemValues {
|
||||
[index: string]: IPlanItemValue;
|
||||
}
|
||||
export interface IPlanItem {
|
||||
id: number;
|
||||
field: string;
|
||||
plans: IPlanItemValues;
|
||||
}
|
||||
|
||||
export const planItems: IPlanItem[] = [
|
||||
{
|
||||
id: 0,
|
||||
field: "License",
|
||||
community: "GNU AGPL v3",
|
||||
communityLink: true,
|
||||
communityDetail: "",
|
||||
standard: "Commercial License",
|
||||
standardDetail: "",
|
||||
enterprise: "Commercial License",
|
||||
enterpriseDetail: "",
|
||||
plans: {
|
||||
Community: {
|
||||
label: "GNU AGPL v3",
|
||||
detail: "",
|
||||
link: true,
|
||||
},
|
||||
Standard: {
|
||||
label: "Commercial License",
|
||||
},
|
||||
Enterprise: {
|
||||
label: "Commercial License",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 1,
|
||||
field: "Software Release",
|
||||
community: "Upstream",
|
||||
standard: "1 Year Long Term Support",
|
||||
enterprise: "5 Years Long Term Support",
|
||||
plans: {
|
||||
Community: {
|
||||
label: "Upstream",
|
||||
},
|
||||
Standard: {
|
||||
label: "1 Year Long Term Support",
|
||||
},
|
||||
Enterprise: {
|
||||
label: "5 Years Long Term Support",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
field: "SLA",
|
||||
community: "No SLA",
|
||||
standard: "<48 Hours (Local Business Hours)",
|
||||
enterprise: "<1 hour",
|
||||
plans: {
|
||||
Community: {
|
||||
label: "No SLA",
|
||||
},
|
||||
Standard: {
|
||||
label: "<48 Hours (Local Business Hours)",
|
||||
},
|
||||
Enterprise: {
|
||||
label: "<1 hour",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
field: "Support",
|
||||
community: "Community:",
|
||||
communityDetail: "Public Slack Channel + Github Issues",
|
||||
standard: "L4 Direct Engineering",
|
||||
standardDetail: " support via SUBNET",
|
||||
enterprise: "L4 Direct Engineering",
|
||||
enterpriseDetail: "support via SUBNET",
|
||||
plans: {
|
||||
Community: {
|
||||
label: "Community:",
|
||||
detail: "Public Slack Channel + Github Issues",
|
||||
},
|
||||
Standard: {
|
||||
label: "L4 Direct Engineering",
|
||||
detail: " support via SUBNET",
|
||||
},
|
||||
Enterprise: {
|
||||
label: "L4 Direct Engineering",
|
||||
detail: "support via SUBNET",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
field: "Security Updates & Critical Bugs",
|
||||
community: "Self Update",
|
||||
standard: "Continuous Scan and Alert",
|
||||
enterprise: "Continuous Scan and Alert",
|
||||
plans: {
|
||||
Community: {
|
||||
label: "Self Update",
|
||||
},
|
||||
Standard: {
|
||||
label: "Continuous Scan and Alert",
|
||||
},
|
||||
Enterprise: {
|
||||
label: "Continuous Scan and Alert",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
field: "Panic Button",
|
||||
community: "N/A",
|
||||
standard: "1 per year",
|
||||
enterprise: "Unlimited",
|
||||
plans: {
|
||||
Community: {
|
||||
label: "N/A",
|
||||
},
|
||||
Standard: {
|
||||
label: "1 per year",
|
||||
},
|
||||
Enterprise: {
|
||||
label: "Unlimited",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 6,
|
||||
field: "Health Diagnostics",
|
||||
community: "N/A",
|
||||
standard: "24/7/365",
|
||||
enterprise: "24/7/365",
|
||||
plans: {
|
||||
Community: {
|
||||
label: "N/A",
|
||||
},
|
||||
Standard: {
|
||||
label: "24/7/365",
|
||||
},
|
||||
Enterprise: {
|
||||
label: "24/7/365",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 6,
|
||||
field: "Annual Architecture Review",
|
||||
community: "N/A",
|
||||
standard: "N/A",
|
||||
enterprise: "Yes",
|
||||
plans: {
|
||||
Community: {
|
||||
label: "N/A",
|
||||
},
|
||||
Standard: {
|
||||
label: "N/A",
|
||||
},
|
||||
Enterprise: {
|
||||
label: "Yes",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 7,
|
||||
field: "Annual Performance Review",
|
||||
community: "N/A",
|
||||
standard: "N/A",
|
||||
enterprise: "Yes",
|
||||
plans: {
|
||||
Community: {
|
||||
label: "N/A",
|
||||
},
|
||||
Standard: {
|
||||
label: "N/A",
|
||||
},
|
||||
Enterprise: {
|
||||
label: "Yes",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 8,
|
||||
field: "Indemnification",
|
||||
community: "N/A",
|
||||
standard: "N/A",
|
||||
enterprise: "Yes",
|
||||
plans: {
|
||||
Community: {
|
||||
label: "N/A",
|
||||
},
|
||||
Standard: {
|
||||
label: "N/A",
|
||||
},
|
||||
Enterprise: {
|
||||
label: "Yes",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 9,
|
||||
field: "Security + Policy Review",
|
||||
community: "N/A",
|
||||
standard: "N/A",
|
||||
enterprise: "Yes",
|
||||
plans: {
|
||||
Community: {
|
||||
label: "N/A",
|
||||
},
|
||||
Standard: {
|
||||
label: "N/A",
|
||||
},
|
||||
Enterprise: {
|
||||
label: "Yes",
|
||||
},
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
export const planButtons = [
|
||||
export interface IPlanButton {
|
||||
id: number;
|
||||
text: string;
|
||||
text2: string;
|
||||
link: string;
|
||||
plan: string;
|
||||
}
|
||||
export const planButtons: IPlanButton[] = [
|
||||
{
|
||||
id: 0,
|
||||
text: "Join Slack",
|
||||
text2: "",
|
||||
link: "https://slack.min.io",
|
||||
plan: "community",
|
||||
plan: "Community",
|
||||
},
|
||||
{
|
||||
id: 1,
|
||||
text: "Subscribe",
|
||||
text2: "Sign up",
|
||||
link: "https://subnet.min.io/subscription",
|
||||
plan: "standard",
|
||||
plan: "Standard",
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
text: "Subscribe",
|
||||
text2: "Sign up",
|
||||
link: "https://subnet.min.io/subscription",
|
||||
plan: "enterprise",
|
||||
plan: "Enterprise",
|
||||
},
|
||||
];
|
||||
|
||||
@@ -18,8 +18,8 @@ import { Theme } from "@mui/material/styles";
|
||||
import createStyles from "@mui/styles/createStyles";
|
||||
import {
|
||||
actionsTray,
|
||||
searchField,
|
||||
containerForHeader,
|
||||
searchField,
|
||||
} from "../Common/FormComponents/common/styleLibrary";
|
||||
import withStyles from "@mui/styles/withStyles";
|
||||
import { Button, Grid, Link, Typography } from "@mui/material";
|
||||
@@ -30,7 +30,6 @@ import { CopyIcon, UsersIcon } from "../../../icons";
|
||||
import RemoveRedEyeIcon from "@mui/icons-material/RemoveRedEye";
|
||||
import VisibilityOffIcon from "@mui/icons-material/VisibilityOff";
|
||||
import OnlineRegistrationIcon from "../../../icons/OnlineRegistrationIcon";
|
||||
import OfflineRegistrationBackIcon from "../../../icons/OfflineRegistrationBackIcon";
|
||||
import OfflineRegistrationIcon from "../../../icons/OfflineRegistrationIcon";
|
||||
import InputBoxWrapper from "../Common/FormComponents/InputBoxWrapper/InputBoxWrapper";
|
||||
import clsx from "clsx";
|
||||
@@ -55,9 +54,11 @@ import {
|
||||
IAM_PAGES,
|
||||
IAM_PAGES_PERMISSIONS,
|
||||
} from "../../../common/SecureComponent/permissions";
|
||||
import VerifiedIcon from "../../../icons/VerifiedIcon";
|
||||
import { connect } from "react-redux";
|
||||
import { setErrorSnackMessage } from "../../../actions";
|
||||
import HelpBox from "../../../common/HelpBox";
|
||||
import SettingsIcon from "../../../icons/SettingsIcon";
|
||||
import RegisterStatus from "./RegisterStatus";
|
||||
|
||||
interface IRegister {
|
||||
classes: any;
|
||||
@@ -201,7 +202,9 @@ const Register = ({ classes, displayErrorMessage }: IRegister) => {
|
||||
setLoadingLicenseInfo(false);
|
||||
})
|
||||
.catch((err: ErrorResponseHandler) => {
|
||||
displayErrorMessage(err);
|
||||
if (err.errorMessage !== "License not found") {
|
||||
displayErrorMessage(err);
|
||||
}
|
||||
setClusterRegistered(false);
|
||||
setLoadingLicenseInfo(false);
|
||||
});
|
||||
@@ -224,6 +227,7 @@ const Register = ({ classes, displayErrorMessage }: IRegister) => {
|
||||
}
|
||||
})
|
||||
.catch((err: ErrorResponseHandler) => {
|
||||
console.log(err);
|
||||
displayErrorMessage(err);
|
||||
setLoading(false);
|
||||
});
|
||||
@@ -440,7 +444,7 @@ const Register = ({ classes, displayErrorMessage }: IRegister) => {
|
||||
target="_blank"
|
||||
href="https://min.io/product/subnet"
|
||||
>
|
||||
Learn more about SUBNET
|
||||
Learn more about SUBNET.
|
||||
</Link>
|
||||
</Grid>
|
||||
<br />
|
||||
@@ -563,33 +567,14 @@ const Register = ({ classes, displayErrorMessage }: IRegister) => {
|
||||
<PageHeader label="Register" />
|
||||
<PageLayout>
|
||||
<Grid item xs={12} className={classes.boxy}>
|
||||
{clusterRegistered && (
|
||||
<Grid container>
|
||||
<Grid item xs={12} className={classes.registeredStatus}>
|
||||
Register Status:
|
||||
<VerifiedIcon />
|
||||
<span>Registered</span>
|
||||
</Grid>
|
||||
</Grid>
|
||||
)}
|
||||
{clusterRegistered && <RegisterStatus />}
|
||||
<Grid container>
|
||||
<Grid item xs={6} className={classes.registerActivationIcon}>
|
||||
{title}
|
||||
</Grid>
|
||||
<Grid item xs={6} className={classes.registerActivationMode}>
|
||||
{onlineActivation ? (
|
||||
<Fragment>
|
||||
<OfflineRegistrationBackIcon />
|
||||
<Link
|
||||
className={classes.link}
|
||||
onClick={() => {
|
||||
fetchSubnetRegToken();
|
||||
setOnlineActivation(!onlineActivation);
|
||||
}}
|
||||
>
|
||||
Offline Activation
|
||||
</Link>
|
||||
</Fragment>
|
||||
<Fragment />
|
||||
) : (
|
||||
<Fragment>
|
||||
<OnlineRegistrationBackIcon />
|
||||
@@ -606,6 +591,32 @@ const Register = ({ classes, displayErrorMessage }: IRegister) => {
|
||||
|
||||
{clusterRegistrationForm}
|
||||
</Grid>
|
||||
{onlineActivation && (
|
||||
<Grid item xs={12} marginTop={"15px"}>
|
||||
<HelpBox
|
||||
title={"Proxy Configuration"}
|
||||
iconComponent={<SettingsIcon />}
|
||||
help={
|
||||
<Fragment>
|
||||
For airgap/firewalled environments it is possible to configure
|
||||
a proxy to connect to Subnet.
|
||||
<br />
|
||||
<br />
|
||||
Alternatively you can try{" "}
|
||||
<Link
|
||||
className={classes.link}
|
||||
onClick={() => {
|
||||
fetchSubnetRegToken();
|
||||
setOnlineActivation(!onlineActivation);
|
||||
}}
|
||||
>
|
||||
Offline Activation.
|
||||
</Link>
|
||||
</Fragment>
|
||||
}
|
||||
/>
|
||||
</Grid>
|
||||
)}
|
||||
</PageLayout>
|
||||
</Fragment>
|
||||
);
|
||||
|
||||
62
portal-ui/src/screens/Console/Support/RegisterStatus.tsx
Normal file
62
portal-ui/src/screens/Console/Support/RegisterStatus.tsx
Normal file
@@ -0,0 +1,62 @@
|
||||
// This file is part of MinIO Console Server
|
||||
// Copyright (c) 2022 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/>.
|
||||
|
||||
import { Grid } from "@mui/material";
|
||||
import VerifiedIcon from "../../../icons/VerifiedIcon";
|
||||
import React from "react";
|
||||
import { Theme } from "@mui/material/styles";
|
||||
import createStyles from "@mui/styles/createStyles";
|
||||
import withStyles from "@mui/styles/withStyles";
|
||||
|
||||
const styles = (theme: Theme) =>
|
||||
createStyles({
|
||||
registeredStatus: {
|
||||
border: "1px solid #E2E2E2",
|
||||
padding: "24px 24px 24px 24px",
|
||||
borderRadius: 2,
|
||||
marginBottom: 25,
|
||||
backgroundColor: "#FBFAFA",
|
||||
"& .min-icon": {
|
||||
width: 20,
|
||||
height: 20,
|
||||
marginLeft: 48,
|
||||
marginRight: 13,
|
||||
verticalAlign: "middle",
|
||||
marginTop: -3,
|
||||
},
|
||||
"& span": {
|
||||
fontWeight: "bold",
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
interface IRegisterStatus {
|
||||
classes: any;
|
||||
}
|
||||
|
||||
function RegisterStatus({ classes }: IRegisterStatus) {
|
||||
return (
|
||||
<Grid container>
|
||||
<Grid item xs={12} className={classes.registeredStatus}>
|
||||
Register Status:
|
||||
<VerifiedIcon />
|
||||
<span>Registered</span>
|
||||
</Grid>
|
||||
</Grid>
|
||||
);
|
||||
}
|
||||
|
||||
export default withStyles(styles)(RegisterStatus);
|
||||
@@ -20,6 +20,7 @@ package restapi
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/go-openapi/runtime/middleware"
|
||||
"github.com/minio/console/cluster"
|
||||
@@ -57,11 +58,14 @@ func registerSubnetHandlers(api *operations.ConsoleAPI) {
|
||||
})
|
||||
// Get subnet info
|
||||
api.AdminAPISubnetInfoHandler = admin_api.SubnetInfoHandlerFunc(func(params admin_api.SubnetInfoParams, session *models.Principal) middleware.Responder {
|
||||
err := GetSubnetInfoResponse(session)
|
||||
client := &cluster.HTTPClient{
|
||||
Client: GetConsoleHTTPClient(),
|
||||
}
|
||||
resp, err := GetSubnetInfoResponse(session, client)
|
||||
if err != nil {
|
||||
return admin_api.NewSubnetInfoDefault(int(err.Code)).WithPayload(err)
|
||||
}
|
||||
return admin_api.NewSubnetInfoOK()
|
||||
return admin_api.NewSubnetInfoOK().WithPayload(resp)
|
||||
})
|
||||
// Get subnet registration token
|
||||
api.AdminAPISubnetRegTokenHandler = admin_api.SubnetRegTokenHandlerFunc(func(params admin_api.SubnetRegTokenParams, session *models.Principal) middleware.Responder {
|
||||
@@ -78,11 +82,11 @@ func SubnetRegisterWithAPIKey(ctx context.Context, minioClient MinioAdmin, apiKe
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
subnetAPIKey, err := subnet.Register(httpClient, serverInfo, apiKey, "", "")
|
||||
registerResult, err := subnet.Register(httpClient, serverInfo, apiKey, "", "")
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
configStr := "subnet license= api_key=" + subnetAPIKey
|
||||
configStr := fmt.Sprintf("subnet license=%s api_key=%s", registerResult.License, registerResult.APIKey)
|
||||
_, err = minioClient.setConfigKV(ctx, configStr)
|
||||
if err != nil {
|
||||
return false, err
|
||||
@@ -179,26 +183,28 @@ func GetSubnetLoginWithMFAResponse(params admin_api.SubnetLoginMFAParams) (*mode
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func GetSubnetKeyFromMinIOConfig(ctx context.Context, minioClient MinioAdmin, key string) (string, error) {
|
||||
func GetSubnetKeyFromMinIOConfig(ctx context.Context, minioClient MinioAdmin) (*subnet.LicenseTokenConfig, error) {
|
||||
sh, err := minioClient.helpConfigKV(ctx, "subnet", "", false)
|
||||
if err != nil {
|
||||
return "", err
|
||||
return nil, err
|
||||
}
|
||||
buf, err := minioClient.getConfigKV(ctx, "subnet")
|
||||
if err != nil {
|
||||
return "", err
|
||||
return nil, err
|
||||
}
|
||||
tgt, err := madmin.ParseSubSysTarget(buf, sh)
|
||||
if err != nil {
|
||||
return "", err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
res := subnet.LicenseTokenConfig{}
|
||||
for _, kv := range tgt.KVS {
|
||||
if kv.Key == key {
|
||||
return kv.Value, nil
|
||||
if kv.Key == "api_key" {
|
||||
res.APIKey = kv.Value
|
||||
} else if kv.Key == "license" {
|
||||
res.License = kv.Value
|
||||
}
|
||||
}
|
||||
return "", errors.New("")
|
||||
return &res, nil
|
||||
}
|
||||
|
||||
func GetSubnetRegister(ctx context.Context, minioClient MinioAdmin, httpClient cluster.HTTPClientI, params admin_api.SubnetRegisterParams) error {
|
||||
@@ -206,11 +212,11 @@ func GetSubnetRegister(ctx context.Context, minioClient MinioAdmin, httpClient c
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
subnetAPIKey, err := subnet.Register(httpClient, serverInfo, "", *params.Body.Token, *params.Body.AccountID)
|
||||
registerResult, err := subnet.Register(httpClient, serverInfo, "", *params.Body.Token, *params.Body.AccountID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
configStr := "subnet license= api_key=" + subnetAPIKey
|
||||
configStr := fmt.Sprintf("subnet license=%s api_key=%s", registerResult.License, registerResult.APIKey)
|
||||
_, err = minioClient.setConfigKV(ctx, configStr)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -235,21 +241,35 @@ func GetSubnetRegisterResponse(session *models.Principal, params admin_api.Subne
|
||||
return nil
|
||||
}
|
||||
|
||||
func GetSubnetInfoResponse(session *models.Principal) *models.Error {
|
||||
func GetSubnetInfoResponse(session *models.Principal, client cluster.HTTPClientI) (*models.License, *models.Error) {
|
||||
fmt.Println("quack")
|
||||
ctx := context.Background()
|
||||
mAdmin, err := NewMinioAdminClient(session)
|
||||
if err != nil {
|
||||
return prepareError(err)
|
||||
return nil, prepareError(err)
|
||||
}
|
||||
adminClient := AdminClient{Client: mAdmin}
|
||||
apiKey, err := GetSubnetKeyFromMinIOConfig(ctx, adminClient, "api_key")
|
||||
subnetTokens, err := GetSubnetKeyFromMinIOConfig(ctx, adminClient)
|
||||
if err != nil {
|
||||
return prepareError(err)
|
||||
return nil, prepareError(err)
|
||||
}
|
||||
if apiKey == "" {
|
||||
return prepareError(errLicenseNotFound)
|
||||
if subnetTokens.APIKey == "" {
|
||||
return nil, prepareError(errLicenseNotFound)
|
||||
}
|
||||
return nil
|
||||
licenseInfo, err := subnet.ParseLicense(client, subnetTokens.License)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
license := &models.License{
|
||||
Email: licenseInfo.Email,
|
||||
AccountID: licenseInfo.AccountID,
|
||||
StorageCapacity: licenseInfo.StorageCapacity,
|
||||
Plan: licenseInfo.Plan,
|
||||
ExpiresAt: licenseInfo.ExpiresAt.String(),
|
||||
Organization: licenseInfo.Organization,
|
||||
}
|
||||
return license, nil
|
||||
|
||||
}
|
||||
|
||||
func GetSubnetRegToken(ctx context.Context, minioClient MinioAdmin) (string, error) {
|
||||
|
||||
Reference in New Issue
Block a user