homebridge-unifi-protect 6.6.0 → 6.8.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE.md +1 -1
- package/README.md +13 -13
- package/config.schema.json +12 -0
- package/dist/protect-camera.js +7 -5
- package/dist/protect-camera.js.map +1 -1
- package/dist/protect-device.d.ts +3 -2
- package/dist/protect-device.js +51 -2
- package/dist/protect-device.js.map +1 -1
- package/dist/protect-ffmpeg-codecs.js +3 -2
- package/dist/protect-ffmpeg-codecs.js.map +1 -1
- package/dist/protect-ffmpeg-record.js +13 -12
- package/dist/protect-ffmpeg-record.js.map +1 -1
- package/dist/protect-ffmpeg.js +5 -4
- package/dist/protect-ffmpeg.js.map +1 -1
- package/dist/protect-light.js +2 -0
- package/dist/protect-light.js.map +1 -1
- package/dist/protect-nvr-events.d.ts +0 -2
- package/dist/protect-nvr-events.js +40 -6
- package/dist/protect-nvr-events.js.map +1 -1
- package/dist/protect-options.d.ts +1 -0
- package/dist/protect-options.js +5 -2
- package/dist/protect-options.js.map +1 -1
- package/dist/protect-platform.js +6 -1
- package/dist/protect-platform.js.map +1 -1
- package/dist/protect-sensor.js +2 -0
- package/dist/protect-sensor.js.map +1 -1
- package/dist/protect-types.d.ts +0 -23
- package/dist/protect-types.js.map +1 -1
- package/dist/settings.d.ts +1 -0
- package/dist/settings.js +2 -0
- package/dist/settings.js.map +1 -1
- package/homebridge-ui/public/index.html +247 -29
- package/homebridge-ui/server.js +12 -10
- package/package.json +8 -8
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
<div id="deviceInfo">
|
|
30
30
|
<table class="table table-sm table-borderless">
|
|
31
31
|
<tr class="align-center">
|
|
32
|
-
<td colspan="2" class="m-0 p-2 text-center font-weight-bold"
|
|
32
|
+
<td id="headerInfo" colspan="2" class="m-0 p-2 text-center font-weight-bold"></td>
|
|
33
33
|
</tr>
|
|
34
34
|
<tr class="align-top">
|
|
35
35
|
<td rowspan="3" class="w-25">
|
|
@@ -50,7 +50,7 @@
|
|
|
50
50
|
</td>
|
|
51
51
|
<td>
|
|
52
52
|
<table class="table table-sm table-borderless border-bottom m-0 p-0" id="deviceStatsTable" style="display: none;">
|
|
53
|
-
<tr>
|
|
53
|
+
<tr id="deviceStatsHeader">
|
|
54
54
|
<th style="width: 30%;" class="m-0 p-0"><B>Model<B></th>
|
|
55
55
|
<th style="width: 25%;" class="m-0 p-0"><B>IP Address</B></th>
|
|
56
56
|
<th style="width: 20%;" class="m-0 p-0"><B>MAC Address</B></th>
|
|
@@ -234,6 +234,17 @@
|
|
|
234
234
|
// Start with a clean slate.
|
|
235
235
|
controllersTable.innerHTML = "";
|
|
236
236
|
|
|
237
|
+
// We haven't configured anything yet - we're done.
|
|
238
|
+
if(!currentConfig[0]?.controllers?.length) {
|
|
239
|
+
|
|
240
|
+
document.getElementById("headerInfo").innerHTML = "Please configure a UniFi Protect controller to access in the main settings tab before configuring feature options."
|
|
241
|
+
homebridge.hideSpinner();
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// Initialize our informational header.
|
|
246
|
+
document.getElementById("headerInfo").innerHTML = "Feature options are applied in prioritized order, from global to device-specific options:<br><i class=\"text-warning\">Global options</i> (lowest priority) → <i class=\"text-success\">Protect controller options</i> → <i class=\"text-info\">Protect device options</i> (highest priority)"
|
|
247
|
+
|
|
237
248
|
// Enumerate our global options.
|
|
238
249
|
const trGlobal = document.createElement("tr");
|
|
239
250
|
|
|
@@ -342,7 +353,12 @@
|
|
|
342
353
|
|
|
343
354
|
devicesTable.innerHTML = "";
|
|
344
355
|
|
|
345
|
-
document.getElementById("device_model").innerHTML = "Unable to connect to the Protect controller. Check your settings for this controller in the settings tab."
|
|
356
|
+
document.getElementById("device_model").innerHTML = "Unable to connect to the Protect controller. Check your settings for this controller in the main settings tab to verify they are correct."
|
|
357
|
+
document.getElementById("device_model").colSpan = 3;
|
|
358
|
+
document.getElementById("device_model").style.fontWeight = "bold";
|
|
359
|
+
document.getElementById("device_model").classList.add("text-center");
|
|
360
|
+
document.getElementById("deviceStatsHeader").style.display = "none";
|
|
361
|
+
|
|
346
362
|
document.getElementById("device_mac").innerHTML = "";
|
|
347
363
|
document.getElementById("device_address").innerHTML = "";
|
|
348
364
|
document.getElementById("device_online").innerHTML = "";
|
|
@@ -355,6 +371,9 @@
|
|
|
355
371
|
const modelKeys = [...new Set(ufpDevices.map(x => x.modelKey))];
|
|
356
372
|
const deviceList = [];
|
|
357
373
|
|
|
374
|
+
// The first entry returned by getDevices is always the controller.
|
|
375
|
+
const nvr = ufpDevices[0];
|
|
376
|
+
|
|
358
377
|
// Start with a clean slate.
|
|
359
378
|
devicesTable.innerHTML = "";
|
|
360
379
|
|
|
@@ -366,7 +385,7 @@
|
|
|
366
385
|
// If it's a controller, we handle that case differently.
|
|
367
386
|
if((key === "nvr") && devices.length) {
|
|
368
387
|
|
|
369
|
-
// Change the name of the
|
|
388
|
+
// Change the name of the controller that we show users once we've connected with the controller.
|
|
370
389
|
controllerList.map(x => (x.name === controller.address) ? x.childNodes[0].nodeValue = devices[0].name : true);
|
|
371
390
|
continue;
|
|
372
391
|
}
|
|
@@ -401,7 +420,7 @@
|
|
|
401
420
|
const label = document.createElement("label");
|
|
402
421
|
|
|
403
422
|
label.name = device.id;
|
|
404
|
-
label.appendChild(document.createTextNode(device.name));
|
|
423
|
+
label.appendChild(document.createTextNode(device.name ?? device.marketName));
|
|
405
424
|
label.style.cursor = "pointer";
|
|
406
425
|
label.classList.add("mx-2", "my-0", "p-0", "w-100");
|
|
407
426
|
|
|
@@ -437,6 +456,13 @@
|
|
|
437
456
|
// Initialize our feature option configuration.
|
|
438
457
|
updateConfigOptions(currentConfig[0].options ?? [])
|
|
439
458
|
|
|
459
|
+
// Is this feature option set explicitly?
|
|
460
|
+
const isOptionSet = (featureOption, deviceMac) => {
|
|
461
|
+
|
|
462
|
+
const optionRegex = new RegExp("^(Enable|Disable)\." + featureOption + (!deviceMac ? "" : "\." + deviceMac) + "$", "gi");
|
|
463
|
+
return optionsList.filter(x => x.match(optionRegex)).length ? true : false;
|
|
464
|
+
};
|
|
465
|
+
|
|
440
466
|
// Is a feature option globally enabled?
|
|
441
467
|
const isGlobalOptionEnabled = (featureOption, defaultValue) => {
|
|
442
468
|
|
|
@@ -448,9 +474,14 @@
|
|
|
448
474
|
);
|
|
449
475
|
};
|
|
450
476
|
|
|
451
|
-
// Is a feature option enabled at the device or
|
|
477
|
+
// Is a feature option enabled at the device or global level. It does not traverse the scoping hierarchy.
|
|
452
478
|
const isDeviceOptionEnabled = (featureOption, mac, defaultValue) => {
|
|
453
479
|
|
|
480
|
+
if(!mac) {
|
|
481
|
+
|
|
482
|
+
return isGlobalOptionEnabled(featureOption, defaultValue);
|
|
483
|
+
}
|
|
484
|
+
|
|
454
485
|
featureOption = featureOption.toUpperCase();
|
|
455
486
|
mac = mac.toUpperCase();
|
|
456
487
|
|
|
@@ -460,6 +491,92 @@
|
|
|
460
491
|
);
|
|
461
492
|
};
|
|
462
493
|
|
|
494
|
+
// Is a feature option enabled at the device or global level. It does traverse the scoping hierarchy.
|
|
495
|
+
const isOptionEnabled = (featureOption, deviceMac, defaultValue) => {
|
|
496
|
+
|
|
497
|
+
if(deviceMac) {
|
|
498
|
+
|
|
499
|
+
// Device level check.
|
|
500
|
+
if(isDeviceOptionEnabled(featureOption, deviceMac, defaultValue) !== defaultValue) {
|
|
501
|
+
|
|
502
|
+
return !defaultValue;
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
// Controller level check.
|
|
506
|
+
if(isDeviceOptionEnabled(featureOption, nvr.mac, defaultValue) !== defaultValue) {
|
|
507
|
+
|
|
508
|
+
return !defaultValue;
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
// Global check.
|
|
513
|
+
if(isGlobalOptionEnabled(featureOption, defaultValue) !== defaultValue) {
|
|
514
|
+
|
|
515
|
+
return !defaultValue;
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
// Return the default.
|
|
519
|
+
return defaultValue;
|
|
520
|
+
};
|
|
521
|
+
|
|
522
|
+
// Return the scope level of a feature option.
|
|
523
|
+
const optionScope = (featureOption, deviceMac, defaultValue) => {
|
|
524
|
+
|
|
525
|
+
// Scope priority is always: device, NVR, global.
|
|
526
|
+
|
|
527
|
+
if(deviceMac) {
|
|
528
|
+
|
|
529
|
+
// Let's see if we've set it at the device-level.
|
|
530
|
+
if((isDeviceOptionEnabled(featureOption, deviceMac, defaultValue) !== defaultValue) || isOptionSet(featureOption, deviceMac)) {
|
|
531
|
+
|
|
532
|
+
return "device";
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
// Now let's test the controller level.
|
|
536
|
+
if((isDeviceOptionEnabled(featureOption, nvr.mac, defaultValue) !== defaultValue) || isOptionSet(featureOption, nvr.mac)) {
|
|
537
|
+
|
|
538
|
+
return "nvr";
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
// Finally, let's test the global level.
|
|
543
|
+
if((isGlobalOptionEnabled(featureOption, defaultValue) !== defaultValue) || isOptionSet(featureOption)) {
|
|
544
|
+
|
|
545
|
+
return "global";
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
// Option isn't set to a non-default value.
|
|
549
|
+
return "none";
|
|
550
|
+
};
|
|
551
|
+
|
|
552
|
+
// Return the color hinting for a given option's scope.
|
|
553
|
+
const optionScopeColor = (featureOption, deviceMac, defaultValue) => {
|
|
554
|
+
|
|
555
|
+
switch(optionScope(featureOption, deviceMac, defaultValue)) {
|
|
556
|
+
|
|
557
|
+
case "device":
|
|
558
|
+
|
|
559
|
+
return "text-info";
|
|
560
|
+
break;
|
|
561
|
+
|
|
562
|
+
case "nvr":
|
|
563
|
+
|
|
564
|
+
return "text-success";
|
|
565
|
+
break;
|
|
566
|
+
|
|
567
|
+
case "global":
|
|
568
|
+
|
|
569
|
+
return deviceMac ? "text-warning" : "text-info";
|
|
570
|
+
break;
|
|
571
|
+
|
|
572
|
+
default:
|
|
573
|
+
|
|
574
|
+
break;
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
return null;
|
|
578
|
+
};
|
|
579
|
+
|
|
463
580
|
// Show feature option information for a specific device, controller, or globally.
|
|
464
581
|
const showDeviceInfo = async (deviceId) => {
|
|
465
582
|
|
|
@@ -474,6 +591,10 @@
|
|
|
474
591
|
// Ensure we have a controller or device. The only time this won't be the case is when we're looking at global options.
|
|
475
592
|
if(ufpDevice) {
|
|
476
593
|
|
|
594
|
+
document.getElementById("deviceStatsHeader").style.display = "";
|
|
595
|
+
document.getElementById("device_model").classList.remove("text-center");
|
|
596
|
+
document.getElementById("device_model").colSpan = 1;
|
|
597
|
+
document.getElementById("device_model").style.fontWeight = "normal";
|
|
477
598
|
document.getElementById("device_model").innerHTML = ufpDevice.marketName ?? ufpDevice.type;
|
|
478
599
|
document.getElementById("device_mac").innerHTML = ufpDevice.mac;
|
|
479
600
|
document.getElementById("device_address").innerHTML = ufpDevice.host ?? (ufpDevice.modelKey === "sensor" ? "Bluetooth Device" : "None");
|
|
@@ -484,6 +605,10 @@
|
|
|
484
605
|
|
|
485
606
|
document.getElementById("deviceStatsTable").style.display = "none";
|
|
486
607
|
|
|
608
|
+
document.getElementById("deviceStatsHeader").style.display = "";
|
|
609
|
+
document.getElementById("device_model").classList.remove("text-center");
|
|
610
|
+
document.getElementById("device_model").colSpan = 1;
|
|
611
|
+
document.getElementById("device_model").style.fontWeight = "normal";
|
|
487
612
|
document.getElementById("device_model").innerHTML = "N/A"
|
|
488
613
|
document.getElementById("device_mac").innerHTML = "N/A";
|
|
489
614
|
document.getElementById("device_address").innerHTML = "N/A";
|
|
@@ -532,6 +657,7 @@
|
|
|
532
657
|
// Finally, add the table head to the table.
|
|
533
658
|
optionTable.appendChild(thead);
|
|
534
659
|
|
|
660
|
+
// Now enumerate all the feature options for a given device.
|
|
535
661
|
for(const option of optionsDevice[category.name]) {
|
|
536
662
|
|
|
537
663
|
// Only show feature options that are valid for this device.
|
|
@@ -553,24 +679,126 @@
|
|
|
553
679
|
|
|
554
680
|
const featureOption = category.name + (option.name.length ? ("." + option.name): "");
|
|
555
681
|
checkbox.type = "checkbox";
|
|
682
|
+
checkbox.readOnly = false;
|
|
556
683
|
checkbox.id = featureOption;
|
|
557
684
|
checkbox.name = featureOption;
|
|
558
685
|
checkbox.value = featureOption + (!ufpDevice ? "" : ("." + ufpDevice.mac));
|
|
559
|
-
|
|
560
|
-
|
|
686
|
+
|
|
687
|
+
switch(optionScope(featureOption, ufpDevice?.mac, option.default)) {
|
|
688
|
+
|
|
689
|
+
case "global":
|
|
690
|
+
case "nvr":
|
|
691
|
+
|
|
692
|
+
// If we're looking at the global scope, show the option value. Otherwise, we show that we're inheriting a value from the scope above.
|
|
693
|
+
if(!ufpDevice) {
|
|
694
|
+
|
|
695
|
+
checkbox.checked = isGlobalOptionEnabled(featureOption, option.default);
|
|
696
|
+
|
|
697
|
+
if(checkbox.checked) {
|
|
698
|
+
|
|
699
|
+
checkbox.indeterminate = false;
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
} else {
|
|
703
|
+
|
|
704
|
+
checkbox.readOnly = checkbox.indeterminate = true;
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
break;
|
|
708
|
+
|
|
709
|
+
case "device":
|
|
710
|
+
case "none":
|
|
711
|
+
default:
|
|
712
|
+
checkbox.checked = isDeviceOptionEnabled(featureOption, ufpDevice?.mac, option.default);
|
|
713
|
+
break;
|
|
714
|
+
}
|
|
561
715
|
|
|
562
716
|
checkbox.defaultChecked = option.default;
|
|
563
717
|
checkbox.classList.add("mx-2");
|
|
564
718
|
|
|
719
|
+
// Add the checkbox to the table cell.
|
|
720
|
+
tdCheckbox.appendChild(checkbox);
|
|
721
|
+
|
|
722
|
+
// Add the checkbox to the table row.
|
|
723
|
+
trX.appendChild(tdCheckbox);
|
|
724
|
+
|
|
725
|
+
const tdLabel = document.createElement("td");
|
|
726
|
+
tdLabel.classList.add("w-100");
|
|
727
|
+
|
|
728
|
+
// Create a label for the checkbox with our option description.
|
|
729
|
+
const labelDescription = document.createElement("label");
|
|
730
|
+
labelDescription.for = checkbox.id;
|
|
731
|
+
labelDescription.style.cursor = "pointer";
|
|
732
|
+
labelDescription.classList.add("user-select-none", "my-0", "py-0");
|
|
733
|
+
|
|
734
|
+
// Highlight options for the user that are different than our defaults.
|
|
735
|
+
const scopeColor = optionScopeColor(featureOption, ufpDevice?.mac, option.default);
|
|
736
|
+
|
|
737
|
+
if(scopeColor) {
|
|
738
|
+
|
|
739
|
+
labelDescription.classList.add(scopeColor);
|
|
740
|
+
}
|
|
741
|
+
|
|
565
742
|
// Add or remove the setting from our configuration when we've changed our state.
|
|
566
743
|
checkbox.addEventListener("change", async () => {
|
|
567
744
|
|
|
568
745
|
// Find the option in our list and delete it if it exists.
|
|
569
|
-
const optionRegex = new RegExp("^(Enable|Disable)\." + checkbox.id + (!ufpDevice ? "" : ("\." + ufpDevice.mac)) + "$", "gi")
|
|
746
|
+
const optionRegex = new RegExp("^(Enable|Disable)\." + checkbox.id + (!ufpDevice ? "" : ("\." + ufpDevice.mac)) + "$", "gi");
|
|
570
747
|
const newOptions = configOptions.filter(x => !x.match(optionRegex));
|
|
571
748
|
|
|
572
|
-
//
|
|
573
|
-
|
|
749
|
+
// Figure out if we've got the option set upstream.
|
|
750
|
+
let upstreamOption = false;
|
|
751
|
+
|
|
752
|
+
// We explicitly want to check for the scope of the feature option above where we are now, so we can appropriately determine what we should show.
|
|
753
|
+
switch(optionScope(checkbox.id, (ufpDevice && (ufpDevice.mac !== nvr.mac)) ? nvr.mac : null, option.default)) {
|
|
754
|
+
|
|
755
|
+
case "device":
|
|
756
|
+
case "nvr":
|
|
757
|
+
|
|
758
|
+
if(ufpDevice.mac !== nvr.mac) {
|
|
759
|
+
|
|
760
|
+
upstreamOption = true;
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
break;
|
|
764
|
+
|
|
765
|
+
case "global":
|
|
766
|
+
|
|
767
|
+
if(ufpDevice) {
|
|
768
|
+
|
|
769
|
+
upstreamOption = true;
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
break;
|
|
773
|
+
|
|
774
|
+
default:
|
|
775
|
+
|
|
776
|
+
break;
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
if(checkbox.readOnly) {
|
|
780
|
+
|
|
781
|
+
// We're truly unchecked. We need this because a checkbox can be in both an unchecked and indeterminate simultaneously,
|
|
782
|
+
// so we use the readOnly property to let us know that we've just cycled from an indeterminate state.
|
|
783
|
+
checkbox.checked = checkbox.readOnly = false;
|
|
784
|
+
} else if(!checkbox.checked) {
|
|
785
|
+
|
|
786
|
+
// If we have an upstream option configured, we reveal a third state to show inheritance of that option and allow the user to select it.
|
|
787
|
+
if(upstreamOption) {
|
|
788
|
+
|
|
789
|
+
// We want to set the readOnly property as well, since it will survive a user interaction when they click the checkbox to clear out the
|
|
790
|
+
// indeterminate state. This allows us to effectively cycle between three states.
|
|
791
|
+
checkbox.readOnly = checkbox.indeterminate = true;
|
|
792
|
+
}
|
|
793
|
+
|
|
794
|
+
} else if(checkbox.checked) {
|
|
795
|
+
|
|
796
|
+
// We've explicitly checked this option.
|
|
797
|
+
checkbox.readOnly = checkbox.indeterminate = false;
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
// The setting is different from the default, highlight it for the user, accounting for upstream scope, and add it to our configuration.
|
|
801
|
+
if(!checkbox.indeterminate && ((checkbox.checked !== option.default) || upstreamOption)) {
|
|
574
802
|
|
|
575
803
|
labelDescription.classList.add("text-info");
|
|
576
804
|
newOptions.push((checkbox.checked ? "Enable." : "Disable.") + checkbox.value);
|
|
@@ -584,28 +812,18 @@
|
|
|
584
812
|
currentConfig[0].options = newOptions;
|
|
585
813
|
updateConfigOptions(newOptions);
|
|
586
814
|
await homebridge.updatePluginConfig(currentConfig);
|
|
587
|
-
});
|
|
588
815
|
|
|
589
|
-
|
|
590
|
-
|
|
816
|
+
// If we've reset to defaults, make sure our color coding for scope is reflected.
|
|
817
|
+
if((checkbox.checked === option.default) || checkbox.indeterminate) {
|
|
591
818
|
|
|
592
|
-
|
|
593
|
-
trX.appendChild(tdCheckbox);
|
|
819
|
+
const scopeColor = optionScopeColor(featureOption, ufpDevice?.mac, option.default);
|
|
594
820
|
|
|
595
|
-
|
|
596
|
-
tdLabel.classList.add("w-100");
|
|
821
|
+
if(scopeColor) {
|
|
597
822
|
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
labelDescription.classList.add("user-select-none", "my-0", "py-0");
|
|
603
|
-
|
|
604
|
-
// Highlight options for the user that are different than our defaults.
|
|
605
|
-
if(checkbox.checked !== checkbox.defaultChecked) {
|
|
606
|
-
|
|
607
|
-
labelDescription.classList.add("text-info");
|
|
608
|
-
}
|
|
823
|
+
labelDescription.classList.add(scopeColor);
|
|
824
|
+
}
|
|
825
|
+
}
|
|
826
|
+
});
|
|
609
827
|
|
|
610
828
|
// Add the actual description for the option after the checkbox.
|
|
611
829
|
labelDescription.appendChild(document.createTextNode(option.description));
|
package/homebridge-ui/server.js
CHANGED
|
@@ -50,40 +50,40 @@ class PluginUiServer extends HomebridgePluginUiServer {
|
|
|
50
50
|
|
|
51
51
|
bootstrap.cameras.sort((a, b) => {
|
|
52
52
|
|
|
53
|
-
const aCase = a.name.toLowerCase();
|
|
54
|
-
const bCase = b.name.toLowerCase();
|
|
53
|
+
const aCase = (a.name ?? a.marketName).toLowerCase();
|
|
54
|
+
const bCase = (b.name ?? b.marketName).toLowerCase();
|
|
55
55
|
|
|
56
56
|
return aCase > bCase ? 1 : (bCase > aCase ? -1 : 0);
|
|
57
57
|
});
|
|
58
58
|
|
|
59
59
|
bootstrap.chimes.sort((a, b) => {
|
|
60
60
|
|
|
61
|
-
const aCase = a.name.toLowerCase();
|
|
62
|
-
const bCase = b.name.toLowerCase();
|
|
61
|
+
const aCase = (a.name ?? a.marketName).toLowerCase();
|
|
62
|
+
const bCase = (b.name ?? b.marketName).toLowerCase();
|
|
63
63
|
|
|
64
64
|
return aCase > bCase ? 1 : (bCase > aCase ? -1 : 0);
|
|
65
65
|
});
|
|
66
66
|
|
|
67
67
|
bootstrap.lights.sort((a, b) => {
|
|
68
68
|
|
|
69
|
-
const aCase = a.name.toLowerCase();
|
|
70
|
-
const bCase = b.name.toLowerCase();
|
|
69
|
+
const aCase = (a.name ?? a.marketName).toLowerCase();
|
|
70
|
+
const bCase = (b.name ?? b.marketName).toLowerCase();
|
|
71
71
|
|
|
72
72
|
return aCase > bCase ? 1 : (bCase > aCase ? -1 : 0);
|
|
73
73
|
});
|
|
74
74
|
|
|
75
75
|
bootstrap.sensors.sort((a, b) => {
|
|
76
76
|
|
|
77
|
-
const aCase = a.name.toLowerCase();
|
|
78
|
-
const bCase = b.name.toLowerCase();
|
|
77
|
+
const aCase = (a.name ?? a.marketName).toLowerCase();
|
|
78
|
+
const bCase = (b.name ?? b.marketName).toLowerCase();
|
|
79
79
|
|
|
80
80
|
return aCase > bCase ? 1 : (bCase > aCase ? -1 : 0);
|
|
81
81
|
});
|
|
82
82
|
|
|
83
83
|
bootstrap.viewers.sort((a, b) => {
|
|
84
84
|
|
|
85
|
-
const aCase = a.name.toLowerCase();
|
|
86
|
-
const bCase = b.name.toLowerCase();
|
|
85
|
+
const aCase = (a.name ?? a.marketName).toLowerCase();
|
|
86
|
+
const bCase = (b.name ?? b.marketName).toLowerCase();
|
|
87
87
|
|
|
88
88
|
return aCase > bCase ? 1 : (bCase > aCase ? -1 : 0);
|
|
89
89
|
});
|
|
@@ -129,6 +129,7 @@ class PluginUiServer extends HomebridgePluginUiServer {
|
|
|
129
129
|
|
|
130
130
|
// Compatibility for older versions of the Homebridge UI.
|
|
131
131
|
this.onRequest("/getCachedAccessories", async () => {
|
|
132
|
+
|
|
132
133
|
try {
|
|
133
134
|
|
|
134
135
|
// Define the plugin and create the array to return.
|
|
@@ -159,6 +160,7 @@ class PluginUiServer extends HomebridgePluginUiServer {
|
|
|
159
160
|
return [];
|
|
160
161
|
}
|
|
161
162
|
});
|
|
163
|
+
|
|
162
164
|
this.ready();
|
|
163
165
|
}
|
|
164
166
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "homebridge-unifi-protect",
|
|
3
|
-
"version": "6.
|
|
3
|
+
"version": "6.8.1",
|
|
4
4
|
"displayName": "Homebridge UniFi Protect",
|
|
5
5
|
"description": "Homebridge UniFi Protect plugin providing complete HomeKit integration for the UniFi Protect ecosystem with full support for most features including autoconfiguration, motion detection, multiple controllers, and realtime updates.",
|
|
6
6
|
"author": {
|
|
@@ -68,18 +68,18 @@
|
|
|
68
68
|
"@homebridge/plugin-ui-utils": "^0.0.19",
|
|
69
69
|
"ffmpeg-for-homebridge": "^0.1.4",
|
|
70
70
|
"mqtt": "4.3.7",
|
|
71
|
-
"unifi-protect": "^4.2.
|
|
71
|
+
"unifi-protect": "^4.2.4",
|
|
72
72
|
"ws": "^8.13.0"
|
|
73
73
|
},
|
|
74
74
|
"devDependencies": {
|
|
75
|
-
"@types/node": "^20.
|
|
75
|
+
"@types/node": "^20.2.5",
|
|
76
76
|
"@types/ws": "^8.5.4",
|
|
77
|
-
"@typescript-eslint/eslint-plugin": "^5.59.
|
|
78
|
-
"@typescript-eslint/parser": "^5.59.
|
|
79
|
-
"eslint": "^8.
|
|
77
|
+
"@typescript-eslint/eslint-plugin": "^5.59.8",
|
|
78
|
+
"@typescript-eslint/parser": "^5.59.8",
|
|
79
|
+
"eslint": "^8.42.0",
|
|
80
80
|
"homebridge": "=1.6.1",
|
|
81
81
|
"nodemon": "^2.0.22",
|
|
82
|
-
"rimraf": "^5.0.
|
|
83
|
-
"typescript": "^5.
|
|
82
|
+
"rimraf": "^5.0.1",
|
|
83
|
+
"typescript": "^5.1.3"
|
|
84
84
|
}
|
|
85
85
|
}
|