mirror of
https://github.com/jokob-sk/NetAlertX.git
synced 2026-04-15 22:51:37 -07:00
FIX: lowercase MAC normalization across project v0.1
Signed-off-by: jokob-sk <jokob.sk@gmail.com>
This commit is contained in:
+15
-77
@@ -42,14 +42,7 @@ h4 {
|
|||||||
.content-header > .breadcrumb > li > a {
|
.content-header > .breadcrumb > li > a {
|
||||||
color: #bec5cb;
|
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 > thead > tr.odd,
|
||||||
.table > tbody > tr.odd,
|
.table > tbody > tr.odd,
|
||||||
.table > tfoot > tr.odd {
|
.table > tfoot > tr.odd {
|
||||||
@@ -73,7 +66,6 @@ h4 {
|
|||||||
border: 1px solid #353c42;
|
border: 1px solid #353c42;
|
||||||
}
|
}
|
||||||
.dataTables_wrapper input[type="search"] {
|
.dataTables_wrapper input[type="search"] {
|
||||||
border-radius: 4px;
|
|
||||||
background-color: #353c42;
|
background-color: #353c42;
|
||||||
border: 0;
|
border: 0;
|
||||||
color: #bec5cb;
|
color: #bec5cb;
|
||||||
@@ -126,7 +118,6 @@ h4 {
|
|||||||
border-color: #3c8dbc;
|
border-color: #3c8dbc;
|
||||||
}
|
}
|
||||||
.sidebar-menu > li > .treeview-menu {
|
.sidebar-menu > li > .treeview-menu {
|
||||||
margin: 0 1px;
|
|
||||||
background-color: #32393e;
|
background-color: #32393e;
|
||||||
}
|
}
|
||||||
.sidebar a {
|
.sidebar a {
|
||||||
@@ -144,16 +135,13 @@ h4 {
|
|||||||
color: #fff;
|
color: #fff;
|
||||||
}
|
}
|
||||||
.sidebar-form {
|
.sidebar-form {
|
||||||
border-radius: 3px;
|
|
||||||
border: 1px solid #3e464c;
|
border: 1px solid #3e464c;
|
||||||
margin: 10px;
|
|
||||||
}
|
}
|
||||||
.sidebar-form input[type="text"],
|
.sidebar-form input[type="text"],
|
||||||
.sidebar-form .btn {
|
.sidebar-form .btn {
|
||||||
box-shadow: none;
|
box-shadow: none;
|
||||||
background-color: #3e464c;
|
background-color: #3e464c;
|
||||||
border: 1px solid transparent;
|
border: 1px solid transparent;
|
||||||
height: 35px;
|
|
||||||
}
|
}
|
||||||
.sidebar-form input[type="text"] {
|
.sidebar-form input[type="text"] {
|
||||||
color: #666;
|
color: #666;
|
||||||
@@ -207,20 +195,13 @@ h4 {
|
|||||||
.box > .box-header .btn {
|
.box > .box-header .btn {
|
||||||
color: #bec5cb;
|
color: #bec5cb;
|
||||||
}
|
}
|
||||||
.box.box-info,
|
|
||||||
.box.box-primary,
|
|
||||||
.box.box-success,
|
|
||||||
.box.box-warning,
|
|
||||||
.box.box-danger {
|
|
||||||
border-top-width: 3px;
|
|
||||||
}
|
|
||||||
.main-header .navbar {
|
.main-header .navbar {
|
||||||
background-color: #272c30;
|
background-color: #272c30;
|
||||||
}
|
}
|
||||||
.main-header .navbar .nav > li > a,
|
.main-header .navbar .nav > li > a,
|
||||||
.main-header .navbar .nav > li > .navbar-text {
|
.main-header .navbar .nav > li > .navbar-text {
|
||||||
color: #bec5cb;
|
color: #bec5cb;
|
||||||
max-height: 50px;
|
|
||||||
}
|
}
|
||||||
.main-header .navbar .nav > li > a:hover,
|
.main-header .navbar .nav > li > a:hover,
|
||||||
.main-header .navbar .nav > li > a:active,
|
.main-header .navbar .nav > li > a:active,
|
||||||
@@ -277,7 +258,6 @@ h4 {
|
|||||||
background: rgba(64, 72, 80, 0.666);
|
background: rgba(64, 72, 80, 0.666);
|
||||||
}
|
}
|
||||||
.nav-tabs-custom > .nav-tabs > li {
|
.nav-tabs-custom > .nav-tabs > li {
|
||||||
margin-right: 1px;
|
|
||||||
color: #bec5cb;
|
color: #bec5cb;
|
||||||
}
|
}
|
||||||
.nav-tabs-custom > .nav-tabs > li.active > a,
|
.nav-tabs-custom > .nav-tabs > li.active > a,
|
||||||
@@ -386,11 +366,8 @@ h4 {
|
|||||||
|
|
||||||
code,
|
code,
|
||||||
pre {
|
pre {
|
||||||
padding: 2px 4px;
|
|
||||||
font-size: 90%;
|
|
||||||
color: #bec5cb;
|
color: #bec5cb;
|
||||||
background-color: #353c42;
|
background-color: #353c42;
|
||||||
border-radius: 4px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Used in the Query Log table */
|
/* Used in the Query Log table */
|
||||||
@@ -456,7 +433,7 @@ td.highlight {
|
|||||||
/* Used by the long-term pages */
|
/* Used by the long-term pages */
|
||||||
.daterangepicker {
|
.daterangepicker {
|
||||||
background-color: #3e464c;
|
background-color: #3e464c;
|
||||||
border-radius: 4px;
|
|
||||||
border: 1px solid #353c42;
|
border: 1px solid #353c42;
|
||||||
}
|
}
|
||||||
.daterangepicker .ranges li:hover {
|
.daterangepicker .ranges li:hover {
|
||||||
@@ -467,7 +444,7 @@ td.highlight {
|
|||||||
}
|
}
|
||||||
.daterangepicker .calendar-table {
|
.daterangepicker .calendar-table {
|
||||||
background-color: #3e464c;
|
background-color: #3e464c;
|
||||||
border-radius: 4px;
|
|
||||||
border: 1px solid #353c42;
|
border: 1px solid #353c42;
|
||||||
}
|
}
|
||||||
.daterangepicker td.off,
|
.daterangepicker td.off,
|
||||||
@@ -535,7 +512,7 @@ textarea[readonly],
|
|||||||
.panel-body,
|
.panel-body,
|
||||||
.panel-default > .panel-heading {
|
.panel-default > .panel-heading {
|
||||||
background-color: #3e464c;
|
background-color: #3e464c;
|
||||||
border-radius: 4px;
|
|
||||||
border: 1px solid #353c42;
|
border: 1px solid #353c42;
|
||||||
color: #bec5cb;
|
color: #bec5cb;
|
||||||
}
|
}
|
||||||
@@ -568,23 +545,10 @@ input[type="password"]::-webkit-caps-lock-indicator {
|
|||||||
background-image: linear-gradient(to right, #114100 0%, #525200 100%);
|
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 */
|
/* Overlay box with spinners as shown during data collection for graphs */
|
||||||
.box .overlay,
|
.box .overlay,
|
||||||
.overlay-wrapper .overlay {
|
.overlay-wrapper .overlay {
|
||||||
z-index: 50;
|
|
||||||
background-color: rgba(53, 60, 66, 0.733);
|
background-color: rgba(53, 60, 66, 0.733);
|
||||||
border-radius: 3px;
|
|
||||||
}
|
}
|
||||||
.box .overlay > .fa,
|
.box .overlay > .fa,
|
||||||
.overlay-wrapper .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 {
|
.navbar-nav > .user-menu > .dropdown-menu > .user-footer {
|
||||||
background-color: #353c42bb;
|
background-color: #353c42bb;
|
||||||
padding: 10px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.modal-content {
|
.modal-content {
|
||||||
@@ -626,29 +589,29 @@ input[type="password"]::-webkit-caps-lock-indicator {
|
|||||||
/*** Additional fixes For UI ***/
|
/*** Additional fixes For UI ***/
|
||||||
.pa-small-box-aqua .inner {
|
.pa-small-box-aqua .inner {
|
||||||
background-color: rgb(45,108,133);
|
background-color: rgb(45,108,133);
|
||||||
border-top-left-radius: 10px;
|
|
||||||
border-top-right-radius: 10px;
|
|
||||||
}
|
}
|
||||||
.pa-small-box-green .inner {
|
.pa-small-box-green .inner {
|
||||||
background-color: rgb(31,76,46);
|
background-color: rgb(31,76,46);
|
||||||
border-top-left-radius: 10px;
|
|
||||||
border-top-right-radius: 10px;
|
|
||||||
}
|
}
|
||||||
.pa-small-box-yellow .inner {
|
.pa-small-box-yellow .inner {
|
||||||
background-color: rgb(151,104,37);
|
background-color: rgb(151,104,37);
|
||||||
border-top-left-radius: 10px;
|
|
||||||
border-top-right-radius: 10px;
|
|
||||||
}
|
}
|
||||||
.pa-small-box-red .inner {
|
.pa-small-box-red .inner {
|
||||||
background-color: rgb(120,50,38);
|
background-color: rgb(120,50,38);
|
||||||
border-top-left-radius: 10px;
|
|
||||||
border-top-right-radius: 10px;
|
|
||||||
}
|
}
|
||||||
.pa-small-box-gray .inner {
|
.pa-small-box-gray .inner {
|
||||||
background-color: #777;
|
background-color: #777;
|
||||||
/* color: rgba(20,20,20,30%); */
|
/* color: rgba(20,20,20,30%); */
|
||||||
border-top-left-radius: 10px;
|
|
||||||
border-top-right-radius: 10px;
|
|
||||||
}
|
}
|
||||||
.pa-small-box-gray .inner h3 {
|
.pa-small-box-gray .inner h3 {
|
||||||
color: #bbb;
|
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(1) {background: #272c30}
|
||||||
.db_tools_table_cell_b:nth-child(2) {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 {
|
.nav-tabs-custom > .nav-tabs > li:hover > a, .nav-tabs-custom > .nav-tabs > li.active:hover > a {
|
||||||
background-color: #272c30;
|
background-color: #272c30;
|
||||||
color: #bec5cb;
|
color: #bec5cb;
|
||||||
@@ -738,23 +692,7 @@ table.dataTable tbody tr.selected, table.dataTable tbody tr .selected
|
|||||||
color: #bec5cb;
|
color: #bec5cb;
|
||||||
background-color: #272c30;
|
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{
|
.nax_semitransparent-panel{
|
||||||
background-color: #000 !important;
|
background-color: #000 !important;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -76,9 +76,7 @@
|
|||||||
border: 1px solid #353c42;
|
border: 1px solid #353c42;
|
||||||
}
|
}
|
||||||
.dataTables_wrapper input[type="search"] {
|
.dataTables_wrapper input[type="search"] {
|
||||||
border-radius: 4px;
|
|
||||||
background-color: #353c42;
|
background-color: #353c42;
|
||||||
border: 0;
|
|
||||||
color: #bec5cb;
|
color: #bec5cb;
|
||||||
}
|
}
|
||||||
.dataTables_paginate .pagination li > a {
|
.dataTables_paginate .pagination li > a {
|
||||||
@@ -129,7 +127,6 @@
|
|||||||
border-color: #3c8dbc;
|
border-color: #3c8dbc;
|
||||||
}
|
}
|
||||||
.sidebar-menu > li > .treeview-menu {
|
.sidebar-menu > li > .treeview-menu {
|
||||||
margin: 0 1px;
|
|
||||||
background-color: #32393e;
|
background-color: #32393e;
|
||||||
}
|
}
|
||||||
.sidebar a {
|
.sidebar a {
|
||||||
@@ -147,23 +144,16 @@
|
|||||||
color: #fff;
|
color: #fff;
|
||||||
}
|
}
|
||||||
.sidebar-form {
|
.sidebar-form {
|
||||||
border-radius: 3px;
|
|
||||||
border: 1px solid #3e464c;
|
border: 1px solid #3e464c;
|
||||||
margin: 10px;
|
|
||||||
}
|
}
|
||||||
.sidebar-form input[type="text"],
|
.sidebar-form input[type="text"],
|
||||||
.sidebar-form .btn {
|
.sidebar-form .btn {
|
||||||
box-shadow: none;
|
box-shadow: none;
|
||||||
background-color: #3e464c;
|
background-color: #3e464c;
|
||||||
border: 1px solid transparent;
|
border: 1px solid transparent;
|
||||||
height: 35px;
|
|
||||||
}
|
}
|
||||||
.sidebar-form input[type="text"] {
|
.sidebar-form input[type="text"] {
|
||||||
color: #666;
|
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,
|
||||||
.sidebar-form input[type="text"]:focus + .input-group-btn .btn {
|
.sidebar-form input[type="text"]:focus + .input-group-btn .btn {
|
||||||
@@ -175,10 +165,6 @@
|
|||||||
}
|
}
|
||||||
.sidebar-form .btn {
|
.sidebar-form .btn {
|
||||||
color: #999;
|
color: #999;
|
||||||
border-top-left-radius: 0;
|
|
||||||
border-top-right-radius: 2px;
|
|
||||||
border-bottom-right-radius: 2px;
|
|
||||||
border-bottom-left-radius: 0;
|
|
||||||
}
|
}
|
||||||
.box,
|
.box,
|
||||||
.box-footer,
|
.box-footer,
|
||||||
@@ -210,20 +196,12 @@
|
|||||||
.box > .box-header .btn {
|
.box > .box-header .btn {
|
||||||
color: #bec5cb;
|
color: #bec5cb;
|
||||||
}
|
}
|
||||||
.box.box-info,
|
|
||||||
.box.box-primary,
|
|
||||||
.box.box-success,
|
|
||||||
.box.box-warning,
|
|
||||||
.box.box-danger {
|
|
||||||
border-top-width: 3px;
|
|
||||||
}
|
|
||||||
.main-header .navbar {
|
.main-header .navbar {
|
||||||
background-color: #272c30;
|
background-color: #272c30;
|
||||||
}
|
}
|
||||||
.main-header .navbar .nav > li > a,
|
.main-header .navbar .nav > li > a,
|
||||||
.main-header .navbar .nav > li > .navbar-text {
|
.main-header .navbar .nav > li > .navbar-text {
|
||||||
color: #bec5cb;
|
color: #bec5cb;
|
||||||
max-height: 50px;
|
|
||||||
}
|
}
|
||||||
.main-header .navbar .nav > li > a:hover,
|
.main-header .navbar .nav > li > a:hover,
|
||||||
.main-header .navbar .nav > li > a:active,
|
.main-header .navbar .nav > li > a:active,
|
||||||
@@ -280,7 +258,6 @@
|
|||||||
background: rgba(64, 72, 80, 0.666);
|
background: rgba(64, 72, 80, 0.666);
|
||||||
}
|
}
|
||||||
.nav-tabs-custom > .nav-tabs > li {
|
.nav-tabs-custom > .nav-tabs > li {
|
||||||
margin-right: 1px;
|
|
||||||
color: #bec5cb;
|
color: #bec5cb;
|
||||||
}
|
}
|
||||||
.nav-tabs-custom > .nav-tabs > li.active > a,
|
.nav-tabs-custom > .nav-tabs > li.active > a,
|
||||||
@@ -389,11 +366,8 @@
|
|||||||
|
|
||||||
code,
|
code,
|
||||||
pre {
|
pre {
|
||||||
padding: 2px 4px;
|
|
||||||
font-size: 90%;
|
|
||||||
color: #bec5cb;
|
color: #bec5cb;
|
||||||
background-color: #353c42;
|
background-color: #353c42;
|
||||||
border-radius: 4px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Used in the Query Log table */
|
/* Used in the Query Log table */
|
||||||
@@ -459,7 +433,6 @@
|
|||||||
/* Used by the long-term pages */
|
/* Used by the long-term pages */
|
||||||
.daterangepicker {
|
.daterangepicker {
|
||||||
background-color: #3e464c;
|
background-color: #3e464c;
|
||||||
border-radius: 4px;
|
|
||||||
border: 1px solid #353c42;
|
border: 1px solid #353c42;
|
||||||
}
|
}
|
||||||
.daterangepicker .ranges li:hover {
|
.daterangepicker .ranges li:hover {
|
||||||
@@ -470,7 +443,6 @@
|
|||||||
}
|
}
|
||||||
.daterangepicker .calendar-table {
|
.daterangepicker .calendar-table {
|
||||||
background-color: #3e464c;
|
background-color: #3e464c;
|
||||||
border-radius: 4px;
|
|
||||||
border: 1px solid #353c42;
|
border: 1px solid #353c42;
|
||||||
}
|
}
|
||||||
.daterangepicker td.off,
|
.daterangepicker td.off,
|
||||||
@@ -537,7 +509,6 @@
|
|||||||
.panel-body,
|
.panel-body,
|
||||||
.panel-default > .panel-heading {
|
.panel-default > .panel-heading {
|
||||||
background-color: #3e464c;
|
background-color: #3e464c;
|
||||||
border-radius: 4px;
|
|
||||||
border: 1px solid #353c42;
|
border: 1px solid #353c42;
|
||||||
color: #bec5cb;
|
color: #bec5cb;
|
||||||
}
|
}
|
||||||
@@ -570,23 +541,10 @@
|
|||||||
background-image: linear-gradient(to right, #114100 0%, #525200 100%);
|
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 */
|
/* Overlay box with spinners as shown during data collection for graphs */
|
||||||
.box .overlay,
|
.box .overlay,
|
||||||
.overlay-wrapper .overlay {
|
.overlay-wrapper .overlay {
|
||||||
z-index: 50;
|
|
||||||
background-color: rgba(53, 60, 66, 0.733);
|
background-color: rgba(53, 60, 66, 0.733);
|
||||||
border-radius: 3px;
|
|
||||||
}
|
}
|
||||||
.box .overlay > .fa,
|
.box .overlay > .fa,
|
||||||
.overlay-wrapper .overlay > .fa,
|
.overlay-wrapper .overlay > .fa,
|
||||||
@@ -596,7 +554,6 @@
|
|||||||
|
|
||||||
.navbar-nav > .user-menu > .dropdown-menu > .user-footer {
|
.navbar-nav > .user-menu > .dropdown-menu > .user-footer {
|
||||||
background-color: #353c42bb;
|
background-color: #353c42bb;
|
||||||
padding: 10px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.modal-content {
|
.modal-content {
|
||||||
@@ -625,36 +582,21 @@
|
|||||||
border-color: rgb(120, 127, 133);
|
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 {
|
.pa-small-box-aqua .inner {
|
||||||
background-color: rgb(45,108,133);
|
background-color: rgb(45,108,133);
|
||||||
border-top-left-radius: 10px;
|
|
||||||
border-top-right-radius: 10px;
|
|
||||||
}
|
}
|
||||||
.pa-small-box-green .inner {
|
.pa-small-box-green .inner {
|
||||||
background-color: rgb(31,76,46);
|
background-color: rgb(31,76,46);
|
||||||
border-top-left-radius: 10px;
|
|
||||||
border-top-right-radius: 10px;
|
|
||||||
}
|
}
|
||||||
.pa-small-box-yellow .inner {
|
.pa-small-box-yellow .inner {
|
||||||
background-color: rgb(151,104,37);
|
background-color: rgb(151,104,37);
|
||||||
border-top-left-radius: 10px;
|
|
||||||
border-top-right-radius: 10px;
|
|
||||||
}
|
}
|
||||||
.pa-small-box-red .inner {
|
.pa-small-box-red .inner {
|
||||||
background-color: rgb(120,50,38);
|
background-color: rgb(120,50,38);
|
||||||
border-top-left-radius: 10px;
|
|
||||||
border-top-right-radius: 10px;
|
|
||||||
}
|
}
|
||||||
.pa-small-box-gray .inner {
|
.pa-small-box-gray .inner {
|
||||||
background-color: #777;
|
background-color: #777;
|
||||||
/* color: rgba(20,20,20,30%); */
|
/* color: rgba(20,20,20,30%); */
|
||||||
border-top-left-radius: 10px;
|
|
||||||
border-top-right-radius: 10px;
|
|
||||||
}
|
}
|
||||||
.pa-small-box-gray .inner h3 {
|
.pa-small-box-gray .inner h3 {
|
||||||
color: #bbb;
|
color: #bbb;
|
||||||
@@ -693,15 +635,6 @@
|
|||||||
.db_tools_table_cell_b:nth-child(1) {background: #272c30}
|
.db_tools_table_cell_b:nth-child(1) {background: #272c30}
|
||||||
.db_tools_table_cell_b:nth-child(2) {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 {
|
.nav-tabs-custom > .nav-tabs > li:hover > a, .nav-tabs-custom > .nav-tabs > li.active:hover > a {
|
||||||
background-color: #272c30;
|
background-color: #272c30;
|
||||||
color: #bec5cb;
|
color: #bec5cb;
|
||||||
@@ -723,15 +656,6 @@
|
|||||||
color: white !important;
|
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 {
|
.login-page {
|
||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
}
|
}
|
||||||
@@ -744,23 +668,6 @@
|
|||||||
color: #bec5cb;
|
color: #bec5cb;
|
||||||
background-color: #272c30;
|
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{
|
.nax_semitransparent-panel{
|
||||||
background-color: #000 !important;
|
background-color: #000 !important;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -423,7 +423,7 @@ function setDeviceData(direction = '', refreshCallback = '') {
|
|||||||
|
|
||||||
// Build payload
|
// Build payload
|
||||||
const payload = {
|
const payload = {
|
||||||
devName: $('#NEWDEV_devName').val().replace(/'/g, "’"),
|
devName: $('#NEWDEV_devName').val(),
|
||||||
devOwner: $('#NEWDEV_devOwner').val().replace(/'/g, "’"),
|
devOwner: $('#NEWDEV_devOwner').val().replace(/'/g, "’"),
|
||||||
devType: $('#NEWDEV_devType').val().replace(/'/g, ""),
|
devType: $('#NEWDEV_devType').val().replace(/'/g, ""),
|
||||||
devVendor: $('#NEWDEV_devVendor').val().replace(/'/g, "’"),
|
devVendor: $('#NEWDEV_devVendor').val().replace(/'/g, "’"),
|
||||||
@@ -432,7 +432,7 @@ function setDeviceData(direction = '', refreshCallback = '') {
|
|||||||
devFavorite: ($('#NEWDEV_devFavorite')[0].checked * 1),
|
devFavorite: ($('#NEWDEV_devFavorite')[0].checked * 1),
|
||||||
devGroup: $('#NEWDEV_devGroup').val().replace(/'/g, "’"),
|
devGroup: $('#NEWDEV_devGroup').val().replace(/'/g, "’"),
|
||||||
devLocation: $('#NEWDEV_devLocation').val().replace(/'/g, "’"),
|
devLocation: $('#NEWDEV_devLocation').val().replace(/'/g, "’"),
|
||||||
devComments: encodeSpecialChars($('#NEWDEV_devComments').val()),
|
devComments: ($('#NEWDEV_devComments').val()),
|
||||||
|
|
||||||
devParentMAC: $('#NEWDEV_devParentMAC').val(),
|
devParentMAC: $('#NEWDEV_devParentMAC').val(),
|
||||||
devParentPort: $('#NEWDEV_devParentPort').val(),
|
devParentPort: $('#NEWDEV_devParentPort').val(),
|
||||||
|
|||||||
+24
-16
@@ -85,20 +85,24 @@
|
|||||||
// \
|
// \
|
||||||
// PC (leaf) <------- leafs are not included in this SQL query
|
// PC (leaf) <------- leafs are not included in this SQL query
|
||||||
const rawSql = `
|
const rawSql = `
|
||||||
SELECT node_name, node_mac, online, node_type, node_ports_count, parent_mac, node_icon, node_alert
|
SELECT
|
||||||
FROM (
|
parent.devName AS node_name,
|
||||||
SELECT a.devName as node_name, a.devMac as node_mac, a.devPresentLastScan as online,
|
parent.devMac AS node_mac,
|
||||||
a.devType as node_type, a.devParentMAC as parent_mac, a.devIcon as node_icon, a.devAlertDown as node_alert
|
parent.devPresentLastScan AS online,
|
||||||
FROM Devices a
|
parent.devType AS node_type,
|
||||||
WHERE a.devType IN (${networkDeviceTypes}) and a.devIsArchived = 0
|
parent.devParentMAC AS parent_mac,
|
||||||
) t1
|
parent.devIcon AS node_icon,
|
||||||
LEFT JOIN (
|
parent.devAlertDown AS node_alert,
|
||||||
SELECT b.devParentMAC as node_mac_2, count() as node_ports_count
|
COUNT(child.devMac) AS node_ports_count
|
||||||
FROM Devices b
|
FROM Devices AS parent
|
||||||
WHERE b.devParentMAC NOT NULL
|
LEFT JOIN Devices AS child
|
||||||
GROUP BY b.devParentMAC
|
ON child.devParentMAC = parent.devMac
|
||||||
) t2
|
WHERE parent.devType IN (
|
||||||
ON (t1.node_mac = t2.node_mac_2)
|
${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();
|
const apiBase = getApiBase();
|
||||||
@@ -378,6 +382,10 @@
|
|||||||
|
|
||||||
// ----------------------------------------------------
|
// ----------------------------------------------------
|
||||||
function loadConnectedDevices(node_mac) {
|
function loadConnectedDevices(node_mac) {
|
||||||
|
|
||||||
|
// 1. Force to lowercase to match the new DB standard
|
||||||
|
const normalized_mac = node_mac.toLowerCase();
|
||||||
|
|
||||||
const sql = `
|
const sql = `
|
||||||
SELECT devName, devMac, devLastIP, devVendor, devPresentLastScan, devAlertDown, devParentPort,
|
SELECT devName, devMac, devLastIP, devVendor, devPresentLastScan, devAlertDown, devParentPort,
|
||||||
CASE
|
CASE
|
||||||
@@ -389,12 +397,12 @@
|
|||||||
ELSE 'Unknown status'
|
ELSE 'Unknown status'
|
||||||
END AS devStatus
|
END AS devStatus
|
||||||
FROM Devices
|
FROM Devices
|
||||||
WHERE devParentMac = '${node_mac}'`;
|
WHERE devParentMac = '${normalized_mac}'`;
|
||||||
|
|
||||||
const id = node_mac.replace(/:/g, '_');
|
const id = node_mac.replace(/:/g, '_');
|
||||||
|
|
||||||
const wrapperHtml = `
|
const wrapperHtml = `
|
||||||
<table class="table table-bordered table-striped node-leafs-table " id="table_leafs_${id}" data-node-mac="${node_mac}">
|
<table class="table table-bordered table-striped node-leafs-table " id="table_leafs_${id}" data-node-mac="${normalized_mac}">
|
||||||
|
|
||||||
</table>`;
|
</table>`;
|
||||||
|
|
||||||
|
|||||||
@@ -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 = Plugin_Objects(RESULT_FILE)
|
||||||
# plugin_objects.add_object(
|
# plugin_objects.add_object(
|
||||||
# primaryId = 'Internet', # MAC (Device Name)
|
# primaryId = 'internet', # MAC (Device Name)
|
||||||
# secondaryId = new_internet_IP, # IP Address
|
# secondaryId = new_internet_IP, # IP Address
|
||||||
# watched1 = f'Previous IP: {PREV_IP}',
|
# watched1 = f'Previous IP: {PREV_IP}',
|
||||||
# watched2 = '',
|
# watched2 = '',
|
||||||
# watched3 = '',
|
# watched3 = '',
|
||||||
# watched4 = '',
|
# watched4 = '',
|
||||||
# extra = f'Previous IP: {PREV_IP}',
|
# extra = f'Previous IP: {PREV_IP}',
|
||||||
# foreignKey = 'Internet')
|
# foreignKey = 'internet')
|
||||||
|
|
||||||
# plugin_objects.write_result_file()
|
# plugin_objects.write_result_file()
|
||||||
|
|
||||||
|
|||||||
@@ -79,14 +79,14 @@ def main():
|
|||||||
plugin_objects = Plugin_Objects(RESULT_FILE)
|
plugin_objects = Plugin_Objects(RESULT_FILE)
|
||||||
|
|
||||||
plugin_objects.add_object(
|
plugin_objects.add_object(
|
||||||
primaryId = 'Internet', # MAC (Device Name)
|
primaryId = 'internet', # MAC (Device Name)
|
||||||
secondaryId = new_internet_IP, # IP Address
|
secondaryId = new_internet_IP, # IP Address
|
||||||
watched1 = f'Previous IP: {PREV_IP}',
|
watched1 = f'Previous IP: {PREV_IP}',
|
||||||
watched2 = cmd_output.replace('\n', ''),
|
watched2 = cmd_output.replace('\n', ''),
|
||||||
watched3 = retries_needed,
|
watched3 = retries_needed,
|
||||||
watched4 = 'Gateway',
|
watched4 = 'Gateway',
|
||||||
extra = f'Previous IP: {PREV_IP}',
|
extra = f'Previous IP: {PREV_IP}',
|
||||||
foreignKey = 'Internet'
|
foreignKey = 'internet'
|
||||||
)
|
)
|
||||||
|
|
||||||
plugin_objects.write_result_file()
|
plugin_objects.write_result_file()
|
||||||
@@ -101,8 +101,8 @@ def main():
|
|||||||
# ===============================================================================
|
# ===============================================================================
|
||||||
def check_internet_IP(PREV_IP, DIG_GET_IP_ARG):
|
def check_internet_IP(PREV_IP, DIG_GET_IP_ARG):
|
||||||
|
|
||||||
# Get Internet IP
|
# Get internet IP
|
||||||
mylog('verbose', [f'[{pluginName}] - Retrieving Internet IP'])
|
mylog('verbose', [f'[{pluginName}] - Retrieving internet IP'])
|
||||||
internet_IP, cmd_output = get_internet_IP(DIG_GET_IP_ARG)
|
internet_IP, cmd_output = get_internet_IP(DIG_GET_IP_ARG)
|
||||||
|
|
||||||
mylog('verbose', [f'[{pluginName}] Current internet_IP : {internet_IP}'])
|
mylog('verbose', [f'[{pluginName}] Current internet_IP : {internet_IP}'])
|
||||||
|
|||||||
@@ -330,8 +330,8 @@ def main():
|
|||||||
myssid = device[PORT_SSID] if not device[PORT_SSID].isdigit() else ""
|
myssid = device[PORT_SSID] if not device[PORT_SSID].isdigit() else ""
|
||||||
ParentNetworkNode = (
|
ParentNetworkNode = (
|
||||||
ieee2ietf_mac_formater(device[SWITCH_AP])
|
ieee2ietf_mac_formater(device[SWITCH_AP])
|
||||||
if device[SWITCH_AP] != "Internet"
|
if device[SWITCH_AP].lower() != "internet"
|
||||||
else "Internet"
|
else "internet"
|
||||||
)
|
)
|
||||||
mymac = ieee2ietf_mac_formater(device[MAC])
|
mymac = ieee2ietf_mac_formater(device[MAC])
|
||||||
plugin_objects.add_object(
|
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"
|
device_data_bymac[default_router_mac][TYPE] = "Firewall"
|
||||||
# step2 let's find the first switch and set the default router parent to internet
|
# 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]
|
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:
|
# 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]=default_router_mac
|
||||||
# device_data_bymac[first_switch][SWITCH_AP][PORT_SSID] = '1'
|
# device_data_bymac[first_switch][SWITCH_AP][PORT_SSID] = '1'
|
||||||
|
|||||||
@@ -413,11 +413,11 @@ class OmadaData:
|
|||||||
|
|
||||||
OmadaHelper.verbose(f"Making entry for: {entry['mac_address']}")
|
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()
|
device_type = entry["device_type"].lower()
|
||||||
parent_node = entry["parent_node_mac_address"]
|
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"]):
|
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
|
# Some device type naming exceptions
|
||||||
if device_type == "iphone":
|
if device_type == "iphone":
|
||||||
|
|||||||
@@ -177,27 +177,25 @@ def decode_settings_base64(encoded_str, convert_types=True):
|
|||||||
# -------------------------------------------------------------------
|
# -------------------------------------------------------------------
|
||||||
def normalize_mac(mac):
|
def normalize_mac(mac):
|
||||||
"""
|
"""
|
||||||
Normalize a MAC address to the standard format with colon separators.
|
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".
|
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:*".
|
wildcard mac addresses like "AA:BB:CC:*" will be normalized to "aa:bb:cc:*".
|
||||||
|
|
||||||
:param mac: The MAC address to normalize.
|
:param mac: the mac address to normalize.
|
||||||
:return: The normalized MAC address.
|
:return: the normalized mac address (lowercase).
|
||||||
"""
|
"""
|
||||||
s = str(mac).strip()
|
s = str(mac).strip().lower()
|
||||||
|
|
||||||
if s.lower() == "internet":
|
if s == "internet":
|
||||||
return "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:
|
||||||
parts = s.split(':')
|
parts = s.split(':')
|
||||||
elif '-' in s:
|
elif '-' in s:
|
||||||
parts = s.split('-')
|
parts = s.split('-')
|
||||||
else:
|
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)]
|
parts = [s[i:i + 2] for i in range(0, len(s), 2)]
|
||||||
|
|
||||||
normalized_parts = []
|
normalized_parts = []
|
||||||
@@ -206,10 +204,10 @@ def normalize_mac(mac):
|
|||||||
if part == '*':
|
if part == '*':
|
||||||
normalized_parts.append('*')
|
normalized_parts.append('*')
|
||||||
else:
|
else:
|
||||||
# Ensure two hex digits (zfill is fine for alphanumeric input)
|
# ensure two hex digits
|
||||||
normalized_parts.append(part.zfill(2))
|
normalized_parts.append(part.zfill(2))
|
||||||
|
|
||||||
# Use colon as canonical separator
|
# use colon as canonical separator
|
||||||
return ':'.join(normalized_parts)
|
return ':'.join(normalized_parts)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -74,7 +74,7 @@ def main():
|
|||||||
watched1 = device['dev_name'], # name
|
watched1 = device['dev_name'], # name
|
||||||
watched2 = device['dev_type'], # device_type (AP/Switch etc)
|
watched2 = device['dev_type'], # device_type (AP/Switch etc)
|
||||||
watched3 = device['dev_connected'], # connectedAt or empty
|
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 = '',
|
extra = '',
|
||||||
foreignKey = device['dev_mac']
|
foreignKey = device['dev_mac']
|
||||||
)
|
)
|
||||||
@@ -115,10 +115,10 @@ def get_device_data(site, api):
|
|||||||
continue
|
continue
|
||||||
device_id_to_mac[dev["id"]] = dev.get("macAddress", "")
|
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):
|
def resolve_parent_mac(uplink_id):
|
||||||
if not uplink_id:
|
if not uplink_id:
|
||||||
return "Internet"
|
return "internet"
|
||||||
return device_id_to_mac.get(uplink_id, "Unknown")
|
return device_id_to_mac.get(uplink_id, "Unknown")
|
||||||
|
|
||||||
# Process Unifi devices
|
# Process Unifi devices
|
||||||
|
|||||||
@@ -173,7 +173,7 @@ def collect_details(device_type, devices, online_macs, processed_macs, plugin_ob
|
|||||||
|
|
||||||
# override parent MAC if this is a router
|
# override parent MAC if this is a router
|
||||||
if parentMac == 'null' and is_typical_router_ip(ipTmp):
|
if parentMac == 'null' and is_typical_router_ip(ipTmp):
|
||||||
parentMac = 'Internet'
|
parentMac = 'internet'
|
||||||
|
|
||||||
# Add object only if not processed
|
# Add object only if not processed
|
||||||
if macTmp not in processed_macs and (status == 1 or force_import is True):
|
if macTmp not in processed_macs and (status == 1 or force_import is True):
|
||||||
|
|||||||
@@ -216,7 +216,7 @@ def generate_rows(args: argparse.Namespace, header: list[str]) -> list[dict[str,
|
|||||||
|
|
||||||
rows: list[dict[str, 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
|
required_devices = 1 + args.switches + args.aps + args.devices
|
||||||
if required_devices > len(ip_pool):
|
if required_devices > len(ip_pool):
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
@@ -229,12 +229,12 @@ def generate_rows(args: argparse.Namespace, header: list[str]) -> list[dict[str,
|
|||||||
ip_pool.remove(choice)
|
ip_pool.remove(choice)
|
||||||
return 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(
|
root_row = build_row(
|
||||||
name="Internet",
|
name="internet",
|
||||||
dev_type="Gateway",
|
dev_type="Gateway",
|
||||||
vendor="NetAlertX",
|
vendor="NetAlertX",
|
||||||
mac="Internet",
|
mac="internet",
|
||||||
parent_mac="",
|
parent_mac="",
|
||||||
ip="",
|
ip="",
|
||||||
header=header,
|
header=header,
|
||||||
@@ -243,7 +243,7 @@ def generate_rows(args: argparse.Namespace, header: list[str]) -> list[dict[str,
|
|||||||
ssid=args.ssid,
|
ssid=args.ssid,
|
||||||
now=now,
|
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["devParentRelType"] = "Root"
|
||||||
root_row["devStaticIP"] = "0"
|
root_row["devStaticIP"] = "0"
|
||||||
root_row["devScan"] = "0"
|
root_row["devScan"] = "0"
|
||||||
@@ -261,7 +261,7 @@ def generate_rows(args: argparse.Namespace, header: list[str]) -> list[dict[str,
|
|||||||
dev_type="Firewall",
|
dev_type="Firewall",
|
||||||
vendor=random.choice(VENDORS),
|
vendor=random.choice(VENDORS),
|
||||||
mac=router_mac,
|
mac=router_mac,
|
||||||
parent_mac="Internet",
|
parent_mac="internet",
|
||||||
ip=router_ip,
|
ip=router_ip,
|
||||||
header=header,
|
header=header,
|
||||||
owner=args.owner,
|
owner=args.owner,
|
||||||
|
|||||||
@@ -64,9 +64,9 @@ ALLOWED_EVENT_TYPES = Literal[
|
|||||||
|
|
||||||
def validate_mac(value: str) -> str:
|
def validate_mac(value: str) -> str:
|
||||||
"""Validate and normalize MAC address format."""
|
"""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":
|
if value.lower() == "internet":
|
||||||
return "Internet"
|
return "internet"
|
||||||
|
|
||||||
if not is_mac(value):
|
if not is_mac(value):
|
||||||
raise ValueError(f"Invalid MAC address format: {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]:
|
def sanitize_text_fields(cls, v: Optional[str]) -> Optional[str]:
|
||||||
if v is None:
|
if v is None:
|
||||||
return v
|
return v
|
||||||
return sanitize_string(v)
|
return v
|
||||||
|
|
||||||
|
|
||||||
class DeleteDevicesRequest(BaseModel):
|
class DeleteDevicesRequest(BaseModel):
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ from db.db_upgrade import (
|
|||||||
ensure_Parameters,
|
ensure_Parameters,
|
||||||
ensure_Settings,
|
ensure_Settings,
|
||||||
ensure_Indexes,
|
ensure_Indexes,
|
||||||
|
ensure_mac_lowercase_triggers,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -198,6 +199,9 @@ class DB:
|
|||||||
# Indexes
|
# Indexes
|
||||||
ensure_Indexes(self.sql)
|
ensure_Indexes(self.sql)
|
||||||
|
|
||||||
|
# Normalization triggers
|
||||||
|
ensure_mac_lowercase_triggers(self.sql)
|
||||||
|
|
||||||
# commit changes
|
# commit changes
|
||||||
self.commitDB()
|
self.commitDB()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
|||||||
@@ -105,6 +105,50 @@ def ensure_column(sql, table: str, column_name: str, column_type: str) -> bool:
|
|||||||
return False
|
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:
|
def ensure_views(sql) -> bool:
|
||||||
"""
|
"""
|
||||||
Ensures required views exist.
|
Ensures required views exist.
|
||||||
|
|||||||
@@ -358,7 +358,7 @@ def importConfigs(pm, db, all_plugins):
|
|||||||
"Router",
|
"Router",
|
||||||
"USB LAN Adapter",
|
"USB LAN Adapter",
|
||||||
"USB WIFI Adapter",
|
"USB WIFI Adapter",
|
||||||
"Internet",
|
"internet",
|
||||||
],
|
],
|
||||||
c_d,
|
c_d,
|
||||||
"Network device types",
|
"Network device types",
|
||||||
|
|||||||
@@ -495,7 +495,7 @@ class DeviceInstance:
|
|||||||
|
|
||||||
# Fetch children
|
# Fetch children
|
||||||
cur.execute(
|
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"],),
|
(device_data["devMac"],),
|
||||||
)
|
)
|
||||||
children_rows = cur.fetchall()
|
children_rows = cur.fetchall()
|
||||||
|
|||||||
@@ -728,10 +728,10 @@ def create_new_devices(db):
|
|||||||
scanParentMAC = raw_parent_mac
|
scanParentMAC = raw_parent_mac
|
||||||
scanParentMAC = (
|
scanParentMAC = (
|
||||||
scanParentMAC
|
scanParentMAC
|
||||||
if scanParentMAC and scanMac != "Internet"
|
if scanParentMAC and scanMac.lower() != "internet"
|
||||||
else (
|
else (
|
||||||
get_setting_value("NEWDEV_devParentMAC")
|
get_setting_value("NEWDEV_devParentMAC")
|
||||||
if scanMac != "Internet"
|
if scanMac.lower() != "internet"
|
||||||
else "null"
|
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):
|
def check_mac_or_internet(input_str):
|
||||||
# Regular expression pattern for matching a MAC address
|
# 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})"
|
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})"
|
||||||
|
|||||||
@@ -179,7 +179,7 @@ def guess_device_attributes(
|
|||||||
|
|
||||||
# # Internet shortcut
|
# # Internet shortcut
|
||||||
# if mac == "INTERNET":
|
# 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
|
type_ = None
|
||||||
icon = None
|
icon = None
|
||||||
|
|||||||
@@ -132,7 +132,7 @@ def test_update_device_column(client, api_token, test_mac):
|
|||||||
# Update its parent MAC
|
# Update its parent MAC
|
||||||
resp = client.post(
|
resp = client.post(
|
||||||
f"/device/{test_mac}/update-column",
|
f"/device/{test_mac}/update-column",
|
||||||
json={"columnName": "devParentMAC", "columnValue": "Internet"},
|
json={"columnName": "devParentMAC", "columnValue": "internet"},
|
||||||
headers=auth_headers(api_token),
|
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
|
# Try updating a non-existent device
|
||||||
resp_missing = client.post(
|
resp_missing = client.post(
|
||||||
"/device/11:22:33:44:55:66/update-column",
|
"/device/11:22:33:44:55:66/update-column",
|
||||||
json={"columnName": "devParentMAC", "columnValue": "Internet"},
|
json={"columnName": "devParentMAC", "columnValue": "internet"},
|
||||||
headers=auth_headers(api_token),
|
headers=auth_headers(api_token),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -1,70 +1,78 @@
|
|||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
import random
|
|
||||||
from helper import get_setting_value
|
from helper import get_setting_value
|
||||||
from api_server.api_server_start import app
|
from api_server.api_server_start import app
|
||||||
from models.device_instance import DeviceInstance
|
from models.device_instance import DeviceInstance
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(scope="session")
|
@pytest.fixture(scope="session")
|
||||||
def api_token():
|
def api_token():
|
||||||
return get_setting_value("API_TOKEN")
|
return get_setting_value("API_TOKEN")
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def client():
|
def client():
|
||||||
with app.test_client() as client:
|
with app.test_client() as client:
|
||||||
yield client
|
yield client
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def test_mac_norm():
|
def test_mac_norm():
|
||||||
# Normalized MAC
|
# Now normalized to lowercase
|
||||||
return "AA:BB:CC:DD:EE:FF"
|
return "aa:bb:cc:dd:ee:ff"
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def test_parent_mac_input():
|
def test_parent_mac_input():
|
||||||
# Lowercase input MAC
|
# Input with mixed/upper case to test the trigger/normalization
|
||||||
return "aa:bb:cc:dd:ee:00"
|
return "AA:BB:CC:DD:EE:00"
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def test_parent_mac_norm():
|
def test_parent_mac_norm():
|
||||||
# Normalized expected MAC
|
# Expected result in DB (lowercase)
|
||||||
return "AA:BB:CC:DD:EE:00"
|
return "aa:bb:cc:dd:ee:00"
|
||||||
|
|
||||||
|
|
||||||
def auth_headers(token):
|
def auth_headers(token):
|
||||||
return {"Authorization": f"Bearer {token}"}
|
return {"Authorization": f"Bearer {token}"}
|
||||||
|
|
||||||
|
|
||||||
def test_update_normalization(client, api_token, test_mac_norm, test_parent_mac_input, test_parent_mac_norm):
|
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 = {
|
create_payload = {
|
||||||
"createNew": True,
|
"createNew": True,
|
||||||
"devName": "Normalization Test Device",
|
"devName": "Normalization Test Device",
|
||||||
"devOwner": "Unit Test",
|
"devOwner": "Unit Test",
|
||||||
}
|
}
|
||||||
|
# Pass the lowercase mac
|
||||||
resp = client.post(f"/device/{test_mac_norm}", json=create_payload, headers=auth_headers(api_token))
|
resp = client.post(f"/device/{test_mac_norm}", json=create_payload, headers=auth_headers(api_token))
|
||||||
assert resp.status_code == 200
|
assert resp.status_code == 200
|
||||||
assert resp.json.get("success") is True
|
assert resp.json.get("success") is True
|
||||||
|
|
||||||
# 2. Update the device using LOWERCASE MAC in URL
|
# 2. Update the device sending UPPERCASE parent MAC
|
||||||
# And set devParentMAC to LOWERCASE
|
# To verify the triggers/logic flip it to lowercase
|
||||||
update_payload = {
|
update_payload = {
|
||||||
"devParentMAC": test_parent_mac_input,
|
"devParentMAC": test_parent_mac_input,
|
||||||
"devName": "Updated Device"
|
"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.status_code == 200
|
||||||
assert resp.json.get("success") is True
|
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()
|
device_handler = DeviceInstance()
|
||||||
|
# Query using lowercase mac
|
||||||
device = device_handler.getDeviceData(test_mac_norm)
|
device = device_handler.getDeviceData(test_mac_norm)
|
||||||
|
|
||||||
assert device is not None
|
assert device is not None
|
||||||
assert device["devName"] == "Updated Device"
|
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_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
|
# Cleanup
|
||||||
device_handler.deleteDeviceByMAC(test_mac_norm)
|
device_handler.deleteDeviceByMAC(test_mac_norm)
|
||||||
@@ -19,6 +19,6 @@ def test_normalize_mac_preserves_wildcard():
|
|||||||
|
|
||||||
|
|
||||||
def test_normalize_mac_preserves_internet_root():
|
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"
|
||||||
|
|||||||
Reference in New Issue
Block a user