doc/developers/README.UI.architecture.md
ntopng uses a hybrid rendering model: Lua handles server-side page rendering and exposes REST API endpoints, while Vue 3 powers the interactive UI components. The long-term goal is to migrate fully to Vue 3 SPA.
http_src/ # Vue 3 source (compiled → httpdocs/dist/)
├── vue/ # Page-level Vue SFCs (.vue files)
│ └── ntop_vue.js # Registry: maps component names → Vue SFC exports
├── components/ # Reusable sub-components (charts, widgets, DataTable)
├── services/ # Global services (URL manager, event bus, etc.)
├── utilities/ # Formatters, validators, DataTable helpers
├── constants/ # Shared constants
└── ntopng.js # Main JS entry point
httpdocs/ # Web root served by ntopng's built-in HTTP server
├── dist/ # Compiled assets (DO NOT edit manually)
│ ├── ntopng.js # Main Vue bundle (output of build)
│ ├── vendor-*.js # Split vendor chunks (Vue, DataTables, charts…)
│ └── *.css # Compiled CSS (dark/white/custom themes)
└── templates/
├── pages/
│ └── vue_page.template # Generic Lua template that mounts a Vue component
└── widgets/ # Reusable Lua widget templates
A Lua file (e.g. scripts/lua/alert_stats.lua) handles the HTTP request. It:
local context = {
ifid = interface.getId(),
-- other initial data…
}
local json_context = json.encode(context)
vue_page.templateThe Lua script delegates HTML rendering to the shared Vue template:
template_utils.render("pages/vue_page.template", {
vue_page_name = "PageAlertStats", -- must match a key in ntop_vue.js
page_context = json_context -- JSON passed verbatim to the browser
})
The template emits a thin HTML shell containing the mount point and a bootstrap script:
<div id="PageAlertStats" style="position: relative">
<page-vue :context="context"></page-vue>
</div>
<script>
$(function () {
// ntopVue is the global object built from ntop_vue.js
start_vue("PageAlertStats", ntopVue["PageAlertStats"], /* JSON context */);
});
</script>
start_vue() (defined inside vue_page.template) creates a Vue 3 app and mounts the named component onto the <div>. The JSON context is passed as the context prop.
ntopVue is populated by http_src/vue/ntop_vue.js, which explicitly imports and re-exports every page component:
// http_src/vue/ntop_vue.js
import { default as PageAlertStats } from "./page-alert-stats.vue";
// … all other page components …
export { PageAlertStats, /* … */ };
Once mounted, the component calls Lua-backed REST endpoints for live data:
// inside page-alert-stats.vue
const response = await ntop_utils.http_request(
`/lua/rest/v2/get/alert/top.lua?ifid=${props.context.ifid}`
);
REST endpoints live in scripts/lua/rest/v2/<verb>/<entity>/<resource>.lua and always return:
{ "rc": 0, "rc_str": "OK", "rsp": { /* payload */ } }
http_src/vue/page-my-feature.vue.http_src/vue/ntop_vue.js:
import { default as PageMyFeature } from "./page-my-feature.vue";
export { /* existing… */ PageMyFeature };
scripts/lua/my_feature.lua:
local context = { ifid = interface.getId() }
local json_context = json.encode(context)
template_utils.render("pages/vue_page.template", {
vue_page_name = "PageMyFeature",
page_context = json_context
})
/lua/my_feature.lua.context inside the Vue component// page-my-feature.vue <script setup>
const props = defineProps({ context: Object });
const ifid = props.context.ifid;
Three bundlers are used (in practice, build:ntopngjs is the common daily command):
| Command | What it does |
|---|---|
npm run watch | Vite/Rollup dev watch — rebuilds on save |
npm run build:ntopngjs | Production build of Vue bundle only (fast) |
npm run build | Full build: webpack (CSS/assets) + Rollup/Vite (JS) |
Output always goes to httpdocs/dist/. Commit httpdocs/dist/ changes — the compiled assets are tracked in git.
| Topic | Detail |
|---|---|
| i18n | Strings live in scripts/locales/en.lua. In Vue use i18n("key") in <script setup> and _i18n("key") in <template>. |
| HTTP requests | Use ntop_utils.http_request(url) — it handles CSRF tokens automatically. |
| URL state | Use ntopng_url_manager from http_src/services/context/ntopng_globals_services.js. |
| Parameter validation | All query parameters that reach Lua must be declared in scripts/lua/modules/http_lint.lua. |
| Navbar | Sections defined in page_utils.menu_sections, entries in page_utils.menu_entries (scripts/lua/modules/page_utils.lua). |
| REST format | Responses always wrap payloads: { rc, rc_str, rsp }. Use rest_utils.answer(rc, payload). |
Browser
│ GET /lua/my_feature.lua
▼
Lua script
│ builds context JSON → calls template_utils.render("vue_page.template", …)
▼
HTML response
│ <div id="PageMyFeature"> + ntopng.js (Vue bundle)
▼
Browser JS (jQuery ready)
│ start_vue("PageMyFeature", ntopVue["PageMyFeature"], contextJSON)
▼
Vue 3 app mounted
│ component calls REST APIs for live data
▼
Lua REST endpoint (/lua/rest/v2/…)
│ returns { rc, rc_str, rsp }
▼
Vue reactive UI updates
The current hybrid model exists because many pages are still pure Lua HTML. The planned migration is:
In the interim, both styles coexist: older pages are pure Lua HTML, newer ones use vue_page.template.