Skip to content

Commit 8ea7fe4

Browse files
committed
arrow binding in input focus
1 parent 1aeafa4 commit 8ea7fe4

File tree

11 files changed

+153
-26
lines changed

11 files changed

+153
-26
lines changed

app/actions.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -704,6 +704,12 @@ export async function updateSettings(formData) {
704704
metrics: formData.get("metricsEnabled") === "true",
705705
};
706706
}
707+
if (updateIfExists("bihost")) {
708+
newConfig.blueiris = {
709+
...currentConfig.blueiris,
710+
host: formData.get("bihost"),
711+
};
712+
}
707713
const result = await saveConfig(newConfig);
708714
if (!result.success) {
709715
return { success: false, error: result.error };

app/api/plate-reads/route.js

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,24 @@ export async function POST(req) {
209209
}
210210
}
211211

212+
let biPath = null;
213+
if (data.ALERT_CLIP && data.ALERT_PATH && camera) {
214+
try {
215+
// Extract offset from ALERT_PATH (format: CAMERAID.YYYYMMDD_HHMMSS.OFFSET.3-1.jpg)
216+
const parts = data.ALERT_PATH.split(".");
217+
const msOffset = parts[2];
218+
// Remove @ from ALERT_CLIP and construct path
219+
const recId = data.ALERT_CLIP.replace("@", "");
220+
biPath = `ui3.htm?rec=${recId}-${msOffset}&cam=${camera}`;
221+
} catch (error) {
222+
console.error("Error constructing bi_path:", error);
223+
}
224+
} else {
225+
console.warn(
226+
"[Blue Iris] Incomplete request. Your alert action is not sending the ALERT_PATH and ALERT_CLIP macros. These values are needed for BI integration. See readme for more info."
227+
);
228+
}
229+
212230
const result = await dbClient.query(
213231
`WITH new_plate AS (
214232
INSERT INTO plates (plate_number)
@@ -222,9 +240,10 @@ export async function POST(req) {
222240
image_path,
223241
thumbnail_path,
224242
timestamp,
225-
camera_name
243+
camera_name,
244+
bi_path
226245
)
227-
SELECT $1, $2, $3, $4, $5, $6
246+
SELECT $1, $2, $3, $4, $5, $6, $7
228247
WHERE NOT EXISTS (
229248
SELECT 1 FROM plate_reads
230249
WHERE plate_number = $1 AND timestamp = $5
@@ -239,6 +258,7 @@ export async function POST(req) {
239258
imagePaths.thumbnailPath,
240259
timestamp,
241260
camera,
261+
biPath, // Add biPath parameter here
242262
]
243263
);
244264

app/live_feed/page.jsx

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
import {
2+
getSettings,
23
getLatestPlateReads,
34
getTags,
45
getCameraNames,
56
getTimeFormat,
67
} from "@/app/actions";
8+
79
import PlateTableWrapper from "@/components/PlateTableWrapper";
810
import DashboardLayout from "@/components/layout/MainLayout";
911
import BasicTitle from "@/components/layout/BasicTitle";
@@ -37,12 +39,14 @@ export default async function LivePlates(props) {
3739
sortDirection: searchParams?.sortDirection,
3840
};
3941

40-
const [platesRes, tagsRes, camerasRes, timeFormat] = await Promise.all([
41-
getLatestPlateReads(params),
42-
getTags(),
43-
getCameraNames(),
44-
getTimeFormat(),
45-
]);
42+
const [platesRes, tagsRes, camerasRes, timeFormat, config] =
43+
await Promise.all([
44+
getLatestPlateReads(params),
45+
getTags(),
46+
getCameraNames(),
47+
getTimeFormat(),
48+
getSettings(),
49+
]);
4650

4751
return (
4852
<DashboardLayout>
@@ -54,6 +58,7 @@ export default async function LivePlates(props) {
5458
tags={tagsRes.success ? tagsRes.data : []}
5559
cameras={camerasRes.success ? camerasRes.data : []}
5660
timeFormat={timeFormat}
61+
biHost={config?.blueiris?.host} // Add this line
5762
/>
5863
</Suspense>
5964
</BasicTitle>

app/settings/SettingsForm.jsx

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ const navigation = [
4747
{ title: "Database", id: "database" },
4848
{ title: "Push Notifications", id: "push" },
4949
{ title: "HomeAssistant", id: "homeassistant" },
50+
{ title: "Blue Iris", id: "blueiris" },
5051
{ title: "Security", id: "security" },
5152
{ title: "Sharing & Privacy", id: "privacy" },
5253
];
@@ -106,6 +107,9 @@ export default function SettingsForm({ initialSettings, initialApiKey }) {
106107
newFormData.append("haWhitelist", formData.get("haWhitelist"));
107108
}
108109
break;
110+
case "blueiris":
111+
newFormData.append("bihost", formData.get("bihost"));
112+
break;
109113
case "privacy":
110114
newFormData.append(
111115
"metricsEnabled",
@@ -572,6 +576,26 @@ export default function SettingsForm({ initialSettings, initialApiKey }) {
572576
</div>
573577
);
574578

579+
const renderBlueirisSection = () => (
580+
<div key="mqtt-section" className="space-y-4">
581+
<h3 className="text-lg font-semibold">Blue Iris Configuration</h3>
582+
<div className="grid grid-cols-2 gap-4">
583+
<div className="space-y-2">
584+
<Label htmlFor="bihost">
585+
Blue Iris Hostname or IP address (Include :port if not port 80)
586+
</Label>
587+
<Input
588+
id="bihost"
589+
name="bihost"
590+
defaultValue={initialSettings.blueiris.host}
591+
placeholder="192.168.1.68"
592+
autoComplete="off"
593+
/>
594+
</div>
595+
</div>
596+
</div>
597+
);
598+
575599
const renderSection = () => (
576600
<div key={activeSection}>
577601
{(() => {
@@ -590,6 +614,8 @@ export default function SettingsForm({ initialSettings, initialApiKey }) {
590614
return renderSecuritySection();
591615
case "privacy":
592616
return renderPrivacySection();
617+
case "blueiris":
618+
return renderBlueirisSection();
593619
default:
594620
return null;
595621
}

components/PlateTable.jsx

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import {
1919
ChevronUp,
2020
ChevronDown,
2121
ChevronsUpDown,
22+
Pencil,
2223
} from "lucide-react";
2324
import { Badge } from "@/components/ui/badge";
2425
import { Button } from "@/components/ui/button";
@@ -71,6 +72,7 @@ import { format } from "date-fns";
7172
import { Switch } from "@/components/ui/switch";
7273
import { useRouter } from "next/navigation";
7374
import PlateImage from "@/components/PlateImage";
75+
import { getSettings } from "@/app/actions";
7476

7577
const SortButton = ({ label, field, sort, onSort }) => {
7678
const isActive = sort.field === field;
@@ -125,11 +127,20 @@ export default function PlateTable({
125127
const [searchInput, setSearchInput] = useState(filters.search || "");
126128
const [isLive, setIsLive] = useState(true);
127129
const [prefetchedImages, setPrefetchedImages] = useState(new Set());
130+
const [biHost, setBiHost] = useState(null);
128131

129132
const router = useRouter();
130133

131134
// Cycle through images without clicking out with arrow keys
132135
const handleKeyPress = (e) => {
136+
// Don't handle arrow keys if any input element is focused
137+
if (
138+
document.activeElement?.tagName === "INPUT" ||
139+
document.activeElement?.tagName === "TEXTAREA"
140+
) {
141+
return;
142+
}
143+
133144
if (selectedImage === null) return;
134145

135146
if (e.key === "ArrowRight") {
@@ -154,6 +165,17 @@ export default function PlateTable({
154165
}
155166
}, [selectedImage, selectedIndex, data]);
156167

168+
useEffect(() => {
169+
async function fetchBiHost() {
170+
const config = await getSettings();
171+
console.log(config.blueiris.host);
172+
if (config?.blueiris?.host) {
173+
setBiHost(config.blueiris.host);
174+
}
175+
}
176+
fetchBiHost();
177+
}, []);
178+
157179
useEffect(() => {
158180
let interval;
159181
if (isLive) {
@@ -841,6 +863,7 @@ export default function PlateTable({
841863
hour12: timeFormat === 12,
842864
})}
843865
</TableCell>
866+
844867
<TableCell>
845868
<div className="flex space-x-2 justify-end">
846869
<DropdownMenu>
@@ -893,8 +916,22 @@ export default function PlateTable({
893916
setIsCorrectPlateOpen(true);
894917
}}
895918
>
896-
<Edit className="h-4 w-4" />
919+
<Pencil className="h-4 w-4" />
897920
</Button>
921+
{biHost && plate.bi_path && (
922+
<Button
923+
variant="ghost"
924+
size="icon"
925+
onClick={() =>
926+
window.open(
927+
`http://${biHost}/${plate.bi_path}`,
928+
"_blank"
929+
)
930+
}
931+
>
932+
<ExternalLink className="h-4 w-4" />
933+
</Button>
934+
)}
898935
<Button
899936
variant="ghost"
900937
size="icon"

