unifi: list networks and set the DNS their DHCP hands out (hq issue 198) #215
@@ -23,6 +23,19 @@ export interface UnifiPortForward {
|
|||||||
site_id?: string;
|
site_id?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface UnifiNetwork {
|
||||||
|
_id: string;
|
||||||
|
name: string;
|
||||||
|
purpose: string;
|
||||||
|
ip_subnet?: string;
|
||||||
|
dhcpd_enabled?: boolean;
|
||||||
|
dhcpd_dns_enabled?: boolean;
|
||||||
|
dhcpd_dns_1?: string;
|
||||||
|
dhcpd_dns_2?: string;
|
||||||
|
dhcpd_dns_3?: string;
|
||||||
|
dhcpd_dns_4?: string;
|
||||||
|
}
|
||||||
|
|
||||||
export interface UnifiDevice {
|
export interface UnifiDevice {
|
||||||
_id: string;
|
_id: string;
|
||||||
name: string;
|
name: string;
|
||||||
@@ -232,6 +245,17 @@ export class UnifiApiClient {
|
|||||||
await this.request<unknown>("DELETE", `/api/s/${this.site}/rest/portforward/${id}`);
|
await this.request<unknown>("DELETE", `/api/s/${this.site}/rest/portforward/${id}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --- Networks ---
|
||||||
|
|
||||||
|
async listNetworks(): Promise<UnifiNetwork[]> {
|
||||||
|
return this.request<UnifiNetwork>("GET", `/api/s/${this.site}/rest/networkconf`);
|
||||||
|
}
|
||||||
|
|
||||||
|
async updateNetwork(id: string, fields: Partial<UnifiNetwork>): Promise<UnifiNetwork> {
|
||||||
|
const result = await this.request<UnifiNetwork>("PUT", `/api/s/${this.site}/rest/networkconf/${id}`, fields);
|
||||||
|
return result[0];
|
||||||
|
}
|
||||||
|
|
||||||
// --- Devices ---
|
// --- Devices ---
|
||||||
|
|
||||||
async listDevices(): Promise<UnifiDevice[]> {
|
async listDevices(): Promise<UnifiDevice[]> {
|
||||||
|
|||||||
@@ -1,10 +1,23 @@
|
|||||||
// unifi's tools — its own code (novox/hq ADR 0039), importing unifi's own client. They return
|
// unifi's tools — its own code (novox/hq ADR 0039), importing unifi's own client. They return
|
||||||
// structured data; the mesh serves them through the sdk's tool harness. unifi is tools-only (no
|
// structured data; the mesh serves them through the sdk's tool harness. unifi is tools-only (no
|
||||||
// events entrypoint): the controller does not push lifecycle events the mesh consumes, so this
|
// events entrypoint): the controller does not push lifecycle events the mesh consumes, so this
|
||||||
// module reads its resources — port forwards, devices, clients — and exposes them, and stops there.
|
// module reads its resources — port forwards, networks, devices, clients — and exposes them, and stops there.
|
||||||
|
|
||||||
import { registerModuleTools, type ToolDefinition } from "@novox/mesh-sdk/tools";
|
import { registerModuleTools, type ToolDefinition } from "@novox/mesh-sdk/tools";
|
||||||
import { UnifiApiClient, type UnifiPortForward } from "../client.js";
|
import { UnifiApiClient, type UnifiNetwork, type UnifiPortForward } from "../client.js";
|
||||||
|
|
||||||
|
function summarizeNetwork(n: UnifiNetwork): Record<string, unknown> {
|
||||||
|
const dns = [n.dhcpd_dns_1, n.dhcpd_dns_2, n.dhcpd_dns_3, n.dhcpd_dns_4].filter((s): s is string => !!s);
|
||||||
|
return {
|
||||||
|
id: n._id,
|
||||||
|
name: n.name,
|
||||||
|
purpose: n.purpose,
|
||||||
|
subnet: n.ip_subnet ?? null,
|
||||||
|
dhcp: n.dhcpd_enabled ?? null,
|
||||||
|
// What DHCP hands out as DNS: the listed servers when set, otherwise the gateway itself.
|
||||||
|
dhcp_dns: n.dhcpd_dns_enabled ? dns : "the gateway",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
function summarizePortForward(r: UnifiPortForward): Record<string, unknown> {
|
function summarizePortForward(r: UnifiPortForward): Record<string, unknown> {
|
||||||
return {
|
return {
|
||||||
@@ -88,6 +101,40 @@ export function getUnifiTools(unifi: UnifiApiClient): ToolDefinition[] {
|
|||||||
return { deleted: true, id: String(args.id) };
|
return { deleted: true, id: String(args.id) };
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "unifi_list_networks",
|
||||||
|
description: "List the networks the UniFi controller manages, with the DNS servers each one's DHCP hands out.",
|
||||||
|
input: {},
|
||||||
|
run: async () => {
|
||||||
|
const nets = await unifi.listNetworks();
|
||||||
|
return { count: nets.length, networks: nets.map(summarizeNetwork) };
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "unifi_set_network_dns",
|
||||||
|
description:
|
||||||
|
"Set the DNS servers a network's DHCP hands out to its devices (see unifi_list_networks for ids). " +
|
||||||
|
"Up to four addresses, comma-separated; \"gateway\" hands out the gateway itself. Devices pick it up when they renew.",
|
||||||
|
input: {
|
||||||
|
id: { type: "string", description: "the network id" },
|
||||||
|
dns: { type: "string", description: "comma-separated DNS server addresses, or \"gateway\"" },
|
||||||
|
},
|
||||||
|
run: async (args) => {
|
||||||
|
const asked = String(args.dns ?? "").trim();
|
||||||
|
if (!asked) return { updated: false, reason: "say dns: addresses, or \"gateway\"" };
|
||||||
|
const servers = asked === "gateway" ? [] : asked.split(",").map((s) => s.trim()).filter(Boolean);
|
||||||
|
if (servers.length > 4) return { updated: false, reason: "DHCP hands out at most four DNS servers" };
|
||||||
|
const fields: Partial<UnifiNetwork> = {
|
||||||
|
dhcpd_dns_enabled: servers.length > 0,
|
||||||
|
dhcpd_dns_1: servers[0] ?? "",
|
||||||
|
dhcpd_dns_2: servers[1] ?? "",
|
||||||
|
dhcpd_dns_3: servers[2] ?? "",
|
||||||
|
dhcpd_dns_4: servers[3] ?? "",
|
||||||
|
};
|
||||||
|
const net = await unifi.updateNetwork(String(args.id), fields);
|
||||||
|
return { updated: summarizeNetwork(net) };
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "unifi_list_devices",
|
name: "unifi_list_devices",
|
||||||
description: "List the network devices (APs, switches, gateways) the UniFi controller manages.",
|
description: "List the network devices (APs, switches, gateways) the UniFi controller manages.",
|
||||||
|
|||||||
Reference in New Issue
Block a user