Skip to content

Add support for manual devices in device configuration and DHCP manager#186

Open
DanielLavrushin wants to merge 4 commits intomainfrom
1-49-0
Open

Add support for manual devices in device configuration and DHCP manager#186
DanielLavrushin wants to merge 4 commits intomainfrom
1-49-0

Conversation

@DanielLavrushin
Copy link
Copy Markdown
Owner

  • Introduced ManualDevice struct and updated DevicesConfig to include manual_devices.
  • Enhanced DHCP manager to handle manual devices, including methods for setting and retrieving them.
  • Updated UI components to display and manage manual devices, including adding and removing functionality.
  • Added translations for manual device features in English and Russian.

- Introduced ManualDevice struct and updated DevicesConfig to include manual_devices.
- Enhanced DHCP manager to handle manual devices, including methods for setting and retrieving them.
- Updated UI components to display and manage manual devices, including adding and removing functionality.
- Added translations for manual device features in English and Russian.
Copilot AI review requested due to automatic review settings April 7, 2026 21:37
@codacy-production
Copy link
Copy Markdown

codacy-production bot commented Apr 7, 2026

Not up to standards ⛔

🔴 Issues 1 high · 2 medium

Alerts:
⚠ 3 issues (≤ 0 issues of at least minor severity)

Results:
3 new issues

Category Results
BestPractice 2 medium
Security 1 high

View in Codacy

🟢 Metrics 58 complexity

Metric Results
Complexity 58

View in Codacy

TIP This summary will be updated as you push new changes. Give us feedback

…ve unified device model, remove separate alias file, and fix traffic routing issue on Keenetic routers.
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds “manual devices” support by unifying device configuration into a single device model across backend config, DHCP mapping, and the UI, so devices that aren’t discoverable via ARP can still appear in device pickers and source-device filters.

Changes:

  • Replaced the legacy mac / mss_clamps device config with a unified devices: Device[] model (incl. selected, name, mss_clamp, is_manual, ip).
  • Extended the DHCP manager to merge ARP-derived mappings with configured manual devices (and operate in manual-only mode).
  • Updated UI to manage manual devices and reflect them in device lists (plus i18n updates).

Reviewed changes

Copilot reviewed 25 out of 25 changed files in this pull request and generated 9 comments.

Show a summary per file
File Description
src/tables/routing.go Adjusts auto routing table ID allocation range.
src/tables/nftables.go Switches device filtering to use SelectedMACs() from the new device model.
src/tables/monitor.go Updates monitoring checks to rely on SelectedMACs().
src/tables/iptables.go Updates iptables manifest generation to rely on SelectedMACs().
src/nfq/pool.go Plumbs manual devices into DHCP manager on startup and config update.
src/http/ui/src/models/devices.ts Updates UI typings to use unified Device model and adds is_manual.
src/http/ui/src/models/config.ts Introduces unified Device type and updates DevicesConfig.
src/http/ui/src/i18n/ru.json Adds RU strings for manual device UI.
src/http/ui/src/i18n/en.json Adds EN strings for manual device UI.
src/http/ui/src/hooks/useDevices.ts Removes alias mutation helpers; keeps list/refresh only.
src/http/ui/src/components/settings/Devices.tsx Adds manual device add/remove UI and migrates selection/MSS/name to unified model.
src/http/ui/src/components/sets/Target.tsx Displays manual devices in source-device selection (chip + MAC placeholder).
src/http/ui/src/barrels/settings.ts Re-exports the new Device type for consumers.
src/http/ui/src/api/devices.ts Removes alias endpoints usage from the UI API client.
src/http/handler/types.go Removes deviceAliases from API handler state.
src/http/handler/devices.go Populates alias/is_manual from config device entries instead of alias store + adds is_manual field.
src/http/handler/common.go Stops initializing the removed device-alias store.
src/dhcp/manager.go Adds manual device merging, manual-only availability, and source reporting.
src/config/types.go Replaces legacy device config fields with unified DevicesConfig.Devices []Device.
src/config/migration.go Adds v33→v34 migration to unify device model + migrate aliases/manual devices.
src/config/methods.go Updates MSS clamp collection + adds SelectedMACs/FindByMAC/ManualEntries helpers.
src/config/methods_test.go Updates MSS clamp tests to use unified device model.
src/config/devices.go Removes the old on-disk MAC alias store implementation.
src/config/config.go Updates defaults to initialize Devices: []Device{}.
Comments suppressed due to low confidence (1)

src/dhcp/manager.go:158

  • The refresh() log message says entries were loaded "from ARP table" even when ARP is unavailable and mappings come only from manual devices. Adjust the message to reflect the actual source (e.g., manual-only vs arp+manual) to avoid misleading operational logs.
	count := len(m.ipToMAC)
	m.mu.Unlock()

	log.Infof("DHCP: loaded %d entries from ARP table", count)
	m.notifyCallbacks()

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/http/ui/src/components/settings/Devices.tsx Outdated
Comment thread src/http/ui/src/components/settings/Devices.tsx Outdated
Comment thread src/tables/monitor.go
Comment thread src/tables/routing.go
Comment thread src/config/methods.go Outdated
Comment thread src/config/methods.go
Comment thread src/dhcp/manager.go
Comment thread src/tables/nftables.go Outdated
Comment thread src/tables/iptables.go Outdated
Copilot AI review requested due to automatic review settings April 7, 2026 22:18
@sonarqubecloud
Copy link
Copy Markdown

