Init commit
This commit is contained in:
commit
f3f831653f
104 files changed
+43827
No files matched your search
+256
@@ -0,0 +1,256 @@
|
||||
/**
|
||||
* RouterOS 7.x REST API client
|
||||
*
|
||||
* RouterOS 7.1+ exposes a REST API at https://<router>/rest/
|
||||
* All responses are JSON; authentication is HTTP Basic or Bearer token.
|
||||
*
|
||||
* Usage (browser / server-component):
|
||||
* const client = new RouterOsApiClient({ host: "10.0.0.1", port: 443, ... })
|
||||
* const ifaces = await client.getInterfaces()
|
||||
*/
|
||||
|
||||
export interface ApiClientConfig {
|
||||
host: string // "10.0.0.1"
|
||||
port: number // 443 (HTTPS) or 80 (HTTP)
|
||||
tls: boolean // use HTTPS
|
||||
username: string
|
||||
password: string
|
||||
tlsVerify: boolean // verify TLS certificate (false for self-signed)
|
||||
timeout: number // ms, default 8000
|
||||
}
|
||||
|
||||
// ─── RouterOS REST response shapes ───────────────────────────────────────────
|
||||
|
||||
export interface RosSystemIdentity {
|
||||
name: string
|
||||
}
|
||||
|
||||
export interface RosSystemResource {
|
||||
uptime: string
|
||||
version: string
|
||||
"build-time": string
|
||||
"free-memory": string
|
||||
"total-memory": string
|
||||
"cpu": string
|
||||
"cpu-count": string
|
||||
"cpu-load": string
|
||||
"board-name": string
|
||||
"platform": string
|
||||
"architecture-name": string
|
||||
"bad-blocks": string
|
||||
}
|
||||
|
||||
export interface RosInterface {
|
||||
".id": string
|
||||
name: string
|
||||
type: string
|
||||
mtu: string
|
||||
"actual-mtu": string
|
||||
"mac-address": string
|
||||
running: string
|
||||
disabled: string
|
||||
comment?: string
|
||||
}
|
||||
|
||||
export interface RosBgpSession {
|
||||
".id": string
|
||||
name: string
|
||||
"remote.address": string
|
||||
"remote.as": string
|
||||
state: string
|
||||
"uptime": string
|
||||
"prefix-count": string
|
||||
disabled: string
|
||||
}
|
||||
|
||||
export interface RosWireGuardInterface {
|
||||
".id": string
|
||||
name: string
|
||||
"listen-port": string
|
||||
mtu: string
|
||||
"public-key": string
|
||||
running: string
|
||||
disabled: string
|
||||
comment?: string
|
||||
}
|
||||
|
||||
export interface RosWireGuardPeer {
|
||||
".id": string
|
||||
interface: string
|
||||
"public-key": string
|
||||
"endpoint-address"?: string
|
||||
"endpoint-port"?: string
|
||||
"allowed-address": string
|
||||
"last-handshake"?: string
|
||||
"rx": string
|
||||
"tx": string
|
||||
disabled: string
|
||||
comment?: string
|
||||
}
|
||||
|
||||
// ─── Error class ─────────────────────────────────────────────────────────────
|
||||
|
||||
export class RouterOsApiError extends Error {
|
||||
constructor(
|
||||
public readonly status: number,
|
||||
public readonly detail: string,
|
||||
message: string,
|
||||
) {
|
||||
super(message)
|
||||
this.name = "RouterOsApiError"
|
||||
}
|
||||
}
|
||||
|
||||
// ─── Client class ─────────────────────────────────────────────────────────────
|
||||
|
||||
export class RouterOsApiClient {
|
||||
private readonly baseUrl: string
|
||||
private readonly headers: Record<string, string>
|
||||
private readonly timeout: number
|
||||
|
||||
constructor(private readonly cfg: ApiClientConfig) {
|
||||
const scheme = cfg.tls ? "https" : "http"
|
||||
this.baseUrl = `${scheme}://${cfg.host}:${cfg.port}/rest`
|
||||
const creds = btoa(`${cfg.username}:${cfg.password}`)
|
||||
this.headers = {
|
||||
"Authorization": `Basic ${creds}`,
|
||||
"Content-Type": "application/json",
|
||||
}
|
||||
this.timeout = cfg.timeout ?? 8000
|
||||
}
|
||||
|
||||
// ── low-level fetch ──────────────────────────────────────────────────────
|
||||
|
||||
private async request<T>(
|
||||
method: "GET" | "POST" | "PUT" | "PATCH" | "DELETE",
|
||||
path: string,
|
||||
body?: unknown,
|
||||
): Promise<T> {
|
||||
const controller = new AbortController()
|
||||
const timer = setTimeout(() => controller.abort(), this.timeout)
|
||||
|
||||
try {
|
||||
const res = await fetch(`${this.baseUrl}${path}`, {
|
||||
method,
|
||||
headers: this.headers,
|
||||
body: body !== undefined ? JSON.stringify(body) : undefined,
|
||||
signal: controller.signal,
|
||||
})
|
||||
clearTimeout(timer)
|
||||
|
||||
if (!res.ok) {
|
||||
let detail = ""
|
||||
try { detail = (await res.json())?.message ?? "" } catch { /* ignore */ }
|
||||
throw new RouterOsApiError(res.status, detail, `RouterOS API ${method} ${path} → ${res.status}: ${detail}`)
|
||||
}
|
||||
|
||||
if (res.status === 204) return undefined as T
|
||||
return res.json() as Promise<T>
|
||||
} catch (e) {
|
||||
clearTimeout(timer)
|
||||
throw e
|
||||
}
|
||||
}
|
||||
|
||||
async get<T>(path: string): Promise<T> {
|
||||
return this.request<T>("GET", path)
|
||||
}
|
||||
|
||||
async post<T>(path: string, body: unknown): Promise<T> {
|
||||
return this.request<T>("POST", path, body)
|
||||
}
|
||||
|
||||
/** RouterOS uses PUT for updates on a specific item by /.id */
|
||||
async put<T>(path: string, id: string, body: unknown): Promise<T> {
|
||||
return this.request<T>("PUT", `${path}/${encodeURIComponent(id)}`, body)
|
||||
}
|
||||
|
||||
async patch<T>(path: string, id: string, body: unknown): Promise<T> {
|
||||
return this.request<T>("PATCH", `${path}/${encodeURIComponent(id)}`, body)
|
||||
}
|
||||
|
||||
async delete(path: string, id: string): Promise<void> {
|
||||
return this.request<void>("DELETE", `${path}/${encodeURIComponent(id)}`)
|
||||
}
|
||||
|
||||
// ── high-level helpers ───────────────────────────────────────────────────
|
||||
|
||||
async getSystemIdentity(): Promise<RosSystemIdentity> {
|
||||
return this.get<RosSystemIdentity>("/system/identity")
|
||||
}
|
||||
|
||||
async getSystemResource(): Promise<RosSystemResource> {
|
||||
return this.get<RosSystemResource>("/system/resource")
|
||||
}
|
||||
|
||||
async getInterfaces(): Promise<RosInterface[]> {
|
||||
return this.get<RosInterface[]>("/interface")
|
||||
}
|
||||
|
||||
async getBgpSessions(): Promise<RosBgpSession[]> {
|
||||
return this.get<RosBgpSession[]>("/routing/bgp/session")
|
||||
}
|
||||
|
||||
async getWireGuardInterfaces(): Promise<RosWireGuardInterface[]> {
|
||||
return this.get<RosWireGuardInterface[]>("/interface/wireguard")
|
||||
}
|
||||
|
||||
async getWireGuardPeers(): Promise<RosWireGuardPeer[]> {
|
||||
return this.get<RosWireGuardPeer[]>("/interface/wireguard/peers")
|
||||
}
|
||||
|
||||
/** Add a WireGuard interface */
|
||||
async addWireGuardInterface(params: {
|
||||
name: string
|
||||
listenPort: number
|
||||
mtu?: number
|
||||
comment?: string
|
||||
}): Promise<RosWireGuardInterface> {
|
||||
return this.post<RosWireGuardInterface>("/interface/wireguard", {
|
||||
name: params.name,
|
||||
"listen-port": String(params.listenPort),
|
||||
mtu: String(params.mtu ?? 1420),
|
||||
comment: params.comment ?? "",
|
||||
})
|
||||
}
|
||||
|
||||
/** Add a WireGuard peer */
|
||||
async addWireGuardPeer(params: {
|
||||
iface: string
|
||||
publicKey: string
|
||||
allowedAddresses: string[]
|
||||
endpoint?: string
|
||||
endpointPort?: number
|
||||
persistentKeepalive?: number
|
||||
comment?: string
|
||||
}): Promise<RosWireGuardPeer> {
|
||||
return this.post<RosWireGuardPeer>("/interface/wireguard/peers", {
|
||||
interface: params.iface,
|
||||
"public-key": params.publicKey,
|
||||
"allowed-address": params.allowedAddresses.join(","),
|
||||
...(params.endpoint ? { "endpoint-address": params.endpoint } : {}),
|
||||
...(params.endpointPort ? { "endpoint-port": String(params.endpointPort) } : {}),
|
||||
...(params.persistentKeepalive ? { "persistent-keepalive": String(params.persistentKeepalive) } : {}),
|
||||
comment: params.comment ?? "",
|
||||
})
|
||||
}
|
||||
|
||||
/** Ping test from the router — uses /tool/ping */
|
||||
async ping(address: string, count = 4): Promise<{ "avg-rtt": string; "packet-loss": string }[]> {
|
||||
return this.post("/tool/ping", { address, count: String(count) })
|
||||
}
|
||||
}
|
||||
|
||||
// ─── Factory helper ───────────────────────────────────────────────────────────
|
||||
|
||||
export function createClient(overrides: Partial<ApiClientConfig> & { host: string }): RouterOsApiClient {
|
||||
return new RouterOsApiClient({
|
||||
port: 443,
|
||||
tls: true,
|
||||
username: "admin",
|
||||
password: "",
|
||||
tlsVerify: false,
|
||||
timeout: 8000,
|
||||
...overrides,
|
||||
})
|
||||
}
|
||||
Reference in new issue
Block a user