Compare commits

...

11 Commits

Author SHA1 Message Date
Jokob @NetAlertX
dfd2cf9e20 Merge pull request #1321 from jokob-sk/main
sync
2025-11-30 00:23:45 +00:00
jokob-sk
61824abb9f BE: restore previous version retrieval as a test #1320
Signed-off-by: jokob-sk <jokob.sk@gmail.com>
2025-11-30 11:21:24 +11:00
jokob-sk
33c5548fe1 Merge branch 'main' of https://github.com/jokob-sk/NetAlertX 2025-11-30 11:15:25 +11:00
jokob-sk
fd41c395ae DOCS: old link removal
Signed-off-by: jokob-sk <jokob.sk@gmail.com>
2025-11-30 11:15:19 +11:00
jokob-sk
1a980844f0 BE: restore previous verison retrieval as a test #1320
Signed-off-by: jokob-sk <jokob.sk@gmail.com>
2025-11-30 11:14:45 +11:00
jokob-sk
82e018e284 FE: more defensive network topology hierarchy check #1308
Signed-off-by: jokob-sk <jokob.sk@gmail.com>
2025-11-30 10:55:08 +11:00
jokob-sk
e0e1233b1c DOCS: migration docs
Signed-off-by: jokob-sk <jokob.sk@gmail.com>
2025-11-30 10:27:33 +11:00
jokob-sk
74677f940e FE: more defensive network topology hierarchy check #1308
Signed-off-by: jokob-sk <jokob.sk@gmail.com>
2025-11-30 10:27:23 +11:00
Jokob @NetAlertX
21a4d20579 Merge pull request #1317 from mmomjian/main
Fix typo in warning message for read-only mode
2025-11-29 23:17:43 +00:00
jokob-sk
9634e4e0f7 FE: YYYY-DD-MM timestamp handling #1312
Signed-off-by: jokob-sk <jokob.sk@gmail.com>
2025-11-30 09:36:56 +11:00
Matthew Momjian
59b417705e Fix typo in warning message for read-only mode 2025-11-29 11:02:42 -05:00
8 changed files with 283 additions and 241 deletions

View File

@@ -32,6 +32,18 @@ jobs:
- name: Set up Docker Buildx - name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3 uses: docker/setup-buildx-action@v3
# --- Previous approach Get release version from tag
- name: Set up dynamic build ARGs
id: getargs
run: echo "version=$(cat ./stable/VERSION)" >> $GITHUB_OUTPUT
- name: Get release version
id: get_version_prev
run: echo "::set-output name=version::${GITHUB_REF#refs/tags/}"
- name: Create .VERSION file
run: echo "${{ steps.get_version.outputs.version }}" >> .VERSION_PREV
# --- Get release version from tag # --- Get release version from tag
- name: Get release version - name: Get release version
id: get_version id: get_version

1
.gitignore vendored
View File

