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 ( {loading ? ( ) : ( {/* Token Generation Card */} setShowGenerateTokenModal(true)} sx={{ backgroundColor: '#1976d2', color: 'white' }} > Generate New Token } /> Manage your API tokens here. Generate new tokens or delete existing ones. Token Registry handleSearch(e.target.value)} InputProps={{ startAdornment: ( ), sx: { borderRadius: '10px', boxShadow: '0px 4px 12px rgba(0, 0, 0, 0.1)', } }} /> openModal()} color="primary" sx={{ mr: 2 }} > {Object.keys(visibleColumns).filter(key => visibleColumns[key]).map(key => ( {key.charAt(0).toUpperCase() + key.slice(1)} ))} {slicedTokens.length === 0 ? ( visibleColumns[key]).length} align="center" > No Data Available ) : ( slicedTokens.map((token, index) => ( {Object.keys(visibleColumns).filter(key => visibleColumns[key]).map(key => ( {key === "actions" ? ( <> openModal(token)} color="primary" sx={{ mr: 1 }} > handleDeleteClick(token.id)} color="error" > ) : key === "isActive" ? ( ) : key === "scopes" ? ( {token.scopes && token.scopes.map(scope => { const scopeInfo = availableScopes.find(s => s.value === scope); return ( ); })} ) : ( token[key] )} ))} )) )}
{/* Manage Columns */} {Object.keys(visibleColumns).map((column) => ( toggleColumn(column)}> ))} {/* Records Per Page */} {[1, 5, 10, 20, 50].map((number) => ( handleRecordsPerPageChange(number)} sx={{ minWidth: '100px' }} > {recordsPerPage === number && } ))} {/* Generate Token Modal */} Generate New Token setNewTokenName(e.target.value)} required sx={{ mb: 2 }} /> Token Scopes Select a scope Select and add the permissions this token should have {/* Display selected scopes as chips */} {selectedScopes.length > 0 && ( {selectedScopes.map(scope => { const scopeInfo = availableScopes.find(s => s.value === scope); return ( removeScope(scope)} sx={{ mr: 1, mb: 1 }} /> ); })} )} {generatedToken && ( Generated Token Copy this token and store it securely. You won't be able to see it again. )} {/* Add/Edit Token Modal */} {isEditing ? "Edit Token" : "Add Token"} Token Scopes Select a scope Select and add the permissions this token should have {/* Display selected scopes as chips */} {currentToken.scopes && currentToken.scopes.length > 0 && ( {currentToken.scopes.map(scope => { const scopeInfo = availableScopes.find(s => s.value === scope); return ( { setCurrentToken(prev => ({ ...prev, scopes: prev.scopes.filter(s => s !== scope) })); }} sx={{ mr: 1, mb: 1 }} /> ); })} )} } label="Active?" sx={{ mt: 2 }} /> {/* Delete Confirmation Dialog */} setDeleteConfirmOpen(false)} > Confirm Delete Are you sure you want to delete this token?
)}
); } export default TOKENRegistry;