sonarqubecloud bot commented Apr 7, 2026

Quality Gate Failed Quality Gate failed

Failed conditions
12 Security Hotspots

See analysis details on SonarQube Cloud

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 33 out of 34 changed files in this pull request and generated 4 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/tables/routing.go
Comment on lines 696 to 700
for attempt := uint32(0); attempt < 4096; attempt++ {
table := 100 + int((base+attempt)%2000)
table := 100 + int((base+attempt)%150)
mark := uint32(0x100 + (base+attempt)%0x7E00)
if _, ok := usedMarks[mark]; ok {
continue
Copy link

Copilot AI Apr 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

routeResolveIDs now constrains the primary table selection to 100..249, but the fallback path (after the 4096-attempt loop) still increments table++ without any upper bound. If the safe-range tables are exhausted, this fallback can still produce table IDs >255, which defeats the stated BusyBox/keenetic compatibility goal.

Consider constraining the fallback table allocation to the same safe range (or returning an explicit error when no safe table is available) so table IDs never exceed 255.

Copilot uses AI. Check for mistakes.
Comment on lines +92 to +108
const handleSelectAll = (selectAll: boolean) => {
const current = [...configDevices];
const allMacs = new Set(devices.map((d) => d.mac.toUpperCase()));
const updated = current.map((d) =>
allMacs.has(d.mac.toUpperCase()) ? { ...d, selected: selectAll } : d
);
if (size === null || size === 0) {
if (idx !== -1) current.splice(idx, 1);
} else if (idx === -1) {
current.push({ mac: mac.toUpperCase(), size });
} else {
current[idx] = { ...current[idx], size };
if (selectAll) {
for (const d of devices) {
if (!updated.some((u) => u.mac.toUpperCase() === d.mac.toUpperCase())) {
updated.push({ mac: d.mac.toUpperCase(), selected: true });
}
}
}
onChange("queue.devices.mss_clamps", current);
const cleaned = updated.filter(
(d) => d.selected || d.is_manual || (d.mss_clamp && d.mss_clamp > 0) || d.name
);
onChange("queue.devices.devices", cleaned);
Copy link

Copilot AI Apr 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

handleSelectAll(false) only clears selected for config entries whose MACs are present in the currently-discovered devices list. Any previously-selected MAC that is no longer in devices (e.g., an offline device) will stay selected in config, but it won’t be visible in the table to let the user deselect it.

Consider making deselect-all clear selection for all non-manual config devices (or otherwise ensure hidden/stale selections can be cleared).

Copilot uses AI. Check for mistakes.
Comment on lines +36 to 46
const generateSyntheticMAC = (ip: string): string => {
const parts = ip.trim().split(".");
if (parts.length !== 4) return "";
const octets = parts.map((p) => {
if (!/^\d+$/.test(p)) return -1;
const n = Number(p);
return n >= 0 && n <= 255 ? n : -1;
});
if (octets.some((o) => o < 0)) return "";
return `02:B4:${octets.map((o) => o.toString(16).toUpperCase().padStart(2, "0")).join(":")}`;
};
Copy link

Copilot AI Apr 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The UI-side generateSyntheticMAC only accepts IPv4 dotted-quad input. The backend migration helper (config/migration.go:generateSyntheticMAC) supports both IPv4 and IPv6, so users could end up with configs containing IPv6 manual devices that the UI cannot add/recreate.

Consider either adding IPv6 support here (e.g., via a real IP parser) or explicitly validating/rejecting IPv6 with a clear UI error message so behavior is consistent across UI and backend.

Copilot uses AI. Check for mistakes.
Comment thread src/dhcp/manager_test.go
Comment on lines +159 to +171
m.mu.Lock()
m.ipToMAC = map[string]string{"192.168.1.1": "AA:BB:CC:DD:EE:FF"}
m.macToIP = map[string]string{"AA:BB:CC:DD:EE:FF": "192.168.1.1"}
m.hostnames = make(map[string]string)
for _, d := range m.manualDevices {
mac := normalizeMAC(d.MAC)
m.ipToMAC[d.IP] = mac
m.macToIP[mac] = d.IP
if d.Name != "" {
m.hostnames[mac] = d.Name
}
}
m.mu.Unlock()
Copy link

Copilot AI Apr 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TestManualDevicesMerge reimplements the manual-device merge logic inline instead of exercising (*Manager).refresh(). This means the test can pass even if refresh() is broken (e.g., lock ordering, overwrite behavior, hostname handling).

Since this test is in package dhcp, it can call the unexported m.refresh() directly after setting m.manualDevices / m.available as needed, which would better validate the production code path.

Suggested change
m.mu.Lock()
m.ipToMAC = map[string]string{"192.168.1.1": "AA:BB:CC:DD:EE:FF"}
m.macToIP = map[string]string{"AA:BB:CC:DD:EE:FF": "192.168.1.1"}
m.hostnames = make(map[string]string)
for _, d := range m.manualDevices {
mac := normalizeMAC(d.MAC)
m.ipToMAC[d.IP] = mac
m.macToIP[mac] = d.IP
if d.Name != "" {
m.hostnames[mac] = d.Name
}
}
m.mu.Unlock()
m.refresh()

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants