mirror of
https://github.com/jokob-sk/NetAlertX.git
synced 2026-10-06 21:13:00 -04:00
7 files changed
+212
-26
No files matched your search
@@ -4,15 +4,15 @@ The Main Info section is where most of the device identifiable information is st
|
||||
|
||||
> [!NOTE]
|
||||
>
|
||||
> You can multi-edit devices by selecting them in the main Devices view, from the Mainetence section, or via the CSV Export functionality under Maintenance. More info can be found in the [Devices Bulk-editing docs](./DEVICES_BULK_EDITING.md).
|
||||
> You can multi-edit devices by selecting them in the main Devices view, from the Maintenance section, or via the CSV Export functionality under Maintenance. More info can be found in the [Devices Bulk-editing docs](./DEVICES_BULK_EDITING.md).
|
||||
|
||||
|
||||

|
||||
|
||||
## Main Info
|
||||
|
||||
- **MAC**: MAC addres of the device. Not editable, unless creating a new dummy device.
|
||||
- **Last IP**: IP addres of the device. Not editable, unless creating a new dummy device.
|
||||
- **MAC**: MAC address of the device. Not editable, unless creating a new dummy device.
|
||||
- **Last IP**: IP address of the device. Not editable, unless creating a new dummy device.
|
||||
- **Name**: Friendly device name. Autodetected via various 🆎 Name discovery [plugins](https://docs.netalertx.com/PLUGINS_OVERVIEW). The app attaches `(IP match)` if the name is discovered via an IP match and not MAC match which could mean the name could be incorrect as IPs might change.
|
||||
- **Icon**: Partially autodetected. Select an existing or [add a custom icon](./ICONS.md). You can also auto-apply the same icon on all devices of the same type.
|
||||
- **Owner**: Device owner (The list is self-populated with existing owners and you can add custom values).
|
||||
|
||||
+12
-6
@@ -1944,6 +1944,8 @@ textarea[readonly],
|
||||
|
||||
#networkTree .box
|
||||
{
|
||||
/* Positioned HTML inside SVG foreignObject is misplaced by WebKit. */
|
||||
position:static;
|
||||
border-width:1px;
|
||||
border-top-color:grey;
|
||||
padding:0px;
|
||||
@@ -1966,7 +1968,7 @@ textarea[readonly],
|
||||
}
|
||||
#networkTree .netNodeText
|
||||
{
|
||||
position: absolute;
|
||||
position: static;
|
||||
}
|
||||
#networkTree .netPort
|
||||
{
|
||||
@@ -1977,7 +1979,8 @@ textarea[readonly],
|
||||
|
||||
#networkTree .portBckgIcon
|
||||
{
|
||||
opacity: 0.3;
|
||||
color: var(--color-gray);
|
||||
color: color-mix(in srgb, currentColor 30%, transparent);
|
||||
display: initial;
|
||||
float: left;
|
||||
width: 1em;
|
||||
@@ -2054,8 +2057,8 @@ textarea[readonly],
|
||||
|
||||
#networkTree .netCollapse
|
||||
{
|
||||
position: absolute;
|
||||
right: 0;
|
||||
position: static;
|
||||
float: right;
|
||||
margin-right: -3px;
|
||||
}
|
||||
|
||||
@@ -2078,9 +2081,12 @@ textarea[readonly],
|
||||
|
||||
#networkTree .network-hw-icon
|
||||
{
|
||||
position: absolute;
|
||||
position: static;
|
||||
display: inline-block;
|
||||
margin-left: -0.4em;
|
||||
opacity: 0.3;
|
||||
/* Alpha in the color avoids WebKit's foreignObject opacity layer bug. */
|
||||
color: var(--color-gray);
|
||||
color: color-mix(in srgb, currentColor 30%, transparent);
|
||||
margin-top: 0.1em;
|
||||
}
|
||||
|
||||
|
||||
@@ -281,7 +281,7 @@ function initTree(myHierarchy)
|
||||
// generate +/- icon if node has children nodes
|
||||
collapseExpandHtml = nodeData.data.hasChildren ?
|
||||
`<div class="netCollapse"
|
||||
style="font-size:${nodeHeightPx/2}px;top:${Math.floor(nodeHeightPx / 4)}px"
|
||||
style="font-size:${nodeHeightPx/2}px;margin-top:-${nodeHeightPx - Math.floor(nodeHeightPx / 4)}px"
|
||||
data-mytreepath="${nodeData.data.path}"
|
||||
data-mytreemac="${nodeData.data.devMac}">
|
||||
<i class="fa fa-${collapseExpandIcon} pointer"></i>
|
||||
|
||||
@@ -233,8 +233,8 @@
|
||||
"Device_Shortcut_OnlineChart": "Присутствие устройств",
|
||||
"Device_Shortcut_Unstable": "Нестабильный",
|
||||
"Device_TableHead_AlertDown": "Оповещение о сост. ВЫКЛ",
|
||||
"Device_TableHead_AlertEvents": "",
|
||||
"Device_TableHead_CanSleep": "",
|
||||
"Device_TableHead_AlertEvents": "Оповещения о событиях",
|
||||
"Device_TableHead_CanSleep": "Может спать",
|
||||
"Device_TableHead_Comments": "Комментарии",
|
||||
"Device_TableHead_Connected_Devices": "Соединения",
|
||||
"Device_TableHead_CustomProps": "Свойства / Действия",
|
||||
@@ -265,7 +265,7 @@
|
||||
"Device_TableHead_Rowid": "ID строки",
|
||||
"Device_TableHead_SSID": "SSID",
|
||||
"Device_TableHead_SourcePlugin": "Исходный плагин",
|
||||
"Device_TableHead_StaticIP": "",
|
||||
"Device_TableHead_StaticIP": "Статический IP",
|
||||
"Device_TableHead_Status": "Статус",
|
||||
"Device_TableHead_SyncHubNodeName": "Узел синхронизации",
|
||||
"Device_TableHead_Type": "Тип",
|
||||
@@ -341,7 +341,7 @@
|
||||
"Gen_Copy": "Запустить",
|
||||
"Gen_CopyToClipboard": "Копировать в буфер обмена",
|
||||
"Gen_DataUpdatedUITakesTime": "ОК - Обновление UI может занять некоторое время, если сканирование выполняется.",
|
||||
"Gen_Day": "",
|
||||
"Gen_Day": "День",
|
||||
"Gen_Delete": "Удалить",
|
||||
"Gen_DeleteAll": "Удалить все",
|
||||
"Gen_Description": "Описание",
|
||||
@@ -350,11 +350,11 @@
|
||||
"Gen_Filter": "Фильтр",
|
||||
"Gen_Flapping": "Нестабильный",
|
||||
"Gen_Generate": "Генерировать",
|
||||
"Gen_Hour": "",
|
||||
"Gen_Hour": "Час",
|
||||
"Gen_InvalidMac": "Неверный Mac-адрес.",
|
||||
"Gen_Invalid_Value": "Введено некорректное значение",
|
||||
"Gen_LockedDB": "ОШИБКА - Возможно, база данных заблокирована. Проверьте инструменты разработчика F12 -> Консоль или повторите попытку позже.",
|
||||
"Gen_Month": "",
|
||||
"Gen_Month": "Месяц",
|
||||
"Gen_NetworkMask": "Маска сети",
|
||||
"Gen_New": "Новый",
|
||||
"Gen_Next": "Следующий",
|
||||
@@ -386,7 +386,7 @@
|
||||
"Gen_Update_Value": "Обновить значение",
|
||||
"Gen_ValidIcon": "<i class=\"fa-solid fa-chevron-right \"></i>",
|
||||
"Gen_Warning": "Предупреждение",
|
||||
"Gen_Week": "",
|
||||
"Gen_Week": "Неделя",
|
||||
"Gen_Work_In_Progress": "Работа продолжается, самое время оставить отзыв на https://github.com/netalertx/NetAlertX/issues",
|
||||
"Gen_create_new_device": "Новое устройство",
|
||||
"Gen_create_new_device_info": "Устройства обычно обнаруживаются с помощью <a target=\"_blank\" href=\"https://docs.netalertx.com/PLUGINS\">плагинов</a>. Однако в некоторых случаях вам может потребоваться добавить устройства вручную. Для изучения конкретных сценариев ознакомьтесь с документацией <a target=\"_blank\" href=\"https://docs.netalertx.com/REMOTE_NETWORKS\">Remote Networks</a>.",
|
||||
@@ -716,14 +716,14 @@
|
||||
"Systeminfo_Network_Server_Query": "Запрос сервера:",
|
||||
"Systeminfo_Network_Server_Query_String": "Нет строки запроса",
|
||||
"Systeminfo_Network_Server_Version": "Версия сервера:",
|
||||
"Systeminfo_Performance": "",
|
||||
"Systeminfo_Performance_CPU": "",
|
||||
"Systeminfo_Performance_Disabled": "",
|
||||
"Systeminfo_Performance_Duration": "",
|
||||
"Systeminfo_Performance_IO_Caption": "",
|
||||
"Systeminfo_Performance_IO_Read": "",
|
||||
"Systeminfo_Performance_IO_Write": "",
|
||||
"Systeminfo_Performance_Memory": "",
|
||||
"Systeminfo_Performance": "Производительность",
|
||||
"Systeminfo_Performance_CPU": "CPU %",
|
||||
"Systeminfo_Performance_Disabled": "Сбор истории ресурсов в настоящее время отключен. <a onclick=\"toggleAllSettings()\" href=\"#MAINT_PERF_DAYS\">Включите его в настройках</a>.",
|
||||
"Systeminfo_Performance_Duration": "Продолжительность сканирования (сек.)",
|
||||
"Systeminfo_Performance_IO_Caption": "Только ввод/вывод прямого процесса NetAlertX - подпроцесс ввода/вывода плагина (например, сканер, записывающий свой собственный файл результатов) не включен.",
|
||||
"Systeminfo_Performance_IO_Read": "Чтение ввода-вывода (Мб)",
|
||||
"Systeminfo_Performance_IO_Write": "Запись ввода-вывода (Мб)",
|
||||
"Systeminfo_Performance_Memory": "Использование памяти (Мб)",
|
||||
"Systeminfo_Services": "Службы",
|
||||
"Systeminfo_Services_Description": "Описание службы",
|
||||
"Systeminfo_Services_Name": "Имя службы",
|
||||
@@ -854,4 +854,4 @@
|
||||
"settings_system_label": "Система",
|
||||
"settings_update_item_warning": "Обновить значение ниже. Будьте осторожны, следуя предыдущему формату. <b>Проверка не выполняется.</b>",
|
||||
"test_event_tooltip": "Сначала сохраните изменения, прежде чем проверять настройки."
|
||||
}
|
||||
}
|
||||
@@ -413,4 +413,24 @@ Tests that the settings form submits correctly to `php/server/util.php` with `fu
|
||||
Verifies that all settings are preserved when saved (no data loss during save operation).
|
||||
|
||||
**Key Coverage**: Form submission flow → PHP `saveSettings()` → Config file generation with Python-compatible formatting
|
||||
# Network topology rendering in WebKit, Chromium and Firefox
|
||||
|
||||
The standalone regression uses the real Treeviz bundle, network renderer and styles
|
||||
with four fixture devices. No running NetAlertX backend or database is required.
|
||||
It checks painted pixels, node clicks, collapse/expand, wheel zoom and drag pan.
|
||||
Pixel checks are necessary because WebKit may return correct DOM bounds while
|
||||
painting positioned or translucent HTML at the SVG origin instead.
|
||||
|
||||
Run from the repository root (Node.js required):
|
||||
|
||||
```sh
|
||||
npm install --prefix /tmp/netalertx-browser-tests playwright pngjs
|
||||
/tmp/netalertx-browser-tests/node_modules/.bin/playwright install --with-deps webkit chromium firefox
|
||||
NODE_PATH=/tmp/netalertx-browser-tests/node_modules node test/ui/test_network_topology.cjs
|
||||
```
|
||||
|
||||
Screenshots are written to `/tmp/netalertx-topology`, or to the directory specified
|
||||
by `NETALERTX_TOPOLOGY_ARTIFACTS`. This regression runs separately from pytest.
|
||||
Also verify the fixture in native Safari when a Mac is available; Playwright
|
||||
WebKit is useful regression coverage but is not the Safari application.
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<meta charset="utf-8">
|
||||
<title>NetAlertX network topology regression fixture</title>
|
||||
<link rel="stylesheet" href="/front/lib/bootstrap/bootstrap.min.css">
|
||||
<link rel="stylesheet" href="/front/lib/AdminLTE/dist/css/AdminLTE.css">
|
||||
<link rel="stylesheet" href="/front/lib/font-awesome/all.min.css">
|
||||
<link rel="stylesheet" href="/front/css/app.css">
|
||||
<body>
|
||||
<style>body { background: #fff; }</style>
|
||||
<h1>Network topology — discussion #1379</h1>
|
||||
<div class="networkTable"><div id="networkTree"></div></div>
|
||||
<script src="/front/lib/jquery/jquery.min.js"></script>
|
||||
<script src="/front/lib/treeviz/treeviz.iife.js"></script>
|
||||
<script src="/front/js/network-tree.js"></script>
|
||||
<script>
|
||||
var emptyArr = [null, undefined, ""];
|
||||
var clicks = [];
|
||||
/** Provide a neutral device badge for fixed fixture data. */
|
||||
function badgeFromDevice() { return {cssText: ""}; }
|
||||
/** Return the fixed, trusted fixture labels. */
|
||||
function encodeSpecialChars(value) { return String(value); }
|
||||
/** Keep fixture timestamps independent of locale. */
|
||||
function localizeTimestamp(value) { return value; }
|
||||
/** Provide a fixed link color for visual assertions. */
|
||||
function getRelationshipConf() { return {color: "#336c87"}; }
|
||||
/** Attach the production collapse handlers after initialization. */
|
||||
function hideSpinner() { attachTreeEvents(); }
|
||||
/** Record node clicks for pointer interaction assertions. */
|
||||
function handleNodeClick(node) { clicks.push(node.dataset.mac); }
|
||||
/** Remove the collapsed MAC using the application helper contract. */
|
||||
function removeItemFromArray(items, item) { items.splice(items.indexOf(item), 1); }
|
||||
deviceListGlobal = [
|
||||
{devMac: "internet", devName: "Internet", devParentMAC: "", devIsNetworkNodeDynamic: 1},
|
||||
{devMac: "aa:bb:cc:dd:ee:01", devName: "Router", devParentMAC: "internet", devIsNetworkNodeDynamic: 1},
|
||||
{devMac: "aa:bb:cc:dd:ee:02", devName: "Laptop", devParentMAC: "aa:bb:cc:dd:ee:01", devParentPort: "1"},
|
||||
{devMac: "aa:bb:cc:dd:ee:03", devName: "Phone", devParentMAC: "aa:bb:cc:dd:ee:01", devSSID: "Home Wi-Fi"}
|
||||
].map(device => ({devIcon: btoa('<i class="fa fa-desktop"></i>'), devSSID: "", devParentPort: "", devLastIP: "192.0.2.1", devVendor: "Test", devType: "Device", devParentRelType: "wired", devVlan: "", ...device}));
|
||||
initTree(getHierarchy());
|
||||
</script>
|
||||
</body></html>
|
||||
@@ -0,0 +1,119 @@
|
||||
// Standalone browser regression for SVG foreignObject rendering; see TESTING_GUIDE.md.
|
||||
const assert = require('node:assert/strict');
|
||||
const fs = require('node:fs');
|
||||
const http = require('node:http');
|
||||
const os = require('node:os');
|
||||
const path = require('node:path');
|
||||
const {chromium, firefox, webkit} = require('playwright');
|
||||
const {PNG} = require('pngjs');
|
||||
|
||||
/** Verify actual painted pixels, since WebKit can report correct bounds for misplaced HTML. */
|
||||
async function assertPainted(page, selector) {
|
||||
const items = await page.locator(selector).all();
|
||||
assert(items.length > 0, selector + ' is missing');
|
||||
for (const item of items) {
|
||||
const rect = await item.boundingBox();
|
||||
assert(rect && rect.width > 0 && rect.height > 0, selector + ' has no bounds');
|
||||
const png = PNG.sync.read(await page.screenshot({clip: {
|
||||
x: Math.ceil(rect.x + 1), y: Math.ceil(rect.y + 2),
|
||||
width: Math.max(1, Math.floor(rect.width - 2)), height: Math.max(1, Math.floor(rect.height - 4))
|
||||
}}));
|
||||
let pixels = 0;
|
||||
for (let i = 0; i < png.data.length; i += 4) {
|
||||
if (png.data[i] < 240 && png.data[i + 1] < 240 && png.data[i + 2] < 240) pixels++;
|
||||
}
|
||||
assert(pixels >= 3, selector + ' is not painted at its SVG coordinates');
|
||||
}
|
||||
}
|
||||
|
||||
/** Serve the fixture and repository assets on an ephemeral loopback port. */
|
||||
function createServer() {
|
||||
const root = path.resolve(__dirname, '../..');
|
||||
return http.createServer((request, response) => {
|
||||
const pathname = new URL(request.url, 'http://localhost').pathname;
|
||||
const file = path.resolve(root, '.' + pathname);
|
||||
if (!file.startsWith(root + path.sep)) {
|
||||
response.writeHead(403).end();
|
||||
return;
|
||||
}
|
||||
fs.readFile(file, (error, data) => {
|
||||
if (error) {
|
||||
response.writeHead(404).end();
|
||||
return;
|
||||
}
|
||||
const types = {'.html': 'text/html', '.css': 'text/css', '.js': 'text/javascript'};
|
||||
response.writeHead(200, {'Content-Type': types[path.extname(file)] || 'application/octet-stream'});
|
||||
response.end(data);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/** Check painting and real pointer interactions before and after tree updates and transforms. */
|
||||
async function checkBrowser(name, engine, url, artifacts) {
|
||||
const browser = await engine.launch();
|
||||
try {
|
||||
const page = await browser.newPage({viewport: {width: 1200, height: 800}});
|
||||
const errors = [];
|
||||
page.on('pageerror', error => errors.push(error.message));
|
||||
await page.goto(url);
|
||||
await page.waitForFunction(() => document.querySelectorAll('#networkTree g.node').length === 4);
|
||||
// Treeviz transitions take 600 ms; wait for both them and icon fonts to settle.
|
||||
await page.evaluate(() => document.fonts.ready);
|
||||
await page.waitForTimeout(900);
|
||||
await page.screenshot({path: path.join(artifacts, name + '-initial.png')});
|
||||
for (const selector of ['.spanNetworkTree', '.netIcon', '.portBckgIcon', '.network-hw-icon', '.netCollapse']) {
|
||||
await assertPainted(page, '#networkTree ' + selector);
|
||||
}
|
||||
const laptop = page.locator('.node-inner[data-mac="aa:bb:cc:dd:ee:02"] .spanNetworkTree');
|
||||
await laptop.click();
|
||||
assert.deepEqual(await page.evaluate(() => clicks), ['aa:bb:cc:dd:ee:02']);
|
||||
const collapse = page.locator('.netCollapse[data-mytreemac="aa:bb:cc:dd:ee:01"]');
|
||||
await collapse.click();
|
||||
await page.waitForFunction(() => document.querySelectorAll('#networkTree g.node').length === 2);
|
||||
await page.waitForTimeout(700);
|
||||
await collapse.click();
|
||||
await page.waitForFunction(() => document.querySelectorAll('#networkTree g.node').length === 4);
|
||||
await page.waitForTimeout(700);
|
||||
await assertPainted(page, '#networkTree .spanNetworkTree');
|
||||
// Exercise Treeviz/D3's actual wheel zoom and drag pan handlers.
|
||||
const canvas = page.locator('#networkTree svg > g');
|
||||
const beforeZoom = await canvas.getAttribute('transform');
|
||||
await page.mouse.move(600, 400);
|
||||
await page.mouse.wheel(0, 300);
|
||||
await page.waitForTimeout(500);
|
||||
const afterZoom = await canvas.getAttribute('transform');
|
||||
assert.notEqual(afterZoom, beforeZoom, 'Wheel zoom did not change the SVG transform');
|
||||
await page.mouse.move(600, 400);
|
||||
await page.mouse.down();
|
||||
await page.mouse.move(650, 440, {steps: 5});
|
||||
await page.mouse.up();
|
||||
assert.notEqual(await canvas.getAttribute('transform'), afterZoom, 'Drag pan did not change the SVG transform');
|
||||
await assertPainted(page, '#networkTree .spanNetworkTree');
|
||||
await laptop.click();
|
||||
assert.equal((await page.evaluate(() => clicks)).length, 2);
|
||||
await page.screenshot({path: path.join(artifacts, name + '-zoom-pan.png')});
|
||||
// The bundled Treeviz demo expects #tree, which network.php also does not provide.
|
||||
assert.deepEqual(errors.filter(error => error !== 'Cannot find dom element with id:tree'), []);
|
||||
console.log(name + ': PASS — painting, clicks, collapse/expand, zoom and pan');
|
||||
} finally {
|
||||
await browser.close();
|
||||
}
|
||||
}
|
||||
|
||||
/** Run the same rendering checks in WebKit, Chromium and Firefox without an app backend. */
|
||||
async function main() {
|
||||
const artifacts = process.env.NETALERTX_TOPOLOGY_ARTIFACTS || path.join(os.tmpdir(), 'netalertx-topology');
|
||||
fs.mkdirSync(artifacts, {recursive: true});
|
||||
const server = createServer();
|
||||
await new Promise(resolve => server.listen(0, '127.0.0.1', resolve));
|
||||
const url = `http://127.0.0.1:${server.address().port}/test/ui/fixtures/network_topology.html`;
|
||||
try {
|
||||
for (const [name, engine] of Object.entries({webkit, chromium, firefox})) {
|
||||
await checkBrowser(name, engine, url, artifacts);
|
||||
}
|
||||
} finally {
|
||||
await new Promise(resolve => server.close(resolve));
|
||||
}
|
||||
}
|
||||
|
||||
main().catch(error => { console.error(error); process.exitCode = 1; });
|
||||
Reference in new issue
Block a user