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:
Daniel Valdivia
2022-01-24 15:40:38 -08:00
committed by GitHub
parent f27902ab92
commit be054fe4ce
8 changed files with 1060 additions and 754 deletions

View File

@@ -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 == "" {

View File

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

View 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);

View File

@@ -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",
},
];

View File

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

View 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);

View File

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