Compare commits

..

3 Commits

Author SHA1 Message Date
jokob-sk
1319c3380d UNIFIAPI v0.3
Some checks failed
Code checks / check-url-paths (push) Has been cancelled
docker / docker_dev (push) Has been cancelled
Deploy MkDocs / deploy (push) Has been cancelled
2025-08-10 21:24:17 +10:00
jokob-sk
dce8c34064 docs, rewrite docker image 2025-08-10 20:22:43 +10:00
jokob-sk
9502ee0cd0 UNIFIAPI v0.2, not ofund mac handling #1132 2025-08-10 20:08:09 +10:00
23 changed files with 821 additions and 289 deletions

81
.github/workflows/docker_rewrite.yml vendored Executable file
View File

@@ -0,0 +1,81 @@
name: docker
on:
push:
branches:
- rewrite
tags:
- '*.*.*'
pull_request:
branches:
- rewrite
jobs:
docker_rewrite:
runs-on: ubuntu-latest
timeout-minutes: 30
permissions:
contents: read
packages: write
if: >
contains(github.event.head_commit.message, 'PUSHPROD') != 'True' &&
github.repository == 'jokob-sk/NetAlertX'
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Set up dynamic build ARGs
id: getargs
run: echo "version=$(cat ./stable/VERSION)" >> $GITHUB_OUTPUT
- name: Get release version
id: get_version
run: echo "version=Dev" >> $GITHUB_OUTPUT
- name: Create .VERSION file
run: echo "${{ steps.get_version.outputs.version }}" >> .VERSION
- name: Docker meta
id: meta
uses: docker/metadata-action@v4
with:
images: |
ghcr.io/jokob-sk/netalertx-dev-rewrite
jokobsk/netalertx-dev-rewrite
tags: |
type=raw,value=latest
type=ref,event=branch
type=ref,event=pr
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=semver,pattern={{major}}
type=sha
- name: Log in to Github Container Registry (GHCR)
uses: docker/login-action@v3
with:
registry: ghcr.io
username: jokob-sk
password: ${{ secrets.GITHUB_TOKEN }}
- name: Log in to DockerHub
if: github.event_name != 'pull_request'
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Build and push
uses: docker/build-push-action@v3
with:
context: .
platforms: linux/amd64,linux/arm64,linux/arm/v7,linux/arm/v6
push: ${{ github.event_name != 'pull_request' }}
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}

View File

@@ -53,6 +53,9 @@ The function `guess_device_attributes(...)` runs a series of matching functions
4. IP pattern → `match_ip()`
5. Final fallback → defaults defined in the `NEWDEV_devIcon` and `NEWDEV_devType` settings.
> [!NOTE]
> The app will try guessing the device type or icon if `devType` or `devIcon` are `""` or `"null"`.
### Use of default values
The guessing process runs for every device **as long as the current type or icon still matches the default values**. Even if earlier heuristics return a match, the system continues evaluating additional clues — like name or IP — to try and replace placeholders.

View File

@@ -1,7 +1,7 @@
<?php
require 'php/templates/header.php';
require 'php/templates/notification.php';
require 'php/templates/modals.php';
?>
<!-- ----------------------------------------------------------------------- -->

View File

@@ -1726,6 +1726,16 @@ input[readonly] {
width: 92%;
}
#modal-ok
{
z-index: 1051; /*highest priority*/
}
#modal-form-plc
{
display: grid;
}
/* ----------------------------------------------------------------- */
/* NETWORK page */
/* ----------------------------------------------------------------- */

View File

@@ -25,7 +25,7 @@
<!-- Content header--------------------------------------------------------- -->
<section class="content-header">
<?php require 'php/templates/notification.php'; ?>
<?php require 'php/templates/modals.php'; ?>
<h1 id="pageTitle">
&nbsp<small>Quering device info...</small>

View File

@@ -290,18 +290,6 @@
});
}
// ----------------------------------------
// Show the description of a setting
function showDescriptionPopup(e) {
console.log($(e).attr("my-set-key"));
showModalOK("Info", getString($(e).attr("my-set-key") + '_description'))
}
// -----------------------------------------------------------------------------
// Save device data to DB
function setDeviceData(direction = '', refreshCallback = '') {

View File

@@ -1004,9 +1004,7 @@ function initializeDatatable (status) {
}
});
});
}

View File

@@ -1032,6 +1032,7 @@ function getDevDataByMac(macAddress, dbColumn) {
}
}
console.error("⚠ Device with MAC not found:" + macAddress)
return "Unknown"; // Return a default value if MAC address is not found
}

View File

