We are excited to announce the first release of IRIS Portico, a web-based management portal and admin console for InterSystems IRIS.
Please refer to the README and Documentation for detailed installation and usage instructions.
This is the initial release. Feedback and contributions are welcome!
A permission-aware Management Portal for InterSystems IRIS.
IRIS Portico gives administrators a single, secure, role-aware console to manage
everything the IRIS SysAdmin API (/api/admin) exposes — with a focus on the
areas most existing portals under-serve: security & secrets and a unified
log center.
Built from scratch against the public
sysadmin-api-specification.
The portal covers all six core functional areas, mapped to the SysAdmin API:
| Functional area | What you can do | Backing API |
|---|---|---|
| Web Apps & REST | List/create/enable web applications, inspect PCT access, manage web sessions & namespaces | /v2/web-apps, /v2/web-sessions, /v2/namespaces |
| Permission management | Manage users, roles, resources, services, SQL privileges, web authentication | /v2/security/users, /roles, /resources, /services, /sql-privileges |
| Security & secrets | Wallets, X.509 credentials, OAuth 2.0 (servers/clients/resource servers), SSL, encryption, MFT/LDAP, superservers | /v2/wallet, /v2/security/x509-credentials, /oauth2, /ssl-configurations, … |
| Task management | Create / edit / delete scheduled tasks, run / suspend / resume, view history & upcoming schedules, control the task manager | /v2/tasks, /v2/task (CRUD), /v2/task/history, /v2/task/upcoming |
| System management | Inspect & control processes, devices, system usage, locks, databases | /v2/processes, /v2/devices, /v2/monitor/*, /v2/locks, /v2/databases |
| Logs | A unified Log Center aggregating system status + security audit + journal activity into one time-ordered stream, plus journal (files, file detail, async record browser, settings) and audit (enable toggle, event definitions, async record query, purge) deep-dives | /v2/journal/*, /v2/security/audit/*, /v2/monitor/* |
Plus a dedicated Async Task Center for long-running operations (the
202 + Location pattern) with cancel / pause / resume — and a full set of
management pages:
/v2/database-dir*): list,/v2/database)./v2/namespace*) — list, detail, create/delete, and all/v2/license/*) — license key info / validate / activate,/v2/wqm-category*) — wait-queue category list, detail, upsert,/v2/ecp/*) — settings, data servers/v2/ext-lang-server*) —The System page also covers device detail + settings + subtype CRUD and
process broadcast, the Tasks page covers full task CRUD, and the
Security page covers the full write block: user / role / resource /
service CRUD, SQL admin + column privilege grant/revoke, wallet secret
upsert, X.509 credential CRUD, MFT / LDAP / superserver CRUD, privileged-
routine CRUD, web-auth SMTP password, audit-event CRUD, encryption key-file
management, and OAuth 2.0 AS / client / resource-server configuration.
I18nService (≈ 1026 strings,core/i18n/en.ts + core/i18n/zh.ts) with a top-bar toggle. The choicelocalStorage and defaults to navigator.language. Deliberately@angular/localize: that requires a per-locale build pipeline, while:root; the light theme is a full token override under:root[data-theme='light']. ThemeService flips the attribute, persists theprefers-color-scheme.The SysAdmin API gates every operation on a %Admin_* privilege. IRIS Portico
reads the current user’s privileges from GET /info and enables or hides each
action accordingly — a user without %Admin_Secure simply won’t see the user/
role management controls. The top bar shows how many privileges the session holds.
┌──────────────────────────────────────────────────────────────────────┐
│ Browser — Angular 18 (standalone components, single build) │
│ │
│ /login ── standalone sign-in page (no shell chrome) │
│ │
│ '' (authGuard) ── ShellComponent │
│ ├─ sidebar (collapsible) or top nav (toggleable): 14 items │
│ ├─ top bar: 中文/EN toggle · 🌙/☀️ theme toggle · user box │
│ └─ router-outlet ── 14 screens: dashboard + webapps, │
│ permissions, security, tasks, system, databases, logs, │
│ async, ecp, ext-lang-servers, namespaces, license, wqm │
│ │
│ I18nService (runtime zh/en dictionaries) │
│ ThemeService (data-theme on <html>, CSS custom properties) │
└──────────────┬──────────────────────────────────┬───────────────────┘
│ same origin (port 80) │ optional (IRIS :52773)
▼ ▼
┌────────────────────────────────┐ ┌──────────────────────────────┐
│ nginx (port 80) │ │ /csp/portico-api (BFF) │
│ · serves the Angular SPA │ │ ObjectScript: health + │
│ · proxies /api/admin → IRIS │ │ server-side log aggregation │
└───────────────┬────────────────┘ └──────────────────────────────┘
│
▼
┌────────────────────────────────────────────────────────────────┐
│ /api/admin (IRIS system API, port 52773) │
│ 276 operations · JWT (POST /login) + Basic auth │
└────────────────────────────────────────────────────────────────┘
▲
│ shared, framework-agnostic
┌────────────────────────────────────────────────────────────────┐
│ @iris-portico/api-client (TypeScript) │
│ 142 generated types · auth (JWT + basic) · envelope unwrap │
│ error mapping · 13 domain groups · async helpers │
└────────────────────────────────────────────────────────────────┘
Two design decisions stand out.
1. A framework-agnostic API client. All the hard parts — authentication
(JWT with basic fallback), the BaseResponse envelope unwrapping, error
mapping (401/403/404/409), the 142 generated types, and the 13 domain groups —
live in @iris-portico/api-client. The Angular app is a thin layer on top.
This is what makes it easy to ship React or Vue variants later: they just
consume the same client.
2. nginx in front of IRIS. The frontend is served as a static SPA by nginx
on port 80, which also proxies /api/admin to IRIS’s built-in web server on
52773. Because the SPA and the API share the same origin (port 80), the browser
calls /api/admin directly with JWT (obtained from POST /login, with
Basic auth as a fallback for older instances) and no CORS is involved. This
deliberately decouples frontend delivery from IRIS web-app registration — the
portal works even if the ObjectScript BFF never loads.
3. The shell is a component, not the app root. app.component.ts is a bare
<router-outlet />. The authenticated chrome (sidebar + top bar) lives in a
standalone ShellComponent that is the loadComponent of the guard-protected
'' route, so unauthenticated visitors see only the login page — no sidebar,
no navigation, nothing to click. A direct hit on a protected URL (e.g.
/tasks) is redirected to /login by the CanActivateFn guard.
4. The session survives the access token’s 60 s TTL. IRIS 2026.2 issues a
short-lived JWT: the access token expires after ~60 s, while the refresh
token lives ~15 min. The portal never asks the user to re-sign-in in that
window — on the first 401 the client calls POST /refresh, swaps in the new
access token, and retries the request once (the user sees no error). The whole
auth state (access and refresh token) is persisted to localStorage, so a
page reload restores a refreshable session, not a dead one. Refresh is
single-flight (concurrent 401s share one /refresh call). When the session
finally dies (the refresh token is expired, rotated away, or invalidated by a
server restart), the client clears the state and navigates to /login —
the user is never stranded on a page full of 401 errors (the route guard only
runs on navigation, so it cannot react to a mid-page token death; AuthService
watches the authed flag and redirects on an authenticated → unauthenticated
transition).
Requires IRIS 2026.2 or later. The
v2SysAdmin API (the 276/v2/*
operations and the JWTPOST /loginendpoint) only exists in 2026.2+. On
2026.1 the API reportsapiVersion: 1and the/v2/*paths return404,
so the six functional-area screens would have nothing to call. The Dockerfile is
pinned tointersystemsdc/iris-community:2026.2for this reason.
docker compose up --build
Then open:
http://localhost:80/
Sign in with the credentials the container creates:
username: Portico
password: Portico123
The container:
:80, proxies /api/admin → IRIS :52773),Portico user (so /api/admin JWT + Basic auth work),ipm load compiles + activates + commits the portico.* classes, thenDispatchClass).The BFF is optional. If it fails to load, the portal is fully functional —
the Log Center falls back to client-side aggregation, and all CRUD + auth go
straight to/api/admin(via the nginx proxy).
v2 SysAdmin API —/v2/* operations + JWT POST /login — is only built in from 2026.2).cd api-client && npm install && npm run build
cd ../src/web && npm install && npm run build:prod
ipm load; one-time: install the IPM (ZPM) client
do $system.OBJ.Load("<path>/ipm-installer.xml","ck")
; load the package (module.xml + portico/ live in src/cls/)
ipm "load -verbose <path>/src/cls"
; register the web apps (frontend + BFF with its DispatchClass)
do ##class(portico.Install).Run()
Point the portico web app’s ppath at dist/portico-web/browser.http://localhost:52773/csp/portico/.The BFF is optional. If you skip it, the portal is fully functional —
the Log Center falls back to client-side aggregation, and all CRUD + auth go
straight to/api/admin.
For a minimal-footprint demo, skip nginx entirely and let IRIS’s built-in web
server serve everything:
docker compose -f docker-compose.demo.yml up --build
Then open:
http://localhost:52773/csp/portico/
Sign in with the same Portico / Portico123 credentials.
In this mode the portico web app serves the Angular SPA as static files
(the ServeFiles web-app setting, on by default, lets the IRIS built-in web
server serve files from the app’s physical path) and the frontend calls
/api/admin directly on the same origin — no proxy, no extra packages. The
Dockerfile build arg INSTALL_NGINX=0 skips the nginx install; the default
docker-compose.yml keeps the production layout (nginx on :80). The
frontend’s <base href="https://github.com/xohg/iris-portico/blob/main/auto"> resolves asset paths correctly in both modes.
One demo-mode limitation: IRIS web-application definitions
(Security.Applications) have no “fallback” setting, so the built-in web
server does not https://github.com/xohg/iris-portico/blob/main/auto-redirect SPA deep links (e.g./csp/portico/tasks)
toindex.html. Enter via the app root —
http://localhost:52773/csp/portico/— which servesindex.html; the
client-side router then handles all in-app navigation. (In production,
nginx’stry_files ... /index.htmlprovides the fallback, so deep links
work there.)
├── Dockerfile / docker-compose.yml one-command run (nginx + IRIS, production)
├── docker-compose.demo.yml demo mode (IRIS built-in web server only)
├── nginx-portico.conf SPA on :80 + /api/admin proxy
├── portico-setup.sh start-of-container provisioning (user + IPM + ZPM)
├── api-client/ framework-agnostic TS client (shared foundation)
│ ├── scripts/gen-types.js spec → 142 TS types
│ ├── src/types/index.ts generated types
│ ├── src/client/ AdminClient, AuthManager, 13 domain groups, errors
│ └── test/client.test.js 13 unit tests (node:test, mock fetch)
├── src/cls/ IPM/ZPM package (module.xml + portico/)
│ ├── module.xml ZPM module descriptor (portico 1.0.0)
│ └── portico/ ObjectScript BFF
│ ├── ZPM.cls module descriptor class
│ ├── Install.cls one-time, idempotent web-app setup
│ ├── Web/Api.cls %CSP entry point (health + log center)
│ ├── Service/LogAggregator.cls server-side log aggregation
│ └── UnitTest.cls %UnitTest cases (run inside IRIS)
└── src/web/ Angular 18 frontend (standalone components)
├── public/ static assets (favicon + logo, served at the app root)
└── src/app/
├── core/ AdminService, AuthService, PermissionService,
│ ThemeService, I18nService, coalesce, auth guard
├── core/i18n/ en.ts + zh.ts runtime dictionaries (~1026 keys)
├── areas/ all 14 screens: dashboard + 6 functional areas + async,
│ databases, ecp, ext-lang-servers, namespaces,
│ license, wqm
├── login/ standalone sign-in page
├── shell.component.ts authenticated shell (sidebar + top bar + outlet)
├── app.component.ts bare <router-outlet />
└── app.routes.ts guard-protected '' route + standalone /login
# API client (13 unit tests, mock fetch) cd api-client && npm install && npm testType-check the client
cd api-client && npm run typecheck
Build the frontend (production)
cd src/web && npm install && npm run build:prod
The ObjectScript unit tests (portico.UnitTest) run inside IRIS:
set $namespace = "portico"
do ##class(%UnitTest.Run).Run("portico.UnitTest")