@coffeerunhobby/mcp-ha-connect 2.2.1 → 2.4.0

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.
Files changed (293) hide show
  1. package/.env.example +1 -1
  2. package/README.md +24 -2
  3. package/dist/omadaClient/accessControl.d.ts +290 -0
  4. package/dist/omadaClient/accessControl.js +509 -0
  5. package/dist/omadaClient/accessControl.js.map +1 -0
  6. package/dist/omadaClient/client.d.ts +6 -1
  7. package/dist/omadaClient/client.js +12 -1
  8. package/dist/omadaClient/client.js.map +1 -1
  9. package/dist/omadaClient/diagnose.d.ts +68 -0
  10. package/dist/omadaClient/diagnose.js +164 -0
  11. package/dist/omadaClient/diagnose.js.map +1 -0
  12. package/dist/omadaClient/index.d.ts +9 -0
  13. package/dist/omadaClient/index.js +19 -0
  14. package/dist/omadaClient/index.js.map +1 -1
  15. package/dist/omadaClient/macGroups.d.ts +58 -0
  16. package/dist/omadaClient/macGroups.js +110 -0
  17. package/dist/omadaClient/macGroups.js.map +1 -0
  18. package/dist/omadaClient/serialize.d.ts +13 -0
  19. package/dist/omadaClient/serialize.js +29 -0
  20. package/dist/omadaClient/serialize.js.map +1 -0
  21. package/dist/omadaClient/timeRange.d.ts +41 -0
  22. package/dist/omadaClient/timeRange.js +109 -0
  23. package/dist/omadaClient/timeRange.js.map +1 -0
  24. package/dist/tools/infra/actions.d.ts +1 -1
  25. package/dist/tools/infra/actions.js +1 -1
  26. package/dist/tools/omada/accessControl.d.ts +151 -0
  27. package/dist/tools/omada/accessControl.js +158 -0
  28. package/dist/tools/omada/accessControl.js.map +1 -0
  29. package/dist/tools/omada/index.js +9 -0
  30. package/dist/tools/omada/index.js.map +1 -1
  31. package/dist/tools/omada/macGroups.d.ts +19 -0
  32. package/dist/tools/omada/macGroups.js +77 -0
  33. package/dist/tools/omada/macGroups.js.map +1 -0
  34. package/dist/tools/omada/namespace.js +16 -0
  35. package/dist/tools/omada/namespace.js.map +1 -1
  36. package/dist/version.d.ts +1 -1
  37. package/dist/version.js +1 -1
  38. package/package.json +5 -1
  39. package/.codex/review-rules.md +0 -16
  40. package/.dockerignore +0 -8
  41. package/.gitattributes +0 -30
  42. package/.github/workflows/ci.yml +0 -193
  43. package/AGENTS.md +0 -389
  44. package/Dockerfile +0 -50
  45. package/docker-compose.yml +0 -23
  46. package/docs/CHANGELOG.md +0 -341
  47. package/docs/COVERAGE.md +0 -219
  48. package/docs/QUICK_START.md +0 -240
  49. package/eslint.config.js +0 -33
  50. package/n8n/ai-agent-safety-monitor.json +0 -355
  51. package/openapi.json +0 -1259
  52. package/scripts/check-version-sync.mjs +0 -69
  53. package/scripts/generate-jwt.ts +0 -121
  54. package/src/config.ts +0 -440
  55. package/src/haClient/automations.ts +0 -261
  56. package/src/haClient/calendars.ts +0 -94
  57. package/src/haClient/config.ts +0 -52
  58. package/src/haClient/devices.ts +0 -653
  59. package/src/haClient/entities.ts +0 -128
  60. package/src/haClient/events.ts +0 -689
  61. package/src/haClient/history.ts +0 -73
  62. package/src/haClient/index.ts +0 -336
  63. package/src/haClient/request.ts +0 -142
  64. package/src/haClient/services.ts +0 -45
  65. package/src/haClient/states.ts +0 -60
  66. package/src/haClient/updates.ts +0 -58
  67. package/src/haClient/wsCommand.ts +0 -44
  68. package/src/index.ts +0 -164
  69. package/src/localAI/index.ts +0 -88
  70. package/src/localAI/prompts.ts +0 -50
  71. package/src/localAI/providers/base.ts +0 -120
  72. package/src/localAI/providers/ollama.ts +0 -78
  73. package/src/localAI/providers/openai.ts +0 -86
  74. package/src/localAI/types.ts +0 -67
  75. package/src/omadaClient/auth.ts +0 -178
  76. package/src/omadaClient/client.ts +0 -337
  77. package/src/omadaClient/device.ts +0 -118
  78. package/src/omadaClient/index.ts +0 -310
  79. package/src/omadaClient/network.ts +0 -284
  80. package/src/omadaClient/request.ts +0 -365
  81. package/src/omadaClient/security.ts +0 -51
  82. package/src/omadaClient/site.ts +0 -344
  83. package/src/ownerQuestions/service.ts +0 -536
  84. package/src/permissions/index.ts +0 -143
  85. package/src/resources/index.ts +0 -213
  86. package/src/server/auth.ts +0 -175
  87. package/src/server/chatFace.ts +0 -789
  88. package/src/server/chatSlice.ts +0 -57
  89. package/src/server/common.ts +0 -170
  90. package/src/server/eventSubscription.ts +0 -343
  91. package/src/server/http.ts +0 -626
  92. package/src/server/instructions.ts +0 -97
  93. package/src/server/rateLimiter.ts +0 -227
  94. package/src/server/stdio.ts +0 -41
  95. package/src/server/stream.ts +0 -205
  96. package/src/tools/ai/analyzeSensors.ts +0 -33
  97. package/src/tools/ai/index.ts +0 -20
  98. package/src/tools/common.ts +0 -442
  99. package/src/tools/homeassistant/askOwner.ts +0 -133
  100. package/src/tools/homeassistant/automationControls.ts +0 -66
  101. package/src/tools/homeassistant/calendar.ts +0 -97
  102. package/src/tools/homeassistant/callService.ts +0 -25
  103. package/src/tools/homeassistant/controlClimate.ts +0 -68
  104. package/src/tools/homeassistant/controlCover.ts +0 -48
  105. package/src/tools/homeassistant/controlFan.ts +0 -44
  106. package/src/tools/homeassistant/controlLight.ts +0 -38
  107. package/src/tools/homeassistant/controlMediaPlayer.ts +0 -50
  108. package/src/tools/homeassistant/createAutomation.ts +0 -35
  109. package/src/tools/homeassistant/deleteAutomation.ts +0 -24
  110. package/src/tools/homeassistant/entityAction.ts +0 -35
  111. package/src/tools/homeassistant/entityGuard.ts +0 -28
  112. package/src/tools/homeassistant/getAllSensors.ts +0 -46
  113. package/src/tools/homeassistant/getAutomationTrace.ts +0 -30
  114. package/src/tools/homeassistant/getDomainSummary.ts +0 -29
  115. package/src/tools/homeassistant/getEntitiesByDomain.ts +0 -56
  116. package/src/tools/homeassistant/getHistory.ts +0 -24
  117. package/src/tools/homeassistant/getState.ts +0 -24
  118. package/src/tools/homeassistant/getStates.ts +0 -46
  119. package/src/tools/homeassistant/getVersion.ts +0 -21
  120. package/src/tools/homeassistant/index.ts +0 -121
  121. package/src/tools/homeassistant/listAutomations.ts +0 -35
  122. package/src/tools/homeassistant/listEntities.ts +0 -44
  123. package/src/tools/homeassistant/listPersons.ts +0 -46
  124. package/src/tools/homeassistant/sceneAndScript.ts +0 -53
  125. package/src/tools/homeassistant/searchEntities.ts +0 -58
  126. package/src/tools/homeassistant/sendNotification.ts +0 -179
  127. package/src/tools/homeassistant/systemTools.ts +0 -52
  128. package/src/tools/homeassistant/triggerAutomation.ts +0 -24
  129. package/src/tools/index.ts +0 -78
  130. package/src/tools/infra/actions.ts +0 -95
  131. package/src/tools/infra/index.ts +0 -138
  132. package/src/tools/omada/blockClient.ts +0 -28
  133. package/src/tools/omada/cyclePoePort.ts +0 -32
  134. package/src/tools/omada/deleteClient.ts +0 -27
  135. package/src/tools/omada/disableClientRateLimit.ts +0 -23
  136. package/src/tools/omada/getClient.ts +0 -14
  137. package/src/tools/omada/getDevice.ts +0 -14
  138. package/src/tools/omada/getFirewallSetting.ts +0 -14
  139. package/src/tools/omada/getInternetInfo.ts +0 -14
  140. package/src/tools/omada/getLanNetworkList.ts +0 -14
  141. package/src/tools/omada/getLanProfileList.ts +0 -14
  142. package/src/tools/omada/getPortForwardingStatus.ts +0 -26
  143. package/src/tools/omada/getRateLimitProfiles.ts +0 -14
  144. package/src/tools/omada/getSiteNtpStatus.ts +0 -14
  145. package/src/tools/omada/getSsidDetail.ts +0 -22
  146. package/src/tools/omada/getSsidList.ts +0 -21
  147. package/src/tools/omada/getSwitchStackDetail.ts +0 -14
  148. package/src/tools/omada/getThreatList.ts +0 -42
  149. package/src/tools/omada/getWlanGroupList.ts +0 -14
  150. package/src/tools/omada/graph.ts +0 -187
  151. package/src/tools/omada/index.ts +0 -153
  152. package/src/tools/omada/listBlockedClients.ts +0 -17
  153. package/src/tools/omada/listClients.ts +0 -14
  154. package/src/tools/omada/listClientsActivity.ts +0 -25
  155. package/src/tools/omada/listClientsPastConnections.ts +0 -44
  156. package/src/tools/omada/listDevices.ts +0 -14
  157. package/src/tools/omada/listDevicesStats.ts +0 -41
  158. package/src/tools/omada/listMostActiveClients.ts +0 -14
  159. package/src/tools/omada/listSites.ts +0 -18
  160. package/src/tools/omada/namespace.ts +0 -1164
  161. package/src/tools/omada/searchDevices.ts +0 -20
  162. package/src/tools/omada/setClientRateLimit.ts +0 -26
  163. package/src/tools/omada/setClientRateLimitProfile.ts +0 -25
  164. package/src/tools/omada/setSiteNtpServers.ts +0 -36
  165. package/src/tools/omada/setSsidEnabled.ts +0 -38
  166. package/src/tools/omada/unblockClient.ts +0 -26
  167. package/src/tools/types.ts +0 -11
  168. package/src/tools/utils/pagination-schema.ts +0 -27
  169. package/src/types/ai/index.ts +0 -11
  170. package/src/types/ai/types.ts +0 -67
  171. package/src/types/homeassistant/automations.ts +0 -43
  172. package/src/types/homeassistant/calendars.ts +0 -26
  173. package/src/types/homeassistant/config.ts +0 -14
  174. package/src/types/homeassistant/entities.ts +0 -14
  175. package/src/types/homeassistant/errors.ts +0 -33
  176. package/src/types/homeassistant/haConfig.ts +0 -7
  177. package/src/types/homeassistant/history.ts +0 -13
  178. package/src/types/homeassistant/index.ts +0 -46
  179. package/src/types/homeassistant/request.ts +0 -6
  180. package/src/types/homeassistant/serviceCall.ts +0 -18
  181. package/src/types/homeassistant/states.ts +0 -58
  182. package/src/types/homeassistant/updates.ts +0 -28
  183. package/src/types/index.ts +0 -12
  184. package/src/types/omada/activeClientInfo.ts +0 -17
  185. package/src/types/omada/clientActivity.ts +0 -32
  186. package/src/types/omada/clientBlockStatus.ts +0 -11
  187. package/src/types/omada/clientPastConnection.ts +0 -82
  188. package/src/types/omada/index.ts +0 -14
  189. package/src/types/omada/omadaApiResponse.ts +0 -5
  190. package/src/types/omada/omadaClientInfo.ts +0 -11
  191. package/src/types/omada/omadaDeviceInfo.ts +0 -11
  192. package/src/types/omada/omadaDeviceStats.ts +0 -31
  193. package/src/types/omada/omadaSiteSummary.ts +0 -5
  194. package/src/types/omada/oswStackDetail.ts +0 -11
  195. package/src/types/omada/paginatedResult.ts +0 -6
  196. package/src/types/omada/rateLimitProfile.ts +0 -58
  197. package/src/types/omada/threatInfo.ts +0 -38
  198. package/src/types/omada/tokenResult.ts +0 -6
  199. package/src/types.ts +0 -2
  200. package/src/utils/config-validations.ts +0 -68
  201. package/src/utils/jwt.ts +0 -112
  202. package/src/utils/logger.ts +0 -121
  203. package/src/utils/plainText.ts +0 -48
  204. package/src/utils/redact.ts +0 -59
  205. package/src/utils/sanitizeError.ts +0 -18
  206. package/src/utils/tlsDispatcher.ts +0 -69
  207. package/src/version.ts +0 -5
  208. package/tests/bdd/automation-creation.spec.ts +0 -214
  209. package/tests/bdd/automation-management.spec.ts +0 -267
  210. package/tests/bdd/calendar.spec.ts +0 -190
  211. package/tests/bdd/configuration.spec.ts +0 -283
  212. package/tests/bdd/device-control.spec.ts +0 -344
  213. package/tests/bdd/entity-listing.spec.ts +0 -208
  214. package/tests/bdd/entity-search.spec.ts +0 -183
  215. package/tests/bdd/history-retrieval.spec.ts +0 -195
  216. package/tests/bdd/notifications.spec.ts +0 -238
  217. package/tests/bdd/service-calls.spec.ts +0 -144
  218. package/tests/bdd/state-retrieval.spec.ts +0 -204
  219. package/tests/bench/hotpaths.bench.ts +0 -68
  220. package/tests/config.test.ts +0 -355
  221. package/tests/features/automation-creation.feature +0 -36
  222. package/tests/features/automation-management.feature +0 -54
  223. package/tests/features/calendar.feature +0 -37
  224. package/tests/features/configuration.feature +0 -70
  225. package/tests/features/device-control.feature +0 -100
  226. package/tests/features/entity-listing.feature +0 -51
  227. package/tests/features/entity-search.feature +0 -44
  228. package/tests/features/history-retrieval.feature +0 -38
  229. package/tests/features/notifications.feature +0 -37
  230. package/tests/features/service-calls.feature +0 -35
  231. package/tests/features/state-retrieval.feature +0 -37
  232. package/tests/fixtures/openapi-default-slice.json +0 -290
  233. package/tests/haClient/automationTrace.test.ts +0 -59
  234. package/tests/haClient/automations.test.ts +0 -149
  235. package/tests/haClient/config.test.ts +0 -117
  236. package/tests/haClient/entities.test.ts +0 -188
  237. package/tests/haClient/events.test.ts +0 -455
  238. package/tests/haClient/eventsTls.test.ts +0 -52
  239. package/tests/haClient/history.test.ts +0 -228
  240. package/tests/haClient/index.test.ts +0 -243
  241. package/tests/haClient/request.test.ts +0 -217
  242. package/tests/haClient/services.test.ts +0 -120
  243. package/tests/haClient/states.test.ts +0 -119
  244. package/tests/haClient/updates.test.ts +0 -193
  245. package/tests/index.test.ts +0 -160
  246. package/tests/integration/homeassistant.test.ts +0 -144
  247. package/tests/integration/omada.test.ts +0 -167
  248. package/tests/integration/server.test.ts +0 -151
  249. package/tests/localAI/index.test.ts +0 -177
  250. package/tests/localAI/ollama.test.ts +0 -187
  251. package/tests/localAI/openai.test.ts +0 -257
  252. package/tests/omadaClient/actions.test.ts +0 -161
  253. package/tests/omadaClient/clientBlocks.test.ts +0 -170
  254. package/tests/omadaClient/ntp.test.ts +0 -381
  255. package/tests/omadaClient/site.test.ts +0 -37
  256. package/tests/ownerQuestions/service.test.ts +0 -499
  257. package/tests/ownerQuestions/tools.test.ts +0 -186
  258. package/tests/permissions/index.test.ts +0 -217
  259. package/tests/resources/index.test.ts +0 -288
  260. package/tests/scripts/generateJwt.test.ts +0 -44
  261. package/tests/security/accessControl.test.ts +0 -223
  262. package/tests/security/authentication.test.ts +0 -117
  263. package/tests/security/configuration.test.ts +0 -170
  264. package/tests/security/cryptoTransport.test.ts +0 -172
  265. package/tests/security/dependencies.test.ts +0 -152
  266. package/tests/security/injection.test.ts +0 -117
  267. package/tests/security/insecureDesign.test.ts +0 -121
  268. package/tests/security/logging.test.ts +0 -131
  269. package/tests/security/resourceConsumption.test.ts +0 -142
  270. package/tests/server/auth.test.ts +0 -249
  271. package/tests/server/chatFace.test.ts +0 -320
  272. package/tests/server/common.test.ts +0 -84
  273. package/tests/server/http.test.ts +0 -561
  274. package/tests/server/instructions.test.ts +0 -152
  275. package/tests/server/memoizeJsonSchema.test.ts +0 -71
  276. package/tests/server/stdio.test.ts +0 -99
  277. package/tests/server/stream.test.ts +0 -285
  278. package/tests/server/toolVisibility.http.test.ts +0 -111
  279. package/tests/server/toolVisibility.test.ts +0 -129
  280. package/tests/tools/ai/analyzeSensors.test.ts +0 -227
  281. package/tests/tools/common.test.ts +0 -123
  282. package/tests/tools/homeassistant/handlers.test.ts +0 -872
  283. package/tests/tools/infra/invokeAction.test.ts +0 -280
  284. package/tests/tools/omada/graph.test.ts +0 -385
  285. package/tests/tools/omada/handlers.test.ts +0 -897
  286. package/tests/tools/omada/namespace.test.ts +0 -483
  287. package/tests/tools/registry.test.ts +0 -168
  288. package/tests/utils/config-validations.test.ts +0 -352
  289. package/tests/utils/jwt.test.ts +0 -115
  290. package/tests/utils/logger.test.ts +0 -327
  291. package/tests/utils/redact.test.ts +0 -105
  292. package/tsconfig.json +0 -24
  293. package/vitest.config.ts +0 -38
