diff --git a/packages/ui/src/views/credentials/AddEditCredentialDialog.js b/packages/ui/src/views/credentials/AddEditCredentialDialog.js
new file mode 100644
index 00000000..65b72a5f
--- /dev/null
+++ b/packages/ui/src/views/credentials/AddEditCredentialDialog.js
@@ -0,0 +1,283 @@
+import { createPortal } from 'react-dom'
+import PropTypes from 'prop-types'
+import { useState, useEffect } from 'react'
+import { useDispatch } from 'react-redux'
+import { enqueueSnackbar as enqueueSnackbarAction, closeSnackbar as closeSnackbarAction } from 'store/actions'
+import parser from 'html-react-parser'
+
+// Material
+import { Button, Dialog, DialogActions, DialogContent, DialogTitle, Box, Stack, OutlinedInput, Typography } from '@mui/material'
+
+// Project imports
+import { StyledButton } from 'ui-component/button/StyledButton'
+import ConfirmDialog from 'ui-component/dialog/ConfirmDialog'
+import CredentialInputHandler from './CredentialInputHandler'
+
+// Icons
+import { IconX } from '@tabler/icons'
+
+// API
+import credentialsApi from 'api/credentials'
+
+// Hooks
+import useApi from 'hooks/useApi'
+
+// utils
+import useNotifier from 'utils/useNotifier'
+
+// const
+import { baseURL } from 'store/constant'
+import { HIDE_CANVAS_DIALOG, SHOW_CANVAS_DIALOG } from 'store/actions'
+
+const AddEditCredentialDialog = ({ show, dialogProps, onCancel, onConfirm }) => {
+ const portalElement = document.getElementById('portal')
+
+ const dispatch = useDispatch()
+
+ // ==============================|| Snackbar ||============================== //
+
+ useNotifier()
+
+ const enqueueSnackbar = (...args) => dispatch(enqueueSnackbarAction(...args))
+ const closeSnackbar = (...args) => dispatch(closeSnackbarAction(...args))
+
+ const getSpecificCredentialApi = useApi(credentialsApi.getSpecificCredential)
+ const getSpecificComponentCredentialApi = useApi(credentialsApi.getSpecificComponentCredential)
+
+ const [credential, setCredential] = useState({})
+ const [name, setName] = useState('')
+ const [credentialData, setCredentialData] = useState({})
+ const [componentCredential, setComponentCredential] = useState({})
+
+ useEffect(() => {
+ if (getSpecificCredentialApi.data) {
+ setCredential(getSpecificCredentialApi.data)
+ if (getSpecificCredentialApi.data.name) {
+ setName(getSpecificCredentialApi.data.name)
+ }
+ if (getSpecificCredentialApi.data.plainDataObj) {
+ setCredentialData(getSpecificCredentialApi.data.plainDataObj)
+ }
+ getSpecificComponentCredentialApi.request(getSpecificCredentialApi.data.credentialName)
+ }
+
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [getSpecificCredentialApi.data])
+
+ useEffect(() => {
+ if (getSpecificComponentCredentialApi.data) {
+ setComponentCredential(getSpecificComponentCredentialApi.data)
+ }
+ }, [getSpecificComponentCredentialApi.data])
+
+ useEffect(() => {
+ if (dialogProps.type === 'EDIT' && dialogProps.data) {
+ // When credential dialog is opened from Credentials dashboard
+ getSpecificCredentialApi.request(dialogProps.data.id)
+ } else if (dialogProps.type === 'EDIT' && dialogProps.credentialId) {
+ // When credential dialog is opened from node in canvas
+ getSpecificCredentialApi.request(dialogProps.credentialId)
+ } else if (dialogProps.type === 'ADD' && dialogProps.credentialComponent) {
+ // When credential dialog is to add a new credential
+ setName('')
+ setCredential({})
+ setCredentialData({})
+ setComponentCredential(dialogProps.credentialComponent)
+ }
+
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [dialogProps])
+
+ useEffect(() => {
+ if (show) dispatch({ type: SHOW_CANVAS_DIALOG })
+ else dispatch({ type: HIDE_CANVAS_DIALOG })
+ return () => dispatch({ type: HIDE_CANVAS_DIALOG })
+ }, [show, dispatch])
+
+ const addNewCredential = async () => {
+ try {
+ const obj = {
+ name,
+ credentialName: componentCredential.name,
+ plainDataObj: credentialData
+ }
+ const createResp = await credentialsApi.createCredential(obj)
+ if (createResp.data) {
+ enqueueSnackbar({
+ message: 'New Credential added',
+ options: {
+ key: new Date().getTime() + Math.random(),
+ variant: 'success',
+ action: (key) => (
+
+ )
+ }
+ })
+ onConfirm(createResp.data.id)
+ }
+ } catch (error) {
+ const errorData = error.response.data || `${error.response.status}: ${error.response.statusText}`
+ enqueueSnackbar({
+ message: `Failed to add new Credential: ${errorData}`,
+ options: {
+ key: new Date().getTime() + Math.random(),
+ variant: 'error',
+ persist: true,
+ action: (key) => (
+
+ )
+ }
+ })
+ onCancel()
+ }
+ }
+
+ const saveCredential = async () => {
+ try {
+ const saveResp = await credentialsApi.updateCredential(credential.id, {
+ name,
+ credentialName: componentCredential.name,
+ plainDataObj: credentialData
+ })
+ if (saveResp.data) {
+ enqueueSnackbar({
+ message: 'Credential saved',
+ options: {
+ key: new Date().getTime() + Math.random(),
+ variant: 'success',
+ action: (key) => (
+
+ )
+ }
+ })
+ onConfirm(saveResp.data.id)
+ }
+ } catch (error) {
+ const errorData = error.response.data || `${error.response.status}: ${error.response.statusText}`
+ enqueueSnackbar({
+ message: `Failed to save Credential: ${errorData}`,
+ options: {
+ key: new Date().getTime() + Math.random(),
+ variant: 'error',
+ persist: true,
+ action: (key) => (
+
+ )
+ }
+ })
+ onCancel()
+ }
+ }
+
+ const component = show ? (
+
+ ) : null
+
+ return createPortal(component, portalElement)
+}
+
+AddEditCredentialDialog.propTypes = {
+ show: PropTypes.bool,
+ dialogProps: PropTypes.object,
+ onCancel: PropTypes.func,
+ onConfirm: PropTypes.func
+}
+
+export default AddEditCredentialDialog
diff --git a/packages/ui/src/views/credentials/CredentialInputHandler.js b/packages/ui/src/views/credentials/CredentialInputHandler.js
new file mode 100644
index 00000000..30cc5746
--- /dev/null
+++ b/packages/ui/src/views/credentials/CredentialInputHandler.js
@@ -0,0 +1,137 @@
+import PropTypes from 'prop-types'
+import { useRef, useState } from 'react'
+import { useSelector } from 'react-redux'
+
+// material-ui
+import { Box, Typography, IconButton } from '@mui/material'
+import { IconArrowsMaximize, IconAlertTriangle } from '@tabler/icons'
+
+// project import
+import { Dropdown } from 'ui-component/dropdown/Dropdown'
+import { Input } from 'ui-component/input/Input'
+import { SwitchInput } from 'ui-component/switch/Switch'
+import { JsonEditorInput } from 'ui-component/json/JsonEditor'
+import { TooltipWithParser } from 'ui-component/tooltip/TooltipWithParser'
+
+// ===========================|| NodeInputHandler ||=========================== //
+
+const CredentialInputHandler = ({ inputParam, data, disabled = false }) => {
+ const customization = useSelector((state) => state.customization)
+ const ref = useRef(null)
+
+ const [showExpandDialog, setShowExpandDialog] = useState(false)
+ const [expandDialogProps, setExpandDialogProps] = useState({})
+
+ const onExpandDialogClicked = (value, inputParam) => {
+ const dialogProp = {
+ value,
+ inputParam,
+ disabled,
+ confirmButtonName: 'Save',
+ cancelButtonName: 'Cancel'
+ }
+ setExpandDialogProps(dialogProp)
+ setShowExpandDialog(true)
+ }
+
+ const onExpandDialogSave = (newValue, inputParamName) => {
+ setShowExpandDialog(false)
+ data[inputParamName] = newValue
+ }
+
+ return (
+
+ {inputParam && (
+ <>
+
+
+
+ {inputParam.label}
+ {!inputParam.optional && *}
+ {inputParam.description && }
+
+
+ {inputParam.type === 'string' && inputParam.rows && (
+
onExpandDialogClicked(data[inputParam.name] ?? inputParam.default ?? '', inputParam)}
+ >
+
+
+ )}
+
+ {inputParam.warning && (
+
+
+ {inputParam.warning}
+
+ )}
+
+ {inputParam.type === 'boolean' && (
+ (data[inputParam.name] = newValue)}
+ value={data[inputParam.name] ?? inputParam.default ?? false}
+ />
+ )}
+ {(inputParam.type === 'string' || inputParam.type === 'password' || inputParam.type === 'number') && (
+ (data[inputParam.name] = newValue)}
+ value={data[inputParam.name] ?? inputParam.default ?? ''}
+ showDialog={showExpandDialog}
+ dialogProps={expandDialogProps}
+ onDialogCancel={() => setShowExpandDialog(false)}
+ onDialogConfirm={(newValue, inputParamName) => onExpandDialogSave(newValue, inputParamName)}
+ />
+ )}
+ {inputParam.type === 'json' && (
+ (data[inputParam.name] = newValue)}
+ value={data[inputParam.name] ?? inputParam.default ?? ''}
+ isDarkMode={customization.isDarkMode}
+ />
+ )}
+ {inputParam.type === 'options' && (
+ (data[inputParam.name] = newValue)}
+ value={data[inputParam.name] ?? inputParam.default ?? 'choose an option'}
+ />
+ )}
+
+ >
+ )}
+
+ )
+}
+
+CredentialInputHandler.propTypes = {
+ inputAnchor: PropTypes.object,
+ inputParam: PropTypes.object,
+ data: PropTypes.object,
+ disabled: PropTypes.bool
+}
+
+export default CredentialInputHandler
diff --git a/packages/ui/src/views/credentials/CredentialListDialog.js b/packages/ui/src/views/credentials/CredentialListDialog.js
new file mode 100644
index 00000000..e0a3e08d
--- /dev/null
+++ b/packages/ui/src/views/credentials/CredentialListDialog.js
@@ -0,0 +1,179 @@
+import { useState, useEffect } from 'react'
+import { createPortal } from 'react-dom'
+import { useSelector, useDispatch } from 'react-redux'
+import PropTypes from 'prop-types'
+import {
+ List,
+ ListItemButton,
+ ListItem,
+ ListItemAvatar,
+ ListItemText,
+ Dialog,
+ DialogContent,
+ DialogTitle,
+ Box,
+ OutlinedInput,
+ InputAdornment
+} from '@mui/material'
+import { useTheme } from '@mui/material/styles'
+import { IconSearch, IconX } from '@tabler/icons'
+
+// const
+import { baseURL } from 'store/constant'
+import { HIDE_CANVAS_DIALOG, SHOW_CANVAS_DIALOG } from 'store/actions'
+
+const CredentialListDialog = ({ show, dialogProps, onCancel, onCredentialSelected }) => {
+ const portalElement = document.getElementById('portal')
+ const customization = useSelector((state) => state.customization)
+ const dispatch = useDispatch()
+ const theme = useTheme()
+ const [searchValue, setSearchValue] = useState('')
+ const [componentsCredentials, setComponentsCredentials] = useState([])
+
+ const filterSearch = (value) => {
+ setSearchValue(value)
+ setTimeout(() => {
+ if (value) {
+ const searchData = dialogProps.componentsCredentials.filter((crd) => crd.name.toLowerCase().includes(value.toLowerCase()))
+ setComponentsCredentials(searchData)
+ } else if (value === '') {
+ setComponentsCredentials(dialogProps.componentsCredentials)
+ }
+ // scrollTop()
+ }, 500)
+ }
+
+ useEffect(() => {
+ if (dialogProps.componentsCredentials) {
+ setComponentsCredentials(dialogProps.componentsCredentials)
+ }
+ }, [dialogProps])
+
+ useEffect(() => {
+ if (show) dispatch({ type: SHOW_CANVAS_DIALOG })
+ else dispatch({ type: HIDE_CANVAS_DIALOG })
+ return () => dispatch({ type: HIDE_CANVAS_DIALOG })
+ }, [show, dispatch])
+
+ const component = show ? (
+
+ ) : null
+
+ return createPortal(component, portalElement)
+}
+
+CredentialListDialog.propTypes = {
+ show: PropTypes.bool,
+ dialogProps: PropTypes.object,
+ onCancel: PropTypes.func,
+ onCredentialSelected: PropTypes.func
+}
+
+export default CredentialListDialog
diff --git a/packages/ui/src/views/credentials/index.js b/packages/ui/src/views/credentials/index.js
new file mode 100644
index 00000000..9db990a7
--- /dev/null
+++ b/packages/ui/src/views/credentials/index.js
@@ -0,0 +1,276 @@
+import { useEffect, useState } from 'react'
+import { useDispatch, useSelector } from 'react-redux'
+import { enqueueSnackbar as enqueueSnackbarAction, closeSnackbar as closeSnackbarAction } from 'store/actions'
+import moment from 'moment'
+
+// material-ui
+import { Button, Box, Stack, Table, TableBody, TableCell, TableContainer, TableHead, TableRow, Paper, IconButton } from '@mui/material'
+import { useTheme } from '@mui/material/styles'
+
+// project imports
+import MainCard from 'ui-component/cards/MainCard'
+import { StyledButton } from 'ui-component/button/StyledButton'
+import CredentialListDialog from './CredentialListDialog'
+import ConfirmDialog from 'ui-component/dialog/ConfirmDialog'
+import AddEditCredentialDialog from './AddEditCredentialDialog'
+
+// API
+import credentialsApi from 'api/credentials'
+
+// Hooks
+import useApi from 'hooks/useApi'
+import useConfirm from 'hooks/useConfirm'
+
+// utils
+import useNotifier from 'utils/useNotifier'
+
+// Icons
+import { IconTrash, IconEdit, IconX, IconPlus } from '@tabler/icons'
+import CredentialEmptySVG from 'assets/images/credential_empty.svg'
+
+// const
+import { baseURL } from 'store/constant'
+import { SET_COMPONENT_CREDENTIALS } from 'store/actions'
+
+// ==============================|| Credentials ||============================== //
+
+const Credentials = () => {
+ const theme = useTheme()
+ const customization = useSelector((state) => state.customization)
+
+ const dispatch = useDispatch()
+ useNotifier()
+
+ const enqueueSnackbar = (...args) => dispatch(enqueueSnackbarAction(...args))
+ const closeSnackbar = (...args) => dispatch(closeSnackbarAction(...args))
+
+ const [showCredentialListDialog, setShowCredentialListDialog] = useState(false)
+ const [credentialListDialogProps, setCredentialListDialogProps] = useState({})
+ const [showSpecificCredentialDialog, setShowSpecificCredentialDialog] = useState(false)
+ const [specificCredentialDialogProps, setSpecificCredentialDialogProps] = useState({})
+ const [credentials, setCredentials] = useState([])
+ const [componentsCredentials, setComponentsCredentials] = useState([])
+
+ const { confirm } = useConfirm()
+
+ const getAllCredentialsApi = useApi(credentialsApi.getAllCredentials)
+ const getAllComponentsCredentialsApi = useApi(credentialsApi.getAllComponentsCredentials)
+
+ const listCredential = () => {
+ const dialogProp = {
+ title: 'Add New Credential',
+ componentsCredentials
+ }
+ setCredentialListDialogProps(dialogProp)
+ setShowCredentialListDialog(true)
+ }
+
+ const addNew = (credentialComponent) => {
+ const dialogProp = {
+ type: 'ADD',
+ cancelButtonName: 'Cancel',
+ confirmButtonName: 'Add',
+ credentialComponent
+ }
+ setSpecificCredentialDialogProps(dialogProp)
+ setShowSpecificCredentialDialog(true)
+ }
+
+ const edit = (credential) => {
+ const dialogProp = {
+ type: 'EDIT',
+ cancelButtonName: 'Cancel',
+ confirmButtonName: 'Save',
+ data: credential
+ }
+ setSpecificCredentialDialogProps(dialogProp)
+ setShowSpecificCredentialDialog(true)
+ }
+
+ const deleteCredential = async (credential) => {
+ const confirmPayload = {
+ title: `Delete`,
+ description: `Delete credential ${credential.name}?`,
+ confirmButtonName: 'Delete',
+ cancelButtonName: 'Cancel'
+ }
+ const isConfirmed = await confirm(confirmPayload)
+
+ if (isConfirmed) {
+ try {
+ const deleteResp = await credentialsApi.deleteCredential(credential.id)
+ if (deleteResp.data) {
+ enqueueSnackbar({
+ message: 'Credential deleted',
+ options: {
+ key: new Date().getTime() + Math.random(),
+ variant: 'success',
+ action: (key) => (
+
+ )
+ }
+ })
+ onConfirm()
+ }
+ } catch (error) {
+ const errorData = error.response.data || `${error.response.status}: ${error.response.statusText}`
+ enqueueSnackbar({
+ message: `Failed to delete Credential: ${errorData}`,
+ options: {
+ key: new Date().getTime() + Math.random(),
+ variant: 'error',
+ persist: true,
+ action: (key) => (
+
+ )
+ }
+ })
+ onCancel()
+ }
+ }
+ }
+
+ const onCredentialSelected = (credentialComponent) => {
+ setShowCredentialListDialog(false)
+ addNew(credentialComponent)
+ }
+
+ const onConfirm = () => {
+ setShowCredentialListDialog(false)
+ setShowSpecificCredentialDialog(false)
+ getAllCredentialsApi.request()
+ }
+
+ useEffect(() => {
+ getAllCredentialsApi.request()
+ getAllComponentsCredentialsApi.request()
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [])
+
+ useEffect(() => {
+ if (getAllCredentialsApi.data) {
+ setCredentials(getAllCredentialsApi.data)
+ }
+ }, [getAllCredentialsApi.data])
+
+ useEffect(() => {
+ if (getAllComponentsCredentialsApi.data) {
+ setComponentsCredentials(getAllComponentsCredentialsApi.data)
+ dispatch({ type: SET_COMPONENT_CREDENTIALS, componentsCredentials: getAllComponentsCredentialsApi.data })
+ }
+ }, [getAllComponentsCredentialsApi.data, dispatch])
+
+ return (
+ <>
+
+
+ Credentials
+
+
+ }
+ >
+ Add Credential
+
+
+ {credentials.length <= 0 && (
+
+
+
+
+ No Credentials Yet
+
+ )}
+ {credentials.length > 0 && (
+
+
+
+
+ Name
+ Last Updated
+ Created
+
+
+
+
+
+ {credentials.map((credential, index) => (
+
+
+
+
+

+
+ {credential.name}
+
+
+ {moment(credential.updatedDate).format('DD-MMM-YY')}
+ {moment(credential.createdDate).format('DD-MMM-YY')}
+
+ edit(credential)}>
+
+
+
+
+ deleteCredential(credential)}>
+
+
+
+
+ ))}
+
+
+
+ )}
+
+
setShowCredentialListDialog(false)}
+ onCredentialSelected={onCredentialSelected}
+ >
+
setShowSpecificCredentialDialog(false)}
+ onConfirm={onConfirm}
+ >
+
+ >
+ )
+}
+
+export default Credentials
diff --git a/packages/ui/src/views/tools/ToolDialog.js b/packages/ui/src/views/tools/ToolDialog.js
index 77ef770d..5e286789 100644
--- a/packages/ui/src/views/tools/ToolDialog.js
+++ b/packages/ui/src/views/tools/ToolDialog.js
@@ -29,6 +29,7 @@ import useApi from 'hooks/useApi'
// utils
import useNotifier from 'utils/useNotifier'
import { generateRandomGradient } from 'utils/genericHelper'
+import { HIDE_CANVAS_DIALOG, SHOW_CANVAS_DIALOG } from 'store/actions'
const exampleAPIFunc = `/*
* You can use any libraries imported in Flowise
@@ -155,6 +156,12 @@ const ToolDialog = ({ show, dialogProps, onUseTemplate, onCancel, onConfirm }) =
}
}
+ useEffect(() => {
+ if (show) dispatch({ type: SHOW_CANVAS_DIALOG })
+ else dispatch({ type: HIDE_CANVAS_DIALOG })
+ return () => dispatch({ type: HIDE_CANVAS_DIALOG })
+ }, [show, dispatch])
+
useEffect(() => {
if (getSpecificToolApi.data) {
setToolId(getSpecificToolApi.data.id)