unifi-cli 0.2.2__tar.gz → 0.2.3__tar.gz
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.
- {unifi_cli-0.2.2 → unifi_cli-0.2.3}/.github/workflows/release.yml +10 -3
- unifi_cli-0.2.3/.gitignore +2 -0
- {unifi_cli-0.2.2 → unifi_cli-0.2.3}/CHANGELOG.md +7 -0
- {unifi_cli-0.2.2 → unifi_cli-0.2.3}/Cargo.lock +1 -1
- {unifi_cli-0.2.2 → unifi_cli-0.2.3}/Cargo.toml +1 -1
- {unifi_cli-0.2.2 → unifi_cli-0.2.3}/PKG-INFO +1 -1
- {unifi_cli-0.2.2 → unifi_cli-0.2.3}/src/api/client.rs +23 -4
- {unifi_cli-0.2.2 → unifi_cli-0.2.3}/tests/mock_server.rs +74 -0
- unifi_cli-0.2.2/.gitignore +0 -1
- {unifi_cli-0.2.2 → unifi_cli-0.2.3}/.github/workflows/ci.yml +0 -0
- {unifi_cli-0.2.2 → unifi_cli-0.2.3}/LICENSE +0 -0
- {unifi_cli-0.2.2 → unifi_cli-0.2.3}/Makefile +0 -0
- {unifi_cli-0.2.2 → unifi_cli-0.2.3}/README.md +0 -0
- {unifi_cli-0.2.2 → unifi_cli-0.2.3}/prek.toml +0 -0
- {unifi_cli-0.2.2 → unifi_cli-0.2.3}/pyproject.toml +0 -0
- {unifi_cli-0.2.2 → unifi_cli-0.2.3}/src/api/mod.rs +0 -0
- {unifi_cli-0.2.2 → unifi_cli-0.2.3}/src/api/tests.rs +0 -0
- {unifi_cli-0.2.2 → unifi_cli-0.2.3}/src/api/types.rs +0 -0
- {unifi_cli-0.2.2 → unifi_cli-0.2.3}/src/commands/clients.rs +0 -0
- {unifi_cli-0.2.2 → unifi_cli-0.2.3}/src/commands/devices.rs +0 -0
- {unifi_cli-0.2.2 → unifi_cli-0.2.3}/src/commands/events.rs +0 -0
- {unifi_cli-0.2.2 → unifi_cli-0.2.3}/src/commands/mod.rs +0 -0
- {unifi_cli-0.2.2 → unifi_cli-0.2.3}/src/commands/networks.rs +0 -0
- {unifi_cli-0.2.2 → unifi_cli-0.2.3}/src/commands/protect.rs +0 -0
- {unifi_cli-0.2.2 → unifi_cli-0.2.3}/src/commands/system.rs +0 -0
- {unifi_cli-0.2.2 → unifi_cli-0.2.3}/src/lib.rs +0 -0
- {unifi_cli-0.2.2 → unifi_cli-0.2.3}/src/main.rs +0 -0
- {unifi_cli-0.2.2 → unifi_cli-0.2.3}/src/output.rs +0 -0
- {unifi_cli-0.2.2 → unifi_cli-0.2.3}/src/schema.rs +0 -0
- {unifi_cli-0.2.2 → unifi_cli-0.2.3}/src/tui.rs +0 -0
- {unifi_cli-0.2.2 → unifi_cli-0.2.3}/tests/fixtures/clispec-v0.2.json +0 -0
- {unifi_cli-0.2.2 → unifi_cli-0.2.3}/tests/spec_compliance.rs +0 -0
- {unifi_cli-0.2.2 → unifi_cli-0.2.3}/unifi_cli/__init__.py +0 -0
- {unifi_cli-0.2.2 → unifi_cli-0.2.3}/unifi_cli/__main__.py +0 -0
|
@@ -332,7 +332,7 @@ jobs:
|
|
|
332
332
|
|
|
333
333
|
- name: Update Homebrew formula
|
|
334
334
|
env:
|
|
335
|
-
|
|
335
|
+
HOMEBREW_TAP_DEPLOY_KEY: ${{ secrets.HOMEBREW_TAP_DEPLOY_KEY }}
|
|
336
336
|
run: |
|
|
337
337
|
VERSION="${{ github.ref_name }}"
|
|
338
338
|
VERSION_NUM="${VERSION#v}"
|
|
@@ -381,8 +381,15 @@ jobs:
|
|
|
381
381
|
sed -i "s/SHA_AARCH64_UNKNOWN_LINUX_GNU/${{ steps.hashes.outputs.aarch64_unknown_linux_gnu }}/g" /tmp/unifi-cli.rb
|
|
382
382
|
sed -i "s/SHA_X86_64_UNKNOWN_LINUX_GNU/${{ steps.hashes.outputs.x86_64_unknown_linux_gnu }}/g" /tmp/unifi-cli.rb
|
|
383
383
|
|
|
384
|
-
#
|
|
385
|
-
|
|
384
|
+
# Authenticate to the tap with a write deploy key scoped to
|
|
385
|
+
# rvben/homebrew-tap. The key does not expire, so cross-repo pushes
|
|
386
|
+
# keep working without periodic token rotation.
|
|
387
|
+
mkdir -p ~/.ssh
|
|
388
|
+
echo "$HOMEBREW_TAP_DEPLOY_KEY" > ~/.ssh/id_ed25519
|
|
389
|
+
chmod 600 ~/.ssh/id_ed25519
|
|
390
|
+
ssh-keyscan -t ed25519 github.com >> ~/.ssh/known_hosts 2>/dev/null
|
|
391
|
+
|
|
392
|
+
git clone git@github.com:rvben/homebrew-tap.git /tmp/tap
|
|
386
393
|
mkdir -p /tmp/tap/Formula
|
|
387
394
|
cp /tmp/unifi-cli.rb /tmp/tap/Formula/unifi-cli.rb
|
|
388
395
|
cd /tmp/tap
|
|
@@ -4,6 +4,13 @@ All notable changes to this project will be documented in this file.
|
|
|
4
4
|
|
|
5
5
|
|
|
6
6
|
|
|
7
|
+
|
|
8
|
+
## [0.2.3](https://github.com/rvben/unifi-cli/compare/v0.2.2...v0.2.3) - 2026-07-07
|
|
9
|
+
|
|
10
|
+
### Fixed
|
|
11
|
+
|
|
12
|
+
- **events**: fall back to rest/alarm when stat/event is 404 on UniFi OS 9+ ([ac8a69c](https://github.com/rvben/unifi-cli/commit/ac8a69c743c5eab05def7bcd67e08e5b060275ea))
|
|
13
|
+
|
|
7
14
|
## [0.2.2](https://github.com/rvben/unifi-cli/compare/v0.2.1...v0.2.2) - 2026-06-11
|
|
8
15
|
|
|
9
16
|
### Added
|
|
@@ -367,11 +367,30 @@ impl UnifiClient {
|
|
|
367
367
|
}
|
|
368
368
|
|
|
369
369
|
// Events
|
|
370
|
+
//
|
|
371
|
+
// Legacy `stat/event` was removed in UniFi Network 9+ (UniFi OS) and now
|
|
372
|
+
// returns api.err.NotFound (404). On those controllers the surviving REST
|
|
373
|
+
// surface for notable events is `rest/alarm`, whose records share this
|
|
374
|
+
// `Event` shape, so fall back to it. (The full live event stream on newer
|
|
375
|
+
// controllers is only exposed over the events WebSocket, which this REST
|
|
376
|
+
// client does not consume.)
|
|
370
377
|
pub async fn list_events(&self, limit: usize) -> Result<Vec<Event>, ApiError> {
|
|
371
|
-
|
|
372
|
-
.get_legacy(&format!("/stat/event?_limit={limit}"))
|
|
373
|
-
.await
|
|
374
|
-
|
|
378
|
+
match self
|
|
379
|
+
.get_legacy::<Event>(&format!("/stat/event?_limit={limit}"))
|
|
380
|
+
.await
|
|
381
|
+
{
|
|
382
|
+
Ok(events) => Ok(events),
|
|
383
|
+
Err(ApiError::NotFound(_)) => {
|
|
384
|
+
let mut alarms: Vec<Event> = self.get_legacy("/rest/alarm").await?;
|
|
385
|
+
// `rest/alarm` is neither time-ordered nor limited server-side;
|
|
386
|
+
// present the most recent `limit` records to match the
|
|
387
|
+
// semantics `stat/event?_limit=` provided on older controllers.
|
|
388
|
+
alarms.sort_by_key(|e| std::cmp::Reverse(e.time));
|
|
389
|
+
alarms.truncate(limit);
|
|
390
|
+
Ok(alarms)
|
|
391
|
+
}
|
|
392
|
+
Err(e) => Err(e),
|
|
393
|
+
}
|
|
375
394
|
}
|
|
376
395
|
|
|
377
396
|
// Port table for a specific device
|
|
@@ -1706,6 +1706,80 @@ mod command_output {
|
|
|
1706
1706
|
.unwrap_err();
|
|
1707
1707
|
assert!(err.to_string().contains("Not found"));
|
|
1708
1708
|
}
|
|
1709
|
+
|
|
1710
|
+
#[tokio::test]
|
|
1711
|
+
async fn list_events_returns_stat_event_records() {
|
|
1712
|
+
let server = MockServer::start().await;
|
|
1713
|
+
Mock::given(method("GET"))
|
|
1714
|
+
.and(path("/proxy/network/api/s/default/stat/event"))
|
|
1715
|
+
.respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
|
|
1716
|
+
"meta": {"rc": "ok"},
|
|
1717
|
+
"data": [
|
|
1718
|
+
{"key": "EVT_AP_Connected", "msg": "AP connected", "subsystem": "wlan", "time": 200, "datetime": "2026-07-07T16:00:00Z"},
|
|
1719
|
+
{"key": "EVT_SW_LostContact", "msg": "Switch lost contact", "subsystem": "lan", "time": 100, "datetime": "2026-07-07T15:00:00Z"}
|
|
1720
|
+
]
|
|
1721
|
+
})))
|
|
1722
|
+
.mount(&server)
|
|
1723
|
+
.await;
|
|
1724
|
+
|
|
1725
|
+
let client = mock_client(&server).await;
|
|
1726
|
+
let events = client.list_events(10).await.unwrap();
|
|
1727
|
+
assert_eq!(events.len(), 2);
|
|
1728
|
+
assert_eq!(events[0].key.as_deref(), Some("EVT_AP_Connected"));
|
|
1729
|
+
}
|
|
1730
|
+
|
|
1731
|
+
// UniFi Network 9+ (UniFi OS) removed the legacy stat/event route, which now
|
|
1732
|
+
// returns api.err.NotFound (404). list_events must fall back to rest/alarm and
|
|
1733
|
+
// return the most recent `limit` records.
|
|
1734
|
+
#[tokio::test]
|
|
1735
|
+
async fn list_events_falls_back_to_alarms_on_404() {
|
|
1736
|
+
let server = MockServer::start().await;
|
|
1737
|
+
Mock::given(method("GET"))
|
|
1738
|
+
.and(path("/proxy/network/api/s/default/stat/event"))
|
|
1739
|
+
.respond_with(ResponseTemplate::new(404).set_body_json(serde_json::json!({
|
|
1740
|
+
"meta": {"rc": "error", "msg": "api.err.NotFound"},
|
|
1741
|
+
"data": []
|
|
1742
|
+
})))
|
|
1743
|
+
.expect(1)
|
|
1744
|
+
.mount(&server)
|
|
1745
|
+
.await;
|
|
1746
|
+
Mock::given(method("GET"))
|
|
1747
|
+
.and(path("/proxy/network/api/s/default/rest/alarm"))
|
|
1748
|
+
.respond_with(ResponseTemplate::new(200).set_body_json(serde_json::json!({
|
|
1749
|
+
"meta": {"rc": "ok"},
|
|
1750
|
+
"data": [
|
|
1751
|
+
{"key": "EVT_GW_Older", "msg": "older", "time": 100, "datetime": "a"},
|
|
1752
|
+
{"key": "EVT_GW_Newest", "msg": "newest", "time": 300, "datetime": "c"},
|
|
1753
|
+
{"key": "EVT_GW_Middle", "msg": "middle", "time": 200, "datetime": "b"}
|
|
1754
|
+
]
|
|
1755
|
+
})))
|
|
1756
|
+
.expect(1)
|
|
1757
|
+
.mount(&server)
|
|
1758
|
+
.await;
|
|
1759
|
+
|
|
1760
|
+
let client = mock_client(&server).await;
|
|
1761
|
+
let events = client.list_events(2).await.unwrap();
|
|
1762
|
+
// Most-recent-first, truncated to the requested limit.
|
|
1763
|
+
assert_eq!(events.len(), 2);
|
|
1764
|
+
assert_eq!(events[0].msg.as_deref(), Some("newest"));
|
|
1765
|
+
assert_eq!(events[1].msg.as_deref(), Some("middle"));
|
|
1766
|
+
}
|
|
1767
|
+
|
|
1768
|
+
#[tokio::test]
|
|
1769
|
+
async fn list_events_propagates_non_404_errors() {
|
|
1770
|
+
let server = MockServer::start().await;
|
|
1771
|
+
Mock::given(method("GET"))
|
|
1772
|
+
.and(path("/proxy/network/api/s/default/stat/event"))
|
|
1773
|
+
.respond_with(ResponseTemplate::new(500).set_body_json(serde_json::json!({
|
|
1774
|
+
"meta": {"rc": "error", "msg": "api.err.ServerError"},
|
|
1775
|
+
"data": []
|
|
1776
|
+
})))
|
|
1777
|
+
.mount(&server)
|
|
1778
|
+
.await;
|
|
1779
|
+
|
|
1780
|
+
let client = mock_client(&server).await;
|
|
1781
|
+
assert!(client.list_events(10).await.is_err());
|
|
1782
|
+
}
|
|
1709
1783
|
}
|
|
1710
1784
|
|
|
1711
1785
|
// --- Client construction tests ---
|
unifi_cli-0.2.2/.gitignore
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
/target
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|