A small shared UI "shell" + helper library designed to be used across various FERCBusters Open Source projects.
It assumes you're serving mostly-static HTML pages (no SPA) and using:
- Bootstrap 5 (CSS + JS bundle)
- bootstrap-icons
- D3 (page-specific; not bundled here)
- Fixed horizontal Bootstrap navbar (
navbar-keen) - Consistent brand slot (icon + name)
- Consistent buttons, cards, tables, muted text (via
styles.css) - Optional:
- Framework selector (adds
?framework=<slug>to nav links) - Global search box (optionally with datalist autocomplete)
- Notification bell + badge (polling + optional websocket)
- Shortcuts dropdown: "Save this view…" + list + delete
- Framework selector (adds
- CSRF header handling
apiGet/apiPost/apiPatch/apiPut/apiDelete/apiPostForm- querystring helpers (
qs/qbool/qint/setQuery) - timestamp helpers (
fmtTs, filename-safefmtTsFilename) - configurable visible date-format ↔ yyyy-mm-dd helpers
- sortable tables
toast()helper for Bootstrap alertsbindAutoApply()helper (debounced auto-apply for filters)
dist/app.js– bundled ESM module for consuming applications.dist/styles.css– bundled themeable CSS for consuming applications.src/js/*.js– maintainable JavaScript source fragments, split by feature area.src/css/*.css– maintainable CSS source fragments, split by feature area.scripts/build.mjs– concatenates the ordered source fragments intodist/app.jsanddist/styles.css.
The source fragments intentionally share one bundle scope and are concatenated in the explicit order listed in scripts/build.mjs. Edit the relevant file under src/js/ or src/css/, then run npm run build; do not edit generated files under dist/ directly.
- Copy the kit into each app's
public/(recommended path):
services/ui/public/vendor/mosp-design-system/
app.js
styles.css
- Include Bootstrap + icons
<link href="/vendor/bootstrap/bootstrap.min.css" rel="stylesheet">
<link href="/vendor/bootstrap-icons/bootstrap-icons.min.css" rel="stylesheet">
<link rel="stylesheet" href="/vendor/mosp-design-system/styles.css">- Add a navbar mount element:
<body data-page="controls">
<div id="navbar"></div>
...
</body>- Configure BEFORE importing (inline
<script>above any module imports):
Example for KEEN:
<script>
window.mospUiConfig = {
appId: "keen",
appName: "Keen",
brandIcon: "/keen.svg",
brandHref: "/",
csrf: { cookieName: "keen_csrf" },
auth: {
meEndpoint: "/api/v1/me",
logoutEndpoint: "/api/v1/auth/logout",
logoutRedirect: "/login.html",
accountHref: "/account.html"
},
links: [
{ label: "Controls", href: "/?stay=1", key: "controls" },
{ label: "Events", href: "/events.html", key: "events" },
{ label: "Sources", href: "/sources.html", key: "sources" },
{ label: "Admin", href: "/admin.html", key: "admin", requireAny: ["is_admin", "can_audit_trail"] }
],
framework: {
enabled: true,
endpoint: "/api/v1/frameworks",
queryParam: "framework",
default: "ISO27001:2022"
},
search: {
enabled: true,
placeholder: "Search events",
targetPath: "/events.html",
queryParam: "q",
datalistId: "mospSavedSearches",
datalistEndpoint: "/api/v1/me/saved-searches"
},
shortcuts: {
enabled: true,
endpoint: "/api/v1/me/shortcuts"
},
bell: {
enabled: true,
href: "/admin.html#questions",
title: "Open questions",
summaryEndpoint: "/api/v1/admin/questions/summary",
countKey: "open_count",
wsUrl: (location.protocol === "https:" ? "wss://" : "ws://") + location.host + "/api/ws/notifications",
wsType: "questions.open_count"
}
};
</script>- In your page modules, call
initNavbar():
import { initNavbar, apiGet } from "/vendor/mosp-design-system/app.js";
const me = await initNavbar();
// ... page-specific logic ...In each app's UI build directory:
-
add dependency:
"@FERCBusters/mosp-design-system": "git+ssh://git@github.com/FERCBusters/mosp-design-system.git#dev"
-
add a build/copy step to place
dist/intoservices/ui/public/vendor/mosp-design-system/.
- Add the UI kit as a submodule into each repo (e.g.
vendor/mosp-design-system/) - Copy
dist/*intopublic/vendor/mosp-design-system/during build/deploy.
Navbar autocomplete and shortcuts share their initial saved-search result when configured with the same endpoint. Saving or deleting a shortcut still refreshes the list from the API. A failed autocomplete request allows shortcuts to retry.
getLoadedFrameworkCatalog() returns the original successful framework-picker
response, including application-specific metadata, or null before it is loaded.
Applications can reuse it after initNavbar() to render additional navigation
without requesting the catalogue again. Explicit refreshes remain the caller's
responsibility. The response stays in memory for the current document.
Run npm test for the navbar data regression checks and npm run build to build
the distributed assets.