Skip to content

Checking Site Local Templates

jeneric edited this page Sep 13, 2026 · 5 revisions

Introduction

Netdisco lets you override any shipped template by putting a file with the same relative path in nd-site-local/share/views, or in a directory named by template_paths. Your copy wins.

That is what makes site-local customization work, and it is also what makes it fragile. Your copy keeps calling whatever the shipped template called on the day you copied it. When a release removes a JavaScript library, your file keeps calling it, and Netdisco has no way to know.

❗
Most of these checks are for things that fail silently in the browser. Nothing is logged, no error appears, and the page looks like it loaded. Only the data is wrong, or missing entirely. That is why the check exists: nothing else reports them.
ℹ️
2.109000 rebuilt the interface on the browser’s own facilities and removed the JavaScript libraries that stood in for them. The check grew from 5 rules to 29 in that release, and 24 of them are new. If you are upgrading to 2.109000 from anything earlier, expect this report to be long.

Running the Check

netdisco-do checksitelocal

It reads the same directories the web application reads, reports what it finds, and writes nothing. Run it after every upgrade.

Output is one block per finding. Most findings cite a line:

/home/netdisco/nd-site-local/share/views/ajax/report/custom.tt line 29
  return '<a href="...">' + he.encode(data || '') + '</a>';
  since 2.105002: he.js was removed. Use DataTable.util.escapeHtml(), which is loaded on every page already.

A shadowed tab page has no line to cite, because the fault is something the file does not contain:

/home/netdisco/nd-site-local/share/views/device.tt
  since 2.105006: this copy predates the htmx tab transport, so its sidebar form never fetches the pane and the tab renders empty. Copy the hx-get, hx-target, hx-headers and hx-indicator attributes from the shipped template of the same name.

Nothing to report means nothing to do:

Checked 2 paths, nothing to report.

If it says no site-local template paths are configured, then site_local_files is off and template_paths is empty, so you have no site-local templates for it to check.


What It Currently Checks

he.js, removed in 2.105002

Your file throws ReferenceError: he is not defined. A DataTables table using it does not draw at all.

history.js, removed in 2.105004

Silent. window.History still exists, as the browser’s own interface constructor, so a window.History && …​ guard simply stops running its branch. Nothing throws.

natural.js, removed in 2.105004

Silent. A column asking for the natural sort type gets no such type, falls back to string sorting, and the rows are quietly in the wrong order.

do_search(), deprecated in 2.105006

Still works, for now. The shipped function forwards to htmx and writes a notice to the browser console. Your tab loads and nothing looks wrong, which is why this one is easy to leave until the removal breaks it.

A shadowed device.tt, search.tt or report.tt, changed in 2.105006

Silent, and the worst of these. A copy made before the htmx tab transport has no hx-get on its sidebar form, so nothing fetches the pane. The page draws its tabs and sidebar, and the content area below stays empty. Nothing appears in the browser console and nothing appears in the log.

Added in 2.109000

Twenty-four more rules arrived with the interface rebuild. They fall into four groups. The report names the file, the line and the replacement for each one, so what follows is what to expect rather than a list to work from.

Libraries that were removed

jQuery itself, jQuery UI and its autocomplete, jsTree, floatThead, jquery-deserialize, toastr, moment.js and daterangepicker. A copy of the layout that still names one of these loads a file that is no longer there. The script tag 404s, and whatever the page expected that library to do does not happen.

Files that were renamed

jquery.dataTables.min.js became dataTables.min.js, the library no longer needing jQuery, and netdisco_portcontrol.js became netdisco-portcontrol.js. A copy naming the old file 404s. The DataTables rename is the more serious of the two: nothing else loads the library, so until it is fixed no table on any page builds.

Helpers a template used to call

nd_submit(), do_search(), update_page_title(), update_browser_history(), update_csv_download_link(), the has_sidebar global, an INCLUDE of ajax/datatabledefaults.tt or of anything under share/views/js/, and an htmx:abort trigger. These have been replaced by markup the server sends, so the fix is usually to delete the call and add an attribute.

Markup a copied page no longer carries

A layout copy predating data-nd-uri-base and the other body attributes, a page with no nd_csv-download or nd_sidebar-reset-link anchor for the pane response to swap into, and a sidebar form with no hx-sync. These are absences rather than lines, so the report cites the file alone.

