Pages and Routing
Hub GUI uses @Resolver() decorated controllers to map URL paths to page views. Pages are built on a layout hierarchy with a shared tab system for organizing content.
Page Hierarchy
BasePanel (io-oidis-gui)
└── BasePageLayout (abstract)
├── StickyHeaderLayout — sticky header + footer, scrollable content
│ └── MainPage — user-facing hub (/index)
├── MenuPageLayout — sidebar menu + header, scrollable content
│ └── DashboardPage — admin dashboard (/dashboard)
└── SSOPage — centered auth forms (/login)
AsyncBasePageController (io-oidis-services)
└── BasePageLayoutController<Page> — access validation, tab lifecycle, notifications
├── StickyHeaderLayoutController<Page> — header binding (no menu)
│ └── MainPageController — user-facing hub controller
├── MenuPageLayoutController<Page> — menu binding, role-scoped items
│ └── DashboardPageController — admin dashboard controller
└── SSOController — SSO auth flow controller
Standalone pages (no tab system): InitPage, AdminInitPage, PrintPage.
Route Registration
Routes are registered via the @Resolver() decorator on page controllers:
| Route | Controller | Page | Auth Required |
|---|---|---|---|
/, /index, /index.html, /web/ | MainPageController | MainPage | Yes (any authenticated user) |
/dashboard, /tools, /tools/{toolId}, /backdoorLogin | DashboardPageController | DashboardPage | Yes (admin or sysadmin) |
/login, /logout, /signOut, /signIn, /signUp, /resetPassword, /resetPassword/{token}, /accountActivation, /accountActivation/{token}, /invite, /invite/{token}, /twoFactorChallenge | SSOController | SSOPage | No |
/ServerError/NotReady | InitPageController | InitPage | No |
/AdminInit | AdminInitPageController | AdminInitPage | No (token-based) |
/print/{user}/{token} | PrintPageController | PrintPage | No (URL token-based) |
@Resolver("/", "/index", "/index.html", "/web/")
export class MainPageController extends StickyHeaderLayoutController<MainPage> {
// ...
}
Tab System
Pages that extend BasePageLayout use a tab system to organize content into switchable panels. Tabs are defined in the page's getTabsConfiguration() method.
ITabDescriptor
Each tab is described by an ITabDescriptor object:
| Property | Type | Purpose |
|---|---|---|
id | string | Unique tab identifier |
name | string | Display name in menu |
link | string | Primary URL route (e.g. /users) |
altLinks | string[] | Alternative routes (e.g. /users/{userId}) |
icon | string | SVG or HTML icon for menu item |
scope | MenuItemScope | Visibility scope (see Dashboard docs) |
content | BasePanel | IClassName | Panel view class or instance |
controller | BasePanelController | IClassName | Panel controller class or instance |
isDefault | boolean | Selected on page load |
enabled | boolean | Visible in UI (false = registered but hidden) |
autoReload | boolean | Re-fetch data when tab is re-selected |
reloadOnRestart | boolean | Re-fetch data on server restart notification |
lazyLoad | boolean | Load panel only when first selected (default: true) |
declaredBy | string | Override property name on page instance |
The tab panel is assigned to the page as this[declaredBy ?? id] and the tab controller to the page controller
under the same name. An ID that matches an existing framework member silently overwrites it — for example a tab
with ID events replaces GuiCommons.events (the element events manager) and the page fails in Register() with
this.events.Subscribe is not a function. Use IDs that cannot collide with base class members (e.g.
eventsSection) or set declaredBy. The tab route comes from link or the controller's @Resolver, not from the ID.
PageTabIds
Common tab IDs shared across pages:
| Constant | Value | Purpose |
|---|---|---|
PageTabIds.FAQ | "faq" | FAQ panel |
PageTabIds.FEEDBACK | "feedback" | User feedback panel |
PageTabIds.RELEASE_NOTES | "releaseNotes" | Release notes |
PageTabIds.NOTIFICATIONS | "notifications" | Notifications |
Tab Registration Example
protected getTabsConfiguration() : ITabDescriptor[] {
return [
{
id: "main",
isDefault: true,
content: MainPanel,
controller: MainPanelController
},
{
id: PageTabIds.FAQ,
enabled: false,
content: FAQPanel,
controller: FAQPanelController
}
];
}
Tab Selection Flow
- URL arrives →
@Resolvermatches route to page controller accessValidator()checks authentication and authorizationbeforeLoad()initializes controller-specific stateafterLoad()registers tabs fromgetTabsConfiguration(), binds dialogsprocessRequestPath()matches current URL against tablink/altLinksusing pattern matching ({param}→ wildcard)selectTab()activates the matching tab:- Lazy-loaded tabs: Added to DOM on first selection,
beforeLoad()called on controller - Non-lazy tabs: Made visible immediately
- Menu item marked active (dashboard only)
- Lazy-loaded tabs: Added to DOM on first selection,
FetchData()called on panel controller ifautoReloador first load
If user.Need2FACheck is true, the controller redirects to /twoFactorChallenge before showing any content.
Layout Anatomy
StickyHeaderLayout (MainPage)
┌──────────────────────────────────┐
│ HeaderPanel (sticky, 70px) │ ← logo, login/logout, theme toggle, notifications
├──────────────────────────────────┤
│ │
│ pageContent (centered) │ ← tab panels rendered here
│ ┌────────────────────────┐ │
│ │ panelsContainer │ │
│ └────────────────────────┘ │
│ │
├──────────────────────────────────┤
│ FooterPanel │ ← footer links, version info
└──────────────────────────────────┘
│ printSection (hidden) │ ← print-optimized content
│ toasts │ ← toast notifications
│ systemState │ ← server state indicator
MenuPageLayout (DashboardPage)
┌──────────────────────────────────┐
│ HeaderPanel (fixed, 70px) │ ← logo, user menu, theme toggle
├──────────┬───────────────────────┤
│ │ │
│ menuPanel│ scrollBody │
│ (sidebar)│ ┌─────────────────┐ │
│ │ │ pageContent │ │
│ MenuItem │ │ ┌─────────────┐ │ │
│ MenuItem │ │ │ panel tabs │ │ │
│ MenuItem │ │ └─────────────┘ │ │
│ ... │ └─────────────────┘ │
│ │ │
├──────────┴───────────────────────┤
│ footerPanel (DashboardFooter) │
└──────────────────────────────────┘
Access Validation
Each page controller implements accessValidator() to guard access:
// MainPageController — requires any authenticated user
protected async accessValidator() : Promise<boolean> {
await this.user.Reload();
return super.accessValidator(); // checks IsAuthenticated
}
// DashboardPageController — requires admin
protected async accessValidator() : Promise<boolean> {
await this.user.Reload();
return this.user.IsAuthenticated && this.user.IsAdmin;
}
When validation fails:
- Not authenticated → redirects to
/login - Authenticated but not admin →
reloadToNotAuthorized()logs error and shows access denied - System not ready → redirects to
/ServerError/NotReady
Shared Components
All BasePageLayout pages include these built-in components:
| Component | Type | Purpose |
|---|---|---|
skeletonLoader | SkeletonLoader | Loading placeholder during data fetch |
headerStrip | GuiCommons | Environment banner (dev, eap, localhost) |
systemState | SystemStateNotification | Real-time server health indicator |
releaseNotesDialog | ReleaseNotesDialog | What's new dialog |
notificationsDialog | NotificationsDialog | Notification center |
backdoorLoginDialog | BackdoorLoginDialog | Emergency admin login |
printSection | PrintContainer | Print-optimized content area |
toasts | GuiCommons | Toast notification container |
See also: Hub GUI Overview | Dashboard and Tools Menu | SSO and Authentication | Routing