Routes Frontend + Improvements
This commit is contained in:
		
							parent
							
								
									50ae32595d
								
							
						
					
					
						commit
						90e7024a73
					
				| @ -5,6 +5,7 @@ namespace Core\API { | ||||
|   use Core\API\Routes\GenerateCache; | ||||
|   use Core\Objects\Context; | ||||
|   use Core\Objects\DatabaseEntity\Route; | ||||
|   use Core\Objects\Router\ApiRoute; | ||||
| 
 | ||||
|   abstract class RoutesAPI extends Request { | ||||
| 
 | ||||
| @ -24,6 +25,8 @@ namespace Core\API { | ||||
|         return false; | ||||
|       } else if ($route === null) { | ||||
|         return $this->createError("Route not found"); | ||||
|       } else if ($route instanceof ApiRoute) { | ||||
|         return $this->createError("This route cannot be modified"); | ||||
|       } | ||||
| 
 | ||||
|       $route->setActive($active); | ||||
| @ -71,6 +74,7 @@ namespace Core\API\Routes { | ||||
|   use Core\Objects\Context; | ||||
|   use Core\Objects\DatabaseEntity\Group; | ||||
|   use Core\Objects\DatabaseEntity\Route; | ||||
|   use Core\Objects\Router\ApiRoute; | ||||
|   use Core\Objects\Router\Router; | ||||
| 
 | ||||
|   class Fetch extends RoutesAPI { | ||||
| @ -87,10 +91,7 @@ namespace Core\API\Routes { | ||||
|       $this->success = ($routes !== FALSE); | ||||
| 
 | ||||
|       if ($this->success) { | ||||
|         $this->result["routes"] = []; | ||||
|         foreach ($routes as $route) { | ||||
|           $this->result["routes"][$route->getId()] = $route->jsonSerialize(); | ||||
|         } | ||||
|         $this->result["routes"] = $routes; | ||||
|       } | ||||
| 
 | ||||
|       return $this->success; | ||||
| @ -307,7 +308,6 @@ namespace Core\API\Routes { | ||||
|       parent::__construct($context, $externalCall, array( | ||||
|         "id" => new Parameter("id", Parameter::TYPE_INT) | ||||
|       )); | ||||
|       $this->isPublic = false; | ||||
|     } | ||||
| 
 | ||||
|     public function _execute(): bool { | ||||
| @ -319,6 +319,8 @@ namespace Core\API\Routes { | ||||
|         return $this->createError("Error fetching route: " . $sql->getLastError()); | ||||
|       } else if ($route === null) { | ||||
|         return $this->createError("Route not found"); | ||||
|       } else if ($route instanceof ApiRoute) { | ||||
|         return $this->createError("This route cannot be deleted"); | ||||
|       } | ||||
| 
 | ||||
|       $this->success = $route->delete($sql) !== false; | ||||
| @ -336,7 +338,6 @@ namespace Core\API\Routes { | ||||
|       parent::__construct($context, $externalCall, array( | ||||
|         "id" => new Parameter("id", Parameter::TYPE_INT) | ||||
|       )); | ||||
|       $this->isPublic = false; | ||||
|     } | ||||
| 
 | ||||
|     public function _execute(): bool { | ||||
| @ -354,7 +355,6 @@ namespace Core\API\Routes { | ||||
|       parent::__construct($context, $externalCall, array( | ||||
|         "id" => new Parameter("id", Parameter::TYPE_INT) | ||||
|       )); | ||||
|       $this->isPublic = false; | ||||
|     } | ||||
| 
 | ||||
|     public function _execute(): bool { | ||||
| @ -373,7 +373,6 @@ namespace Core\API\Routes { | ||||
| 
 | ||||
|     public function __construct(Context $context, bool $externalCall = false) { | ||||
|       parent::__construct($context, $externalCall, []); | ||||
|       $this->isPublic = false; | ||||
|       $this->router = null; | ||||
|     } | ||||
| 
 | ||||
| @ -408,7 +407,10 @@ namespace Core\API\Routes { | ||||
|     } | ||||
| 
 | ||||
|     public static function getDefaultACL(Insert $insert): void { | ||||
|       $insert->addRow(self::getEndpoint(), [Group::ADMIN, Group::SUPPORT], "Allows users to regenerate the routing cache", true); | ||||
|       $insert->addRow(self::getEndpoint(), | ||||
|         [Group::ADMIN, Group::SUPPORT], | ||||
|         "Allows users to regenerate the routing cache", true | ||||
|       ); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|  | ||||
| @ -5,6 +5,7 @@ namespace Core\Objects\Router; | ||||
| use Core\Driver\SQL\SQL; | ||||
| use Core\Elements\Document; | ||||
| use Core\Objects\Context; | ||||
| use Core\Objects\DatabaseEntity\Attribute\Transient; | ||||
| use Core\Objects\DatabaseEntity\Route; | ||||
| use Core\Objects\Search\Searchable; | ||||
| use Core\Objects\Search\SearchQuery; | ||||
| @ -15,7 +16,10 @@ class DocumentRoute extends Route { | ||||
| 
 | ||||
|   use Searchable; | ||||
| 
 | ||||
|   #[Transient]
 | ||||
|   private array $args; | ||||
| 
 | ||||
|   #[Transient]
 | ||||
|   private ?\ReflectionClass $reflectionClass = null; | ||||
| 
 | ||||
|   public function __construct(string $pattern, bool $exact, string $className, ...$args) { | ||||
|  | ||||
| @ -2,11 +2,13 @@ | ||||
| 
 | ||||
| namespace Core\Objects\Router; | ||||
| 
 | ||||
| use Core\Objects\DatabaseEntity\Attribute\Transient; | ||||
| use Core\Objects\DatabaseEntity\Route; | ||||
| use JetBrains\PhpStorm\Pure; | ||||
| 
 | ||||
| class RedirectRoute extends Route { | ||||
| 
 | ||||
|   #[Transient]
 | ||||
|   private int $code; | ||||
| 
 | ||||
|   public function __construct(string $type, string $pattern, bool $exact, string $destination, int $code = 307) { | ||||
|  | ||||
| @ -4,6 +4,7 @@ namespace Core\Objects\Router; | ||||
| 
 | ||||
| use Core\Driver\SQL\SQL; | ||||
| use Core\Objects\Context; | ||||
| use Core\Objects\DatabaseEntity\Attribute\Transient; | ||||
| use Core\Objects\DatabaseEntity\Route; | ||||
| use Core\Objects\Search\Searchable; | ||||
| use Core\Objects\Search\SearchQuery; | ||||
| @ -14,6 +15,7 @@ class StaticFileRoute extends Route { | ||||
| 
 | ||||
|   use Searchable; | ||||
| 
 | ||||
|   #[Transient]
 | ||||
|   private int $code; | ||||
| 
 | ||||
|   public function __construct(string $pattern, bool $exact, string $path, int $code = 200) { | ||||
|  | ||||
| @ -2,11 +2,15 @@ | ||||
| 
 | ||||
| namespace Core\Objects\Router; | ||||
| 
 | ||||
| use Core\Objects\DatabaseEntity\Attribute\Transient; | ||||
| use Core\Objects\DatabaseEntity\Route; | ||||
| 
 | ||||
| class StaticRoute extends Route { | ||||
| 
 | ||||
|   #[Transient]
 | ||||
|   private string $data; | ||||
| 
 | ||||
|   #[Transient]
 | ||||
|   private int $code; | ||||
| 
 | ||||
|   public function __construct(string $pattern, bool $exact, string $data, int $code = 200) { | ||||
|  | ||||
| @ -20,6 +20,7 @@ const GroupListView = lazy(() => import('./views/group/group-list')); | ||||
| const EditGroupView = lazy(() => import('./views/group/group-edit')); | ||||
| const LogView = lazy(() => import("./views/log-view")); | ||||
| const AccessControlList = lazy(() => import("./views/access-control-list")); | ||||
| const RouteListView = lazy(() => import("./views/routes")); | ||||
| 
 | ||||
| export default function AdminDashboard(props) { | ||||
| 
 | ||||
| @ -79,6 +80,7 @@ export default function AdminDashboard(props) { | ||||
|                             <Route path={"/admin/group/:groupId"} element={<EditGroupView {...controlObj} />}/> | ||||
|                             <Route path={"/admin/logs"} element={<LogView {...controlObj} />}/> | ||||
|                             <Route path={"/admin/acl"} element={<AccessControlList {...controlObj} />}/> | ||||
|                             <Route path={"/admin/routes"} element={<RouteListView {...controlObj} />}/> | ||||
|                             <Route path={"*"} element={<View404 />} /> | ||||
|                         </Routes> | ||||
|                     </Suspense> | ||||
|  | ||||
| @ -36,7 +36,7 @@ export default function Sidebar(props) { | ||||
|             "name": "admin.groups", | ||||
|             "icon": "users-cog" | ||||
|         }, | ||||
|         "pages": { | ||||
|         "routes": { | ||||
|             "name": "admin.page_routes", | ||||
|             "icon": "copy", | ||||
|         }, | ||||
|  | ||||
| @ -12,12 +12,11 @@ import { | ||||
|     TableContainer, | ||||
|     TableHead, | ||||
|     TableRow, | ||||
|     IconButton | ||||
|     IconButton, styled | ||||
| } from "@material-ui/core"; | ||||
| import {Add, Delete, Edit, Refresh} from "@material-ui/icons"; | ||||
| import {USER_GROUP_ADMIN} from "shared/constants"; | ||||
| import Dialog from "shared/elements/dialog"; | ||||
| import {TableFooter} from "@mui/material"; | ||||
| 
 | ||||
| 
 | ||||
| export default function AccessControlList(props) { | ||||
| @ -74,18 +73,22 @@ export default function AccessControlList(props) { | ||||
|     const onChangePermission = useCallback((methodIndex, groupId, selected) => { | ||||
|         let newGroups = null; | ||||
|         let currentGroups = acl[methodIndex].groups; | ||||
|         let groupIndex = currentGroups.indexOf(groupId); | ||||
|         if (!selected) { | ||||
|             if (currentGroups.length === 0) { | ||||
|                 // it was an "everyone permission" before
 | ||||
|                 newGroups = groups.filter(group => group.id !== groupId).map(group => group.id); | ||||
|             } else if (groupIndex !== -1 && currentGroups.length > 1) { | ||||
|         if (groupId === null) { | ||||
|             newGroups = []; | ||||
|         } else { | ||||
|             let groupIndex = currentGroups.indexOf(groupId); | ||||
|             if (!selected) { | ||||
|                 if (currentGroups.length === 0) { | ||||
|                     // it was an "everyone permission" before
 | ||||
|                     newGroups = groups.filter(group => group.id !== groupId).map(group => group.id); | ||||
|                 } else if (groupIndex !== -1) { | ||||
|                     newGroups = [...currentGroups]; | ||||
|                     newGroups.splice(groupIndex, 1); | ||||
|                 } | ||||
|             } else if (groupIndex === -1) { | ||||
|                 newGroups = [...currentGroups]; | ||||
|                 newGroups.splice(groupIndex, 1); | ||||
|                 newGroups.push(groupId); | ||||
|             } | ||||
|         } else if (groupIndex === -1) { | ||||
|             newGroups = [...currentGroups]; | ||||
|             newGroups.push(groupId); | ||||
|         } | ||||
| 
 | ||||
|         if (newGroups !== null) { | ||||
| @ -94,6 +97,7 @@ export default function AccessControlList(props) { | ||||
|                    let newACL = [...acl]; | ||||
|                    newACL[methodIndex].groups = newGroups; | ||||
|                    setACL(newACL); | ||||
|                    props.api.fetchUser(); | ||||
|                } else { | ||||
|                    props.showDialog("Error updating permission: " + data.msg); | ||||
|                } | ||||
| @ -106,6 +110,7 @@ export default function AccessControlList(props) { | ||||
|             if (data.success) { | ||||
|                 let newACL = acl.filter(acl => acl.method.toLowerCase() !== method.toLowerCase()); | ||||
|                 setACL(newACL); | ||||
|                 props.api.fetchUser(); | ||||
|             } else { | ||||
|                 props.showDialog("Error deleting permission: " + data.msg); | ||||
|             } | ||||
| @ -119,6 +124,7 @@ export default function AccessControlList(props) { | ||||
|                 newACL.push({method: inputData.method, groups: groups, description: inputData.description}); | ||||
|                 newACL = newACL.sort((a, b) => a.method.localeCompare(b.method)) | ||||
|                 setACL(newACL); | ||||
|                 props.api.fetchUser(); | ||||
|             } else { | ||||
|                 props.showDialog("Error updating permission: " + data.msg); | ||||
|             } | ||||
| @ -180,8 +186,13 @@ export default function AccessControlList(props) { | ||||
|                             </div> | ||||
|                         </div> | ||||
|                     </TableCell> | ||||
|                     <BorderedColumn key={"perm-" + index + "-everyone"} align={"center"}> | ||||
|                         <Checkbox checked={!permission.groups.length} | ||||
|                                   onChange={(e) => onChangePermission(index, null, e.target.checked)} | ||||
|                                   disabled={isRestricted(permission.method)} /> | ||||
|                     </BorderedColumn> | ||||
|                     {groups.map(group => <TableCell key={"perm-" + index + "-group-" + group.id} align={"center"}> | ||||
|                         <Checkbox checked={!permission.groups.length || permission.groups.includes(group.id)} | ||||
|                         <Checkbox checked={permission.groups.includes(group.id)} | ||||
|                                   onChange={(e) => onChangePermission(index, group.id, e.target.checked)} | ||||
|                                   disabled={isRestricted(permission.method)} /> | ||||
|                     </TableCell>)} | ||||
| @ -192,6 +203,11 @@ export default function AccessControlList(props) { | ||||
|         return <>{rows}</> | ||||
|     } | ||||
| 
 | ||||
|     const BorderedColumn = styled(TableCell)({ | ||||
|         borderLeft: "1px dotted #666", | ||||
|         borderRight: "1px dotted #666", | ||||
|     }); | ||||
| 
 | ||||
|     return <> | ||||
|         <div className={"content-header"}> | ||||
|             <div className={"container-fluid"}> | ||||
| @ -244,23 +260,22 @@ export default function AccessControlList(props) { | ||||
|                 </div> | ||||
|             </div> | ||||
|         </div> | ||||
|         <div> | ||||
|             <TableContainer component={Paper} style={{overflowX: "initial"}}> | ||||
|                 <Table stickyHeader size={"small"} className={"table-striped"}> | ||||
|                     <TableHead> | ||||
|                         <TableRow> | ||||
|                             <TableCell>{L("permission")}</TableCell> | ||||
|                             { groups.map(group => <TableCell key={"group-" + group.id} align={"center"}> | ||||
|                                 {group.name} | ||||
|                             </TableCell>) } | ||||
|                         </TableRow> | ||||
|                     </TableHead> | ||||
|                     <TableBody> | ||||
|                         <PermissionList /> | ||||
|                     </TableBody> | ||||
|                 </Table> | ||||
|             </TableContainer> | ||||
|         </div> | ||||
|         <TableContainer component={Paper} style={{overflowX: "initial"}}> | ||||
|             <Table stickyHeader size={"small"} className={"table-striped"}> | ||||
|                 <TableHead> | ||||
|                     <TableRow> | ||||
|                         <TableCell>{L("permission")}</TableCell> | ||||
|                         <BorderedColumn align={"center"}><i>{L("everyone")}</i></BorderedColumn> | ||||
|                         { groups.map(group => <TableCell key={"group-" + group.id} align={"center"}> | ||||
|                             {group.name} | ||||
|                         </TableCell>) } | ||||
|                     </TableRow> | ||||
|                 </TableHead> | ||||
|                 <TableBody> | ||||
|                     <PermissionList /> | ||||
|                 </TableBody> | ||||
|             </Table> | ||||
|         </TableContainer> | ||||
|         <Dialog show={dialogData.open} | ||||
|                 onClose={() => setDialogData({open: false})} | ||||
|                 title={dialogData.title} | ||||
| @ -268,6 +283,5 @@ export default function AccessControlList(props) { | ||||
|                 onOption={dialogData.onOption} | ||||
|                 inputs={dialogData.inputs} | ||||
|                 options={[L("general.ok"), L("general.cancel")]} /> | ||||
| 
 | ||||
|     </> | ||||
| } | ||||
							
								
								
									
										226
									
								
								react/admin-panel/src/views/routes.js
									
									
									
									
									
										Normal file
									
								
							
							
								
								
								
								
								
									
									
								
							
						
						
									
										226
									
								
								react/admin-panel/src/views/routes.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,226 @@ | ||||
| import {Link, useNavigate} from "react-router-dom"; | ||||
| import { | ||||
|     Paper, | ||||
|     styled, | ||||
|     Table, | ||||
|     TableBody, | ||||
|     TableCell, | ||||
|     TableContainer, | ||||
|     TableHead, | ||||
|     TableRow, | ||||
|     IconButton, Button, Checkbox | ||||
| } from "@material-ui/core"; | ||||
| import {useCallback, useContext, useEffect, useState} from "react"; | ||||
| import {LocaleContext} from "shared/locale"; | ||||
| import {Add, Cached, Delete, Edit, Refresh} from "@material-ui/icons"; | ||||
| import {Quiz} from "@mui/icons-material"; | ||||
| import Dialog from "shared/elements/dialog"; | ||||
| 
 | ||||
| 
 | ||||
| export default function RouteListView(props) { | ||||
| 
 | ||||
|     // meta
 | ||||
|     const api = props.api; | ||||
|     const {translate: L, requestModules, currentLocale} = useContext(LocaleContext); | ||||
|     const navigate = useNavigate(); | ||||
| 
 | ||||
|     // data
 | ||||
|     const [fetchRoutes, setFetchRoutes] = useState(true); | ||||
|     const [routes, setRoutes] = useState({}); | ||||
| 
 | ||||
|     // ui
 | ||||
|     const [dialogData, setDialogData] = useState({show: false}); | ||||
|     const [isGeneratingCache, setGeneratingCache] = useState(false); | ||||
| 
 | ||||
|     const onFetchRoutes = useCallback((force = false) => { | ||||
|         if (force || fetchRoutes) { | ||||
|             setFetchRoutes(false); | ||||
|             props.api.fetchRoutes().then(res => { | ||||
|                 if (!res.success) { | ||||
|                     props.showDialog(res.msg, "Error fetching routes"); | ||||
|                     navigate("/admin/dashboard"); | ||||
|                 } else { | ||||
|                     setRoutes(res.routes); | ||||
|                 } | ||||
|             }); | ||||
|         } | ||||
|     }, [fetchRoutes]); | ||||
| 
 | ||||
|     useEffect(() => { | ||||
|         onFetchRoutes(); | ||||
|     }, []); | ||||
| 
 | ||||
|     useEffect(() => { | ||||
|         requestModules(props.api, ["general"], currentLocale).then(data => { | ||||
|             if (!data.success) { | ||||
|                 props.showDialog("Error fetching translations: " + data.msg); | ||||
|             } | ||||
|         }); | ||||
|     }, [currentLocale]); | ||||
| 
 | ||||
|     const onToggleRoute = useCallback((id, active) => { | ||||
|         if (active) { | ||||
|             props.api.enableRoute(id).then(data => { | ||||
|                 if (!data.success) { | ||||
|                     props.showDialog(data.msg, L("Error enabling route")); | ||||
|                 } else { | ||||
|                     setRoutes({...routes, [id]: { ...routes[id], active: true }}); | ||||
|                 } | ||||
|             }); | ||||
|         } else { | ||||
|             props.api.disableRoute(id).then(data => { | ||||
|                 if (!data.success) { | ||||
|                     props.showDialog(data.msg, L("Error enabling route")); | ||||
|                 } else { | ||||
|                     setRoutes({...routes, [id]: { ...routes[id], active: false }}); | ||||
|                 } | ||||
|             }); | ||||
|         } | ||||
|     }, [routes]); | ||||
| 
 | ||||
|     const onDeleteRoute = useCallback(id => { | ||||
|         props.api.deleteRoute(id).then(data => { | ||||
|             if (!data.success) { | ||||
|                 props.showDialog(data.msg, L("Error removing route")); | ||||
|             } else { | ||||
|                 let newRoutes = { ...routes }; | ||||
|                 delete newRoutes[id]; | ||||
|                 setRoutes(newRoutes); | ||||
|             } | ||||
|         }) | ||||
|     }, [routes]); | ||||
| 
 | ||||
|     const onRegenerateCache = useCallback(() => { | ||||
|         if (!isGeneratingCache) { | ||||
|             setGeneratingCache(true); | ||||
|             props.api.regenerateRouterCache().then(data => { | ||||
|                 if (!data.success) { | ||||
|                     props.showDialog(data.msg, L("Error regenerating router cache")); | ||||
|                     setGeneratingCache(false); | ||||
|                 } else { | ||||
|                     setDialogData({ | ||||
|                         open: true, | ||||
|                         title: L("general.success"), | ||||
|                         message: L("Router cache successfully regenerated"), | ||||
|                         onClose: () => setGeneratingCache(false) | ||||
|                     }) | ||||
|                 } | ||||
|             }); | ||||
|         } | ||||
|     }, [isGeneratingCache]); | ||||
| 
 | ||||
|     const RouteTableRow = styled(TableRow)((props) => ({ | ||||
|         "& td": { | ||||
|             fontFamily: "monospace" | ||||
|         } | ||||
|     })); | ||||
| 
 | ||||
|     const BoolCell = (props) => props.checked ? L("general.yes") : L("general.no") | ||||
| 
 | ||||
|     return <> | ||||
|         <div className={"content-header"}> | ||||
|             <div className={"container-fluid"}> | ||||
|                 <div className={"row mb-2"}> | ||||
|                     <div className={"col-sm-6"}> | ||||
|                         <h1 className={"m-0 text-dark"}>Routes</h1> | ||||
|                     </div> | ||||
|                     <div className={"col-sm-6"}> | ||||
|                         <ol className={"breadcrumb float-sm-right"}> | ||||
|                             <li className={"breadcrumb-item"}><Link to={"/admin/dashboard"}>Home</Link></li> | ||||
|                             <li className="breadcrumb-item active">Routes</li> | ||||
|                         </ol> | ||||
|                     </div> | ||||
|                 </div> | ||||
|             </div> | ||||
|         </div> | ||||
|         <div className={"row"}> | ||||
|             <div className={"col-6"} /> | ||||
|                 <div className={"col-6 text-right"}> | ||||
|                 <div className={"form-group"}> | ||||
|                     <Button variant={"outlined"} color={"primary"} className={"m-1"} startIcon={<Refresh />} onClick={() => onFetchRoutes(true)}> | ||||
|                         {L("general.reload")} | ||||
|                     </Button> | ||||
|                     <Button variant={"outlined"} className={"m-1"} startIcon={<Add />} | ||||
|                             disabled={!props.api.hasPermission("routes/add")} | ||||
|                             onClick={() => navigate("/admin/routes/new")} > | ||||
|                         {L("general.add")} | ||||
|                     </Button> | ||||
|                     <Button variant={"outlined"} className={"m-1"} startIcon={<Cached />} | ||||
|                             disabled={!props.api.hasPermission("routes/generateCache") || isGeneratingCache} | ||||
|                             onClick={onRegenerateCache} > | ||||
|                         {isGeneratingCache ? L("regenerating_cache") + "…" : L("regenerate_cache")} | ||||
|                     </Button> | ||||
|                 </div> | ||||
|             </div> | ||||
|         </div> | ||||
|         <TableContainer component={Paper} style={{overflowX: "initial"}}> | ||||
|             <Table stickyHeader size={"small"} className={"table-striped"}> | ||||
|                 <TableHead> | ||||
|                     <TableRow> | ||||
|                         <TableCell>{L("general.id")}</TableCell> | ||||
|                         <TableCell>{L("Route")}</TableCell> | ||||
|                         <TableCell>{L("Type")}</TableCell> | ||||
|                         <TableCell>{L("Target")}</TableCell> | ||||
|                         <TableCell>{L("Extra")}</TableCell> | ||||
|                         <TableCell align={"center"}>{L("Active")}</TableCell> | ||||
|                         <TableCell align={"center"}>{L("Exact")}</TableCell> | ||||
|                         <TableCell align={"center"}>{L("general.controls")}</TableCell> | ||||
|                     </TableRow> | ||||
|                 </TableHead> | ||||
|                 <TableBody> | ||||
|                     {Object.entries(routes).map(([id, route]) => | ||||
|                         <RouteTableRow key={"route-" + id}> | ||||
|                             <TableCell>{route.id}</TableCell> | ||||
|                             <TableCell>{route.pattern}</TableCell> | ||||
|                             <TableCell>{route.type}</TableCell> | ||||
|                             <TableCell>{route.target}</TableCell> | ||||
|                             <TableCell>{route.extra}</TableCell> | ||||
|                             <TableCell align={"center"}> | ||||
|                                 <Checkbox checked={route.active} | ||||
|                                           size={"small"} | ||||
|                                           disabled={!api.hasPermission(route.active ? "routes/disable" : "routes/enable")} | ||||
|                                           onChange={(e) => onToggleRoute(route.id, e.target.checked)} /> | ||||
|                             </TableCell> | ||||
|                             <TableCell align={"center"}><BoolCell checked={route.exact} /></TableCell> | ||||
|                             <TableCell align={"center"}> | ||||
|                                 <IconButton size={"small"} title={L("general.edit")} | ||||
|                                             disabled={!api.hasPermission("routes/add")} | ||||
|                                             color={"primary"} | ||||
|                                             onClick={() => navigate("/admin/routes/" + id)}> | ||||
|                                     <Edit /> | ||||
|                                 </IconButton> | ||||
|                                 <IconButton size={"small"} title={L("general.delete")} | ||||
|                                             disabled={!api.hasPermission("routes/remove")} | ||||
|                                             color={"secondary"} | ||||
|                                             onClick={() => setDialogData({ | ||||
|                                                 open: true, | ||||
|                                                 title: L("Delete Route"), | ||||
|                                                 message: L("Do you really want to delete the following route?"), | ||||
|                                                 inputs: [ | ||||
|                                                     { type: "text", value: route.pattern, disabled: true} | ||||
|                                                 ], | ||||
|                                                 options: [L("general.ok"), L("general.cancel")], | ||||
|                                                 onOption: btn => btn === 0 && onDeleteRoute(route.id) | ||||
|                                             })}> | ||||
|                                     <Delete /> | ||||
|                                 </IconButton> | ||||
|                                 <IconButton size={"small"} title={L("general.test")} | ||||
|                                             disabled={!api.hasPermission("routes/check")} | ||||
|                                             color={"primary"}> | ||||
|                                     <Quiz /> | ||||
|                                 </IconButton> | ||||
|                             </TableCell> | ||||
|                         </RouteTableRow> | ||||
|                     )} | ||||
|                 </TableBody> | ||||
|             </Table> | ||||
|         </TableContainer> | ||||
|         <Dialog show={dialogData.open} | ||||
|                 onClose={() => { setDialogData({open: false}); dialogData.onClose && dialogData.onClose() }} | ||||
|                 title={dialogData.title} | ||||
|                 message={dialogData.message} | ||||
|                 onOption={dialogData.onOption} | ||||
|                 inputs={dialogData.inputs} | ||||
|                 options={[L("general.ok"), L("general.cancel")]} /> | ||||
|     </> | ||||
| } | ||||
| @ -213,14 +213,30 @@ export default class API { | ||||
|     } | ||||
| 
 | ||||
|     /** RoutesAPI **/ | ||||
|     async getRoutes() { | ||||
|     async fetchRoutes() { | ||||
|         return this.apiCall("routes/fetch"); | ||||
|     } | ||||
| 
 | ||||
|     async enableRoute(id) { | ||||
|         return this.apiCall("routes/enable", { id: id }); | ||||
|     } | ||||
| 
 | ||||
|     async disableRoute(id) { | ||||
|         return this.apiCall("routes/disable", { id: id }); | ||||
|     } | ||||
| 
 | ||||
|     async deleteRoute(id) { | ||||
|         return this.apiCall("routes/remove", { id: id }); | ||||
|     } | ||||
| 
 | ||||
|     async saveRoutes(routes) { | ||||
|         return this.apiCall("routes/save", { routes: routes }); | ||||
|     } | ||||
| 
 | ||||
|     async regenerateRouterCache() { | ||||
|         return this.apiCall("routes/generateCache"); | ||||
|     } | ||||
| 
 | ||||
|     /** GroupAPI **/ | ||||
|     async createGroup(name, color) { | ||||
|         return this.apiCall("groups/create", { name: name, color: color }); | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user