Feature: Option to add apikeys to the DB instead of api.json. (#2783)

* Feature: Option to add apikeys to the DB instead of api.json.

* add api storage type env variable

* code cleanup and simplification.

* md table fixes

---------

Co-authored-by: Henry <hzj94@hotmail.com>
This commit is contained in:
Vinod Kiran
2024-07-26 22:22:12 +05:30
committed by GitHub
parent e5018d2743
commit 2dd1791ec2
26 changed files with 647 additions and 38 deletions
+4 -1
View File
@@ -8,9 +8,12 @@ const updateAPI = (id, body) => client.put(`/apikey/${id}`, body)
const deleteAPI = (id) => client.delete(`/apikey/${id}`)
const importAPI = (body) => client.post(`/apikey/import`, body)
export default {
getAllAPIKeys,
createNewAPI,
updateAPI,
deleteAPI
deleteAPI,
importAPI
}
@@ -0,0 +1,186 @@
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'
// Material
import { Button, Dialog, DialogActions, DialogContent, DialogTitle, Box, Typography, Stack } from '@mui/material'
// Project imports
import { StyledButton } from '@/ui-component/button/StyledButton'
import ConfirmDialog from '@/ui-component/dialog/ConfirmDialog'
import { File } from '@/ui-component/file/File'
// Icons
import { IconFileUpload, IconX } from '@tabler/icons-react'
// API
import apikeyAPI from '@/api/apikey'
// utils
import useNotifier from '@/utils/useNotifier'
// const
import { HIDE_CANVAS_DIALOG, SHOW_CANVAS_DIALOG } from '@/store/actions'
import { Dropdown } from '@/ui-component/dropdown/Dropdown'
const importModes = [
{
label: 'Add & Overwrite',
name: 'overwriteIfExist',
description: 'Add keys and overwrite existing keys with the same name'
},
{
label: 'Add & Ignore',
name: 'ignoreIfExist',
description: 'Add keys and ignore existing keys with the same name'
},
{
label: 'Add & Verify',
name: 'errorIfExist',
description: 'Add Keys and throw error if key with same name exists'
},
{
label: 'Replace All',
name: 'replaceAll',
description: 'Replace all keys with the imported keys'
}
]
const UploadJSONFileDialog = ({ 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 [selectedFile, setSelectedFile] = useState()
const [importMode, setImportMode] = useState('overwrite')
useEffect(() => {
return () => {
setSelectedFile()
}
}, [dialogProps])
useEffect(() => {
if (show) dispatch({ type: SHOW_CANVAS_DIALOG })
else dispatch({ type: HIDE_CANVAS_DIALOG })
return () => dispatch({ type: HIDE_CANVAS_DIALOG })
}, [show, dispatch])
const importKeys = async () => {
try {
const obj = {
importMode: importMode,
jsonFile: selectedFile
}
const createResp = await apikeyAPI.importAPI(obj)
if (createResp.data) {
enqueueSnackbar({
message: 'Imported keys successfully!',
options: {
key: new Date().getTime() + Math.random(),
variant: 'success',
action: (key) => (
<Button style={{ color: 'white' }} onClick={() => closeSnackbar(key)}>
<IconX />
</Button>
)
}
})
onConfirm(createResp.data.id)
}
} catch (error) {
enqueueSnackbar({
message: `Failed to import keys: ${
typeof error.response.data === 'object' ? error.response.data.message : error.response.data
}`,
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'>
<div style={{ display: 'flex', flexDirection: 'row', alignItems: 'center' }}>
<IconFileUpload style={{ marginRight: '10px' }} />
Import API Keys
</div>
</DialogTitle>
<DialogContent>
<Box sx={{ p: 2 }}>
<Stack sx={{ position: 'relative' }} direction='row'>
<Typography variant='overline'>
Import api.json file
<span style={{ color: 'red' }}>&nbsp;*</span>
</Typography>
</Stack>
<File
disabled={false}
fileType='.json'
onChange={(newValue) => setSelectedFile(newValue)}
value={selectedFile ?? 'Choose a file to upload'}
/>
</Box>
<Box sx={{ p: 2 }}>
<Stack sx={{ position: 'relative' }} direction='row'>
<Typography variant='overline'>
Import Mode
<span style={{ color: 'red' }}>&nbsp;*</span>
</Typography>
</Stack>
<Dropdown
key={importMode}
name={importMode}
options={importModes}
onSelect={(newValue) => setImportMode(newValue)}
value={importMode ?? 'choose an option'}
/>
</Box>
</DialogContent>
<DialogActions>
<Button onClick={() => onCancel()}>{dialogProps.cancelButtonName}</Button>
<StyledButton disabled={!selectedFile} variant='contained' onClick={importKeys}>
{dialogProps.confirmButtonName}
</StyledButton>
</DialogActions>
<ConfirmDialog />
</Dialog>
) : null
return createPortal(component, portalElement)
}
UploadJSONFileDialog.propTypes = {
show: PropTypes.bool,
dialogProps: PropTypes.object,
onCancel: PropTypes.func,
onConfirm: PropTypes.func
}
export default UploadJSONFileDialog
+45 -1
View File
@@ -44,8 +44,20 @@ import useConfirm from '@/hooks/useConfirm'
import useNotifier from '@/utils/useNotifier'
// Icons
import { IconTrash, IconEdit, IconCopy, IconChevronsUp, IconChevronsDown, IconX, IconPlus, IconEye, IconEyeOff } from '@tabler/icons-react'
import {
IconTrash,
IconEdit,
IconCopy,
IconChevronsUp,
IconChevronsDown,
IconX,
IconPlus,
IconEye,
IconEyeOff,
IconFileUpload
} from '@tabler/icons-react'
import APIEmptySVG from '@/assets/images/api_empty.svg'
import UploadJSONFileDialog from '@/views/apikey/UploadJSONFileDialog'
// ==============================|| APIKey ||============================== //
@@ -200,6 +212,9 @@ const APIKey = () => {
const [showApiKeys, setShowApiKeys] = useState([])
const openPopOver = Boolean(anchorEl)
const [showUploadDialog, setShowUploadDialog] = useState(false)
const [uploadDialogProps, setUploadDialogProps] = useState({})
const [search, setSearch] = useState('')
const onSearchChange = (event) => {
setSearch(event.target.value)
@@ -254,6 +269,17 @@ const APIKey = () => {
setShowDialog(true)
}
const uploadDialog = () => {
const dialogProp = {
type: 'ADD',
cancelButtonName: 'Cancel',
confirmButtonName: 'Upload',
data: {}
}
setUploadDialogProps(dialogProp)
setShowUploadDialog(true)
}
const deleteKey = async (key) => {
const confirmPayload = {
title: `Delete`,
@@ -308,6 +334,7 @@ const APIKey = () => {
const onConfirm = () => {
setShowDialog(false)
setShowUploadDialog(false)
getAllAPIKeysApi.request()
}
@@ -341,6 +368,15 @@ const APIKey = () => {
) : (
<Stack flexDirection='column' sx={{ gap: 3 }}>
<ViewHeader onSearchChange={onSearchChange} search={true} searchPlaceholder='Search API Keys' title='API Keys'>
<Button
variant='outlined'
sx={{ borderRadius: 2, height: '100%' }}
onClick={uploadDialog}
startIcon={<IconFileUpload />}
id='btn_importApiKeys'
>
Import
</Button>
<StyledButton
variant='contained'
sx={{ borderRadius: 2, height: '100%' }}
@@ -468,6 +504,14 @@ const APIKey = () => {
onConfirm={onConfirm}
setError={setError}
></APIKeyDialog>
{showUploadDialog && (
<UploadJSONFileDialog
show={showUploadDialog}
dialogProps={uploadDialogProps}
onCancel={() => setShowUploadDialog(false)}
onConfirm={onConfirm}
></UploadJSONFileDialog>
)}
<ConfirmDialog />
</>
)