Adding an admin screen
The admin panel (admin/) is React 18 with umi/max (@umijs/max) and Ant Design. A screen is a
route in config/routes.ts, an access rule in src/access.ts, a page under src/pages, API calls
in src/services/ulams and menu labels in src/locales. The example below is the LTI screen
(Integrations → LTI, /integrations/lti), added in Phase 1.
-
Permission constant. The API permission string (from the package’s permission enum, see Adding an endpoint) goes into the
PERMISSIONSenum inadmin/src/consts/permissions.ts:LtiManage = 'lti_manage', -
Access rule.
admin/src/access.tsreturns one boolean per rule name for the current user.havePermissionsInDashboard(...)requires the dashboard permission plus the listed ones. Other helpers:havePackageInstalled(PACKAGES.X)(the API reports installed packages, seesrc/consts/packages.ts) andhaveSettingsInDashboard('hideInMenu-...', true)to hide a menu entry by a setting.ltiPermission: havePermissionsInDashboard(PERMISSIONS.LtiManage), -
Route. Add an entry to
admin/config/routes.ts.namebuilds the menu label key,accessnames the rule from step 2,componentis relative tosrc/pages:{path: '/integrations',name: 'Integrations',icon: 'api',access: 'ltiPermission',routes: [{ path: '/integrations', redirect: '/integrations/lti', access: 'ltiPermission' },{path: '/integrations/lti',name: 'LTI',icon: 'api',access: 'ltiPermission',component: './Lti',},],},A route whose rule is false is left out of the menu, and opening it directly does not render the page.
-
Service. API calls are plain functions over umi’s
requestinadmin/src/services/ulams/<name>.ts. Paths are relative: the request interceptor insrc/app.tsxprefixes the tenant API URL and adds the bearer token andX-localeheader. Fromsrc/services/ulams/lti.ts:import { request } from 'umi';type Response<T> = { success: boolean; data: T; message: string };export const ltiTools = () =>request<Response<LtiTool[]>>('/api/admin/lti/tools', { method: 'GET' });export const saveLtiTool = (tool: Partial<LtiTool>) =>request<Response<LtiTool>>(tool.id ? `/api/admin/lti/tools/${tool.id}` : '/api/admin/lti/tools', {method: tool.id ? 'PUT' : 'POST',data: tool,});Older services put shared types in
src/services/ulams/typings.d.ts(API.*); new ones may export their types next to the functions, aslti.tsdoes. -
Page.
admin/src/pages/Lti/index.tsxexports a component wrapped inPageContainerfrom@ant-design/pro-layout, built from Ant Design components, with strings throughFormattedMessage/useIntlfromumi:const LtiPage: React.FC = () => {// ...return (<PageContainercontent={<FormattedMessageid="lti.intro"defaultMessage="LTI 1.3: add external tools to lessons (with grades and deep linking), and let other LMSs open ulams courses."/>}><Tabs items={[/* Tools, Platforms */]} /></PageContainer>);};export default LtiPage; -
Translations. Menu labels are
menu.<name>andmenu.<parent>.<child>. Add them to every locale you ship; English and Polish are the maintained ones:admin/src/locales/en-US.ts 'menu.Integrations': 'Integrations','menu.Integrations.LTI': 'LTI',// admin/src/locales/pl-PL.ts'menu.Integrations': 'Integracje','menu.Integrations.LTI': 'LTI',Screen strings can rely on
defaultMessage(the LTI page’slti.*ids have no locale entries); add entries when the screen needs a Polish translation.
Styling and imports
Section titled “Styling and imports”- Ant Design components first. For custom styles use a CSS Module or Less and the
--ulams-*custom properties;styled-componentsimports are blocked by ESLint (admin/.eslintrc.js,no-restricted-imports) and byfront’slint:styledcheck in CI. @lumieducation/*,h5p-*and@escolalms/h5p-reactimports are blocked: H5P is GPL and runs inapi/h5p. Use the iframe wrappers insrc/components/H5P/.- Shared libraries come from
front/src/libas@ulams/*(aliases inadmin/config/config.ts).
Check it
Section titled “Check it”corepack yarn dev:admin # http://localhost:8000corepack yarn turbo run typecheck lint test --filter=admin# the Prettier check CI runs (admin's own lint script writes instead of checking)cd admin && node "$(node -p "require.resolve('prettier/bin-prettier.js', { paths: [process.cwd()] })")" \ --check "**/**.{js,jsx,tsx,ts,less,md,json}" --end-of-line autoLog in as admin@ulams.app / secret (platform) or admin@<slug>.ulams.app on a tenant admin
(http://<slug>.admin.localhost). For an end-to-end test, add a spec to admin/src/e2e (Playwright,
see Testing). The screen also needs a user-facing page in the
Administrators section of this site; the docs coverage check
(yarn workspace @ulams/docs coverage) fails until some page lists the route in adminRoutes.