components/PlateTableWrapper.jsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ export default function PlateTableWrapper({
1717
tags,
1818
cameras,
1919
timeFormat,
20+
biHost,
2021
}) {
2122
const router = useRouter();
2223
const pathname = usePathname();
@@ -131,6 +132,7 @@ export default function PlateTableWrapper({
131132
availableTags={[{ name: "untagged", color: "#6B7280" }, ...tags]}
132133
availableCameras={cameras}
133134
timeFormat={timeFormat}
135+
biHost={biHost}
134136
pagination={{
135137
page: parseInt(params.get("page") || "1"),
136138
pageSize: parseInt(params.get("pageSize") || "25"),
File renamed without changes.

lib/db.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -200,6 +200,7 @@ export async function getPlateReads({
200200
pr.plate_number,
201201
pr.image_data,
202202
pr.image_path,
203+
pr.bi_path,
203204
pr.thumbnail_path,
204205
pr.timestamp,
205206
pr.camera_name,
@@ -221,6 +222,7 @@ export async function getPlateReads({
221222
pr.image_data,
222223
pr.image_path,
223224
pr.thumbnail_path,
225+
pr.bi_path,
224226
pr.timestamp,
225227
pr.camera_name,
226228
p.occurrence_count,

lib/settings.js

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,9 @@ const DEFAULT_CONFIG = {
3535
enabled: false,
3636
whitelist: [],
3737
},
38+
blueiris: {
39+
host: "",
40+
},
3841
privacy: {
3942
metrics: true,
4043
},
@@ -86,6 +89,9 @@ function getInitialEnvConfig() {
8689
enabled: DEFAULT_CONFIG.homeassistant.enabled,
8790
whitelist: DEFAULT_CONFIG.homeassistant.whitelist,
8891
},
92+
blueiris: {
93+
host: DEFAULT_CONFIG.privacy.metrics,
94+
},
8995
privacy: {
9096
metrics: process.env.METRICS || DEFAULT_CONFIG.privacy.metrics,
9197
},
@@ -152,6 +158,10 @@ export async function getConfig() {
152158
...DEFAULT_CONFIG.homeassistant,
153159
...fileConfig.homeassistant,
154160
},
161+
blueiris: {
162+
...DEFAULT_CONFIG.blueiris,
163+
...fileConfig.blueiris,
164+
},
155165
privacy: {
156166
...DEFAULT_CONFIG.privacy,
157167
...fileConfig.privacy,
@@ -201,6 +211,10 @@ export async function saveConfig(newConfig) {
201211
...DEFAULT_CONFIG.homeassistant,
202212
...newConfig.homeassistant,
203213
},
214+
blueiris: {
215+
...DEFAULT_CONFIG.blueiris,
216+
...newConfig.blueiris,
217+
},
204218
privacy: {
205219
...DEFAULT_CONFIG.privacy,
206220
...newConfig.privacy,

middleware.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ export async function middleware(request) {
2323
"/api/verify-whitelist",
2424
"/api/check-update",
2525
"/update",
26+
"/api/test",
2627
];
2728

2829
// Check for API key in query parameters for iframe embeds (insecure)

0 commit comments

Comments
 (0)