@@ -1,1164 +0,0 @@
1
- /**
2
- * Omada resource-graph namespace.
3
- *
4
- * Instead of registering one MCP tool per Omada endpoint (which explodes the
5
- * tool-schema budget for small models), graph mode exposes a single discoverable
6
- * namespace via `omada_browse` (navigate) + `omada_read` (fetch). This manifest is
7
- * the source of truth mapping graph paths → fetch logic + per-path RBAC.
8
- *
9
- * Design (see the "resource graph with lazy expansion" discussion):
10
- * - `browse` returns resource *types/metadata*, never instances — no enumeration,
11
- * no controller calls, zero payload explosion.
12
- * - `read` is where data is fetched: single leaves, collections (optionally a
13
- * single member via `id`), or paginated logs (single page via page/pageSize).
14
- * - Authorization is per-path: each node declares the Permission bit it needs.
15
- * Most nodes are QUERY; the security subtree (`/security/threats`) requires
16
- * ADMIN, demonstrating that sensitive subtrees can require a higher bit without
17
- * touching the tools.
18
- *
19
- * `graph` mode is a complete superset of `eager` reads: every eager read getter
20
- * has a corresponding graph node here, including the ADMIN-gated IDS/IPS
21
- * threat-management log (`/security/threats`), which maps its mandatory epoch
22
- * time-window onto the required `params.startTime`/`params.endTime` validated by
23
- * `omada_read` before any controller call.
24
- */
25
-
26
- import type { OmadaClient } from '../../omadaClient/index.js';
27
- import { Permission } from '../../permissions/index.js';
28
-
29
- /** Arguments passed from `omada_read` to a node's fetch function. */
30
- export interface ReadArgs {
31
- siteId?: string;
32
- /** Look up a single member of a collection by id/MAC. */
33
- id?: string;
34
- /** Path parameters some resources require (e.g. gatewayMac, apMac, deviceMac). */
35
- params?: Record<string, string>;
36
- page?: number;
37
- pageSize?: number;
38
- }
39
-
40
- /** A declared parameter for a resource (surfaced by browse, validated by read). */
41
- export interface ResourceParam {
42
- name: string;
43
- required: boolean;
44
- description: string;
45
- }
46
-
47
- export type ResourceKind = 'container' | 'collection' | 'leaf';
48
-
49
- export interface ResourceNode {
50
- /** Graph path, e.g. '/gateway/wan'. Always normalized (leading slash, no trailing). */
51
- path: string;
52
- kind: ResourceKind;
53
- /** Permission bit required to browse-to / read this node. */
54
- permission: number;
55
- description: string;
56
- estimatedSize?: 'small' | 'medium' | 'large';
57
- paginated?: boolean;
58
- defaultPageSize?: number;
59
- params?: ResourceParam[];
60
- /** Undefined for pure containers (browse-only — no data of their own). */
61
- fetch?: (client: OmadaClient, args: ReadArgs) => Promise<unknown>;
62
- }
63
-
64
- const Q = Permission.QUERY;
65
- const A = Permission.ADMIN;
66
-
67
- const HOUR_MS = 60 * 60 * 1000;
68
-
69
- /**
70
- * Resolve an epoch-MILLISECONDS time window for the event/alert log endpoints
71
- * (`/sites/{siteId}/logs/{events,alerts}`), which require `filters.timeStart` and
72
- * `filters.timeEnd`. Explicit `params.startTime`/`params.endTime` win; otherwise
73
- * default to the last 7 days so a bare `omada_read('/events')` "just works".
74
- * Exported for unit testing (inject `now` to keep tests deterministic).
75
- */
76
- export function resolveLogWindowMs(
77
- params: Record<string, string> | undefined,
78
- now = Date.now()
79
- ): { timeStart: number; timeEnd: number } {
80
- const timeEnd = params?.endTime ? Number(params.endTime) : now;
81
- const timeStart = params?.startTime ? Number(params.startTime) : timeEnd - 7 * 24 * HOUR_MS;
82
- return { timeStart, timeEnd };
83
- }
84
-
85
- /**
86
- * Resolve an epoch-SECONDS time window for the dashboard top-usage endpoints
87
- * (`top-device-cpu-usage` / `top-device-memory-usage`), which require `start` and
88
- * `end`. Explicit `params.startTime`/`params.endTime` (seconds) win; otherwise
89
- * default to the last 24 hours. Exported for unit testing.
90
- */
91
- export function resolveUsageWindowSec(
92
- params: Record<string, string> | undefined,
93
- now = Date.now()
94
- ): { start: number; end: number } {
95
- const end = params?.endTime ? Number(params.endTime) : Math.floor(now / 1000);
96
- const start = params?.startTime ? Number(params.startTime) : end - 24 * 60 * 60;
97
- return { start, end };
98
- }
99
-
100
- /**
101
- * Build the optional query filters for the audit-log endpoints from caller params.
102
- * `startTime`/`endTime` are epoch MILLISECONDS, forwarded as the controller's
103
- * `filters.startTime`/`filters.endTime`; `searchKey` is fuzzy text. Absent params
104
- * are omitted so a bare read returns the most recent page unfiltered. Exported for
105
- * unit testing.
106
- */
107
- export function auditFilters(params: Record<string, string> | undefined): Record<string, unknown> {
108
- const query: Record<string, unknown> = {};
109
- if (params?.startTime !== undefined) {
110
- query['filters.startTime'] = Number(params.startTime);
111
- }
112
- if (params?.endTime !== undefined) {
113
- query['filters.endTime'] = Number(params.endTime);
114
- }
115
- if (params?.searchKey) {
116
- query.searchKey = params.searchKey;
117
- }
118
- return query;
119
- }
120
-
121
- /**
122
- * The Omada resource graph. Containers have no `fetch` (browse-only); collections
123
- * and leaves fetch data. Existing client methods back the well-trodden reads;
124
- * the generic `client.readResource(...)` backs newly-added endpoints with zero
125
- * new client code.
126
- */
127
- export const OMADA_RESOURCES: ResourceNode[] = [
128
- {
129
- path: '/',
130
- kind: 'container',
131
- permission: Q,
132
- description: 'Root of the Omada network resource graph. Browse children to discover resources.',
133
- },
134
-
135
- // ---- Sites -------------------------------------------------------------
136
- {
137
- path: '/sites',
138
- kind: 'collection',
139
- permission: Q,
140
- estimatedSize: 'small',
141
- description: 'All sites on the controller. Most resources are scoped to a site via the optional siteId argument.',
142
- fetch: (c) => c.listSites(),
143
- },
144
-
145
- // ---- Devices (infrastructure) -----------------------------------------
146
- {
147
- path: '/devices',
148
- kind: 'collection',
149
- permission: Q,
150
- estimatedSize: 'medium',
151
- description: 'Network infrastructure (APs, switches, gateways). Pass id=<MAC> to look up a single device.',
152
- params: [{ name: 'id', required: false, description: 'Device MAC to fetch a single device' }],
153
- fetch: (c, a) => (a.id ? c.getDevice(a.id, a.siteId) : c.listDevices(a.siteId)),
154
- },
155
- {
156
- path: '/devices/pending',
157
- kind: 'collection',
158
- permission: Q,
159
- paginated: true,
160
- defaultPageSize: 50,
161
- estimatedSize: 'small',
162
- description: 'Devices discovered but not yet adopted into the site (paginated). Use page/pageSize.',
163
- fetch: (c, a) =>
164
- c.readResource({
165
- pathTemplate: '/sites/{siteId}/grid/devices/pending',
166
- siteId: a.siteId,
167
- paginated: true,
168
- page: a.page,
169
- pageSize: a.pageSize,
170
- }),
171
- },
172
- {
173
- path: '/devices/stats',
174
- kind: 'collection',
175
- permission: Q,
176
- paginated: true,
177
- defaultPageSize: 50,
178
- estimatedSize: 'medium',
179
- description: 'Per-device statistics (CPU, memory, traffic). Paginated — use page/pageSize.',
180
- fetch: (c, a) => c.listDevicesStats({ page: a.page ?? 1, pageSize: a.pageSize ?? 50 }),
181
- },
182
- {
183
- path: '/devices/stack',
184
- kind: 'leaf',
185
- permission: Q,
186
- estimatedSize: 'small',
187
- description: 'Switch-stack detail. Requires stackId.',
188
- params: [{ name: 'stackId', required: true, description: 'Switch stack identifier' }],
189
- fetch: (c, a) => c.getSwitchStackDetail(a.params?.stackId ?? '', a.siteId),
190
- },
191
- {
192
- path: '/devices/search',
193
- kind: 'collection',
194
- permission: Q,
195
- estimatedSize: 'small',
196
- description: 'Search infrastructure devices across all sites by name/MAC/model. Requires searchKey.',
197
- params: [{ name: 'searchKey', required: true, description: 'Text to match against device name, MAC, or model' }],
198
- fetch: (c, a) => c.searchDevices(a.params?.searchKey ?? ''),
199
- },
200
-
201
- // ---- Cable test (switch port diagnostics) -----------------------------
202
- {
203
- path: '/devices/cable-test',
204
- kind: 'container',
205
- permission: Q,
206
- description: 'Switch-port cable diagnostics. Children require a switchMac (find via /devices).',
207
- },
208
- {
209
- path: '/devices/cable-test/ports',
210
- kind: 'collection',
211
- permission: Q,
212
- estimatedSize: 'small',
213
- description: 'Ports available for cable testing on a switch. Requires switchMac.',
214
- params: [{ name: 'switchMac', required: true, description: 'Switch MAC address' }],
215
- fetch: (c, a) =>
216
- c.readResource({
217
- pathTemplate: '/sites/{siteId}/cable-test/switches/{switchMac}/ports',
218
- siteId: a.siteId,
219
- pathParams: { switchMac: a.params?.switchMac ?? '' },
220
- }),
221
- },
222
- {
223
- path: '/devices/cable-test/results',
224
- kind: 'leaf',
225
- permission: Q,
226
- estimatedSize: 'small',
227
- description: 'Full cable-test results (pair status, length, fault distance) for a switch. Requires switchMac.',
228
- params: [{ name: 'switchMac', required: true, description: 'Switch MAC address' }],
229
- fetch: (c, a) =>
230
- c.readResource({
231
- pathTemplate: '/sites/{siteId}/cable-test/switches/{switchMac}/full-results',
232
- siteId: a.siteId,
233
- pathParams: { switchMac: a.params?.switchMac ?? '' },
234
- }),
235
- },
236
- {
237
- path: '/devices/cable-test/logs',
238
- kind: 'collection',
239
- permission: Q,
240
- estimatedSize: 'small',
241
- description: 'Cable-test run history for a switch. Requires switchMac.',
242
- params: [{ name: 'switchMac', required: true, description: 'Switch MAC address' }],
243
- fetch: (c, a) =>
244
- c.readResource({
245
- pathTemplate: '/sites/{siteId}/cable-test/switches/{switchMac}/logs',
246
- siteId: a.siteId,
247
- pathParams: { switchMac: a.params?.switchMac ?? '' },
248
- }),
249
- },
250
- {
251
- path: '/devices/poe',
252
- kind: 'collection',
253
- permission: Q,
254
- paginated: true,
255
- defaultPageSize: 50,
256
- estimatedSize: 'small',
257
- description: 'Per-port PoE status across switches (which ports deliver power, to what, and how much). Paginated.',
258
- fetch: (c, a) =>
259
- c.readResource({
260
- pathTemplate: '/sites/{siteId}/switches/ports/poe-info',
261
- siteId: a.siteId,
262
- paginated: true,
263
- page: a.page,
264
- pageSize: a.pageSize,
265
- }),
266
- },
267
- {
268
- path: '/devices/lldp',
269
- kind: 'collection',
270
- permission: Q,
271
- estimatedSize: 'small',
272
- description: 'Site-wide LLDP neighbor information (physical link/topology discovery between devices).',
273
- fetch: (c, a) => c.readResource({ pathTemplate: '/sites/{siteId}/lldp', siteId: a.siteId }),
274
- },
275
-
276
- // ---- Clients (connected users) ----------------------------------------
277
- {
278
- path: '/clients',
279
- kind: 'collection',
280
- permission: Q,
281
- estimatedSize: 'large',
282
- description: 'Connected clients (phones, laptops, IoT). Pass id=<MAC> to look up a single client.',
283
- params: [{ name: 'id', required: false, description: 'Client MAC to fetch a single client' }],
284
- fetch: (c, a) => (a.id ? c.getClient(a.id, a.siteId) : c.listClients(a.siteId)),
285
- },
286
- {
287
- path: '/clients/active',
288
- kind: 'collection',
289
- permission: Q,
290
- estimatedSize: 'small',
291
- description: 'Most active clients by traffic.',
292
- fetch: (c, a) => c.listMostActiveClients(a.siteId),
293
- },
294
- {
295
- path: '/clients/known',
296
- kind: 'collection',
297
- permission: Q,
298
- paginated: true,
299
- defaultPageSize: 50,
300
- estimatedSize: 'medium',
301
- description:
302
- 'Clients the controller remembers, online and offline, with their block state (paginated; use page/pageSize). ' +
303
- 'Blocked clients have block=true; the controller cannot filter by it, so check every page.',
304
- fetch: (c, a) =>
305
- c.readResource({
306
- pathTemplate: '/sites/{siteId}/insight/clients',
307
- siteId: a.siteId,
308
- paginated: true,
309
- page: a.page,
310
- pageSize: a.pageSize,
311
- }),
312
- },
313
- {
314
- path: '/clients/activity',
315
- kind: 'collection',
316
- permission: Q,
317
- estimatedSize: 'medium',
318
- description: 'Per-client activity (traffic over time). Optional time window via params.start/params.end (epoch seconds).',
319
- params: [
320
- { name: 'start', required: false, description: 'Start timestamp, epoch seconds' },
321
- { name: 'end', required: false, description: 'End timestamp, epoch seconds' },
322
- ],
323
- fetch: (c, a) =>
324
- c.listClientsActivity({
325
- siteId: a.siteId,
326
- start: a.params?.start !== undefined ? Number(a.params.start) : undefined,
327
- end: a.params?.end !== undefined ? Number(a.params.end) : undefined,
328
- }),
329
- },
330
- {
331
- path: '/clients/past-connections',
332
- kind: 'collection',
333
- permission: Q,
334
- paginated: true,
335
- defaultPageSize: 50,
336
- estimatedSize: 'large',
337
- description: 'Historical client connections (paginated). Use page/pageSize.',
338
- fetch: (c, a) => c.listClientsPastConnections({ siteId: a.siteId, page: a.page ?? 1, pageSize: a.pageSize ?? 50 }),
339
- },
340
-
341
- // ---- Gateway -----------------------------------------------------------
342
- { path: '/gateway', kind: 'container', permission: Q, description: 'Gateway/router status and connectivity.' },
343
- {
344
- path: '/gateway/wan',
345
- kind: 'leaf',
346
- permission: Q,
347
- estimatedSize: 'small',
348
- description: 'WAN port status for a gateway: WAN IP, DNS, uptime, link speed, TX/RX. Requires gatewayMac (find via /devices).',
349
- params: [{ name: 'gatewayMac', required: true, description: 'Gateway MAC address' }],
350
- fetch: (c, a) =>
351
- c.readResource({
352
- pathTemplate: '/sites/{siteId}/gateways/{gatewayMac}/wan-status',
353
- siteId: a.siteId,
354
- pathParams: { gatewayMac: a.params?.gatewayMac ?? '' },
355
- }),
356
- },
357
- {
358
- path: '/gateway/health',
359
- kind: 'leaf',
360
- permission: Q,
361
- estimatedSize: 'small',
362
- description: 'Detailed per-WAN health for a gateway. Requires gatewayMac (find via /devices).',
363
- params: [{ name: 'gatewayMac', required: true, description: 'Gateway MAC address' }],
364
- fetch: (c, a) =>
365
- c.readResource({
366
- pathTemplate: '/sites/{siteId}/health/gateways/{gatewayMac}/wans/details',
367
- siteId: a.siteId,
368
- pathParams: { gatewayMac: a.params?.gatewayMac ?? '' },
369
- }),
370
- },
371
-
372
- // ---- Network config ----------------------------------------------------
373
- { path: '/network', kind: 'container', permission: Q, description: 'Network configuration: internet, LANs, WLAN groups, firewall.' },
374
- {
375
- path: '/network/internet',
376
- kind: 'leaf',
377
- permission: Q,
378
- estimatedSize: 'small',
379
- description: 'Internet/WAN connection information for the site.',
380
- fetch: (c, a) => c.getInternetInfo(a.siteId),
381
- },
382
- {
383
- path: '/network/ntp',
384
- kind: 'leaf',
385
- permission: Q,
386
- estimatedSize: 'small',
387
- description: 'NTP (network time) server configuration and status for the site.',
388
- fetch: (c, a) => c.getSiteNtpStatus(a.siteId),
389
- },
390
- {
391
- path: '/network/lan-networks',
392
- kind: 'collection',
393
- permission: Q,
394
- estimatedSize: 'small',
395
- description: 'Configured LAN networks / VLANs.',
396
- fetch: (c, a) => c.getLanNetworkList(a.siteId),
397
- },
398
- {
399
- path: '/network/lan-profiles',
400
- kind: 'collection',
401
- permission: Q,
402
- estimatedSize: 'small',
403
- description: 'LAN profiles.',
404
- fetch: (c, a) => c.getLanProfileList(a.siteId),
405
- },
406
- {
407
- path: '/network/wlan-groups',
408
- kind: 'collection',
409
- permission: Q,
410
- estimatedSize: 'small',
411
- description: 'WLAN groups.',
412
- fetch: (c, a) => c.getWlanGroupList(a.siteId),
413
- },
414
- {
415
- path: '/network/firewall',
416
- kind: 'leaf',
417
- permission: Q,
418
- estimatedSize: 'small',
419
- description: 'Gateway firewall settings for the site.',
420
- fetch: (c, a) => c.getFirewallSetting(a.siteId),
421
- },
422
- {
423
- path: '/network/port-forwarding',
424
- kind: 'collection',
425
- permission: Q,
426
- paginated: true,
427
- defaultPageSize: 10,
428
- estimatedSize: 'small',
429
- description: "Port-forwarding status (paginated). params.type is 'User' (default) or 'UPnP'.",
430
- params: [{ name: 'type', required: false, description: "'User' (default) or 'UPnP'" }],
431
- fetch: (c, a) =>
432
- c.getPortForwardingStatus(a.params?.type === 'UPnP' ? 'UPnP' : 'User', a.siteId, a.page ?? 1, a.pageSize ?? 10),
433
- },
434
- {
435
- path: '/network/port-forwarding/rules',
436
- kind: 'collection',
437
- permission: Q,
438
- paginated: true,
439
- defaultPageSize: 50,
440
- estimatedSize: 'small',
441
- description: 'Configured NAT port-forwarding rules (the full rule list, not just status). Paginated — use page/pageSize.',
442
- fetch: (c, a) =>
443
- c.readResource({
444
- pathTemplate: '/sites/{siteId}/nat/port-forwardings',
445
- siteId: a.siteId,
446
- paginated: true,
447
- page: a.page,
448
- pageSize: a.pageSize,
449
- }),
450
- },
451
- {
452
- path: '/network/dhcp-leases',
453
- kind: 'collection',
454
- permission: Q,
455
- paginated: true,
456
- defaultPageSize: 50,
457
- estimatedSize: 'medium',
458
- description: 'Active DHCP leases across all DHCP servers on the site (paginated). Use page/pageSize. See /network/dhcp-reservations for static reservations.',
459
- fetch: (c, a) =>
460
- c.readResource({
461
- pathTemplate: '/sites/{siteId}/setting/service/dhcp/user-list',
462
- siteId: a.siteId,
463
- paginated: true,
464
- page: a.page,
465
- pageSize: a.pageSize,
466
- }),
467
- },
468
- {
469
- path: '/network/load-balance',
470
- kind: 'leaf',
471
- permission: Q,
472
- estimatedSize: 'small',
473
- description: 'Multi-WAN load-balance status for the gateway (which WANs are up / in the balancing pool).',
474
- fetch: (c, a) => c.readResource({ pathTemplate: '/sites/{siteId}/internet/load-balance/status', siteId: a.siteId }),
475
- },
476
- {
477
- path: '/network/dhcp-reservations',
478
- kind: 'collection',
479
- permission: Q,
480
- paginated: true,
481
- defaultPageSize: 50,
482
- estimatedSize: 'small',
483
- description: 'Static DHCP reservations (each maps a client MAC to a fixed IP). Paginated. See /network/dhcp-leases for active leases.',
484
- fetch: (c, a) =>
485
- c.readResource({
486
- pathTemplate: '/sites/{siteId}/setting/service/dhcp',
487
- siteId: a.siteId,
488
- paginated: true,
489
- page: a.page,
490
- pageSize: a.pageSize,
491
- }),
492
- },
493
- {
494
- path: '/network/static-routes',
495
- kind: 'collection',
496
- permission: Q,
497
- paginated: true,
498
- defaultPageSize: 50,
499
- estimatedSize: 'small',
500
- description: 'Configured static routes for the gateway (destination → next-hop). Paginated.',
501
- fetch: (c, a) =>
502
- c.readResource({
503
- pathTemplate: '/sites/{siteId}/routing/static-routings',
504
- siteId: a.siteId,
505
- paginated: true,
506
- page: a.page,
507
- pageSize: a.pageSize,
508
- }),
509
- },
510
- {
511
- path: '/network/ip-mac-binding',
512
- kind: 'collection',
513
- permission: Q,
514
- paginated: true,
515
- defaultPageSize: 50,
516
- estimatedSize: 'small',
517
- description: 'IP↔MAC binding entries (ARP-spoofing protection: which MACs are pinned to which IPs). Paginated.',
518
- fetch: (c, a) =>
519
- c.readResource({
520
- pathTemplate: '/sites/{siteId}/ip-mac-binds',
521
- siteId: a.siteId,
522
- paginated: true,
523
- page: a.page,
524
- pageSize: a.pageSize,
525
- }),
526
- },
527
- {
528
- path: '/network/attack-defense',
529
- kind: 'leaf',
530
- permission: Q,
531
- estimatedSize: 'small',
532
- description: 'Gateway attack-defense settings (flood/scan/spoof protection toggles and thresholds).',
533
- fetch: (c, a) => c.readResource({ pathTemplate: '/sites/{siteId}/attack-defense', siteId: a.siteId }),
534
- },
535
-
536
- // ---- Access-control lists (read-only policy visibility) ---------------
537
- { path: '/network/acls', kind: 'container', permission: Q, description: 'Access-control lists by enforcement point (gateway, switch, EAP/AP).' },
538
- {
539
- path: '/network/acls/gateway',
540
- kind: 'collection',
541
- permission: Q,
542
- paginated: true,
543
- defaultPageSize: 50,
544
- estimatedSize: 'small',
545
- description: 'Gateway ACL rules (osg-acls). Paginated.',
546
- fetch: (c, a) =>
547
- c.readResource({ pathTemplate: '/sites/{siteId}/acls/osg-acls', siteId: a.siteId, paginated: true, page: a.page, pageSize: a.pageSize }),
548
- },
549
- {
550
- path: '/network/acls/switch',
551
- kind: 'collection',
552
- permission: Q,
553
- paginated: true,
554
- defaultPageSize: 50,
555
- estimatedSize: 'small',
556
- description: 'Switch ACL rules (osw-acls). Paginated.',
557
- fetch: (c, a) =>
558
- c.readResource({ pathTemplate: '/sites/{siteId}/acls/osw-acls', siteId: a.siteId, paginated: true, page: a.page, pageSize: a.pageSize }),
559
- },
560
- {
561
- path: '/network/acls/eap',
562
- kind: 'collection',
563
- permission: Q,
564
- paginated: true,
565
- defaultPageSize: 50,
566
- estimatedSize: 'small',
567
- description: 'EAP/AP ACL rules (eap-acls). Paginated.',
568
- fetch: (c, a) =>
569
- c.readResource({ pathTemplate: '/sites/{siteId}/acls/eap-acls', siteId: a.siteId, paginated: true, page: a.page, pageSize: a.pageSize }),
570
- },
571
-
572
- // ---- URL filtering (content controls) ---------------------------------
573
- { path: '/network/url-filters', kind: 'container', permission: Q, description: 'URL-filtering rules (content/parental controls) by enforcement point.' },
574
- {
575
- path: '/network/url-filters/gateway',
576
- kind: 'collection',
577
- permission: Q,
578
- paginated: true,
579
- defaultPageSize: 50,
580
- estimatedSize: 'small',
581
- description: 'Gateway URL-filter rules. Paginated.',
582
- fetch: (c, a) =>
583
- c.readResource({ pathTemplate: '/sites/{siteId}/url-filters/gateway', siteId: a.siteId, paginated: true, page: a.page, pageSize: a.pageSize }),
584
- },
585
- {
586
- path: '/network/url-filters/eap',
587
- kind: 'collection',
588
- permission: Q,
589
- paginated: true,
590
- defaultPageSize: 50,
591
- estimatedSize: 'small',
592
- description: 'EAP/AP URL-filter rules. Paginated.',
593
- fetch: (c, a) =>
594
- c.readResource({ pathTemplate: '/sites/{siteId}/url-filters/eap', siteId: a.siteId, paginated: true, page: a.page, pageSize: a.pageSize }),
595
- },
596
-
597
- // ---- MAC filtering -----------------------------------------------------
598
- { path: '/network/mac-filters', kind: 'container', permission: Q, description: 'Wireless MAC allow/deny filtering lists.' },
599
- {
600
- path: '/network/mac-filters/allow',
601
- kind: 'collection',
602
- permission: Q,
603
- paginated: true,
604
- defaultPageSize: 50,
605
- estimatedSize: 'small',
606
- description: 'MAC addresses on the allow list. Paginated.',
607
- fetch: (c, a) =>
608
- c.readResource({ pathTemplate: '/sites/{siteId}/mac-filters/allow', siteId: a.siteId, paginated: true, page: a.page, pageSize: a.pageSize }),
609
- },
610
- {
611
- path: '/network/mac-filters/deny',
612
- kind: 'collection',
613
- permission: Q,
614
- paginated: true,
615
- defaultPageSize: 50,
616
- estimatedSize: 'small',
617
- description: 'MAC addresses on the deny list. Paginated.',
618
- fetch: (c, a) =>
619
- c.readResource({ pathTemplate: '/sites/{siteId}/mac-filters/deny', siteId: a.siteId, paginated: true, page: a.page, pageSize: a.pageSize }),
620
- },
621
- {
622
- path: '/network/rate-limit-profiles',
623
- kind: 'collection',
624
- permission: Q,
625
- estimatedSize: 'small',
626
- description: 'Configured client rate-limit profiles (input for the rate-limit write tools).',
627
- fetch: (c, a) => c.getRateLimitProfiles(a.siteId),
628
- },
629
-
630
- // ---- WiFi / wireless security -----------------------------------------
631
- { path: '/wifi', kind: 'container', permission: Q, description: 'Wireless: SSIDs, speed tests, rogue-AP and intrusion detection.' },
632
- {
633
- path: '/wifi/speedtest',
634
- kind: 'leaf',
635
- permission: Q,
636
- estimatedSize: 'small',
637
- description: 'Latest speed-test result for an access point. Requires apMac (find via /devices).',
638
- params: [{ name: 'apMac', required: true, description: 'Access point MAC address' }],
639
- fetch: (c, a) =>
640
- c.readResource({
641
- pathTemplate: '/sites/{siteId}/aps/{apMac}/speed-test-result',
642
- siteId: a.siteId,
643
- pathParams: { apMac: a.params?.apMac ?? '' },
644
- }),
645
- },
646
- {
647
- path: '/wifi/rogue',
648
- kind: 'collection',
649
- permission: Q,
650
- paginated: true,
651
- defaultPageSize: 50,
652
- estimatedSize: 'small',
653
- description: 'Rogue access points detected near the site (paginated). Use page/pageSize.',
654
- fetch: (c, a) =>
655
- c.readResource({
656
- pathTemplate: '/sites/{siteId}/insight/rogueaps',
657
- siteId: a.siteId,
658
- paginated: true,
659
- page: a.page,
660
- pageSize: a.pageSize,
661
- }),
662
- },
663
- {
664
- path: '/wifi/wids',
665
- kind: 'collection',
666
- permission: Q,
667
- paginated: true,
668
- defaultPageSize: 50,
669
- estimatedSize: 'small',
670
- description:
671
- 'Wireless Intrusion Detection System (WIDS) entries (paginated). Use page/pageSize. ' +
672
- 'Note: the controller restricts this endpoint to Omada Pro controllers/sites; on a ' +
673
- 'standard controller it returns a "Pro only" message rather than data.',
674
- fetch: (c, a) =>
675
- c.readResource({
676
- pathTemplate: '/sites/{siteId}/insight/wids',
677
- siteId: a.siteId,
678
- paginated: true,
679
- page: a.page,
680
- pageSize: a.pageSize,
681
- }),
682
- },
683
- {
684
- path: '/wifi/ssids',
685
- kind: 'collection',
686
- permission: Q,
687
- estimatedSize: 'small',
688
- description: 'SSIDs in a WLAN group. Requires wlanId; pass params.ssidId too to fetch a single SSID detail.',
689
- params: [
690
- { name: 'wlanId', required: true, description: 'WLAN group id (find via /network/wlan-groups)' },
691
- { name: 'ssidId', required: false, description: 'SSID id — when set, returns that SSID detail instead of the list' },
692
- ],
693
- fetch: (c, a) =>
694
- a.params?.ssidId
695
- ? c.getSsidDetail(a.params.wlanId ?? '', a.params.ssidId, a.siteId)
696
- : c.getSsidList(a.params?.wlanId ?? '', a.siteId),
697
- },
698
- {
699
- path: '/wifi/band-steering',
700
- kind: 'leaf',
701
- permission: Q,
702
- estimatedSize: 'small',
703
- description: 'Band-steering settings for the site (steering clients between 2.4/5/6 GHz radios).',
704
- fetch: (c, a) => c.readResource({ pathTemplate: '/sites/{siteId}/band-steering', siteId: a.siteId }),
705
- },
706
-
707
- // ---- Events / alerts (paginated) --------------------------------------
708
- {
709
- path: '/events',
710
- kind: 'collection',
711
- permission: Q,
712
- paginated: true,
713
- defaultPageSize: 50,
714
- estimatedSize: 'large',
715
- description:
716
- 'Site event log (paginated). Use page/pageSize. The controller requires a time window: by default this ' +
717
- 'returns the last 7 days. Override with params.startTime / params.endTime (epoch MILLISECONDS, e.g. ' +
718
- '1679297710438). Optional params.module filters by source module.',
719
- params: [
720
- { name: 'startTime', required: false, description: 'Window start, epoch ms (default: 7 days ago)' },
721
- { name: 'endTime', required: false, description: 'Window end, epoch ms (default: now)' },
722
- { name: 'module', required: false, description: 'Filter by module (e.g. "Device", "Client", "User")' },
723
- ],
724
- fetch: (c, a) => {
725
- const { timeStart, timeEnd } = resolveLogWindowMs(a.params);
726
- const query: Record<string, unknown> = { 'filters.timeStart': timeStart, 'filters.timeEnd': timeEnd };
727
- if (a.params?.module) {
728
- query['filters.module'] = a.params.module;
729
- }
730
- return c.readResource({ pathTemplate: '/sites/{siteId}/logs/events', siteId: a.siteId, paginated: true, page: a.page, pageSize: a.pageSize, query });
731
- },
732
- },
733
- {
734
- path: '/events/alerts',
735
- kind: 'collection',
736
- permission: Q,
737
- paginated: true,
738
- defaultPageSize: 50,
739
- estimatedSize: 'large',
740
- description:
741
- 'Site alert log (paginated). Use page/pageSize. The controller requires a time window: by default this ' +
742
- 'returns the last 7 days. Override with params.startTime / params.endTime (epoch MILLISECONDS). Optional ' +
743
- 'params.module filters by source module; params.resolved ("true"/"false") filters by resolution state.',
744
- params: [
745
- { name: 'startTime', required: false, description: 'Window start, epoch ms (default: 7 days ago)' },
746
- { name: 'endTime', required: false, description: 'Window end, epoch ms (default: now)' },
747
- { name: 'module', required: false, description: 'Filter by module (e.g. "Device", "Client", "User")' },
748
- { name: 'resolved', required: false, description: '"true" or "false" to filter by resolution state' },
749
- ],
750
- fetch: (c, a) => {
751
- const { timeStart, timeEnd } = resolveLogWindowMs(a.params);
752
- const query: Record<string, unknown> = { 'filters.timeStart': timeStart, 'filters.timeEnd': timeEnd };
753
- if (a.params?.module) {
754
- query['filters.module'] = a.params.module;
755
- }
756
- if (a.params?.resolved === 'true' || a.params?.resolved === 'false') {
757
- query['filters.resolved'] = a.params.resolved;
758
- }
759
- return c.readResource({ pathTemplate: '/sites/{siteId}/logs/alerts', siteId: a.siteId, paginated: true, page: a.page, pageSize: a.pageSize, query });
760
- },
761
- },
762
-
763
- // ---- Dashboard ---------------------------------------------------------
764
- {
765
- path: '/dashboard',
766
- kind: 'leaf',
767
- permission: Q,
768
- estimatedSize: 'medium',
769
- description: 'Site dashboard overview diagram (topology + summary).',
770
- fetch: (c, a) => c.readResource({ pathTemplate: '/sites/{siteId}/dashboard/overview-diagram', siteId: a.siteId }),
771
- },
772
- {
773
- path: '/dashboard/cpu',
774
- kind: 'collection',
775
- permission: Q,
776
- estimatedSize: 'small',
777
- description:
778
- 'Devices with the highest CPU usage over a time window (default: last 24h). Override with params.startTime / ' +
779
- 'params.endTime (epoch SECONDS, e.g. 1682000000).',
780
- params: [
781
- { name: 'startTime', required: false, description: 'Window start, epoch seconds (default: 24h ago)' },
782
- { name: 'endTime', required: false, description: 'Window end, epoch seconds (default: now)' },
783
- ],
784
- fetch: (c, a) =>
785
- c.readResource({ pathTemplate: '/sites/{siteId}/dashboard/top-device-cpu-usage', siteId: a.siteId, query: resolveUsageWindowSec(a.params) }),
786
- },
787
- {
788
- path: '/dashboard/memory',
789
- kind: 'collection',
790
- permission: Q,
791
- estimatedSize: 'small',
792
- description:
793
- 'Devices with the highest memory usage over a time window (default: last 24h). Override with params.startTime / ' +
794
- 'params.endTime (epoch SECONDS, e.g. 1682000000).',
795
- params: [
796
- { name: 'startTime', required: false, description: 'Window start, epoch seconds (default: 24h ago)' },
797
- { name: 'endTime', required: false, description: 'Window end, epoch seconds (default: now)' },
798
- ],
799
- fetch: (c, a) =>
800
- c.readResource({ pathTemplate: '/sites/{siteId}/dashboard/top-device-memory-usage', siteId: a.siteId, query: resolveUsageWindowSec(a.params) }),
801
- },
802
- {
803
- path: '/dashboard/client-distribution',
804
- kind: 'leaf',
805
- permission: Q,
806
- estimatedSize: 'small',
807
- description: 'Breakdown of connected clients by category (wired/wireless, band, SSID). No time window required.',
808
- fetch: (c, a) => c.readResource({ pathTemplate: '/sites/{siteId}/dashboard/client-distribution', siteId: a.siteId }),
809
- },
810
- {
811
- path: '/dashboard/traffic-distribution',
812
- kind: 'leaf',
813
- permission: Q,
814
- estimatedSize: 'small',
815
- description:
816
- 'Traffic distribution across the site over a time window (default: last 24h). Override with params.startTime / ' +
817
- 'params.endTime (epoch SECONDS).',
818
- params: [
819
- { name: 'startTime', required: false, description: 'Window start, epoch seconds (default: 24h ago)' },
820
- { name: 'endTime', required: false, description: 'Window end, epoch seconds (default: now)' },
821
- ],
822
- fetch: (c, a) =>
823
- c.readResource({ pathTemplate: '/sites/{siteId}/dashboard/traffic-distribution', siteId: a.siteId, query: resolveUsageWindowSec(a.params) }),
824
- },
825
- {
826
- path: '/dashboard/traffic-activities',
827
- kind: 'leaf',
828
- permission: Q,
829
- estimatedSize: 'medium',
830
- description:
831
- 'Traffic activity over time (a time-series for charting) for the site (default window: last 24h). Override with ' +
832
- 'params.startTime / params.endTime (epoch SECONDS).',
833
- params: [
834
- { name: 'startTime', required: false, description: 'Window start, epoch seconds (default: 24h ago)' },
835
- { name: 'endTime', required: false, description: 'Window end, epoch seconds (default: now)' },
836
- ],
837
- fetch: (c, a) =>
838
- c.readResource({ pathTemplate: '/sites/{siteId}/dashboard/traffic-activities', siteId: a.siteId, query: resolveUsageWindowSec(a.params) }),
839
- },
840
-
841
- // ---- Firmware ----------------------------------------------------------
842
- {
843
- path: '/firmware',
844
- kind: 'leaf',
845
- permission: Q,
846
- estimatedSize: 'small',
847
- description: 'Latest available firmware info for a device. Requires deviceMac (find via /devices).',
848
- params: [{ name: 'deviceMac', required: true, description: 'Device MAC address' }],
849
- fetch: (c, a) =>
850
- c.readResource({
851
- pathTemplate: '/sites/{siteId}/devices/{deviceMac}/latest-firmware-info',
852
- siteId: a.siteId,
853
- pathParams: { deviceMac: a.params?.deviceMac ?? '' },
854
- }),
855
- },
856
- {
857
- path: '/firmware/critical',
858
- kind: 'leaf',
859
- permission: Q,
860
- estimatedSize: 'small',
861
- description: 'Controller-wide overview of critical firmware upgrades (not site-scoped).',
862
- fetch: (c) => c.readResource({ pathTemplate: '/upgrade/overview/critical', siteScoped: false }),
863
- },
864
-
865
- // ---- VPN (status & tunnels) -------------------------------------------
866
- {
867
- path: '/vpn',
868
- kind: 'container',
869
- permission: Q,
870
- description: 'VPN status: site-to-site, client-to-site, WireGuard, and IPsec tunnel stats.',
871
- },
872
- {
873
- path: '/vpn/site-to-site',
874
- kind: 'collection',
875
- permission: Q,
876
- estimatedSize: 'small',
877
- description: 'Site-to-site VPN tunnels. Pass id=<vpnId> to fetch a single tunnel.',
878
- params: [{ name: 'id', required: false, description: 'VPN id to fetch a single site-to-site tunnel' }],
879
- fetch: (c, a) =>
880
- a.id
881
- ? c.readResource({
882
- pathTemplate: '/sites/{siteId}/vpn/site-to-site-vpns/{vpnId}',
883
- siteId: a.siteId,
884
- pathParams: { vpnId: a.id },
885
- })
886
- : c.readResource({ pathTemplate: '/sites/{siteId}/vpn/site-to-site-vpns', siteId: a.siteId }),
887
- },
888
- {
889
- path: '/vpn/client-to-site',
890
- kind: 'container',
891
- permission: Q,
892
- description: 'Client-to-site (remote-access) VPN: configured servers and connected clients.',
893
- },
894
- {
895
- path: '/vpn/client-to-site/servers',
896
- kind: 'collection',
897
- permission: Q,
898
- estimatedSize: 'small',
899
- description: 'Configured client-to-site VPN servers (L2TP/OpenVPN/etc.).',
900
- fetch: (c, a) =>
901
- c.readResource({ pathTemplate: '/sites/{siteId}/vpn/client-to-site-vpn-servers', siteId: a.siteId }),
902
- },
903
- {
904
- path: '/vpn/client-to-site/clients',
905
- kind: 'collection',
906
- permission: Q,
907
- estimatedSize: 'small',
908
- description: 'Currently connected client-to-site (remote-access) VPN clients.',
909
- fetch: (c, a) =>
910
- c.readResource({ pathTemplate: '/sites/{siteId}/vpn/client-to-site-vpn-clients', siteId: a.siteId }),
911
- },
912
- {
913
- path: '/vpn/wireguard',
914
- kind: 'collection',
915
- permission: Q,
916
- estimatedSize: 'small',
917
- description: 'Configured WireGuard VPN interfaces on the gateway.',
918
- fetch: (c, a) => c.readResource({ pathTemplate: '/sites/{siteId}/vpn/wireguards', siteId: a.siteId }),
919
- },
920
- {
921
- path: '/vpn/ipsec-stats',
922
- kind: 'collection',
923
- permission: Q,
924
- paginated: true,
925
- defaultPageSize: 50,
926
- estimatedSize: 'small',
927
- description: 'IPsec VPN tunnel statistics (paginated — use page/pageSize).',
928
- fetch: (c, a) =>
929
- c.readResource({
930
- pathTemplate: '/sites/{siteId}/setting/vpn/stats/ipsec',
931
- siteId: a.siteId,
932
- paginated: true,
933
- page: a.page,
934
- pageSize: a.pageSize,
935
- }),
936
- },
937
-
938
- // ---- Profiles (reusable network profiles) -----------------------------
939
- {
940
- path: '/profiles',
941
- kind: 'container',
942
- permission: Q,
943
- description: 'Reusable network profiles: per-device Wi-Fi keys (PPSK) and time-range schedules.',
944
- },
945
- {
946
- path: '/profiles/ppsk',
947
- kind: 'collection',
948
- permission: Q,
949
- estimatedSize: 'small',
950
- description:
951
- 'PPSK (Private Pre-Shared Key) profiles — unique per-user/per-device Wi-Fi passwords on a shared SSID. ' +
952
- 'REQUIRES params.type: 0 = PPSK without RADIUS, 1 = PPSK with RADIUS.',
953
- params: [{ name: 'type', required: true, description: '0 = PPSK without RADIUS, 1 = PPSK with RADIUS' }],
954
- fetch: (c, a) =>
955
- c.readResource({
956
- pathTemplate: '/sites/{siteId}/ppsk-profiles',
957
- siteId: a.siteId,
958
- query: { type: Number(a.params?.type) },
959
- }),
960
- },
961
- {
962
- path: '/profiles/time-range',
963
- kind: 'collection',
964
- permission: Q,
965
- estimatedSize: 'small',
966
- description: 'Time-range profiles — reusable schedules referenced by ACLs, Wi-Fi, and PoE rules.',
967
- fetch: (c, a) => c.readResource({ pathTemplate: '/sites/{siteId}/time-range-profiles', siteId: a.siteId }),
968
- },
969
-
970
- // ---- Schedules (PoE / port / firmware-upgrade) ------------------------
971
- {
972
- path: '/schedules',
973
- kind: 'container',
974
- permission: Q,
975
- description:
976
- 'Device schedules: PoE power, switch-port on/off, and firmware-upgrade windows. ' +
977
- '(Reboot schedules are site-template-scoped enterprise config and are intentionally not exposed here.)',
978
- },
979
- {
980
- path: '/schedules/poe',
981
- kind: 'collection',
982
- permission: Q,
983
- estimatedSize: 'small',
984
- description: 'PoE schedules — when switch ports deliver power to PoE devices.',
985
- fetch: (c, a) => c.readResource({ pathTemplate: '/sites/{siteId}/poe-schedules', siteId: a.siteId }),
986
- },
987
- {
988
- path: '/schedules/port',
989
- kind: 'collection',
990
- permission: Q,
991
- estimatedSize: 'small',
992
- description: 'Port schedules — when switch ports are administratively up/down.',
993
- fetch: (c, a) => c.readResource({ pathTemplate: '/sites/{siteId}/port-schedules', siteId: a.siteId }),
994
- },
995
- {
996
- path: '/schedules/upgrade',
997
- kind: 'collection',
998
- permission: Q,
999
- estimatedSize: 'small',
1000
- description: 'Firmware-upgrade schedules — planned maintenance windows for device upgrades.',
1001
- fetch: (c, a) => c.readResource({ pathTemplate: '/sites/{siteId}/upgrade-schedules', siteId: a.siteId }),
1002
- },
1003
-
1004
- // ---- Backup (controller/site backup status) ---------------------------
1005
- {
1006
- path: '/backup',
1007
- kind: 'container',
1008
- permission: Q,
1009
- description: 'Site backup status: available backup files and the most recent backup result.',
1010
- },
1011
- {
1012
- path: '/backup/files',
1013
- kind: 'collection',
1014
- permission: Q,
1015
- estimatedSize: 'small',
1016
- description: 'List of available site backup files.',
1017
- fetch: (c, a) => c.readResource({ pathTemplate: '/sites/{siteId}/maintenance/backup/files', siteId: a.siteId }),
1018
- },
1019
- {
1020
- path: '/backup/result',
1021
- kind: 'leaf',
1022
- permission: Q,
1023
- estimatedSize: 'small',
1024
- description: 'Result of the most recent site backup operation.',
1025
- fetch: (c, a) => c.readResource({ pathTemplate: '/sites/{siteId}/backup/result', siteId: a.siteId }),
1026
- },
1027
-
1028
- // ---- Audit logs (administrative operation history) — ADMIN only -------
1029
- {
1030
- path: '/audit',
1031
- kind: 'container',
1032
- permission: A,
1033
- description:
1034
- 'Administrative audit logs — who did what on the controller. ADMIN only (reveals full admin activity).',
1035
- },
1036
- {
1037
- path: '/audit/site',
1038
- kind: 'collection',
1039
- permission: A,
1040
- paginated: true,
1041
- defaultPageSize: 10,
1042
- estimatedSize: 'large',
1043
- description:
1044
- 'Site-scoped administrative audit log (paginated, ADMIN). Optional params: startTime/endTime ' +
1045
- '(epoch MILLISECONDS) to bound the window, and searchKey (fuzzy text).',
1046
- params: [
1047
- { name: 'startTime', required: false, description: 'Window start, epoch milliseconds' },
1048
- { name: 'endTime', required: false, description: 'Window end, epoch milliseconds' },
1049
- { name: 'searchKey', required: false, description: 'Fuzzy search on the log text' },
1050
- ],
1051
- fetch: (c, a) =>
1052
- c.readResource({
1053
- pathTemplate: '/sites/{siteId}/audit-logs',
1054
- siteId: a.siteId,
1055
- paginated: true,
1056
- page: a.page,
1057
- pageSize: a.pageSize,
1058
- query: auditFilters(a.params),
1059
- }),
1060
- },
1061
- {
1062
- path: '/audit/global',
1063
- kind: 'collection',
1064
- permission: A,
1065
- paginated: true,
1066
- defaultPageSize: 10,
1067
- estimatedSize: 'large',
1068
- description:
1069
- 'Controller-wide administrative audit log across all sites (paginated, ADMIN). Optional params: ' +
1070
- 'startTime/endTime (epoch MILLISECONDS) and searchKey (fuzzy text).',
1071
- params: [
1072
- { name: 'startTime', required: false, description: 'Window start, epoch milliseconds' },
1073
- { name: 'endTime', required: false, description: 'Window end, epoch milliseconds' },
1074
- { name: 'searchKey', required: false, description: 'Fuzzy search on the log text' },
1075
- ],
1076
- fetch: (c, a) =>
1077
- c.readResource({
1078
- pathTemplate: '/audit-logs',
1079
- siteScoped: false,
1080
- paginated: true,
1081
- page: a.page,
1082
- pageSize: a.pageSize,
1083
- query: auditFilters(a.params),
1084
- }),
1085
- },
1086
-
1087
- // ---- Security (IDS/IPS) — ADMIN only -----------------------------------
1088
- {
1089
- path: '/security',
1090
- kind: 'container',
1091
- permission: A,
1092
- description: 'Security: IDS/IPS threat-management log. ADMIN only.',
1093
- },
1094
- {
1095
- path: '/security/threats',
1096
- kind: 'collection',
1097
- permission: A,
1098
- paginated: true,
1099
- defaultPageSize: 10,
1100
- estimatedSize: 'large',
1101
- description:
1102
- 'IDS/IPS threat-management log (paginated, ADMIN). REQUIRES a time window: params.startTime and params.endTime ' +
1103
- '(epoch SECONDS, e.g. 1682000000). Optional params: archived ("true"/"false", default false), severity ' +
1104
- '(0=Critical,1=Major,2=Concerning,3=Minor), sortTime ("asc"/"desc"), searchKey (fuzzy text), siteList ' +
1105
- '(comma-separated site IDs; default all sites).',
1106
- params: [
1107
- { name: 'startTime', required: true, description: 'Window start, epoch seconds (e.g. 1682000000)' },
1108
- { name: 'endTime', required: true, description: 'Window end, epoch seconds (e.g. 1682003600)' },
1109
- { name: 'archived', required: false, description: '"true" or "false" (default false)' },
1110
- { name: 'severity', required: false, description: '0=Critical, 1=Major, 2=Concerning, 3=Minor' },
1111
- { name: 'sortTime', required: false, description: 'Sort by time: "asc" or "desc"' },
1112
- { name: 'searchKey', required: false, description: 'Fuzzy search on description/classification' },
1113
- { name: 'siteList', required: false, description: 'Comma-separated site IDs (default: all sites)' },
1114
- ],
1115
- fetch: (c, a) =>
1116
- c.getThreatList({
1117
- siteList: a.params?.siteList,
1118
- archived: a.params?.archived === 'true',
1119
- page: a.page ?? 1,
1120
- pageSize: a.pageSize ?? 10,
1121
- startTime: Number(a.params?.startTime),
1122
- endTime: Number(a.params?.endTime),
1123
- severity: a.params?.severity !== undefined ? Number(a.params.severity) : undefined,
1124
- sortTime: a.params?.sortTime === 'asc' ? 'asc' : a.params?.sortTime === 'desc' ? 'desc' : undefined,
1125
- searchKey: a.params?.searchKey,
1126
- }),
1127
- },
1128
- ];
1129
-
1130
- const byPath = new Map<string, ResourceNode>(OMADA_RESOURCES.map((n) => [n.path, n]));
1131
-
1132
- /** Normalize a user-supplied path: leading slash, no trailing slash, '/' for root. */
1133
- export function normalizePath(path: string): string {
1134
- if (!path || path === '/') {
1135
- return '/';
1136
- }
1137
- let p = path.trim();
1138
- if (!p.startsWith('/')) {
1139
- p = `/${p}`;
1140
- }
1141
- if (p.length > 1 && p.endsWith('/')) {
1142
- p = p.slice(0, -1);
1143
- }
1144
- return p;
1145
- }
1146
-
1147
- /** The immediate parent path, or null for the root. */
1148
- function parentPath(path: string): string | null {
1149
- if (path === '/') {
1150
- return null;
1151
- }
1152
- const idx = path.lastIndexOf('/');
1153
- return idx <= 0 ? '/' : path.slice(0, idx);
1154
- }
1155
-
1156
- export function getResourceNode(path: string): ResourceNode | undefined {
1157
- return byPath.get(normalizePath(path));
1158
- }
1159
-
1160
- /** Direct children (one segment deeper) of the given path. */
1161
- export function childrenOf(path: string): ResourceNode[] {
1162
- const norm = normalizePath(path);
1163
- return OMADA_RESOURCES.filter((n) => parentPath(n.path) === norm);
1164
- }