Environment Variables: Dashboard along with CRUD Operations on variables.
This commit is contained in:
parent
bac91eed00
commit
1d1bd4f556
|
|
@ -68,6 +68,15 @@ export interface ICredential {
|
|||
createdDate: Date
|
||||
}
|
||||
|
||||
export interface IVariable {
|
||||
id: string
|
||||
name: string
|
||||
value: string
|
||||
type: string
|
||||
updatedDate: Date
|
||||
createdDate: Date
|
||||
}
|
||||
|
||||
export interface IComponentNodes {
|
||||
[key: string]: INode
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,25 @@
|
|||
/* eslint-disable */
|
||||
import { Entity, Column, CreateDateColumn, UpdateDateColumn, PrimaryGeneratedColumn } from 'typeorm'
|
||||
import { IVariable } from "../../Interface";
|
||||
|
||||
@Entity()
|
||||
export class Variable implements IVariable{
|
||||
@PrimaryGeneratedColumn('uuid')
|
||||
id: string
|
||||
|
||||
@Column()
|
||||
name: string
|
||||
|
||||
@Column({ nullable: true, type: 'text' })
|
||||
value: string
|
||||
|
||||
@Column({default: 'string', type: 'text'})
|
||||
type: string
|
||||
|
||||
|
||||
@CreateDateColumn()
|
||||
createdDate: Date
|
||||
|
||||
@UpdateDateColumn()
|
||||
updatedDate: Date
|
||||
}
|
||||
|
|
@ -3,11 +3,13 @@ import { ChatMessage } from './ChatMessage'
|
|||
import { Credential } from './Credential'
|
||||
import { Tool } from './Tool'
|
||||
import { Assistant } from './Assistant'
|
||||
import { Variable } from './Variable'
|
||||
|
||||
export const entities = {
|
||||
ChatFlow,
|
||||
ChatMessage,
|
||||
Credential,
|
||||
Tool,
|
||||
Assistant
|
||||
Assistant,
|
||||
Variable
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,21 @@
|
|||
import { MigrationInterface, QueryRunner } from 'typeorm'
|
||||
|
||||
export class AddVariableEntity1699325775451 implements MigrationInterface {
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(
|
||||
`CREATE TABLE IF NOT EXISTS \`variable\` (
|
||||
\`id\` varchar(36) NOT NULL,
|
||||
\`name\` varchar(255) NOT NULL,
|
||||
\`value\` text NOT NULL,
|
||||
\`type\` varchar(255) DEFAULT NULL,
|
||||
\`createdDate\` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
|
||||
\`updatedDate\` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6),
|
||||
PRIMARY KEY (\`id\`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;`
|
||||
)
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`DROP TABLE variable`)
|
||||
}
|
||||
}
|
||||
|
|
@ -10,6 +10,7 @@ import { AddAssistantEntity1699325775451 } from './1699325775451-AddAssistantEnt
|
|||
import { AddUsedToolsToChatMessage1699481607341 } from './1699481607341-AddUsedToolsToChatMessage'
|
||||
import { AddCategoryToChatFlow1699900910291 } from './1699900910291-AddCategoryToChatFlow'
|
||||
import { AddFileAnnotationsToChatMessage1700271021237 } from './1700271021237-AddFileAnnotationsToChatMessage'
|
||||
import { AddVariableEntity1699325775451 } from './1702200925471-AddVariableEntity'
|
||||
|
||||
export const mysqlMigrations = [
|
||||
Init1693840429259,
|
||||
|
|
@ -23,5 +24,6 @@ export const mysqlMigrations = [
|
|||
AddAssistantEntity1699325775451,
|
||||
AddUsedToolsToChatMessage1699481607341,
|
||||
AddCategoryToChatFlow1699900910291,
|
||||
AddFileAnnotationsToChatMessage1700271021237
|
||||
AddFileAnnotationsToChatMessage1700271021237,
|
||||
AddVariableEntity1699325775451
|
||||
]
|
||||
|
|
|
|||
|
|
@ -0,0 +1,21 @@
|
|||
import { MigrationInterface, QueryRunner } from 'typeorm'
|
||||
|
||||
export class AddVariableEntity1699325775451 implements MigrationInterface {
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(
|
||||
`CREATE TABLE IF NOT EXISTS variable (
|
||||
id uuid NOT NULL DEFAULT uuid_generate_v4(),
|
||||
"name" varchar NOT NULL,
|
||||
"value" text NOT NULL,
|
||||
"type" text NULL,
|
||||
"createdDate" timestamp NOT NULL DEFAULT now(),
|
||||
"updatedDate" timestamp NOT NULL DEFAULT now(),
|
||||
CONSTRAINT "PK_3c7cea7a044ac4c92764576cdbf" PRIMARY KEY (id)
|
||||
);`
|
||||
)
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`DROP TABLE variable`)
|
||||
}
|
||||
}
|
||||
|
|
@ -10,6 +10,7 @@ import { AddAssistantEntity1699325775451 } from './1699325775451-AddAssistantEnt
|
|||
import { AddUsedToolsToChatMessage1699481607341 } from './1699481607341-AddUsedToolsToChatMessage'
|
||||
import { AddCategoryToChatFlow1699900910291 } from './1699900910291-AddCategoryToChatFlow'
|
||||
import { AddFileAnnotationsToChatMessage1700271021237 } from './1700271021237-AddFileAnnotationsToChatMessage'
|
||||
import { AddVariableEntity1699325775451 } from './1702200925471-AddVariableEntity'
|
||||
|
||||
export const postgresMigrations = [
|
||||
Init1693891895163,
|
||||
|
|
@ -23,5 +24,6 @@ export const postgresMigrations = [
|
|||
AddAssistantEntity1699325775451,
|
||||
AddUsedToolsToChatMessage1699481607341,
|
||||
AddCategoryToChatFlow1699900910291,
|
||||
AddFileAnnotationsToChatMessage1700271021237
|
||||
AddFileAnnotationsToChatMessage1700271021237,
|
||||
AddVariableEntity1699325775451
|
||||
]
|
||||
|
|
|
|||
|
|
@ -0,0 +1,13 @@
|
|||
import { MigrationInterface, QueryRunner } from 'typeorm'
|
||||
|
||||
export class AddVariableEntity1699325775451 implements MigrationInterface {
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(
|
||||
`CREATE TABLE IF NOT EXISTS "variable" ("id" varchar PRIMARY KEY NOT NULL, "name" text NOT NULL, "value" text NOT NULL, "type" varchar, "createdDate" datetime NOT NULL DEFAULT (datetime('now')), "updatedDate" datetime NOT NULL DEFAULT (datetime('now')));`
|
||||
)
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`DROP TABLE variable`)
|
||||
}
|
||||
}
|
||||
|
|
@ -10,6 +10,7 @@ import { AddAssistantEntity1699325775451 } from './1699325775451-AddAssistantEnt
|
|||
import { AddUsedToolsToChatMessage1699481607341 } from './1699481607341-AddUsedToolsToChatMessage'
|
||||
import { AddCategoryToChatFlow1699900910291 } from './1699900910291-AddCategoryToChatFlow'
|
||||
import { AddFileAnnotationsToChatMessage1700271021237 } from './1700271021237-AddFileAnnotationsToChatMessage'
|
||||
import { AddVariableEntity1699325775451 } from './1702200925471-AddVariableEntity'
|
||||
|
||||
export const sqliteMigrations = [
|
||||
Init1693835579790,
|
||||
|
|
@ -23,5 +24,6 @@ export const sqliteMigrations = [
|
|||
AddAssistantEntity1699325775451,
|
||||
AddUsedToolsToChatMessage1699481607341,
|
||||
AddCategoryToChatFlow1699900910291,
|
||||
AddFileAnnotationsToChatMessage1700271021237
|
||||
AddFileAnnotationsToChatMessage1700271021237,
|
||||
AddVariableEntity1699325775451
|
||||
]
|
||||
|
|
|
|||
|
|
@ -62,6 +62,7 @@ import { sanitizeMiddleware } from './utils/XSS'
|
|||
import axios from 'axios'
|
||||
import { Client } from 'langchainhub'
|
||||
import { parsePrompt } from './utils/hub'
|
||||
import { Variable } from "./database/entities/Variable";
|
||||
|
||||
export class App {
|
||||
app: express.Application
|
||||
|
|
@ -1150,6 +1151,47 @@ export class App {
|
|||
return res.json(templates)
|
||||
})
|
||||
|
||||
// ----------------------------------------
|
||||
// Variables
|
||||
// ----------------------------------------
|
||||
this.app.get('/api/v1/variables', async (req: Request, res: Response) => {
|
||||
const variables = await getDataSource().getRepository(Variable).find()
|
||||
return res.json(variables)
|
||||
})
|
||||
|
||||
// Create new variable
|
||||
this.app.post('/api/v1/variables', async (req: Request, res: Response) => {
|
||||
const body = req.body
|
||||
const newVariable = new Variable()
|
||||
Object.assign(newVariable, body)
|
||||
const variable = this.AppDataSource.getRepository(Variable).create(newVariable)
|
||||
const results = await this.AppDataSource.getRepository(Variable).save(variable)
|
||||
return res.json(results)
|
||||
})
|
||||
|
||||
// Update variable
|
||||
this.app.put('/api/v1/variables/:id', async (req: Request, res: Response) => {
|
||||
const variable = await this.AppDataSource.getRepository(Variable).findOneBy({
|
||||
id: req.params.id
|
||||
})
|
||||
|
||||
if (!variable) return res.status(404).send(`Variable ${req.params.id} not found`)
|
||||
|
||||
const body = req.body
|
||||
const updateVariable = new Variable()
|
||||
Object.assign(updateVariable, body)
|
||||
this.AppDataSource.getRepository(Variable).merge(variable, updateVariable)
|
||||
const result = await this.AppDataSource.getRepository(Variable).save(variable)
|
||||
|
||||
return res.json(result)
|
||||
})
|
||||
|
||||
// Delete variable via id
|
||||
this.app.delete('/api/v1/variables/:id', async (req: Request, res: Response) => {
|
||||
const results = await this.AppDataSource.getRepository(Variable).delete({ id: req.params.id })
|
||||
return res.json(results)
|
||||
})
|
||||
|
||||
// ----------------------------------------
|
||||
// API Keys
|
||||
// ----------------------------------------
|
||||
|
|
|
|||
|
|
@ -0,0 +1,16 @@
|
|||
import client from './client'
|
||||
|
||||
const getAllVariables = () => client.get('/variables')
|
||||
|
||||
const createVariable = (body) => client.post(`/variables`, body)
|
||||
|
||||
const updateVariable = (id, body) => client.put(`/variables/${id}`, body)
|
||||
|
||||
const deleteVariable = (id) => client.delete(`/variables/${id}`)
|
||||
|
||||
export default {
|
||||
getAllVariables,
|
||||
createVariable,
|
||||
updateVariable,
|
||||
deleteVariable
|
||||
}
|
||||
|
|
@ -1,8 +1,8 @@
|
|||
// assets
|
||||
import { IconHierarchy, IconBuildingStore, IconKey, IconTool, IconLock, IconRobot } from '@tabler/icons'
|
||||
import { IconHierarchy, IconBuildingStore, IconKey, IconTool, IconLock, IconRobot, IconVariable } from '@tabler/icons'
|
||||
|
||||
// constant
|
||||
const icons = { IconHierarchy, IconBuildingStore, IconKey, IconTool, IconLock, IconRobot }
|
||||
const icons = { IconHierarchy, IconBuildingStore, IconKey, IconTool, IconLock, IconRobot, IconVariable }
|
||||
|
||||
// ==============================|| DASHBOARD MENU ITEMS ||============================== //
|
||||
|
||||
|
|
@ -51,6 +51,14 @@ const dashboard = {
|
|||
icon: icons.IconLock,
|
||||
breadcrumbs: true
|
||||
},
|
||||
{
|
||||
id: 'variables',
|
||||
title: 'Environment Variables',
|
||||
type: 'item',
|
||||
url: '/variables',
|
||||
icon: icons.IconVariable,
|
||||
breadcrumbs: true
|
||||
},
|
||||
{
|
||||
id: 'apikey',
|
||||
title: 'API Keys',
|
||||
|
|
|
|||
|
|
@ -22,6 +22,9 @@ const Assistants = Loadable(lazy(() => import('views/assistants')))
|
|||
// credentials routing
|
||||
const Credentials = Loadable(lazy(() => import('views/credentials')))
|
||||
|
||||
// variables routing
|
||||
const Variables = Loadable(lazy(() => import('views/variables')))
|
||||
|
||||
// ==============================|| MAIN ROUTING ||============================== //
|
||||
|
||||
const MainRoutes = {
|
||||
|
|
@ -55,6 +58,10 @@ const MainRoutes = {
|
|||
{
|
||||
path: '/credentials',
|
||||
element: <Credentials />
|
||||
},
|
||||
{
|
||||
path: '/variables',
|
||||
element: <Variables />
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,265 @@
|
|||
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, OutlinedInput } from '@mui/material'
|
||||
|
||||
// Project imports
|
||||
import { StyledButton } from 'ui-component/button/StyledButton'
|
||||
import ConfirmDialog from 'ui-component/dialog/ConfirmDialog'
|
||||
|
||||
// Icons
|
||||
import { IconX, IconVariable } from '@tabler/icons'
|
||||
|
||||
// API
|
||||
import variablesApi from 'api/variables'
|
||||
|
||||
// Hooks
|
||||
|
||||
// utils
|
||||
import useNotifier from 'utils/useNotifier'
|
||||
|
||||
// const
|
||||
import { HIDE_CANVAS_DIALOG, SHOW_CANVAS_DIALOG } from 'store/actions'
|
||||
import { TooltipWithParser } from '../../ui-component/tooltip/TooltipWithParser'
|
||||
import { Dropdown } from '../../ui-component/dropdown/Dropdown'
|
||||
|
||||
const variableTypes = [
|
||||
{
|
||||
label: 'Static Variable',
|
||||
name: 'static'
|
||||
},
|
||||
{
|
||||
label: 'Runtime Variable',
|
||||
name: 'runtime'
|
||||
}
|
||||
]
|
||||
|
||||
const AddEditVariableDialog = ({ 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 [name, setName] = useState('')
|
||||
const [value, setValue] = useState('')
|
||||
const [type, setType] = useState('')
|
||||
|
||||
const [variable, setVariable] = useState({})
|
||||
|
||||
useEffect(() => {
|
||||
if (dialogProps.type === 'EDIT' && dialogProps.data) {
|
||||
// When variable dialog is opened from Variables dashboard
|
||||
setName(dialogProps.data.name)
|
||||
setValue(dialogProps.data.value)
|
||||
setType(dialogProps.data.type)
|
||||
//setVariable(dialogProps.data)
|
||||
} else if (dialogProps.type === 'ADD' && dialogProps.data) {
|
||||
// When variable dialog is to add a new variable
|
||||
setName('')
|
||||
setValue('')
|
||||
setType('static')
|
||||
//setVariable({ name: '', value: '', type: 'static' })
|
||||
}
|
||||
|
||||
// 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 addNewVariable = async () => {
|
||||
try {
|
||||
const obj = {
|
||||
name,
|
||||
value,
|
||||
type
|
||||
}
|
||||
const createResp = await variablesApi.createVariable(obj)
|
||||
if (createResp.data) {
|
||||
enqueueSnackbar({
|
||||
message: 'New Variable added',
|
||||
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 (err) {
|
||||
const errorData = typeof err === 'string' ? err : err.response?.data || `${err.response?.status}: ${err.response?.statusText}`
|
||||
enqueueSnackbar({
|
||||
message: `Failed to add new Variable: ${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 saveVariable = async () => {
|
||||
try {
|
||||
const saveObj = {
|
||||
name,
|
||||
value,
|
||||
type
|
||||
}
|
||||
|
||||
const saveResp = await variablesApi.updateVariable(variable.id, saveObj)
|
||||
if (saveResp.data) {
|
||||
enqueueSnackbar({
|
||||
message: 'Variable saved',
|
||||
options: {
|
||||
key: new Date().getTime() + Math.random(),
|
||||
variant: 'success',
|
||||
action: (key) => (
|
||||
<Button style={{ color: 'white' }} onClick={() => closeSnackbar(key)}>
|
||||
<IconX />
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
})
|
||||
onConfirm(saveResp.data.id)
|
||||
}
|
||||
} catch (error) {
|
||||
const errorData = error.response?.data || `${error.response?.status}: ${error.response?.statusText}`
|
||||
enqueueSnackbar({
|
||||
message: `Failed to save Variable: ${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'>
|
||||
<div style={{ display: 'flex', flexDirection: 'row', alignItems: 'center' }}>
|
||||
<div
|
||||
style={{
|
||||
width: 50,
|
||||
height: 50,
|
||||
marginRight: 10,
|
||||
borderRadius: '50%',
|
||||
backgroundColor: 'white'
|
||||
}}
|
||||
>
|
||||
<IconVariable
|
||||
style={{
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
padding: 7,
|
||||
borderRadius: '50%',
|
||||
objectFit: 'contain'
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
{dialogProps.type === 'ADD' ? 'Add Variable' : 'Edit Variable'}
|
||||
</div>
|
||||
</DialogTitle>
|
||||
<DialogContent>
|
||||
<Box sx={{ p: 2 }}>
|
||||
<div style={{ display: 'flex', flexDirection: 'row' }}>
|
||||
<Typography>
|
||||
Variable Name<span style={{ color: 'red' }}> *</span>
|
||||
</Typography>
|
||||
|
||||
<div style={{ flexGrow: 1 }}></div>
|
||||
</div>
|
||||
<OutlinedInput type='string' fullWidth key='name' onChange={(e) => setName(e.target.value)} value={name ?? ''} />
|
||||
</Box>
|
||||
<Box sx={{ p: 2 }}>
|
||||
<div style={{ display: 'flex', flexDirection: 'row' }}>
|
||||
<Typography>
|
||||
Value
|
||||
<TooltipWithParser style={{ marginLeft: 10 }} title='Value is mandatory for static variables.' />
|
||||
</Typography>
|
||||
<div style={{ flexGrow: 1 }}></div>
|
||||
</div>
|
||||
<OutlinedInput type='string' fullWidth key='value' onChange={(e) => setValue(e.target.value)} value={value ?? ''} />
|
||||
<Typography variant='subtitle2'>Leave the value empty for runtime variables. Will be populated at runtime.</Typography>
|
||||
</Box>
|
||||
<Box sx={{ p: 2 }}>
|
||||
<div style={{ display: 'flex', flexDirection: 'row' }}>
|
||||
<Typography>
|
||||
Type<span style={{ color: 'red' }}> *</span>
|
||||
</Typography>
|
||||
<div style={{ flexGrow: 1 }}></div>
|
||||
</div>
|
||||
<Dropdown
|
||||
key='type'
|
||||
name='variable-type'
|
||||
options={variableTypes}
|
||||
onSelect={(newValue) => setType(newValue)}
|
||||
value={type ?? 'static'}
|
||||
/>
|
||||
<Typography variant='subtitle2'>
|
||||
Runtime: Value would be populated from env. Static: Value would be used as is.
|
||||
</Typography>
|
||||
</Box>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<StyledButton
|
||||
disabled={!name}
|
||||
variant='contained'
|
||||
onClick={() => (dialogProps.type === 'ADD' ? addNewVariable() : saveVariable())}
|
||||
>
|
||||
{dialogProps.confirmButtonName}
|
||||
</StyledButton>
|
||||
</DialogActions>
|
||||
<ConfirmDialog />
|
||||
</Dialog>
|
||||
) : null
|
||||
|
||||
return createPortal(component, portalElement)
|
||||
}
|
||||
|
||||
AddEditVariableDialog.propTypes = {
|
||||
show: PropTypes.bool,
|
||||
dialogProps: PropTypes.object,
|
||||
onCancel: PropTypes.func,
|
||||
onConfirm: PropTypes.func
|
||||
}
|
||||
|
||||
export default AddEditVariableDialog
|
||||
|
|
@ -0,0 +1,301 @@
|
|||
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,
|
||||
Toolbar,
|
||||
TextField,
|
||||
InputAdornment,
|
||||
ButtonGroup
|
||||
} 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 ConfirmDialog from 'ui-component/dialog/ConfirmDialog'
|
||||
|
||||
// API
|
||||
import variablesApi from 'api/variables'
|
||||
|
||||
// Hooks
|
||||
import useApi from 'hooks/useApi'
|
||||
import useConfirm from 'hooks/useConfirm'
|
||||
|
||||
// utils
|
||||
import useNotifier from 'utils/useNotifier'
|
||||
|
||||
// Icons
|
||||
import { IconTrash, IconEdit, IconX, IconPlus, IconSearch, IconVariable } from '@tabler/icons'
|
||||
import CredentialEmptySVG from 'assets/images/credential_empty.svg'
|
||||
|
||||
// const
|
||||
import AddEditVariableDialog from './AddEditVariableDialog'
|
||||
|
||||
// ==============================|| Credentials ||============================== //
|
||||
|
||||
const Variables = () => {
|
||||
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 [showVariableDialog, setShowVariableDialog] = useState(false)
|
||||
const [variableDialogProps, setVariableDialogProps] = useState({})
|
||||
const [variables, setVariables] = useState([])
|
||||
|
||||
const { confirm } = useConfirm()
|
||||
|
||||
const getAllVariables = useApi(variablesApi.getAllVariables)
|
||||
|
||||
const [search, setSearch] = useState('')
|
||||
const onSearchChange = (event) => {
|
||||
setSearch(event.target.value)
|
||||
}
|
||||
function filterVariables(data) {
|
||||
return data.name.toLowerCase().indexOf(search.toLowerCase()) > -1
|
||||
}
|
||||
|
||||
const addNew = () => {
|
||||
const dialogProp = {
|
||||
type: 'ADD',
|
||||
cancelButtonName: 'Cancel',
|
||||
confirmButtonName: 'Add',
|
||||
data: {}
|
||||
}
|
||||
setVariableDialogProps(dialogProp)
|
||||
setShowVariableDialog(true)
|
||||
}
|
||||
|
||||
const edit = (variable) => {
|
||||
const dialogProp = {
|
||||
type: 'EDIT',
|
||||
cancelButtonName: 'Cancel',
|
||||
confirmButtonName: 'Save',
|
||||
data: variable
|
||||
}
|
||||
setVariableDialogProps(dialogProp)
|
||||
setShowVariableDialog(true)
|
||||
}
|
||||
|
||||
const deleteVariable = async (credential) => {
|
||||
const confirmPayload = {
|
||||
title: `Delete`,
|
||||
description: `Delete variable ${variable.name}?`,
|
||||
confirmButtonName: 'Delete',
|
||||
cancelButtonName: 'Cancel'
|
||||
}
|
||||
const isConfirmed = await confirm(confirmPayload)
|
||||
|
||||
if (isConfirmed) {
|
||||
try {
|
||||
const deleteResp = await variablesApi.deleteVariable(credential.id)
|
||||
if (deleteResp.data) {
|
||||
enqueueSnackbar({
|
||||
message: 'Variable 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 Variable: ${errorData}`,
|
||||
options: {
|
||||
key: new Date().getTime() + Math.random(),
|
||||
variant: 'error',
|
||||
persist: true,
|
||||
action: (key) => (
|
||||
<Button style={{ color: 'white' }} onClick={() => closeSnackbar(key)}>
|
||||
<IconX />
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const onConfirm = () => {
|
||||
setShowVariableDialog(false)
|
||||
getAllVariables.request()
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
getAllVariables.request()
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
if (getAllVariables.data) {
|
||||
setVariables(getAllVariables.data)
|
||||
}
|
||||
}, [getAllVariables.data])
|
||||
|
||||
return (
|
||||
<>
|
||||
<MainCard sx={{ background: customization.isDarkMode ? theme.palette.common.black : '' }}>
|
||||
<Stack flexDirection='row'>
|
||||
<Box sx={{ flexGrow: 1 }}>
|
||||
<Toolbar
|
||||
disableGutters={true}
|
||||
style={{
|
||||
margin: 1,
|
||||
padding: 1,
|
||||
paddingBottom: 10,
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
width: '100%'
|
||||
}}
|
||||
>
|
||||
<h1>Environment Variables </h1>
|
||||
<TextField
|
||||
size='small'
|
||||
sx={{ display: { xs: 'none', sm: 'block' }, ml: 3 }}
|
||||
variant='outlined'
|
||||
placeholder='Search variable name'
|
||||
onChange={onSearchChange}
|
||||
InputProps={{
|
||||
startAdornment: (
|
||||
<InputAdornment position='start'>
|
||||
<IconSearch />
|
||||
</InputAdornment>
|
||||
)
|
||||
}}
|
||||
/>
|
||||
<Box sx={{ flexGrow: 1 }} />
|
||||
<ButtonGroup
|
||||
sx={{ maxHeight: 40 }}
|
||||
disableElevation
|
||||
variant='contained'
|
||||
aria-label='outlined primary button group'
|
||||
>
|
||||
<ButtonGroup disableElevation aria-label='outlined primary button group'>
|
||||
<StyledButton
|
||||
variant='contained'
|
||||
sx={{ color: 'white', mr: 1, height: 37 }}
|
||||
onClick={addNew}
|
||||
startIcon={<IconPlus />}
|
||||
>
|
||||
Add Variable
|
||||
</StyledButton>
|
||||
</ButtonGroup>
|
||||
</ButtonGroup>
|
||||
</Toolbar>
|
||||
</Box>
|
||||
</Stack>
|
||||
{variables.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={CredentialEmptySVG}
|
||||
alt='CredentialEmptySVG'
|
||||
/>
|
||||
</Box>
|
||||
<div>No Variables Yet</div>
|
||||
</Stack>
|
||||
)}
|
||||
{variables.length > 0 && (
|
||||
<TableContainer component={Paper}>
|
||||
<Table sx={{ minWidth: 650 }} aria-label='simple table'>
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
<TableCell>Name</TableCell>
|
||||
<TableCell>Value</TableCell>
|
||||
<TableCell>Type</TableCell>
|
||||
<TableCell>Last Updated</TableCell>
|
||||
<TableCell>Created</TableCell>
|
||||
<TableCell> </TableCell>
|
||||
<TableCell> </TableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{variables.filter(filterVariables).map((variable, index) => (
|
||||
<TableRow key={index} sx={{ '&:last-child td, &:last-child th': { border: 0 } }}>
|
||||
<TableCell component='th' scope='row'>
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center'
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
width: 25,
|
||||
height: 25,
|
||||
marginRight: 10,
|
||||
borderRadius: '50%'
|
||||
}}
|
||||
>
|
||||
<IconVariable
|
||||
style={{
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
borderRadius: '50%',
|
||||
objectFit: 'contain'
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
{variable.name}
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCell>{variable.value}</TableCell>
|
||||
<TableCell>{variable.type === 'static' ? 'Static Variable' : 'Runtime Variable'}</TableCell>
|
||||
<TableCell>{moment(variable.updatedDate).format('DD-MMM-YY')}</TableCell>
|
||||
<TableCell>{moment(variable.createdDate).format('DD-MMM-YY')}</TableCell>
|
||||
<TableCell>
|
||||
<IconButton title='Edit' color='primary' onClick={() => edit(variable)}>
|
||||
<IconEdit />
|
||||
</IconButton>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<IconButton title='Delete' color='error' onClick={() => deleteVariable(variable)}>
|
||||
<IconTrash />
|
||||
</IconButton>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
)}
|
||||
</MainCard>
|
||||
<AddEditVariableDialog
|
||||
show={showVariableDialog}
|
||||
dialogProps={variableDialogProps}
|
||||
onCancel={() => setShowVariableDialog(false)}
|
||||
onConfirm={onConfirm}
|
||||
></AddEditVariableDialog>
|
||||
<ConfirmDialog />
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default Variables
|
||||
Loading…
Reference in New Issue