github.com/minio/console@v1.4.1/web-app/src/screens/Console/EventDestinations/WebhookSettings/EditWebhookEndpoint.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, useEffect, useState } from "react"; 18 import { 19 Button, 20 ConsoleIcon, 21 FormLayout, 22 Grid, 23 InputBox, 24 PendingItemsIcon, 25 ProgressBar, 26 ReadBox, 27 Switch, 28 Tooltip, 29 WebhookIcon, 30 } from "mds"; 31 import ModalWrapper from "../../Common/ModalWrapper/ModalWrapper"; 32 import { modalStyleUtils } from "../../Common/FormComponents/common/styleLibrary"; 33 import { 34 configurationIsLoading, 35 setErrorSnackMessage, 36 setServerNeedsRestart, 37 setSnackBarMessage, 38 } from "../../../../systemSlice"; 39 import { useAppDispatch } from "../../../../store"; 40 41 import { IConfigurationSys } from "../../Configurations/types"; 42 import { overrideFields } from "../../Configurations/utils"; 43 import { api } from "api"; 44 import { errorToHandler } from "api/errors"; 45 46 interface IEndpointModal { 47 open: boolean; 48 type: string; 49 endpointInfo: IConfigurationSys; 50 onCloseEndpoint: () => void; 51 } 52 53 const EditEndpointModal = ({ 54 open, 55 type, 56 endpointInfo, 57 onCloseEndpoint, 58 }: IEndpointModal) => { 59 const [name, setName] = useState<string>(""); 60 const [endpoint, setEndpoint] = useState<string>(""); 61 const [authToken, setAuthToken] = useState<string>(""); 62 const [endpointState, setEndpointState] = useState<string>("on"); 63 const [saving, setSaving] = useState<boolean>(false); 64 const [invalidInputs, setInvalidInput] = useState<string[]>([]); 65 66 const dispatch = useAppDispatch(); 67 68 useEffect(() => { 69 if (endpointInfo) { 70 const endpointLocate = endpointInfo.key_values.find( 71 (key) => key.key === "endpoint", 72 ); 73 const tokenLocate = endpointInfo.key_values.find( 74 (key) => key.key === "auth_token", 75 ); 76 const enable = endpointInfo.key_values.find( 77 (key) => key.key === "enable", 78 ); 79 80 let invalidInputs: string[] = []; 81 82 if (endpointLocate) { 83 const endpointValue = endpointLocate.value; 84 85 if (endpointValue === "") { 86 invalidInputs.push("endpoint"); 87 } else { 88 setEndpoint(endpointValue); 89 } 90 } 91 92 if (tokenLocate) { 93 const tokenValue = tokenLocate.value; 94 95 if (tokenValue === "") { 96 invalidInputs.push("auth-token"); 97 } else { 98 setAuthToken(tokenValue); 99 } 100 } 101 102 if (enable) { 103 if (enable.value === "off") { 104 setEndpointState(enable.value); 105 } 106 } 107 108 setName(endpointInfo.name || ""); 109 setInvalidInput(invalidInputs); 110 } 111 }, [endpointInfo]); 112 113 const updateWebhook = () => { 114 if (saving) { 115 return; 116 } 117 118 if (invalidInputs.length !== 0) { 119 return; 120 } 121 122 if (!endpoint || endpoint.trim() === "") { 123 setInvalidInput([...invalidInputs, "endpoint"]); 124 125 return; 126 } 127 128 setSaving(true); 129 130 const payload = { 131 key_values: [ 132 { 133 key: "endpoint", 134 value: endpoint, 135 }, 136 { 137 key: "auth_token", 138 value: authToken, 139 }, 140 { 141 key: "enable", 142 value: endpointState, 143 }, 144 ], 145 }; 146 147 api.configs 148 .setConfig(name, payload) 149 .then((res) => { 150 setSaving(false); 151 dispatch(setServerNeedsRestart(res.data.restart || false)); 152 if (!res.data.restart) { 153 dispatch(setSnackBarMessage("Configuration saved successfully")); 154 } 155 156 onCloseEndpoint(); 157 dispatch(configurationIsLoading(true)); 158 }) 159 .catch((err) => { 160 setSaving(false); 161 dispatch(setErrorSnackMessage(errorToHandler(err.error))); 162 }); 163 }; 164 165 const validateInput = (name: string, valid: boolean) => { 166 if (invalidInputs.includes(name) && valid) { 167 setInvalidInput(invalidInputs.filter((item) => item !== name)); 168 return; 169 } 170 171 if (!valid && !invalidInputs.includes(name)) { 172 setInvalidInput([...invalidInputs, name]); 173 } 174 }; 175 176 const defaultWH = !name.includes(":"); 177 const hasOverride = endpointInfo.key_values.filter( 178 (itm) => !!itm.env_override, 179 ); 180 181 const overrideValues = overrideFields(hasOverride); 182 183 let title = "Edit Webhook"; 184 let icon = <WebhookIcon />; 185 186 switch (type) { 187 case "logger_webhook": 188 title = `Edit ${defaultWH ? " the Default " : ""}Logger Webhook`; 189 icon = <ConsoleIcon />; 190 break; 191 case "audit_webhook": 192 title = `Edit ${defaultWH ? " the Default " : ""}Audit Webhook`; 193 icon = <PendingItemsIcon />; 194 break; 195 } 196 197 if (hasOverride.length > 0) { 198 title = "View env variable Webhook"; 199 } 200 201 return ( 202 <Fragment> 203 <ModalWrapper 204 modalOpen={open} 205 title={`${title}${defaultWH ? "" : ` - ${name}`}`} 206 onClose={onCloseEndpoint} 207 titleIcon={icon} 208 > 209 <FormLayout withBorders={false} containerPadding={false}> 210 {hasOverride.length > 0 ? ( 211 <Fragment> 212 <ReadBox 213 label={"Enabled"} 214 sx={{ width: "100%" }} 215 actionButton={ 216 <Grid 217 item 218 sx={{ 219 display: "flex", 220 justifyContent: "flex-end", 221 paddingRight: "10px", 222 }} 223 > 224 <Tooltip 225 tooltip={ 226 overrideValues.enable 227 ? `This value is set from the ${ 228 overrideValues.enable?.overrideEnv || "N/A" 229 } environment variable` 230 : "" 231 } 232 placement={"left"} 233 > 234 <ConsoleIcon style={{ width: 20 }} /> 235 </Tooltip> 236 </Grid> 237 } 238 > 239 {overrideValues.enable?.value || "-"} 240 </ReadBox> 241 <ReadBox 242 label={"Endpoint"} 243 sx={{ width: "100%" }} 244 actionButton={ 245 <Grid 246 item 247 sx={{ 248 display: "flex", 249 justifyContent: "flex-end", 250 paddingRight: "10px", 251 }} 252 > 253 <Tooltip 254 tooltip={ 255 overrideValues.enable 256 ? `This value is set from the ${ 257 overrideValues.endpoint?.overrideEnv || "N/A" 258 } environment variable` 259 : "" 260 } 261 placement={"left"} 262 > 263 <ConsoleIcon style={{ width: 20 }} /> 264 </Tooltip> 265 </Grid> 266 } 267 > 268 {overrideValues.endpoint?.value || "-"} 269 </ReadBox> 270 <ReadBox 271 label={"Auth Token"} 272 sx={{ width: "100%" }} 273 actionButton={ 274 <Grid 275 item 276 sx={{ 277 display: "flex", 278 justifyContent: "flex-end", 279 paddingRight: "10px", 280 }} 281 > 282 <Tooltip 283 tooltip={ 284 overrideValues.enable 285 ? `This value is set from the ${ 286 overrideValues.auth_token?.overrideEnv || "N/A" 287 } environment variable` 288 : "" 289 } 290 placement={"left"} 291 > 292 <ConsoleIcon style={{ width: 20 }} /> 293 </Tooltip> 294 </Grid> 295 } 296 > 297 {overrideValues.auth_token?.value || "-"} 298 </ReadBox> 299 </Fragment> 300 ) : ( 301 <Fragment> 302 <Switch 303 onChange={(e: React.ChangeEvent<HTMLInputElement>) => { 304 const value = e.target.checked ? "on" : "off"; 305 setEndpointState(value); 306 }} 307 id={"endpoint_enabled"} 308 name={"endpoint_enabled"} 309 label={"Enabled"} 310 value={"switch_on"} 311 checked={endpointState === "on"} 312 /> 313 <InputBox 314 id="endpoint" 315 name="endpoint" 316 onChange={(event: React.ChangeEvent<HTMLInputElement>) => { 317 setEndpoint(event.target.value); 318 validateInput("endpoint", event.target.validity.valid); 319 }} 320 error={ 321 invalidInputs.includes("endpoint") 322 ? "Invalid Endpoint set" 323 : "" 324 } 325 label="Endpoint" 326 value={endpoint} 327 pattern={ 328 "^(https?):\\/\\/([a-zA-Z0-9\\-.]+)(:[0-9]+)?(\\/[a-zA-Z0-9\\-.\\/]*)?$" 329 } 330 required 331 /> 332 <InputBox 333 id="auth-token" 334 name="auth-token" 335 onChange={(event: React.ChangeEvent<HTMLInputElement>) => { 336 setAuthToken(event.target.value); 337 }} 338 label="Auth Token" 339 value={authToken} 340 /> 341 {saving && ( 342 <Grid 343 item 344 xs={12} 345 sx={{ 346 marginBottom: 10, 347 }} 348 > 349 <ProgressBar /> 350 </Grid> 351 )} 352 <Grid item sx={modalStyleUtils.modalButtonBar}> 353 <Button 354 id={"reset"} 355 type="button" 356 variant="regular" 357 disabled={saving} 358 onClick={onCloseEndpoint} 359 label={"Cancel"} 360 /> 361 <Button 362 id={"save-lifecycle"} 363 type="submit" 364 variant="callAction" 365 color="primary" 366 disabled={saving || invalidInputs.length !== 0} 367 label={"Update"} 368 onClick={updateWebhook} 369 /> 370 </Grid> 371 </Fragment> 372 )} 373 </FormLayout> 374 </ModalWrapper> 375 </Fragment> 376 ); 377 }; 378 379 export default EditEndpointModal;