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.
@@ -138,8 +138,142 @@ module Keenetic
138
138
  normalize_license(response)
139
139
  end
140
140
 
141
+ # Reboot the router.
142
+ #
143
+ # == Keenetic API Request
144
+ # POST /rci/system/reboot
145
+ # Body: {}
146
+ #
147
+ # == Warning
148
+ # This will immediately restart the router. All active connections
149
+ # will be dropped and the router will be unavailable for 1-2 minutes.
150
+ #
151
+ # @return [Hash, nil] API response
152
+ # @example
153
+ # client.system.reboot
154
+ #
155
+ def reboot
156
+ post('/rci/system/reboot', {})
157
+ end
158
+
159
+ # Factory reset the router.
160
+ #
161
+ # == Keenetic API Request
162
+ # POST /rci/system/default
163
+ # Body: {}
164
+ #
165
+ # == Warning
166
+ # This will erase ALL configuration and restore factory defaults.
167
+ # The router will reboot and you will lose access until you
168
+ # reconfigure it. Use with extreme caution!
169
+ #
170
+ # @return [Hash, nil] API response
171
+ # @example
172
+ # client.system.factory_reset
173
+ #
174
+ def factory_reset
175
+ post('/rci/system/default', {})
176
+ end
177
+
178
+ # Check for available firmware updates.
179
+ #
180
+ # == Keenetic API Request
181
+ # GET /rci/show/system/update
182
+ #
183
+ # == Response Fields
184
+ # - available: Whether an update is available
185
+ # - version: Available firmware version
186
+ # - current: Current firmware version
187
+ # - channel: Update channel (stable, preview)
188
+ #
189
+ # @return [Hash] Update availability information
190
+ # @example
191
+ # update_info = client.system.check_updates
192
+ # # => { available: true, version: "4.2.0", current: "4.1.0", ... }
193
+ #
194
+ def check_updates
195
+ response = get('/rci/show/system/update')
196
+ normalize_update_info(response)
197
+ end
198
+
199
+ # Apply available firmware update.
200
+ #
201
+ # == Keenetic API Request
202
+ # POST /rci/system/update
203
+ # Body: {}
204
+ #
205
+ # == Warning
206
+ # This will download and install the latest firmware update.
207
+ # The router will reboot automatically after the update is applied.
208
+ # Do not power off the router during the update process!
209
+ #
210
+ # @return [Hash, nil] API response
211
+ # @example
212
+ # client.system.apply_update
213
+ #
214
+ def apply_update
215
+ post('/rci/system/update', {})
216
+ end
217
+
218
+ # Control LED mode on the router.
219
+ #
220
+ # == Keenetic API Request
221
+ # POST /rci/system/led
222
+ # Body: { "mode": "on" | "off" | "auto" }
223
+ #
224
+ # @param mode [String] LED mode: "on", "off", or "auto"
225
+ # @return [Hash, nil] API response
226
+ # @raise [ArgumentError] if mode is invalid
227
+ #
228
+ # @example Turn off LEDs
229
+ # client.system.set_led_mode('off')
230
+ #
231
+ # @example Set to automatic
232
+ # client.system.set_led_mode('auto')
233
+ #
234
+ def set_led_mode(mode)
235
+ valid_modes = %w[on off auto]
236
+ unless valid_modes.include?(mode)
237
+ raise ArgumentError, "Invalid LED mode: #{mode}. Valid modes: #{valid_modes.join(', ')}"
238
+ end
239
+
240
+ post('/rci/system/led', { 'mode' => mode })
241
+ end
242
+
243
+ # Get button configuration.
244
+ #
245
+ # == Keenetic API Request
246
+ # GET /rci/show/button
247
+ #
248
+ # Returns information about physical buttons on the router
249
+ # and their configured actions.
250
+ #
251
+ # @return [Hash] Button configuration
252
+ # @example
253
+ # buttons = client.system.button_config
254
+ # # => { wifi: { action: "toggle" }, fn: { action: "wps" } }
255
+ #
256
+ def button_config
257
+ response = get('/rci/show/button')
258
+ normalize_button_config(response)
259
+ end
260
+
141
261
  private
142
262
 
263
+ def normalize_update_info(response)
264
+ return {} unless response.is_a?(Hash)
265
+
266
+ result = deep_normalize_keys(response)
267
+ normalize_booleans(result, %i[available downloading])
268
+ result
269
+ end
270
+
271
+ def normalize_button_config(response)
272
+ return {} unless response.is_a?(Hash)
273
+
274
+ deep_normalize_keys(response)
275
+ end
276
+
143
277
  def normalize_resources(response)
144
278
  return {} unless response.is_a?(Hash)
145
279
 
@@ -0,0 +1,135 @@
1
+ module Keenetic
2
+ module Resources
3
+ # USB resource for managing USB devices and storage.
4
+ #
5
+ # == API Endpoints Used
6
+ #
7
+ # === Reading USB Devices
8
+ # GET /rci/show/usb
9
+ # Returns: Connected USB devices
10
+ #
11
+ # === Reading Storage/Media
12
+ # GET /rci/show/media
13
+ # Returns: Mounted storage partitions
14
+ #
15
+ # === Safely Eject USB
16
+ # POST /rci/usb/eject
17
+ # Body: { port }
18
+ #
19
+ class Usb < Base
20
+ # Get connected USB devices.
21
+ #
22
+ # == Keenetic API Request
23
+ # GET /rci/show/usb
24
+ #
25
+ # == Response Fields (per device)
26
+ # - port: USB port number
27
+ # - manufacturer: Device manufacturer
28
+ # - product: Product name
29
+ # - serial: Serial number
30
+ # - class: USB device class
31
+ # - speed: USB speed
32
+ # - connected: Currently connected
33
+ #
34
+ # @return [Array<Hash>] List of USB devices
35
+ # @example
36
+ # devices = client.usb.devices
37
+ # # => [{ port: 1, manufacturer: "SanDisk", product: "USB Flash", ... }]
38
+ #
39
+ def devices
40
+ response = get('/rci/show/usb')
41
+ normalize_devices(response)
42
+ end
43
+
44
+ # Get mounted storage partitions.
45
+ #
46
+ # == Keenetic API Request
47
+ # GET /rci/show/media
48
+ #
49
+ # == Response Fields (per partition)
50
+ # - name: Device name
51
+ # - label: Volume label
52
+ # - uuid: Volume UUID
53
+ # - fs: Filesystem type
54
+ # - mountpoint: Mount path
55
+ # - total: Total bytes
56
+ # - used: Used bytes
57
+ # - free: Free bytes
58
+ #
59
+ # @return [Array<Hash>] List of storage partitions
60
+ # @example
61
+ # media = client.usb.media
62
+ # # => [{ name: "sda1", label: "USB_DRIVE", fs: "ext4", total: 32000000000, ... }]
63
+ #
64
+ def media
65
+ response = get('/rci/show/media')
66
+ normalize_media(response)
67
+ end
68
+
69
+ # Alias for media
70
+ alias storage media
71
+
72
+ # Safely eject a USB device.
73
+ #
74
+ # == Keenetic API Request
75
+ # POST /rci/usb/eject
76
+ # Body: { "port": 1 }
77
+ #
78
+ # @param port [Integer] USB port number to eject
79
+ # @return [Hash, nil] API response
80
+ #
81
+ # @example
82
+ # client.usb.eject(port: 1)
83
+ #
84
+ def eject(port:)
85
+ post('/rci/usb/eject', { 'port' => port })
86
+ end
87
+
88
+ private
89
+
90
+ def normalize_devices(response)
91
+ devices_data = case response
92
+ when Array
93
+ response
94
+ when Hash
95
+ response['device'] || response['devices'] || []
96
+ else
97
+ []
98
+ end
99
+
100
+ return [] unless devices_data.is_a?(Array)
101
+
102
+ devices_data.map { |device| normalize_device(device) }.compact
103
+ end
104
+
105
+ def normalize_device(data)
106
+ return nil unless data.is_a?(Hash)
107
+
108
+ result = deep_normalize_keys(data)
109
+ normalize_booleans(result, %i[connected])
110
+ result
111
+ end
112
+
113
+ def normalize_media(response)
114
+ media_data = case response
115
+ when Array
116
+ response
117
+ when Hash
118
+ response['media'] || response['partition'] || []
119
+ else
120
+ []
121
+ end
122
+
123
+ return [] unless media_data.is_a?(Array)
124
+
125
+ media_data.map { |partition| normalize_partition(partition) }.compact
126
+ end
127
+
128
+ def normalize_partition(data)
129
+ return nil unless data.is_a?(Hash)
130
+
131
+ deep_normalize_keys(data)
132
+ end
133
+ end
134
+ end
135
+ end
@@ -0,0 +1,92 @@
1
+ module Keenetic
2
+ module Resources
3
+ # Users resource for managing router user accounts.
4
+ #
5
+ # == API Endpoints Used
6
+ #
7
+ # === List Users
8
+ # GET /rci/show/user
9
+ #
10
+ # === Create User
11
+ # POST /rci/user
12
+ #
13
+ # === Delete User
14
+ # POST /rci/user with { "name": "...", "no": true }
15
+ #
16
+ # == User Tags (permissions)
17
+ # - http: Web interface access
18
+ # - cli: CLI/Telnet access
19
+ # - cifs: File sharing access
20
+ # - ftp: FTP access
21
+ # - vpn: VPN access
22
+ #
23
+ class Users < Base
24
+ # List all users.
25
+ #
26
+ # @return [Array<Hash>] List of users
27
+ # @example
28
+ # users = client.users.all
29
+ #
30
+ def all
31
+ response = get('/rci/show/user')
32
+ normalize_users(response)
33
+ end
34
+
35
+ # Find a user by name.
36
+ #
37
+ # @param name [String] User name
38
+ # @return [Hash, nil] User data or nil
39
+ #
40
+ def find(name)
41
+ all.find { |u| u[:name] == name }
42
+ end
43
+
44
+ # Create a new user.
45
+ #
46
+ # @param name [String] User name
47
+ # @param password [String] User password
48
+ # @param tag [Array<String>] Permission tags (http, cli, cifs, ftp, vpn)
49
+ # @return [Hash, nil] API response
50
+ # @example
51
+ # client.users.create(name: 'guest', password: 'guestpass', tag: ['http', 'cifs'])
52
+ #
53
+ def create(name:, password:, tag: [])
54
+ params = { 'name' => name, 'password' => password }
55
+ params['tag'] = tag unless tag.empty?
56
+ post('/rci/user', params)
57
+ end
58
+
59
+ # Delete a user.
60
+ #
61
+ # @param name [String] User name
62
+ # @return [Hash, nil] API response
63
+ #
64
+ def delete(name:)
65
+ post('/rci/user', { 'name' => name, 'no' => true })
66
+ end
67
+
68
+ private
69
+
70
+ def normalize_users(response)
71
+ users_data = case response
72
+ when Array
73
+ response
74
+ when Hash
75
+ response['user'] || response['users'] || []
76
+ else
77
+ []
78
+ end
79
+
80
+ return [] unless users_data.is_a?(Array)
81
+
82
+ users_data.map { |user| normalize_user(user) }.compact
83
+ end
84
+
85
+ def normalize_user(data)
86
+ return nil unless data.is_a?(Hash)
87
+
88
+ deep_normalize_keys(data)
89
+ end
90
+ end
91
+ end
92
+ end
@@ -0,0 +1,153 @@
1
+ module Keenetic
2
+ module Resources
3
+ # VPN resource for managing VPN server and monitoring connections.
4
+ #
5
+ # == API Endpoints Used
6
+ #
7
+ # === Reading VPN Server Status
8
+ # GET /rci/show/vpn-server
9
+ # Returns: VPN server configuration and status
10
+ #
11
+ # === Reading VPN Clients
12
+ # GET /rci/show/vpn-server/clients
13
+ # Returns: Array of connected VPN clients
14
+ #
15
+ # === Reading IPsec Status
16
+ # GET /rci/show/crypto/ipsec/sa
17
+ # Returns: IPsec security associations
18
+ #
19
+ # === Configuring VPN Server
20
+ # POST /rci/vpn-server
21
+ # Body: { type, enabled, pool-start, pool-end, ... }
22
+ #
23
+ class Vpn < Base
24
+ # Get VPN server status and configuration.
25
+ #
26
+ # == Keenetic API Request
27
+ # GET /rci/show/vpn-server
28
+ #
29
+ # == Response Fields
30
+ # - type: VPN server type (pptp, l2tp, sstp, etc.)
31
+ # - enabled: Whether server is enabled
32
+ # - running: Whether server is currently running
33
+ # - pool_start: Start of IP pool for clients
34
+ # - pool_end: End of IP pool for clients
35
+ # - interface: VPN interface name
36
+ #
37
+ # @return [Hash] VPN server status
38
+ # @example
39
+ # status = client.vpn.status
40
+ # # => { type: "l2tp", enabled: true, running: true, ... }
41
+ #
42
+ def status
43
+ response = get('/rci/show/vpn-server')
44
+ normalize_status(response)
45
+ end
46
+
47
+ # Get connected VPN clients.
48
+ #
49
+ # == Keenetic API Request
50
+ # GET /rci/show/vpn-server/clients
51
+ #
52
+ # == Response Fields (per client)
53
+ # - name: Client username
54
+ # - ip: Assigned IP address
55
+ # - uptime: Connection duration in seconds
56
+ # - rxbytes: Bytes received
57
+ # - txbytes: Bytes transmitted
58
+ #
59
+ # @return [Array<Hash>] List of connected VPN clients
60
+ # @example
61
+ # clients = client.vpn.clients
62
+ # # => [{ name: "user1", ip: "192.168.1.200", uptime: 3600, ... }]
63
+ #
64
+ def clients
65
+ response = get('/rci/show/vpn-server/clients')
66
+ normalize_clients(response)
67
+ end
68
+
69
+ # Get IPsec security associations status.
70
+ #
71
+ # == Keenetic API Request
72
+ # GET /rci/show/crypto/ipsec/sa
73
+ #
74
+ # == Response Fields
75
+ # - established: Number of established SAs
76
+ # - sa: Array of security associations
77
+ #
78
+ # @return [Hash] IPsec status with security associations
79
+ # @example
80
+ # ipsec = client.vpn.ipsec_status
81
+ # # => { established: 2, sa: [...] }
82
+ #
83
+ def ipsec_status
84
+ response = get('/rci/show/crypto/ipsec/sa')
85
+ normalize_ipsec(response)
86
+ end
87
+
88
+ # Configure VPN server.
89
+ #
90
+ # == Keenetic API Request
91
+ # POST /rci/vpn-server
92
+ # Body: { type, enabled, pool-start, pool-end, ... }
93
+ #
94
+ # @param type [String] VPN type: "pptp", "l2tp", "sstp"
95
+ # @param enabled [Boolean] Enable or disable the server
96
+ # @param pool_start [String, nil] Start of client IP pool
97
+ # @param pool_end [String, nil] End of client IP pool
98
+ # @param mppe [String, nil] MPPE encryption: "require", "prefer", "none"
99
+ # @return [Hash, Array, nil] API response
100
+ #
101
+ # @example Enable L2TP server
102
+ # client.vpn.configure(
103
+ # type: 'l2tp',
104
+ # enabled: true,
105
+ # pool_start: '192.168.1.200',
106
+ # pool_end: '192.168.1.210'
107
+ # )
108
+ #
109
+ # @example Disable VPN server
110
+ # client.vpn.configure(type: 'pptp', enabled: false)
111
+ #
112
+ def configure(type:, enabled:, pool_start: nil, pool_end: nil, mppe: nil)
113
+ params = {
114
+ 'type' => type,
115
+ 'enabled' => enabled
116
+ }
117
+ params['pool-start'] = pool_start if pool_start
118
+ params['pool-end'] = pool_end if pool_end
119
+ params['mppe'] = mppe if mppe
120
+
121
+ post('/rci/vpn-server', params)
122
+ end
123
+
124
+ private
125
+
126
+ def normalize_status(response)
127
+ return {} unless response.is_a?(Hash)
128
+
129
+ result = deep_normalize_keys(response)
130
+ normalize_booleans(result, %i[enabled running])
131
+ result
132
+ end
133
+
134
+ def normalize_clients(response)
135
+ return [] unless response.is_a?(Array)
136
+
137
+ response.map { |client_data| normalize_client(client_data) }.compact
138
+ end
139
+
140
+ def normalize_client(data)
141
+ return nil unless data.is_a?(Hash)
142
+
143
+ deep_normalize_keys(data)
144
+ end
145
+
146
+ def normalize_ipsec(response)
147
+ return {} unless response.is_a?(Hash)
148
+
149
+ deep_normalize_keys(response)
150
+ end
151
+ end
152
+ end
153
+ end
@@ -1,3 +1,3 @@
1
1
  module Keenetic
2
- VERSION = '0.2.0'.freeze
2
+ VERSION = '1.0.0'.freeze
3
3
  end
data/lib/keenetic.rb CHANGED
@@ -16,6 +16,19 @@ require_relative 'keenetic/resources/logs'
16
16
  require_relative 'keenetic/resources/routes'
17
17
  require_relative 'keenetic/resources/hotspot'
18
18
  require_relative 'keenetic/resources/config'
19
+ require_relative 'keenetic/resources/nat'
20
+ require_relative 'keenetic/resources/vpn'
21
+ require_relative 'keenetic/resources/diagnostics'
22
+ require_relative 'keenetic/resources/firewall'
23
+ require_relative 'keenetic/resources/mesh'
24
+ require_relative 'keenetic/resources/usb'
25
+ require_relative 'keenetic/resources/dns'
26
+ require_relative 'keenetic/resources/dyndns'
27
+ require_relative 'keenetic/resources/schedule'
28
+ require_relative 'keenetic/resources/users'
29
+ require_relative 'keenetic/resources/components'
30
+ require_relative 'keenetic/resources/qos'
31
+ require_relative 'keenetic/resources/ipv6'
19
32
 
20
33
  # Keenetic Router API Client
21
34
  #
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: keenetic
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 1.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Anton Zaytsev
@@ -52,18 +52,31 @@ files:
52
52
  - lib/keenetic/configuration.rb
53
53
  - lib/keenetic/errors.rb
54
54
  - lib/keenetic/resources/base.rb
55
+ - lib/keenetic/resources/components.rb
55
56
  - lib/keenetic/resources/config.rb
56
57
  - lib/keenetic/resources/devices.rb
57
58
  - lib/keenetic/resources/dhcp.rb
59
+ - lib/keenetic/resources/diagnostics.rb
60
+ - lib/keenetic/resources/dns.rb
61
+ - lib/keenetic/resources/dyndns.rb
62
+ - lib/keenetic/resources/firewall.rb
58
63
  - lib/keenetic/resources/hotspot.rb
59
64
  - lib/keenetic/resources/internet.rb
65
+ - lib/keenetic/resources/ipv6.rb
60
66
  - lib/keenetic/resources/logs.rb
67
+ - lib/keenetic/resources/mesh.rb
68
+ - lib/keenetic/resources/nat.rb
61
69
  - lib/keenetic/resources/network.rb
62
70
  - lib/keenetic/resources/policies.rb
63
71
  - lib/keenetic/resources/ports.rb
72
+ - lib/keenetic/resources/qos.rb
64
73
  - lib/keenetic/resources/routes.rb
65
74
  - lib/keenetic/resources/routing.rb
75
+ - lib/keenetic/resources/schedule.rb
66
76
  - lib/keenetic/resources/system.rb
77
+ - lib/keenetic/resources/usb.rb
78
+ - lib/keenetic/resources/users.rb
79
+ - lib/keenetic/resources/vpn.rb
67
80
  - lib/keenetic/resources/wifi.rb
68
81
  - lib/keenetic/version.rb
69
82
  homepage: https://github.com/antonzaytsev/keenetic-ruby