From 3b7cbba32b117e9bd605b19c33ecbdc6bc158e56 Mon Sep 17 00:00:00 2001 From: jokob-sk Date: Sun, 16 Jun 2024 12:23:14 +1000 Subject: [PATCH] =?UTF-8?q?=F0=9F=8E=A8=20Device=20tiles=20setting?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/WEB_UI_PORT_DEBUG.md | 2 +- front/css/app.css | 4 + front/devices.php | 156 ++++++++---------- front/php/components/tile_cards.php | 43 +++++ front/php/components/tile_cards_defaults.json | 17 ++ front/php/templates/footer.php | 2 +- front/php/templates/language/de_de.json | 1 + front/php/templates/language/en_us.json | 1 + front/php/templates/language/es_es.json | 1 + front/php/templates/language/fr_fr.json | 1 + front/php/templates/language/it_it.json | 1 + front/php/templates/language/nb_no.json | 1 + front/php/templates/language/pl_pl.json | 1 + front/php/templates/language/pt_br.json | 1 + front/php/templates/language/ru_ru.json | 1 + front/php/templates/language/tr_tr.json | 1 + front/php/templates/language/zh_cn.json | 1 + front/plugins/ui_settings/README.md | 23 +++ front/plugins/ui_settings/config.json | 85 ++++++++++ server/initialise.py | 16 +- 20 files changed, 258 insertions(+), 101 deletions(-) create mode 100755 front/php/components/tile_cards.php create mode 100755 front/php/components/tile_cards_defaults.json create mode 100755 front/plugins/ui_settings/README.md create mode 100755 front/plugins/ui_settings/config.json diff --git a/docs/WEB_UI_PORT_DEBUG.md b/docs/WEB_UI_PORT_DEBUG.md index 90ea2c2f..9ea3f06f 100755 --- a/docs/WEB_UI_PORT_DEBUG.md +++ b/docs/WEB_UI_PORT_DEBUG.md @@ -7,7 +7,7 @@ When opening an issue please: 1. Include a screenshot of what you see when accessing `HTTP:///20211` (or your custom port) 1. [Follow steps 1, 2, 3, 4 on this page](https://github.com/jokob-sk/NetAlertX/blob/main/docs/DEBUG_TIPS.md) 1. Execute the following in the container to see the processes and their ports and submit a screenshot of the result: - 1. `sudo apt-get install lsof` + 1. `sudo apk add lsof` 1. `sudo lsof -i` 1. Try running the `nginx` command in the container 1. if you get `nginx: [emerg] bind() to 0.0.0.0:20211 failed (98: Address in use)` try using a different port number diff --git a/front/css/app.css b/front/css/app.css index dab0b2cf..306f8dde 100755 --- a/front/css/app.css +++ b/front/css/app.css @@ -1106,6 +1106,10 @@ input[readonly] { height: 1.5em !important; } +#TileCards .tile .inner +{ + color: white; +} #dropdownIcon li svg, #dropdownIcon li i{ height: 1.5em !important; diff --git a/front/devices.php b/front/devices.php index 8cdb71e7..b2135c80 100755 --- a/front/devices.php +++ b/front/devices.php @@ -42,77 +42,7 @@ @@ -321,9 +251,7 @@ function main () { // Initialize components with parameters - initializeDatatable(getUrlAnchor('my')); - - + initializeDatatable(getUrlAnchor('my_devices')); // check if data outdated and show spinner if so handleLoadingDialog() @@ -358,21 +286,49 @@ function getDevicesTotals(devicesData) { if (getCache("getDevicesTotals") !== "") { resultJSON = getCache("getDevicesTotals"); } else { - // combined query - const devices = filterDataByStatus(devicesData, 'my'); - const connectedDevices = filterDataByStatus(devicesData, 'connected'); - const favoritesDevices = filterDataByStatus(devicesData, 'favorites'); - const newDevices = filterDataByStatus(devicesData, 'new'); - const downDevices = filterDataByStatus(devicesData, 'down'); - const archivedDevices = filterDataByStatus(devicesData, 'archived'); + // Define filter conditions and corresponding objects + const filters = [ + { status: 'my_devices', color: 'bg-aqua', label: getString('Device_Shortcut_AllDevices'), icon: 'fa-laptop' }, + { status: 'all', color: 'bg-aqua', label: getString('Gen_All_Devices'), icon: 'fa-laptop' }, + { status: 'connected', color: 'bg-green', label: getString('Device_Shortcut_Connected'), icon: 'fa-plug' }, + { status: 'favorites', color: 'bg-yellow', label: getString('Device_Shortcut_Favorites'), icon: 'fa-star' }, + { status: 'new', color: 'bg-yellow', label: getString('Device_Shortcut_NewDevices'), icon: 'fa-plus' }, + { status: 'down', color: 'bg-red', label: getString('Device_Shortcut_DownOnly'), icon: 'fa-warning' }, + { status: 'archived', color: 'bg-gray', label: getString('Device_Shortcut_Archived'), icon: 'fa-eye-slash' }, + { status: 'offline', color: 'bg-gray', label: getString('Gen_Offline'), icon: 'fa-xmark' } + ]; - $('#devicesMy').html (devices.length); - $('#devicesConnected').html (connectedDevices.length); - $('#devicesFavorites').html (favoritesDevices.length); - $('#devicesNew').html (newDevices.length); - $('#devicesDown').html (downDevices.length); - $('#devicesArchived').html (archivedDevices.length); + // Initialize an empty array to store the final objects + let dataArray = []; + + // Loop through each filter condition + filters.forEach(filter => { + // Calculate count dynamically based on filter condition + let count = filterDataByStatus(devicesData, filter.status).length; + + console.log(getSetting('UI_hide_empty')); + + // Check any condition to skip adding the object to dataArray + if ( + (['', 'False'].includes(getSetting('UI_hide_empty')) || (getSetting('UI_hide_empty') == "True" && count > 0)) && + (getSetting('UI_shown_cards') == "" || getSetting('UI_shown_cards').includes(filter.status)) + ) { + dataArray.push({ + onclickEvent: `initializeDatatable('${filter.status}')`, + color: filter.color, + title: count, + label: filter.label, + icon: filter.icon + }); + } + + }); + + // render info boxes/tile cards + renderInfoboxes( + dataArray + ) // save to cache setCache("getDevicesTotals", resultJSON); @@ -381,12 +337,28 @@ function getDevicesTotals(devicesData) { // console.log(resultJSON); } +//------------------------------------------------------------------------------ +function renderInfoboxes(customData) { + $.ajax({ + url: 'php/components/tile_cards.php', // PHP script URL + type: 'POST', // Use POST method to send data + dataType: 'html', // Expect HTML response + data: { items: JSON.stringify(customData) }, // Send customData as JSON + success: function(response) { + $('#TileCards').html(response); // Replace container content with fetched HTML + }, + error: function(xhr, status, error) { + console.error('Error fetching infoboxes:', error); + } + }); + } + // ----------------------------------------------------------------------------- // Define a function to filter data based on deviceStatus function filterDataByStatus(data, status) { return data.filter(function(item) { switch (status) { - case 'my': + case 'my_devices': to_display = getSetting('UI_MY_DEVICES'); let result = true; @@ -403,7 +375,7 @@ function filterDataByStatus(data, status) { result = false; } - return result; // Include all items for 'my' status + return result; // Include all items for 'my_devices' status case 'connected': return item.dev_PresentLastScan === 1; case 'favorites': @@ -455,7 +427,7 @@ function initializeDatatable (status) { if(!status) { - status = 'my' + status = 'my_devices' } // Save status selected @@ -463,12 +435,14 @@ function initializeDatatable (status) { // Define color & title for the status selected switch (deviceStatus) { - case 'my': tableTitle = getString('Device_Shortcut_AllDevices'); color = 'aqua'; break; + case 'my_devices': tableTitle = getString('Device_Shortcut_AllDevices'); color = 'aqua'; break; case 'connected': tableTitle = getString('Device_Shortcut_Connected'); color = 'green'; break; + case 'all': tableTitle = getString('Gen_All_Devices'); color = 'aqua'; break; case 'favorites': tableTitle = getString('Device_Shortcut_Favorites'); color = 'yellow'; break; case 'new': tableTitle = getString('Device_Shortcut_NewDevices'); color = 'yellow'; break; case 'down': tableTitle = getString('Device_Shortcut_DownOnly'); color = 'red'; break; case 'archived': tableTitle = getString('Device_Shortcut_Archived'); color = 'gray'; break; + case 'offline': tableTitle = getString('Gen_Offline'); color = 'gray'; break; default: tableTitle = getString('Device_Shortcut_Devices'); color = 'gray'; break; } diff --git a/front/php/components/tile_cards.php b/front/php/components/tile_cards.php new file mode 100755 index 00000000..25be71e3 --- /dev/null +++ b/front/php/components/tile_cards.php @@ -0,0 +1,43 @@ + + +
+
+

' . htmlspecialchars($title) . '

+

' . htmlspecialchars($label) . '

+
+
+ +
+
+
+ '; +} + +// Load default data from JSON file +$defaultDataFile = 'tile_cards_defaults.json'; +$defaultData = file_exists($defaultDataFile) ? json_decode(file_get_contents($defaultDataFile), true) : []; + +// Check if 'items' parameter exists and is valid JSON +$items = isset($_POST['items']) ? json_decode($_POST['items'], true) : []; + +// Use default data if 'items' is not provided or cannot be decoded +if (empty($items)) { + $items = $defaultData; +} + +$html = ''; +foreach ($items as $item) { + $html .= renderInfobox($item); +} +echo $html; +exit(); +?> diff --git a/front/php/components/tile_cards_defaults.json b/front/php/components/tile_cards_defaults.json new file mode 100755 index 00000000..931317e1 --- /dev/null +++ b/front/php/components/tile_cards_defaults.json @@ -0,0 +1,17 @@ +[ + { + "onclickEvent": "handleClick(1)", + "color": "bg-primary", + "title": "Default Infobox 1", + "label": "Default label for Infobox 1", + "icon": "fa-bell" + }, + { + "onclickEvent": "handleClick(2)", + "color": "bg-success", + "title": "Default Infobox 2", + "label": "Default label for Infobox 2", + "icon": "fa-envelope" + } +] + \ No newline at end of file diff --git a/front/php/templates/footer.php b/front/php/templates/footer.php index a28d977f..21a4d363 100755 --- a/front/php/templates/footer.php +++ b/front/php/templates/footer.php @@ -17,7 +17,7 @@ - Net Alertx + NetAlertx diff --git a/front/php/templates/language/de_de.json b/front/php/templates/language/de_de.json index 445ee9ad..6139153e 100755 --- a/front/php/templates/language/de_de.json +++ b/front/php/templates/language/de_de.json @@ -282,6 +282,7 @@ "Gen_Action": "Action", "Gen_Add": "", "Gen_Add_All": "", + "Gen_All_Devices": "", "Gen_AreYouSure": "Sind Sie sich sicher?", "Gen_Backup": "Sichern", "Gen_Cancel": "Abbrechen", diff --git a/front/php/templates/language/en_us.json b/front/php/templates/language/en_us.json index 126ab5c9..81555935 100755 --- a/front/php/templates/language/en_us.json +++ b/front/php/templates/language/en_us.json @@ -270,6 +270,7 @@ "Gen_Action": "Action", "Gen_Add": "Add", "Gen_Add_All": "Add all", + "Gen_All_Devices": "All Devices", "Gen_AreYouSure": "Are you sure?", "Gen_Backup": "Run Backup", "Gen_Cancel": "Cancel", diff --git a/front/php/templates/language/es_es.json b/front/php/templates/language/es_es.json index 8cd1c029..49e931ab 100755 --- a/front/php/templates/language/es_es.json +++ b/front/php/templates/language/es_es.json @@ -280,6 +280,7 @@ "Gen_Action": "Acción", "Gen_Add": "Añadir", "Gen_Add_All": "Añadir todo", + "Gen_All_Devices": "", "Gen_AreYouSure": "¿Estás seguro?", "Gen_Backup": "Ejecutar copia de seguridad", "Gen_Cancel": "Cancelar", diff --git a/front/php/templates/language/fr_fr.json b/front/php/templates/language/fr_fr.json index ae6af500..3fa8ca75 100755 --- a/front/php/templates/language/fr_fr.json +++ b/front/php/templates/language/fr_fr.json @@ -270,6 +270,7 @@ "Gen_Action": "Action", "Gen_Add": "", "Gen_Add_All": "", + "Gen_All_Devices": "", "Gen_AreYouSure": "", "Gen_Backup": "", "Gen_Cancel": "Annuler", diff --git a/front/php/templates/language/it_it.json b/front/php/templates/language/it_it.json index cca5df45..1e1f1a67 100755 --- a/front/php/templates/language/it_it.json +++ b/front/php/templates/language/it_it.json @@ -270,6 +270,7 @@ "Gen_Action": "Azione", "Gen_Add": "Aggiungi", "Gen_Add_All": "Aggiungi tutti", + "Gen_All_Devices": "", "Gen_AreYouSure": "Sei sicuro?", "Gen_Backup": "Esegui backup", "Gen_Cancel": "Annulla", diff --git a/front/php/templates/language/nb_no.json b/front/php/templates/language/nb_no.json index 63de5567..62df412b 100755 --- a/front/php/templates/language/nb_no.json +++ b/front/php/templates/language/nb_no.json @@ -270,6 +270,7 @@ "Gen_Action": "Handling", "Gen_Add": "Legg til", "Gen_Add_All": "Legg til alle", + "Gen_All_Devices": "", "Gen_AreYouSure": "Er du sikker?", "Gen_Backup": "Kjør sikkerhetskopiering", "Gen_Cancel": "Avbryt", diff --git a/front/php/templates/language/pl_pl.json b/front/php/templates/language/pl_pl.json index e0c53b04..2fc5df4c 100755 --- a/front/php/templates/language/pl_pl.json +++ b/front/php/templates/language/pl_pl.json @@ -270,6 +270,7 @@ "Gen_Action": "Akcja", "Gen_Add": "Dodaj", "Gen_Add_All": "Dodaj wszystko", + "Gen_All_Devices": "", "Gen_AreYouSure": "Jesteś pewien?", "Gen_Backup": "Wykonaj Kopie Zapasową", "Gen_Cancel": "Anuluj", diff --git a/front/php/templates/language/pt_br.json b/front/php/templates/language/pt_br.json index af5af316..a3ca6892 100755 --- a/front/php/templates/language/pt_br.json +++ b/front/php/templates/language/pt_br.json @@ -270,6 +270,7 @@ "Gen_Action": "", "Gen_Add": "", "Gen_Add_All": "", + "Gen_All_Devices": "", "Gen_AreYouSure": "", "Gen_Backup": "", "Gen_Cancel": "", diff --git a/front/php/templates/language/ru_ru.json b/front/php/templates/language/ru_ru.json index b03825a1..a71d6850 100755 --- a/front/php/templates/language/ru_ru.json +++ b/front/php/templates/language/ru_ru.json @@ -270,6 +270,7 @@ "Gen_Action": "Действия", "Gen_Add": "Добавить", "Gen_Add_All": "Добавить все", + "Gen_All_Devices": "", "Gen_AreYouSure": "Вы уверены?", "Gen_Backup": "Запустить резервное копирование", "Gen_Cancel": "Отмена", diff --git a/front/php/templates/language/tr_tr.json b/front/php/templates/language/tr_tr.json index 989c288f..023a0d09 100755 --- a/front/php/templates/language/tr_tr.json +++ b/front/php/templates/language/tr_tr.json @@ -270,6 +270,7 @@ "Gen_Action": "", "Gen_Add": "", "Gen_Add_All": "", + "Gen_All_Devices": "", "Gen_AreYouSure": "", "Gen_Backup": "", "Gen_Cancel": "", diff --git a/front/php/templates/language/zh_cn.json b/front/php/templates/language/zh_cn.json index 6c4e0203..fc74ebc3 100755 --- a/front/php/templates/language/zh_cn.json +++ b/front/php/templates/language/zh_cn.json @@ -270,6 +270,7 @@ "Gen_Action": "", "Gen_Add": "", "Gen_Add_All": "", + "Gen_All_Devices": "", "Gen_AreYouSure": "", "Gen_Backup": "", "Gen_Cancel": "", diff --git a/front/plugins/ui_settings/README.md b/front/plugins/ui_settings/README.md new file mode 100755 index 00000000..c73241c3 --- /dev/null +++ b/front/plugins/ui_settings/README.md @@ -0,0 +1,23 @@ +## Overview + +PLugin functionality overview and links to external resources if relevant. Include use cases if available. + +> [!TIP] +> Some tip. + +### Quick setup guide + +To set up the plugin correctly, make sure... + +#### Required Settings + +- When to run `PREF_RUN` +- + +### Usage + +- Head to **Settings** > **Plugin name** to adjust the default values. + +### Notes + +- Additional notes, limitations, Author info. \ No newline at end of file diff --git a/front/plugins/ui_settings/config.json b/front/plugins/ui_settings/config.json new file mode 100755 index 00000000..f97f5079 --- /dev/null +++ b/front/plugins/ui_settings/config.json @@ -0,0 +1,85 @@ +{ + "code_name": "ui_settings", + "unique_prefix": "UI", + "plugin_type": "core", + "enabled": true, + "data_source": "template", + "show_ui": false, + "localized": ["display_name", "description", "icon"], + "display_name": [ + { + "language_code": "en_us", + "string": "UI settings" + } + ], + "description": [ + { + "language_code": "en_us", + "string": "Plugin to adjust UI settings." + } + ], + "icon": [ + { + "language_code": "en_us", + "string": "" + } + ], + "params": [], + "settings": [ + { + "function": "shown_cards", + "type": "text.multiselect", + "maxLength": 50, + "default_value": [ + "my_devices", + "connected", + "favorites", + "new", + "down", + "archived" + ], + "options": [ + "my_devices", + "connected", + "favorites", + "new", + "down", + "archived", + "offline", + "all" + ], + "localized": ["name", "description"], + "name": [ + { + "language_code": "en_us", + "string": "Tiles to Show" + } + ], + "description": [ + { + "language_code": "en_us", + "string": "Which tiles to show on teh top of the Devices page." + } + ] + }, + { + "function": "hide_empty", + "type": "boolean", + "default_value": false, + "options": [], + "localized": ["name", "description"], + "name": [ + { + "language_code": "en_us", + "string": "Hide empty tiles" + } + ], + "description": [ + { + "language_code": "en_us", + "string": "Hide Device tiles with zero results." + } + ] + } + ] +} diff --git a/server/initialise.py b/server/initialise.py index 9667109f..57a7279f 100755 --- a/server/initialise.py +++ b/server/initialise.py @@ -132,24 +132,24 @@ def importConfigs (db, all_plugins): # ccd(key, default, config_dir, name, inputtype, options, group, events=[], desc = "", regex = "", setJsonMetadata = {}, overrideTemplate = {}) conf.LOADED_PLUGINS = ccd('LOADED_PLUGINS', [] , c_d, 'Loaded plugins', 'text.multiselect', '', 'General') - # conf.TEST_TEST2 = ccd('TEST_TEST2', [] , c_d, 'TEST_TEST2', 'text.multiselect', '', 'General') conf.SCAN_SUBNETS = ccd('SCAN_SUBNETS', ['192.168.1.0/24 --interface=eth1', '192.168.1.0/24 --interface=eth0'] , c_d, 'Subnets to scan', 'subnets', '', 'General') conf.LOG_LEVEL = ccd('LOG_LEVEL', 'verbose' , c_d, 'Log verboseness', 'text.select', "['none', 'minimal', 'verbose', 'debug']", 'General') conf.TIMEZONE = ccd('TIMEZONE', 'Europe/Berlin' , c_d, 'Time zone', 'text', '', 'General') conf.PLUGINS_KEEP_HIST = ccd('PLUGINS_KEEP_HIST', 250 , c_d, 'Keep history entries', 'integer', '', 'General') conf.REPORT_DASHBOARD_URL = ccd('REPORT_DASHBOARD_URL', 'http://netalertx/' , c_d, 'NetAlertX URL', 'text', '', 'General') - conf.UI_LANG = ccd('UI_LANG', 'English' , c_d, 'Language Interface', 'text.select', "['English', 'French', 'German', 'Norwegian', 'Russian', 'Spanish', 'Italian (it_it)', 'Portuguese (pt_br)', 'Polish (pl_pl)', 'Turkish (tr_tr)', 'Chinese (zh_cn)' ]", 'General') - conf.UI_PRESENCE = ccd('UI_PRESENCE', ['online', 'offline', 'archived'] , c_d, 'Include in presence', 'text.multiselect', "['online', 'offline', 'archived']", 'General') - conf.UI_DEV_SECTIONS = ccd('UI_DEV_SECTIONS', [] , c_d, 'Show sections', 'text.multiselect', "['Tile Cards', 'Device Presence']", 'General') - conf.UI_MY_DEVICES = ccd('UI_MY_DEVICES', ['online', 'offline', 'archived', 'new', 'down'] , c_d, 'Include in My Devices', 'text.multiselect', "['online', 'offline', 'archived', 'new', 'down']", 'General') - conf.UI_NOT_RANDOM_MAC = ccd('UI_NOT_RANDOM_MAC', [] , c_d, 'Exlude from Random Prefix', 'list', "", 'General') - conf.UI_ICONS = ccd('UI_ICONS', ['PGkgY2xhc3M9ImZhIGZhLWNvbXB1dGVyIj48L2k+', 'PGkgY2xhc3M9ImZhIGZhLWV0aGVybmV0Ij48L2k+', 'PGkgY2xhc3M9ImZhIGZhLWdhbWVwYWQiPjwvaT4', 'PGkgY2xhc3M9ImZhIGZhLWdsb2JlIj48L2k+', 'PGkgY2xhc3M9ImZhIGZhLWxhcHRvcCI+PC9pPg==', 'PGkgY2xhc3M9ImZhIGZhLWxpZ2h0YnVsYiI+PC9pPg==', 'PGkgY2xhc3M9ImZhIGZhLXNoaWVsZCI+PC9pPg==', 'PGkgY2xhc3M9ImZhIGZhLXdpZmkiPjwvaT4'] , c_d, 'Icons', 'list', "", 'General') - conf.UI_REFRESH = ccd('UI_REFRESH', 0 , c_d, 'Refresh interval', 'integer', "", 'General') conf.DAYS_TO_KEEP_EVENTS = ccd('DAYS_TO_KEEP_EVENTS', 90 , c_d, 'Delete events days', 'integer', '', 'General') conf.HRS_TO_KEEP_NEWDEV = ccd('HRS_TO_KEEP_NEWDEV', 0 , c_d, 'Keep new devices for', 'integer', "0", 'General') conf.API_CUSTOM_SQL = ccd('API_CUSTOM_SQL', 'SELECT * FROM Devices WHERE dev_PresentLastScan = 0' , c_d, 'Custom endpoint', 'text', '', 'General') conf.NETWORK_DEVICE_TYPES = ccd('NETWORK_DEVICE_TYPES', ['AP', 'Gateway', 'Firewall', 'Hypervisor', 'Powerline', 'Switch', 'WLAN', 'PLC', 'Router','USB LAN Adapter', 'USB WIFI Adapter', 'Internet'] , c_d, 'Network device types', 'list', '', 'General') + # UI + conf.UI_LANG = ccd('UI_LANG', 'English' , c_d, 'Language Interface', 'text.select', "['English', 'French', 'German', 'Norwegian', 'Russian', 'Spanish', 'Italian (it_it)', 'Portuguese (pt_br)', 'Polish (pl_pl)', 'Turkish (tr_tr)', 'Chinese (zh_cn)' ]", 'UI') + conf.UI_NOT_RANDOM_MAC = ccd('UI_NOT_RANDOM_MAC', [] , c_d, 'Exlude from Random Prefix', 'list', "", 'UI') + conf.UI_ICONS = ccd('UI_ICONS', ['PGkgY2xhc3M9ImZhIGZhLWNvbXB1dGVyIj48L2k+', 'PGkgY2xhc3M9ImZhIGZhLWV0aGVybmV0Ij48L2k+', 'PGkgY2xhc3M9ImZhIGZhLWdhbWVwYWQiPjwvaT4', 'PGkgY2xhc3M9ImZhIGZhLWdsb2JlIj48L2k+', 'PGkgY2xhc3M9ImZhIGZhLWxhcHRvcCI+PC9pPg==', 'PGkgY2xhc3M9ImZhIGZhLWxpZ2h0YnVsYiI+PC9pPg==', 'PGkgY2xhc3M9ImZhIGZhLXNoaWVsZCI+PC9pPg==', 'PGkgY2xhc3M9ImZhIGZhLXdpZmkiPjwvaT4'] , c_d, 'Icons', 'list', "", 'UI') + conf.UI_REFRESH = ccd('UI_REFRESH', 0 , c_d, 'Refresh interval', 'integer', "", 'UI') + conf.UI_DEV_SECTIONS = ccd('UI_DEV_SECTIONS', [] , c_d, 'Show sections', 'text.multiselect', "['Tile Cards', 'Device Presence']", 'UI') + conf.UI_PRESENCE = ccd('UI_PRESENCE', ['online', 'offline', 'archived'] , c_d, 'Include in presence', 'text.multiselect', "['online', 'offline', 'archived']", 'UI') + conf.UI_MY_DEVICES = ccd('UI_MY_DEVICES', ['online', 'offline', 'archived', 'new', 'down'] , c_d, 'Include in My Devices', 'text.multiselect', "['online', 'offline', 'archived', 'new', 'down']", 'UI') # Init timezone in case it changed conf.tz = timezone(conf.TIMEZONE)