@@ -11,6 +11,7 @@ nohup.out
config/* config/*
.ash_history .ash_history
.VERSION .VERSION
.VERSION_PREV
config/pialert.conf config/pialert.conf
config/app.conf config/app.conf
db/* db/*

View File

@@ -138,6 +138,7 @@ RUN install -d -o ${NETALERTX_USER} -g ${NETALERTX_GROUP} -m 700 ${READ_WRITE_FO
# Copy version information into the image # Copy version information into the image
COPY --chown=${NETALERTX_USER}:${NETALERTX_GROUP} .[V]ERSION ${NETALERTX_APP}/.VERSION COPY --chown=${NETALERTX_USER}:${NETALERTX_GROUP} .[V]ERSION ${NETALERTX_APP}/.VERSION
COPY --chown=${NETALERTX_USER}:${NETALERTX_GROUP} .[V]ERSION ${NETALERTX_APP}/.VERSION_PREV
# Copy the virtualenv from the builder stage # Copy the virtualenv from the builder stage
COPY --from=builder --chown=20212:20212 ${VIRTUAL_ENV} ${VIRTUAL_ENV} COPY --from=builder --chown=20212:20212 ${VIRTUAL_ENV} ${VIRTUAL_ENV}
@@ -147,12 +148,12 @@ COPY --from=builder --chown=20212:20212 ${VIRTUAL_ENV} ${VIRTUAL_ENV}
# This is done after the copy of the venv to ensure the venv is in place # This is done after the copy of the venv to ensure the venv is in place
# although it may be quicker to do it before the copy, it keeps the image # although it may be quicker to do it before the copy, it keeps the image
# layers smaller to do it after. # layers smaller to do it after.
RUN if [ -f '.VERSION' ]; then \ RUN for vfile in .VERSION .VERSION_PREV; do \
cp '.VERSION' "${NETALERTX_APP}/.VERSION"; \ if [ ! -f "${NETALERTX_APP}/${vfile}" ]; then \
else \ echo "DEVELOPMENT 00000000" > "${NETALERTX_APP}/${vfile}"; \
echo "DEVELOPMENT 00000000" > "${NETALERTX_APP}/.VERSION"; \ fi; \
fi && \ chown 20212:20212 "${NETALERTX_APP}/${vfile}"; \
chown 20212:20212 "${NETALERTX_APP}/.VERSION" && \ done && \
apk add --no-cache libcap && \ apk add --no-cache libcap && \
setcap cap_net_raw+ep /bin/busybox && \ setcap cap_net_raw+ep /bin/busybox && \
setcap cap_net_raw,cap_net_admin+eip /usr/bin/nmap && \ setcap cap_net_raw,cap_net_admin+eip /usr/bin/nmap && \

View File

@@ -253,11 +253,11 @@ docker run -it --rm --name netalertx --user "0" \
ghcr.io/jokob-sk/netalertx:latest ghcr.io/jokob-sk/netalertx:latest
``` ```
..or alternatively execute: ...or alternatively execute:
```bash ```bash
sudo chown -R 20211:20211 /local_data_dir sudo chown -R 20211:20211 /local_data_dir
sudo chmod -R a+rwx /local_data_dir/ sudo chmod -R a+rwx /local_data_dir
``` ```
7. Stop the container 7. Stop the container

View File

@@ -63,7 +63,6 @@ There is also an in-app Help / FAQ section that should be answering frequently a
#### ♻ Misc #### ♻ Misc
- [Version history (legacy)](./VERSIONS_HISTORY.md)
- [Reverse proxy (Nginx, Apache, SWAG)](./REVERSE_PROXY.md) - [Reverse proxy (Nginx, Apache, SWAG)](./REVERSE_PROXY.md)
- [Installing Updates](./UPDATES.md) - [Installing Updates](./UPDATES.md)
- [Setting up Authelia](./AUTHELIA.md) (DRAFT) - [Setting up Authelia](./AUTHELIA.md) (DRAFT)

View File

@@ -378,7 +378,7 @@ function localizeTimestamp(input) {
let tz = getSetting("TIMEZONE") || 'Europe/Berlin'; let tz = getSetting("TIMEZONE") || 'Europe/Berlin';
input = String(input || '').trim(); input = String(input || '').trim();
// 1. Unix timestamps (10 or 13 digits) // 1. Unix timestamps (10 or 13 digits)
if (/^\d+$/.test(input)) { if (/^\d+$/.test(input)) {
const ms = input.length === 10 ? parseInt(input, 10) * 1000 : parseInt(input, 10); const ms = input.length === 10 ? parseInt(input, 10) * 1000 : parseInt(input, 10);
return new Intl.DateTimeFormat('default', { return new Intl.DateTimeFormat('default', {
@@ -389,39 +389,53 @@ function localizeTimestamp(input) {
}).format(new Date(ms)); }).format(new Date(ms));
} }
// 2. European DD/MM/YYYY // 2. European DD/MM/YYYY
let match = input.match(/^(\d{1,2})\/(\d{1,2})\/(\d{4})(?:[ ,]+(\d{1,2}:\d{2}(?::\d{2})?))?(.*)$/); let match = input.match(/^(\d{1,2})\/(\d{1,2})\/(\d{4})(?:[ ,]+(\d{1,2}:\d{2}(?::\d{2})?))?(.*)$/);
if (match) { if (match) {
let [ , d, m, y, t = "00:00:00", tzPart = "" ] = match; let [, d, m, y, t = "00:00:00", tzPart = ""] = match;
const iso = `${y}-${m.padStart(2,'0')}-${d.padStart(2,'0')}T${t.length===5?t+":00":t}${tzPart}`; const iso = `${y}-${m.padStart(2,'0')}-${d.padStart(2,'0')}T${t.length===5?t+":00":t}${tzPart}`;
return formatSafe(iso, tz); return formatSafe(iso, tz);
} }
// 3. US MM/DD/YYYY // 3. US MM/DD/YYYY
match = input.match(/^(\d{1,2})\/(\d{1,2})\/(\d{4})(?:[ ,]+(\d{1,2}:\d{2}(?::\d{2})?))?(.*)$/); match = input.match(/^(\d{1,2})\/(\d{1,2})\/(\d{4})(?:[ ,]+(\d{1,2}:\d{2}(?::\d{2})?))?(.*)$/);
if (match) { if (match) {
let [ , m, d, y, t = "00:00:00", tzPart = "" ] = match; let [, m, d, y, t = "00:00:00", tzPart = ""] = match;
const iso = `${y}-${m.padStart(2,'0')}-${d.padStart(2,'0')}T${t.length===5?t+":00":t}${tzPart}`; const iso = `${y}-${m.padStart(2,'0')}-${d.padStart(2,'0')}T${t.length===5?t+":00":t}${tzPart}`;
return formatSafe(iso, tz); return formatSafe(iso, tz);
} }
// 4. ISO-style (with T, Z, offsets) // 4. ISO YYYY-MM-DD with optional Z/+offset
match = input.match(/^(\d{4}-\d{1,2}-\d{1,2})[ T](\d{1,2}:\d{2}(?::\d{2})?)(Z|[+-]\d{2}:?\d{2})?$/); match = input.match(/^(\d{4})-(0[1-9]|1[0-2])-(0[1-9]|[12]\d|3[01])[ T](\d{1,2}:\d{2}(?::\d{2})?)(Z|[+-]\d{2}:?\d{2})?$/);
if (match) { if (match) {
let [ , ymd, time, offset = "" ] = match; let [, y, m, d, time, offset = ""] = match;
// normalize to YYYY-MM-DD
let [y, m, d] = ymd.split('-').map(x => x.padStart(2,'0'));
const iso = `${y}-${m}-${d}T${time.length===5?time+":00":time}${offset}`; const iso = `${y}-${m}-${d}T${time.length===5?time+":00":time}${offset}`;
return formatSafe(iso, tz); return formatSafe(iso, tz);
} }
// 5. RFC2822 / "25 Aug 2025 13:45:22 +0200" // 5. RFC2822 / "25 Aug 2025 13:45:22 +0200"
match = input.match(/^\d{1,2} [A-Za-z]{3,} \d{4}/); match = input.match(/^\d{1,2} [A-Za-z]{3,} \d{4}/);
if (match) { if (match) {
return formatSafe(input, tz); return formatSafe(input, tz);
} }
// 6. Fallback (whatever Date() can parse) // 6. DD-MM-YYYY with optional time
match = input.match(/^(\d{1,2})-(\d{1,2})-(\d{4})(?:[ T](\d{1,2}:\d{2}(?::\d{2})?))?$/);
if (match) {
let [, d, m, y, time = "00:00:00"] = match;
const iso = `${y}-${m.padStart(2,'0')}-${d.padStart(2,'0')}T${time.length===5?time+":00":time}`;
return formatSafe(iso, tz);
}
// 7. Strict YYYY-DD-MM with optional time
match = input.match(/^(\d{4})-(0[1-9]|[12]\d|3[01])-(0[1-9]|1[0-2])(?:[ T](\d{1,2}:\d{2}(?::\d{2})?))?$/);
if (match) {
let [, y, d, m, time = "00:00:00"] = match;
const iso = `${y}-${m}-${d}T${time.length === 5 ? time + ":00" : time}`;
return formatSafe(iso, tz);
}
// 8. Fallback
return formatSafe(input, tz); return formatSafe(input, tz);
function formatSafe(str, tz) { function formatSafe(str, tz) {
@@ -440,6 +454,7 @@ function localizeTimestamp(input) {
} }
// ---------------------------------------------------- // ----------------------------------------------------
/** /**
* Replaces double quotes within single-quoted strings, then converts all single quotes to double quotes, * Replaces double quotes within single-quoted strings, then converts all single quotes to double quotes,
@@ -1629,7 +1644,7 @@ async function executeOnce() {
await cacheSettings(); await cacheSettings();
await cacheStrings(); await cacheStrings();
console.log("All AJAX callbacks have completed"); console.log("All AJAX callbacks have completed");
onAllCallsComplete(); onAllCallsComplete();
} catch (error) { } catch (error) {
console.error("Error:", error); console.error("Error:", error);

View File

@@ -521,13 +521,17 @@ function getChildren(node, list, path, visited = [])
// Loop through all items to find children of the current node // Loop through all items to find children of the current node
for (var i in list) { for (var i in list) {
if (list[i].devParentMAC.toLowerCase() == node.devMac.toLowerCase() && !hiddenMacs.includes(list[i].devParentMAC)) { const item = list[i];
const parentMac = item.devParentMAC || ""; // null-safe
const nodeMac = node.devMac || ""; // null-safe
visibleNodesCount++; if (parentMac != "" && parentMac.toLowerCase() == nodeMac.toLowerCase() && !hiddenMacs.includes(parentMac)) {
// Process children recursively, passing a copy of the visited list visibleNodesCount++;
children.push(getChildren(list[i], list, path + ((path == "") ? "" : '|') + list[i].devParentMAC, visited));
} // Process children recursively, passing a copy of the visited list
children.push(getChildren(list[i], list, path + ((path == "") ? "" : '|') + parentMac, visited));
}
} }
// Track leaf and parent node counts // Track leaf and parent node counts
@@ -565,14 +569,27 @@ function getChildren(node, list, path, visited = [])
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
function getHierarchy() function getHierarchy()
{ {
let internetNode = null;
for(i in deviceListGlobal) for(i in deviceListGlobal)
{ {
if(deviceListGlobal[i].devMac == 'Internet') if(deviceListGlobal[i].devMac == 'Internet')
{ {
return (getChildren(deviceListGlobal[i], deviceListGlobal, '')) internetNode = deviceListGlobal[i];
return (getChildren(internetNode, deviceListGlobal, ''))
break; break;
} }
} }
if (!internetNode) {
showModalOk(
getString('Network_Configuration_Error'),
getString('Network_Root_Not_Configured')
);
console.error("getHierarchy(): Internet node not found");
return null;
}
} }
//--------------------------------------------------------------------------- //---------------------------------------------------------------------------
@@ -671,8 +688,6 @@ function handleNodeClick(el)
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
var myTree; var myTree;
var emSize; var emSize;
var nodeHeight; var nodeHeight;
// var sizeCoefficient = 1.4 // var sizeCoefficient = 1.4
@@ -689,140 +704,139 @@ function emToPx(em, element) {
function initTree(myHierarchy) function initTree(myHierarchy)
{ {
// calculate the drawing area based on teh tree width and available screen size if(myHierarchy && myHierarchy.type !== "")
let baseFontSize = parseFloat($('html').css('font-size'));
let treeAreaHeight = ($(window).height() - 155); ;
// calculate the font size of the leaf nodes to fit everything into the tree area
leafNodesCount == 0 ? 1 : leafNodesCount;
emSize = pxToEm((treeAreaHeight/(leafNodesCount)).toFixed(2));
let screenWidthEm = pxToEm($('.networkTable').width()-15);
// init the drawing area size
$("#networkTree").attr('style', `height:${treeAreaHeight}px; width:${emToPx(screenWidthEm)}px`)
if(myHierarchy.type == "")
{ {
showModalOk(getString('Network_Configuration_Error'), getString('Network_Root_Not_Configured')) // calculate the drawing area based on the tree width and available screen size
let baseFontSize = parseFloat($('html').css('font-size'));
let treeAreaHeight = ($(window).height() - 155); ;
return; // calculate the font size of the leaf nodes to fit everything into the tree area
} leafNodesCount == 0 ? 1 : leafNodesCount;
// handle canvas and node size if only a few nodes emSize = pxToEm((treeAreaHeight/(leafNodesCount)).toFixed(2));
emSize > 1 ? emSize = 1 : emSize = emSize;
let nodeHeightPx = emToPx(emSize*1); let screenWidthEm = pxToEm($('.networkTable').width()-15);
let nodeWidthPx = emToPx(screenWidthEm / (parentNodesCount));
// handle if only a few nodes // init the drawing area size
nodeWidthPx > 160 ? nodeWidthPx = 160 : nodeWidthPx = nodeWidthPx; $("#networkTree").attr('style', `height:${treeAreaHeight}px; width:${emToPx(screenWidthEm)}px`)
console.log(Treeviz); // handle canvas and node size if only a few nodes
emSize > 1 ? emSize = 1 : emSize = emSize;
myTree = Treeviz.create({ let nodeHeightPx = emToPx(emSize*1);
htmlId: "networkTree", let nodeWidthPx = emToPx(screenWidthEm / (parentNodesCount));
renderNode: nodeData => {
(!emptyArr.includes(nodeData.data.port )) ? port = nodeData.data.port : port = ""; // handle if only a few nodes
nodeWidthPx > 160 ? nodeWidthPx = 160 : nodeWidthPx = nodeWidthPx;
(port == "" || port == 0 || port == 'None' ) ? portBckgIcon = `<i class="fa fa-wifi"></i>` : portBckgIcon = `<i class="fa fa-ethernet"></i>`; console.log(Treeviz);
portHtml = (port == "" || port == 0 || port == 'None' ) ? " &nbsp " : port; myTree = Treeviz.create({
htmlId: "networkTree",
renderNode: nodeData => {
// Build HTML for individual nodes in the network diagram (!emptyArr.includes(nodeData.data.port )) ? port = nodeData.data.port : port = "";
deviceIcon = (!emptyArr.includes(nodeData.data.icon )) ?
`<div class="netIcon">
${atob(nodeData.data.icon)}
</div>` : "";
devicePort = `<div class="netPort"
style="width:${emSize}em;height:${emSize}em">
${portHtml}</div>
<div class="portBckgIcon"
style="margin-left:-${emSize*0.7}em;">
${portBckgIcon}
</div>`;
collapseExpandIcon = nodeData.data.hiddenChildren ?
"square-plus" : "square-minus";
// generate +/- icon if node has children nodes (port == "" || port == 0 || port == 'None' ) ? portBckgIcon = `<i class="fa fa-wifi"></i>` : portBckgIcon = `<i class="fa fa-ethernet"></i>`;
collapseExpandHtml = nodeData.data.hasChildren ?
`<div class="netCollapse"
style="font-size:${nodeHeightPx/2}px;top:${Math.floor(nodeHeightPx / 4)}px"
data-mytreepath="${nodeData.data.path}"
data-mytreemac="${nodeData.data.mac}">
<i class="fa fa-${collapseExpandIcon} pointer"></i>
</div>` : "";
selectedNodeMac = $(".nav-tabs-custom .active a").attr('data-mytabmac') portHtml = (port == "" || port == 0 || port == 'None' ) ? " &nbsp " : port;
highlightedCss = nodeData.data.mac == selectedNodeMac ? // Build HTML for individual nodes in the network diagram
" highlightedNode " : ""; deviceIcon = (!emptyArr.includes(nodeData.data.icon )) ?
cssNodeType = nodeData.data.devIsNetworkNodeDynamic ? `<div class="netIcon">
" node-network-device " : " node-standard-device "; ${atob(nodeData.data.icon)}
</div>` : "";
devicePort = `<div class="netPort"
style="width:${emSize}em;height:${emSize}em">
${portHtml}</div>
<div class="portBckgIcon"
style="margin-left:-${emSize*0.7}em;">
${portBckgIcon}
</div>`;
collapseExpandIcon = nodeData.data.hiddenChildren ?
"square-plus" : "square-minus";
networkHardwareIcon = nodeData.data.devIsNetworkNodeDynamic ? `<span class="network-hw-icon"> // generate +/- icon if node has children nodes
<i class="fa-solid fa-hard-drive"></i> collapseExpandHtml = nodeData.data.hasChildren ?
</span>` : ""; `<div class="netCollapse"
style="font-size:${nodeHeightPx/2}px;top:${Math.floor(nodeHeightPx / 4)}px"
data-mytreepath="${nodeData.data.path}"
data-mytreemac="${nodeData.data.mac}">
<i class="fa fa-${collapseExpandIcon} pointer"></i>
</div>` : "";
const badgeConf = getStatusBadgeParts(nodeData.data.presentLastScan, nodeData.data.alertDown, nodeData.data.mac, statusText = '') selectedNodeMac = $(".nav-tabs-custom .active a").attr('data-mytabmac')
return result = `<div highlightedCss = nodeData.data.mac == selectedNodeMac ?
class="node-inner hover-node-info box pointer ${highlightedCss} ${cssNodeType}" " highlightedNode " : "";
style="height:${nodeHeightPx}px;font-size:${nodeHeightPx-5}px;" cssNodeType = nodeData.data.devIsNetworkNodeDynamic ?
onclick="handleNodeClick(this)" " node-network-device " : " node-standard-device ";
data-mac="${nodeData.data.mac}"
data-parentMac="${nodeData.data.parentMac}" networkHardwareIcon = nodeData.data.devIsNetworkNodeDynamic ? `<span class="network-hw-icon">
data-name="${nodeData.data.name}" <i class="fa-solid fa-hard-drive"></i>
data-ip="${nodeData.data.ip}" </span>` : "";
data-mac="${nodeData.data.mac}"
data-vendor="${nodeData.data.vendor}" const badgeConf = getStatusBadgeParts(nodeData.data.presentLastScan, nodeData.data.alertDown, nodeData.data.mac, statusText = '')
data-type="${nodeData.data.type}"
data-devIsNetworkNodeDynamic="${nodeData.data.devIsNetworkNodeDynamic}" return result = `<div
data-lastseen="${nodeData.data.lastseen}" class="node-inner hover-node-info box pointer ${highlightedCss} ${cssNodeType}"
data-firstseen="${nodeData.data.firstseen}" style="height:${nodeHeightPx}px;font-size:${nodeHeightPx-5}px;"
data-relationship="${nodeData.data.relType}" onclick="handleNodeClick(this)"
data-status="${nodeData.data.status}" data-mac="${nodeData.data.mac}"
data-present="${nodeData.data.presentLastScan}" data-parentMac="${nodeData.data.parentMac}"
data-alert="${nodeData.data.alertDown}" data-name="${nodeData.data.name}"
data-icon="${nodeData.data.icon}" data-ip="${nodeData.data.ip}"
> data-mac="${nodeData.data.mac}"
<div class="netNodeText"> data-vendor="${nodeData.data.vendor}"
<strong><span>${devicePort} <span class="${badgeConf.cssText}">${deviceIcon}</span></span> data-type="${nodeData.data.type}"
<span class="spanNetworkTree anonymizeDev" style="width:${nodeWidthPx-50}px">${nodeData.data.name}</span> data-devIsNetworkNodeDynamic="${nodeData.data.devIsNetworkNodeDynamic}"
${networkHardwareIcon} data-lastseen="${nodeData.data.lastseen}"
</strong> data-firstseen="${nodeData.data.firstseen}"
data-relationship="${nodeData.data.relType}"
data-status="${nodeData.data.status}"
data-present="${nodeData.data.presentLastScan}"
data-alert="${nodeData.data.alertDown}"
data-icon="${nodeData.data.icon}"
>
<div class="netNodeText">
<strong><span>${devicePort} <span class="${badgeConf.cssText}">${deviceIcon}</span></span>
<span class="spanNetworkTree anonymizeDev" style="width:${nodeWidthPx-50}px">${nodeData.data.name}</span>
${networkHardwareIcon}
</strong>
</div>
</div> </div>
</div> ${collapseExpandHtml}`;
${collapseExpandHtml}`; },
}, mainAxisNodeSpacing: 'auto',
mainAxisNodeSpacing: 'auto', // secondaryAxisNodeSpacing: 0.3,
// secondaryAxisNodeSpacing: 0.3, nodeHeight: nodeHeightPx,
nodeHeight: nodeHeightPx, nodeWidth: nodeWidthPx,
nodeWidth: nodeWidthPx, marginTop: '5',
marginTop: '5', isHorizontal : true,
isHorizontal : true, hasZoom: true,
hasZoom: true, hasPan: true,
hasPan: true, marginLeft: '10',
marginLeft: '10', marginRight: '10',
marginRight: '10', idKey: "mac",
idKey: "mac", hasFlatData: false,
hasFlatData: false, relationnalField: "children",
relationnalField: "children", linkWidth: (nodeData) => 2,
linkWidth: (nodeData) => 2, linkColor: (nodeData) => {
linkColor: (nodeData) => { relConf = getRelationshipConf(nodeData.data.relType)
relConf = getRelationshipConf(nodeData.data.relType) return relConf.color;
return relConf.color; }
} // onNodeClick: (nodeData) => handleNodeClick(nodeData),
// onNodeClick: (nodeData) => handleNodeClick(nodeData), });
});
console.log(deviceListGlobal); console.log(deviceListGlobal);
myTree.refresh(myHierarchy); myTree.refresh(myHierarchy);
// hide spinning icon // hide spinning icon
hideSpinner() hideSpinner()
} else
{
console.error("getHierarchy() not returning expected result");
}
} }

View File

@@ -14,7 +14,7 @@ if ! awk '$2 == "/" && $4 ~ /ro/ {found=1} END {exit !found}' /proc/mounts; then
══════════════════════════════════════════════════════════════════════════════ ══════════════════════════════════════════════════════════════════════════════
⚠️ Warning: Container is running as read-write, not in read-only mode. ⚠️ Warning: Container is running as read-write, not in read-only mode.
Please mount the root filesystem as --read-only or use read-only: true Please mount the root filesystem as --read-only or use read_only: true
https://github.com/jokob-sk/NetAlertX/blob/main/docs/docker-troubleshooting/read-only-filesystem.md https://github.com/jokob-sk/NetAlertX/blob/main/docs/docker-troubleshooting/read-only-filesystem.md
══════════════════════════════════════════════════════════════════════════════ ══════════════════════════════════════════════════════════════════════════════
EOF EOF