From 35fca6ece6b90cb8bd9e6f622ec1f9e41d31e458 Mon Sep 17 00:00:00 2001 From: Prateek Surana Date: Fri, 12 Apr 2024 17:53:26 +0530 Subject: [PATCH] Update core config UI --- src/assets/edit-unfilled.svg | 3 + src/assets/question-mark.svg | 9 +- src/images.ts | 1 + .../tenantDetail/CoreConfigSection.tsx | 376 ++++++------------ .../EditCoreConfigPropertyDialog.tsx | 180 +++++++++ .../editCoreConfigPropertyDialog.scss | 85 ++++ .../tenants/tenantDetail/tenantDetail.scss | 319 ++++++++++----- src/ui/styles/variables.css | 4 +- 8 files changed, 617 insertions(+), 360 deletions(-) create mode 100644 src/assets/edit-unfilled.svg create mode 100644 src/ui/components/tenants/tenantDetail/editCoreConfigPropertyDialog/EditCoreConfigPropertyDialog.tsx create mode 100644 src/ui/components/tenants/tenantDetail/editCoreConfigPropertyDialog/editCoreConfigPropertyDialog.scss diff --git a/src/assets/edit-unfilled.svg b/src/assets/edit-unfilled.svg new file mode 100644 index 00000000..48e7df02 --- /dev/null +++ b/src/assets/edit-unfilled.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/question-mark.svg b/src/assets/question-mark.svg index 6b622b8e..e71456e2 100644 --- a/src/assets/question-mark.svg +++ b/src/assets/question-mark.svg @@ -1,5 +1,6 @@ - - - - + + + + + diff --git a/src/images.ts b/src/images.ts index 7638bdc6..5d033bd3 100644 --- a/src/images.ts +++ b/src/images.ts @@ -37,6 +37,7 @@ import "./assets/cross.svg"; import "./assets/delete-login-method.png"; import "./assets/delete.svg"; import "./assets/edit-login-method.png"; +import "./assets/edit-unfilled.svg"; import "./assets/edit.svg"; import "./assets/email.svg"; import "./assets/envelope-green.svg"; diff --git a/src/ui/components/tenants/tenantDetail/CoreConfigSection.tsx b/src/ui/components/tenants/tenantDetail/CoreConfigSection.tsx index f6a90fcc..6e53917a 100644 --- a/src/ui/components/tenants/tenantDetail/CoreConfigSection.tsx +++ b/src/ui/components/tenants/tenantDetail/CoreConfigSection.tsx @@ -12,20 +12,18 @@ * License for the specific language governing permissions and limitations * under the License. */ -import { useContext, useEffect, useState } from "react"; -import { useUpdateCoreConfigService } from "../../../../api/tenants"; -import { ReactComponent as PencilIcon } from "../../../../assets/edit.svg"; +import { useState } from "react"; +import { ReactComponent as PencilIcon } from "../../../../assets/edit-unfilled.svg"; import { ReactComponent as InfoIcon } from "../../../../assets/info-icon.svg"; import { ReactComponent as QuestionMarkIcon } from "../../../../assets/question-mark.svg"; import { PUBLIC_TENANT_ID } from "../../../../constants"; -import { getConnectionUri, getImageUrl } from "../../../../utils"; -import { PopupContentContext } from "../../../contexts/PopupContentContext"; -import Button from "../../button"; -import { Checkbox } from "../../checkbox/Checkbox"; -import InputField from "../../inputField/InputField"; -import { NativeSelect } from "../../nativeSelect/NativeSelect"; -import { Toggle } from "../../toggle/Toggle"; +import { getConnectionUri } from "../../../../utils"; +// import { Checkbox } from "../../checkbox/Checkbox"; +// import InputField from "../../inputField/InputField"; +// import { NativeSelect } from "../../nativeSelect/NativeSelect"; +// import { Toggle } from "../../toggle/Toggle"; import TooltipContainer from "../../tooltip/tooltip"; +import { EditCoreConfigPropertyDialog } from "./editCoreConfigPropertyDialog/EditCoreConfigPropertyDialog"; import { EditPluginPropertyDialog } from "./editPluginPropertyDialog/EditPluginPropertyDialog"; import { useTenantDetailContext } from "./TenantDetailContext"; import { PanelHeader, PanelHeaderTitleWithTooltip, PanelRoot } from "./tenantDetailPanel/TenantDetailPanel"; @@ -56,67 +54,81 @@ export const CoreConfigSection = () => {
- {tenantInfo.coreConfig - .filter((config) => !config.isPluginProperty) - .map((config) => { - return ( - - ); - })} - {hasPluginProperties && ( - <> -
-

- Database Properties -

-
-

- Some of these properties need to be modified together, hence they cannot be directly - modified from the UI, instead you can make an API request to core to modify these - properties.{" "} - {" "} - to see an example. -

+
+
Property name
+
Value
+
+
+ {tenantInfo.coreConfig + .filter((config) => !config.isPluginProperty) + .map((config) => { + return ( + + ); + })} +
+
+ {hasPluginProperties && ( + <> +
+

+ Database Properties +

+
+

+ Some of these properties need to be modified together, hence they cannot be directly + modified from the UI, instead you can make an API request to core to modify these + properties.{" "} + {" "} + to see an example. +

+
+
+
+
Property name
+
Value
+
+
+ {tenantInfo.coreConfig + .filter((config) => config.isPluginProperty) + .map((config) => { + return ( + + ); + })}
- {tenantInfo.coreConfig - .filter((config) => config.isPluginProperty) - .map((config) => { - return ( - - ); - })} {showPluginDialog && databaseType !== null && ( { databaseType={databaseType} /> )} - - )} -
+
+ + )} ); }; @@ -162,16 +174,12 @@ const CoreConfigTableRow = ({ isModifyableOnlyViaConfigYaml, isPluginProperty, isPluginPropertyEditable, + defaultValue, }: CoreConfigTableRowProps) => { - const [isEditing, setIsEditing] = useState(false); - const [currentValue, setCurrentValue] = useState(value); - const { tenantInfo, refetchTenant } = useTenantDetailContext(); - const updateCoreConfig = useUpdateCoreConfigService(); - const [isLoading, setIsLoading] = useState(false); + const { tenantInfo } = useTenantDetailContext(); const [isUneditablePropertyDialogVisible, setIsUneditablePropertyDialogVisible] = useState(false); - const isMultiValue = Array.isArray(possibleValues) && possibleValues.length > 0; + const [isEditPropertyDialogVisible, setIsEditPropertyDialogVisible] = useState(false); const isPublicTenant = tenantInfo.tenantId === PUBLIC_TENANT_ID; - const { showToast } = useContext(PopupContentContext); const isUneditable = isPublicTenant || @@ -180,93 +188,6 @@ const CoreConfigTableRow = ({ (isUsingSaaS && isSaaSProtected) || (!isPublicTenant && !isDifferentAcrossTenants); - // Keep the state in sync with the prop value - useEffect(() => { - setCurrentValue(value); - }, [value]); - - const toggleNull = () => { - if (currentValue === null) { - setCurrentValue(type === "number" ? 0 : ""); - } else { - setCurrentValue(null); - } - }; - - const handleCancelEdit = () => { - setIsEditing(false); - setCurrentValue(value); - }; - - const handleSaveProperty = async () => { - try { - setIsLoading(true); - const res = await updateCoreConfig(tenantInfo.tenantId, name, currentValue); - if (res.status !== "OK") { - if (res.status === "UNKNOWN_TENANT_ERROR") { - showToast({ - iconImage: getImageUrl("form-field-error-icon.svg"), - toastType: "error", - children: <>Tenant not found., - }); - } else { - showToast({ - iconImage: getImageUrl("form-field-error-icon.svg"), - toastType: "error", - children: <>{res.message}, - }); - } - return; - } - await refetchTenant(); - setIsEditing(false); - } catch (e) { - showToast({ - iconImage: getImageUrl("form-field-error-icon.svg"), - toastType: "error", - children: <>Something went wrong please try again., - }); - } finally { - setIsLoading(false); - } - }; - - const renderConfigAction = () => { - if (isUneditable) { - return null; - } - - if (!isEditing) { - return ( - - ); - } - - return ( -
- - -
- ); - }; - const renderUneditablePropertyReason = () => { if (isModifyableOnlyViaConfigYaml && isUsingSaaS) { return "This property cannot be modified since you are using the managed service."; @@ -309,86 +230,51 @@ const CoreConfigTableRow = ({ return ( <>
-
-
-
- {tooltip && ( - - - - )} - {name} -
- {renderConfigAction()} -
-
- -
- {isMultiValue && ( - { - setCurrentValue(e.target.value); - }} - /> - )} - - {(type === "string" || type === "number") && !isMultiValue && ( - { - setCurrentValue(e.target.value); - }} - value={currentValue === null ? "[null]" : `${currentValue}`} - /> - )} - - {typeof currentValue === "boolean" && type === "boolean" && ( - { - setCurrentValue(!currentValue); - }} - /> - )} - {isNullable && ( - - )} -
-
+
+ {tooltip && ( + + {name} +
+ {tooltip} + + }> + + + )} +
{name}
+
+
+
{`${value}`}
+ {isUneditable ? ( + + ) : ( + + )}
- {isUneditable && ( - - )}
+ {isEditPropertyDialogVisible && ( + setIsEditPropertyDialogVisible(false)} + defaultValue={defaultValue} + description={tooltip} + /> + )} {isUneditablePropertyDialogVisible && ( setIsUneditablePropertyDialogVisible(false)}> {renderUneditablePropertyReason()} diff --git a/src/ui/components/tenants/tenantDetail/editCoreConfigPropertyDialog/EditCoreConfigPropertyDialog.tsx b/src/ui/components/tenants/tenantDetail/editCoreConfigPropertyDialog/EditCoreConfigPropertyDialog.tsx new file mode 100644 index 00000000..96810066 --- /dev/null +++ b/src/ui/components/tenants/tenantDetail/editCoreConfigPropertyDialog/EditCoreConfigPropertyDialog.tsx @@ -0,0 +1,180 @@ +/* Copyright (c) 2024, VRAI Labs and/or its affiliates. All rights reserved. + * + * This software is licensed under the Apache License, Version 2.0 (the + * "License") as published by the Apache Software Foundation. + * + * You may not use this file except in compliance with the License. You may + * obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +import { useContext, useState } from "react"; +import { useUpdateCoreConfigService } from "../../../../../api/tenants"; +import { getImageUrl } from "../../../../../utils"; +import { PopupContentContext } from "../../../../contexts/PopupContentContext"; +import Button from "../../../button"; +import { Checkbox } from "../../../checkbox/Checkbox"; +import { Dialog, DialogContent, DialogFooter } from "../../../dialog"; +import InputField from "../../../inputField/InputField"; +import { NativeSelect } from "../../../nativeSelect/NativeSelect"; +import { Toggle } from "../../../toggle/Toggle"; +import { useTenantDetailContext } from "../TenantDetailContext"; +import "./editCoreConfigPropertyDialog.scss"; + +export const EditCoreConfigPropertyDialog = ({ + onCloseDialog, + possibleValues, + value, + type, + name, + isNullable, + description, + defaultValue, +}: { + onCloseDialog: () => void; + possibleValues?: string[]; + value: string | number | boolean | null; + type: "string" | "number" | "boolean"; + name: string; + isNullable: boolean; + description: string; + defaultValue: string | number | boolean | null; +}) => { + const [currentValue, setCurrentValue] = useState(value); + const { tenantInfo, refetchTenant } = useTenantDetailContext(); + const updateCoreConfig = useUpdateCoreConfigService(); + const [isLoading, setIsLoading] = useState(false); + const isMultiValue = Array.isArray(possibleValues) && possibleValues.length > 0; + const { showToast } = useContext(PopupContentContext); + + const toggleNull = () => { + if (currentValue === null) { + setCurrentValue(type === "number" ? 0 : ""); + } else { + setCurrentValue(null); + } + }; + + const handleSaveProperty = async () => { + try { + setIsLoading(true); + const res = await updateCoreConfig(tenantInfo.tenantId, name, currentValue); + if (res.status !== "OK") { + if (res.status === "UNKNOWN_TENANT_ERROR") { + showToast({ + iconImage: getImageUrl("form-field-error-icon.svg"), + toastType: "error", + children: <>Tenant not found., + }); + } else { + showToast({ + iconImage: getImageUrl("form-field-error-icon.svg"), + toastType: "error", + children: <>{res.message}, + }); + } + return; + } + await refetchTenant(); + onCloseDialog(); + } catch (e) { + showToast({ + iconImage: getImageUrl("form-field-error-icon.svg"), + toastType: "error", + children: <>Something went wrong please try again., + }); + } finally { + setIsLoading(false); + } + }; + + return ( + + +
+
+
Property Name:
+
{name}
+
+
+
Value:
+
+ {isMultiValue && ( + { + setCurrentValue(e.target.value); + }} + /> + )} + + {(type === "string" || type === "number") && !isMultiValue && ( + { + setCurrentValue(e.target.value); + }} + value={currentValue === null ? "[null]" : `${currentValue}`} + /> + )} + + {typeof currentValue === "boolean" && type === "boolean" && ( + { + setCurrentValue(!currentValue); + }} + /> + )} + {isNullable && ( + + )} +
+
+
+
Info
+

{description}

+
+ Default Value: {`${defaultValue}`} +
+
+
+ + + + +
+
+ ); +}; diff --git a/src/ui/components/tenants/tenantDetail/editCoreConfigPropertyDialog/editCoreConfigPropertyDialog.scss b/src/ui/components/tenants/tenantDetail/editCoreConfigPropertyDialog/editCoreConfigPropertyDialog.scss new file mode 100644 index 00000000..1c6adb2d --- /dev/null +++ b/src/ui/components/tenants/tenantDetail/editCoreConfigPropertyDialog/editCoreConfigPropertyDialog.scss @@ -0,0 +1,85 @@ +/* Copyright (c) 2024, VRAI Labs and/or its affiliates. All rights reserved. + * + * This software is licensed under the Apache License, Version 2.0 (the + * "License") as published by the Apache Software Foundation. + * + * You may not use this file except in compliance with the License. You may + * obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +.edit-config-property-name-container { + display: flex; + gap: 9px; + margin-top: 32px; +} + +.edit-config-property-label { + font-family: inherit; + font-size: 14px; + font-weight: 500; + line-height: 17px; + color: var(--color-secondary-text); +} + +.edit-config-property-name { + font-family: inherit; + font-size: 14px; + font-weight: 500; + line-height: 17px; + color: var(--color-black); +} + +.edit-config-field-container { + display: flex; + flex-direction: column; + gap: 8px; +} + +.edit-config-property-value-container { + display: flex; + gap: 8px; + flex-direction: column; + margin-bottom: 24px; + margin-top: 30px; + + &--row { + flex-direction: row; + gap: 12px; + align-items: center; + } +} + +.edit-config-property-description { + margin-bottom: 28px !important; + box-shadow: none !important; + + &__info-pill { + background: var(--color-info-pill-bg); + border-radius: 8px; + padding: 1px 8px; + color: white; + font-size: 12px; + font-weight: 500; + line-height: 15px; + text-transform: uppercase; + width: fit-content; + margin-bottom: 12px; + } + + &__default-value { + font-family: inherit; + font-size: inherit; + width: fit-content; + margin-top: 16px; + padding: 6px; + gap: 10px; + border-radius: 6px; + background-color: rgba(210, 231, 255, 1); + } +} diff --git a/src/ui/components/tenants/tenantDetail/tenantDetail.scss b/src/ui/components/tenants/tenantDetail/tenantDetail.scss index 268efb13..569a5fe9 100644 --- a/src/ui/components/tenants/tenantDetail/tenantDetail.scss +++ b/src/ui/components/tenants/tenantDetail/tenantDetail.scss @@ -55,154 +55,255 @@ } &__core-config-table { - margin-top: 24px; + margin-top: 32px; + padding: 24px; + border: solid 1px var(--color-border-command); + border-radius: 6px; width: 100%; - display: flex; - flex-direction: column; - gap: 12px; - - &__plugin-properties-container { - margin-top: 8px; - } - - &__plugin-propertier-header { - font-size: 16px; - font-weight: 500; - line-height: 30px; - font-family: inherit; - color: var(--color-black); - } - &__plugin-properties-divider { - display: block; - height: 1px; - background-color: var(--color-border); - margin: 10px 0px; + @media screen and (max-width: 480px) { + padding: 0px; border: none; + margin-top: 20px; } - &__plugin-properties-description { - font-size: 14px; - line-height: 23px; - font-family: inherit; - color: var(--color-secondary-text); - } - - &__row { - position: relative; - } - - &__row-container { - background-color: var(--color-config-bg); - border-radius: 6px; - padding: 10px 24px; - border: 1px solid var(--color-secondary); - width: 100%; + &__header { + display: flex; + margin-bottom: 16px; + border-bottom: solid 1px var(--color-border-command); + padding-bottom: 16px; + padding-left: 24px; - &--editing { - box-shadow: 0px 4px 4px 0px rgba(0, 0, 0, 0.15); + &__item { + font-size: 14px; + color: var(--color-secondary-text); + font-weight: 500; + line-height: normal; + text-transform: uppercase; + flex: 1; } - &--uneditable { - opacity: 0.6; + @media screen and (max-width: 480px) { + display: none; } } - &__row-info { + &__body { display: flex; - align-items: center; - justify-content: space-between; - width: 100%; + flex-direction: column; + gap: 16px; + margin-top: 20px; + + @media screen and (max-width: 480px) { + margin-top: 0px; + } } - &__row-name { + &__row { display: flex; - gap: 10px; align-items: center; - padding: 4px 10px; - border-radius: 3px; - background-color: var(--color-config-name-bg); + padding: 10px 24px; + height: 40px; + border-radius: 6px; + background: var(--color-input-unfocused); font-size: 14px; - line-height: 19px; + color: black; font-weight: 500; - color: var(--color-config-property-label); + line-height: normal; - path { - fill: var(--color-config-property-label); + @media screen and (max-width: 480px) { + flex-direction: column; + align-items: flex-start; + justify-content: space-between; + height: 75px; } - } - &__row-edit-button-container { - background-color: white; - border-radius: 4px; - padding: 8px; - border: 1px solid var(--color-secondary); - - svg { - width: 11px; - height: 12px; + &__label { + max-width: 270px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; } - &:hover { - cursor: pointer; - background-color: var(--color-config-action-hover-bg); + &__label-container { + flex: 1; + gap: 9px; + display: flex; + align-items: center; } - } - &__row-uneditable-button-container { - position: absolute; - right: 24px; - top: 10px; - background-color: white; - border-radius: 4px; - padding: 7px; - border: 1px solid rgba(128, 188, 255, 1); + &__value { + flex: 1; + padding-left: 18px; + display: flex; + justify-content: space-between; + align-items: center; - svg { - width: 13px; - height: 13px; - } + @media screen and (max-width: 480px) { + padding-left: 0px; + width: 100%; + } - &:hover { - cursor: pointer; - background-color: var(--color-config-action-hover-bg); + &__text { + max-width: 500px; + padding: 3px 8px; + background: var(--color-copy-box-bg); + color: var(--color-copy-box); + font-size: 13px; + font-family: Menlo, "Source Code Pro", Monaco, Consolas, "Courier New", monospace; + border-radius: 3px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + } } - } - &__row-buttons { - display: flex; - gap: 10px; - align-items: center; - } + &__edit-button-container { + background-color: white; + border-radius: 4px; + padding: 5px; + border: 1px solid var(--color-border-icon-button); - &__row-value-container { - margin-top: 14px; - display: flex; - gap: 9px; + svg { + width: 10px; + height: 10px; + } - > label { - font-family: inherit; - font-weight: 500; - font-size: 14px; - line-height: 19px; - color: var(--color-black); - transform: translateY(4px); + &:hover { + cursor: pointer; + border-color: var(--color-primary); + svg { + path { + fill: var(--color-primary); + } + } + } } - &--toggle { - align-items: center; - > label { - transform: translateY(0); + &__uneditable-button-container { + background-color: white; + border-radius: 4px; + padding: 4px; + border: 1px solid var(--color-secondary); + + svg { + width: 13px; + height: 13px; + } + + &:hover { + cursor: pointer; + background-color: var(--color-config-action-hover-bg); } } } - &__row-field-container { - display: flex; - flex-direction: column; - gap: 8px; + &__plugin-properties-container { + margin-top: 8px; + } + + &__plugin-propertier-header { + font-size: 16px; + font-weight: 500; + line-height: 30px; + font-family: inherit; + color: var(--color-black); + margin-top: 20px; } + &__plugin-properties-divider { + display: block; + height: 1px; + background-color: var(--color-border); + margin: 10px 0px; + border: none; + } + + &__plugin-properties-description { + font-size: 14px; + line-height: 23px; + font-family: inherit; + color: var(--color-secondary-text); + } + + // &__row { + // position: relative; + // } + + // &__row-container { + // background-color: var(--color-config-bg); + // border-radius: 6px; + // padding: 10px 24px; + // border: 1px solid var(--color-secondary); + // width: 100%; + + // &--editing { + // box-shadow: 0px 4px 4px 0px rgba(0, 0, 0, 0.15); + // } + + // &--uneditable { + // opacity: 0.6; + // } + // } + + // &__row-info { + // display: flex; + // align-items: center; + // justify-content: space-between; + // width: 100%; + // } + + // &__row-name { + // display: flex; + // gap: 10px; + // align-items: center; + // padding: 4px 10px; + // border-radius: 3px; + // background-color: var(--color-config-name-bg); + // font-size: 14px; + // line-height: 19px; + // font-weight: 500; + // color: var(--color-config-property-label); + + // path { + // fill: var(--color-config-property-label); + // } + // } + + // &__row-buttons { + // display: flex; + // gap: 10px; + // align-items: center; + // } + + // &__row-value-container { + // margin-top: 14px; + // display: flex; + // gap: 9px; + + // > label { + // font-family: inherit; + // font-weight: 500; + // font-size: 14px; + // line-height: 19px; + // color: var(--color-black); + // transform: translateY(4px); + // } + + // &--toggle { + // align-items: center; + // > label { + // transform: translateY(0); + // } + // } + // } + + // &__row-field-container { + // display: flex; + // flex-direction: column; + // gap: 8px; + // } + &__button-link { background: none; border: none; diff --git a/src/ui/styles/variables.css b/src/ui/styles/variables.css index 16c1c308..4fc8f295 100644 --- a/src/ui/styles/variables.css +++ b/src/ui/styles/variables.css @@ -56,8 +56,7 @@ body { --color-info-box-bg: rgba(246, 246, 246); --color-config-bg: rgba(240, 244, 255); --color-config-name-bg: rgba(147, 176, 255, 0.33); - --color-config-action-hover-bg: rgba(128, 188, 255, 0.4); - + --color-config-action-hover-bg: rgba(0, 122, 255, 0.2); /* Border Colors */ --color-border: rgb(229, 229, 229); --color-border-error: rgb(239, 121, 119); @@ -66,6 +65,7 @@ body { --color-border-success: rgb(73, 200, 153); --color-border-command: rgb(221, 221, 221); --color-border-error-block: rgba(255, 18, 18, 1); + --color-border-icon-button: rgb(227, 227, 227); /* Text Colors */ --color-secondary-text: rgb(110, 106, 101); /* Below title, table headers, placeholders etc */