From b9d3f430fe1c203b05e0ab3c5859f818902ff4d5 Mon Sep 17 00:00:00 2001 From: jokob-sk Date: Thu, 27 Nov 2025 12:10:33 +1100 Subject: [PATCH] FE: regex validation for cron run schedules Signed-off-by: jokob-sk --- docs/API.md | 2 +- docs/DEVICE_MANAGEMENT.md | 22 +- docs/HELPER_SCRIPTS.md | 6 +- docs/HW_INSTALL.md | 9 +- docs/MIGRATION.md | 12 +- docs/SESSION_INFO.md | 80 +++--- docs/UPDATES.md | 11 +- front/css/app.css | 199 +++++++------- front/js/modal.js | 57 ++-- front/js/settings_utils.js | 268 ++++++++++--------- front/js/ui_components.js | 141 +++++----- front/multiEditCore.php | 103 +++---- front/php/templates/language/ar_ar.json | 1 + front/php/templates/language/ca_ca.json | 1 + front/php/templates/language/cs_cz.json | 1 + 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/fa_fa.json | 1 + front/php/templates/language/fr_fr.json | 1 + front/php/templates/language/it_it.json | 1 + front/php/templates/language/ja_jp.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/pt_pt.json | 1 + front/php/templates/language/ru_ru.json | 1 + front/php/templates/language/sv_sv.json | 1 + front/php/templates/language/tr_tr.json | 1 + front/php/templates/language/uk_ua.json | 1 + front/php/templates/language/zh_cn.json | 1 + front/plugins/__template/config.json | 2 +- front/plugins/_publisher_mqtt/config.json | 2 +- front/plugins/arp_scan/config.json | 2 +- front/plugins/asuswrt_import/config.json | 2 +- front/plugins/avahi_scan/config.json | 2 +- front/plugins/csv_backup/config.json | 2 +- front/plugins/db_cleanup/config.json | 2 +- front/plugins/ddns_update/config.json | 2 +- front/plugins/dhcp_leases/config.json | 2 +- front/plugins/dhcp_servers/config.json | 2 +- front/plugins/dig_scan/config.json | 2 +- front/plugins/freebox/config.json | 2 +- front/plugins/icmp_scan/config.json | 2 +- front/plugins/internet_ip/config.json | 2 +- front/plugins/internet_speedtest/config.json | 2 +- front/plugins/ipneigh/config.json | 2 +- front/plugins/maintenance/config.json | 2 +- front/plugins/mikrotik_scan/config.json | 2 +- front/plugins/nbtscan_scan/config.json | 2 +- front/plugins/nmap_dev_scan/config.json | 2 +- front/plugins/nmap_scan/config.json | 2 +- front/plugins/nslookup_scan/config.json | 2 +- front/plugins/omada_sdn_imp/config.json | 2 +- front/plugins/omada_sdn_openapi/config.json | 2 +- front/plugins/pihole_api_scan/config.json | 2 +- front/plugins/pihole_scan/config.json | 2 +- front/plugins/snmp_discovery/config.json | 2 +- front/plugins/sync/config.json | 2 +- front/plugins/unifi_api_import/config.json | 4 +- front/plugins/vendor_update/config.json | 2 +- front/plugins/wake_on_lan/config.json | 2 +- front/plugins/website_monitor/config.json | 2 +- front/settings.php | 263 +++++++++--------- 64 files changed, 666 insertions(+), 592 deletions(-) diff --git a/docs/API.md b/docs/API.md index 8c9c3767..3ad69a96 100755 --- a/docs/API.md +++ b/docs/API.md @@ -1,4 +1,4 @@ -# NetAlertX API Documentation +# API Documentation This API provides programmatic access to **devices, events, sessions, metrics, network tools, and sync** in NetAlertX. It is implemented as a **REST and GraphQL server**. All requests require authentication via **API Token** (`API_TOKEN` setting) unless explicitly noted. For example, to authorize a GraphQL request, you need to use a `Authorization: Bearer API_TOKEN` header as per example below: diff --git a/docs/DEVICE_MANAGEMENT.md b/docs/DEVICE_MANAGEMENT.md index dc95ee7e..f106da24 100755 --- a/docs/DEVICE_MANAGEMENT.md +++ b/docs/DEVICE_MANAGEMENT.md @@ -1,8 +1,8 @@ -# NetAlertX - Device Management +# Device Management The Main Info section is where most of the device identifiable information is stored and edited. Some of the information is autodetected via various plugins. Initial values for most of the fields can be specified in the `NEWDEV` plugin. -> [!NOTE] +> [!NOTE] > > You can multi-edit devices by selecting them in the main Devices view, from the Mainetence section, or via the CSV Export functionality under Maintenance. More info can be found in the [Devices Bulk-editing docs](./DEVICES_BULK_EDITING.md). @@ -14,23 +14,23 @@ The Main Info section is where most of the device identifiable information is st - **MAC**: MAC addres of the device. Not editable, unless creating a new dummy device. - **Last IP**: IP addres of the device. Not editable, unless creating a new dummy device. - **Name**: Friendly device name. Autodetected via various 🆎 Name discovery [plugins](https://github.com/jokob-sk/NetAlertX/blob/main/docs/PLUGINS.md). The app attaches `(IP match)` if the name is discovered via an IP match and not MAC match which could mean the name could be incorrect as IPs might change. - - **Icon**: Partially autodetected. Select an existing or [add a custom icon](./ICONS.md). You can also auto-apply the same icon on all devices of the same type. + - **Icon**: Partially autodetected. Select an existing or [add a custom icon](./ICONS.md). You can also auto-apply the same icon on all devices of the same type. - **Owner**: Device owner (The list is self-populated with existing owners and you can add custom values). - **Type**: Select a device type from the dropdown list (`Smartphone`, `Tablet`, - `Laptop`, `TV`, `router`, etc.) or add a new device type. If you want the device to act as a **Network device** (and be able to be a network node in the Network view), select a type under Network Devices or add a new Network Device type in Settings. More information can be found in the [Network Setup docs](./NETWORK_TREE.md). + `Laptop`, `TV`, `router`, etc.) or add a new device type. If you want the device to act as a **Network device** (and be able to be a network node in the Network view), select a type under Network Devices or add a new Network Device type in Settings. More information can be found in the [Network Setup docs](./NETWORK_TREE.md). - **Vendor**: The manufacturing vendor. Automatically updated by NetAlertX when empty or unknown, can be edited. - **Group**: Select a group (`Always on`, `Personal`, `Friends`, etc.) or type your own Group name. - - **Location**: Select the location, usually a room, where the device is located (`Kitchen`, `Attic`, `Living room`, etc.) or add a custom Location. + - **Location**: Select the location, usually a room, where the device is located (`Kitchen`, `Attic`, `Living room`, etc.) or add a custom Location. - **Comments**: Add any comments for the device, such as a serial number, or maintenance information. -> [!NOTE] +> [!NOTE] > -> Please note the above usage of the fields are only suggestions. You can use most of these fields for other purposes, such as storing the network interface, company owning a device, or similar. +> Please note the above usage of the fields are only suggestions. You can use most of these fields for other purposes, such as storing the network interface, company owning a device, or similar. ## Dummy devices -You can create dummy devices from the Devices listing screen. +You can create dummy devices from the Devices listing screen. ![Create Dummy Device](./img/DEVICE_MANAGEMENT/Devices_CreateDummyDevice.png) @@ -39,12 +39,12 @@ The **MAC** field and the **Last IP** field will then become editable. ![Save Dummy Device](./img/DEVICE_MANAGEMENT/DeviceEdit_SaveDummyDevice.png) -> [!NOTE] +> [!NOTE] > > You can couple this with the `ICMP` plugin which can be used to monitor the status of these devices, if they are actual devices reachable with the `ping` command. If not, you can use a loopback IP address so they appear online, such as `0.0.0.0` or `127.0.0.1`. -## Copying data from an existing device. +## Copying data from an existing device. -To speed up device population you can also copy data from an existing device. This can be done from the **Tools** tab on the Device details. +To speed up device population you can also copy data from an existing device. This can be done from the **Tools** tab on the Device details. diff --git a/docs/HELPER_SCRIPTS.md b/docs/HELPER_SCRIPTS.md index 628ea19b..fa4ea6b3 100755 --- a/docs/HELPER_SCRIPTS.md +++ b/docs/HELPER_SCRIPTS.md @@ -1,4 +1,4 @@ -# NetAlertX Community Helper Scripts Overview +# Community Helper Scripts Overview This page provides an overview of community-contributed scripts for NetAlertX. These scripts are not actively maintained and are provided as-is. @@ -14,8 +14,8 @@ You can find all scripts in this [scripts GitHub folder](https://github.com/joko ## Important Notes -> [!NOTE] -> These scripts are community-supplied and not actively maintained. Use at your own discretion. +> [!NOTE] +> These scripts are community-supplied and not actively maintained. Use at your own discretion. For detailed usage instructions, refer to each script's documentation in each [scripts GitHub folder](https://github.com/jokob-sk/NetAlertX/tree/main/scripts). diff --git a/docs/HW_INSTALL.md b/docs/HW_INSTALL.md index 814230da..e34535cf 100755 --- a/docs/HW_INSTALL.md +++ b/docs/HW_INSTALL.md @@ -5,7 +5,7 @@ To download and install NetAlertX on the hardware/server directly use the `curl` > [!NOTE] > This is an Experimental feature 🧪 and it relies on community support. > -> 🙏 Looking for maintainers for this installation method 🙂 Current community volunteers: +> 🙏 Looking for maintainers for this installation method 🙂 Current community volunteers: > - [slammingprogramming](https://github.com/slammingprogramming) > - [ingoratsdorf](https://github.com/ingoratsdorf) > @@ -13,8 +13,7 @@ To download and install NetAlertX on the hardware/server directly use the `curl` > Data loss is a possibility, **it is recommended to install NetAlertX using the supplied Docker image**. > [!WARNING] -> A warning to the installation method below: Piping to bash is [controversial](https://pi-hole.net/2016/07/25/curling-and-piping-to-bash) and may -be dangerous, as you cannot see the code that's about to be executed on your system. +> A warning to the installation method below: Piping to bash is [controversial](https://pi-hole.net/2016/07/25/curling-and-piping-to-bash) and may be dangerous, as you cannot see the code that's about to be executed on your system. If you trust this repo, you can download the install script via one of the methods (curl/wget) below and it will fo its best to install NetAlertX on your system. @@ -40,7 +39,7 @@ Some facts about what and where something will be changed/installed by the HW in - Only tested to work on the system listed in the install directory. - **EXPERIMENTAL** and not recommended way to install NetAlertX. -> [!TIP] +> [!TIP] > If the below fails try grabbing and installing one of the [previous releases](https://github.com/jokob-sk/NetAlertX/releases) and run the installation from the zip package. These commands will download the `install.debian12.sh` script from the GitHub repository, make it executable with `chmod`, and then run it using `./install.debian12.sh`. @@ -81,7 +80,7 @@ wget https://raw.githubusercontent.com/jokob-sk/NetAlertX/main/install/ubuntu24/ > [!NOTE] > Use this on a clean LXC/VM for Debian 13 OR Ubuntu 24. -> The Scipt will detect OS and build acordingly. +> The Scipt will detect OS and build acordingly. > Maintained by [JVKeller](https://github.com/JVKeller) ### Installation via wget diff --git a/docs/MIGRATION.md b/docs/MIGRATION.md index fb112405..d1d08e1b 100755 --- a/docs/MIGRATION.md +++ b/docs/MIGRATION.md @@ -218,7 +218,7 @@ services: ### 1.3 Migration from NetAlertX `v25.10.1` -Starting from v25.10.1, the container uses a [more secure, read-only runtime environment](./SECURITY_FEATURES.md), which requires all writable paths (e.g., logs, API cache, temporary data) to be mounted as `tmpfs` or permanent writable volumes, with sufficient access [permissions](./FILE_PERMISSIONS.md). +Starting from v25.10.1, the container uses a [more secure, read-only runtime environment](./SECURITY_FEATURES.md), which requires all writable paths (e.g., logs, API cache, temporary data) to be mounted as `tmpfs` or permanent writable volumes, with sufficient access [permissions](./FILE_PERMISSIONS.md). The data location has also hanged from `/app/db` and `/app/config` to `/data/db` and `/data/config`. See detailed steps below. #### STEPS: @@ -234,8 +234,8 @@ services: network_mode: "host" restart: unless-stopped volumes: - - /local_data_dir/config:/data/config - - /local_data_dir/db:/data/db + - /local_data_dir/config:/app/config + - /local_data_dir/db:/app/db # (optional) useful for debugging if you have issues setting up the container - /local_data_dir/logs:/tmp/log environment: @@ -284,10 +284,8 @@ services: - NET_BIND_SERVICE # 🆕 New line restart: unless-stopped volumes: - - /local_data_dir/config:/data/config - - /local_data_dir/db:/data/db - # (optional) useful for debugging if you have issues setting up the container - #- /local_data_dir/logs:/tmp/log + - /local_data_dir/config:/data/config # 🆕 This has changed from /app to /data + - /local_data_dir/db:/data/db # 🆕 This has changed from /app to /data # Ensuring the timezone is the same as on the server - make sure also the TIMEZONE setting is configured - /etc/localtime:/etc/localtime:ro # 🆕 New line environment: diff --git a/docs/SESSION_INFO.md b/docs/SESSION_INFO.md index 757a9746..092b9288 100755 --- a/docs/SESSION_INFO.md +++ b/docs/SESSION_INFO.md @@ -1,62 +1,64 @@ -# Sessions Section in Device View +# Sessions Section – Device View -The **Sessions Section** provides details about a device's connection history. This data is automatically detected and cannot be edited by the user. +The **Sessions Section** shows a device’s connection history. All data is automatically detected and **cannot be edited**. - ![Session info](./img/SESSION_INFO/DeviceDetails_SessionInfo.png) +![Session info](./img/SESSION_INFO/DeviceDetails_SessionInfo.png) --- ## Key Fields -1. **Date and Time of First Connection** - - **Description:** Displays the first detected connection time for the device. - - **Editability:** Uneditable (auto-detected). - - **Source:** Automatically captured when the device is first added to the system. - -2. **Date and Time of Last Connection** - - **Description:** Shows the most recent time the device was online. - - **Editability:** Uneditable (auto-detected). - - **Source:** Updated with every new connection event. - -3. **Offline Devices with Missing or Conflicting Data** - - **Description:** Handles cases where a device is offline but has incomplete or conflicting session data (e.g., missing start times). - - **Handling:** The system flags these cases for review and attempts to infer missing details. +| Field | Description | Editable? | +| ------------------------------ | ------------------------------------------------------------------------------------------------ | --------------- | +| **First Connection** | The first time the device was detected on the network. | ❌ Auto-detected | +| **Last Connection** | The most recent time the device was online. | ❌ Auto-detected | --- -## How Sessions are Discovered and Calculated +## How Session Information Works ### 1. Detecting New Devices -When a device is first detected in the network, the system logs it in the events table: -`INSERT INTO Events (eve_MAC, eve_IP, eve_DateTime, eve_EventType, eve_AdditionalInfo, eve_PendingAlertEmail) SELECT cur_MAC, cur_IP, '{startTime}', 'New Device', cur_Vendor, 1 FROM CurrentScan WHERE NOT EXISTS (SELECT 1 FROM Devices WHERE devMac = cur_MAC)` +* New devices are automatically detected when they first appear on the network. +* A **New Device** record is created, capturing the MAC, IP, vendor, and detection time. -- Devices scanned in the current cycle (**CurrentScan**) are checked against the **Devices** table. -- If a device is new: - - A **New Device** event is logged. - - The device’s MAC, IP, vendor, and detection time are recorded. +### 2. Recording Connection Sessions -### 2. Logging Connection Sessions -When a new connection is detected, the system creates a session record: +* Every time a device connects, a session entry is created. +* Captured details include: -`INSERT INTO Sessions (ses_MAC, ses_IP, ses_EventTypeConnection, ses_DateTimeConnection, ses_EventTypeDisconnection, ses_DateTimeDisconnection, ses_StillConnected, ses_AdditionalInfo) SELECT cur_MAC, cur_IP, 'Connected', '{startTime}', NULL, NULL, 1, cur_Vendor FROM CurrentScan WHERE NOT EXISTS (SELECT 1 FROM Sessions WHERE ses_MAC = cur_MAC)` - -- A new session is logged in the **Sessions** table if no prior session exists. -- Fields like `MAC`, `IP`, `Connection Type`, and `Connection Time` are populated. -- The `Still Connected` flag is set to `1` (active connection). + * Connection type (wired or wireless) + * Connection time + * Device details (MAC, IP, vendor) ### 3. Handling Missing or Conflicting Data -- Devices with incomplete or conflicting session data (e.g., missing start times) are detected. -- The system flags these records and attempts corrections by inferring details from available data. + +* **Triggers:** + Devices are flagged when session data is incomplete, inconsistent, or conflicting. Examples include: + + * Missing first or last connection timestamps + * Overlapping session records + * Sessions showing a device as connected and disconnected at the same time + +* **System response:** + + * Automatically highlights affected devices in the **Sessions Section**. + * Attempts to **infer missing information** from available data, such as: + + * Estimating first or last connection times from nearby session events + * Correcting overlapping session periods + * Reconciling conflicting connection statuses + +* **User impact:** + + * Users do **not** need to manually fix session data. + * The system ensures the device’s connection history remains as accurate as possible for monitoring and reporting. ### 4. Updating Sessions -- When a device reconnects, its session is updated with a new connection timestamp. -- When a device disconnects: - - The **Disconnection Time** is recorded. - - The `Still Connected` flag is set to `0`. -The session information is then used to display the device presence under **Monitoring** -> **Presence**. +* **Reconnect:** Updates session with the new connection timestamp. +* **Disconnect:** Records disconnection time and marks the device as offline. + +This session information feeds directly into **Monitoring → Presence**, providing a live view of which devices are currently online. ![Monitoring Device Presence](./img/SESSION_INFO/Monitoring_Presence.png) - - diff --git a/docs/UPDATES.md b/docs/UPDATES.md index 2ac560d8..2d398dde 100755 --- a/docs/UPDATES.md +++ b/docs/UPDATES.md @@ -1,7 +1,8 @@ # Docker Update Strategies to upgrade NetAlertX -> [!WARNING] +> [!WARNING] > For versions prior to `v25.6.7` upgrade to version `v25.5.24` first (`docker pull ghcr.io/jokob-sk/netalertx:25.5.24`) as later versions don't support a full upgrade. Alternatively, devices and settings can be migrated manually, e.g. via [CSV import](./DEVICES_BULK_EDITING.md). +> See the [Migration guide](./MIGRATION.md) for details. This guide outlines approaches for updating Docker containers, usually when upgrading to a newer version of NetAlertX. Each method offers different benefits depending on the situation. Here are the methods: @@ -15,7 +16,7 @@ You can choose any approach that fits your workflow. > In the examples I assume that the container name is `netalertx` and the image name is `netalertx` as well. > [!NOTE] -> See also [Backup strategies](./BACKUPS.md) to be on the safe side. +> See also [Backup strategies](./BACKUPS.md) to be on the safe side. ## 1. Manual Updates @@ -48,7 +49,7 @@ sudo docker-compose up --pull always -d ## 2. Dockcheck for Bulk Container Updates -Always check the [Dockcheck](https://github.com/mag37/dockcheck) docs if encountering issues with the guide below. +Always check the [Dockcheck](https://github.com/mag37/dockcheck) docs if encountering issues with the guide below. Dockcheck is a useful tool if you have multiple containers to update and some flexibility for handling potential issues that might arise during mass updates. Dockcheck allows you to inspect each container and decide when to update. @@ -74,7 +75,7 @@ sudo ./dockcheck.sh ## 3. Automated Updates with Watchtower -Always check the [watchtower](https://github.com/containrrr/watchtower) docs if encountering issues with the guide below. +Always check the [watchtower](https://github.com/containrrr/watchtower) docs if encountering issues with the guide below. Watchtower monitors your Docker containers and automatically updates them when new images are available. This is ideal for ongoing updates without manual intervention. @@ -96,7 +97,7 @@ docker run -d \ --interval 300 # Check for updates every 5 minutes ``` -#### 3. Run Watchtower to update only NetAlertX: +#### 3. Run Watchtower to update only NetAlertX: You can specify which containers to monitor by listing them. For example, to monitor netalertx only: diff --git a/front/css/app.css b/front/css/app.css index 5d15b426..8c67112e 100755 --- a/front/css/app.css +++ b/front/css/app.css @@ -1,6 +1,6 @@ /* ----------------------------------------------------------------------------- # NetAlertX -# Open Source Network Guard / WIFI & LAN intrusion detector +# Open Source Network Guard / WIFI & LAN intrusion detector # # app.css - Front module. CSS styles #------------------------------------------------------------------------------- @@ -36,7 +36,7 @@ a[target="_blank"] { display: inline-block; /* Needed for positioning */ padding-right: 0.6em; /* Space for the icon */ } - + a[target="_blank"]::after { content: '↗'; position: absolute; @@ -55,7 +55,7 @@ a[target="_blank"] { right: -7px; top: 1px; } */ - + /* .select2-container--default .select2-selection--multiple .select2-selection__choice { padding-right: 15px !important; @@ -70,6 +70,11 @@ a[target="_blank"] { opacity: 1; } +[data-is-valid="0"] { + /* border: 1px solid red; */ + background-color: #ff4b4b; +} + /* ----------------------------------------------------------------------------- Helper Classes ----------------------------------------------------------------------------- */ @@ -100,7 +105,7 @@ a[target="_blank"] { background-color: black; font-family: 'Courier New', monospace; font-size: .85em; - + } .logs-row textarea { @@ -110,12 +115,12 @@ a[target="_blank"] { display:contents; position: relative; padding: 0.4em - + } #tab_Logging .actions .toggle{ - margin: 0.5em; + margin: 0.5em; height: 3em; } @@ -134,8 +139,8 @@ a[target="_blank"] { } .log-area { - padding: 3px; - width:100%; + padding: 3px; + width:100%; border-bottom-width: 1px; border-bottom-style: solid; border-color: #606060; @@ -246,7 +251,7 @@ a[target="_blank"] { { padding:8px; color: white; -} +} .header-status { @@ -262,7 +267,7 @@ a[target="_blank"] { position: absolute; top: 3px; margin-left: 15px; - display: none; + display: none; } @@ -298,9 +303,9 @@ body .NetAlertX-logo { - border-color:transparent !important; - height: 50px !important; - width: 50px !important; + border-color:transparent !important; + height: 50px !important; + width: 50px !important; margin-top:15px !important; border-radius: 1px !important; } @@ -327,7 +332,7 @@ body .content-wrapper, .right-side, .main-footer { - margin-left: 150px; + margin-left: 150px; } @@ -740,7 +745,7 @@ body text-decoration: underline; } -#ticker-message +#ticker-message { color:#FFFFFF; } @@ -774,7 +779,7 @@ body .file-checking .icon-wrap{ width: 200px; overflow: hidden; - text-overflow: ellipsis; + text-overflow: ellipsis; display: block; } @@ -788,7 +793,7 @@ body .file-checking .file-name-wrap{ overflow: hidden; - text-overflow: ellipsis; + text-overflow: ellipsis; display: flex; padding: 5px; } @@ -796,7 +801,7 @@ body .file-checking{ display: block; overflow: hidden; - text-overflow: ellipsis; + text-overflow: ellipsis; } @@ -854,16 +859,16 @@ body .db_tools_table_cell_a { display: table-cell; - text-align: center; - padding: 10px; - min-width: 180px; - width: 20%; + text-align: center; + padding: 10px; + min-width: 180px; + width: 20%; vertical-align: middle; } .db_tools_table_cell_b { display: table-cell; - text-align: justify; - font-size: 16px; + text-align: justify; + font-size: 16px; vertical-align: middle; padding: 10px; } @@ -876,12 +881,12 @@ height: 50px; } .nav-tabs-custom .tab-content { - background-color: white; - + background-color: white; + } @media (max-width: 767px) { - .nav-tabs-custom .tab-content { + .nav-tabs-custom .tab-content { overflow: scroll; } } @@ -898,7 +903,7 @@ height: 50px; font-size: 16px !important; } -.deviceSelector +.deviceSelector { display: block; } @@ -935,7 +940,7 @@ height: 50px; height: 10px; display: inline-block; /* background: #fff; */ - opacity: .75; + opacity: .75; } /* --------------------------------------------------------- */ @@ -979,32 +984,32 @@ height: 50px; } /* .setting_input{ width:70%; - + } .setting_name { - width:30%; + width:30%; } */ } @media (min-width: 768px) { -.setting_description { +.setting_description { /* color: green; */ display: block; } /* .setting_input{ - width:40%; + width:40%; } .setting_name { - width:19%; + width:19%; } */ } /* Hide unusable buttons on the settings page for the NEWDEV plugin*/ -#settingsPage #add_option_NEWDEV_devGroup, -#settingsPage #add_option_NEWDEV_devLocation, +#settingsPage #add_option_NEWDEV_devGroup, +#settingsPage #add_option_NEWDEV_devLocation, #settingsPage #add_option_NEWDEV_devOwner, #settingsPage #copy_icons_NEWDEV_devIcon, #settingsPage #add_icon_NEWDEV_devIcon, @@ -1024,11 +1029,11 @@ height: 50px; #settingsPage .small-box .inner .card-title { overflow: hidden; - text-overflow: ellipsis; + text-overflow: ellipsis; white-space: nowrap; color: white; } - + .settingswrap { @@ -1048,13 +1053,13 @@ height: 50px; .padding-bottom { padding-bottom: 100px; -} +} .settings-group -{ +{ font-size: 20px; padding-top: 7px; - padding-bottom: 9px; + padding-bottom: 9px; } .overview-section .small-box .icon @@ -1069,7 +1074,7 @@ height: 50px; } .overview-group -{ +{ font-size: 20px; padding-top: 7px; padding-bottom: 9px; @@ -1082,8 +1087,8 @@ height: 50px; } -#settingsPage .table_row { - padding: 3px; +#settingsPage .table_row { + padding: 3px; /* width:100%; */ /* display: flex; */ border-bottom-width: 1px; @@ -1102,7 +1107,7 @@ height: 50px; .setting_name { /* width:19%; */ - font-weight: 300; + font-weight: 300; } @@ -1111,24 +1116,24 @@ height: 50px; display:none !important; } -.center +.center { margin: 0; - position: relative; + position: relative; left: 50%; -ms-transform: translate(-50%, -50%); transform: translate(-50%, -50%); } -.top-margin +.top-margin { margin-top: 50px; } /* Settings */ -#settingsPage .overview-setting-value{ - display:unset; +#settingsPage .overview-setting-value{ + display:unset; } @@ -1165,7 +1170,7 @@ height: 50px; } .text-overflow-hidden -{ +{ overflow: hidden; text-overflow: clip; } @@ -1175,9 +1180,9 @@ height: 50px; padding: 10px; /* background-color: #272c30; */ margin: 10px; - + } -#settingsPage .panel-heading:hover{ +#settingsPage .panel-heading:hover{ background-color: #272c30; } @@ -1185,12 +1190,12 @@ height: 50px; font-size: medium; /* background-color: #272c30; */ margin: 10px; - + } -.settings_content input[type=checkbox] -{ - width: auto +.settings_content input[type=checkbox] +{ + width: auto } .override{ @@ -1212,7 +1217,7 @@ height: 50px; input[readonly] { /* Apply styles to the readonly input */ background-color: #646566 !important; - color: #e6e6e6; + color: #e6e6e6; cursor: not-allowed; } @@ -1300,7 +1305,7 @@ input[readonly] { /* margin-bottom:20px; */ } -#settingsPage .select2-selection +#settingsPage .select2-selection { width: initial; display: inline-block; @@ -1314,8 +1319,8 @@ input[readonly] { #settingsPage .select2-selection { background-color: rgb(96, 96, 96); -} -#settingsPage .select2-container +} +#settingsPage .select2-container { width: 100% !important; } @@ -1398,7 +1403,7 @@ input[readonly] { backdrop-filter: brightness(50%); } -.iconPreviewSelector +.iconPreviewSelector { text-align: center; padding: 15px; @@ -1440,7 +1445,7 @@ input[readonly] { } -.dummyDevice +.dummyDevice { text-align: end; } @@ -1461,7 +1466,7 @@ input[readonly] { } .info-icon-nav -{ +{ top: -6px; position: absolute; z-index: 1; @@ -1538,7 +1543,7 @@ input[readonly] { } #panDetails .input-group { - + min-height: 40px; } @@ -1583,7 +1588,7 @@ input[readonly] { } .devicePropAction -{ +{ width: 1.2em; height: 1.2em; display: inline-block; @@ -1593,11 +1598,11 @@ input[readonly] { } .devicePropAction:hover -{ +{ font-size: larger; padding: 0em; margin: 0em; - + } @@ -1607,7 +1612,7 @@ input[readonly] { display: block; float:inline-end; height: 2em; -} +} #panDetails .dataTables_wrapper .bottom .dataTables_info { @@ -1636,22 +1641,22 @@ input[readonly] { height: 14px; } -#deviceDetailsEdit .select2-container--default .select2-selection--multiple .select2-selection__choice +#deviceDetailsEdit .select2-container--default .select2-selection--multiple .select2-selection__choice { height: 20px; } -#deviceDetailsEdit .select2-container--disabled +#deviceDetailsEdit .select2-container--disabled { - background-color: #606060; + background-color: #606060; } -#deviceDetailsEdit .select2-container--default .select2-selection--multiple .select2-selection__choice span +#deviceDetailsEdit .select2-container--default .select2-selection--multiple .select2-selection__choice span { font-size: 14px; } -#deviceDetailsEdit .select2-selection +#deviceDetailsEdit .select2-selection { width: initial; display: inline-block; @@ -1681,7 +1686,7 @@ input[readonly] { font-size: 14px; } .custom-badge -{ +{ border: 1px solid #aaa; border-radius: 4px; border-style: solid; @@ -1716,7 +1721,7 @@ input[readonly] { } -#deviceDetailsEdit .select2-container +#deviceDetailsEdit .select2-container { width: 100% !important; } @@ -1799,7 +1804,7 @@ input[readonly] { z-index: 5; } #networkTree .netNodeText -{ +{ position: absolute; } #networkTree .netPort @@ -1812,7 +1817,7 @@ input[readonly] { #networkTree .portBckgIcon { opacity: 0.3; - display: initial; + display: initial; float: left; width: 1em; } @@ -1822,7 +1827,7 @@ input[readonly] { margin-left: 16px; /* border: solid; border-color:#606060; */ - position: relative; + position: relative; } #networkTree .netIcon { @@ -1850,8 +1855,8 @@ input[readonly] { } #hover-box .devName -{ - font-size: larger; +{ + font-size: larger; display: contents; } @@ -1910,7 +1915,7 @@ input[readonly] { #networkTree .highlightedNode { /* border: solid; */ - border-color:var(--color-lightblue); + border-color:var(--color-lightblue); box-shadow: var(--color-lightblue) 0px 0px 20px; } @@ -1968,7 +1973,7 @@ input[readonly] { } .sort-btn { - + right: 5px; top: 50%; transform: translateY(-50%); @@ -2020,7 +2025,7 @@ input[readonly] { } .plugin-filters -{ +{ margin: 7px; margin-right: 7px; margin-bottom: 9px; @@ -2054,7 +2059,7 @@ input[readonly] { } .plugin-content #tabs-content-location -{ +{ margin: 0px; padding-top: 0; } @@ -2066,7 +2071,7 @@ input[readonly] { } .plugin-content .tab-content -{ +{ padding-top: 10px; } @@ -2103,7 +2108,7 @@ input[readonly] { @media (max-width: 500px) { .header-server-time { - display: none; + display: none; } } @@ -2234,12 +2239,12 @@ input[readonly] { display: grid; } -#workflowContainerWrap .panel-collapse +#workflowContainerWrap .panel-collapse { padding: 5px; } -.workflows +.workflows { max-width: 800px; } @@ -2285,7 +2290,7 @@ input[readonly] { color: #000; } -.workflows .button-container +.workflows .button-container { /* display: contents; */ text-align: center; @@ -2305,7 +2310,7 @@ input[readonly] { margin: 5px; } -.workflows .button-container +.workflows .button-container { padding-right: 0px !important; padding-left: 0px !important; @@ -2318,19 +2323,19 @@ input[readonly] { /* .button-container button { - width:100%; + width:100%; } */ .red-hover-text:hover { - color: var(--color-red) !important; + color: var(--color-red) !important; } .green-hover-text:hover { color: var(--color-green) !important; } - + .workflows .bckg-icon-1-line { font-size: 3em; @@ -2362,7 +2367,7 @@ input[readonly] { z-index: 1; } -.workflows .workflow-card +.workflows .workflow-card { display: block; } @@ -2372,7 +2377,7 @@ input[readonly] { padding: 10px; } -.workflow-card, .actions-list +.workflow-card, .actions-list { display: contents; padding: 5px; @@ -2384,7 +2389,7 @@ input[readonly] { z-index:1; } -.condition +.condition { padding: 5px; padding-left: 10px; diff --git a/front/js/modal.js b/front/js/modal.js index 54073067..dbcf5e10 100755 --- a/front/js/modal.js +++ b/front/js/modal.js @@ -96,7 +96,7 @@ function showModalInput( btnOK = getString("Gen_Okay"), callbackFunction = null, triggeredBy = null, - defaultValue = "" + defaultValue = "" ) { prefix = "modal-input"; @@ -121,7 +121,7 @@ function showModalInput( setTimeout(function () { $(`#${prefix}-textarea`).focus(); }, 500); - + } // ----------------------------------------------------------------------------- @@ -143,7 +143,7 @@ function showModalFieldInput( $(`#${prefix}-OK`).html(btnOK); if (callbackFunction != null) { - + modalCallbackFunction = callbackFunction; } @@ -181,11 +181,11 @@ function showModalPopupForm( $(`#${prefix}-cancel`).html(btnCancel); $(`#${prefix}-OK`).html(btnOK); - // if curValue not null + // if curValue not null if (curValue) { - initialValues = JSON.parse(atob(curValue)); + initialValues = JSON.parse(atob(curValue)); } outputHtml = ""; @@ -193,7 +193,7 @@ function showModalPopupForm( if (Array.isArray(popupFormJson)) { popupFormJson.forEach((field, index) => { // You'll need to define these or map them from `field` - const setKey = field.function || `field_${index}`; + const setKey = field.function || `field_${index}`; const setName = getString(`${parentSettingKey}_popupform_${setKey}_name`); const labelClasses = "col-sm-2"; // example, or from your obj.labelClasses const inputClasses = "col-sm-10"; // example, or from your obj.inputClasses @@ -207,9 +207,9 @@ function showModalPopupForm( } } - const fieldOptionsOverride = field.type?.elements[0]?.elementOptions || []; + const fieldOptionsOverride = field.type?.elements[0]?.elementOptions || []; const setValue = initialValue; - const setType = JSON.stringify(field.type); + const setType = JSON.stringify(field.type); const setEvents = field.events || []; // default to empty array if missing const setObj = { setKey, setValue, setType, setEvents }; @@ -218,17 +218,17 @@ function showModalPopupForm(
${generateFormHtml( null, // settingsData only required for datatables - setObj, - null, - fieldOptionsOverride, + setObj, + null, + fieldOptionsOverride, null )}
@@ -239,7 +239,7 @@ function showModalPopupForm( outputHtml += inputFormHtml; }); } - + $(`#modal-form-plc`).html(outputHtml); // Bind OK button click event @@ -247,12 +247,19 @@ function showModalPopupForm( let settingsArray = []; if (Array.isArray(popupFormJson)) { popupFormJson.forEach(field => { - collectSetting( + const result = collectSetting( `${parentSettingKey}_popupform`, // prefix field.function, // setCodeName field.type, // setType (object) settingsArray ); + settingsArray = result.settingsArray; + + if (!result.dataIsValid) { + msg = getString("Gen_Invalid_Value") + ":" + result.failedSettingKey; + console.error(msg); + showModalOk("ERROR", msg); + } }); } @@ -276,7 +283,7 @@ function showModalPopupForm( const newOption = $("") .attr("value", encodedValue) .text(label); - + $("#" + selectId).append(newOption); initListInteractionOptions(newOption); } @@ -429,10 +436,10 @@ function safeDecodeURIComponent(content) { return content; // Return the original content if decoding fails } } - + // ----------------------------------------------------------------------------- -// Backend notification Polling +// Backend notification Polling // ----------------------------------------------------------------------------- // Function to check for notifications function checkNotification() { @@ -440,7 +447,7 @@ function checkNotification() { const phpEndpoint = 'php/server/utilNotification.php'; $.ajax({ - url: notificationEndpoint, + url: notificationEndpoint, type: 'GET', success: function(response) { // console.log(response); @@ -492,7 +499,7 @@ function checkNotification() { }, error: function() { console.warn(`🟥 Error checking ${notificationEndpoint}`) - + } }); } @@ -582,7 +589,7 @@ const phpEndpoint = 'php/server/utilNotification.php'; // -------------------------------------------------- // Write a notification -function write_notification(content, level) { +function write_notification(content, level) { $.ajax({ url: phpEndpoint, // Change this to the path of your PHP script @@ -603,8 +610,8 @@ function write_notification(content, level) { // -------------------------------------------------- // Write a notification -function markNotificationAsRead(guid) { - +function markNotificationAsRead(guid) { + $.ajax({ url: phpEndpoint, type: 'GET', @@ -628,8 +635,8 @@ function markNotificationAsRead(guid) { // -------------------------------------------------- // Remove a notification -function removeNotification(guid) { - +function removeNotification(guid) { + $.ajax({ url: phpEndpoint, type: 'GET', diff --git a/front/js/settings_utils.js b/front/js/settings_utils.js index c0056a66..b567c532 100755 --- a/front/js/settings_utils.js +++ b/front/js/settings_utils.js @@ -71,7 +71,7 @@ function getPluginConfig(pluginsData, prefix) { // Show the description of a setting function showDescriptionPopup(e) { - console.log($(e).attr("my-set-key")); + console.log($(e).attr("my-set-key")); showModalOK("Info", getString($(e).attr("my-set-key") + '_description')) } @@ -92,13 +92,13 @@ function pluginCards(prefixesOfEnabledPlugins, includeSettings) { prefix + "_" + set }"> ${getSetting(prefix + "_" + set)} -
+ `; }); - html += ` + html += `
@@ -110,10 +110,10 @@ function pluginCards(prefixesOfEnabledPlugins, includeSettings) { ${includeSettings_html}
-
${getString(prefix + "_icon")}
-
+
${getString(prefix + "_icon")}
+
- +
`; }); @@ -251,17 +251,17 @@ function settingsCollectedCorrectly(settingsArray, settingsJSON_DB) { function cloneDataTableRow(el){ console.log(el); - + const id = "NEWDEV_devCustomProps_table"; // Your table ID const table = $('#'+id).DataTable(); - + // Get the 'my-index' attribute from the closest tr element const myIndex = parseInt($(el).closest("tr").attr("my-index")); // Find the row in the table with the matching 'my-index' const row = table.rows().nodes().to$().filter(`[my-index="${myIndex}"]`).first().get(0); - + // Clone the row (including its data and controls) let clonedRow = $(row).clone(true, true); // The true arguments copy the data and event handlers @@ -270,7 +270,7 @@ function cloneDataTableRow(el){ console.log(clonedRow); - + // Add the cloned row to the DataTable table.row.add(clonedRow[0]).draw(); @@ -291,13 +291,13 @@ function removeDataTableRow(el) { // Find the row in the table with the matching 'my-index' const row = table.rows().nodes().to$().filter(`[my-index="${myIndex}"]`).first().get(0); - + // Remove the row from the DataTable table.row(row).remove().draw(); } else { - showMessage (getString("CustProps_cant_remove"), 3000, "modal_red"); + showMessage (getString("CustProps_cant_remove"), 3000, "modal_red"); } } @@ -308,9 +308,9 @@ function addViaPopupForm(element) { const toId = $(element).attr("my-input-to"); const curValue = $(`#${toId}`).val(); - const parsed = JSON.parse(atob($(`#${toId}`).data("elementoptionsbase64"))); + const parsed = JSON.parse(atob($(`#${toId}`).data("elementoptionsbase64"))); const popupFormJson = parsed.find(obj => "popupForm" in obj)?.popupForm ?? null; - + console.log(`toId | curValue: ${toId} | ${curValue}`); showModalPopupForm( @@ -393,7 +393,7 @@ function selectAll(element) { settingsChanged(); var selectElement = $(`#${$(element).attr("my-input-to")}`); - + // Iterate over each option within the select element selectElement.find('option').each(function() { // Mark each option as selected @@ -409,13 +409,13 @@ function selectAll(element) { function unselectAll(element) { settingsChanged(); var selectElement = $(`#${$(element).attr("my-input-to")}`); - + // Iterate over each option within the select element selectElement.find('option').each(function() { // Unselect each option $(this).prop('selected', false); }); - + // Trigger the 'change' event to notify Bootstrap Select of the changes selectElement.trigger('change'); } @@ -426,7 +426,7 @@ function selectChange(element) { settingsChanged(); var selectElement = $(`#${$(element).attr("my-input-to")}`); - + selectElement.parent().find("input").focus().click(); } @@ -464,9 +464,9 @@ function initListInteractionOptions(element) { // Parent has my-transformers="name|base64" const toId = $parent.attr("id"); const curValue = $option.val(); - const parsed = JSON.parse(atob($parent.data("elementoptionsbase64"))); + const parsed = JSON.parse(atob($parent.data("elementoptionsbase64"))); const popupFormJson = parsed.find(obj => "popupForm" in obj)?.popupForm ?? null; - + showModalPopupForm( ` ${getString("Gen_Update_Value")}`, // title "", // message @@ -515,8 +515,8 @@ function filterRows(inputText) { var $panelHeader = $panel.find('.panel-heading'); var $panelBody = $panel.find('.panel-collapse'); - $panel.show() - $panelHeader.show() + $panel.show() + $panelHeader.show() $panelBody.collapse('show'); $panelBody.find(".table_row:not(.docs)").each(function () { @@ -525,11 +525,11 @@ function filterRows(inputText) { var isMetadataRow = rowId && rowId.endsWith("__metadata"); if (!isMetadataRow) { $row.show() - } + } }); - + }); - + } else{ // filter @@ -537,25 +537,25 @@ function filterRows(inputText) { var $panel = $(this); var $panelHeader = $panel.find('.panel-heading'); var $panelBody = $panel.find('.panel-collapse'); - + var anyVisible = false; // Flag to check if any row is visible - + $panelBody.find(".table_row:not(.docs)").each(function () { var $row = $(this); - + // Check if the row ID ends with "__metadata" var rowId = $row.attr("id"); var isMetadataRow = rowId && rowId.endsWith("__metadata"); - + // Always hide metadata rows if (isMetadataRow) { $row.hide(); return; // Skip further processing for metadata rows } - + var description = $row.find(".setting_description").text().toLowerCase(); var setKey = $row.find(".setting_name code").text().toLowerCase(); - + if ( description.includes(inputText.toLowerCase()) || setKey.includes(inputText.toLowerCase()) @@ -566,7 +566,7 @@ function filterRows(inputText) { $row.hide(); } }); - + // Determine whether to hide or show the panel based on visibility of rows if (anyVisible) { $panelBody.collapse('show'); // Ensure the panel body is shown if there are visible rows @@ -582,7 +582,7 @@ function filterRows(inputText) { } - + } @@ -661,7 +661,7 @@ function generateOptionsOrSetOptions( processDataCallback, // Callback function to generate entries based on options targetField, // Target field or element where selected value should be applied or updated transformers = [], // Transformers to be applied to the values - overrideOptions = null // override options if available + overrideOptions = null // override options if available ) { // console.log(setKey); @@ -712,7 +712,7 @@ function applyTransformers(val, transformers) { break; case "getString": // no change - val = val; + val = val; break; default: console.warn(`Unknown transformer: ${transformer}`); @@ -745,13 +745,13 @@ function reverseTransformers(val, transformers) { break; case "getString": // retrieve string - val = getString(val); + val = getString(val); break; case "deviceChip": - mac = val // value is mac + mac = val // value is mac val = `${getDevDataByMac(mac, "devName")}` break; - case "deviceRelType": + case "deviceRelType": val = val; // nothing to do break; default: @@ -779,10 +779,11 @@ const handleElementOptions = (setKey, elementOptions, transformers, val) => { let getStringKey = ""; let onClick = "console.log('onClick - Not implemented');"; let onChange = "console.log('onChange - Not implemented');"; + let focusout = "console.log('focusout - Not implemented');"; let customParams = ""; let customId = ""; let columns = []; - let base64Regex = ""; + let base64Regex = ""; let elementOptionsBase64 = btoa(JSON.stringify(elementOptions)); elementOptions.forEach((option) => { @@ -830,6 +831,9 @@ const handleElementOptions = (setKey, elementOptions, transformers, val) => { if (option.onChange) { onChange = option.onChange; } + if (option.focusout) { + focusout = option.focusout; + } if (option.customParams) { customParams = option.customParams; } @@ -867,7 +871,8 @@ const handleElementOptions = (setKey, elementOptions, transformers, val) => { customId, columns, base64Regex, - elementOptionsBase64 + elementOptionsBase64, + focusout }; }; @@ -877,7 +882,7 @@ const handleElementOptions = (setKey, elementOptions, transformers, val) => { // ----------------------------------------------------------------------------- // -------------------------------------------------- -// Creates an object from an array +// Creates an object from an array function arrayToObject(array) { const obj = []; array.forEach((item, index) => { @@ -895,18 +900,18 @@ function generateOptions(options, valuesArray, targetField, transformers, placeh resultArray = [] selectedArray = [] - cssClass = "" + cssClass = "" // determine if options or values are used in the listing if (valuesArray.length > 0 && options.length > 0){ - // multiselect list -> options only + selected the ones in valuesArray + // multiselect list -> options only + selected the ones in valuesArray resultArray = options; selectedArray = valuesArray } else if (valuesArray.length > 0 && options.length == 0){ - // editable list -> values only + // editable list -> values only resultArray = arrayToObject(valuesArray) cssClass = "interactable-option" // generates [1x 📝 | 2x 🚮] } else if (options.length > 0){ @@ -914,7 +919,7 @@ function generateOptions(options, valuesArray, targetField, transformers, placeh // dropdown -> options only (value == 1 STRING not ARRAY) resultArray = options; } - + // Create a map to track the index of each item in valuesArray const orderMap = new Map(valuesArray.map((item, index) => [item, index])); @@ -961,7 +966,7 @@ function generateList(options, valuesArray, targetField, transformers, placehold listHtml += `
  • ${labelName}
  • `; }); - + // Place the resulting HTML into the specified placeholder div $("#" + placeholder).replaceWith(listHtml); } @@ -972,7 +977,7 @@ function genListWithInputSet(options, valuesArray, targetField, transformers, pl var listHtml = ""; - + options.forEach(function(item) { let selected = valuesArray.includes(item.id) ? 'selected' : ''; @@ -988,9 +993,9 @@ function genListWithInputSet(options, valuesArray, targetField, transformers, pl } listHtml += `
  • - ${labelName} + ${labelName}
  • `; - + }); // Place the resulting HTML into the specified placeholder div @@ -1001,8 +1006,8 @@ function genListWithInputSet(options, valuesArray, targetField, transformers, pl // Collects a setting based on code name function collectSetting(prefix, setCodeName, setType, settingsArray) { // Parse setType if it's a JSON string - const setTypeObject = (typeof setType === "string") - ? JSON.parse(processQuotes(setType)) + const setTypeObject = (typeof setType === "string") + ? JSON.parse(processQuotes(setType)) : setType; const dataType = setTypeObject.dataType; @@ -1015,6 +1020,20 @@ function collectSetting(prefix, setCodeName, setType, settingsArray) { const { elementType, elementOptions = [], transformers = [] } = elementWithInputValue; + // Check if validation failed + if ( + $(`#${setCodeName}`) + && $(`#${setCodeName}`).attr("data-is-valid") + && $(`#${setCodeName}`).attr("data-is-valid") == 0 + ) + { + return { + "settingsArray": settingsArray, + "dataIsValid": false, + "failedSettingKey": setCodeName + }; + } + const opts = handleElementOptions('none', elementOptions, transformers, val = ""); // Map of handlers @@ -1038,7 +1057,7 @@ function collectSetting(prefix, setCodeName, setType, settingsArray) { let temps = []; if (opts.isOrdeable) { temps = $(`#${setCodeName}`).val(); - } else { + } else { const sel = $(`#${setCodeName}`).attr("my-editable") === "true" ? "" : ":selected"; $(`#${setCodeName} option${sel}`).each(function() { const vl = $(this).val(); @@ -1066,7 +1085,7 @@ function collectSetting(prefix, setCodeName, setType, settingsArray) { let handlerKey; if (dataType === "string" && elementType === "datatable") { handlerKey = "datatableString"; - } else if (dataType === "string" || + } else if (dataType === "string" || (dataType === "integer" && (opts.inputType === "number" || opts.inputType === "text"))) { handlerKey = "simpleValue"; } else if (opts.inputType === "checkbox") { @@ -1084,7 +1103,11 @@ function collectSetting(prefix, setCodeName, setType, settingsArray) { const value = handlers[handlerKey](); settingsArray.push([prefix, setCodeName, dataType, value]); - return settingsArray; + return { + "settingsArray": settingsArray, + "dataIsValid": true, + "failedSettingKey": "" + }; } @@ -1093,22 +1116,22 @@ function collectSetting(prefix, setCodeName, setType, settingsArray) { function generateFormHtml(settingsData, set, overrideValue, overrideOptions, originalSetKey) { let inputHtml = ''; - isEmpty(overrideValue) ? inVal = set['setValue'] : inVal = overrideValue; + isEmpty(overrideValue) ? inVal = set['setValue'] : inVal = overrideValue; const setKey = set['setKey']; const setType = set['setType']; // if (setKey == '') { - + // console.log(setType); // console.log(setKey); // console.log(overrideValue); - // console.log(inVal); + // console.log(inVal); // } // Parse the setType JSON string // console.log(processQuotes(setType)); - + const setTypeObject = JSON.parse(processQuotes(setType)) const dataType = setTypeObject.dataType; const elements = setTypeObject.elements || []; @@ -1137,20 +1160,21 @@ function generateFormHtml(settingsData, set, overrideValue, overrideOptions, ori customId, columns, base64Regex, - elementOptionsBase64 + elementOptionsBase64, + focusout } = handleElementOptions(setKey, elementOptions, transformers, inVal); // Override value let val = valRes; // if (setKey == '') { - + // console.log(setType); // console.log(setKey); // console.log(overrideValue); - // console.log(inVal); - // console.log(val); - + // console.log(inVal); + // console.log(val); + // } // Generate HTML based on elementType @@ -1159,16 +1183,17 @@ function generateFormHtml(settingsData, set, overrideValue, overrideOptions, ori const multi = isMultiSelect ? "multiple" : ""; const addCss = isOrdeable ? "select2 select2-hidden-accessible" : ""; - inputHtml += ``; break; case 'button': - inputHtml += `