mirror of
https://github.com/farcasclaudiu/Flowise.git
synced 2026-06-28 15:00:57 +03:00
add API authorization
This commit is contained in:
@@ -0,0 +1,231 @@
|
||||
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 {
|
||||
Box,
|
||||
Typography,
|
||||
Button,
|
||||
Dialog,
|
||||
DialogActions,
|
||||
DialogContent,
|
||||
DialogTitle,
|
||||
Stack,
|
||||
IconButton,
|
||||
OutlinedInput,
|
||||
Popover
|
||||
} from '@mui/material'
|
||||
import { useTheme } from '@mui/material/styles'
|
||||
import { StyledButton } from 'ui-component/button/StyledButton'
|
||||
|
||||
// Icons
|
||||
import { IconX, IconCopy } from '@tabler/icons'
|
||||
|
||||
// API
|
||||
import apikeyApi from 'api/apikey'
|
||||
|
||||
// utils
|
||||
import useNotifier from 'utils/useNotifier'
|
||||
|
||||
const APIKeyDialog = ({ show, dialogProps, onCancel, onConfirm }) => {
|
||||
const portalElement = document.getElementById('portal')
|
||||
|
||||
const theme = useTheme()
|
||||
const dispatch = useDispatch()
|
||||
|
||||
// ==============================|| Snackbar ||============================== //
|
||||
|
||||
useNotifier()
|
||||
|
||||
const enqueueSnackbar = (...args) => dispatch(enqueueSnackbarAction(...args))
|
||||
const closeSnackbar = (...args) => dispatch(closeSnackbarAction(...args))
|
||||
|
||||
const [keyName, setKeyName] = useState('')
|
||||
const [anchorEl, setAnchorEl] = useState(null)
|
||||
const openPopOver = Boolean(anchorEl)
|
||||
|
||||
useEffect(() => {
|
||||
if (dialogProps.type === 'EDIT' && dialogProps.key) {
|
||||
setKeyName(dialogProps.key.keyName)
|
||||
} else if (dialogProps.type === 'ADD') {
|
||||
setKeyName('')
|
||||
}
|
||||
}, [dialogProps])
|
||||
|
||||
const handleClosePopOver = () => {
|
||||
setAnchorEl(null)
|
||||
}
|
||||
|
||||
const addNewKey = async () => {
|
||||
try {
|
||||
const createResp = await apikeyApi.createNewAPI({ keyName })
|
||||
if (createResp.data) {
|
||||
enqueueSnackbar({
|
||||
message: 'New API key added',
|
||||
options: {
|
||||
key: new Date().getTime() + Math.random(),
|
||||
variant: 'success',
|
||||
action: (key) => (
|
||||
<Button style={{ color: 'white' }} onClick={() => closeSnackbar(key)}>
|
||||
<IconX />
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
})
|
||||
onConfirm()
|
||||
}
|
||||
} catch (error) {
|
||||
const errorData = error.response.data || `${error.response.status}: ${error.response.statusText}`
|
||||
enqueueSnackbar({
|
||||
message: `Failed to add new API key: ${errorData}`,
|
||||
options: {
|
||||
key: new Date().getTime() + Math.random(),
|
||||
variant: 'error',
|
||||
persist: true,
|
||||
action: (key) => (
|
||||
<Button style={{ color: 'white' }} onClick={() => closeSnackbar(key)}>
|
||||
<IconX />
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
})
|
||||
onCancel()
|
||||
}
|
||||
}
|
||||
|
||||
const saveKey = async () => {
|
||||
try {
|
||||
const saveResp = await apikeyApi.updateAPI(dialogProps.key.id, { keyName })
|
||||
if (saveResp.data) {
|
||||
enqueueSnackbar({
|
||||
message: 'API Key saved',
|
||||
options: {
|
||||
key: new Date().getTime() + Math.random(),
|
||||
variant: 'success',
|
||||
action: (key) => (
|
||||
<Button style={{ color: 'white' }} onClick={() => closeSnackbar(key)}>
|
||||
<IconX />
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
})
|
||||
onConfirm()
|
||||
}
|
||||
} catch (error) {
|
||||
const errorData = error.response.data || `${error.response.status}: ${error.response.statusText}`
|
||||
enqueueSnackbar({
|
||||
message: `Failed to save API key: ${errorData}`,
|
||||
options: {
|
||||
key: new Date().getTime() + Math.random(),
|
||||
variant: 'error',
|
||||
persist: true,
|
||||
action: (key) => (
|
||||
<Button style={{ color: 'white' }} onClick={() => closeSnackbar(key)}>
|
||||
<IconX />
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
})
|
||||
onCancel()
|
||||
}
|
||||
}
|
||||
|
||||
const component = show ? (
|
||||
<Dialog
|
||||
fullWidth
|
||||
maxWidth='sm'
|
||||
open={show}
|
||||
onClose={onCancel}
|
||||
aria-labelledby='alert-dialog-title'
|
||||
aria-describedby='alert-dialog-description'
|
||||
>
|
||||
<DialogTitle sx={{ fontSize: '1rem' }} id='alert-dialog-title'>
|
||||
{dialogProps.title}
|
||||
</DialogTitle>
|
||||
<DialogContent>
|
||||
{dialogProps.type === 'EDIT' && (
|
||||
<Box sx={{ p: 2 }}>
|
||||
<Typography variant='overline'>API Key</Typography>
|
||||
<Stack direction='row' sx={{ mb: 1 }}>
|
||||
<Typography
|
||||
sx={{
|
||||
p: 1,
|
||||
borderRadius: 10,
|
||||
backgroundColor: theme.palette.primary.light,
|
||||
width: 'max-content',
|
||||
height: 'max-content'
|
||||
}}
|
||||
variant='h5'
|
||||
>
|
||||
{dialogProps.key.apiKey}
|
||||
</Typography>
|
||||
<IconButton
|
||||
title='Copy API Key'
|
||||
color='success'
|
||||
onClick={(event) => {
|
||||
navigator.clipboard.writeText(dialogProps.key.apiKey)
|
||||
setAnchorEl(event.currentTarget)
|
||||
setTimeout(() => {
|
||||
handleClosePopOver()
|
||||
}, 1500)
|
||||
}}
|
||||
>
|
||||
<IconCopy />
|
||||
</IconButton>
|
||||
<Popover
|
||||
open={openPopOver}
|
||||
anchorEl={anchorEl}
|
||||
onClose={handleClosePopOver}
|
||||
anchorOrigin={{
|
||||
vertical: 'top',
|
||||
horizontal: 'right'
|
||||
}}
|
||||
transformOrigin={{
|
||||
vertical: 'top',
|
||||
horizontal: 'left'
|
||||
}}
|
||||
>
|
||||
<Typography variant='h6' sx={{ pl: 1, pr: 1, color: 'white', background: theme.palette.success.dark }}>
|
||||
Copied!
|
||||
</Typography>
|
||||
</Popover>
|
||||
</Stack>
|
||||
</Box>
|
||||
)}
|
||||
|
||||
<Box sx={{ p: 2 }}>
|
||||
<Stack sx={{ position: 'relative' }} direction='row'>
|
||||
<Typography variant='overline'>Key Name</Typography>
|
||||
</Stack>
|
||||
<OutlinedInput
|
||||
id='keyName'
|
||||
type='string'
|
||||
fullWidth
|
||||
placeholder='My New Key'
|
||||
value={keyName}
|
||||
name='keyName'
|
||||
onChange={(e) => setKeyName(e.target.value)}
|
||||
/>
|
||||
</Box>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<StyledButton variant='contained' onClick={() => (dialogProps.type === 'ADD' ? addNewKey() : saveKey())}>
|
||||
{dialogProps.confirmButtonName}
|
||||
</StyledButton>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
) : null
|
||||
|
||||
return createPortal(component, portalElement)
|
||||
}
|
||||
|
||||
APIKeyDialog.propTypes = {
|
||||
show: PropTypes.bool,
|
||||
dialogProps: PropTypes.object,
|
||||
onCancel: PropTypes.func,
|
||||
onConfirm: PropTypes.func
|
||||
}
|
||||
|
||||
export default APIKeyDialog
|
||||
@@ -0,0 +1,279 @@
|
||||
import { useEffect, useState } from 'react'
|
||||
import { useDispatch, useSelector } from 'react-redux'
|
||||
import { enqueueSnackbar as enqueueSnackbarAction, closeSnackbar as closeSnackbarAction } from 'store/actions'
|
||||
|
||||
// material-ui
|
||||
import {
|
||||
Button,
|
||||
Box,
|
||||
Stack,
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableContainer,
|
||||
TableHead,
|
||||
TableRow,
|
||||
Paper,
|
||||
IconButton,
|
||||
Popover,
|
||||
Typography
|
||||
} 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 APIKeyDialog from './APIKeyDialog'
|
||||
import ConfirmDialog from 'ui-component/dialog/ConfirmDialog'
|
||||
|
||||
// API
|
||||
import apiKeyApi from 'api/apikey'
|
||||
|
||||
// Hooks
|
||||
import useApi from 'hooks/useApi'
|
||||
import useConfirm from 'hooks/useConfirm'
|
||||
|
||||
// utils
|
||||
import useNotifier from 'utils/useNotifier'
|
||||
|
||||
// Icons
|
||||
import { IconTrash, IconEdit, IconCopy, IconX, IconPlus, IconEye, IconEyeOff } from '@tabler/icons'
|
||||
import APIEmptySVG from 'assets/images/api_empty.svg'
|
||||
|
||||
// ==============================|| APIKey ||============================== //
|
||||
|
||||
const APIKey = () => {
|
||||
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 [showDialog, setShowDialog] = useState(false)
|
||||
const [dialogProps, setDialogProps] = useState({})
|
||||
const [apiKeys, setAPIKeys] = useState([])
|
||||
const [anchorEl, setAnchorEl] = useState(null)
|
||||
const [showApiKeys, setShowApiKeys] = useState([])
|
||||
const openPopOver = Boolean(anchorEl)
|
||||
|
||||
const { confirm } = useConfirm()
|
||||
|
||||
const getAllAPIKeysApi = useApi(apiKeyApi.getAllAPIKeys)
|
||||
|
||||
const onShowApiKeyClick = (apikey) => {
|
||||
const index = showApiKeys.indexOf(apikey)
|
||||
if (index > -1) {
|
||||
//showApiKeys.splice(index, 1)
|
||||
const newShowApiKeys = showApiKeys.filter(function (item) {
|
||||
return item !== apikey
|
||||
})
|
||||
setShowApiKeys(newShowApiKeys)
|
||||
} else {
|
||||
setShowApiKeys((prevkeys) => [...prevkeys, apikey])
|
||||
}
|
||||
}
|
||||
|
||||
const handleClosePopOver = () => {
|
||||
setAnchorEl(null)
|
||||
}
|
||||
|
||||
const addNew = () => {
|
||||
const dialogProp = {
|
||||
title: 'Add New API Key',
|
||||
type: 'ADD',
|
||||
cancelButtonName: 'Cancel',
|
||||
confirmButtonName: 'Add'
|
||||
}
|
||||
setDialogProps(dialogProp)
|
||||
setShowDialog(true)
|
||||
}
|
||||
|
||||
const edit = (key) => {
|
||||
const dialogProp = {
|
||||
title: 'Edit API Key',
|
||||
type: 'EDIT',
|
||||
cancelButtonName: 'Cancel',
|
||||
confirmButtonName: 'Save',
|
||||
key
|
||||
}
|
||||
setDialogProps(dialogProp)
|
||||
setShowDialog(true)
|
||||
}
|
||||
|
||||
const deleteKey = async (key) => {
|
||||
const confirmPayload = {
|
||||
title: `Delete`,
|
||||
description: `Delete key ${key.keyName}?`,
|
||||
confirmButtonName: 'Delete',
|
||||
cancelButtonName: 'Cancel'
|
||||
}
|
||||
const isConfirmed = await confirm(confirmPayload)
|
||||
|
||||
if (isConfirmed) {
|
||||
try {
|
||||
const deleteResp = await apiKeyApi.deleteAPI(key.id)
|
||||
if (deleteResp.data) {
|
||||
enqueueSnackbar({
|
||||
message: 'API key deleted',
|
||||
options: {
|
||||
key: new Date().getTime() + Math.random(),
|
||||
variant: 'success',
|
||||
action: (key) => (
|
||||
<Button style={{ color: 'white' }} onClick={() => closeSnackbar(key)}>
|
||||
<IconX />
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
})
|
||||
onConfirm()
|
||||
}
|
||||
} catch (error) {
|
||||
const errorData = error.response.data || `${error.response.status}: ${error.response.statusText}`
|
||||
enqueueSnackbar({
|
||||
message: `Failed to delete API key: ${errorData}`,
|
||||
options: {
|
||||
key: new Date().getTime() + Math.random(),
|
||||
variant: 'error',
|
||||
persist: true,
|
||||
action: (key) => (
|
||||
<Button style={{ color: 'white' }} onClick={() => closeSnackbar(key)}>
|
||||
<IconX />
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
})
|
||||
onCancel()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const onConfirm = () => {
|
||||
setShowDialog(false)
|
||||
getAllAPIKeysApi.request()
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
getAllAPIKeysApi.request()
|
||||
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
if (getAllAPIKeysApi.data) {
|
||||
setAPIKeys(getAllAPIKeysApi.data)
|
||||
}
|
||||
}, [getAllAPIKeysApi.data])
|
||||
|
||||
return (
|
||||
<>
|
||||
<MainCard sx={{ background: customization.isDarkMode ? theme.palette.common.black : '' }}>
|
||||
<Stack flexDirection='row'>
|
||||
<h1>API Keys </h1>
|
||||
<Box sx={{ flexGrow: 1 }} />
|
||||
|
||||
<StyledButton variant='contained' sx={{ color: 'white', mr: 1, height: 37 }} onClick={addNew} startIcon={<IconPlus />}>
|
||||
Create Key
|
||||
</StyledButton>
|
||||
</Stack>
|
||||
{apiKeys.length <= 0 && (
|
||||
<Stack sx={{ alignItems: 'center', justifyContent: 'center' }} flexDirection='column'>
|
||||
<Box sx={{ p: 2, height: 'auto' }}>
|
||||
<img style={{ objectFit: 'cover', height: '30vh', width: 'auto' }} src={APIEmptySVG} alt='APIEmptySVG' />
|
||||
</Box>
|
||||
<div>No API Keys Yet</div>
|
||||
</Stack>
|
||||
)}
|
||||
{apiKeys.length > 0 && (
|
||||
<TableContainer component={Paper}>
|
||||
<Table sx={{ minWidth: 650 }} aria-label='simple table'>
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
<TableCell>Key Name</TableCell>
|
||||
<TableCell>API Key</TableCell>
|
||||
<TableCell>Created</TableCell>
|
||||
<TableCell> </TableCell>
|
||||
<TableCell> </TableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{apiKeys.map((key, index) => (
|
||||
<TableRow key={index} sx={{ '&:last-child td, &:last-child th': { border: 0 } }}>
|
||||
<TableCell component='th' scope='row'>
|
||||
{key.keyName}
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
{showApiKeys.includes(key.apiKey)
|
||||
? key.apiKey
|
||||
: `${key.apiKey.substring(0, 2)}${'•'.repeat(18)}${key.apiKey.substring(
|
||||
key.apiKey.length - 5
|
||||
)}`}
|
||||
<IconButton
|
||||
title='Copy'
|
||||
color='success'
|
||||
onClick={(event) => {
|
||||
navigator.clipboard.writeText(key.apiKey)
|
||||
setAnchorEl(event.currentTarget)
|
||||
setTimeout(() => {
|
||||
handleClosePopOver()
|
||||
}, 1500)
|
||||
}}
|
||||
>
|
||||
<IconCopy />
|
||||
</IconButton>
|
||||
<IconButton title='Show' color='inherit' onClick={() => onShowApiKeyClick(key.apiKey)}>
|
||||
{showApiKeys.includes(key.apiKey) ? <IconEyeOff /> : <IconEye />}
|
||||
</IconButton>
|
||||
<Popover
|
||||
open={openPopOver}
|
||||
anchorEl={anchorEl}
|
||||
onClose={handleClosePopOver}
|
||||
anchorOrigin={{
|
||||
vertical: 'top',
|
||||
horizontal: 'right'
|
||||
}}
|
||||
transformOrigin={{
|
||||
vertical: 'top',
|
||||
horizontal: 'left'
|
||||
}}
|
||||
>
|
||||
<Typography
|
||||
variant='h6'
|
||||
sx={{ pl: 1, pr: 1, color: 'white', background: theme.palette.success.dark }}
|
||||
>
|
||||
Copied!
|
||||
</Typography>
|
||||
</Popover>
|
||||
</TableCell>
|
||||
<TableCell>{key.createdAt}</TableCell>
|
||||
<TableCell>
|
||||
<IconButton title='Edit' color='primary' onClick={() => edit(key)}>
|
||||
<IconEdit />
|
||||
</IconButton>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<IconButton title='Delete' color='error' onClick={() => deleteKey(key)}>
|
||||
<IconTrash />
|
||||
</IconButton>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
)}
|
||||
</MainCard>
|
||||
<APIKeyDialog
|
||||
show={showDialog}
|
||||
dialogProps={dialogProps}
|
||||
onCancel={() => setShowDialog(false)}
|
||||
onConfirm={onConfirm}
|
||||
></APIKeyDialog>
|
||||
<ConfirmDialog />
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default APIKey
|
||||
@@ -8,7 +8,7 @@ import { useTheme } from '@mui/material/styles'
|
||||
import { Avatar, Box, ButtonBase, Typography, Stack, TextField } from '@mui/material'
|
||||
|
||||
// icons
|
||||
import { IconSettings, IconChevronLeft, IconDeviceFloppy, IconPencil, IconCheck, IconX, IconWorldWww } from '@tabler/icons'
|
||||
import { IconSettings, IconChevronLeft, IconDeviceFloppy, IconPencil, IconCheck, IconX, IconCode } from '@tabler/icons'
|
||||
|
||||
// project imports
|
||||
import Settings from 'views/settings'
|
||||
@@ -82,7 +82,8 @@ const CanvasHeader = ({ chatflow, handleSaveFlow, handleDeleteFlow, handleLoadFl
|
||||
const onAPIDialogClick = () => {
|
||||
setAPIDialogProps({
|
||||
title: 'Use this chatflow with API',
|
||||
chatflowid: chatflow.id
|
||||
chatflowid: chatflow.id,
|
||||
chatflowApiKeyId: chatflow.apikeyid
|
||||
})
|
||||
setAPIDialogOpen(true)
|
||||
}
|
||||
@@ -247,7 +248,7 @@ const CanvasHeader = ({ chatflow, handleSaveFlow, handleDeleteFlow, handleLoadFl
|
||||
color='inherit'
|
||||
onClick={onAPIDialogClick}
|
||||
>
|
||||
<IconWorldWww stroke={1.5} size='1.3rem' />
|
||||
<IconCode stroke={1.5} size='1.3rem' />
|
||||
</Avatar>
|
||||
</ButtonBase>
|
||||
<ButtonBase title='Save Chatflow' sx={{ borderRadius: '50%', mr: 2 }}>
|
||||
|
||||
Reference in New Issue
Block a user