Skip to content

Commit

Permalink
Enhancement: support new unifi API with key
Browse files Browse the repository at this point in the history
  • Loading branch information
shamoon committed Mar 1, 2025
1 parent d682b6a commit 4cd4de7
Show file tree
Hide file tree
Showing 5 changed files with 43 additions and 24 deletions.
13 changes: 9 additions & 4 deletions docs/widgets/info/unifi_controller.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,17 @@ An optional 'site' parameter can be supplied, if it is not the widget will use t

<img width="162" alt="unifi_infowidget" src="https://user-images.githubusercontent.com/4887959/197706832-f5a8706b-7282-4892-a666-b7d999752562.png">

| Unifi API | Homepage Widget Version |
| ------------------ | ----------------------- |
| Controller API | 1 (default) |
| Network API (2024) | 2 |

```yaml
- unifi_console:
url: https://unifi.host.or.ip:port
username: user
password: pass
site: Site Name # optional
username: user # version 1
password: pass # version 1
key: unifiapikey # version 2
version: 2 # default is 1
```
_Added in v0.4.18, updated in 0.6.7_
13 changes: 9 additions & 4 deletions docs/widgets/services/unifi-controller.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,18 @@ Allowed fields: `["uptime", "wan", "lan", "lan_users", "lan_devices", "wlan", "w

If you enter e.g. incorrect credentials and receive an "API Error", you may need to recreate the container or restart the service to clear the cache.

| Unifi API | Homepage Widget Version |
| ------------------ | ----------------------- |
| Controller API | 1 (default) |
| Network API (2024) | 2 |

```yaml
widget:
type: unifi
url: https://unifi.host.or.ip:port
username: username
password: password
site: Site Name # optional
username: user # version 1
password: pass # version 1
key: unifiapikey # version 2
version: 2 # default is 1
```
_Added in v0.4.18, updated in 0.6.7_
4 changes: 2 additions & 2 deletions src/utils/config/service-helpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -304,7 +304,7 @@ export function cleanServiceGroups(groups) {
// frigate
enableRecentEvents,

// beszel, glances, immich, mealie, pihole, pfsense, speedtest
// beszel, glances, immich, mealie, pihole, pfsense, speedtest, unifi
version,

// glances
Expand Down Expand Up @@ -482,7 +482,7 @@ export function cleanServiceGroups(groups) {
if (snapshotHost) widget.snapshotHost = snapshotHost;
if (snapshotPath) widget.snapshotPath = snapshotPath;
}
if (["beszel", "glances", "immich", "mealie", "pfsense", "pihole", "speedtest"].includes(type)) {
if (["beszel", "glances", "immich", "mealie", "pfsense", "pihole", "speedtest", "unifi"].includes(type)) {
if (version) widget.version = parseInt(version, 10);
}
if (type === "glances") {
Expand Down
5 changes: 3 additions & 2 deletions src/utils/config/widget-helpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ export async function cleanWidgetGroups(widgets) {
}
});

// delete url from the sanitized options if the widget is not a search or glances widgeth
// delete url from the sanitized options if the widget is not a search or glances widget
if (widget.type !== "search" && widget.type !== "glances" && optionKeys.includes("url")) {
delete sanitizedOptions.url;
}
Expand All @@ -57,7 +57,7 @@ export async function getPrivateWidgetOptions(type, widgetIndex) {
const widgets = await widgetsFromConfig();

const privateOptions = widgets.map((widget) => {
const { index, url, username, password, key, apiKey } = widget.options;
const { index, url, username, password, key, apiKey, version } = widget.options;

return {
type: widget.type,
Expand All @@ -68,6 +68,7 @@ export async function getPrivateWidgetOptions(type, widgetIndex) {
password,
key,
apiKey,
version,
},
};
});
Expand Down
32 changes: 20 additions & 12 deletions src/widgets/unifi/proxy.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ async function getWidget(req) {
}
}

console.log(widget);

return widget;
}

Expand Down Expand Up @@ -75,26 +77,32 @@ export default async function unifiProxyHandler(req, res) {
let [status, contentType, data, responseHeaders] = [];
let prefix = cache.get(`${prefixCacheKey}.${service}`);
let csrfToken;
const headers = {};
if (prefix === null) {
// auto detect if we're talking to a UDM Pro, and cache the result so that we
// don't make two requests each time data from Unifi is required
[status, contentType, data, responseHeaders] = await httpProxy(widget.url);
prefix = "";
if (responseHeaders?.["x-csrf-token"]) {
// Unifi OS < 3.2.5 passes & requires csrf-token
prefix = udmpPrefix;
csrfToken = responseHeaders["x-csrf-token"];
} else if (responseHeaders?.["access-control-expose-headers"]) {
// Unifi OS ≥ 3.2.5 doesnt pass csrf token but still uses different endpoint
prefix = udmpPrefix;
if (widget.version === 2) {
prefix = "/proxy/network";
headers["X-API-KEY"] = widget.key;
} else {
// auto detect if we're talking to a UDM Pro, and cache the result so that we
// don't make two requests each time data from Unifi is required
[status, contentType, data, responseHeaders] = await httpProxy(widget.url);
prefix = "";
if (responseHeaders?.["x-csrf-token"]) {
// Unifi OS < 3.2.5 passes & requires csrf-token
prefix = udmpPrefix;
csrfToken = responseHeaders["x-csrf-token"];
} else if (responseHeaders?.["access-control-expose-headers"]) {
// Unifi OS ≥ 3.2.5 doesnt pass csrf token but still uses different endpoint
prefix = udmpPrefix;
}
}
cache.put(`${prefixCacheKey}.${service}`, prefix);
}

widget.prefix = prefix;
const { endpoint } = req.query;
const url = new URL(formatApiCall(api, { endpoint, ...widget }));
const params = { method: "GET", headers: {} };
const params = { method: "GET", headers };
setCookieHeader(url, params);

[status, contentType, data, responseHeaders] = await httpProxy(url, params);
Expand Down

0 comments on commit 4cd4de7

Please sign in to comment.