@@ -161,6 +161,112 @@ function showModalFieldInput(
$(`#${prefix}`).modal("show");
}
// -----------------------------------------------------------------------------
function showModalPopupForm(
title,
message,
btnCancel = getString("Gen_Cancel"),
btnOK = getString("Gen_Okay"),
curValue = "",
callbackFunction = null,
triggeredBy = null,
popupFormJson = null,
parentSettingKey = null
) {
// set captions
prefix = "modal-form";
console.log(popupFormJson);
$(`#${prefix}-title`).html(title);
$(`#${prefix}-message`).html(message);
$(`#${prefix}-cancel`).html(btnCancel);
$(`#${prefix}-OK`).html(btnOK);
if (triggeredBy != null) {
$('#'+prefix).attr("data-myparam-triggered-by", triggeredBy)
}
outputHtml = "";
if (Array.isArray(popupFormJson)) {
popupFormJson.forEach((field, index) => {
// You'll need to define these or map them from `field`
const setName = field.name?.find(n => n.language_code === "en_us")?.string || setKey;
const labelClasses = "col-sm-2"; // example, or from your obj.labelClasses
const inputClasses = "col-sm-10"; // example, or from your obj.inputClasses
const fieldData = field.default_value ?? "";
const fieldOptionsOverride = field.type?.elements[0]?.elementOptions || [];
const setKey = field.function || `field_${index}`;
const setValue = field.default_value ?? "";
const setType = JSON.stringify(field.type);
const setEvents = field.events || []; // default to empty array if missing
const setObj = { setKey, setValue, setType, setEvents };
// Generate the input field HTML
const inputFormHtml = `
<div class="form-group col-xs-12">
<label id="${setKey}_label" class="${labelClasses}"> ${setName}
<i my-set-key="${parentSettingKey}_popupform_${setKey}"
title="${getString("Settings_Show_Description")}"
class="fa fa-circle-info pointer helpIconSmallTopRight"
onclick="showDescriptionPopup(this)">
</i>
</label>
<div class="${inputClasses}">
${generateFormHtml(
null, // settingsData only required for datatables
setObj,
fieldData.toString(),
fieldOptionsOverride,
null
)}
</div>
</div>
`;
// Append to result
outputHtml += inputFormHtml;
});
}
$(`#modal-form-plc`).html(outputHtml);
// $(`#${prefix}-field`).val(curValue);
// setTimeout(function () {
// $(`#${prefix}-field`).focus();
// }, 500);
// Bind OK button click event
$(`#${prefix}-OK`).off("click").on("click", function() {
let settingsArray = [];
if (Array.isArray(popupFormJson)) {
popupFormJson.forEach(field => {
collectSetting(
`${parentSettingKey}_popupform`, // prefix
field.function + '_in', // setCodeName + sourceSuffixes
field.type, // setType (object)
settingsArray
);
});
}
console.log("Collected popup form settings:", settingsArray);
if (typeof modalCallbackFunction === "function") {
modalCallbackFunction(settingsArray);
}
$(`#${prefix}`).modal("hide");
});
// Show modal
$(`#${prefix}`).modal("show");
}
// -----------------------------------------------------------------------------
function modalDefaultOK() {
// Hide modal

View File

@@ -67,6 +67,15 @@ function getPluginConfig(pluginsData, prefix) {
return result;
}
// ----------------------------------------
// Show the description of a setting
function showDescriptionPopup(e) {
console.log($(e).attr("my-set-key"));
showModalOK("Info", getString($(e).attr("my-set-key") + '_description'))
}
// -------------------------------------------------------------------
// Generate plugin HTML card based on prefixes in an array
function pluginCards(prefixesOfEnabledPlugins, includeSettings) {
@@ -299,6 +308,45 @@ function removeDataTableRow(el) {
}
}
// ---------------------------------------------------------
// Add item via pop up form dialog
function addViaPopupForm(element) {
console.log(element)
const fromId = $(element).attr("my-input-from");
const toId = $(element).attr("my-input-to");
const curValue = $(`#${fromId}`).val();
const triggeredBy = $(element).attr("id");
const parsed = JSON.parse(atob($(element).data("elementoptionsbase64")));
const popupFormJson = parsed.find(obj => "popupForm" in obj)?.popupForm ?? null;
console.log(`fromId | toId | triggeredBy | curValue: ${fromId} | ${toId} | ${triggeredBy} | ${curValue}`);
showModalPopupForm(
`<i class="fa fa-pen-to-square"></i> ${getString(
"Gen_Update_Value"
)}`, // title
getString("settings_update_item_warning"), // message
getString("Gen_Cancel"), // btnCancel
getString("Gen_Add"), // btnOK
curValue, // curValue
null, // callbackFunction
triggeredBy, // triggeredBy
popupFormJson, // popupform
toId // parentSettingKey
);
}
// ---------------------------------------------------------
// Add item to list via popup form
function addViaPopupFormToList(element, clearInput = true) {
// flag something changes to prevent navigating from page
settingsChanged();
}
// ---------------------------------------------------------
// Add item to list
function addList(element, clearInput = true) {
@@ -622,8 +670,6 @@ function generateOptionsOrSetOptions(
// obj.push({ id: item, name: item })
options = arrayToObject(createArray(overrideOptions ? overrideOptions : getSettingOptions(setKey)))
// Call to render lists
renderList(
options,
@@ -633,8 +679,6 @@ function generateOptionsOrSetOptions(
targetField,
transformers
);
}
@@ -655,6 +699,13 @@ function applyTransformers(val, transformers) {
val = btoa(val);
}
break;
case "name|base64":
// // Implement base64 logic
// if (!isBase64(val)) {
// val = btoa(val);
// }
val = val; // probably TODO ⚠
break;
case "getString":
// no change
val = val;
@@ -681,6 +732,13 @@ function reverseTransformers(val, transformers) {
val = atob(val);
}
break;
case "name|base64":
// // Implement base64 decoding logic
// if (isBase64(val)) {
// val = atob(val);
// }
val = val; // probably TODO ⚠
break;
case "getString":
// retrieve string
val = getString(val);
@@ -720,8 +778,8 @@ const handleElementOptions = (setKey, elementOptions, transformers, val) => {
let customParams = "";
let customId = "";
let columns = [];
let base64Regex = "";
let base64Regex = "";
let elementOptionsBase64 = btoa(JSON.stringify(elementOptions));
elementOptions.forEach((option) => {
if (option.prefillValue) {
@@ -804,7 +862,8 @@ const handleElementOptions = (setKey, elementOptions, transformers, val) => {
customParams,
customId,
columns,
base64Regex
base64Regex,
elementOptionsBase64
};
};
@@ -934,6 +993,96 @@ function genListWithInputSet(options, valuesArray, targetField, transformers, pl
$("#" + placeholder).replaceWith(listHtml);
}
// -----------------------------------------------------------------
// 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))
: setType;
const dataType = setTypeObject.dataType;
// Pick element with input value
let elements = setTypeObject.elements.filter(el => el.elementHasInputValue === 1);
let elementWithInputValue = elements.length === 0
? setTypeObject.elements[setTypeObject.elements.length - 1]
: elements[0];
const { elementType, elementOptions = [], transformers = [] } = elementWithInputValue;
const opts = handleElementOptions('none', elementOptions, transformers, val = "");
// Map of handlers
const handlers = {
datatableString: () => {
const value = collectTableData(`#${setCodeName}_table`);
return btoa(JSON.stringify(value));
},
simpleValue: () => {
let value = $(`#${setCodeName}`).val();
return applyTransformers(value, transformers);
},
checkbox: () => {
let value = $(`#${setCodeName}`).is(':checked') ? 1 : 0;
if (dataType === "boolean") {
value = value === 1 ? "True" : "False";
}
return applyTransformers(value, transformers);
},
array: () => {
let temps = [];
if (opts.isOrdeable) {
temps = $(`#${setCodeName}`).val();
} else {
const sel = $(`#${setCodeName}`).attr("my-editable") === "true" ? "" : ":selected";
$(`#${setCodeName} option${sel}`).each(function() {
const vl = $(this).val();
if (vl !== '') {
temps.push(applyTransformers(vl, transformers));
}
});
}
return JSON.stringify(temps);
},
none: () => "",
json: () => {
let value = $(`#${setCodeName}`).val();
value = applyTransformers(value, transformers);
return JSON.stringify(value, null, 2);
},
fallback: () => {
console.error(`[collectSetting] Couldn't determine how to handle (${setCodeName}|${dataType}|${opts.inputType})`);
let value = $(`#${setCodeName}`).val();
return applyTransformers(value, transformers);
}
};
// Select handler key
let handlerKey;
if (dataType === "string" && elementType === "datatable") {
handlerKey = "datatableString";
} else if (dataType === "string" ||
(dataType === "integer" && (opts.inputType === "number" || opts.inputType === "text"))) {
handlerKey = "simpleValue";
} else if (opts.inputType === "checkbox") {
handlerKey = "checkbox";
} else if (dataType === "array") {
handlerKey = "array";
} else if (dataType === "none") {
handlerKey = "none";
} else if (dataType === "json") {
handlerKey = "json";
} else {
handlerKey = "fallback";
}
const value = handlers[handlerKey]();
settingsArray.push([prefix, setCodeName, dataType, value]);
return settingsArray;
}
// ------------------------------------------------------------------------------
// Generate the form control for setting
@@ -955,6 +1104,8 @@ function generateFormHtml(settingsData, set, overrideValue, overrideOptions, ori
// }
// Parse the setType JSON string
console.log(processQuotes(setType));
const setTypeObject = JSON.parse(processQuotes(setType))
const dataType = setTypeObject.dataType;
const elements = setTypeObject.elements || [];
@@ -982,7 +1133,8 @@ function generateFormHtml(settingsData, set, overrideValue, overrideOptions, ori
customParams,
customId,
columns,
base64Regex
base64Regex,
elementOptionsBase64
} = handleElementOptions(setKey, elementOptions, transformers, inVal);
// Override value
@@ -1051,6 +1203,7 @@ function generateFormHtml(settingsData, set, overrideValue, overrideOptions, ori
my-originalSetKey="${originalSetKey}"
my-input-from="${sourceIds}"
my-input-to="${setKey}"
data-elementoptionsbase64="${elementOptionsBase64}"
onclick="${onClick}">
${getString(getStringKey)}
</button>`;

View File

@@ -782,25 +782,17 @@ function initSelect2() {
// ------------------------------------------
// Render a device link with hover-over functionality
function renderDeviceLink(data, container, useName = false) {
if (!data.id) return data.text; // default placeholder etc.
if (!data.id || !isValidMac(data.id)) return data.text; // default placeholder etc.
const device = getDevDataByMac(data.id);
console.log('mac 🔽');
console.log(data.id);
console.log('mac 🔼');
console.log('device 🔽');
console.log(device);
console.log('device 🔼');
const badge = getStatusBadgeParts(
device.devPresentLastScan,
device.devAlertDown,
device.devMac
);
// Add badge class and hover-info class to container
// badge class and hover-info class to container
$(container)
.addClass(`${badge.cssClass} hover-node-info`)
.attr({

View File

@@ -1,6 +1,6 @@
<?php
require 'php/templates/header.php';
require 'php/templates/notification.php';
require 'php/templates/modals.php';
?>
<!-- Page ------------------------------------------------------------------ -->

View File

@@ -136,7 +136,8 @@
customParams,
customId,
columns,
base64Regex
base64Regex,
elementOptionsBase64
} = handleElementOptions('none', elementOptions, transformers, val = "");
// render based on element type

View File

@@ -1,6 +1,6 @@
<?php
require 'php/templates/header.php';
require 'php/templates/notification.php';
require 'php/templates/modals.php';
?>
<script>

View File

@@ -1,6 +1,6 @@
<?php
require 'php/templates/notification.php';
require 'php/templates/modals.php';
//------------------------------------------------------------------------------
// check if authenticated
require_once $_SERVER['DOCUMENT_ROOT'] . '/php/templates/security.php';

View File

@@ -117,6 +117,29 @@
</div>
</div>
<!-- Modal form input -->
<div class="modal fade" id="modal-form" data-myparam-triggered-by="" style="display: none;">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
<h4 id="modal-form-title" class="modal-title"> Modal Title </h4>
</div>
<div id="modal-form-message" class="modal-body"> Modal message </div>
<div id="modal-form-plc"></div>
<div class="modal-footer">
<button id="modal-form-cancel" type="button" class="btn btn-outline pull-left" style="min-width: 80px;" data-dismiss="modal"> Cancel </button>
<button id="modal-form-OK" type="button" class="btn btn-outline btn-modal-submit" style="min-width: 80px;" > OK </button>
</div>
</div>
</div>
</div>
<!-- Modal field input -->
<div class="modal modal-warning fade" id="modal-field-input" data-myparam-triggered-by="" style="display: none;">

View File

@@ -1,7 +1,7 @@
<?php
require 'php/templates/header.php';
require 'php/templates/notification.php';
require 'php/templates/modals.php';
?>
<!-- Page ------------------------------------------------------------------ -->

View File

@@ -2,7 +2,7 @@
"code_name": "unifi_api_import",
"unique_prefix": "UNIFIAPI",
"plugin_type": "device_scanner",
"execution_order" : "Layer_0",
"execution_order": "Layer_0",
"enabled": true,
"data_source": "script",
"mapped_to_table": "CurrentScan",
@@ -16,17 +16,21 @@
}
],
"show_ui": true,
"localized": ["display_name", "description", "icon"],
"localized": [
"display_name",
"description",
"icon"
],
"display_name": [
{
"language_code": "en_us",
"string": "Display Name"
"string": "UniFi import (API)"
}
],
"description": [
{
"language_code": "en_us",
"string": "Plugin to ..."
"string": "This plugin is used to import devices from an UNIFI controller via the Site Manager API."
}
],
"icon": [
@@ -39,21 +43,29 @@
"settings": [
{
"function": "RUN",
"events": ["run"],
"events": [
"run"
],
"type": {
"dataType": "string",
"elements": [
{ "elementType": "select", "elementOptions": [], "transformers": [] }
{
"elementType": "select",
"elementOptions": [],
"transformers": []
}
]
},
"default_value": "disabled",
"options": [
"disabled",
"once",
"schedule"
],
"localized": ["name", "description"],
"localized": [
"name",
"description"
],
"name": [
{
"language_code": "en_us",
@@ -63,7 +75,7 @@
"description": [
{
"language_code": "en_us",
"string": "When the plugin should run. Good options are <code>always_after_scan</code>, <code>on_new_device</code>, <code>on_notification</code>"
"string": "When the plugin should run. Good options are <code>schedule</code>, <code>once</code>."
}
]
},
@@ -100,7 +112,10 @@
},
"default_value": "*/5 * * * *",
"options": [],
"localized": ["name", "description"],
"localized": [
"name",
"description"
],
"name": [
{
"language_code": "en_us",
@@ -110,7 +125,7 @@
"description": [
{
"language_code": "en_us",
"string": "Only enabled if you select <code>schedule</code> in the <a href=\"#SYNC_RUN\"><code>SYNC_RUN</code> setting</a>. Make sure you enter the schedule in the correct cron-like format (e.g. validate at <a href=\"https://crontab.guru/\" target=\"_blank\">crontab.guru</a>). For example entering <code>0 4 * * *</code> will run the scan after 4 am in the <a onclick=\"toggleAllSettings()\" href=\"#TIMEZONE\"><code>TIMEZONE</code> you set above</a>. Will be run NEXT time the time passes."
"string": "Only enabled if you select <code>schedule</code> in the <a href=\"#UNIFIAPI_RUN\"><code>UNIFIAPI_RUN</code> setting</a>. Make sure you enter the schedule in the correct cron-like format (e.g. validate at <a href=\"https://crontab.guru/\" target=\"_blank\">crontab.guru</a>). For example entering <code>0 4 * * *</code> will run the scan after 4 am in the <a onclick=\"toggleAllSettings()\" href=\"#TIMEZONE\"><code>TIMEZONE</code> you set above</a>. Will be run NEXT time the time passes."
}
]
},
@@ -121,14 +136,21 @@
"elements": [
{
"elementType": "input",
"elementOptions": [{ "readonly": "true" }],
"elementOptions": [
{
"readonly": "true"
}
],
"transformers": []
}
]
},
"default_value": "python3 /app/front/plugins/unifi_api_import/unifi_api_import.py",
"options": [],
"localized": ["name", "description"],
"localized": [
"name",
"description"
],
"name": [
{
"language_code": "en_us",
@@ -149,14 +171,21 @@
"elements": [
{
"elementType": "input",
"elementOptions": [{ "type": "number" }],
"elementOptions": [
{
"type": "number"
}
],
"transformers": []
}
]
},
"default_value": 10,
"options": [],
"localized": ["name", "description"],
"localized": [
"name",
"description"
],
"name": [
{
"language_code": "en_us",
@@ -171,28 +200,52 @@
]
},
{
"function": "devCustomProps",
"function": "sites",
"type": {
"dataType": "array",
"elements": [
{
"elementType": "popupform",
"elementHasInputValue": 1,
"elementType": "button",
"elementOptions": [
{
"fields": [
"sourceSuffixes": [
"_in"
]
},
{
"separator": ""
},
{
"cssClasses": "col-xs-12"
},
{
"onClick": "addViaPopupForm(this)"
},
{
"getStringKey": "Gen_Add"
},
{
"popupForm": [
{
"function": "hide.site.name",
"function": "name",
"type": {
"dataType": "string",
"elements": [
{
"elementType": "input",
"elementOptions": [
{ "placeholder": "Enter value" },
{ "suffix": "_in" },
{ "cssClasses": "col-sm-10" },
{ "prefillValue": "null" }
{
"placeholder": "Enter value"
},
{
"suffix": "_in"
},
{
"cssClasses": "col-sm-10"
},
{
"prefillValue": "null"
}
],
"transformers": []
}
@@ -200,7 +253,10 @@
},
"default_value": "default",
"options": [],
"localized": ["name", "description"],
"localized": [
"name",
"description"
],
"name": [
{
"language_code": "en_us",
@@ -210,74 +266,239 @@
"description": [
{
"language_code": "en_us",
"string": "The name of your site. Not used in code and only for.... "
"string": "The name of your site. Use a descriptive name."
}
]
},
{
"settingKey": "hide.site.name",
"typeOverride": {
"function": "base_url",
"type": {
"dataType": "string",
"elements": [
{
"elementType": "span",
"elementType": "input",
"elementOptions": [
{
"cssClasses": "input-group-addon iconPreview"
"placeholder": "https://host_ip/proxy/network/integration/"
},
{
"getStringKey": "Gen_SelectIcon"
"suffix": "_in"
},
{
"customId": "CUSTPROP_icon_preview"
}
],
"transformers": []
},
{
"elementType": "select",
"elementHasInputValue": 1,
"elementOptions": [
{
"cssClasses": "iconInputVal myhidden"
"cssClasses": "col-sm-10"
},
{
"onChange": "updateIconPreview(this)"
},
{
"customParams": "CUSTPROP_icon,CUSTPROP_icon_preview"
"prefillValue": "null"
}
],
"transformers": []
}
]
}
},
"default_value": "default",
"options": [],
"localized": [
"name",
"description"
],
"name": [
{
"language_code": "en_us",
"string": "Base URL"
}
],
"description": [
{
"language_code": "en_us",
"string": "You can find your base url in the UniFi Site Manager in <i>Settings -> Control Plane -> Integrations</i> in the <i>API Request Format</i> section, (e.g. <code>https://host_ip/proxy/network/integration/</code>)."
}
]
},
{
"settingKey": "hide.site.base_url",
"optionsOverride": "setting.CUSTPROP_type",
"typeOverride": {
"function": "version",
"type": {
"dataType": "string",
"elements": [
{
"elementType": "select",
"elementOptions": [],
"elementType": "input",
"elementOptions": [
{
"placeholder": "v1"
},
{
"suffix": "_in"
},
{
"cssClasses": "col-sm-10"
},
{
"prefillValue": "null"
}
],
"transformers": []
}
]
}
},
"default_value": "default",
"options": [],
"localized": [
"name",
"description"
],
"name": [
{
"language_code": "en_us",
"string": "API version"
}
],
"description": [
{
"language_code": "en_us",
"string": "The version of the API (e.g.: <code>v1</code>)."
}
]
},
{
"settingKey": "hide.site.version"
"function": "api_key",
"type": {
"dataType": "string",
"elements": [
{
"elementType": "input",
"elementOptions": [
{
"placeholder": "Enter value"
},
{
"suffix": "_in"
},
{
"cssClasses": "col-sm-10"
},
{
"prefillValue": "null"
}
],
"transformers": []
}
]
},
"default_value": "default",
"options": [],
"localized": [
"name",
"description"
],
"name": [
{
"language_code": "en_us",
"string": "API key"
}
],
"description": [
{
"language_code": "en_us",
"string": "You can get an API key in your UniFi Site Manager in <i>Settings -> Control Plane -> Integrations</i>."
}
]
},
{
"settingKey": "hide.site.api_key"
},
{
"settingKey": "hide.site.verify_ssl"
"function": "hide.site.verify_ssl",
"type": {
"dataType": "boolean",
"elements": [
{
"elementType": "input",
"elementOptions": [
{
"type": "checkbox"
}
],
"transformers": []
}
]
},
"default_value": 1,
"options": [],
"localized": [
"name",
"description"
],
"name": [
{
"language_code": "en_us",
"string": "Verify SSL"
}
],
"description": [
{
"language_code": "en_us",
"string": "Disable if you do not have an SSL certificate set up."
}
]
}
]
],
"transformers": []
}
],
"transformers": []
},
{
"elementType": "select",
"elementHasInputValue": 1,
"elementOptions": [
{
"multiple": "true"
},
{
"readonly": "true"
},
{
"editable": "true"
}
],
"transformers": [
"name|base64"
]
},
{
"elementType": "button",
"elementOptions": [
{
"sourceSuffixes": []
},
{
"separator": ""
},
{
"cssClasses": "col-xs-6"
},
{
"onClick": "removeFromList(this)"
},
{
"getStringKey": "Gen_Remove_Last"
}
],
"transformers": []
},
{
"elementType": "button",
"elementOptions": [
{
"sourceSuffixes": []
},
{
"separator": ""
},
{
"cssClasses": "col-xs-6"
},
{
"onClick": "removeAllOptions(this)"
},
{
"getStringKey": "Gen_Remove_All"
}
],
"transformers": []
@@ -299,92 +520,7 @@
"description": [
{
"language_code": "en_us",
"string": "Unifi sites"
}
]
},
{
"function": "sites",
"type": {
"dataType": "array",
"elements": [
{
"elementType": "input",
"elementOptions": [
{ "placeholder": "Enter value" },
{ "suffix": "_in" },
{ "cssClasses": "col-sm-10" },
{ "prefillValue": "null" }
],
"transformers": []
},
{
"elementType": "button",
"elementOptions": [
{ "sourceSuffixes": ["_in"] },
{ "separator": "" },
{ "cssClasses": "col-xs-12" },
{ "onClick": "addList(this,false)" },
{ "getStringKey": "Gen_Add" }
],
"transformers": []
},
{
"elementType": "select",
"elementHasInputValue": 1,
"elementOptions": [
{ "multiple": "true" },
{ "readonly": "true" },
{ "editable": "true" }
],
"transformers": []
},
{
"elementType": "button",
"elementOptions": [
{ "sourceSuffixes": [] },
{ "separator": "" },
{ "cssClasses": "col-xs-6" },
{ "onClick": "removeAllOptions(this)" },
{ "getStringKey": "Gen_Remove_All" }
],
"transformers": []
},
{
"elementType": "button",
"elementOptions": [
{ "sourceSuffixes": [] },
{ "separator": "" },
{ "cssClasses": "col-xs-6" },
{ "onClick": "removeFromList(this)" },
{ "getStringKey": "Gen_Remove_Last" }
],
"transformers": []
}
]
},
"maxLength": 50,
"default_value": [
"none",
"data",
"link",
"link_new_tab",
"show_notes",
"delete_dev",
"run_plugin"
],
"options": [],
"localized": ["name","description"],
"name": [
{
"language_code": "en_us",
"string": "Type"
}
],
"description": [
{
"language_code": "en_us",
"string": "List of property types. The default ones have specific functionality associated with it."
"string": "UniFi sites"
}
]
}
@@ -397,7 +533,9 @@
"type": "none",
"default_value": "",
"options": [],
"localized": ["name"],
"localized": [
"name"
],
"name": [
{
"language_code": "en_us",
@@ -413,7 +551,9 @@
"type": "device_name_mac",
"default_value": "",
"options": [],
"localized": ["name"],
"localized": [
"name"
],
"name": [
{
"language_code": "en_us",
@@ -429,7 +569,9 @@
"type": "device_ip",
"default_value": "",
"options": [],
"localized": ["name"],
"localized": [
"name"
],
"name": [
{
"language_code": "en_us",
@@ -445,7 +587,9 @@
"type": "label",
"default_value": "",
"options": [],
"localized": ["name"],
"localized": [
"name"
],
"name": [
{
"language_code": "en_us",
@@ -461,7 +605,9 @@
"type": "label",
"default_value": "",
"options": [],
"localized": ["name"],
"localized": [
"name"
],
"name": [
{
"language_code": "en_us",
@@ -477,7 +623,9 @@
"type": "label",
"default_value": "",
"options": [],
"localized": ["name"],
"localized": [
"name"
],
"name": [
{
"language_code": "en_us",
@@ -492,7 +640,9 @@
"type": "label",
"default_value": "",
"options": [],
"localized": ["name"],
"localized": [
"name"
],
"name": [
{
"language_code": "en_us",
@@ -511,7 +661,9 @@
"type": "label",
"default_value": "",
"options": [],
"localized": ["name"],
"localized": [
"name"
],
"name": [
{
"language_code": "en_us",
@@ -526,7 +678,9 @@
"type": "label",
"default_value": "",
"options": [],
"localized": ["name"],
"localized": [
"name"
],
"name": [
{
"language_code": "en_us",
@@ -541,7 +695,9 @@
"type": "label",
"default_value": "",
"options": [],
"localized": ["name"],
"localized": [
"name"
],
"name": [
{
"language_code": "en_us",
@@ -573,7 +729,9 @@
"replacement": "<div style='text-align:center'><i class='fa-solid fa-question'></i></div>"
}
],
"localized": ["name"],
"localized": [
"name"
],
"name": [
{
"language_code": "en_us",
@@ -582,4 +740,4 @@
]
}
]
}
}

View File

@@ -1,7 +1,7 @@
<?php
require 'php/templates/header.php';
require 'php/templates/notification.php';
require 'php/templates/modals.php';
?>

View File

@@ -58,6 +58,12 @@ $settingsJSON_DB = json_encode($settings, JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX
<div id="settingsPage" class="content-wrapper">
<a style="cursor:pointer">
<span>
<i id='toggleSettings' onclick="toggleAllSettings()" class="settings-expand-icon fa fa-angle-double-down"></i>
</span>
</a>
<!-- Content header--------------------------------------------------------- -->
<section class="content-header">
@@ -547,7 +553,7 @@ $settingsJSON_DB = json_encode($settings, JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX
}, 1500);
} else {
var settingsArray = [];
let settingsArray = [];
// collect values for each of the different input form controls
// get settings to determine setting type to store values appropriately
@@ -559,120 +565,123 @@ $settingsJSON_DB = json_encode($settings, JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX
setType = set["setType"]
setCodeName = set["setKey"]
// console.log(prefix);
settingsArray = collectSetting(prefix, setCodeName, setType, settingsArray)
const setTypeObject = JSON.parse(processQuotes(setType))
// console.log(setTypeObject);
// // console.log(prefix);
const dataType = setTypeObject.dataType;
// const setTypeObject = JSON.parse(processQuotes(setType))
// // console.log(setTypeObject);
// get the element with the input value(s)
let elements = setTypeObject.elements.filter(element => element.elementHasInputValue === 1);
// const dataType = setTypeObject.dataType;
// if none found, take last
if(elements.length == 0)
{
elementWithInputValue = setTypeObject.elements[setTypeObject.elements.length - 1]
} else
{
elementWithInputValue = elements[0]
}
// // get the element with the input value(s)
// let elements = setTypeObject.elements.filter(element => element.elementHasInputValue === 1);
const { elementType, elementOptions = [], transformers = [] } = elementWithInputValue;
const {
inputType,
readOnly,
isMultiSelect,
isOrdeable,
cssClasses,
placeholder,
suffix,
sourceIds,
separator,
editable,
valRes,
getStringKey,
onClick,
onChange,
customParams,
customId,
columns,
base64Regex
} = handleElementOptions('none', elementOptions, transformers, val = "");
// // if none found, take last
// if(elements.length == 0)
// {
// elementWithInputValue = setTypeObject.elements[setTypeObject.elements.length - 1]
// } else
// {
// elementWithInputValue = elements[0]
// }
let value;
// const { elementType, elementOptions = [], transformers = [] } = elementWithInputValue;
// const {
// inputType,
// readOnly,
// isMultiSelect,
// isOrdeable,
// cssClasses,
// placeholder,
// suffix,
// sourceIds,
// separator,
// editable,
// valRes,
// getStringKey,
// onClick,
// onChange,
// customParams,
// customId,
// columns,
// base64Regex,
// elementOptionsBase64
// } = handleElementOptions('none', elementOptions, transformers, val = "");
if (dataType === "string" && elementWithInputValue.elementType === "datatable" ) {
// let value;
value = collectTableData(`#${setCodeName}_table`)
settingsArray.push([prefix, setCodeName, dataType, btoa(JSON.stringify(value))]);
// if (dataType === "string" && elementWithInputValue.elementType === "datatable" ) {
} else if (dataType === "string" ||
(dataType === "integer" && (inputType === "number" || inputType === "text"))) {
// value = collectTableData(`#${setCodeName}_table`)
// settingsArray.push([prefix, setCodeName, dataType, btoa(JSON.stringify(value))]);
// } else if (dataType === "string" ||
// (dataType === "integer" && (inputType === "number" || inputType === "text"))) {
value = $('#' + setCodeName).val();
value = applyTransformers(value, transformers);
// value = $('#' + setCodeName).val();
// value = applyTransformers(value, transformers);
settingsArray.push([prefix, setCodeName, dataType, value]);
// settingsArray.push([prefix, setCodeName, dataType, value]);
} else if (inputType === 'checkbox') {
// } else if (inputType === 'checkbox') {
value = $(`#${setCodeName}`).is(':checked') ? 1 : 0;
// value = $(`#${setCodeName}`).is(':checked') ? 1 : 0;
if(dataType === "boolean")
{
value = value == 1 ? "True" : "False";
}
// if(dataType === "boolean")
// {
// value = value == 1 ? "True" : "False";
// }
value = applyTransformers(value, transformers);
settingsArray.push([prefix, setCodeName, dataType, value]);
// value = applyTransformers(value, transformers);
// settingsArray.push([prefix, setCodeName, dataType, value]);
} else if (dataType === "array" ) {
// } else if (dataType === "array" ) {
let temps = [];
// let temps = [];
if(isOrdeable)
{
temps = $(`#${setCodeName}`).val()
} else
{
// make sure to collect all if set as "editable" or selected only otherwise
$(`#${setCodeName}`).attr("my-editable") == "true" ? additionalSelector = "" : additionalSelector = ":selected";
// if(isOrdeable)
// {
// temps = $(`#${setCodeName}`).val()
// } else
// {
// // make sure to collect all if set as "editable" or selected only otherwise
// $(`#${setCodeName}`).attr("my-editable") == "true" ? additionalSelector = "" : additionalSelector = ":selected";
$(`#${setCodeName} option${additionalSelector}`).each(function() {
const vl = $(this).val();
if (vl !== '') {
temps.push(applyTransformers(vl, transformers));
}
});
}
// $(`#${setCodeName} option${additionalSelector}`).each(function() {
// const vl = $(this).val();
// if (vl !== '') {
// temps.push(applyTransformers(vl, transformers));
// }
// });
// }
value = JSON.stringify(temps);
// value = JSON.stringify(temps);
settingsArray.push([prefix, setCodeName, dataType, value]);
// settingsArray.push([prefix, setCodeName, dataType, value]);
} else if (dataType === "none") {
// no value to save
value = ""
settingsArray.push([prefix, setCodeName, dataType, value]);
// } else if (dataType === "none") {
// // no value to save
// value = ""
// settingsArray.push([prefix, setCodeName, dataType, value]);
} else if (dataType === "json") {
// } else if (dataType === "json") {
value = $('#' + setCodeName).val();
value = applyTransformers(value, transformers);
value = JSON.stringify(value, null, 2)
settingsArray.push([prefix, setCodeName, dataType, value]);
// value = $('#' + setCodeName).val();
// value = applyTransformers(value, transformers);
// value = JSON.stringify(value, null, 2)
// settingsArray.push([prefix, setCodeName, dataType, value]);
} else {
// } else {
console.error(`[saveSettings] Couldn't determine how to handle (setCodeName|dataType|inputType):(${setCodeName}|${dataType}|${inputType})`);
// console.error(`[saveSettings] Couldn't determine how to handle (setCodeName|dataType|inputType):(${setCodeName}|${dataType}|${inputType})`);
value = $('#' + setCodeName).val();
value = applyTransformers(value, transformers);
console.error(`[saveSettings] Saving value "${value}"`);
settingsArray.push([prefix, setCodeName, dataType, value]);
}
// value = $('#' + setCodeName).val();
// value = applyTransformers(value, transformers);
// console.error(`[saveSettings] Saving value "${value}"`);
// settingsArray.push([prefix, setCodeName, dataType, value]);
// }
});
// sanity check to make sure settings were loaded & collected correctly

View File

@@ -13,7 +13,7 @@
require 'php/templates/header.php';
?>
<?php require 'php/templates/notification.php'; ?>
<?php require 'php/templates/modals.php'; ?>
<!-- ----------------------------------------------------------------------- -->

View File

@@ -1,7 +1,7 @@
<?php
require 'php/templates/header.php';
require 'php/templates/notification.php';
require 'php/templates/modals.php';
?>
<!-- ----------------------------------------------------------------------- -->

View File

@@ -275,6 +275,15 @@ def importConfigs (db, all_plugins):
# Save the user defined value into the object
set["value"] = v
# Now check for popupForm inside elements → elementOptions
elements = set.get("type", {}).get("elements", [])
for element in elements:
for option in element.get("elementOptions", []):
if "popupForm" in option:
for popup_entry in option["popupForm"]:
popup_pref = key + "_popupform_" + popup_entry.get("function", "")
stringSqlParams = collect_lang_strings(popup_entry, popup_pref, stringSqlParams)
# Collect settings related language strings
# Creates an entry with key, for example ARPSCAN_CMD_name
stringSqlParams = collect_lang_strings(set, pref + "_" + set["function"], stringSqlParams)