mirror of
https://github.com/jokob-sk/NetAlertX.git
synced 2026-03-30 23:03:03 -07:00
feat: Enhance plugin configurations and improve MAC normalization
This commit is contained in:
@@ -333,6 +333,68 @@
|
|||||||
"string": "Maximum time in seconds to wait for the script to finish. If this time is exceeded the script is aborted."
|
"string": "Maximum time in seconds to wait for the script to finish. If this time is exceeded the script is aborted."
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"function": "SET_ALWAYS",
|
||||||
|
"type": {
|
||||||
|
"dataType": "array",
|
||||||
|
"elements": [
|
||||||
|
{
|
||||||
|
"elementType": "select",
|
||||||
|
"elementOptions": [{ "multiple": "true", "ordeable": "true" }],
|
||||||
|
"transformers": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"default_value": ["devMac", "devLastIP"],
|
||||||
|
"options": [
|
||||||
|
"devMac",
|
||||||
|
"devLastIP"
|
||||||
|
],
|
||||||
|
"localized": ["name", "description"],
|
||||||
|
"name": [
|
||||||
|
{
|
||||||
|
"language_code": "en_us",
|
||||||
|
"string": "Set always columns"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": [
|
||||||
|
{
|
||||||
|
"language_code": "en_us",
|
||||||
|
"string": "These columns are always overwritten by this plugin, unless the user locks or overwrites the value."
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"function": "SET_EMPTY",
|
||||||
|
"type": {
|
||||||
|
"dataType": "array",
|
||||||
|
"elements": [
|
||||||
|
{
|
||||||
|
"elementType": "select",
|
||||||
|
"elementOptions": [{ "multiple": "true", "ordeable": "true" }],
|
||||||
|
"transformers": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"default_value": [],
|
||||||
|
"options": [
|
||||||
|
"devMac",
|
||||||
|
"devLastIP"
|
||||||
|
],
|
||||||
|
"localized": ["name", "description"],
|
||||||
|
"name": [
|
||||||
|
{
|
||||||
|
"language_code": "en_us",
|
||||||
|
"string": "Set empty columns"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": [
|
||||||
|
{
|
||||||
|
"language_code": "en_us",
|
||||||
|
"string": "These columns are only overwritten if they are empty or set to NEWDEV."
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"database_column_definitions": [
|
"database_column_definitions": [
|
||||||
|
|||||||
@@ -307,6 +307,68 @@
|
|||||||
"string": "Some devices don't have a MAC assigned. Enabling the FAKE_MAC setting generates a fake MAC address from the IP address to track devices, but it may cause inconsistencies if IPs change or devices are re-discovered with a different MAC. Static IPs are recommended. Device type and icon might not be detected correctly and some plugins might fail if they depend on a valid MAC address. When unchecked, devices with empty MAC addresses are skipped."
|
"string": "Some devices don't have a MAC assigned. Enabling the FAKE_MAC setting generates a fake MAC address from the IP address to track devices, but it may cause inconsistencies if IPs change or devices are re-discovered with a different MAC. Static IPs are recommended. Device type and icon might not be detected correctly and some plugins might fail if they depend on a valid MAC address. When unchecked, devices with empty MAC addresses are skipped."
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"function": "SET_ALWAYS",
|
||||||
|
"type": {
|
||||||
|
"dataType": "array",
|
||||||
|
"elements": [
|
||||||
|
{
|
||||||
|
"elementType": "select",
|
||||||
|
"elementOptions": [{ "multiple": "true", "ordeable": "true" }],
|
||||||
|
"transformers": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"default_value": ["devMac", "devLastIP"],
|
||||||
|
"options": [
|
||||||
|
"devMac",
|
||||||
|
"devLastIP"
|
||||||
|
],
|
||||||
|
"localized": ["name", "description"],
|
||||||
|
"name": [
|
||||||
|
{
|
||||||
|
"language_code": "en_us",
|
||||||
|
"string": "Set always columns"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": [
|
||||||
|
{
|
||||||
|
"language_code": "en_us",
|
||||||
|
"string": "These columns are always overwritten by this plugin, unless the user locks or overwrites the value."
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"function": "SET_EMPTY",
|
||||||
|
"type": {
|
||||||
|
"dataType": "array",
|
||||||
|
"elements": [
|
||||||
|
{
|
||||||
|
"elementType": "select",
|
||||||
|
"elementOptions": [{ "multiple": "true", "ordeable": "true" }],
|
||||||
|
"transformers": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"default_value": [],
|
||||||
|
"options": [
|
||||||
|
"devMac",
|
||||||
|
"devLastIP"
|
||||||
|
],
|
||||||
|
"localized": ["name", "description"],
|
||||||
|
"name": [
|
||||||
|
{
|
||||||
|
"language_code": "en_us",
|
||||||
|
"string": "Set empty columns"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": [
|
||||||
|
{
|
||||||
|
"language_code": "en_us",
|
||||||
|
"string": "These columns are only overwritten if they are empty or set to NEWDEV."
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"database_column_definitions": [
|
"database_column_definitions": [
|
||||||
|
|||||||
@@ -274,7 +274,7 @@
|
|||||||
"elements": [
|
"elements": [
|
||||||
{
|
{
|
||||||
"elementType": "select",
|
"elementType": "select",
|
||||||
"elementOptions": [{ "multiple": "true", "orderable": "true" }],
|
"elementOptions": [{ "multiple": "true", "ordeable": "true"}],
|
||||||
"transformers": []
|
"transformers": []
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@@ -305,7 +305,7 @@
|
|||||||
"elements": [
|
"elements": [
|
||||||
{
|
{
|
||||||
"elementType": "select",
|
"elementType": "select",
|
||||||
"elementOptions": [{ "multiple": "true", "orderable": "true" }],
|
"elementOptions": [{ "multiple": "true", "ordeable": "true" }],
|
||||||
"transformers": []
|
"transformers": []
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -135,7 +135,7 @@
|
|||||||
"elements": [
|
"elements": [
|
||||||
{
|
{
|
||||||
"elementType": "select",
|
"elementType": "select",
|
||||||
"elementOptions": [{ "multiple": "true"}],
|
"elementOptions": [{ "multiple": "true", "ordeable": "true"}],
|
||||||
"transformers": []
|
"transformers": []
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@@ -166,7 +166,7 @@
|
|||||||
"elements": [
|
"elements": [
|
||||||
{
|
{
|
||||||
"elementType": "select",
|
"elementType": "select",
|
||||||
"elementOptions": [{ "multiple": "true"}],
|
"elementOptions": [{ "multiple": "true", "ordeable": "true"}],
|
||||||
"transformers": []
|
"transformers": []
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -89,7 +89,7 @@
|
|||||||
"elements": [
|
"elements": [
|
||||||
{
|
{
|
||||||
"elementType": "select",
|
"elementType": "select",
|
||||||
"elementOptions": [{ "multiple": "true"}],
|
"elementOptions": [{ "multiple": "true", "ordeable": "true"}],
|
||||||
"transformers": []
|
"transformers": []
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@@ -119,7 +119,7 @@
|
|||||||
"elements": [
|
"elements": [
|
||||||
{
|
{
|
||||||
"elementType": "select",
|
"elementType": "select",
|
||||||
"elementOptions": [{ "multiple": "true"}],
|
"elementOptions": [{ "multiple": "true", "ordeable": "true"}],
|
||||||
"transformers": []
|
"transformers": []
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -689,7 +689,7 @@
|
|||||||
"elements": [
|
"elements": [
|
||||||
{
|
{
|
||||||
"elementType": "select",
|
"elementType": "select",
|
||||||
"elementOptions": [{ "multiple": "true", "orderable": "true" }],
|
"elementOptions": [{ "multiple": "true", "ordeable": "true"}],
|
||||||
"transformers": []
|
"transformers": []
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@@ -720,7 +720,7 @@
|
|||||||
"elements": [
|
"elements": [
|
||||||
{
|
{
|
||||||
"elementType": "select",
|
"elementType": "select",
|
||||||
"elementOptions": [{ "multiple": "true", "orderable": "true" }],
|
"elementOptions": [{ "multiple": "true", "ordeable": "true" }],
|
||||||
"transformers": []
|
"transformers": []
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -90,7 +90,7 @@
|
|||||||
"elements": [
|
"elements": [
|
||||||
{
|
{
|
||||||
"elementType": "select",
|
"elementType": "select",
|
||||||
"elementOptions": [{ "multiple": "true", "orderable": "true" }],
|
"elementOptions": [{ "multiple": "true", "ordeable": "true"}],
|
||||||
"transformers": []
|
"transformers": []
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@@ -121,7 +121,7 @@
|
|||||||
"elements": [
|
"elements": [
|
||||||
{
|
{
|
||||||
"elementType": "select",
|
"elementType": "select",
|
||||||
"elementOptions": [{ "multiple": "true", "orderable": "true" }],
|
"elementOptions": [{ "multiple": "true", "ordeable": "true" }],
|
||||||
"transformers": []
|
"transformers": []
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -303,6 +303,68 @@
|
|||||||
"string": "Maximum time in seconds to wait for the script to finish. If this time is exceeded the script is aborted."
|
"string": "Maximum time in seconds to wait for the script to finish. If this time is exceeded the script is aborted."
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"function": "SET_ALWAYS",
|
||||||
|
"type": {
|
||||||
|
"dataType": "array",
|
||||||
|
"elements": [
|
||||||
|
{
|
||||||
|
"elementType": "select",
|
||||||
|
"elementOptions": [{ "multiple": "true", "ordeable": "true" }],
|
||||||
|
"transformers": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"default_value": ["devMac", "devLastIP"],
|
||||||
|
"options": [
|
||||||
|
"devMac",
|
||||||
|
"devLastIP"
|
||||||
|
],
|
||||||
|
"localized": ["name", "description"],
|
||||||
|
"name": [
|
||||||
|
{
|
||||||
|
"language_code": "en_us",
|
||||||
|
"string": "Set always columns"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": [
|
||||||
|
{
|
||||||
|
"language_code": "en_us",
|
||||||
|
"string": "These columns are always overwritten by this plugin, unless the user locks or overwrites the value."
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"function": "SET_EMPTY",
|
||||||
|
"type": {
|
||||||
|
"dataType": "array",
|
||||||
|
"elements": [
|
||||||
|
{
|
||||||
|
"elementType": "select",
|
||||||
|
"elementOptions": [{ "multiple": "true", "ordeable": "true" }],
|
||||||
|
"transformers": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"default_value": [],
|
||||||
|
"options": [
|
||||||
|
"devMac",
|
||||||
|
"devLastIP"
|
||||||
|
],
|
||||||
|
"localized": ["name", "description"],
|
||||||
|
"name": [
|
||||||
|
{
|
||||||
|
"language_code": "en_us",
|
||||||
|
"string": "Set empty columns"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": [
|
||||||
|
{
|
||||||
|
"language_code": "en_us",
|
||||||
|
"string": "These columns are only overwritten if they are empty or set to NEWDEV."
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"database_column_definitions": [
|
"database_column_definitions": [
|
||||||
|
|||||||
@@ -296,6 +296,68 @@
|
|||||||
"string": "Maximum time in seconds to wait for the script to finish. If this time is exceeded the script is aborted."
|
"string": "Maximum time in seconds to wait for the script to finish. If this time is exceeded the script is aborted."
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"function": "SET_ALWAYS",
|
||||||
|
"type": {
|
||||||
|
"dataType": "array",
|
||||||
|
"elements": [
|
||||||
|
{
|
||||||
|
"elementType": "select",
|
||||||
|
"elementOptions": [{ "multiple": "true", "ordeable": "true" }],
|
||||||
|
"transformers": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"default_value": ["devMac", "devLastIP"],
|
||||||
|
"options": [
|
||||||
|
"devMac",
|
||||||
|
"devLastIP"
|
||||||
|
],
|
||||||
|
"localized": ["name", "description"],
|
||||||
|
"name": [
|
||||||
|
{
|
||||||
|
"language_code": "en_us",
|
||||||
|
"string": "Set always columns"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": [
|
||||||
|
{
|
||||||
|
"language_code": "en_us",
|
||||||
|
"string": "These columns are always overwritten by this plugin, unless the user locks or overwrites the value."
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"function": "SET_EMPTY",
|
||||||
|
"type": {
|
||||||
|
"dataType": "array",
|
||||||
|
"elements": [
|
||||||
|
{
|
||||||
|
"elementType": "select",
|
||||||
|
"elementOptions": [{ "multiple": "true", "ordeable": "true" }],
|
||||||
|
"transformers": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"default_value": [],
|
||||||
|
"options": [
|
||||||
|
"devMac",
|
||||||
|
"devLastIP"
|
||||||
|
],
|
||||||
|
"localized": ["name", "description"],
|
||||||
|
"name": [
|
||||||
|
{
|
||||||
|
"language_code": "en_us",
|
||||||
|
"string": "Set empty columns"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": [
|
||||||
|
{
|
||||||
|
"language_code": "en_us",
|
||||||
|
"string": "These columns are only overwritten if they are empty or set to NEWDEV."
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"database_column_definitions": [
|
"database_column_definitions": [
|
||||||
|
|||||||
@@ -410,6 +410,68 @@
|
|||||||
"string": "Benachrichtige nur bei diesen Status. <code>new</code> bedeutet ein neues eindeutiges (einzigartige Kombination aus PrimaryId und SecondaryId) Objekt wurde erkennt. <code>watched-changed</code> bedeutet eine ausgewählte <code>Watched_ValueN</code>-Spalte hat sich geändert."
|
"string": "Benachrichtige nur bei diesen Status. <code>new</code> bedeutet ein neues eindeutiges (einzigartige Kombination aus PrimaryId und SecondaryId) Objekt wurde erkennt. <code>watched-changed</code> bedeutet eine ausgewählte <code>Watched_ValueN</code>-Spalte hat sich geändert."
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"function": "SET_ALWAYS",
|
||||||
|
"type": {
|
||||||
|
"dataType": "array",
|
||||||
|
"elements": [
|
||||||
|
{
|
||||||
|
"elementType": "select",
|
||||||
|
"elementOptions": [{ "multiple": "true", "ordeable": "true" }],
|
||||||
|
"transformers": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"default_value": ["devMac", "devLastIP"],
|
||||||
|
"options": [
|
||||||
|
"devMac",
|
||||||
|
"devLastIP"
|
||||||
|
],
|
||||||
|
"localized": ["name", "description"],
|
||||||
|
"name": [
|
||||||
|
{
|
||||||
|
"language_code": "en_us",
|
||||||
|
"string": "Set always columns"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": [
|
||||||
|
{
|
||||||
|
"language_code": "en_us",
|
||||||
|
"string": "These columns are always overwritten by this plugin, unless the user locks or overwrites the value."
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"function": "SET_EMPTY",
|
||||||
|
"type": {
|
||||||
|
"dataType": "array",
|
||||||
|
"elements": [
|
||||||
|
{
|
||||||
|
"elementType": "select",
|
||||||
|
"elementOptions": [{ "multiple": "true", "ordeable": "true" }],
|
||||||
|
"transformers": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"default_value": [],
|
||||||
|
"options": [
|
||||||
|
"devMac",
|
||||||
|
"devLastIP"
|
||||||
|
],
|
||||||
|
"localized": ["name", "description"],
|
||||||
|
"name": [
|
||||||
|
{
|
||||||
|
"language_code": "en_us",
|
||||||
|
"string": "Set empty columns"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": [
|
||||||
|
{
|
||||||
|
"language_code": "en_us",
|
||||||
|
"string": "These columns are only overwritten if they are empty or set to NEWDEV."
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"database_column_definitions": [
|
"database_column_definitions": [
|
||||||
|
|||||||
@@ -138,7 +138,7 @@
|
|||||||
"elements": [
|
"elements": [
|
||||||
{
|
{
|
||||||
"elementType": "select",
|
"elementType": "select",
|
||||||
"elementOptions": [{ "multiple": "true", "orderable": "true" }],
|
"elementOptions": [{ "multiple": "true", "ordeable": "true" }],
|
||||||
"transformers": []
|
"transformers": []
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@@ -169,7 +169,7 @@
|
|||||||
"elements": [
|
"elements": [
|
||||||
{
|
{
|
||||||
"elementType": "select",
|
"elementType": "select",
|
||||||
"elementOptions": [{ "multiple": "true", "orderable": "true" }],
|
"elementOptions": [{ "multiple": "true", "ordeable": "true" }],
|
||||||
"transformers": []
|
"transformers": []
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -391,7 +391,7 @@
|
|||||||
"elements": [
|
"elements": [
|
||||||
{
|
{
|
||||||
"elementType": "select",
|
"elementType": "select",
|
||||||
"elementOptions": [{ "multiple": "true", "orderable": "true" }],
|
"elementOptions": [{ "multiple": "true", "ordeable": "true" }],
|
||||||
"transformers": []
|
"transformers": []
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@@ -422,7 +422,7 @@
|
|||||||
"elements": [
|
"elements": [
|
||||||
{
|
{
|
||||||
"elementType": "select",
|
"elementType": "select",
|
||||||
"elementOptions": [{ "multiple": "true", "orderable": "true" }],
|
"elementOptions": [{ "multiple": "true", "ordeable": "true" }],
|
||||||
"transformers": []
|
"transformers": []
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -267,6 +267,68 @@
|
|||||||
"string": "Password for Mikrotik Router"
|
"string": "Password for Mikrotik Router"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"function": "SET_ALWAYS",
|
||||||
|
"type": {
|
||||||
|
"dataType": "array",
|
||||||
|
"elements": [
|
||||||
|
{
|
||||||
|
"elementType": "select",
|
||||||
|
"elementOptions": [{ "multiple": "true", "ordeable": "true" }],
|
||||||
|
"transformers": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"default_value": ["devMac", "devLastIP"],
|
||||||
|
"options": [
|
||||||
|
"devMac",
|
||||||
|
"devLastIP"
|
||||||
|
],
|
||||||
|
"localized": ["name", "description"],
|
||||||
|
"name": [
|
||||||
|
{
|
||||||
|
"language_code": "en_us",
|
||||||
|
"string": "Set always columns"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": [
|
||||||
|
{
|
||||||
|
"language_code": "en_us",
|
||||||
|
"string": "These columns are always overwritten by this plugin, unless the user locks or overwrites the value."
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"function": "SET_EMPTY",
|
||||||
|
"type": {
|
||||||
|
"dataType": "array",
|
||||||
|
"elements": [
|
||||||
|
{
|
||||||
|
"elementType": "select",
|
||||||
|
"elementOptions": [{ "multiple": "true", "ordeable": "true" }],
|
||||||
|
"transformers": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"default_value": [],
|
||||||
|
"options": [
|
||||||
|
"devMac",
|
||||||
|
"devLastIP"
|
||||||
|
],
|
||||||
|
"localized": ["name", "description"],
|
||||||
|
"name": [
|
||||||
|
{
|
||||||
|
"language_code": "en_us",
|
||||||
|
"string": "Set empty columns"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": [
|
||||||
|
{
|
||||||
|
"language_code": "en_us",
|
||||||
|
"string": "These columns are only overwritten if they are empty or set to NEWDEV."
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"database_column_definitions": [
|
"database_column_definitions": [
|
||||||
|
|||||||
@@ -89,7 +89,7 @@
|
|||||||
"elements": [
|
"elements": [
|
||||||
{
|
{
|
||||||
"elementType": "select",
|
"elementType": "select",
|
||||||
"elementOptions": [{ "multiple": "true", "orderable": "true" }],
|
"elementOptions": [{ "multiple": "true", "ordeable": "true" }],
|
||||||
"transformers": []
|
"transformers": []
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@@ -119,7 +119,7 @@
|
|||||||
"elements": [
|
"elements": [
|
||||||
{
|
{
|
||||||
"elementType": "select",
|
"elementType": "select",
|
||||||
"elementOptions": [{ "multiple": "true", "orderable": "true" }],
|
"elementOptions": [{ "multiple": "true", "ordeable": "true" }],
|
||||||
"transformers": []
|
"transformers": []
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -451,6 +451,68 @@
|
|||||||
"string": "When scanning remote networks, NMAP can only retrieve the IP address, not the MAC address. Enabling the FAKE_MAC setting generates a fake MAC address from the IP address to track devices, but it may cause inconsistencies if IPs change or devices are re-discovered with a different MAC. Static IPs are recommended. Device type and icon might not be detected correctly and some plugins might fail if they depend on a valid MAC address. When unchecked, devices with empty MAC addresses are skipped."
|
"string": "When scanning remote networks, NMAP can only retrieve the IP address, not the MAC address. Enabling the FAKE_MAC setting generates a fake MAC address from the IP address to track devices, but it may cause inconsistencies if IPs change or devices are re-discovered with a different MAC. Static IPs are recommended. Device type and icon might not be detected correctly and some plugins might fail if they depend on a valid MAC address. When unchecked, devices with empty MAC addresses are skipped."
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"function": "SET_ALWAYS",
|
||||||
|
"type": {
|
||||||
|
"dataType": "array",
|
||||||
|
"elements": [
|
||||||
|
{
|
||||||
|
"elementType": "select",
|
||||||
|
"elementOptions": [{ "multiple": "true", "ordeable": "true" }],
|
||||||
|
"transformers": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"default_value": ["devMac", "devLastIP"],
|
||||||
|
"options": [
|
||||||
|
"devMac",
|
||||||
|
"devLastIP"
|
||||||
|
],
|
||||||
|
"localized": ["name", "description"],
|
||||||
|
"name": [
|
||||||
|
{
|
||||||
|
"language_code": "en_us",
|
||||||
|
"string": "Set always columns"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": [
|
||||||
|
{
|
||||||
|
"language_code": "en_us",
|
||||||
|
"string": "These columns are always overwritten by this plugin, unless the user locks or overwrites the value."
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"function": "SET_EMPTY",
|
||||||
|
"type": {
|
||||||
|
"dataType": "array",
|
||||||
|
"elements": [
|
||||||
|
{
|
||||||
|
"elementType": "select",
|
||||||
|
"elementOptions": [{ "multiple": "true", "ordeable": "true" }],
|
||||||
|
"transformers": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"default_value": [],
|
||||||
|
"options": [
|
||||||
|
"devMac",
|
||||||
|
"devLastIP"
|
||||||
|
],
|
||||||
|
"localized": ["name", "description"],
|
||||||
|
"name": [
|
||||||
|
{
|
||||||
|
"language_code": "en_us",
|
||||||
|
"string": "Set empty columns"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": [
|
||||||
|
{
|
||||||
|
"language_code": "en_us",
|
||||||
|
"string": "These columns are only overwritten if they are empty or set to NEWDEV."
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"database_column_definitions": [
|
"database_column_definitions": [
|
||||||
|
|||||||
@@ -89,7 +89,7 @@
|
|||||||
"elements": [
|
"elements": [
|
||||||
{
|
{
|
||||||
"elementType": "select",
|
"elementType": "select",
|
||||||
"elementOptions": [{ "multiple": "true"}],
|
"elementOptions": [{ "multiple": "true", "ordeable": "true"}],
|
||||||
"transformers": []
|
"transformers": []
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@@ -120,7 +120,7 @@
|
|||||||
"elements": [
|
"elements": [
|
||||||
{
|
{
|
||||||
"elementType": "select",
|
"elementType": "select",
|
||||||
"elementOptions": [{ "multiple": "true"}],
|
"elementOptions": [{ "multiple": "true", "ordeable": "true"}],
|
||||||
"transformers": []
|
"transformers": []
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -467,6 +467,68 @@
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"function": "SET_ALWAYS",
|
||||||
|
"type": {
|
||||||
|
"dataType": "array",
|
||||||
|
"elements": [
|
||||||
|
{
|
||||||
|
"elementType": "select",
|
||||||
|
"elementOptions": [{ "multiple": "true", "ordeable": "true" }],
|
||||||
|
"transformers": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"default_value": ["devMac", "devLastIP"],
|
||||||
|
"options": [
|
||||||
|
"devMac",
|
||||||
|
"devLastIP"
|
||||||
|
],
|
||||||
|
"localized": ["name", "description"],
|
||||||
|
"name": [
|
||||||
|
{
|
||||||
|
"language_code": "en_us",
|
||||||
|
"string": "Set always columns"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": [
|
||||||
|
{
|
||||||
|
"language_code": "en_us",
|
||||||
|
"string": "These columns are always overwritten by this plugin, unless the user locks or overwrites the value."
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"function": "SET_EMPTY",
|
||||||
|
"type": {
|
||||||
|
"dataType": "array",
|
||||||
|
"elements": [
|
||||||
|
{
|
||||||
|
"elementType": "select",
|
||||||
|
"elementOptions": [{ "multiple": "true", "ordeable": "true" }],
|
||||||
|
"transformers": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"default_value": [],
|
||||||
|
"options": [
|
||||||
|
"devMac",
|
||||||
|
"devLastIP"
|
||||||
|
],
|
||||||
|
"localized": ["name", "description"],
|
||||||
|
"name": [
|
||||||
|
{
|
||||||
|
"language_code": "en_us",
|
||||||
|
"string": "Set empty columns"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": [
|
||||||
|
{
|
||||||
|
"language_code": "en_us",
|
||||||
|
"string": "These columns are only overwritten if they are empty or set to NEWDEV."
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"database_column_definitions": [
|
"database_column_definitions": [
|
||||||
|
|||||||
@@ -440,6 +440,68 @@
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"function": "SET_ALWAYS",
|
||||||
|
"type": {
|
||||||
|
"dataType": "array",
|
||||||
|
"elements": [
|
||||||
|
{
|
||||||
|
"elementType": "select",
|
||||||
|
"elementOptions": [{ "multiple": "true", "ordeable": "true" }],
|
||||||
|
"transformers": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"default_value": ["devMac", "devLastIP"],
|
||||||
|
"options": [
|
||||||
|
"devMac",
|
||||||
|
"devLastIP"
|
||||||
|
],
|
||||||
|
"localized": ["name", "description"],
|
||||||
|
"name": [
|
||||||
|
{
|
||||||
|
"language_code": "en_us",
|
||||||
|
"string": "Set always columns"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": [
|
||||||
|
{
|
||||||
|
"language_code": "en_us",
|
||||||
|
"string": "These columns are always overwritten by this plugin, unless the user locks or overwrites the value."
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"function": "SET_EMPTY",
|
||||||
|
"type": {
|
||||||
|
"dataType": "array",
|
||||||
|
"elements": [
|
||||||
|
{
|
||||||
|
"elementType": "select",
|
||||||
|
"elementOptions": [{ "multiple": "true", "ordeable": "true" }],
|
||||||
|
"transformers": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"default_value": [],
|
||||||
|
"options": [
|
||||||
|
"devMac",
|
||||||
|
"devLastIP"
|
||||||
|
],
|
||||||
|
"localized": ["name", "description"],
|
||||||
|
"name": [
|
||||||
|
{
|
||||||
|
"language_code": "en_us",
|
||||||
|
"string": "Set empty columns"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": [
|
||||||
|
{
|
||||||
|
"language_code": "en_us",
|
||||||
|
"string": "These columns are only overwritten if they are empty or set to NEWDEV."
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"database_column_definitions": [
|
"database_column_definitions": [
|
||||||
|
|||||||
@@ -121,7 +121,7 @@
|
|||||||
"elements": [
|
"elements": [
|
||||||
{
|
{
|
||||||
"elementType": "select",
|
"elementType": "select",
|
||||||
"elementOptions": [{ "multiple": "true", "orderable": "true" }],
|
"elementOptions": [{ "multiple": "true", "ordeable": "true" }],
|
||||||
"transformers": []
|
"transformers": []
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@@ -154,7 +154,7 @@
|
|||||||
"elements": [
|
"elements": [
|
||||||
{
|
{
|
||||||
"elementType": "select",
|
"elementType": "select",
|
||||||
"elementOptions": [{ "multiple": "true", "orderable": "true" }],
|
"elementOptions": [{ "multiple": "true", "ordeable": "true" }],
|
||||||
"transformers": []
|
"transformers": []
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -223,7 +223,7 @@
|
|||||||
"elements": [
|
"elements": [
|
||||||
{
|
{
|
||||||
"elementType": "select",
|
"elementType": "select",
|
||||||
"elementOptions": [{ "multiple": "true"}],
|
"elementOptions": [{ "multiple": "true", "ordeable": "true"}],
|
||||||
"transformers": []
|
"transformers": []
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@@ -253,7 +253,7 @@
|
|||||||
"elements": [
|
"elements": [
|
||||||
{
|
{
|
||||||
"elementType": "select",
|
"elementType": "select",
|
||||||
"elementOptions": [{ "multiple": "true"}],
|
"elementOptions": [{ "multiple": "true", "ordeable": "true"}],
|
||||||
"transformers": []
|
"transformers": []
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -184,7 +184,12 @@ def normalize_mac(mac):
|
|||||||
:param mac: The MAC address to normalize.
|
:param mac: The MAC address to normalize.
|
||||||
:return: The normalized MAC address.
|
:return: The normalized MAC address.
|
||||||
"""
|
"""
|
||||||
s = str(mac).upper().strip()
|
s = str(mac).strip()
|
||||||
|
|
||||||
|
if s.lower() == "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:
|
if ':' in s:
|
||||||
|
|||||||
@@ -593,7 +593,7 @@
|
|||||||
"elements": [
|
"elements": [
|
||||||
{
|
{
|
||||||
"elementType": "select",
|
"elementType": "select",
|
||||||
"elementOptions": [{ "multiple": "true", "orderable": "true" }],
|
"elementOptions": [{ "multiple": "true", "ordeable": "true" }],
|
||||||
"transformers": []
|
"transformers": []
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@@ -624,7 +624,7 @@
|
|||||||
"elements": [
|
"elements": [
|
||||||
{
|
{
|
||||||
"elementType": "select",
|
"elementType": "select",
|
||||||
"elementOptions": [{ "multiple": "true", "orderable": "true" }],
|
"elementOptions": [{ "multiple": "true", "ordeable": "true" }],
|
||||||
"transformers": []
|
"transformers": []
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -859,6 +859,68 @@
|
|||||||
"string": "Status"
|
"string": "Status"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"function": "SET_ALWAYS",
|
||||||
|
"type": {
|
||||||
|
"dataType": "array",
|
||||||
|
"elements": [
|
||||||
|
{
|
||||||
|
"elementType": "select",
|
||||||
|
"elementOptions": [{ "multiple": "true", "ordeable": "true" }],
|
||||||
|
"transformers": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"default_value": ["devMac", "devLastIP"],
|
||||||
|
"options": [
|
||||||
|
"devMac",
|
||||||
|
"devLastIP"
|
||||||
|
],
|
||||||
|
"localized": ["name", "description"],
|
||||||
|
"name": [
|
||||||
|
{
|
||||||
|
"language_code": "en_us",
|
||||||
|
"string": "Set always columns"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": [
|
||||||
|
{
|
||||||
|
"language_code": "en_us",
|
||||||
|
"string": "These columns are always overwritten by this plugin, unless the user locks or overwrites the value."
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"function": "SET_EMPTY",
|
||||||
|
"type": {
|
||||||
|
"dataType": "array",
|
||||||
|
"elements": [
|
||||||
|
{
|
||||||
|
"elementType": "select",
|
||||||
|
"elementOptions": [{ "multiple": "true", "ordeable": "true" }],
|
||||||
|
"transformers": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"default_value": [],
|
||||||
|
"options": [
|
||||||
|
"devMac",
|
||||||
|
"devLastIP"
|
||||||
|
],
|
||||||
|
"localized": ["name", "description"],
|
||||||
|
"name": [
|
||||||
|
{
|
||||||
|
"language_code": "en_us",
|
||||||
|
"string": "Set empty columns"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": [
|
||||||
|
{
|
||||||
|
"language_code": "en_us",
|
||||||
|
"string": "These columns are only overwritten if they are empty or set to NEWDEV."
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -505,7 +505,7 @@
|
|||||||
"elements": [
|
"elements": [
|
||||||
{
|
{
|
||||||
"elementType": "select",
|
"elementType": "select",
|
||||||
"elementOptions": [{ "multiple": "true", "orderable": "true" }],
|
"elementOptions": [{ "multiple": "true", "ordeable": "true" }],
|
||||||
"transformers": []
|
"transformers": []
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@@ -538,7 +538,7 @@
|
|||||||
"elements": [
|
"elements": [
|
||||||
{
|
{
|
||||||
"elementType": "select",
|
"elementType": "select",
|
||||||
"elementOptions": [{ "multiple": "true", "orderable": "true" }],
|
"elementOptions": [{ "multiple": "true", "ordeable": "true" }],
|
||||||
"transformers": []
|
"transformers": []
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -923,7 +923,7 @@
|
|||||||
"elements": [
|
"elements": [
|
||||||
{
|
{
|
||||||
"elementType": "select",
|
"elementType": "select",
|
||||||
"elementOptions": [{ "multiple": "true", "orderable": "true" }],
|
"elementOptions": [{ "multiple": "true", "ordeable": "true" }],
|
||||||
"transformers": []
|
"transformers": []
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@@ -959,7 +959,7 @@
|
|||||||
"elements": [
|
"elements": [
|
||||||
{
|
{
|
||||||
"elementType": "select",
|
"elementType": "select",
|
||||||
"elementOptions": [{ "multiple": "true", "orderable": "true" }],
|
"elementOptions": [{ "multiple": "true", "ordeable": "true" }],
|
||||||
"transformers": []
|
"transformers": []
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -233,7 +233,7 @@
|
|||||||
"elements": [
|
"elements": [
|
||||||
{
|
{
|
||||||
"elementType": "select",
|
"elementType": "select",
|
||||||
"elementOptions": [{ "multiple": "true", "orderable": "true" }],
|
"elementOptions": [{ "multiple": "true", "ordeable": "true" }],
|
||||||
"transformers": []
|
"transformers": []
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@@ -265,7 +265,7 @@
|
|||||||
"elements": [
|
"elements": [
|
||||||
{
|
{
|
||||||
"elementType": "select",
|
"elementType": "select",
|
||||||
"elementOptions": [{ "multiple": "true", "orderable": "true" }],
|
"elementOptions": [{ "multiple": "true", "ordeable": "true" }],
|
||||||
"transformers": []
|
"transformers": []
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ from models.user_events_queue_instance import UserEventsQueueInstance # noqa: E
|
|||||||
|
|
||||||
from models.event_instance import EventInstance # noqa: E402 [flake8 lint suppression]
|
from models.event_instance import EventInstance # noqa: E402 [flake8 lint suppression]
|
||||||
# Import tool logic from the MCP/tools module to reuse behavior (no blueprints)
|
# Import tool logic from the MCP/tools module to reuse behavior (no blueprints)
|
||||||
from plugin_helper import is_mac # noqa: E402 [flake8 lint suppression]
|
from plugin_helper import is_mac, normalize_mac # noqa: E402 [flake8 lint suppression]
|
||||||
# is_mac is provided in mcp_endpoint and used by those handlers
|
# is_mac is provided in mcp_endpoint and used by those handlers
|
||||||
# mcp_endpoint contains helper functions; routes moved into this module to keep a single place for routes
|
# mcp_endpoint contains helper functions; routes moved into this module to keep a single place for routes
|
||||||
from messaging.in_app import ( # noqa: E402 [flake8 lint suppression]
|
from messaging.in_app import ( # noqa: E402 [flake8 lint suppression]
|
||||||
@@ -469,33 +469,28 @@ def api_device_field_lock(mac, payload=None):
|
|||||||
if not field_name:
|
if not field_name:
|
||||||
return jsonify({"success": False, "error": "fieldName is required"}), 400
|
return jsonify({"success": False, "error": "fieldName is required"}), 400
|
||||||
|
|
||||||
# Validate that the field can be locked
|
|
||||||
source_field = field_name + "Source"
|
|
||||||
allowed_tracked_fields = {
|
|
||||||
"devMac", "devName", "devLastIP", "devVendor", "devFQDN",
|
|
||||||
"devSSID", "devParentMAC", "devParentPort", "devParentRelType", "devVlan"
|
|
||||||
}
|
|
||||||
if field_name not in allowed_tracked_fields:
|
|
||||||
return jsonify({"success": False, "error": f"Field '{field_name}' cannot be locked"}), 400
|
|
||||||
|
|
||||||
device_handler = DeviceInstance()
|
device_handler = DeviceInstance()
|
||||||
|
normalized_mac = normalize_mac(mac)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# When locking: set source to LOCKED
|
if should_lock:
|
||||||
# When unlocking: check current value and let plugins take over
|
result = device_handler.lockDeviceField(normalized_mac, field_name)
|
||||||
new_source = "LOCKED" if should_lock else "NEWDEV"
|
action = "locked"
|
||||||
result = device_handler.updateDeviceColumn(mac, source_field, new_source)
|
|
||||||
|
|
||||||
if result.get("success"):
|
|
||||||
action = "locked" if should_lock else "unlocked"
|
|
||||||
return jsonify({
|
|
||||||
"success": True,
|
|
||||||
"message": f"Field {field_name} {action}",
|
|
||||||
"fieldName": field_name,
|
|
||||||
"locked": should_lock
|
|
||||||
})
|
|
||||||
else:
|
else:
|
||||||
return jsonify(result), 400
|
result = device_handler.unlockDeviceField(normalized_mac, field_name)
|
||||||
|
action = "unlocked"
|
||||||
|
|
||||||
|
response = dict(result)
|
||||||
|
response["fieldName"] = field_name
|
||||||
|
response["locked"] = should_lock
|
||||||
|
|
||||||
|
if response.get("success"):
|
||||||
|
response.setdefault("message", f"Field {field_name} {action}")
|
||||||
|
return jsonify(response)
|
||||||
|
|
||||||
|
if "does not support" in response.get("error", ""):
|
||||||
|
response["error"] = f"Field '{field_name}' cannot be {action}"
|
||||||
|
return jsonify(response), 400
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
mylog("none", f"Error locking field {field_name} for {mac}: {str(e)}")
|
mylog("none", f"Error locking field {field_name} for {mac}: {str(e)}")
|
||||||
return jsonify({"success": False, "error": str(e)}), 500
|
return jsonify({"success": False, "error": str(e)}), 500
|
||||||
|
|||||||
@@ -152,12 +152,20 @@ def enforce_source_on_user_update(devMac, updates_dict, conn):
|
|||||||
cur = conn.cursor()
|
cur = conn.cursor()
|
||||||
|
|
||||||
# Check if field has a corresponding source and should be updated
|
# Check if field has a corresponding source and should be updated
|
||||||
|
cur = conn.cursor()
|
||||||
|
try:
|
||||||
|
cur.execute("PRAGMA table_info(Devices)")
|
||||||
|
device_columns = {row["name"] for row in cur.fetchall()}
|
||||||
|
except Exception:
|
||||||
|
device_columns = set()
|
||||||
|
|
||||||
updates_to_apply = {}
|
updates_to_apply = {}
|
||||||
for field_name, new_value in updates_dict.items():
|
for field_name, new_value in updates_dict.items():
|
||||||
if field_name in FIELD_SOURCE_MAP:
|
if field_name in FIELD_SOURCE_MAP:
|
||||||
source_field = FIELD_SOURCE_MAP[field_name]
|
source_field = FIELD_SOURCE_MAP[field_name]
|
||||||
# User is updating this field, so mark it as USER
|
# User is updating this field, so mark it as USER
|
||||||
updates_to_apply[source_field] = "USER"
|
if not device_columns or source_field in device_columns:
|
||||||
|
updates_to_apply[source_field] = "USER"
|
||||||
|
|
||||||
if not updates_to_apply:
|
if not updates_to_apply:
|
||||||
return
|
return
|
||||||
@@ -198,6 +206,15 @@ def lock_field(devMac, field_name, conn):
|
|||||||
|
|
||||||
source_field = FIELD_SOURCE_MAP[field_name]
|
source_field = FIELD_SOURCE_MAP[field_name]
|
||||||
cur = conn.cursor()
|
cur = conn.cursor()
|
||||||
|
try:
|
||||||
|
cur.execute("PRAGMA table_info(Devices)")
|
||||||
|
device_columns = {row["name"] for row in cur.fetchall()}
|
||||||
|
except Exception:
|
||||||
|
device_columns = set()
|
||||||
|
|
||||||
|
if device_columns and source_field not in device_columns:
|
||||||
|
mylog("debug", [f"[lock_field] Source column {source_field} missing for {field_name}"])
|
||||||
|
return
|
||||||
|
|
||||||
sql = f"UPDATE Devices SET {source_field}='LOCKED' WHERE devMac = ?"
|
sql = f"UPDATE Devices SET {source_field}='LOCKED' WHERE devMac = ?"
|
||||||
|
|
||||||
@@ -227,6 +244,15 @@ def unlock_field(devMac, field_name, conn):
|
|||||||
|
|
||||||
source_field = FIELD_SOURCE_MAP[field_name]
|
source_field = FIELD_SOURCE_MAP[field_name]
|
||||||
cur = conn.cursor()
|
cur = conn.cursor()
|
||||||
|
try:
|
||||||
|
cur.execute("PRAGMA table_info(Devices)")
|
||||||
|
device_columns = {row["name"] for row in cur.fetchall()}
|
||||||
|
except Exception:
|
||||||
|
device_columns = set()
|
||||||
|
|
||||||
|
if device_columns and source_field not in device_columns:
|
||||||
|
mylog("debug", [f"[unlock_field] Source column {source_field} missing for {field_name}"])
|
||||||
|
return
|
||||||
|
|
||||||
# Unlock by resetting to empty (allows overwrite)
|
# Unlock by resetting to empty (allows overwrite)
|
||||||
sql = f"UPDATE Devices SET {source_field}='' WHERE devMac = ?"
|
sql = f"UPDATE Devices SET {source_field}='' WHERE devMac = ?"
|
||||||
|
|||||||
@@ -593,7 +593,6 @@ class DeviceInstance:
|
|||||||
conn = get_temp_db_connection()
|
conn = get_temp_db_connection()
|
||||||
cur = conn.cursor()
|
cur = conn.cursor()
|
||||||
cur.execute(sql, values)
|
cur.execute(sql, values)
|
||||||
conn.commit()
|
|
||||||
|
|
||||||
if data.get("createNew", False):
|
if data.get("createNew", False):
|
||||||
# Initialize source-tracking fields on device creation.
|
# Initialize source-tracking fields on device creation.
|
||||||
@@ -617,7 +616,6 @@ class DeviceInstance:
|
|||||||
source_values.append(normalized_mac)
|
source_values.append(normalized_mac)
|
||||||
source_sql = f"UPDATE Devices SET {set_clause} WHERE devMac = ?"
|
source_sql = f"UPDATE Devices SET {set_clause} WHERE devMac = ?"
|
||||||
cur.execute(source_sql, source_values)
|
cur.execute(source_sql, source_values)
|
||||||
conn.commit()
|
|
||||||
|
|
||||||
# Enforce source tracking on user updates
|
# Enforce source tracking on user updates
|
||||||
# User-updated fields should have their *Source set to "USER"
|
# User-updated fields should have their *Source set to "USER"
|
||||||
@@ -631,6 +629,8 @@ class DeviceInstance:
|
|||||||
conn.close()
|
conn.close()
|
||||||
return {"success": False, "error": f"Source tracking failed: {e}"}
|
return {"success": False, "error": f"Source tracking failed: {e}"}
|
||||||
|
|
||||||
|
# Commit all changes atomically after all operations succeed
|
||||||
|
conn.commit()
|
||||||
conn.close()
|
conn.close()
|
||||||
|
|
||||||
mylog("debug", f"[DeviceInstance] setDeviceData SQL: {sql.strip()}")
|
mylog("debug", f"[DeviceInstance] setDeviceData SQL: {sql.strip()}")
|
||||||
|
|||||||
@@ -545,12 +545,13 @@ def create_new_devices(db):
|
|||||||
# Derive primary IP family values
|
# Derive primary IP family values
|
||||||
cur_IP = str(cur_IP).strip() if cur_IP else ""
|
cur_IP = str(cur_IP).strip() if cur_IP else ""
|
||||||
cur_IP_normalized = check_IP_format(cur_IP) if ":" not in cur_IP else cur_IP
|
cur_IP_normalized = check_IP_format(cur_IP) if ":" not in cur_IP else cur_IP
|
||||||
|
|
||||||
# Validate IPv6 addresses using format_ip_long for consistency
|
# Validate IPv6 addresses using format_ip_long for consistency (do not store integer result)
|
||||||
if ":" in cur_IP_normalized:
|
if cur_IP_normalized and ":" in cur_IP_normalized:
|
||||||
validated_ipv6 = format_ip_long(cur_IP_normalized)
|
validated_ipv6 = format_ip_long(cur_IP_normalized)
|
||||||
cur_IP_normalized = validated_ipv6 if validated_ipv6 else ""
|
if validated_ipv6 is None or validated_ipv6 < 0:
|
||||||
|
cur_IP_normalized = ""
|
||||||
|
|
||||||
primary_ipv4 = cur_IP_normalized if cur_IP_normalized and ":" not in cur_IP_normalized else ""
|
primary_ipv4 = cur_IP_normalized if cur_IP_normalized and ":" not in cur_IP_normalized else ""
|
||||||
primary_ipv6 = cur_IP_normalized if cur_IP_normalized and ":" in cur_IP_normalized else ""
|
primary_ipv6 = cur_IP_normalized if cur_IP_normalized and ":" in cur_IP_normalized else ""
|
||||||
|
|
||||||
|
|||||||
@@ -115,6 +115,30 @@ class TestDeviceFieldLock:
|
|||||||
assert resp.status_code == 400
|
assert resp.status_code == 400
|
||||||
assert "cannot be locked" in resp.json.get("error", "")
|
assert "cannot be locked" in resp.json.get("error", "")
|
||||||
|
|
||||||
|
def test_lock_field_normalizes_mac(self, client, test_mac, auth_headers):
|
||||||
|
"""Lock endpoint should normalize MACs before applying locks."""
|
||||||
|
# Create device with normalized MAC
|
||||||
|
self.test_create_test_device(client, test_mac, auth_headers)
|
||||||
|
|
||||||
|
mac_variant = "aa-bb-cc-dd-ee-ff"
|
||||||
|
payload = {
|
||||||
|
"fieldName": "devName",
|
||||||
|
"lock": True
|
||||||
|
}
|
||||||
|
resp = client.post(
|
||||||
|
f"/device/{mac_variant}/field/lock",
|
||||||
|
json=payload,
|
||||||
|
headers=auth_headers
|
||||||
|
)
|
||||||
|
assert resp.status_code == 200, f"Failed to lock via normalized MAC: {resp.json}"
|
||||||
|
assert resp.json.get("locked") is True
|
||||||
|
|
||||||
|
# Verify source is LOCKED on normalized MAC
|
||||||
|
resp = client.get(f"/device/{test_mac}", headers=auth_headers)
|
||||||
|
assert resp.status_code == 200
|
||||||
|
device_data = resp.json
|
||||||
|
assert device_data.get("devNameSource") == "LOCKED"
|
||||||
|
|
||||||
def test_lock_all_tracked_fields(self, client, test_mac, auth_headers):
|
def test_lock_all_tracked_fields(self, client, test_mac, auth_headers):
|
||||||
"""Lock each tracked field individually."""
|
"""Lock each tracked field individually."""
|
||||||
# First create device
|
# First create device
|
||||||
|
|||||||
@@ -72,6 +72,103 @@ def ip_test_db():
|
|||||||
conn.close()
|
conn.close()
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def new_device_db():
|
||||||
|
"""Create an in-memory SQLite database for create_new_devices tests."""
|
||||||
|
conn = sqlite3.connect(":memory:")
|
||||||
|
conn.row_factory = sqlite3.Row
|
||||||
|
cur = conn.cursor()
|
||||||
|
|
||||||
|
cur.execute(
|
||||||
|
"""
|
||||||
|
CREATE TABLE Devices (
|
||||||
|
devMac TEXT PRIMARY KEY,
|
||||||
|
devName TEXT,
|
||||||
|
devVendor TEXT,
|
||||||
|
devLastIP TEXT,
|
||||||
|
devPrimaryIPv4 TEXT,
|
||||||
|
devPrimaryIPv6 TEXT,
|
||||||
|
devFirstConnection TEXT,
|
||||||
|
devLastConnection TEXT,
|
||||||
|
devSyncHubNode TEXT,
|
||||||
|
devGUID TEXT,
|
||||||
|
devParentMAC TEXT,
|
||||||
|
devParentPort TEXT,
|
||||||
|
devSite TEXT,
|
||||||
|
devSSID TEXT,
|
||||||
|
devType TEXT,
|
||||||
|
devSourcePlugin TEXT,
|
||||||
|
devAlertEvents INTEGER,
|
||||||
|
devAlertDown INTEGER,
|
||||||
|
devPresentLastScan INTEGER,
|
||||||
|
devIsArchived INTEGER,
|
||||||
|
devIsNew INTEGER,
|
||||||
|
devSkipRepeated INTEGER,
|
||||||
|
devScan INTEGER,
|
||||||
|
devOwner TEXT,
|
||||||
|
devFavorite INTEGER,
|
||||||
|
devGroup TEXT,
|
||||||
|
devComments TEXT,
|
||||||
|
devLogEvents INTEGER,
|
||||||
|
devLocation TEXT,
|
||||||
|
devCustomProps TEXT,
|
||||||
|
devParentRelType TEXT,
|
||||||
|
devReqNicsOnline INTEGER
|
||||||
|
)
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
|
cur.execute(
|
||||||
|
"""
|
||||||
|
CREATE TABLE CurrentScan (
|
||||||
|
cur_MAC TEXT,
|
||||||
|
cur_Name TEXT,
|
||||||
|
cur_Vendor TEXT,
|
||||||
|
cur_ScanMethod TEXT,
|
||||||
|
cur_IP TEXT,
|
||||||
|
cur_SyncHubNodeName TEXT,
|
||||||
|
cur_NetworkNodeMAC TEXT,
|
||||||
|
cur_PORT TEXT,
|
||||||
|
cur_NetworkSite TEXT,
|
||||||
|
cur_SSID TEXT,
|
||||||
|
cur_Type TEXT
|
||||||
|
)
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
|
cur.execute(
|
||||||
|
"""
|
||||||
|
CREATE TABLE Events (
|
||||||
|
eve_MAC TEXT,
|
||||||
|
eve_IP TEXT,
|
||||||
|
eve_DateTime TEXT,
|
||||||
|
eve_EventType TEXT,
|
||||||
|
eve_AdditionalInfo TEXT,
|
||||||
|
eve_PendingAlertEmail INTEGER
|
||||||
|
)
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
|
cur.execute(
|
||||||
|
"""
|
||||||
|
CREATE TABLE Sessions (
|
||||||
|
ses_MAC TEXT,
|
||||||
|
ses_IP TEXT,
|
||||||
|
ses_EventTypeConnection TEXT,
|
||||||
|
ses_DateTimeConnection TEXT,
|
||||||
|
ses_EventTypeDisconnection TEXT,
|
||||||
|
ses_DateTimeDisconnection TEXT,
|
||||||
|
ses_StillConnected INTEGER,
|
||||||
|
ses_AdditionalInfo TEXT
|
||||||
|
)
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
|
conn.commit()
|
||||||
|
yield conn
|
||||||
|
conn.close()
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def mock_ip_handlers():
|
def mock_ip_handlers():
|
||||||
"""Mock device_handling helper functions."""
|
"""Mock device_handling helper functions."""
|
||||||
@@ -311,6 +408,55 @@ def test_invalid_ip_values_rejected(ip_test_db, mock_ip_handlers):
|
|||||||
), f"Invalid IP '{invalid_ip}' should not overwrite valid IPv4"
|
), f"Invalid IP '{invalid_ip}' should not overwrite valid IPv4"
|
||||||
|
|
||||||
|
|
||||||
|
def test_invalid_ipv6_rejected_on_create_new_devices(new_device_db):
|
||||||
|
"""Invalid IPv6 values should not be persisted when creating new devices."""
|
||||||
|
cur = new_device_db.cursor()
|
||||||
|
|
||||||
|
cur.execute(
|
||||||
|
"""
|
||||||
|
INSERT INTO CurrentScan (
|
||||||
|
cur_MAC, cur_Name, cur_Vendor, cur_ScanMethod, cur_IP,
|
||||||
|
cur_SyncHubNodeName, cur_NetworkNodeMAC, cur_PORT,
|
||||||
|
cur_NetworkSite, cur_SSID, cur_Type
|
||||||
|
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||||
|
""",
|
||||||
|
(
|
||||||
|
"AA:BB:CC:DD:EE:10",
|
||||||
|
"",
|
||||||
|
"Vendor",
|
||||||
|
"ARPSCAN",
|
||||||
|
"fe80::zz",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
new_device_db.commit()
|
||||||
|
|
||||||
|
db = Mock()
|
||||||
|
db.sql_connection = new_device_db
|
||||||
|
db.sql = cur
|
||||||
|
db.commitDB = Mock(side_effect=new_device_db.commit)
|
||||||
|
|
||||||
|
with patch("helper.get_setting_value", return_value=""), patch.object(
|
||||||
|
device_handling, "get_setting_value", return_value=""
|
||||||
|
):
|
||||||
|
device_handling.create_new_devices(db)
|
||||||
|
|
||||||
|
row = cur.execute(
|
||||||
|
"SELECT devLastIP, devPrimaryIPv4, devPrimaryIPv6 FROM Devices WHERE devMac = ?",
|
||||||
|
("AA:BB:CC:DD:EE:10",),
|
||||||
|
).fetchone()
|
||||||
|
|
||||||
|
assert row is not None, "Device should be created"
|
||||||
|
assert row["devLastIP"] == "", "Invalid IPv6 should not set devLastIP"
|
||||||
|
assert row["devPrimaryIPv4"] == "", "Invalid IPv6 should not set devPrimaryIPv4"
|
||||||
|
assert row["devPrimaryIPv6"] == "", "Invalid IPv6 should not set devPrimaryIPv6"
|
||||||
|
|
||||||
|
|
||||||
def test_ipv4_ipv6_mixed_in_multiple_scans(ip_test_db, mock_ip_handlers):
|
def test_ipv4_ipv6_mixed_in_multiple_scans(ip_test_db, mock_ip_handlers):
|
||||||
"""Multiple scans with different IP types should set both primary fields correctly."""
|
"""Multiple scans with different IP types should set both primary fields correctly."""
|
||||||
cur = ip_test_db.cursor()
|
cur = ip_test_db.cursor()
|
||||||
|
|||||||
231
test/test_device_atomicity.py
Normal file
231
test/test_device_atomicity.py
Normal file
@@ -0,0 +1,231 @@
|
|||||||
|
"""
|
||||||
|
Test for atomicity of device updates with source-tracking.
|
||||||
|
|
||||||
|
Verifies that:
|
||||||
|
1. If source-tracking fails, the device row is rolled back.
|
||||||
|
2. If source-tracking succeeds, device row and sources are both committed.
|
||||||
|
3. Database remains consistent in both scenarios.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
|
import sqlite3
|
||||||
|
import tempfile
|
||||||
|
import unittest
|
||||||
|
from unittest.mock import patch
|
||||||
|
|
||||||
|
|
||||||
|
# Add server and plugins to path
|
||||||
|
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', 'server'))
|
||||||
|
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', 'front', 'plugins'))
|
||||||
|
|
||||||
|
from models.device_instance import DeviceInstance # noqa: E402 [flake8 lint suppression]
|
||||||
|
from plugin_helper import normalize_mac # noqa: E402 [flake8 lint suppression]
|
||||||
|
|
||||||
|
|
||||||
|
class TestDeviceAtomicity(unittest.TestCase):
|
||||||
|
"""Test atomic transactions for device updates with source-tracking."""
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
"""Create an in-memory SQLite DB for testing."""
|
||||||
|
self.test_db = tempfile.NamedTemporaryFile(delete=False, suffix='.db')
|
||||||
|
self.test_db_path = self.test_db.name
|
||||||
|
self.test_db.close()
|
||||||
|
|
||||||
|
# Create minimal schema
|
||||||
|
conn = sqlite3.connect(self.test_db_path)
|
||||||
|
conn.row_factory = sqlite3.Row
|
||||||
|
cur = conn.cursor()
|
||||||
|
|
||||||
|
# Create Devices table with source-tracking columns
|
||||||
|
cur.execute("""
|
||||||
|
CREATE TABLE Devices (
|
||||||
|
devMac TEXT PRIMARY KEY,
|
||||||
|
devName TEXT,
|
||||||
|
devOwner TEXT,
|
||||||
|
devType TEXT,
|
||||||
|
devVendor TEXT,
|
||||||
|
devIcon TEXT,
|
||||||
|
devFavorite INTEGER DEFAULT 0,
|
||||||
|
devGroup TEXT,
|
||||||
|
devLocation TEXT,
|
||||||
|
devComments TEXT,
|
||||||
|
devParentMAC TEXT,
|
||||||
|
devParentPort TEXT,
|
||||||
|
devSSID TEXT,
|
||||||
|
devSite TEXT,
|
||||||
|
devStaticIP INTEGER DEFAULT 0,
|
||||||
|
devScan INTEGER DEFAULT 0,
|
||||||
|
devAlertEvents INTEGER DEFAULT 0,
|
||||||
|
devAlertDown INTEGER DEFAULT 0,
|
||||||
|
devParentRelType TEXT DEFAULT 'default',
|
||||||
|
devReqNicsOnline INTEGER DEFAULT 0,
|
||||||
|
devSkipRepeated INTEGER DEFAULT 0,
|
||||||
|
devIsNew INTEGER DEFAULT 0,
|
||||||
|
devIsArchived INTEGER DEFAULT 0,
|
||||||
|
devLastConnection TEXT,
|
||||||
|
devFirstConnection TEXT,
|
||||||
|
devLastIP TEXT,
|
||||||
|
devGUID TEXT,
|
||||||
|
devCustomProps TEXT,
|
||||||
|
devSourcePlugin TEXT,
|
||||||
|
devNameSource TEXT,
|
||||||
|
devTypeSource TEXT,
|
||||||
|
devVendorSource TEXT,
|
||||||
|
devIconSource TEXT,
|
||||||
|
devGroupSource TEXT,
|
||||||
|
devLocationSource TEXT,
|
||||||
|
devCommentsSource TEXT,
|
||||||
|
devMacSource TEXT
|
||||||
|
)
|
||||||
|
""")
|
||||||
|
conn.commit()
|
||||||
|
conn.close()
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
"""Clean up test database."""
|
||||||
|
if os.path.exists(self.test_db_path):
|
||||||
|
os.unlink(self.test_db_path)
|
||||||
|
|
||||||
|
def _get_test_db_connection(self):
|
||||||
|
"""Override database connection for testing."""
|
||||||
|
conn = sqlite3.connect(self.test_db_path)
|
||||||
|
conn.row_factory = sqlite3.Row
|
||||||
|
return conn
|
||||||
|
|
||||||
|
def test_create_new_device_atomicity(self):
|
||||||
|
"""
|
||||||
|
Test that device creation and source-tracking are atomic.
|
||||||
|
If source tracking fails, the device should not be created.
|
||||||
|
"""
|
||||||
|
device_instance = DeviceInstance()
|
||||||
|
test_mac = normalize_mac("aa:bb:cc:dd:ee:ff")
|
||||||
|
|
||||||
|
# Patch at module level where it's used
|
||||||
|
with patch('models.device_instance.get_temp_db_connection', self._get_test_db_connection):
|
||||||
|
# Create a new device
|
||||||
|
data = {
|
||||||
|
"createNew": True,
|
||||||
|
"devMac": test_mac,
|
||||||
|
"devName": "Test Device",
|
||||||
|
"devOwner": "John Doe",
|
||||||
|
"devType": "Laptop",
|
||||||
|
}
|
||||||
|
|
||||||
|
result = device_instance.setDeviceData(test_mac, data)
|
||||||
|
|
||||||
|
# Verify success
|
||||||
|
self.assertTrue(result["success"], f"Device creation failed: {result}")
|
||||||
|
|
||||||
|
# Verify device exists
|
||||||
|
conn = self._get_test_db_connection()
|
||||||
|
cur = conn.cursor()
|
||||||
|
cur.execute("SELECT * FROM Devices WHERE devMac = ?", (test_mac,))
|
||||||
|
device = cur.fetchone()
|
||||||
|
conn.close()
|
||||||
|
|
||||||
|
self.assertIsNotNone(device, "Device was not created")
|
||||||
|
self.assertEqual(device["devName"], "Test Device")
|
||||||
|
|
||||||
|
# Verify source tracking was set
|
||||||
|
self.assertEqual(device["devMacSource"], "NEWDEV")
|
||||||
|
self.assertEqual(device["devNameSource"], "NEWDEV")
|
||||||
|
|
||||||
|
def test_update_device_with_source_tracking_atomicity(self):
|
||||||
|
"""
|
||||||
|
Test that device update and source-tracking are atomic.
|
||||||
|
If source tracking fails, the device update should be rolled back.
|
||||||
|
"""
|
||||||
|
device_instance = DeviceInstance()
|
||||||
|
test_mac = normalize_mac("aa:bb:cc:dd:ee:ff")
|
||||||
|
|
||||||
|
# Create initial device
|
||||||
|
conn = self._get_test_db_connection()
|
||||||
|
cur = conn.cursor()
|
||||||
|
cur.execute("""
|
||||||
|
INSERT INTO Devices (
|
||||||
|
devMac, devName, devOwner, devType,
|
||||||
|
devNameSource, devTypeSource
|
||||||
|
) VALUES (?, ?, ?, ?, ?, ?)
|
||||||
|
""", (test_mac, "Old Name", "Old Owner", "Desktop", "PLUGIN", "PLUGIN"))
|
||||||
|
conn.commit()
|
||||||
|
conn.close()
|
||||||
|
|
||||||
|
# Patch database connection
|
||||||
|
with patch('models.device_instance.get_temp_db_connection', self._get_test_db_connection):
|
||||||
|
with patch('models.device_instance.enforce_source_on_user_update') as mock_enforce:
|
||||||
|
mock_enforce.return_value = None
|
||||||
|
data = {
|
||||||
|
"createNew": False,
|
||||||
|
"devMac": test_mac,
|
||||||
|
"devName": "New Name",
|
||||||
|
"devOwner": "New Owner",
|
||||||
|
}
|
||||||
|
|
||||||
|
result = device_instance.setDeviceData(test_mac, data)
|
||||||
|
|
||||||
|
# Verify success
|
||||||
|
self.assertTrue(result["success"], f"Device update failed: {result}")
|
||||||
|
|
||||||
|
# Verify device was updated
|
||||||
|
conn = self._get_test_db_connection()
|
||||||
|
cur = conn.cursor()
|
||||||
|
cur.execute("SELECT * FROM Devices WHERE devMac = ?", (test_mac,))
|
||||||
|
device = cur.fetchone()
|
||||||
|
conn.close()
|
||||||
|
|
||||||
|
self.assertEqual(device["devName"], "New Name")
|
||||||
|
self.assertEqual(device["devOwner"], "New Owner")
|
||||||
|
|
||||||
|
def test_source_tracking_failure_rolls_back_device(self):
|
||||||
|
"""
|
||||||
|
Test that if enforce_source_on_user_update fails, the entire
|
||||||
|
transaction is rolled back (device and sources).
|
||||||
|
"""
|
||||||
|
device_instance = DeviceInstance()
|
||||||
|
test_mac = normalize_mac("aa:bb:cc:dd:ee:ff")
|
||||||
|
|
||||||
|
# Create initial device
|
||||||
|
conn = self._get_test_db_connection()
|
||||||
|
cur = conn.cursor()
|
||||||
|
cur.execute("""
|
||||||
|
INSERT INTO Devices (
|
||||||
|
devMac, devName, devOwner, devType,
|
||||||
|
devNameSource, devTypeSource
|
||||||
|
) VALUES (?, ?, ?, ?, ?, ?)
|
||||||
|
""", (test_mac, "Original Name", "Original Owner", "Desktop", "PLUGIN", "PLUGIN"))
|
||||||
|
conn.commit()
|
||||||
|
conn.close()
|
||||||
|
|
||||||
|
# Patch database connection and mock source enforcement failure
|
||||||
|
with patch('models.device_instance.get_temp_db_connection', self._get_test_db_connection):
|
||||||
|
with patch('models.device_instance.enforce_source_on_user_update') as mock_enforce:
|
||||||
|
# Simulate source tracking failure
|
||||||
|
mock_enforce.side_effect = Exception("Source tracking error")
|
||||||
|
|
||||||
|
data = {
|
||||||
|
"createNew": False,
|
||||||
|
"devMac": test_mac,
|
||||||
|
"devName": "Failed Update",
|
||||||
|
"devOwner": "Failed Owner",
|
||||||
|
}
|
||||||
|
|
||||||
|
result = device_instance.setDeviceData(test_mac, data)
|
||||||
|
|
||||||
|
# Verify error response
|
||||||
|
self.assertFalse(result["success"])
|
||||||
|
self.assertIn("Source tracking failed", result["error"])
|
||||||
|
|
||||||
|
# Verify device was NOT updated (rollback successful)
|
||||||
|
conn = self._get_test_db_connection()
|
||||||
|
cur = conn.cursor()
|
||||||
|
cur.execute("SELECT * FROM Devices WHERE devMac = ?", (test_mac,))
|
||||||
|
device = cur.fetchone()
|
||||||
|
conn.close()
|
||||||
|
|
||||||
|
self.assertEqual(device["devName"], "Original Name", "Device should not have been updated on source tracking failure")
|
||||||
|
self.assertEqual(device["devOwner"], "Original Owner", "Device should not have been updated on source tracking failure")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
unittest.main()
|
||||||
@@ -16,3 +16,9 @@ def test_normalize_mac_preserves_wildcard():
|
|||||||
result = normalize_mac("aabbcc*")
|
result = normalize_mac("aabbcc*")
|
||||||
assert result == "AA:BB:CC:*", f"Expected 'AA:BB:CC:*' but got '{result}'"
|
assert result == "AA:BB:CC:*", f"Expected 'AA:BB:CC:*' but got '{result}'"
|
||||||
assert normalize_mac("aa:bb:cc:dd:ee:ff") == "AA:BB:CC:DD:EE:FF"
|
assert normalize_mac("aa:bb:cc:dd:ee:ff") == "AA:BB:CC:DD:EE:FF"
|
||||||
|
|
||||||
|
|
||||||
|
def test_normalize_mac_preserves_internet_root():
|
||||||
|
assert normalize_mac("internet") == "Internet"
|
||||||
|
assert normalize_mac("Internet") == "Internet"
|
||||||
|
assert normalize_mac("INTERNET") == "Internet"
|
||||||
|
|||||||
Reference in New Issue
Block a user