github.com/minio/console@v1.4.1/web-app/src/screens/Console/Support/OfflineRegistration.tsx (about) 1 // This file is part of MinIO Console Server 2 // Copyright (c) 2023 MinIO, Inc. 3 // 4 // This program is free software: you can redistribute it and/or modify 5 // it under the terms of the GNU Affero General Public License as published by 6 // the Free Software Foundation, either version 3 of the License, or 7 // (at your option) any later version. 8 // 9 // This program is distributed in the hope that it will be useful, 10 // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 // GNU Affero General Public License for more details. 13 // 14 // You should have received a copy of the GNU Affero General Public License 15 // along with this program. If not, see <http://www.gnu.org/licenses/>. 16 17 import React, { Fragment, useState } from "react"; 18 import { 19 Box, 20 Button, 21 CommentBox, 22 CopyIcon, 23 FormLayout, 24 OfflineRegistrationIcon, 25 } from "mds"; 26 import { ClusterRegistered } from "./utils"; 27 import { AppState, useAppDispatch } from "../../../store"; 28 import { useSelector } from "react-redux"; 29 import { fetchLicenseInfo } from "./registerThunks"; 30 import { 31 setErrorSnackMessage, 32 setServerNeedsRestart, 33 } from "../../../systemSlice"; 34 import { modalStyleUtils } from "../Common/FormComponents/common/styleLibrary"; 35 import TooltipWrapper from "../Common/TooltipWrapper/TooltipWrapper"; 36 import CopyToClipboard from "react-copy-to-clipboard"; 37 import RegisterHelpBox from "./RegisterHelpBox"; 38 import { api } from "api"; 39 import { ApiError, HttpResponse, SetConfigResponse } from "api/consoleApi"; 40 import { errorToHandler } from "api/errors"; 41 42 const OfflineRegistration = () => { 43 const dispatch = useAppDispatch(); 44 const subnetRegToken = useSelector( 45 (state: AppState) => state.register.subnetRegToken, 46 ); 47 const clusterRegistered = useSelector( 48 (state: AppState) => state.register.clusterRegistered, 49 ); 50 const licenseInfo = useSelector( 51 (state: AppState) => state.register.licenseInfo, 52 ); 53 54 const offlineRegUrl = `https://subnet.min.io/cluster/register?token=${subnetRegToken}`; 55 56 const [licenseKey, setLicenseKey] = useState(""); 57 const [loadingSave, setLoadingSave] = useState<boolean>(false); 58 59 const applyAirGapLicense = () => { 60 setLoadingSave(true); 61 api.configs 62 .setConfig("subnet", { 63 key_values: [{ key: "license", value: licenseKey }], 64 }) 65 .then((_) => { 66 dispatch(fetchLicenseInfo()); 67 dispatch(setServerNeedsRestart(true)); 68 }) 69 .catch((res: HttpResponse<SetConfigResponse, ApiError>) => { 70 dispatch(setErrorSnackMessage(errorToHandler(res.error))); 71 }) 72 .finally(() => setLoadingSave(false)); 73 }; 74 75 return ( 76 <Fragment> 77 <Box 78 withBorders 79 sx={{ 80 display: "flex", 81 flexFlow: "column", 82 padding: "43px", 83 }} 84 > 85 {clusterRegistered && licenseInfo ? ( 86 <ClusterRegistered email={licenseInfo.email} /> 87 ) : ( 88 <FormLayout 89 title={"Register cluster in an Air-gap environment"} 90 icon={<OfflineRegistrationIcon />} 91 helpBox={<RegisterHelpBox />} 92 withBorders={false} 93 containerPadding={false} 94 > 95 <Box 96 sx={{ 97 display: "flex", 98 flexFlow: "column", 99 flex: "2", 100 marginTop: "15px", 101 "& .step-row": { 102 fontSize: 14, 103 display: "flex", 104 marginTop: "15px", 105 marginBottom: "15px", 106 }, 107 }} 108 > 109 <Box> 110 <Box className="step-row"> 111 <Box className="step-text"> 112 Click on the link to register this cluster in SUBNET and get 113 a License Key for this Air-Gap deployment 114 </Box> 115 </Box> 116 117 <Box 118 sx={{ 119 flex: "1", 120 display: "flex", 121 alignItems: "center", 122 gap: 3, 123 }} 124 > 125 <a href={offlineRegUrl} target="_blank"> 126 https://subnet.min.io/cluster/register 127 </a> 128 129 <TooltipWrapper tooltip={"Copy to Clipboard"}> 130 <CopyToClipboard text={offlineRegUrl}> 131 <Button 132 type={"button"} 133 id={"copy-ult-to-clip-board"} 134 icon={<CopyIcon />} 135 color={"primary"} 136 variant={"regular"} 137 /> 138 </CopyToClipboard> 139 </TooltipWrapper> 140 </Box> 141 142 <Box 143 className={"muted"} 144 sx={{ 145 marginTop: "25px", 146 }} 147 > 148 Note: If this machine does not have internet connection, Copy 149 paste the following URL in a browser where you access SUBNET 150 and follow the instructions to complete the registration 151 </Box> 152 153 <Box 154 sx={{ 155 marginTop: "25px", 156 display: "flex", 157 flexDirection: "column", 158 }} 159 > 160 <label style={{ fontWeight: "bold", marginBottom: "10px" }}> 161 Paste the License Key{" "} 162 </label> 163 <CommentBox 164 value={licenseKey} 165 disabled={loadingSave} 166 label={""} 167 id={"licenseKey"} 168 name={"licenseKey"} 169 placeholder={"License Key"} 170 onChange={(e) => { 171 setLicenseKey(e.target.value); 172 }} 173 /> 174 </Box> 175 <Box sx={modalStyleUtils.modalButtonBar}> 176 <Button 177 id={"apply-license-key"} 178 onClick={applyAirGapLicense} 179 variant={"callAction"} 180 disabled={!licenseKey || loadingSave} 181 label={"Apply Cluster License"} 182 /> 183 </Box> 184 </Box> 185 </Box> 186 </FormLayout> 187 )} 188 </Box> 189 </Fragment> 190 ); 191 }; 192 193 export default OfflineRegistration;