The layout copy is the one to fix first. netdisco.js throws when it reads those body attributes and finds nothing, and every page is then dead.


The Startup Warning

Two of these checks are made by the web application itself, because each leaves a page that gives no clue at all: a shadowed tab page, and since 2.109000 a shadowed layout. Netdisco warns once per worker at startup:

warn /home/netdisco/nd-site-local/share/views/device.tt predates 2.105006. Run "netdisco-do checksitelocal" for details.
warn /home/netdisco/nd-site-local/share/views/layouts/main.tt predates 2.109000. Run "netdisco-do checksitelocal" for details.

The rest are not warned about at startup. They leave the application serving pages, and do_search() already writes its own notice to the browser console. A site with nothing to report sees nothing, which is the point: a warning that appears on every boot everywhere is one nobody reads.

The startup check opens at most one file per shipped tab page per configured path, so it costs little at every worker boot. The full scan that checksitelocal runs walks the whole tree and is not done at startup.


How to Fix Each One

he.encode(…​) and he.decode(…​)

Replace with DataTable.util.escapeHtml(…​). It ships with DataTables, which is already on every page, so there is nothing to add.

// before
return he.encode(data || '');
// after
return DataTable.util.escapeHtml(data || '');
❗
These are not quite drop-in replacements. DataTable.util.escapeHtml does not escape the single quote, where he.encode did, so never place an escaped value inside a single-quoted HTML attribute. See the plugin documentation for this and one other difference.

History.pushState, History.getState, History.Adapter.bind

Replace with the browser’s own history object, which is lowercase. Where history.js fired a statechange event on its own writes, the native API fires nothing, so a listener that relied on that must be called directly. The native pushState also ignores its title argument, so set document.title yourself if you were relying on the tab being retitled.

// before
History.pushState({tab: t}, 'My Title', url);
// after
document.title = 'My Title';
history.pushState({tab: t}, '', url);

A column asking for the natural sort type

Use a built-in DataTables type, or one of the sort plug-ins Netdisco still ships: portsort for interface names and versionsort for version strings.

do_search(event, tab) in a submit handler

2.105006 moved the device, search and report panes onto htmx. The form itself now carries the attributes that fetch the pane, so the submit handler no longer performs the search. What it still does is the bookkeeping around it: the page title, the browser history entry, the CSV download link and the sidebar state.

There are two cases, and which one you are in depends on how much you copied.

You copied only share/views/js/common.js. The shipped form already has the htmx attributes, so do_search detects that and returns without doing a second fetch. Replace the call with nd_apply_sidebar, and drop the event argument along with any preventDefault, because htmx needs the submit event to run:

// before
$('#ports_form').submit(function (event) {
  do_search(event, 'ports');
});
// after
$('#ports_form').submit(function () {
  nd_apply_sidebar('ports');
});

You also copied device.tt, search.tt or report.tt. This is the tab-page-shadow finding, and the one Netdisco warns about at startup. Your form has no htmx attributes and nothing fetches the pane, so give it the four that share/views/device.tt uses:

<form id="[% tab.tag | html_entity %]_form" class="nd_sidebar-form form-stacked"
    method="get" action="[% uri_for('/device') | none %]"
    hx-get="[% uri_for('/ajax/content/device/' _ tab.tag) | none %]"
    hx-target="#[% tab.tag | html_entity %]_pane"
    hx-headers='{"X-Requested-With": "XMLHttpRequest"}'
    hx-indicator="#[% tab.tag | html_entity %]_indicator">

share/views/search.tt and share/views/report.tt use the same shape with their own paths. Copy from the shipped file rather than from here, so you pick up whatever else has changed since your copy was made.

ℹ️
hx-headers is not optional. The content routes answer only requests carrying X-Requested-With, so a form without it does not load its pane.

What It Does Not Check

Site-local Perl under nd-site-local/lib. The plugin API has not moved, and third-party App::NetdiscoX:: plugins are outside the compatibility promise.

It also does not fix anything. It tells you the file, the line and the replacement, and leaves the edit to you. Rewriting Template Toolkit files automatically is not something Netdisco will do to your customizations.

Clone this wiki locally