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.
@@ -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,4 +1,3 @@
1
1
  module Keenetic
2
- VERSION = '0.1.0'.freeze
2
+ VERSION = '1.0.0'.freeze
3
3
  end
4
-
data/lib/keenetic.rb CHANGED
@@ -13,6 +13,22 @@ require_relative 'keenetic/resources/policies'
13
13
  require_relative 'keenetic/resources/dhcp'
14
14
  require_relative 'keenetic/resources/routing'
15
15
  require_relative 'keenetic/resources/logs'
16
+ require_relative 'keenetic/resources/routes'
17
+ require_relative 'keenetic/resources/hotspot'
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'
16
32
 
17
33
  # Keenetic Router API Client
18
34
  #
@@ -57,6 +73,23 @@ require_relative 'keenetic/resources/logs'
57
73
  # # Ports
58
74
  # client.ports.all # Physical port statuses
59
75
  #
76
+ # # Static Routes
77
+ # client.routes.all # All static routes
78
+ # client.routes.add(...) # Add static route
79
+ # client.routes.delete(...) # Delete static route
80
+ #
81
+ # # Hotspot / Policies
82
+ # client.hotspot.policies # All IP policies
83
+ # client.hotspot.hosts # All hosts with policies
84
+ # client.hotspot.set_host_policy(mac: '...', policy: '...')
85
+ #
86
+ # # Configuration
87
+ # client.system_config.save # Save configuration
88
+ # client.system_config.download # Download startup config
89
+ #
90
+ # # Raw RCI Access
91
+ # client.rci({ ... }) # Execute arbitrary RCI commands
92
+ #
60
93
  # == Error Handling
61
94
  #
62
95
  # begin
metadata CHANGED
@@ -1,10 +1,10 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: keenetic
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 1.0.0
5
5
  platform: ruby
6
6
  authors:
7
- - Anton
7
+ - Anton Zaytsev
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
@@ -45,24 +45,41 @@ executables: []
45
45
  extensions: []
46
46
  extra_rdoc_files: []
47
47
  files:
48
+ - LICENSE.txt
48
49
  - README.md
49
50
  - lib/keenetic.rb
50
51
  - lib/keenetic/client.rb
51
52
  - lib/keenetic/configuration.rb
52
53
  - lib/keenetic/errors.rb
53
54
  - lib/keenetic/resources/base.rb
55
+ - lib/keenetic/resources/components.rb
56
+ - lib/keenetic/resources/config.rb
54
57
  - lib/keenetic/resources/devices.rb
55
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
63
+ - lib/keenetic/resources/hotspot.rb
56
64
  - lib/keenetic/resources/internet.rb
65
+ - lib/keenetic/resources/ipv6.rb
57
66
  - lib/keenetic/resources/logs.rb
67
+ - lib/keenetic/resources/mesh.rb
68
+ - lib/keenetic/resources/nat.rb
58
69
  - lib/keenetic/resources/network.rb
59
70
  - lib/keenetic/resources/policies.rb
60
71
  - lib/keenetic/resources/ports.rb
72
+ - lib/keenetic/resources/qos.rb
73
+ - lib/keenetic/resources/routes.rb
61
74
  - lib/keenetic/resources/routing.rb
75
+ - lib/keenetic/resources/schedule.rb
62
76
  - lib/keenetic/resources/system.rb
77
+ - lib/keenetic/resources/usb.rb
78
+ - lib/keenetic/resources/users.rb
79
+ - lib/keenetic/resources/vpn.rb
63
80
  - lib/keenetic/resources/wifi.rb
64
81
  - lib/keenetic/version.rb
65
- homepage: https://github.com/example/keenetic
82
+ homepage: https://github.com/antonzaytsev/keenetic-ruby
66
83
  licenses:
67
84
  - MIT
68
85
  metadata: