keenetic 0.2.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/README.md +25 -51
- data/lib/keenetic/client.rb +82 -1
- data/lib/keenetic/resources/base.rb +4 -0
- data/lib/keenetic/resources/components.rb +88 -0
- data/lib/keenetic/resources/config.rb +37 -1
- 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/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/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 -1
- data/lib/keenetic.rb +13 -0
- metadata +14 -1
|
@@ -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,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
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
module Keenetic
|
|
2
|
+
module Resources
|
|
3
|
+
# NAT resource for managing port forwarding rules.
|
|
4
|
+
#
|
|
5
|
+
# == API Endpoints Used
|
|
6
|
+
#
|
|
7
|
+
# === Reading NAT Rules
|
|
8
|
+
# GET /rci/show/ip/nat
|
|
9
|
+
# Returns: Array of port forwarding rules
|
|
10
|
+
#
|
|
11
|
+
# === Adding Port Forward
|
|
12
|
+
# POST /rci/ip/nat
|
|
13
|
+
# Body: { index, description, protocol, interface, port, to-host, to-port, enabled }
|
|
14
|
+
#
|
|
15
|
+
# === Deleting Port Forward
|
|
16
|
+
# POST /rci/ip/nat
|
|
17
|
+
# Body: { index, no: true }
|
|
18
|
+
#
|
|
19
|
+
# === Reading UPnP Mappings
|
|
20
|
+
# GET /rci/show/upnp/redirect
|
|
21
|
+
# Returns: Array of automatic UPnP port mappings
|
|
22
|
+
#
|
|
23
|
+
class Nat < Base
|
|
24
|
+
# Get all NAT/port forwarding rules.
|
|
25
|
+
#
|
|
26
|
+
# == Keenetic API Request
|
|
27
|
+
# GET /rci/show/ip/nat
|
|
28
|
+
#
|
|
29
|
+
# == Response Fields
|
|
30
|
+
# - index: Rule index/priority
|
|
31
|
+
# - description: Rule description
|
|
32
|
+
# - protocol: "tcp", "udp", or "any"
|
|
33
|
+
# - interface: WAN interface
|
|
34
|
+
# - port: External port
|
|
35
|
+
# - end_port: End of port range (optional)
|
|
36
|
+
# - to_host: Internal host IP
|
|
37
|
+
# - to_port: Internal port
|
|
38
|
+
# - enabled: Rule is active
|
|
39
|
+
#
|
|
40
|
+
# @return [Array<Hash>] List of normalized NAT rules
|
|
41
|
+
# @example
|
|
42
|
+
# rules = client.nat.rules
|
|
43
|
+
# # => [{ index: 1, description: "Web Server", protocol: "tcp", port: 8080, ... }]
|
|
44
|
+
#
|
|
45
|
+
def rules
|
|
46
|
+
response = get('/rci/show/ip/nat')
|
|
47
|
+
normalize_rules(response)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# Find a NAT rule by index.
|
|
51
|
+
#
|
|
52
|
+
# @param index [Integer] Rule index
|
|
53
|
+
# @return [Hash, nil] Rule data or nil if not found
|
|
54
|
+
# @example
|
|
55
|
+
# rule = client.nat.find_rule(1)
|
|
56
|
+
# # => { index: 1, description: "Web Server", ... }
|
|
57
|
+
#
|
|
58
|
+
def find_rule(index)
|
|
59
|
+
rules.find { |r| r[:index] == index }
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# Add a port forwarding rule.
|
|
63
|
+
#
|
|
64
|
+
# == Keenetic API Request
|
|
65
|
+
# POST /rci/ip/nat
|
|
66
|
+
# Body: { index, description, protocol, interface, port, to-host, to-port, enabled }
|
|
67
|
+
#
|
|
68
|
+
# @param index [Integer] Rule index/priority
|
|
69
|
+
# @param protocol [String] Protocol: "tcp", "udp", or "any"
|
|
70
|
+
# @param port [Integer] External port
|
|
71
|
+
# @param to_host [String] Internal host IP address
|
|
72
|
+
# @param to_port [Integer] Internal port
|
|
73
|
+
# @param interface [String] WAN interface name (default: "ISP")
|
|
74
|
+
# @param description [String, nil] Optional rule description
|
|
75
|
+
# @param end_port [Integer, nil] End of port range (optional)
|
|
76
|
+
# @param enabled [Boolean] Whether rule is active (default: true)
|
|
77
|
+
# @return [Hash, Array, nil] API response
|
|
78
|
+
#
|
|
79
|
+
# @example Add simple port forward
|
|
80
|
+
# client.nat.add_forward(
|
|
81
|
+
# index: 1,
|
|
82
|
+
# protocol: 'tcp',
|
|
83
|
+
# port: 8080,
|
|
84
|
+
# to_host: '192.168.1.100',
|
|
85
|
+
# to_port: 80
|
|
86
|
+
# )
|
|
87
|
+
#
|
|
88
|
+
# @example Add port range forward
|
|
89
|
+
# client.nat.add_forward(
|
|
90
|
+
# index: 2,
|
|
91
|
+
# protocol: 'udp',
|
|
92
|
+
# port: 27015,
|
|
93
|
+
# end_port: 27030,
|
|
94
|
+
# to_host: '192.168.1.50',
|
|
95
|
+
# to_port: 27015,
|
|
96
|
+
# description: 'Game Server'
|
|
97
|
+
# )
|
|
98
|
+
#
|
|
99
|
+
def add_forward(index:, protocol:, port:, to_host:, to_port:, interface: 'ISP',
|
|
100
|
+
description: nil, end_port: nil, enabled: true)
|
|
101
|
+
params = {
|
|
102
|
+
'index' => index,
|
|
103
|
+
'protocol' => protocol,
|
|
104
|
+
'interface' => interface,
|
|
105
|
+
'port' => port,
|
|
106
|
+
'to-host' => to_host,
|
|
107
|
+
'to-port' => to_port,
|
|
108
|
+
'enabled' => enabled
|
|
109
|
+
}
|
|
110
|
+
params['description'] = description if description
|
|
111
|
+
params['end-port'] = end_port if end_port
|
|
112
|
+
|
|
113
|
+
post('/rci/ip/nat', params)
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
# Delete a port forwarding rule.
|
|
117
|
+
#
|
|
118
|
+
# == Keenetic API Request
|
|
119
|
+
# POST /rci/ip/nat
|
|
120
|
+
# Body: { index, no: true }
|
|
121
|
+
#
|
|
122
|
+
# @param index [Integer] Rule index to delete
|
|
123
|
+
# @return [Hash, Array, nil] API response
|
|
124
|
+
#
|
|
125
|
+
# @example
|
|
126
|
+
# client.nat.delete_forward(index: 1)
|
|
127
|
+
#
|
|
128
|
+
def delete_forward(index:)
|
|
129
|
+
post('/rci/ip/nat', { 'index' => index, 'no' => true })
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
# Get all UPnP port mappings.
|
|
133
|
+
#
|
|
134
|
+
# UPnP mappings are automatically created by devices on the network.
|
|
135
|
+
# These are read-only and managed by the devices themselves.
|
|
136
|
+
#
|
|
137
|
+
# == Keenetic API Request
|
|
138
|
+
# GET /rci/show/upnp/redirect
|
|
139
|
+
#
|
|
140
|
+
# == Response Fields
|
|
141
|
+
# - protocol: "tcp" or "udp"
|
|
142
|
+
# - interface: WAN interface
|
|
143
|
+
# - port: External port
|
|
144
|
+
# - to_host: Internal host IP
|
|
145
|
+
# - to_port: Internal port
|
|
146
|
+
# - description: Mapping description (from device)
|
|
147
|
+
#
|
|
148
|
+
# @return [Array<Hash>] List of normalized UPnP mappings
|
|
149
|
+
# @example
|
|
150
|
+
# mappings = client.nat.upnp_mappings
|
|
151
|
+
# # => [{ protocol: "tcp", port: 51234, to_host: "192.168.1.50", ... }]
|
|
152
|
+
#
|
|
153
|
+
def upnp_mappings
|
|
154
|
+
response = get('/rci/show/upnp/redirect')
|
|
155
|
+
normalize_upnp_mappings(response)
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
private
|
|
159
|
+
|
|
160
|
+
def normalize_upnp_mappings(response)
|
|
161
|
+
return [] unless response.is_a?(Array)
|
|
162
|
+
|
|
163
|
+
response.map { |mapping| normalize_upnp_mapping(mapping) }.compact
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
def normalize_upnp_mapping(data)
|
|
167
|
+
return nil unless data.is_a?(Hash)
|
|
168
|
+
|
|
169
|
+
{
|
|
170
|
+
protocol: data['protocol'],
|
|
171
|
+
interface: data['interface'],
|
|
172
|
+
port: data['port'],
|
|
173
|
+
to_host: data['to-host'],
|
|
174
|
+
to_port: data['to-port'],
|
|
175
|
+
description: data['description']
|
|
176
|
+
}
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
def normalize_rules(response)
|
|
180
|
+
return [] unless response.is_a?(Array)
|
|
181
|
+
|
|
182
|
+
response.map { |rule| normalize_rule(rule) }.compact
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
def normalize_rule(data)
|
|
186
|
+
return nil unless data.is_a?(Hash)
|
|
187
|
+
|
|
188
|
+
{
|
|
189
|
+
index: data['index'],
|
|
190
|
+
description: data['description'],
|
|
191
|
+
protocol: data['protocol'],
|
|
192
|
+
interface: data['interface'],
|
|
193
|
+
port: data['port'],
|
|
194
|
+
end_port: data['end-port'],
|
|
195
|
+
to_host: data['to-host'],
|
|
196
|
+
to_port: data['to-port'],
|
|
197
|
+
enabled: normalize_boolean(data['enabled'])
|
|
198
|
+
}
|
|
199
|
+
end
|
|
200
|
+
end
|
|
201
|
+
end
|
|
202
|
+
end
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
module Keenetic
|
|
2
|
+
module Resources
|
|
3
|
+
# QoS resource for Quality of Service and traffic control.
|
|
4
|
+
#
|
|
5
|
+
# == API Endpoints Used
|
|
6
|
+
#
|
|
7
|
+
# === Traffic Shaper Status
|
|
8
|
+
# GET /rci/show/ip/traffic-control
|
|
9
|
+
#
|
|
10
|
+
# === IntelliQoS Settings
|
|
11
|
+
# GET /rci/show/ip/qos
|
|
12
|
+
#
|
|
13
|
+
# === Traffic Statistics by Host
|
|
14
|
+
# GET /rci/show/ip/hotspot/summary
|
|
15
|
+
#
|
|
16
|
+
class Qos < Base
|
|
17
|
+
# Get traffic shaper status.
|
|
18
|
+
#
|
|
19
|
+
# @return [Hash] Traffic shaper configuration and status
|
|
20
|
+
# @example
|
|
21
|
+
# shaper = client.qos.traffic_shaper
|
|
22
|
+
#
|
|
23
|
+
def traffic_shaper
|
|
24
|
+
response = get('/rci/show/ip/traffic-control')
|
|
25
|
+
normalize_response(response)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# Alias for traffic_shaper
|
|
29
|
+
alias shaper traffic_shaper
|
|
30
|
+
|
|
31
|
+
# Get IntelliQoS settings.
|
|
32
|
+
#
|
|
33
|
+
# @return [Hash] IntelliQoS configuration
|
|
34
|
+
# @example
|
|
35
|
+
# qos = client.qos.intelliqos
|
|
36
|
+
#
|
|
37
|
+
def intelliqos
|
|
38
|
+
response = get('/rci/show/ip/qos')
|
|
39
|
+
normalize_response(response)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Alias for intelliqos
|
|
43
|
+
alias settings intelliqos
|
|
44
|
+
|
|
45
|
+
# Get traffic statistics by host.
|
|
46
|
+
#
|
|
47
|
+
# @return [Array<Hash>] Traffic statistics per host
|
|
48
|
+
# @example
|
|
49
|
+
# stats = client.qos.traffic_stats
|
|
50
|
+
#
|
|
51
|
+
def traffic_stats
|
|
52
|
+
response = get('/rci/show/ip/hotspot/summary')
|
|
53
|
+
normalize_stats(response)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# Alias for traffic_stats
|
|
57
|
+
alias host_stats traffic_stats
|
|
58
|
+
|
|
59
|
+
private
|
|
60
|
+
|
|
61
|
+
def normalize_response(response)
|
|
62
|
+
return {} unless response.is_a?(Hash)
|
|
63
|
+
|
|
64
|
+
deep_normalize_keys(response)
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def normalize_stats(response)
|
|
68
|
+
stats_data = case response
|
|
69
|
+
when Array
|
|
70
|
+
response
|
|
71
|
+
when Hash
|
|
72
|
+
response['host'] || response['hosts'] || response['stat'] || []
|
|
73
|
+
else
|
|
74
|
+
[]
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
return [] unless stats_data.is_a?(Array)
|
|
78
|
+
|
|
79
|
+
stats_data.map { |stat| normalize_stat(stat) }.compact
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def normalize_stat(data)
|
|
83
|
+
return nil unless data.is_a?(Hash)
|
|
84
|
+
|
|
85
|
+
deep_normalize_keys(data)
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
end
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
module Keenetic
|
|
2
|
+
module Resources
|
|
3
|
+
# Schedule resource for managing access control schedules.
|
|
4
|
+
#
|
|
5
|
+
# == API Endpoints Used
|
|
6
|
+
#
|
|
7
|
+
# === List Schedules
|
|
8
|
+
# GET /rci/show/schedule
|
|
9
|
+
#
|
|
10
|
+
# === Create Schedule
|
|
11
|
+
# POST /rci/schedule
|
|
12
|
+
#
|
|
13
|
+
# === Delete Schedule
|
|
14
|
+
# POST /rci/schedule with { "name": "...", "no": true }
|
|
15
|
+
#
|
|
16
|
+
class Schedule < Base
|
|
17
|
+
# List all schedules.
|
|
18
|
+
#
|
|
19
|
+
# @return [Array<Hash>] List of schedules
|
|
20
|
+
# @example
|
|
21
|
+
# schedules = client.schedule.all
|
|
22
|
+
#
|
|
23
|
+
def all
|
|
24
|
+
response = get('/rci/show/schedule')
|
|
25
|
+
normalize_schedules(response)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# Find a schedule by name.
|
|
29
|
+
#
|
|
30
|
+
# @param name [String] Schedule name
|
|
31
|
+
# @return [Hash, nil] Schedule data or nil
|
|
32
|
+
#
|
|
33
|
+
def find(name)
|
|
34
|
+
all.find { |s| s[:name] == name }
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# Create a new schedule.
|
|
38
|
+
#
|
|
39
|
+
# @param name [String] Schedule name
|
|
40
|
+
# @param entries [Array<Hash>] Schedule entries with days, start, end, action
|
|
41
|
+
# @return [Hash, nil] API response
|
|
42
|
+
# @example
|
|
43
|
+
# client.schedule.create(
|
|
44
|
+
# name: 'kids_bedtime',
|
|
45
|
+
# entries: [
|
|
46
|
+
# { days: 'mon,tue,wed,thu,fri', start: '22:00', end: '07:00', action: 'deny' }
|
|
47
|
+
# ]
|
|
48
|
+
# )
|
|
49
|
+
#
|
|
50
|
+
def create(name:, entries:)
|
|
51
|
+
post('/rci/schedule', { 'name' => name, 'entries' => entries })
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# Delete a schedule.
|
|
55
|
+
#
|
|
56
|
+
# @param name [String] Schedule name
|
|
57
|
+
# @return [Hash, nil] API response
|
|
58
|
+
#
|
|
59
|
+
def delete(name:)
|
|
60
|
+
post('/rci/schedule', { 'name' => name, 'no' => true })
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
private
|
|
64
|
+
|
|
65
|
+
def normalize_schedules(response)
|
|
66
|
+
schedules_data = case response
|
|
67
|
+
when Array
|
|
68
|
+
response
|
|
69
|
+
when Hash
|
|
70
|
+
response['schedule'] || response['schedules'] || []
|
|
71
|
+
else
|
|
72
|
+
[]
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
return [] unless schedules_data.is_a?(Array)
|
|
76
|
+
|
|
77
|
+
schedules_data.map { |schedule| normalize_schedule(schedule) }.compact
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def normalize_schedule(data)
|
|
81
|
+
return nil unless data.is_a?(Hash)
|
|
82
|
+
|
|
83
|
+
deep_normalize_keys(data)
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|