Skip to main content

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:

RouteControllerPageAuth Required
/, /index, /index.html, /web/MainPageControllerMainPageYes (any authenticated user)
/dashboard, /tools, /tools/{toolId}, /backdoorLoginDashboardPageControllerDashboardPageYes (admin or sysadmin)
/login, /logout, /signOut, /signIn, /signUp, /resetPassword, /resetPassword/{token}, /accountActivation, /accountActivation/{token}, /invite, /invite/{token}, /twoFactorChallengeSSOControllerSSOPageNo
/ServerError/NotReadyInitPageControllerInitPageNo
/AdminInitAdminInitPageControllerAdminInitPageNo (token-based)
/print/{user}/{token}PrintPageControllerPrintPageNo (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:

PropertyTypePurpose
idstringUnique tab identifier
namestringDisplay name in menu
linkstringPrimary URL route (e.g. /users)
altLinksstring[]Alternative routes (e.g. /users/{userId})
iconstringSVG or HTML icon for menu item
scopeMenuItemScopeVisibility scope (see Dashboard docs)
contentBasePanel | IClassNamePanel view class or instance
controllerBasePanelController | IClassNamePanel controller class or instance
isDefaultbooleanSelected on page load
enabledbooleanVisible in UI (false = registered but hidden)
autoReloadbooleanRe-fetch data when tab is re-selected
reloadOnRestartbooleanRe-fetch data on server restart notification
lazyLoadbooleanLoad panel only when first selected (default: true)
declaredBystringOverride property name on page instance
Tab IDs become properties

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:

ConstantValuePurpose
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​

  1. URL arrives → @Resolver matches route to page controller
  2. accessValidator() checks authentication and authorization
  3. beforeLoad() initializes controller-specific state
  4. afterLoad() registers tabs from getTabsConfiguration(), binds dialogs
  5. processRequestPath() matches current URL against tab link/altLinks using pattern matching ({param} → wildcard)
  6. 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)
  7. FetchData() called on panel controller if autoReload or first load
tip

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
┌──────────────────────────────────┐
│ 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:

ComponentTypePurpose
skeletonLoaderSkeletonLoaderLoading placeholder during data fetch
headerStripGuiCommonsEnvironment banner (dev, eap, localhost)
systemStateSystemStateNotificationReal-time server health indicator
releaseNotesDialogReleaseNotesDialogWhat's new dialog
notificationsDialogNotificationsDialogNotification center
backdoorLoginDialogBackdoorLoginDialogEmergency admin login
printSectionPrintContainerPrint-optimized content area
toastsGuiCommonsToast notification container

See also: Hub GUI Overview | Dashboard and Tools Menu | SSO and Authentication | Routing