keenetic 0.1.0 → 1.0.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.
- checksums.yaml +4 -4
- data/LICENSE.txt +21 -0
- data/README.md +28 -26
- data/lib/keenetic/client.rb +131 -1
- data/lib/keenetic/resources/base.rb +4 -0
- data/lib/keenetic/resources/components.rb +88 -0
- data/lib/keenetic/resources/config.rb +89 -0
- data/lib/keenetic/resources/diagnostics.rb +102 -0
- data/lib/keenetic/resources/dns.rb +95 -0
- data/lib/keenetic/resources/dyndns.rb +71 -0
- data/lib/keenetic/resources/firewall.rb +103 -0
- data/lib/keenetic/resources/hotspot.rb +282 -0
- data/lib/keenetic/resources/ipv6.rb +74 -0
- data/lib/keenetic/resources/mesh.rb +84 -0
- data/lib/keenetic/resources/nat.rb +202 -0
- data/lib/keenetic/resources/qos.rb +89 -0
- data/lib/keenetic/resources/routes.rb +432 -0
- data/lib/keenetic/resources/schedule.rb +87 -0
- data/lib/keenetic/resources/system.rb +134 -0
- data/lib/keenetic/resources/usb.rb +135 -0
- data/lib/keenetic/resources/users.rb +92 -0
- data/lib/keenetic/resources/vpn.rb +153 -0
- data/lib/keenetic/version.rb +1 -2
- data/lib/keenetic.rb +33 -0
- metadata +20 -3
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
module Keenetic
|
|
2
|
+
module Resources
|
|
3
|
+
# Dynamic DNS resource for managing KeenDNS and third-party DDNS.
|
|
4
|
+
#
|
|
5
|
+
# == API Endpoints Used
|
|
6
|
+
#
|
|
7
|
+
# === KeenDNS Status
|
|
8
|
+
# GET /rci/show/rc/ip/http/dyndns
|
|
9
|
+
#
|
|
10
|
+
# === Configure KeenDNS
|
|
11
|
+
# POST /rci/ip/http/dyndns
|
|
12
|
+
#
|
|
13
|
+
# === Third-Party DDNS
|
|
14
|
+
# GET /rci/show/dyndns
|
|
15
|
+
#
|
|
16
|
+
class Dyndns < Base
|
|
17
|
+
# Get KeenDNS status.
|
|
18
|
+
#
|
|
19
|
+
# @return [Hash] KeenDNS configuration and status
|
|
20
|
+
# @example
|
|
21
|
+
# status = client.dyndns.keendns_status
|
|
22
|
+
#
|
|
23
|
+
def keendns_status
|
|
24
|
+
response = get('/rci/show/rc/ip/http/dyndns')
|
|
25
|
+
normalize_response(response)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# Configure KeenDNS.
|
|
29
|
+
#
|
|
30
|
+
# @param params [Hash] KeenDNS configuration parameters
|
|
31
|
+
# @return [Hash, nil] API response
|
|
32
|
+
# @example
|
|
33
|
+
# client.dyndns.configure_keendns(enabled: true, domain: 'myrouter')
|
|
34
|
+
#
|
|
35
|
+
def configure_keendns(**params)
|
|
36
|
+
post('/rci/ip/http/dyndns', normalize_params(params))
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# Get third-party DDNS providers status.
|
|
40
|
+
#
|
|
41
|
+
# @return [Hash] Third-party DDNS configuration
|
|
42
|
+
# @example
|
|
43
|
+
# ddns = client.dyndns.third_party
|
|
44
|
+
#
|
|
45
|
+
def third_party
|
|
46
|
+
response = get('/rci/show/dyndns')
|
|
47
|
+
normalize_response(response)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# Alias for third_party
|
|
51
|
+
alias providers third_party
|
|
52
|
+
|
|
53
|
+
private
|
|
54
|
+
|
|
55
|
+
def normalize_response(response)
|
|
56
|
+
return {} unless response.is_a?(Hash)
|
|
57
|
+
|
|
58
|
+
deep_normalize_keys(response)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def normalize_params(params)
|
|
62
|
+
result = {}
|
|
63
|
+
params.each do |key, value|
|
|
64
|
+
api_key = key.to_s.tr('_', '-')
|
|
65
|
+
result[api_key] = value
|
|
66
|
+
end
|
|
67
|
+
result
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
module Keenetic
|
|
2
|
+
module Resources
|
|
3
|
+
# Firewall resource for managing firewall policies and access lists.
|
|
4
|
+
#
|
|
5
|
+
# == API Endpoints Used
|
|
6
|
+
#
|
|
7
|
+
# === Reading Firewall Policies
|
|
8
|
+
# GET /rci/show/ip/policy
|
|
9
|
+
# Returns: Firewall policy configuration
|
|
10
|
+
#
|
|
11
|
+
# === Reading Access Lists
|
|
12
|
+
# GET /rci/show/access-list
|
|
13
|
+
# Returns: Access control lists
|
|
14
|
+
#
|
|
15
|
+
# === Adding Firewall Rule
|
|
16
|
+
# POST /rci/ip/policy
|
|
17
|
+
# Body: Rule configuration
|
|
18
|
+
#
|
|
19
|
+
class Firewall < Base
|
|
20
|
+
# Get firewall policies.
|
|
21
|
+
#
|
|
22
|
+
# == Keenetic API Request
|
|
23
|
+
# GET /rci/show/ip/policy
|
|
24
|
+
#
|
|
25
|
+
# @return [Hash] Firewall policy configuration
|
|
26
|
+
# @example
|
|
27
|
+
# policies = client.firewall.policies
|
|
28
|
+
#
|
|
29
|
+
def policies
|
|
30
|
+
response = get('/rci/show/ip/policy')
|
|
31
|
+
normalize_response(response)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Get access control lists.
|
|
35
|
+
#
|
|
36
|
+
# == Keenetic API Request
|
|
37
|
+
# GET /rci/show/access-list
|
|
38
|
+
#
|
|
39
|
+
# @return [Hash] Access lists configuration
|
|
40
|
+
# @example
|
|
41
|
+
# lists = client.firewall.access_lists
|
|
42
|
+
#
|
|
43
|
+
def access_lists
|
|
44
|
+
response = get('/rci/show/access-list')
|
|
45
|
+
normalize_response(response)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# Add a firewall rule.
|
|
49
|
+
#
|
|
50
|
+
# == Keenetic API Request
|
|
51
|
+
# POST /rci/ip/policy
|
|
52
|
+
#
|
|
53
|
+
# @param params [Hash] Rule configuration
|
|
54
|
+
# @return [Hash, nil] API response
|
|
55
|
+
#
|
|
56
|
+
# @example Add a basic rule
|
|
57
|
+
# client.firewall.add_rule(
|
|
58
|
+
# action: 'permit',
|
|
59
|
+
# protocol: 'tcp',
|
|
60
|
+
# src: '192.168.1.0/24',
|
|
61
|
+
# dst_port: 80
|
|
62
|
+
# )
|
|
63
|
+
#
|
|
64
|
+
def add_rule(**params)
|
|
65
|
+
post('/rci/ip/policy', normalize_params(params))
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# Delete a firewall rule.
|
|
69
|
+
#
|
|
70
|
+
# == Keenetic API Request
|
|
71
|
+
# POST /rci/ip/policy
|
|
72
|
+
# Body: { index, no: true }
|
|
73
|
+
#
|
|
74
|
+
# @param index [Integer] Rule index to delete
|
|
75
|
+
# @return [Hash, nil] API response
|
|
76
|
+
#
|
|
77
|
+
# @example
|
|
78
|
+
# client.firewall.delete_rule(index: 1)
|
|
79
|
+
#
|
|
80
|
+
def delete_rule(index:)
|
|
81
|
+
post('/rci/ip/policy', { 'index' => index, 'no' => true })
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
private
|
|
85
|
+
|
|
86
|
+
def normalize_response(response)
|
|
87
|
+
return {} unless response.is_a?(Hash)
|
|
88
|
+
|
|
89
|
+
deep_normalize_keys(response)
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def normalize_params(params)
|
|
93
|
+
result = {}
|
|
94
|
+
params.each do |key, value|
|
|
95
|
+
# Convert snake_case to kebab-case for API
|
|
96
|
+
api_key = key.to_s.tr('_', '-')
|
|
97
|
+
result[api_key] = value
|
|
98
|
+
end
|
|
99
|
+
result
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
end
|
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
module Keenetic
|
|
2
|
+
module Resources
|
|
3
|
+
# Manages hotspot hosts and IP policies.
|
|
4
|
+
#
|
|
5
|
+
# == API Endpoints Used
|
|
6
|
+
#
|
|
7
|
+
# === Reading Policies
|
|
8
|
+
# POST /rci/ (batch format)
|
|
9
|
+
# Body: [{"show": {"sc": {"ip": {"policy": {}}}}}]
|
|
10
|
+
# Returns: All IP policies from configuration
|
|
11
|
+
#
|
|
12
|
+
# === Reading Hosts
|
|
13
|
+
# POST /rci/ (batch format)
|
|
14
|
+
# Body: [
|
|
15
|
+
# {"show": {"sc": {"ip": {"hotspot": {"host": {}}}}}},
|
|
16
|
+
# {"show": {"ip": {"hotspot": {}}}}
|
|
17
|
+
# ]
|
|
18
|
+
# Returns: Configuration hosts and runtime hosts
|
|
19
|
+
#
|
|
20
|
+
# === Setting Host Policy
|
|
21
|
+
# POST /rci/ (batch format)
|
|
22
|
+
# Body: [
|
|
23
|
+
# {"webhelp": {"event": {"push": {"data": "..."}}}},
|
|
24
|
+
# {"ip": {"hotspot": {"host": {"mac": "...", "permit": true, "policy": "..."}}}},
|
|
25
|
+
# {"system": {"configuration": {"save": {}}}}
|
|
26
|
+
# ]
|
|
27
|
+
#
|
|
28
|
+
class Hotspot < Base
|
|
29
|
+
# Get all IP policies.
|
|
30
|
+
#
|
|
31
|
+
# == Keenetic API Request
|
|
32
|
+
# POST /rci/ (batch format)
|
|
33
|
+
# Body: [{"show": {"sc": {"ip": {"policy": {}}}}}]
|
|
34
|
+
#
|
|
35
|
+
# == Response Structure from API
|
|
36
|
+
# [
|
|
37
|
+
# {
|
|
38
|
+
# "id": "Policy0",
|
|
39
|
+
# "description": "VPN Policy",
|
|
40
|
+
# "global": false,
|
|
41
|
+
# "interface": [
|
|
42
|
+
# { "name": "Wireguard0", "priority": 100 }
|
|
43
|
+
# ]
|
|
44
|
+
# }
|
|
45
|
+
# ]
|
|
46
|
+
#
|
|
47
|
+
# @return [Array<Hash>] List of normalized policy hashes
|
|
48
|
+
# @example
|
|
49
|
+
# policies = client.hotspot.policies
|
|
50
|
+
# # => [{ id: "Policy0", description: "VPN Policy", global: false, interfaces: [...] }]
|
|
51
|
+
#
|
|
52
|
+
def policies
|
|
53
|
+
response = client.batch([{ 'show' => { 'sc' => { 'ip' => { 'policy' => {} } } } }])
|
|
54
|
+
policies_data = extract_policies_from_response(response)
|
|
55
|
+
normalize_policies(policies_data)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# Get all registered hosts with their policies.
|
|
59
|
+
#
|
|
60
|
+
# == Keenetic API Request
|
|
61
|
+
# POST /rci/ (batch format)
|
|
62
|
+
# Body: [
|
|
63
|
+
# {"show": {"sc": {"ip": {"hotspot": {"host": {}}}}}},
|
|
64
|
+
# {"show": {"ip": {"hotspot": {}}}}
|
|
65
|
+
# ]
|
|
66
|
+
#
|
|
67
|
+
# Returns merged data from configuration (static settings) and runtime (current status).
|
|
68
|
+
#
|
|
69
|
+
# @return [Array<Hash>] List of normalized host hashes
|
|
70
|
+
# @example
|
|
71
|
+
# hosts = client.hotspot.hosts
|
|
72
|
+
# # => [{ mac: "AA:BB:CC:DD:EE:FF", name: "My Device", policy: "Policy0", permit: true, ... }]
|
|
73
|
+
#
|
|
74
|
+
def hosts
|
|
75
|
+
response = client.batch([
|
|
76
|
+
{ 'show' => { 'sc' => { 'ip' => { 'hotspot' => { 'host' => {} } } } } },
|
|
77
|
+
{ 'show' => { 'ip' => { 'hotspot' => {} } } }
|
|
78
|
+
])
|
|
79
|
+
|
|
80
|
+
config_hosts = extract_config_hosts(response)
|
|
81
|
+
runtime_hosts = extract_runtime_hosts(response)
|
|
82
|
+
|
|
83
|
+
merge_hosts(config_hosts, runtime_hosts)
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# Set or remove policy for a host.
|
|
87
|
+
#
|
|
88
|
+
# == Keenetic API Request
|
|
89
|
+
# POST /rci/ (batch format)
|
|
90
|
+
# Body: [
|
|
91
|
+
# {"webhelp": {"event": {"push": {"data": "{\"type\":\"configuration_change\",\"value\":{\"url\":\"/policies/policy-consumers\"}}"}}}},
|
|
92
|
+
# {"ip": {"hotspot": {"host": {"mac": "...", "permit": true, "policy": "..."}}}},
|
|
93
|
+
# {"system": {"configuration": {"save": {}}}}
|
|
94
|
+
# ]
|
|
95
|
+
#
|
|
96
|
+
# @param mac [String] Client MAC address (required)
|
|
97
|
+
# @param policy [String, nil] Policy name (e.g., "Policy0"), or nil to remove policy
|
|
98
|
+
# @param permit [Boolean] Whether to permit the host (default: true)
|
|
99
|
+
# @return [Array<Hash>] API response
|
|
100
|
+
#
|
|
101
|
+
# @example Assign policy to host
|
|
102
|
+
# client.hotspot.set_host_policy(mac: "AA:BB:CC:DD:EE:FF", policy: "Policy0")
|
|
103
|
+
#
|
|
104
|
+
# @example Remove policy from host
|
|
105
|
+
# client.hotspot.set_host_policy(mac: "AA:BB:CC:DD:EE:FF", policy: nil)
|
|
106
|
+
#
|
|
107
|
+
# @example Assign policy with deny access
|
|
108
|
+
# client.hotspot.set_host_policy(mac: "AA:BB:CC:DD:EE:FF", policy: "Policy0", permit: false)
|
|
109
|
+
#
|
|
110
|
+
def set_host_policy(mac:, policy:, permit: true)
|
|
111
|
+
raise ArgumentError, 'MAC address is required' if mac.nil? || mac.to_s.strip.empty?
|
|
112
|
+
|
|
113
|
+
normalized_mac = mac.downcase
|
|
114
|
+
|
|
115
|
+
host_params = {
|
|
116
|
+
'mac' => normalized_mac,
|
|
117
|
+
'permit' => permit
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
if policy.nil? || policy.to_s.strip.empty?
|
|
121
|
+
# Remove policy assignment
|
|
122
|
+
host_params['policy'] = { 'no' => true }
|
|
123
|
+
else
|
|
124
|
+
host_params['policy'] = policy
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
commands = [
|
|
128
|
+
webhelp_event,
|
|
129
|
+
{ 'ip' => { 'hotspot' => { 'host' => host_params } } },
|
|
130
|
+
save_config_command
|
|
131
|
+
]
|
|
132
|
+
|
|
133
|
+
client.batch(commands)
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
# Find a specific policy by ID.
|
|
137
|
+
#
|
|
138
|
+
# @param id [String] Policy ID (e.g., "Policy0")
|
|
139
|
+
# @return [Hash, nil] Policy data or nil if not found
|
|
140
|
+
# @example
|
|
141
|
+
# policy = client.hotspot.find_policy(id: "Policy0")
|
|
142
|
+
# # => { id: "Policy0", description: "VPN Policy", ... }
|
|
143
|
+
#
|
|
144
|
+
def find_policy(id:)
|
|
145
|
+
policies.find { |p| p[:id] == id }
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
# Find a specific host by MAC address.
|
|
149
|
+
#
|
|
150
|
+
# @param mac [String] MAC address (case-insensitive)
|
|
151
|
+
# @return [Hash, nil] Host data or nil if not found
|
|
152
|
+
# @example
|
|
153
|
+
# host = client.hotspot.find_host(mac: "AA:BB:CC:DD:EE:FF")
|
|
154
|
+
# # => { mac: "AA:BB:CC:DD:EE:FF", name: "My Device", policy: "Policy0", ... }
|
|
155
|
+
#
|
|
156
|
+
def find_host(mac:)
|
|
157
|
+
hosts.find { |h| h[:mac]&.downcase == mac.downcase }
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
private
|
|
161
|
+
|
|
162
|
+
def extract_policies_from_response(response)
|
|
163
|
+
return [] unless response.is_a?(Array) && response.first.is_a?(Hash)
|
|
164
|
+
|
|
165
|
+
response.dig(0, 'show', 'sc', 'ip', 'policy') || []
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
def extract_config_hosts(response)
|
|
169
|
+
return [] unless response.is_a?(Array) && response[0].is_a?(Hash)
|
|
170
|
+
|
|
171
|
+
hosts = response.dig(0, 'show', 'sc', 'ip', 'hotspot', 'host') || []
|
|
172
|
+
hosts.is_a?(Array) ? hosts : [hosts]
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
def extract_runtime_hosts(response)
|
|
176
|
+
return [] unless response.is_a?(Array) && response[1].is_a?(Hash)
|
|
177
|
+
|
|
178
|
+
hosts = response.dig(1, 'show', 'ip', 'hotspot', 'host') || []
|
|
179
|
+
hosts.is_a?(Array) ? hosts : [hosts]
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
def normalize_policies(policies_data)
|
|
183
|
+
return [] unless policies_data.is_a?(Array)
|
|
184
|
+
|
|
185
|
+
policies_data.map { |policy| normalize_policy(policy) }.compact
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
def normalize_policy(data)
|
|
189
|
+
return nil unless data.is_a?(Hash)
|
|
190
|
+
|
|
191
|
+
interfaces = data['interface'] || []
|
|
192
|
+
interfaces = [interfaces] unless interfaces.is_a?(Array)
|
|
193
|
+
|
|
194
|
+
{
|
|
195
|
+
id: data['id'],
|
|
196
|
+
description: data['description'],
|
|
197
|
+
global: normalize_boolean(data['global']),
|
|
198
|
+
interfaces: interfaces.map { |iface| normalize_policy_interface(iface) }.compact
|
|
199
|
+
}
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
def normalize_policy_interface(data)
|
|
203
|
+
return nil unless data.is_a?(Hash)
|
|
204
|
+
|
|
205
|
+
{
|
|
206
|
+
name: data['name'],
|
|
207
|
+
priority: data['priority']
|
|
208
|
+
}
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
def merge_hosts(config_hosts, runtime_hosts)
|
|
212
|
+
# Create lookup from runtime hosts by MAC
|
|
213
|
+
runtime_by_mac = runtime_hosts.each_with_object({}) do |host, lookup|
|
|
214
|
+
next unless host.is_a?(Hash) && host['mac']
|
|
215
|
+
|
|
216
|
+
lookup[host['mac'].upcase] = host
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
# Process config hosts and merge with runtime data
|
|
220
|
+
all_hosts = config_hosts.map do |config_host|
|
|
221
|
+
next unless config_host.is_a?(Hash) && config_host['mac']
|
|
222
|
+
|
|
223
|
+
mac = config_host['mac'].upcase
|
|
224
|
+
runtime_host = runtime_by_mac.delete(mac) || {}
|
|
225
|
+
normalize_host(config_host, runtime_host)
|
|
226
|
+
end.compact
|
|
227
|
+
|
|
228
|
+
# Add any remaining runtime-only hosts
|
|
229
|
+
runtime_by_mac.values.each do |runtime_host|
|
|
230
|
+
all_hosts << normalize_host({}, runtime_host)
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
all_hosts
|
|
234
|
+
end
|
|
235
|
+
|
|
236
|
+
def normalize_host(config_data, runtime_data)
|
|
237
|
+
config_data ||= {}
|
|
238
|
+
runtime_data ||= {}
|
|
239
|
+
|
|
240
|
+
mac = config_data['mac'] || runtime_data['mac']
|
|
241
|
+
return nil unless mac
|
|
242
|
+
|
|
243
|
+
{
|
|
244
|
+
mac: mac.upcase,
|
|
245
|
+
name: config_data['name'] || runtime_data['name'] || runtime_data['hostname'],
|
|
246
|
+
hostname: runtime_data['hostname'],
|
|
247
|
+
ip: runtime_data['ip'],
|
|
248
|
+
interface: runtime_data['interface'],
|
|
249
|
+
via: runtime_data['via'],
|
|
250
|
+
policy: config_data['policy'],
|
|
251
|
+
permit: normalize_boolean(config_data['permit']),
|
|
252
|
+
deny: normalize_boolean(config_data['deny']),
|
|
253
|
+
schedule: config_data['schedule'],
|
|
254
|
+
active: normalize_boolean(runtime_data['active']),
|
|
255
|
+
registered: normalize_boolean(runtime_data['registered']),
|
|
256
|
+
access: runtime_data['access'],
|
|
257
|
+
rxbytes: runtime_data['rxbytes'],
|
|
258
|
+
txbytes: runtime_data['txbytes'],
|
|
259
|
+
uptime: runtime_data['uptime'],
|
|
260
|
+
first_seen: runtime_data['first-seen'],
|
|
261
|
+
last_seen: runtime_data['last-seen']
|
|
262
|
+
}
|
|
263
|
+
end
|
|
264
|
+
|
|
265
|
+
def webhelp_event
|
|
266
|
+
{
|
|
267
|
+
'webhelp' => {
|
|
268
|
+
'event' => {
|
|
269
|
+
'push' => {
|
|
270
|
+
'data' => '{"type":"configuration_change","value":{"url":"/policies/policy-consumers"}}'
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
end
|
|
276
|
+
|
|
277
|
+
def save_config_command
|
|
278
|
+
{ 'system' => { 'configuration' => { 'save' => {} } } }
|
|
279
|
+
end
|
|
280
|
+
end
|
|
281
|
+
end
|
|
282
|
+
end
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
module Keenetic
|
|
2
|
+
module Resources
|
|
3
|
+
# IPv6 resource for IPv6 network information.
|
|
4
|
+
#
|
|
5
|
+
# == API Endpoints Used
|
|
6
|
+
#
|
|
7
|
+
# === IPv6 Interfaces
|
|
8
|
+
# GET /rci/show/ipv6/interface
|
|
9
|
+
#
|
|
10
|
+
# === IPv6 Routes
|
|
11
|
+
# GET /rci/show/ipv6/route
|
|
12
|
+
#
|
|
13
|
+
# === IPv6 Neighbors
|
|
14
|
+
# GET /rci/show/ipv6/neighbor
|
|
15
|
+
#
|
|
16
|
+
class Ipv6 < Base
|
|
17
|
+
# Get IPv6 interfaces.
|
|
18
|
+
#
|
|
19
|
+
# @return [Array<Hash>] List of IPv6 interfaces
|
|
20
|
+
# @example
|
|
21
|
+
# interfaces = client.ipv6.interfaces
|
|
22
|
+
#
|
|
23
|
+
def interfaces
|
|
24
|
+
response = get('/rci/show/ipv6/interface')
|
|
25
|
+
normalize_list(response, 'interface')
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# Get IPv6 routing table.
|
|
29
|
+
#
|
|
30
|
+
# @return [Array<Hash>] List of IPv6 routes
|
|
31
|
+
# @example
|
|
32
|
+
# routes = client.ipv6.routes
|
|
33
|
+
#
|
|
34
|
+
def routes
|
|
35
|
+
response = get('/rci/show/ipv6/route')
|
|
36
|
+
normalize_list(response, 'route')
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# Get IPv6 neighbor table (NDP cache).
|
|
40
|
+
#
|
|
41
|
+
# @return [Array<Hash>] List of IPv6 neighbors
|
|
42
|
+
# @example
|
|
43
|
+
# neighbors = client.ipv6.neighbors
|
|
44
|
+
#
|
|
45
|
+
def neighbors
|
|
46
|
+
response = get('/rci/show/ipv6/neighbor')
|
|
47
|
+
normalize_list(response, 'neighbor')
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
private
|
|
51
|
+
|
|
52
|
+
def normalize_list(response, key)
|
|
53
|
+
data = case response
|
|
54
|
+
when Array
|
|
55
|
+
response
|
|
56
|
+
when Hash
|
|
57
|
+
response[key] || response["#{key}s"] || []
|
|
58
|
+
else
|
|
59
|
+
[]
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
return [] unless data.is_a?(Array)
|
|
63
|
+
|
|
64
|
+
data.map { |item| normalize_item(item) }.compact
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def normalize_item(data)
|
|
68
|
+
return nil unless data.is_a?(Hash)
|
|
69
|
+
|
|
70
|
+
deep_normalize_keys(data)
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
module Keenetic
|
|
2
|
+
module Resources
|
|
3
|
+
# Mesh resource for monitoring mesh Wi-Fi system status.
|
|
4
|
+
#
|
|
5
|
+
# == API Endpoints Used
|
|
6
|
+
#
|
|
7
|
+
# === Reading Mesh Status
|
|
8
|
+
# GET /rci/show/mws
|
|
9
|
+
# Returns: Mesh Wi-Fi system status and configuration
|
|
10
|
+
#
|
|
11
|
+
# === Reading Mesh Members
|
|
12
|
+
# GET /rci/show/mws/member
|
|
13
|
+
# Returns: Connected mesh nodes/extenders
|
|
14
|
+
#
|
|
15
|
+
class Mesh < Base
|
|
16
|
+
# Get mesh Wi-Fi system status.
|
|
17
|
+
#
|
|
18
|
+
# == Keenetic API Request
|
|
19
|
+
# GET /rci/show/mws
|
|
20
|
+
#
|
|
21
|
+
# @return [Hash] Mesh system status and configuration
|
|
22
|
+
# @example
|
|
23
|
+
# status = client.mesh.status
|
|
24
|
+
# # => { enabled: true, role: "controller", members_count: 2, ... }
|
|
25
|
+
#
|
|
26
|
+
def status
|
|
27
|
+
response = get('/rci/show/mws')
|
|
28
|
+
normalize_status(response)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# Get connected mesh members (nodes/extenders).
|
|
32
|
+
#
|
|
33
|
+
# == Keenetic API Request
|
|
34
|
+
# GET /rci/show/mws/member
|
|
35
|
+
#
|
|
36
|
+
# @return [Array<Hash>] List of mesh members
|
|
37
|
+
# @example
|
|
38
|
+
# members = client.mesh.members
|
|
39
|
+
# # => [
|
|
40
|
+
# # { mac: "AA:BB:CC:DD:EE:FF", name: "Living Room", mode: "extender", ... },
|
|
41
|
+
# # ...
|
|
42
|
+
# # ]
|
|
43
|
+
#
|
|
44
|
+
def members
|
|
45
|
+
response = get('/rci/show/mws/member')
|
|
46
|
+
normalize_members(response)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
private
|
|
50
|
+
|
|
51
|
+
def normalize_status(response)
|
|
52
|
+
return {} unless response.is_a?(Hash)
|
|
53
|
+
|
|
54
|
+
result = deep_normalize_keys(response)
|
|
55
|
+
normalize_booleans(result, %i[enabled active])
|
|
56
|
+
result
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def normalize_members(response)
|
|
60
|
+
# Response might be array directly or wrapped in an object
|
|
61
|
+
members_data = case response
|
|
62
|
+
when Array
|
|
63
|
+
response
|
|
64
|
+
when Hash
|
|
65
|
+
response['member'] || response['members'] || []
|
|
66
|
+
else
|
|
67
|
+
[]
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
return [] unless members_data.is_a?(Array)
|
|
71
|
+
|
|
72
|
+
members_data.map { |member| normalize_member(member) }.compact
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def normalize_member(data)
|
|
76
|
+
return nil unless data.is_a?(Hash)
|
|
77
|
+
|
|
78
|
+
result = deep_normalize_keys(data)
|
|
79
|
+
normalize_booleans(result, %i[online active connected])
|
|
80
|
+
result
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|