Core components
Client core
Admin panel system
26 min
overview the admin panel system provides a centralized interface for managing and monitoring the ir engine platform it enables administrators to view and modify users, projects, locations, and server instances through a secure, role based interface by leveraging the client core's routing, authentication, and api services, the admin panel creates a comprehensive management dashboard that separates administrative functions from the regular user experience this chapter explores the implementation, components, and workflow of the admin panel within the ir engine client core concepts centralized management the admin panel serves as a unified control center for administrative tasks consolidated interface provides a single location for all management functions separation of concerns keeps administrative features separate from the regular user experience comprehensive oversight enables monitoring and management of all system aspects structured organization groups related functions into logical sections this centralized approach simplifies administration and provides a consistent management experience data driven interface the admin panel uses standardized components to display and manipulate data tables display lists of entities (users, projects, locations) with sorting and pagination forms provide structured interfaces for creating and editing entities filters allow administrators to narrow down large datasets actions enable operations like enabling/disabling, deleting, or modifying entities these components create a consistent pattern for interacting with different types of data role based access control access to the admin panel and its features is controlled through permissions admin access only users with specific admin permissions can access the panel granular permissions different sections may require different permission scopes feature restrictions some actions may be limited based on the user's role audit trail administrative actions are often logged for accountability this security model ensures that only authorized personnel can perform administrative functions implementation admin routes definition the admin panel's structure is defined through route configurations // simplified from src/admin/defaultadminroutes tsx import react, { lazy } from 'react'; import { hiuser, hioutlinetablecells, hiserver } from 'react icons/hi'; // lazy loaded components for each admin section const dashboard = lazy(() => import(' /components/dashboard')); const users = lazy(() => import(' /components/user')); const projects = lazy(() => import(' /components/project')); const locations = lazy(() => import(' /components/location')); const servers = lazy(() => import(' /components/server')); // define the admin routes and their properties export const defaultadminroutes = { dashboard { name 'user\ dashboard dashboard', scope 'admin', component dashboard, access false, // will be updated based on user permissions icon \<hioutlinetablecells /> }, users { name 'user\ dashboard users', scope 'user', component users, access false, icon \<hiuser /> }, projects { name 'user\ dashboard projects', scope 'projects', component projects, access false, icon \<hioutlinetablecells /> }, locations { name 'user\ dashboard locations', scope 'location', component locations, access false, icon \<hioutlinetablecells /> }, servers { name 'user\ dashboard servers', scope 'instance', component servers, access false, icon \<hiserver /> } }; this configuration defines each admin section with a unique key (e g , "users", "projects") specifies the react component to render for each section indicates the permission scope required to access each section provides an icon for the navigation sidebar sets an initial access value that will be updated based on the user's permissions admin routes state the admin routes are managed through hyperflux state // simplified from src/admin/allowedadminroutesstate tsx import { definestate } from '@ir engine/hyperflux'; import { reactnode } from 'react'; // define the structure of an admin route export type adminroutestatetype = { name string; scope string; component react componenttype; access boolean; icon reactnode; }; // define the state for storing allowed admin routes export const allowedadminroutesstate = definestate({ name 'allowedadminroutesstate', initial {} as record\<string, adminroutestatetype> }); this state defines the structure of admin route information creates a hyperflux state to store the routes will be populated with routes from defaultadminroutes will have the access property updated based on the user's permissions admin panel entry point the main admin routes component serves as the entry point to the admin panel // simplified from src/admin/adminroutes tsx import react, { useeffect } from 'react'; import { routes, route } from 'react router dom'; import { usefind } from '@ir engine/common'; import { scopepath } from '@ir engine/common/src/schema type module'; import { engine } from '@ir engine/ecs'; import { getmutablestate, usemutablestate } from '@ir engine/hyperflux'; import { allowedadminroutesstate } from ' /allowedadminroutesstate'; import { defaultadminroutes } from ' /defaultadminroutes'; import { routerstate } from ' /common/services/routerservice'; import admintopbar from ' /components/admintopbar'; import adminsidebar from ' /components/adminsidebar'; import allowedroutes from ' /allowedroutes'; function adminroutes() { // get the current user id const currentuserid = engine instance userid; // fetch the user's permission scopes from the server const scopequery = usefind(scopepath, { query { userid currentuserid } }); // get the allowed routes state const allowedroutes = usemutablestate(allowedadminroutesstate); // update allowed routes based on user permissions useeffect(() => { if (scopequery data) { // check if the user has admin access const isadmin = scopequery data find(scope => scope type === 'admin\ admin'); if (!isadmin) { // if not an admin, redirect to home page console log('user is not an admin redirecting '); routerstate navigate('/', { redirecturl '/admin' }); return; } // initialize routes with defaultadminroutes const routes = { defaultadminroutes }; // update access for each route based on user's scopes for (const \[key, route] of object entries(routes)) { const hasaccess = scopequery data some(scope => scope type === `admin ${route scope}` || scope type === 'admin\ admin' ); routes\[key] = { route, access hasaccess }; } // update the allowed routes state getmutablestate(allowedadminroutesstate) merge(routes); } }, \[scopequery data]); // if no data yet, show loading if (!scopequery data) { return \<div>loading \</div>; } // render the admin panel layout return ( \<div classname="admin layout"> \<admintopbar /> \<main classname="admin content"> \<adminsidebar /> \<div classname="admin main content"> \<routes> \<route path="/ " element={\<allowedroutes />} /> \</routes> \</div> \</main> \</div> ); } export default adminroutes; this component fetches the user's permission scopes from the server checks if the user has admin access and redirects if not updates the allowedadminroutesstate based on the user's permissions renders the admin panel layout with top bar, sidebar, and content area uses react router to handle sub routes within the admin panel admin sidebar navigation the sidebar provides navigation between admin sections // simplified from src/admin/components/adminsidebar tsx import react from 'react'; import { link } from 'react router dom'; import { usemutablestate } from '@ir engine/hyperflux'; import { allowedadminroutesstate } from ' /allowedadminroutesstate'; function adminsidebar() { // get the allowed routes from state const allowedroutes = usemutablestate(allowedadminroutesstate); return ( \<nav classname="admin sidebar"> \<ul> {/ create a navigation link for each allowed route /} {object entries(allowedroutes value) map((\[key, route]) => { // skip routes the user doesn't have access to if (!route access) return null; return ( \<li key={key}> \<link to={`/admin/${key}`} classname="admin nav link"> \<span classname="admin nav icon">{route icon}\</span> \<span classname="admin nav text">{route name}\</span> \</link> \</li> ); })} \</ul> \</nav> ); } export default adminsidebar; this component reads the allowed routes from allowedadminroutesstate creates navigation links for each route the user has access to uses react router's link component for client side navigation displays the icon and name for each route route rendering the allowedroutes component renders the appropriate content for the current route // simplified from src/admin/allowedroutes tsx import react, { suspense } from 'react'; import { routes, route, uselocation } from 'react router dom'; import { usemutablestate } from '@ir engine/hyperflux'; import { allowedadminroutesstate } from ' /allowedadminroutesstate'; function allowedroutes() { // get the current location const { pathname } = uselocation(); // get the allowed routes from state const allowedroutes = usemutablestate(allowedadminroutesstate); // extract the current route key from the url // e g , "/admin/users" > "users" const path = pathname split('/')\[2] || 'dashboard'; // get the route definition for the current path const currentroute = allowedroutes\[path]? value; // if no route found or user doesn't have access, show not found if (!currentroute || !currentroute access) { return \<div>page not found or access denied\</div>; } // get the component to render const routecomponent = currentroute component; // render the component with suspense for lazy loading return ( \<suspense fallback={\<div>loading \</div>}> \<routes> \<route path="/ " element={\<routecomponent />} /> \</routes> \</suspense> ); } export default allowedroutes; this component determines the current route from the url checks if the user has access to that route renders the appropriate component with suspense for lazy loading handles nested routes within the admin section data table component the admin panel uses a reusable table component for displaying data // simplified from src/admin/common/table tsx import react from 'react'; import { usefind } from '@ir engine/common'; // define the structure of a table column export interface itableheadcell { id string; label string; sortable? boolean; } // props for the datatable component interface datatableprops { query usefind\<any>; // result from usefind hook columns itableheadcell\[]; // column definitions rows any\[]; // data rows onrowclick? (row any) => void; // optional row click handler } function datatable({ query, columns, rows, onrowclick } datatableprops) { // handle sort column change const handlesort = (columnid) => { if (!query setquery) return; // toggle sort direction or set initial sort const currentsort = query query? $sort || {}; const newsort = { \[columnid] currentsort\[columnid] === 1 ? 1 1 }; // update the query with new sort query setquery({ query query, $sort newsort }); }; // handle pagination const handlepagechange = (newpage) => { if (!query setquery) return; const limit = query query? $limit || 10; const skip = newpage limit; // update the query with new pagination query setquery({ query query, $skip skip }); }; // calculate current page and total pages const limit = query query? $limit || 10; const skip = query query? $skip || 0; const currentpage = math floor(skip / limit); const totalpages = math ceil((query total || 0) / limit); return ( \<div classname="data table container"> {/ loading indicator /} {query loading && \<div classname="loading overlay">loading \</div>} {/ table /} \<table classname="data table"> \<thead> \<tr> {columns map((column) => ( \<th key={column id} onclick={() => column sortable && handlesort(column id)} classname={column sortable ? 'sortable column' ''} \> {column label} {/ sort indicator /} {query query? $sort? \[column id] === 1 && ' ↑'} {query query? $sort? \[column id] === 1 && ' ↓'} \</th> ))} \</tr> \</thead> \<tbody> {rows map((row) => ( \<tr key={row\ id} onclick={() => onrowclick && onrowclick(row)} classname={onrowclick ? 'clickable row' ''} \> {columns map((column) => ( \<td key={`${row\ id} ${column id}`}> {row\[column id]} \</td> ))} \</tr> ))} \</tbody> \</table> {/ pagination /} {totalpages > 1 && ( \<div classname="pagination"> \<button onclick={() => handlepagechange(currentpage 1)} disabled={currentpage === 0} \> previous \</button> \<span> page {currentpage + 1} of {totalpages} \</span> \<button onclick={() => handlepagechange(currentpage + 1)} disabled={currentpage === totalpages 1} \> next \</button> \</div> )} \</div> ); } export default datatable; this component takes query results from the usefind hook displays data in a table with sortable columns handles pagination for large datasets supports row click events for interactive tables shows loading indicators during data fetching admin section implementation each admin section is implemented as a react component that fetches and displays data // simplified example of a users admin section import react, { usestate } from 'react'; import { usefind } from '@ir engine/common'; import { userpath } from '@ir engine/common/src/schema type module'; import datatable, { itableheadcell } from ' /common/table'; import userform from ' /userform'; // define the table columns const usercolumns itableheadcell\[] = \[ { id 'name', label 'name', sortable true }, { id 'email', label 'email', sortable true }, { id 'createdat', label 'created', sortable true }, { id 'status', label 'status', sortable true } ]; function usersadminpage() { // state for the selected user (for editing) const \[selecteduser, setselecteduser] = usestate(null); // fetch users from the server const usersquery = usefind(userpath, { query { $limit 10, $skip 0, $sort { createdat 1 } } }); // prepare rows for the table const tablerows = usersquery data? map(user => ({ id user id, name user name, email user email || 'n/a', createdat new date(user createdat) tolocaledatestring(), status user active ? 'active' 'inactive' })) || \[]; // handle row click to select a user for editing const handlerowclick = (row) => { const user = usersquery data find(u => u id === row\ id); setselecteduser(user); }; // handle form close const handleformclose = () => { setselecteduser(null); }; return ( \<div classname="admin section"> \<h1>manage users\</h1> {/ user table /} \<datatable query={usersquery} columns={usercolumns} rows={tablerows} onrowclick={handlerowclick} /> {/ edit form (shown when a user is selected) /} {selecteduser && ( \<userform user={selecteduser} onclose={handleformclose} /> )} \</div> ); } export default usersadminpage; this component fetches user data from the server using the usefind hook formats the data for display in the datatable handles user selection for editing renders a form for editing when a user is selected admin workflow the complete admin panel workflow follows this sequence sequencediagram participant admin as administrator participant browser as web browser participant auth as authentication system participant router as client side router participant adminui as admin panel ui participant api as feathersjs api participant db as database admin >>browser navigate to /admin browser >>router route to /admin router >>auth check admin permissions auth >>api fetch user scopes api >>db query user permissions db >>api return permissions api >>auth return scopes alt has admin permission auth >>adminui render admin layout adminui >>admin display admin panel admin >>adminui click "users" in sidebar adminui >>router navigate to /admin/users router >>adminui render users component adminui >>api fetch users data api >>db query users db >>api return users api >>adminui return user data adminui >>admin display users table else no admin permission auth >>router redirect to home page router >>browser navigate to / browser >>admin display home page end this diagram illustrates the administrator navigates to the admin panel the system checks if the user has admin permissions if authorized, the admin panel layout is rendered the administrator clicks a section in the sidebar the appropriate component is rendered for that section data is fetched from the server and displayed if not authorized, the user is redirected to the home page integration with other components the admin panel integrates with several other components of the ir engine client authentication and authorization the admin panel relies on the authentication system for access control // example of authentication integration import { usefind } from '@ir engine/common'; import { scopepath } from '@ir engine/common/src/schema type module'; import { engine } from '@ir engine/ecs'; function checkadminaccess() { // get the current user id const currentuserid = engine instance userid; // fetch the user's permission scopes const scopequery = usefind(scopepath, { query { userid currentuserid } }); // check if the user has admin permission const isadmin = scopequery data? some(scope => scope type === 'admin\ admin' ); return isadmin; } this integration fetches the user's permission scopes from the server checks if the user has the required admin permissions controls access to the admin panel and its features client side routing the admin panel uses the client side routing system for navigation // example of routing integration import { routes, route, link } from 'react router dom'; import { routerstate } from ' /common/services/routerservice'; function adminnavigation() { // programmatic navigation const navigatetodashboard = () => { routerstate navigate('/admin/dashboard'); }; return ( \<nav> {/ declarative navigation with link /} \<link to="/admin/users">users\</link> \<link to="/admin/projects">projects\</link> {/ programmatic navigation with button /} \<button onclick={navigatetodashboard}>dashboard\</button> \</nav> ); } this integration uses react router for declarative navigation with link components leverages the routerstate for programmatic navigation creates a consistent navigation experience within the admin panel feathersjs api services the admin panel uses feathersjs services to fetch and modify data // example of feathersjs integration import { usefind, usemutation } from '@ir engine/common'; import { userpath } from '@ir engine/common/src/schema type module'; function usermanagement() { // fetch users const usersquery = usefind(userpath); // create mutation for updating users const usermutation = usemutation(userpath); // function to update a user const updateuser = async (userid, data) => { try { await usermutation patch(userid, data); console log('user updated successfully'); } catch (error) { console error('failed to update user ', error); } }; // function to create a new user const createuser = async (userdata) => { try { const newuser = await usermutation create(userdata); console log('user created successfully ', newuser); } catch (error) { console error('failed to create user ', error); } }; // component implementation // } this integration uses the usefind hook to fetch data from services leverages the usemutation hook for creating, updating, and deleting data provides a consistent pattern for data operations across the admin panel benefits of the admin panel the admin panel system provides several key advantages centralized management provides a single interface for all administrative tasks role based access ensures only authorized users can perform administrative functions consistent interface uses shared components for a unified management experience efficient operations streamlines common administrative tasks comprehensive oversight enables monitoring of all system aspects extensibility allows new administrative sections to be added easily separation of concerns keeps administrative functions separate from the regular user experience these benefits make the admin panel an essential tool for managing the ir engine platform conclusion the admin panel system completes our exploration of the ir engine client core throughout this documentation, we've examined hyperflux state management for reactive application state feathersjs api & real time services for server communication user authentication and authorization for security client side routing and navigation for moving between views xrui and in world widgets for 3d user interfaces avatar management and customization for user representation instance provisioning and networking for shared environments admin panel system for platform management together, these components form a comprehensive client architecture that enables immersive, multi user experiences with robust management capabilities