diff --git a/front/css/dark-patch.css b/front/css/dark-patch.css
index 93d43262..dbda3801 100755
--- a/front/css/dark-patch.css
+++ b/front/css/dark-patch.css
@@ -42,14 +42,7 @@ h4 {
.content-header > .breadcrumb > li > a {
color: #bec5cb;
}
-.table > thead > tr > th,
-.table > tbody > tr > th,
-.table > tfoot > tr > th,
-.table > thead > tr > td,
-.table > tbody > tr > td,
-.table > tfoot > tr > td {
- border-top: 0;
-}
+
.table > thead > tr.odd,
.table > tbody > tr.odd,
.table > tfoot > tr.odd {
@@ -73,7 +66,6 @@ h4 {
border: 1px solid #353c42;
}
.dataTables_wrapper input[type="search"] {
- border-radius: 4px;
background-color: #353c42;
border: 0;
color: #bec5cb;
@@ -126,7 +118,6 @@ h4 {
border-color: #3c8dbc;
}
.sidebar-menu > li > .treeview-menu {
- margin: 0 1px;
background-color: #32393e;
}
.sidebar a {
@@ -144,16 +135,13 @@ h4 {
color: #fff;
}
.sidebar-form {
- border-radius: 3px;
border: 1px solid #3e464c;
- margin: 10px;
}
.sidebar-form input[type="text"],
.sidebar-form .btn {
box-shadow: none;
background-color: #3e464c;
border: 1px solid transparent;
- height: 35px;
}
.sidebar-form input[type="text"] {
color: #666;
@@ -207,20 +195,13 @@ h4 {
.box > .box-header .btn {
color: #bec5cb;
}
-.box.box-info,
-.box.box-primary,
-.box.box-success,
-.box.box-warning,
-.box.box-danger {
- border-top-width: 3px;
-}
+
.main-header .navbar {
background-color: #272c30;
}
.main-header .navbar .nav > li > a,
.main-header .navbar .nav > li > .navbar-text {
color: #bec5cb;
- max-height: 50px;
}
.main-header .navbar .nav > li > a:hover,
.main-header .navbar .nav > li > a:active,
@@ -277,7 +258,6 @@ h4 {
background: rgba(64, 72, 80, 0.666);
}
.nav-tabs-custom > .nav-tabs > li {
- margin-right: 1px;
color: #bec5cb;
}
.nav-tabs-custom > .nav-tabs > li.active > a,
@@ -386,11 +366,8 @@ h4 {
code,
pre {
- padding: 2px 4px;
- font-size: 90%;
color: #bec5cb;
background-color: #353c42;
- border-radius: 4px;
}
/* Used in the Query Log table */
@@ -456,7 +433,7 @@ td.highlight {
/* Used by the long-term pages */
.daterangepicker {
background-color: #3e464c;
- border-radius: 4px;
+
border: 1px solid #353c42;
}
.daterangepicker .ranges li:hover {
@@ -467,7 +444,7 @@ td.highlight {
}
.daterangepicker .calendar-table {
background-color: #3e464c;
- border-radius: 4px;
+
border: 1px solid #353c42;
}
.daterangepicker td.off,
@@ -535,7 +512,7 @@ textarea[readonly],
.panel-body,
.panel-default > .panel-heading {
background-color: #3e464c;
- border-radius: 4px;
+
border: 1px solid #353c42;
color: #bec5cb;
}
@@ -568,23 +545,10 @@ input[type="password"]::-webkit-caps-lock-indicator {
background-image: linear-gradient(to right, #114100 0%, #525200 100%);
}
-.icheckbox_polaris,
-.icheckbox_futurico,
-.icheckbox_minimal-blue {
- margin-right: 10px;
-}
-.iradio_polaris,
-.iradio_futurico,
-.iradio_minimal-blue {
- margin-right: 8px;
-}
-
/* Overlay box with spinners as shown during data collection for graphs */
.box .overlay,
.overlay-wrapper .overlay {
- z-index: 50;
background-color: rgba(53, 60, 66, 0.733);
- border-radius: 3px;
}
.box .overlay > .fa,
.overlay-wrapper .overlay > .fa,
@@ -594,7 +558,6 @@ input[type="password"]::-webkit-caps-lock-indicator {
.navbar-nav > .user-menu > .dropdown-menu > .user-footer {
background-color: #353c42bb;
- padding: 10px;
}
.modal-content {
@@ -626,29 +589,29 @@ input[type="password"]::-webkit-caps-lock-indicator {
/*** Additional fixes For UI ***/
.pa-small-box-aqua .inner {
background-color: rgb(45,108,133);
- border-top-left-radius: 10px;
- border-top-right-radius: 10px;
+
+
}
.pa-small-box-green .inner {
background-color: rgb(31,76,46);
- border-top-left-radius: 10px;
- border-top-right-radius: 10px;
+
+
}
.pa-small-box-yellow .inner {
background-color: rgb(151,104,37);
- border-top-left-radius: 10px;
- border-top-right-radius: 10px;
+
+
}
.pa-small-box-red .inner {
background-color: rgb(120,50,38);
- border-top-left-radius: 10px;
- border-top-right-radius: 10px;
+
+
}
.pa-small-box-gray .inner {
background-color: #777;
/* color: rgba(20,20,20,30%); */
- border-top-left-radius: 10px;
- border-top-right-radius: 10px;
+
+
}
.pa-small-box-gray .inner h3 {
color: #bbb;
@@ -687,15 +650,6 @@ table.dataTable tbody tr.selected, table.dataTable tbody tr .selected
.db_tools_table_cell_b:nth-child(1) {background: #272c30}
.db_tools_table_cell_b:nth-child(2) {background: #272c30}
-.db_info_table {
- display: table;
- border-spacing: 0em;
- font-weight: 400;
- font-size: 15px;
- width: 100%;
- margin: auto;
-}
-
.nav-tabs-custom > .nav-tabs > li:hover > a, .nav-tabs-custom > .nav-tabs > li.active:hover > a {
background-color: #272c30;
color: #bec5cb;
@@ -738,23 +692,7 @@ table.dataTable tbody tr.selected, table.dataTable tbody tr .selected
color: #bec5cb;
background-color: #272c30;
}
-/* Add border radius to bottom of the status boxes*/
-.pa-small-box-footer {
- border-bottom-left-radius: 10px;
- border-bottom-right-radius: 10px;
-}
-.small-box > .inner h3, .small-box > .inner p {
- margin-bottom: 0px;
- margin-left: 0px;
-}
-.small-box:hover .icon {
- font-size: 3em;
-}
-.small-box .icon {
- top: 0.01em;
- font-size: 3.25em;
-}
.nax_semitransparent-panel{
background-color: #000 !important;
}
diff --git a/front/css/system-dark-patch.css b/front/css/system-dark-patch.css
index f55ce1c2..353c1679 100755
--- a/front/css/system-dark-patch.css
+++ b/front/css/system-dark-patch.css
@@ -76,9 +76,7 @@
border: 1px solid #353c42;
}
.dataTables_wrapper input[type="search"] {
- border-radius: 4px;
background-color: #353c42;
- border: 0;
color: #bec5cb;
}
.dataTables_paginate .pagination li > a {
@@ -129,7 +127,6 @@
border-color: #3c8dbc;
}
.sidebar-menu > li > .treeview-menu {
- margin: 0 1px;
background-color: #32393e;
}
.sidebar a {
@@ -147,23 +144,16 @@
color: #fff;
}
.sidebar-form {
- border-radius: 3px;
border: 1px solid #3e464c;
- margin: 10px;
}
.sidebar-form input[type="text"],
.sidebar-form .btn {
box-shadow: none;
background-color: #3e464c;
border: 1px solid transparent;
- height: 35px;
}
.sidebar-form input[type="text"] {
color: #666;
- border-top-left-radius: 2px;
- border-top-right-radius: 0;
- border-bottom-right-radius: 0;
- border-bottom-left-radius: 2px;
}
.sidebar-form input[type="text"]:focus,
.sidebar-form input[type="text"]:focus + .input-group-btn .btn {
@@ -175,10 +165,6 @@
}
.sidebar-form .btn {
color: #999;
- border-top-left-radius: 0;
- border-top-right-radius: 2px;
- border-bottom-right-radius: 2px;
- border-bottom-left-radius: 0;
}
.box,
.box-footer,
@@ -210,20 +196,12 @@
.box > .box-header .btn {
color: #bec5cb;
}
- .box.box-info,
- .box.box-primary,
- .box.box-success,
- .box.box-warning,
- .box.box-danger {
- border-top-width: 3px;
- }
.main-header .navbar {
background-color: #272c30;
}
.main-header .navbar .nav > li > a,
.main-header .navbar .nav > li > .navbar-text {
color: #bec5cb;
- max-height: 50px;
}
.main-header .navbar .nav > li > a:hover,
.main-header .navbar .nav > li > a:active,
@@ -280,7 +258,6 @@
background: rgba(64, 72, 80, 0.666);
}
.nav-tabs-custom > .nav-tabs > li {
- margin-right: 1px;
color: #bec5cb;
}
.nav-tabs-custom > .nav-tabs > li.active > a,
@@ -389,11 +366,8 @@
code,
pre {
- padding: 2px 4px;
- font-size: 90%;
color: #bec5cb;
background-color: #353c42;
- border-radius: 4px;
}
/* Used in the Query Log table */
@@ -459,7 +433,6 @@
/* Used by the long-term pages */
.daterangepicker {
background-color: #3e464c;
- border-radius: 4px;
border: 1px solid #353c42;
}
.daterangepicker .ranges li:hover {
@@ -470,7 +443,6 @@
}
.daterangepicker .calendar-table {
background-color: #3e464c;
- border-radius: 4px;
border: 1px solid #353c42;
}
.daterangepicker td.off,
@@ -537,7 +509,6 @@
.panel-body,
.panel-default > .panel-heading {
background-color: #3e464c;
- border-radius: 4px;
border: 1px solid #353c42;
color: #bec5cb;
}
@@ -570,23 +541,10 @@
background-image: linear-gradient(to right, #114100 0%, #525200 100%);
}
- .icheckbox_polaris,
- .icheckbox_futurico,
- .icheckbox_minimal-blue {
- margin-right: 10px;
- }
- .iradio_polaris,
- .iradio_futurico,
- .iradio_minimal-blue {
- margin-right: 8px;
- }
-
/* Overlay box with spinners as shown during data collection for graphs */
.box .overlay,
.overlay-wrapper .overlay {
- z-index: 50;
background-color: rgba(53, 60, 66, 0.733);
- border-radius: 3px;
}
.box .overlay > .fa,
.overlay-wrapper .overlay > .fa,
@@ -596,7 +554,6 @@
.navbar-nav > .user-menu > .dropdown-menu > .user-footer {
background-color: #353c42bb;
- padding: 10px;
}
.modal-content {
@@ -625,36 +582,21 @@
border-color: rgb(120, 127, 133);
}
- /*** Additional fixes For Pi.Alert UI ***/
- .small-box {
- border-radius: 10px;
- border-top: 0px;
- }
.pa-small-box-aqua .inner {
background-color: rgb(45,108,133);
- border-top-left-radius: 10px;
- border-top-right-radius: 10px;
}
.pa-small-box-green .inner {
background-color: rgb(31,76,46);
- border-top-left-radius: 10px;
- border-top-right-radius: 10px;
}
.pa-small-box-yellow .inner {
background-color: rgb(151,104,37);
- border-top-left-radius: 10px;
- border-top-right-radius: 10px;
}
.pa-small-box-red .inner {
background-color: rgb(120,50,38);
- border-top-left-radius: 10px;
- border-top-right-radius: 10px;
}
.pa-small-box-gray .inner {
background-color: #777;
/* color: rgba(20,20,20,30%); */
- border-top-left-radius: 10px;
- border-top-right-radius: 10px;
}
.pa-small-box-gray .inner h3 {
color: #bbb;
@@ -693,15 +635,6 @@
.db_tools_table_cell_b:nth-child(1) {background: #272c30}
.db_tools_table_cell_b:nth-child(2) {background: #272c30}
- .db_info_table {
- display: table;
- border-spacing: 0em;
- font-weight: 400;
- font-size: 15px;
- width: 100%;
- margin: auto;
- }
-
.nav-tabs-custom > .nav-tabs > li:hover > a, .nav-tabs-custom > .nav-tabs > li.active:hover > a {
background-color: #272c30;
color: #bec5cb;
@@ -723,15 +656,6 @@
color: white !important;
}
- /* remove white border that appears on mobile screen sizes */
- .box-body {
- border: 0px;
- }
- /* remove white border that appears on mobile screen sizes */
- .table-responsive {
- border: 0px;
- }
-
.login-page {
background-color: transparent;
}
@@ -744,23 +668,6 @@
color: #bec5cb;
background-color: #272c30;
}
- /* Add border radius to bottom of the status boxes*/
- .pa-small-box-footer {
- border-bottom-left-radius: 10px;
- border-bottom-right-radius: 10px;
- }
-
- .small-box > .inner h3, .small-box > .inner p {
- margin-bottom: 0px;
- margin-left: 0px;
- }
- .small-box:hover .icon {
- font-size: 3em;
- }
- .small-box .icon {
- top: 0.01em;
- font-size: 3.25em;
- }
.nax_semitransparent-panel{
background-color: #000 !important;
}
diff --git a/front/deviceDetailsEdit.php b/front/deviceDetailsEdit.php
index ef447800..c9acb3a5 100755
--- a/front/deviceDetailsEdit.php
+++ b/front/deviceDetailsEdit.php
@@ -423,7 +423,7 @@ function setDeviceData(direction = '', refreshCallback = '') {
// Build payload
const payload = {
- devName: $('#NEWDEV_devName').val().replace(/'/g, "’"),
+ devName: $('#NEWDEV_devName').val(),
devOwner: $('#NEWDEV_devOwner').val().replace(/'/g, "’"),
devType: $('#NEWDEV_devType').val().replace(/'/g, ""),
devVendor: $('#NEWDEV_devVendor').val().replace(/'/g, "’"),
@@ -432,7 +432,7 @@ function setDeviceData(direction = '', refreshCallback = '') {
devFavorite: ($('#NEWDEV_devFavorite')[0].checked * 1),
devGroup: $('#NEWDEV_devGroup').val().replace(/'/g, "’"),
devLocation: $('#NEWDEV_devLocation').val().replace(/'/g, "’"),
- devComments: encodeSpecialChars($('#NEWDEV_devComments').val()),
+ devComments: ($('#NEWDEV_devComments').val()),
devParentMAC: $('#NEWDEV_devParentMAC').val(),
devParentPort: $('#NEWDEV_devParentPort').val(),
diff --git a/front/network.php b/front/network.php
index 13830eed..c634c703 100755
--- a/front/network.php
+++ b/front/network.php
@@ -85,20 +85,24 @@
// \
// PC (leaf) <------- leafs are not included in this SQL query
const rawSql = `
- SELECT node_name, node_mac, online, node_type, node_ports_count, parent_mac, node_icon, node_alert
- FROM (
- SELECT a.devName as node_name, a.devMac as node_mac, a.devPresentLastScan as online,
- a.devType as node_type, a.devParentMAC as parent_mac, a.devIcon as node_icon, a.devAlertDown as node_alert
- FROM Devices a
- WHERE a.devType IN (${networkDeviceTypes}) and a.devIsArchived = 0
- ) t1
- LEFT JOIN (
- SELECT b.devParentMAC as node_mac_2, count() as node_ports_count
- FROM Devices b
- WHERE b.devParentMAC NOT NULL
- GROUP BY b.devParentMAC
- ) t2
- ON (t1.node_mac = t2.node_mac_2)
+ SELECT
+ parent.devName AS node_name,
+ parent.devMac AS node_mac,
+ parent.devPresentLastScan AS online,
+ parent.devType AS node_type,
+ parent.devParentMAC AS parent_mac,
+ parent.devIcon AS node_icon,
+ parent.devAlertDown AS node_alert,
+ COUNT(child.devMac) AS node_ports_count
+ FROM Devices AS parent
+ LEFT JOIN Devices AS child
+ ON child.devParentMAC = parent.devMac
+ WHERE parent.devType IN (
+ ${networkDeviceTypes})
+ AND parent.devIsArchived = 0
+ GROUP BY parent.devMac, parent.devName, parent.devPresentLastScan,
+ parent.devType, parent.devParentMAC, parent.devIcon, parent.devAlertDown
+ ORDER BY parent.devName;
`;
const apiBase = getApiBase();
@@ -378,6 +382,10 @@
// ----------------------------------------------------
function loadConnectedDevices(node_mac) {
+
+ // 1. Force to lowercase to match the new DB standard
+ const normalized_mac = node_mac.toLowerCase();
+
const sql = `
SELECT devName, devMac, devLastIP, devVendor, devPresentLastScan, devAlertDown, devParentPort,
CASE
@@ -389,12 +397,12 @@
ELSE 'Unknown status'
END AS devStatus
FROM Devices
- WHERE devParentMac = '${node_mac}'`;
+ WHERE devParentMac = '${normalized_mac}'`;
const id = node_mac.replace(/:/g, '_');
const wrapperHtml = `
-
+ `;
diff --git a/front/plugins/ddns_update/script.py b/front/plugins/ddns_update/script.py
index 39bdade4..c25526a1 100755
--- a/front/plugins/ddns_update/script.py
+++ b/front/plugins/ddns_update/script.py
@@ -81,14 +81,14 @@ def ddns_update(DDNS_UPDATE_URL, DDNS_USER, DDNS_PASSWORD, DDNS_DOMAIN, PREV_IP)
# plugin_objects = Plugin_Objects(RESULT_FILE)
# plugin_objects.add_object(
- # primaryId = 'Internet', # MAC (Device Name)
+ # primaryId = 'internet', # MAC (Device Name)
# secondaryId = new_internet_IP, # IP Address
# watched1 = f'Previous IP: {PREV_IP}',
# watched2 = '',
# watched3 = '',
# watched4 = '',
# extra = f'Previous IP: {PREV_IP}',
- # foreignKey = 'Internet')
+ # foreignKey = 'internet')
# plugin_objects.write_result_file()
diff --git a/front/plugins/internet_ip/script.py b/front/plugins/internet_ip/script.py
index ff5d3cea..328341a6 100755
--- a/front/plugins/internet_ip/script.py
+++ b/front/plugins/internet_ip/script.py
@@ -79,14 +79,14 @@ def main():
plugin_objects = Plugin_Objects(RESULT_FILE)
plugin_objects.add_object(
- primaryId = 'Internet', # MAC (Device Name)
+ primaryId = 'internet', # MAC (Device Name)
secondaryId = new_internet_IP, # IP Address
watched1 = f'Previous IP: {PREV_IP}',
watched2 = cmd_output.replace('\n', ''),
watched3 = retries_needed,
watched4 = 'Gateway',
extra = f'Previous IP: {PREV_IP}',
- foreignKey = 'Internet'
+ foreignKey = 'internet'
)
plugin_objects.write_result_file()
@@ -101,8 +101,8 @@ def main():
# ===============================================================================
def check_internet_IP(PREV_IP, DIG_GET_IP_ARG):
- # Get Internet IP
- mylog('verbose', [f'[{pluginName}] - Retrieving Internet IP'])
+ # Get internet IP
+ mylog('verbose', [f'[{pluginName}] - Retrieving internet IP'])
internet_IP, cmd_output = get_internet_IP(DIG_GET_IP_ARG)
mylog('verbose', [f'[{pluginName}] Current internet_IP : {internet_IP}'])
diff --git a/front/plugins/omada_sdn_imp/omada_sdn.py b/front/plugins/omada_sdn_imp/omada_sdn.py
index b00ce3f9..9447df69 100755
--- a/front/plugins/omada_sdn_imp/omada_sdn.py
+++ b/front/plugins/omada_sdn_imp/omada_sdn.py
@@ -330,8 +330,8 @@ def main():
myssid = device[PORT_SSID] if not device[PORT_SSID].isdigit() else ""
ParentNetworkNode = (
ieee2ietf_mac_formater(device[SWITCH_AP])
- if device[SWITCH_AP] != "Internet"
- else "Internet"
+ if device[SWITCH_AP].lower() != "internet"
+ else "internet"
)
mymac = ieee2ietf_mac_formater(device[MAC])
plugin_objects.add_object(
@@ -665,7 +665,7 @@ def get_device_data(omada_clients_output, switches_and_aps, device_handler):
device_data_bymac[default_router_mac][TYPE] = "Firewall"
# step2 let's find the first switch and set the default router parent to internet
first_switch = device_data_bymac[default_router_mac][SWITCH_AP]
- device_data_bymac[default_router_mac][SWITCH_AP] = "Internet"
+ device_data_bymac[default_router_mac][SWITCH_AP] = "internet"
# step3 let's set the switch connected to the default gateway uplink to the default gateway and hardcode port to 1 for now:
# device_data_bymac[first_switch][SWITCH_AP]=default_router_mac
# device_data_bymac[first_switch][SWITCH_AP][PORT_SSID] = '1'
diff --git a/front/plugins/omada_sdn_openapi/script.py b/front/plugins/omada_sdn_openapi/script.py
index 7d341126..11b17b60 100755
--- a/front/plugins/omada_sdn_openapi/script.py
+++ b/front/plugins/omada_sdn_openapi/script.py
@@ -413,11 +413,11 @@ class OmadaData:
OmadaHelper.verbose(f"Making entry for: {entry['mac_address']}")
- # If the device_type is gateway, set the parent_node to Internet
+ # If the device_type is gateway, set the parent_node to internet
device_type = entry["device_type"].lower()
parent_node = entry["parent_node_mac_address"]
if len(parent_node) == 0 and entry["device_type"] == "gateway" and is_typical_router_ip(entry["ip_address"]):
- parent_node = "Internet"
+ parent_node = "internet"
# Some device type naming exceptions
if device_type == "iphone":
diff --git a/front/plugins/plugin_helper.py b/front/plugins/plugin_helper.py
index 6e1f99a0..b1c0399a 100755
--- a/front/plugins/plugin_helper.py
+++ b/front/plugins/plugin_helper.py
@@ -177,27 +177,25 @@ def decode_settings_base64(encoded_str, convert_types=True):
# -------------------------------------------------------------------
def normalize_mac(mac):
"""
- Normalize a MAC address to the standard format with colon separators.
- For example, "aa-bb-cc-dd-ee-ff" will be normalized to "AA:BB:CC:DD:EE:FF".
- Wildcard MAC addresses like "AA:BB:CC:*" will be normalized to "AA:BB:CC:*".
+ normalize a mac address to the standard format with colon separators.
+ for example, "AA-BB-CC-DD-EE-FF" will be normalized to "aa:bb:cc:dd:ee:ff".
+ wildcard mac addresses like "AA:BB:CC:*" will be normalized to "aa:bb:cc:*".
- :param mac: The MAC address to normalize.
- :return: The normalized MAC address.
+ :param mac: the mac address to normalize.
+ :return: the normalized mac address (lowercase).
"""
- s = str(mac).strip()
+ s = str(mac).strip().lower()
- if s.lower() == "internet":
- return "Internet"
+ if s == "internet":
+ return "internet"
- s = s.upper()
-
- # Determine separator if present, prefer colon, then hyphen
+ # determine separator if present, prefer colon, then hyphen
if ':' in s:
parts = s.split(':')
elif '-' in s:
parts = s.split('-')
else:
- # No explicit separator; attempt to split every two chars
+ # no explicit separator; attempt to split every two chars
parts = [s[i:i + 2] for i in range(0, len(s), 2)]
normalized_parts = []
@@ -206,10 +204,10 @@ def normalize_mac(mac):
if part == '*':
normalized_parts.append('*')
else:
- # Ensure two hex digits (zfill is fine for alphanumeric input)
+ # ensure two hex digits
normalized_parts.append(part.zfill(2))
- # Use colon as canonical separator
+ # use colon as canonical separator
return ':'.join(normalized_parts)
diff --git a/front/plugins/unifi_api_import/unifi_api_import.py b/front/plugins/unifi_api_import/unifi_api_import.py
index 4c51f7b4..678e8a46 100755
--- a/front/plugins/unifi_api_import/unifi_api_import.py
+++ b/front/plugins/unifi_api_import/unifi_api_import.py
@@ -74,7 +74,7 @@ def main():
watched1 = device['dev_name'], # name
watched2 = device['dev_type'], # device_type (AP/Switch etc)
watched3 = device['dev_connected'], # connectedAt or empty
- watched4 = device['dev_parent_mac'], # parent_mac or "Internet"
+ watched4 = device['dev_parent_mac'], # parent_mac or "internet"
extra = '',
foreignKey = device['dev_mac']
)
@@ -115,10 +115,10 @@ def get_device_data(site, api):
continue
device_id_to_mac[dev["id"]] = dev.get("macAddress", "")
- # Helper to resolve uplinkDeviceId to parent MAC, or "Internet" if no uplink
+ # Helper to resolve uplinkDeviceId to parent MAC, or "internet" if no uplink
def resolve_parent_mac(uplink_id):
if not uplink_id:
- return "Internet"
+ return "internet"
return device_id_to_mac.get(uplink_id, "Unknown")
# Process Unifi devices
diff --git a/front/plugins/unifi_import/script.py b/front/plugins/unifi_import/script.py
index d62154b7..d2a3886b 100755
--- a/front/plugins/unifi_import/script.py
+++ b/front/plugins/unifi_import/script.py
@@ -173,7 +173,7 @@ def collect_details(device_type, devices, online_macs, processed_macs, plugin_ob
# override parent MAC if this is a router
if parentMac == 'null' and is_typical_router_ip(ipTmp):
- parentMac = 'Internet'
+ parentMac = 'internet'
# Add object only if not processed
if macTmp not in processed_macs and (status == 1 or force_import is True):
diff --git a/scripts/generate-device-inventory.py b/scripts/generate-device-inventory.py
index 3ca76a4b..1ad959f3 100644
--- a/scripts/generate-device-inventory.py
+++ b/scripts/generate-device-inventory.py
@@ -216,7 +216,7 @@ def generate_rows(args: argparse.Namespace, header: list[str]) -> list[dict[str,
rows: list[dict[str, str]] = []
- # Include one Internet root device that anchors the tree; it does not consume an IP.
+ # Include one internet root device that anchors the tree; it does not consume an IP.
required_devices = 1 + args.switches + args.aps + args.devices
if required_devices > len(ip_pool):
raise ValueError(
@@ -229,12 +229,12 @@ def generate_rows(args: argparse.Namespace, header: list[str]) -> list[dict[str,
ip_pool.remove(choice)
return choice
- # Root "Internet" device (no parent, no IP) so the topology has a defined root.
+ # Root "internet" device (no parent, no IP) so the topology has a defined root.
root_row = build_row(
- name="Internet",
+ name="internet",
dev_type="Gateway",
vendor="NetAlertX",
- mac="Internet",
+ mac="internet",
parent_mac="",
ip="",
header=header,
@@ -243,7 +243,7 @@ def generate_rows(args: argparse.Namespace, header: list[str]) -> list[dict[str,
ssid=args.ssid,
now=now,
)
- root_row["devComments"] = "Synthetic root device representing the Internet."
+ root_row["devComments"] = "Synthetic root device representing the internet."
root_row["devParentRelType"] = "Root"
root_row["devStaticIP"] = "0"
root_row["devScan"] = "0"
@@ -261,7 +261,7 @@ def generate_rows(args: argparse.Namespace, header: list[str]) -> list[dict[str,
dev_type="Firewall",
vendor=random.choice(VENDORS),
mac=router_mac,
- parent_mac="Internet",
+ parent_mac="internet",
ip=router_ip,
header=header,
owner=args.owner,
diff --git a/server/api_server/openapi/schemas.py b/server/api_server/openapi/schemas.py
index fe8f9618..409ca176 100644
--- a/server/api_server/openapi/schemas.py
+++ b/server/api_server/openapi/schemas.py
@@ -64,9 +64,9 @@ ALLOWED_EVENT_TYPES = Literal[
def validate_mac(value: str) -> str:
"""Validate and normalize MAC address format."""
- # Allow "Internet" as a special case for the gateway/WAN device
+ # Allow "internet" as a special case for the gateway/WAN device
if value.lower() == "internet":
- return "Internet"
+ return "internet"
if not is_mac(value):
raise ValueError(f"Invalid MAC address format: {value}")
@@ -439,7 +439,7 @@ class DeviceUpdateRequest(BaseModel):
def sanitize_text_fields(cls, v: Optional[str]) -> Optional[str]:
if v is None:
return v
- return sanitize_string(v)
+ return v
class DeleteDevicesRequest(BaseModel):
diff --git a/server/database.py b/server/database.py
index 07678c07..b76ff076 100755
--- a/server/database.py
+++ b/server/database.py
@@ -16,6 +16,7 @@ from db.db_upgrade import (
ensure_Parameters,
ensure_Settings,
ensure_Indexes,
+ ensure_mac_lowercase_triggers,
)
@@ -198,6 +199,9 @@ class DB:
# Indexes
ensure_Indexes(self.sql)
+ # Normalization triggers
+ ensure_mac_lowercase_triggers(self.sql)
+
# commit changes
self.commitDB()
except Exception as e:
diff --git a/server/db/db_upgrade.py b/server/db/db_upgrade.py
index b79ddf88..71e7fe5e 100755
--- a/server/db/db_upgrade.py
+++ b/server/db/db_upgrade.py
@@ -105,6 +105,50 @@ def ensure_column(sql, table: str, column_name: str, column_type: str) -> bool:
return False
+def ensure_mac_lowercase_triggers(sql):
+ """
+ Ensures the triggers for lowercasing MAC addresses exist on the Devices table.
+ """
+ try:
+ # 1. Handle INSERT Trigger
+ sql.execute("SELECT name FROM sqlite_master WHERE type='trigger' AND name='trg_lowercase_mac_insert'")
+ if not sql.fetchone():
+ mylog("verbose", ["[db_upgrade] Creating trigger 'trg_lowercase_mac_insert'"])
+ sql.execute("""
+ CREATE TRIGGER trg_lowercase_mac_insert
+ AFTER INSERT ON Devices
+ BEGIN
+ UPDATE Devices
+ SET devMac = LOWER(NEW.devMac),
+ devParentMAC = LOWER(NEW.devParentMAC)
+ WHERE rowid = NEW.rowid;
+ END;
+ """)
+
+ # 2. Handle UPDATE Trigger
+ sql.execute("SELECT name FROM sqlite_master WHERE type='trigger' AND name='trg_lowercase_mac_update'")
+ if not sql.fetchone():
+ mylog("verbose", ["[db_upgrade] Creating trigger 'trg_lowercase_mac_update'"])
+ # Note: Using 'WHEN' to prevent unnecessary updates and recursion
+ sql.execute("""
+ CREATE TRIGGER trg_lowercase_mac_update
+ AFTER UPDATE OF devMac, devParentMAC ON Devices
+ WHEN (NEW.devMac GLOB '*[A-Z]*') OR (NEW.devParentMAC GLOB '*[A-Z]*')
+ BEGIN
+ UPDATE Devices
+ SET devMac = LOWER(NEW.devMac),
+ devParentMAC = LOWER(NEW.devParentMAC)
+ WHERE rowid = NEW.rowid;
+ END;
+ """)
+
+ return True
+
+ except Exception as e:
+ mylog("none", [f"[db_upgrade] ERROR while ensuring MAC triggers: {e}"])
+ return False
+
+
def ensure_views(sql) -> bool:
"""
Ensures required views exist.
diff --git a/server/initialise.py b/server/initialise.py
index e6bb2242..d3b13c22 100755
--- a/server/initialise.py
+++ b/server/initialise.py
@@ -358,7 +358,7 @@ def importConfigs(pm, db, all_plugins):
"Router",
"USB LAN Adapter",
"USB WIFI Adapter",
- "Internet",
+ "internet",
],
c_d,
"Network device types",
diff --git a/server/models/device_instance.py b/server/models/device_instance.py
index 0712ad55..e522f4a9 100755
--- a/server/models/device_instance.py
+++ b/server/models/device_instance.py
@@ -495,7 +495,7 @@ class DeviceInstance:
# Fetch children
cur.execute(
- "SELECT * FROM Devices WHERE devParentMAC = ? ORDER BY devPresentLastScan DESC",
+ "SELECT * FROM Devices WHERE LOWER(devParentMAC) = LOWER(?) ORDER BY devPresentLastScan DESC",
(device_data["devMac"],),
)
children_rows = cur.fetchall()
diff --git a/server/scan/device_handling.py b/server/scan/device_handling.py
index dcb24476..886f550f 100755
--- a/server/scan/device_handling.py
+++ b/server/scan/device_handling.py
@@ -728,10 +728,10 @@ def create_new_devices(db):
scanParentMAC = raw_parent_mac
scanParentMAC = (
scanParentMAC
- if scanParentMAC and scanMac != "Internet"
+ if scanParentMAC and scanMac.lower() != "internet"
else (
get_setting_value("NEWDEV_devParentMAC")
- if scanMac != "Internet"
+ if scanMac.lower() != "internet"
else "null"
)
)
@@ -1243,7 +1243,7 @@ def update_devPresentLastScan_based_on_force_status(db):
# -------------------------------------------------------------------------------
-# Check if the variable contains a valid MAC address or "Internet"
+# Check if the variable contains a valid MAC address or "internet"
def check_mac_or_internet(input_str):
# Regular expression pattern for matching a MAC address
mac_pattern = r"([0-9A-Fa-f]{2}[:-][0-9A-Fa-f]{2}[:-][0-9A-Fa-f]{2}[:-][0-9A-Fa-f]{2}[:-][0-9A-Fa-f]{2}[:-][0-9A-Fa-f]{2})"
diff --git a/server/scan/device_heuristics.py b/server/scan/device_heuristics.py
index b9c14520..71bd183a 100755
--- a/server/scan/device_heuristics.py
+++ b/server/scan/device_heuristics.py
@@ -179,7 +179,7 @@ def guess_device_attributes(
# # Internet shortcut
# if mac == "INTERNET":
- # return ICONS.get("globe", default_icon), DEVICE_TYPES.get("Internet", default_type)
+ # return ICONS.get("globe", default_icon), DEVICE_TYPES.get("internet", default_type)
type_ = None
icon = None
diff --git a/test/api_endpoints/test_device_endpoints.py b/test/api_endpoints/test_device_endpoints.py
index 7a1ffa96..85284f73 100644
--- a/test/api_endpoints/test_device_endpoints.py
+++ b/test/api_endpoints/test_device_endpoints.py
@@ -132,7 +132,7 @@ def test_update_device_column(client, api_token, test_mac):
# Update its parent MAC
resp = client.post(
f"/device/{test_mac}/update-column",
- json={"columnName": "devParentMAC", "columnValue": "Internet"},
+ json={"columnName": "devParentMAC", "columnValue": "internet"},
headers=auth_headers(api_token),
)
@@ -142,7 +142,7 @@ def test_update_device_column(client, api_token, test_mac):
# Try updating a non-existent device
resp_missing = client.post(
"/device/11:22:33:44:55:66/update-column",
- json={"columnName": "devParentMAC", "columnValue": "Internet"},
+ json={"columnName": "devParentMAC", "columnValue": "internet"},
headers=auth_headers(api_token),
)
diff --git a/test/api_endpoints/test_device_update_normalization.py b/test/api_endpoints/test_device_update_normalization.py
index 70176d5e..832111a1 100644
--- a/test/api_endpoints/test_device_update_normalization.py
+++ b/test/api_endpoints/test_device_update_normalization.py
@@ -1,70 +1,78 @@
-
import pytest
-import random
from helper import get_setting_value
from api_server.api_server_start import app
from models.device_instance import DeviceInstance
+
@pytest.fixture(scope="session")
def api_token():
return get_setting_value("API_TOKEN")
+
@pytest.fixture
def client():
with app.test_client() as client:
yield client
+
@pytest.fixture
def test_mac_norm():
- # Normalized MAC
- return "AA:BB:CC:DD:EE:FF"
+ # Now normalized to lowercase
+ return "aa:bb:cc:dd:ee:ff"
+
@pytest.fixture
def test_parent_mac_input():
- # Lowercase input MAC
- return "aa:bb:cc:dd:ee:00"
+ # Input with mixed/upper case to test the trigger/normalization
+ return "AA:BB:CC:DD:EE:00"
+
@pytest.fixture
def test_parent_mac_norm():
- # Normalized expected MAC
- return "AA:BB:CC:DD:EE:00"
+ # Expected result in DB (lowercase)
+ return "aa:bb:cc:dd:ee:00"
+
def auth_headers(token):
return {"Authorization": f"Bearer {token}"}
+
def test_update_normalization(client, api_token, test_mac_norm, test_parent_mac_input, test_parent_mac_norm):
- # 1. Create a device (using normalized MAC)
+ # 1. Create a device
create_payload = {
"createNew": True,
"devName": "Normalization Test Device",
"devOwner": "Unit Test",
}
+ # Pass the lowercase mac
resp = client.post(f"/device/{test_mac_norm}", json=create_payload, headers=auth_headers(api_token))
assert resp.status_code == 200
assert resp.json.get("success") is True
- # 2. Update the device using LOWERCASE MAC in URL
- # And set devParentMAC to LOWERCASE
+ # 2. Update the device sending UPPERCASE parent MAC
+ # To verify the triggers/logic flip it to lowercase
update_payload = {
"devParentMAC": test_parent_mac_input,
"devName": "Updated Device"
}
- # Using lowercase MAC in URL: aa:bb:cc:dd:ee:ff
- lowercase_mac = test_mac_norm.lower()
-
- resp = client.post(f"/device/{lowercase_mac}", json=update_payload, headers=auth_headers(api_token))
+
+ resp = client.post(f"/device/{test_mac_norm}", json=update_payload, headers=auth_headers(api_token))
assert resp.status_code == 200
assert resp.json.get("success") is True
- # 3. Verify in DB that devParentMAC is NORMALIZED
+ # 3. Verify in DB that devParentMAC is LOWERCASE
device_handler = DeviceInstance()
+ # Query using lowercase mac
device = device_handler.getDeviceData(test_mac_norm)
-
+
assert device is not None
assert device["devName"] == "Updated Device"
- # This is the critical check:
+
+ # CRITICAL CHECKS:
+ # It must be lowercase now
assert device["devParentMAC"] == test_parent_mac_norm
- assert device["devParentMAC"] != test_parent_mac_input # Should verify it changed from input if input was different case
+ # It should NOT be the uppercase input we sent
+ assert device["devParentMAC"] != test_parent_mac_input
# Cleanup
- device_handler.deleteDeviceByMAC(test_mac_norm)
+ device_handler.deleteDeviceByMAC(test_mac_norm)
\ No newline at end of file
diff --git a/test/test_plugin_helper.py b/test/test_plugin_helper.py
index 9a88f39b..59e11597 100644
--- a/test/test_plugin_helper.py
+++ b/test/test_plugin_helper.py
@@ -19,6 +19,6 @@ def test_normalize_mac_preserves_wildcard():
def test_normalize_mac_preserves_internet_root():
- assert normalize_mac("internet") == "Internet"
- assert normalize_mac("Internet") == "Internet"
- assert normalize_mac("INTERNET") == "Internet"
+ assert normalize_mac("internet") == "internet"
+ assert normalize_mac("Internet") == "internet"
+ assert normalize_mac("INTERNET") == "internet"