material
This commit is contained in:
@@ -1,145 +1,746 @@
|
||||
import React, { useState, useEffect, useRef } from "react";
|
||||
import { Box, Button } from "@mui/material";
|
||||
import { DataGrid, GridToolbarContainer } from "@mui/x-data-grid";
|
||||
import { BsThreeDotsVertical } from "react-icons/bs"; // Importing react-icons
|
||||
|
||||
const api = process.env.REACT_APP_API_BASE_URL;
|
||||
|
||||
function CustomToolbar({ apiRef, handleModal }) {
|
||||
const handleGoToPage1 = () => {
|
||||
if (apiRef.current) {
|
||||
apiRef.current.setPage(1);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<GridToolbarContainer className="flex flex-wrap justify-between p-2 bg-gray-100 border-b border-gray-200">
|
||||
<Button
|
||||
onClick={handleGoToPage1}
|
||||
className="bg-blue-500 text-white px-4 py-2 rounded shadow hover:bg-blue-600 m-1"
|
||||
>
|
||||
Go to page 1
|
||||
</Button>
|
||||
<Button
|
||||
onClick={handleModal}
|
||||
className="bg-green-500 text-white px-4 py-2 rounded shadow hover:bg-green-600 m-1"
|
||||
>
|
||||
+
|
||||
</Button>
|
||||
</GridToolbarContainer>
|
||||
);
|
||||
}
|
||||
|
||||
function TokenRegistry() {
|
||||
const [menuItems, setMenuItems] = useState([]);
|
||||
const [selectedMenuItem, setSelectedMenuItem] = useState(null);
|
||||
const [, setIsModalOpen] = useState(false);
|
||||
const apiRef = useRef(null);
|
||||
|
||||
useEffect(() => {
|
||||
const fetchData = async () => {
|
||||
const token = localStorage.getItem("token"); // Get token from local storage
|
||||
try {
|
||||
const response = await fetch(
|
||||
`${api}/apiregistery/getall`,
|
||||
{
|
||||
headers: {
|
||||
Authorization: `Bearer ${token}`,
|
||||
},
|
||||
}
|
||||
);
|
||||
const data = await response.json();
|
||||
setMenuItems(data);
|
||||
} catch (error) {
|
||||
console.error("Error fetching data:", error);
|
||||
}
|
||||
};
|
||||
|
||||
fetchData();
|
||||
}, []);
|
||||
|
||||
const handleThreeDotsClick = (menuItemId) => {
|
||||
setSelectedMenuItem(menuItemId === selectedMenuItem ? null : menuItemId);
|
||||
};
|
||||
|
||||
const columns = [
|
||||
{
|
||||
field: "table_id",
|
||||
headerName: "Table ID",
|
||||
width: 200,
|
||||
headerClassName: "custom-header",
|
||||
cellClassName: "custom-cell",
|
||||
},
|
||||
{
|
||||
field: "token_name",
|
||||
headerName: "Token Name",
|
||||
width: 250,
|
||||
headerClassName: "custom-header",
|
||||
cellClassName: "custom-cell",
|
||||
},
|
||||
{
|
||||
field: "token",
|
||||
headerName: "Token",
|
||||
width: 200,
|
||||
headerClassName: "custom-header",
|
||||
cellClassName: "custom-cell",
|
||||
},
|
||||
{
|
||||
field: "actions",
|
||||
headerName: "Actions",
|
||||
width: 150,
|
||||
renderCell: ({ row }) => (
|
||||
<div className="relative">
|
||||
<div
|
||||
className="cursor-pointer"
|
||||
onClick={() => handleThreeDotsClick(row.id)}
|
||||
>
|
||||
<BsThreeDotsVertical /> {/* Updated icon from react-icons */}
|
||||
</div>
|
||||
{selectedMenuItem === row.id && (
|
||||
<div className="absolute right-0 mt-2 py-2 w-48 bg-white rounded-lg shadow-xl">
|
||||
<button className="block px-4 py-2 text-gray-800 hover:bg-gray-200 w-full text-left">
|
||||
Edit
|
||||
</button>
|
||||
<button className="block px-4 py-2 text-gray-800 hover:bg-gray-200 w-full text-left">
|
||||
Delete
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<div className="flex justify-center mt-5 px-2 md:px-0">
|
||||
<Box className="w-full max-w-7xl bg-gray-50 p-4 md:p-6 rounded shadow-md">
|
||||
<p className="text-2xl md:text-3xl text-center text-white bg-gray-400 mb-4 p-3">
|
||||
Token Registry
|
||||
</p>
|
||||
<div className="bg-white p-2 md:p-4 rounded shadow-md">
|
||||
<DataGrid
|
||||
rows={menuItems}
|
||||
columns={columns}
|
||||
components={{
|
||||
Toolbar: () => (
|
||||
<CustomToolbar
|
||||
apiRef={apiRef}
|
||||
handleModal={() => setIsModalOpen(true)}
|
||||
/>
|
||||
),
|
||||
}}
|
||||
pageSize={10}
|
||||
onGridReady={(gridApi) => {
|
||||
apiRef.current = gridApi;
|
||||
}}
|
||||
className="data-grid"
|
||||
/>
|
||||
</div>
|
||||
</Box>
|
||||
{/* Add your modals and other components here */}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default TokenRegistry;
|
||||
import React, { useState, useEffect } from "react";
|
||||
import {
|
||||
Button,
|
||||
Modal,
|
||||
FormControl,
|
||||
InputLabel,
|
||||
Select,
|
||||
MenuItem,
|
||||
TextField,
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableContainer,
|
||||
TableHead,
|
||||
TableRow,
|
||||
Paper,
|
||||
Pagination,
|
||||
IconButton,
|
||||
Checkbox,
|
||||
FormControlLabel,
|
||||
Chip,
|
||||
Box,
|
||||
Typography,
|
||||
Card,
|
||||
CardHeader,
|
||||
CardContent,
|
||||
Divider,
|
||||
Grid,
|
||||
TextareaAutosize,
|
||||
Popover,
|
||||
List,
|
||||
ListItem,
|
||||
ListItemText,
|
||||
ListItemIcon,
|
||||
Dialog,
|
||||
DialogTitle,
|
||||
DialogContent,
|
||||
DialogActions,
|
||||
DialogContentText,
|
||||
CircularProgress
|
||||
} from "@mui/material";
|
||||
import {
|
||||
Edit as EditIcon,
|
||||
Delete as DeleteIcon,
|
||||
Add as AddIcon,
|
||||
Menu as MenuIcon,
|
||||
Close as CloseIcon,
|
||||
Search as SearchIcon,
|
||||
LibraryBooks as LibraryBooksIcon,
|
||||
CheckCircle as CheckCircleIcon
|
||||
} from "@mui/icons-material";
|
||||
import { toast } from "react-toastify";
|
||||
import * as tokenRegistryAPI from './tokenregistryapi';
|
||||
|
||||
function TOKENRegistry() {
|
||||
const [tokens, setTokens] = useState([]);
|
||||
const [showAddEditModal, setShowAddEditModal] = useState(false);
|
||||
const [showGenerateTokenModal, setShowGenerateTokenModal] = useState(false);
|
||||
const [newTokenName, setNewTokenName] = useState("");
|
||||
const [generatedToken, setGeneratedToken] = useState("");
|
||||
const [currentToken, setCurrentToken] = useState({
|
||||
id: "",
|
||||
tokenName: "",
|
||||
tokenValue: "",
|
||||
isActive: true,
|
||||
scopes: []
|
||||
});
|
||||
const [currentPage, setCurrentPage] = useState(1);
|
||||
const [searchQuery, setSearchQuery] = useState("");
|
||||
const [isEditing, setIsEditing] = useState(false);
|
||||
const [recordsPerPage, setRecordsPerPage] = useState(10);
|
||||
const [visibleColumns, setVisibleColumns] = useState({
|
||||
id: true,
|
||||
tokenName: true,
|
||||
tokenValue: true,
|
||||
scopes: true,
|
||||
isActive: true,
|
||||
actions: true
|
||||
});
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [selectedScope, setSelectedScope] = useState("");
|
||||
const [selectedScopes, setSelectedScopes] = useState([]);
|
||||
const [availableScopes] = useState([
|
||||
{ value: 'read', label: 'Read Access' },
|
||||
{ value: 'write', label: 'Write Access' },
|
||||
{ value: 'delete', label: 'Delete Access' },
|
||||
{ value: 'admin', label: 'Admin Access' },
|
||||
]);
|
||||
const [anchorEl, setAnchorEl] = useState(null);
|
||||
const [columnsAnchorEl, setColumnsAnchorEl] = useState(null);
|
||||
const [deleteConfirmOpen, setDeleteConfirmOpen] = useState(false);
|
||||
const [tokenToDelete, setTokenToDelete] = useState(null);
|
||||
|
||||
useEffect(() => {
|
||||
fetchTokens();
|
||||
}, []);
|
||||
|
||||
const fetchTokens = async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
const data = await tokenRegistryAPI.fetchAllTokens();
|
||||
setTokens(data);
|
||||
} catch (error) {
|
||||
handleApiError(error, "fetch tokens");
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleApiError = (error, action) => {
|
||||
if (error.message === 'Unauthorized') {
|
||||
toast.error("Your session has expired. Please log in again.");
|
||||
} else {
|
||||
toast.error(`Failed to ${action}`);
|
||||
console.error(`Error ${action}:`, error);
|
||||
}
|
||||
};
|
||||
|
||||
const toggleColumn = (column) => {
|
||||
setVisibleColumns(prev => ({
|
||||
...prev,
|
||||
[column]: !prev[column],
|
||||
}));
|
||||
};
|
||||
|
||||
const handleInputChange = (event) => {
|
||||
const { name, value, type, checked } = event.target;
|
||||
setCurrentToken(prev => ({
|
||||
...prev,
|
||||
[name]: type === "checkbox" ? checked : value
|
||||
}));
|
||||
};
|
||||
|
||||
const handleSearch = (query) => {
|
||||
setSearchQuery(query);
|
||||
setCurrentPage(1);
|
||||
};
|
||||
|
||||
const handleClose = () => {
|
||||
setShowAddEditModal(false);
|
||||
setShowGenerateTokenModal(false);
|
||||
setGeneratedToken("");
|
||||
setNewTokenName("");
|
||||
setSelectedScopes([]);
|
||||
};
|
||||
|
||||
const handleRecordsPerPageChange = (number) => {
|
||||
setRecordsPerPage(number);
|
||||
setCurrentPage(1);
|
||||
setAnchorEl(null);
|
||||
};
|
||||
|
||||
const filteredTokens = tokens.filter(
|
||||
(item) =>
|
||||
item.tokenName &&
|
||||
item.tokenName.toLowerCase().includes(searchQuery.toLowerCase())
|
||||
);
|
||||
|
||||
const totalPages = Math.ceil(filteredTokens.length / recordsPerPage);
|
||||
const handlePageChange = (event, pageNumber) => {
|
||||
setCurrentPage(pageNumber);
|
||||
};
|
||||
|
||||
const slicedTokens = filteredTokens.slice(
|
||||
(currentPage - 1) * recordsPerPage,
|
||||
currentPage * recordsPerPage
|
||||
);
|
||||
|
||||
const handleSubmit = async (event) => {
|
||||
event.preventDefault();
|
||||
try {
|
||||
if (isEditing) {
|
||||
await tokenRegistryAPI.updateToken(currentToken.id, currentToken);
|
||||
toast.success("Token updated successfully!");
|
||||
} else {
|
||||
await tokenRegistryAPI.createToken(currentToken);
|
||||
toast.success("Token added successfully!");
|
||||
}
|
||||
setShowAddEditModal(false);
|
||||
fetchTokens();
|
||||
} catch (error) {
|
||||
handleApiError(error, isEditing ? "update token" : "add token");
|
||||
}
|
||||
};
|
||||
|
||||
const openModal = (token = { id: "", tokenName: "", tokenValue: "", isActive: false, scopes: [] }) => {
|
||||
setIsEditing(!!token.id);
|
||||
setCurrentToken(token);
|
||||
setSelectedScopes(token.scopes || []);
|
||||
setShowAddEditModal(true);
|
||||
};
|
||||
|
||||
const handleDeleteClick = (id) => {
|
||||
setTokenToDelete(id);
|
||||
setDeleteConfirmOpen(true);
|
||||
};
|
||||
|
||||
const handleDeleteConfirm = async () => {
|
||||
try {
|
||||
await tokenRegistryAPI.deleteToken(tokenToDelete);
|
||||
toast.success('Token deleted successfully');
|
||||
fetchTokens();
|
||||
} catch (error) {
|
||||
handleApiError(error, "delete token");
|
||||
} finally {
|
||||
setDeleteConfirmOpen(false);
|
||||
setTokenToDelete(null);
|
||||
}
|
||||
};
|
||||
|
||||
const addScope = () => {
|
||||
if (selectedScope && !selectedScopes.includes(selectedScope)) {
|
||||
setSelectedScopes([...selectedScopes, selectedScope]);
|
||||
setSelectedScope("");
|
||||
}
|
||||
};
|
||||
|
||||
const removeScope = (scopeToRemove) => {
|
||||
setSelectedScopes(selectedScopes.filter(scope => scope !== scopeToRemove));
|
||||
};
|
||||
|
||||
const generateNewToken = async () => {
|
||||
if (!newTokenName.trim()) {
|
||||
toast.error("Please enter a token name");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const data = await tokenRegistryAPI.generateToken({
|
||||
name: newTokenName,
|
||||
scopes: selectedScopes
|
||||
});
|
||||
setGeneratedToken(data.tokenValue);
|
||||
toast.success("Token generated successfully!");
|
||||
fetchTokens();
|
||||
} catch (error) {
|
||||
handleApiError(error, "generate token");
|
||||
}
|
||||
};
|
||||
|
||||
const handleMenuClick = (event) => {
|
||||
setAnchorEl(event.currentTarget);
|
||||
};
|
||||
|
||||
const handleMenuClose = () => {
|
||||
setAnchorEl(null);
|
||||
};
|
||||
|
||||
const handleColumnsMenuClick = (event) => {
|
||||
setColumnsAnchorEl(event.currentTarget);
|
||||
};
|
||||
|
||||
const handleColumnsMenuClose = () => {
|
||||
setColumnsAnchorEl(null);
|
||||
};
|
||||
|
||||
return (
|
||||
<Box sx={{ marginTop: "1rem", padding: 2 }}>
|
||||
{loading ? (
|
||||
<Box display="flex" justifyContent="center" alignItems="center" minHeight="200px">
|
||||
<CircularProgress />
|
||||
</Box>
|
||||
) : (
|
||||
<Box>
|
||||
{/* Token Generation Card */}
|
||||
<Card sx={{ mb: 4, boxShadow: 3 }}>
|
||||
<CardHeader
|
||||
title="Token Management"
|
||||
action={
|
||||
<Button
|
||||
variant="contained"
|
||||
onClick={() => setShowGenerateTokenModal(true)}
|
||||
sx={{ backgroundColor: '#1976d2', color: 'white' }}
|
||||
>
|
||||
Generate New Token
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
<CardContent>
|
||||
<Typography variant="body1">
|
||||
Manage your API tokens here. Generate new tokens or delete existing ones.
|
||||
</Typography>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Box display="flex" justifyContent="space-between" alignItems="center" mb={4}>
|
||||
<Typography variant="h4" component="h1" sx={{ fontWeight: 'bold' }}>
|
||||
Token Registry
|
||||
</Typography>
|
||||
</Box>
|
||||
|
||||
<Grid container spacing={2} alignItems="center" mb={3}>
|
||||
<Grid item xs={12} md={8} lg={6}>
|
||||
<TextField
|
||||
fullWidth
|
||||
variant="outlined"
|
||||
placeholder="Search"
|
||||
value={searchQuery}
|
||||
onChange={(e) => handleSearch(e.target.value)}
|
||||
InputProps={{
|
||||
startAdornment: (
|
||||
<SearchIcon sx={{ color: 'action.active', mr: 1 }} />
|
||||
),
|
||||
sx: {
|
||||
borderRadius: '10px',
|
||||
boxShadow: '0px 4px 12px rgba(0, 0, 0, 0.1)',
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</Grid>
|
||||
|
||||
<Grid item xs={12} md={4} lg={6} display="flex" justifyContent="flex-end">
|
||||
<IconButton
|
||||
onClick={() => openModal()}
|
||||
color="primary"
|
||||
sx={{ mr: 2 }}
|
||||
>
|
||||
<AddIcon />
|
||||
</IconButton>
|
||||
<IconButton
|
||||
onClick={handleColumnsMenuClick}
|
||||
color="primary"
|
||||
>
|
||||
<MenuIcon />
|
||||
</IconButton>
|
||||
</Grid>
|
||||
</Grid>
|
||||
|
||||
<TableContainer component={Paper} sx={{ boxShadow: 3 }}>
|
||||
<Table>
|
||||
<TableHead sx={{ backgroundColor: '#f5f5f5' }}>
|
||||
<TableRow>
|
||||
{Object.keys(visibleColumns).filter(key => visibleColumns[key]).map(key => (
|
||||
<TableCell key={key}>
|
||||
{key.charAt(0).toUpperCase() + key.slice(1)}
|
||||
</TableCell>
|
||||
))}
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{slicedTokens.length === 0 ? (
|
||||
<TableRow>
|
||||
<TableCell
|
||||
colSpan={Object.keys(visibleColumns).filter(key => visibleColumns[key]).length}
|
||||
align="center"
|
||||
>
|
||||
No Data Available
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
) : (
|
||||
slicedTokens.map((token, index) => (
|
||||
<TableRow key={index} hover>
|
||||
{Object.keys(visibleColumns).filter(key => visibleColumns[key]).map(key => (
|
||||
<TableCell key={key}>
|
||||
{key === "actions" ? (
|
||||
<>
|
||||
<IconButton
|
||||
onClick={() => openModal(token)}
|
||||
color="primary"
|
||||
sx={{ mr: 1 }}
|
||||
>
|
||||
<EditIcon />
|
||||
</IconButton>
|
||||
<IconButton
|
||||
onClick={() => handleDeleteClick(token.id)}
|
||||
color="error"
|
||||
>
|
||||
<DeleteIcon />
|
||||
</IconButton>
|
||||
</>
|
||||
) : key === "isActive" ? (
|
||||
<Chip
|
||||
label={token.isActive ? "Active" : "Inactive"}
|
||||
color={token.isActive ? "success" : "error"}
|
||||
size="small"
|
||||
sx={{
|
||||
backgroundColor: token.isActive ? '#d4f7d4' : '#f7d4d4',
|
||||
color: token.isActive ? 'green' : 'red'
|
||||
}}
|
||||
/>
|
||||
) : key === "scopes" ? (
|
||||
<Box>
|
||||
{token.scopes && token.scopes.map(scope => {
|
||||
const scopeInfo = availableScopes.find(s => s.value === scope);
|
||||
return (
|
||||
<Chip
|
||||
key={scope}
|
||||
label={scopeInfo?.label || scope}
|
||||
color="primary"
|
||||
size="small"
|
||||
sx={{ mr: 1, mb: 1 }}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</Box>
|
||||
) : (
|
||||
token[key]
|
||||
)}
|
||||
</TableCell>
|
||||
))}
|
||||
</TableRow>
|
||||
))
|
||||
)}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
|
||||
{/* Manage Columns */}
|
||||
<Popover
|
||||
open={Boolean(columnsAnchorEl)}
|
||||
anchorEl={columnsAnchorEl}
|
||||
onClose={handleColumnsMenuClose}
|
||||
anchorOrigin={{
|
||||
vertical: 'bottom',
|
||||
horizontal: 'right',
|
||||
}}
|
||||
transformOrigin={{
|
||||
vertical: 'top',
|
||||
horizontal: 'right',
|
||||
}}
|
||||
>
|
||||
<List>
|
||||
{Object.keys(visibleColumns).map((column) => (
|
||||
<ListItem key={column} dense button onClick={() => toggleColumn(column)}>
|
||||
<ListItemIcon>
|
||||
<Checkbox
|
||||
edge="start"
|
||||
checked={visibleColumns[column]}
|
||||
tabIndex={-1}
|
||||
disableRipple
|
||||
/>
|
||||
</ListItemIcon>
|
||||
<ListItemText primary={column.charAt(0).toUpperCase() + column.slice(1).toLowerCase()} />
|
||||
</ListItem>
|
||||
))}
|
||||
</List>
|
||||
</Popover>
|
||||
|
||||
{/* Records Per Page */}
|
||||
<Box display="flex" justifyContent="flex-end" mt={2}>
|
||||
<Button
|
||||
variant="outlined"
|
||||
startIcon={<LibraryBooksIcon />}
|
||||
onClick={handleMenuClick}
|
||||
sx={{ border: '2px solid', borderRadius: '8px', boxShadow: 1 }}
|
||||
>
|
||||
{recordsPerPage}
|
||||
</Button>
|
||||
<Popover
|
||||
open={Boolean(anchorEl)}
|
||||
anchorEl={anchorEl}
|
||||
onClose={handleMenuClose}
|
||||
anchorOrigin={{
|
||||
vertical: 'bottom',
|
||||
horizontal: 'right',
|
||||
}}
|
||||
transformOrigin={{
|
||||
vertical: 'top',
|
||||
horizontal: 'right',
|
||||
}}
|
||||
>
|
||||
<List>
|
||||
{[1, 5, 10, 20, 50].map((number) => (
|
||||
<ListItem
|
||||
key={number}
|
||||
dense
|
||||
button
|
||||
onClick={() => handleRecordsPerPageChange(number)}
|
||||
sx={{ minWidth: '100px' }}
|
||||
>
|
||||
<ListItemText primary={number} />
|
||||
{recordsPerPage === number && <CheckCircleIcon color="primary" />}
|
||||
</ListItem>
|
||||
))}
|
||||
</List>
|
||||
</Popover>
|
||||
</Box>
|
||||
|
||||
<Box display="flex" justifyContent="center" mt={2}>
|
||||
<Pagination
|
||||
count={totalPages}
|
||||
page={currentPage}
|
||||
onChange={handlePageChange}
|
||||
color="primary"
|
||||
showFirstButton
|
||||
showLastButton
|
||||
/>
|
||||
</Box>
|
||||
|
||||
{/* Generate Token Modal */}
|
||||
<Dialog open={showGenerateTokenModal} onClose={handleClose} fullWidth maxWidth="sm">
|
||||
<DialogTitle>
|
||||
Generate New Token
|
||||
<IconButton
|
||||
onClick={handleClose}
|
||||
sx={{
|
||||
position: 'absolute',
|
||||
right: 8,
|
||||
top: 8,
|
||||
}}
|
||||
>
|
||||
<CloseIcon />
|
||||
</IconButton>
|
||||
</DialogTitle>
|
||||
<DialogContent>
|
||||
<Box component="form" sx={{ mt: 1 }}>
|
||||
<TextField
|
||||
margin="normal"
|
||||
fullWidth
|
||||
label="Token Name"
|
||||
value={newTokenName}
|
||||
onChange={(e) => setNewTokenName(e.target.value)}
|
||||
required
|
||||
sx={{ mb: 2 }}
|
||||
/>
|
||||
|
||||
<Typography variant="subtitle1" gutterBottom>
|
||||
Token Scopes
|
||||
</Typography>
|
||||
<Box display="flex" alignItems="center" mb={2}>
|
||||
<FormControl fullWidth sx={{ mr: 2 }}>
|
||||
<InputLabel>Select a scope</InputLabel>
|
||||
<Select
|
||||
value={selectedScope}
|
||||
onChange={(e) => setSelectedScope(e.target.value)}
|
||||
label="Select a scope"
|
||||
>
|
||||
<MenuItem value=""><em>Select a scope</em></MenuItem>
|
||||
{availableScopes.map(scope => (
|
||||
<MenuItem key={scope.value} value={scope.value}>
|
||||
{scope.label}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
<Button
|
||||
variant="outlined"
|
||||
onClick={addScope}
|
||||
disabled={!selectedScope}
|
||||
>
|
||||
Add Scope
|
||||
</Button>
|
||||
</Box>
|
||||
<Typography variant="caption" display="block" gutterBottom>
|
||||
Select and add the permissions this token should have
|
||||
</Typography>
|
||||
|
||||
{/* Display selected scopes as chips */}
|
||||
{selectedScopes.length > 0 && (
|
||||
<Box sx={{ mt: 2 }}>
|
||||
{selectedScopes.map(scope => {
|
||||
const scopeInfo = availableScopes.find(s => s.value === scope);
|
||||
return (
|
||||
<Chip
|
||||
key={scope}
|
||||
label={scopeInfo?.label || scope}
|
||||
color="primary"
|
||||
onDelete={() => removeScope(scope)}
|
||||
sx={{ mr: 1, mb: 1 }}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</Box>
|
||||
)}
|
||||
|
||||
{generatedToken && (
|
||||
<Box sx={{ mt: 2 }}>
|
||||
<Typography variant="subtitle1" gutterBottom>
|
||||
Generated Token
|
||||
</Typography>
|
||||
<TextareaAutosize
|
||||
minRows={3}
|
||||
value={generatedToken}
|
||||
readOnly
|
||||
style={{ width: '100%', padding: '8px', borderRadius: '4px', borderColor: '#ccc' }}
|
||||
/>
|
||||
<Typography variant="caption" display="block" gutterBottom>
|
||||
Copy this token and store it securely. You won't be able to see it again.
|
||||
</Typography>
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={handleClose}>Close</Button>
|
||||
<Button
|
||||
variant="contained"
|
||||
onClick={generateNewToken}
|
||||
disabled={!newTokenName.trim()}
|
||||
>
|
||||
Generate Token
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
|
||||
{/* Add/Edit Token Modal */}
|
||||
<Dialog open={showAddEditModal} onClose={handleClose} fullWidth maxWidth="sm">
|
||||
<DialogTitle>
|
||||
{isEditing ? "Edit Token" : "Add Token"}
|
||||
<IconButton
|
||||
onClick={handleClose}
|
||||
sx={{
|
||||
position: 'absolute',
|
||||
right: 8,
|
||||
top: 8,
|
||||
}}
|
||||
>
|
||||
<CloseIcon />
|
||||
</IconButton>
|
||||
</DialogTitle>
|
||||
<DialogContent>
|
||||
<Box component="form" onSubmit={handleSubmit} sx={{ mt: 1 }}>
|
||||
<TextField
|
||||
margin="normal"
|
||||
fullWidth
|
||||
label="Token Name"
|
||||
name="tokenName"
|
||||
value={currentToken.tokenName}
|
||||
onChange={handleInputChange}
|
||||
required
|
||||
sx={{ mb: 2 }}
|
||||
/>
|
||||
<TextField
|
||||
margin="normal"
|
||||
fullWidth
|
||||
label="Token Value"
|
||||
name="tokenValue"
|
||||
value={currentToken.tokenValue}
|
||||
onChange={handleInputChange}
|
||||
required
|
||||
sx={{ mb: 2 }}
|
||||
/>
|
||||
|
||||
<Typography variant="subtitle1" gutterBottom>
|
||||
Token Scopes
|
||||
</Typography>
|
||||
<Box display="flex" alignItems="center" mb={2}>
|
||||
<FormControl fullWidth sx={{ mr: 2 }}>
|
||||
<InputLabel>Select a scope</InputLabel>
|
||||
<Select
|
||||
value={selectedScope}
|
||||
onChange={(e) => setSelectedScope(e.target.value)}
|
||||
label="Select a scope"
|
||||
>
|
||||
<MenuItem value=""><em>Select a scope</em></MenuItem>
|
||||
{availableScopes.map(scope => (
|
||||
<MenuItem key={scope.value} value={scope.value}>
|
||||
{scope.label}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
<Button
|
||||
variant="outlined"
|
||||
onClick={() => {
|
||||
if (selectedScope && !currentToken.scopes.includes(selectedScope)) {
|
||||
setCurrentToken(prev => ({
|
||||
...prev,
|
||||
scopes: [...prev.scopes, selectedScope]
|
||||
}));
|
||||
setSelectedScope("");
|
||||
}
|
||||
}}
|
||||
disabled={!selectedScope}
|
||||
>
|
||||
Add Scope
|
||||
</Button>
|
||||
</Box>
|
||||
<Typography variant="caption" display="block" gutterBottom>
|
||||
Select and add the permissions this token should have
|
||||
</Typography>
|
||||
|
||||
{/* Display selected scopes as chips */}
|
||||
{currentToken.scopes && currentToken.scopes.length > 0 && (
|
||||
<Box sx={{ mt: 2 }}>
|
||||
{currentToken.scopes.map(scope => {
|
||||
const scopeInfo = availableScopes.find(s => s.value === scope);
|
||||
return (
|
||||
<Chip
|
||||
key={scope}
|
||||
label={scopeInfo?.label || scope}
|
||||
color="primary"
|
||||
onDelete={() => {
|
||||
setCurrentToken(prev => ({
|
||||
...prev,
|
||||
scopes: prev.scopes.filter(s => s !== scope)
|
||||
}));
|
||||
}}
|
||||
sx={{ mr: 1, mb: 1 }}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</Box>
|
||||
)}
|
||||
|
||||
<FormControlLabel
|
||||
control={
|
||||
<Checkbox
|
||||
checked={currentToken.isActive}
|
||||
onChange={handleInputChange}
|
||||
name="isActive"
|
||||
color="primary"
|
||||
/>
|
||||
}
|
||||
label="Active?"
|
||||
sx={{ mt: 2 }}
|
||||
/>
|
||||
</Box>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={handleClose}>Close</Button>
|
||||
<Button
|
||||
variant="contained"
|
||||
onClick={handleSubmit}
|
||||
type="submit"
|
||||
>
|
||||
{isEditing ? "Update Token" : "Add Token"}
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
|
||||
{/* Delete Confirmation Dialog */}
|
||||
<Dialog
|
||||
open={deleteConfirmOpen}
|
||||
onClose={() => setDeleteConfirmOpen(false)}
|
||||
>
|
||||
<DialogTitle>Confirm Delete</DialogTitle>
|
||||
<DialogContent>
|
||||
<DialogContentText>
|
||||
Are you sure you want to delete this token?
|
||||
</DialogContentText>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={() => setDeleteConfirmOpen(false)}>Cancel</Button>
|
||||
<Button onClick={handleDeleteConfirm} color="error" autoFocus>
|
||||
Delete
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
export default TOKENRegistry;
|
||||
Reference in New Issue
Block a user