enfcli 3.3.4 → 3.4.0.pre.alpha
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/Gemfile.lock +3 -3
- data/lib/enfapi.rb +224 -50
- data/lib/enfcli/commands/captive.rb +485 -0
- data/lib/enfcli/commands/xdns.rb +446 -0
- data/lib/enfcli/version.rb +1 -1
- data/lib/enfcli.rb +11 -1
- data/lib/enfthor.rb +12 -12
- metadata +6 -4
@@ -0,0 +1,485 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
#
|
4
|
+
# Copyright 2018-2019 Xaptum,Inc
|
5
|
+
#
|
6
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
7
|
+
# you may not use this file except in compliance with the License.
|
8
|
+
# You may obtain a copy of the License at
|
9
|
+
#
|
10
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
11
|
+
#
|
12
|
+
# Unless required by applicable law or agreed to in writing, software
|
13
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
14
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
15
|
+
# See the License for the specific language governing permissions and
|
16
|
+
# limitations under the License.
|
17
|
+
#
|
18
|
+
require 'enfthor'
|
19
|
+
require 'enfapi'
|
20
|
+
require 'json'
|
21
|
+
|
22
|
+
module EnfCli
|
23
|
+
module Cmd
|
24
|
+
class Captive < EnfThor
|
25
|
+
desc 'list-wifi-configurations',
|
26
|
+
'List wifi configuration information for all or the matching records.'
|
27
|
+
method_option :name, default: nil, type: :string, banner: 'NAME',
|
28
|
+
desc: 'where NAME will match the user-given name.'
|
29
|
+
method_option :domain, default: nil, type: :string, banner: 'DOMAIN',
|
30
|
+
aliases: '-d'
|
31
|
+
method_option :ssid, default: nil, type: :string, banner: 'SSID'
|
32
|
+
def list_wifi_configurations
|
33
|
+
try_with_rescue_in_session do
|
34
|
+
## TODO: V1 is only listing all of the wifi configurations, it is not
|
35
|
+
## using the options - add the queries based on the options given.
|
36
|
+
wifi_configs = EnfApi::Captive.instance.list_wifi_configurations
|
37
|
+
|
38
|
+
if wifi_configs.class == Array
|
39
|
+
# display the data
|
40
|
+
display_wifi_configs wifi_configs
|
41
|
+
else
|
42
|
+
say 'No WiFi configurations found.'
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
desc 'create-wifi-configuration', 'Create a new wifi configuration.'
|
48
|
+
method_option :'wifi-config-file', type: :array, required: true,
|
49
|
+
banner: '<file>',
|
50
|
+
desc: '<file> is JSON file with parameters required for configuring the router card.'
|
51
|
+
def create_wifi_configuration
|
52
|
+
try_with_rescue_in_session do
|
53
|
+
json_file_name = options[:'wifi-config-file'].join(' ')
|
54
|
+
|
55
|
+
# read in the entire file - yes it is slurping the file, but it should
|
56
|
+
# never be bigger than a few KB.
|
57
|
+
content = File.read json_file_name
|
58
|
+
json_hash = JSON.parse(content)
|
59
|
+
|
60
|
+
resp_data = EnfApi::Captive.instance.create_wifi_configuration json_hash
|
61
|
+
|
62
|
+
# display the data
|
63
|
+
display_wifi_detail resp_data
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
desc 'get-wifi-configuration',
|
68
|
+
'Get details of the specified wifi configuration. Exactly one of: ' \
|
69
|
+
'--wifi-id, --profile-id, --device-id, or --wifi-name must be ' \
|
70
|
+
'specified.'
|
71
|
+
method_option :'wifi-id', defaut: nil, type: :string, banner: 'WIFI-ID',
|
72
|
+
desc: 'WIFI-ID is the UUID of the wifi record'
|
73
|
+
method_option :version, defaut: nil, type: :string, banner: 'VERSION',
|
74
|
+
desc: 'Optionally used with --wifi-id'
|
75
|
+
method_option :'profile-id', defaut: nil, type: :string, banner: 'PROFILE-ID',
|
76
|
+
desc: 'PROFILE-ID is the UUID of the profile.'
|
77
|
+
method_option :'device-id', defaut: nil, type: :string, banner: 'DEVICE-ID',
|
78
|
+
desc: 'DEVICE-ID is the UUID of the device.'
|
79
|
+
method_option :'wifi-name', defaut: nil, type: :string, banner: 'WIFI-NAME',
|
80
|
+
desc: 'Result matches any wifi configuration that contains the substring WIFI-NAME'
|
81
|
+
def get_wifi_configuration
|
82
|
+
try_with_rescue_in_session do
|
83
|
+
## TODO - Currently, this only handles wifi-id, add the others to v2
|
84
|
+
|
85
|
+
wifi_id = options[:'wifi-id']
|
86
|
+
profile_id = options[:'profile-id']
|
87
|
+
device_id = options[:'device-id']
|
88
|
+
wifi_name = options[:'wifi-name']
|
89
|
+
version = options[:version]
|
90
|
+
|
91
|
+
num_query_opts = 0
|
92
|
+
num_query_opts += 1 if wifi_id
|
93
|
+
num_query_opts += 1 if profile_id
|
94
|
+
num_query_opts += 1 if device_id
|
95
|
+
num_query_opts += 1 if wifi_name
|
96
|
+
|
97
|
+
|
98
|
+
raise 'ERROR: Exactly one of: --wifi-id, --profile-id, --device-id, or --wifi-name must be specified.' if num_query_opts != 1
|
99
|
+
|
100
|
+
if wifi_id
|
101
|
+
wifi_config = EnfApi::Captive.instance.get_wifi_configuration wifi_id, version
|
102
|
+
|
103
|
+
# display the data
|
104
|
+
display_wifi_detail wifi_config
|
105
|
+
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
desc 'update-wifi-configuration',
|
111
|
+
'Update an existing wifi configuration.'
|
112
|
+
method_option :'wifi-id', defaut: nil, type: :string, banner: 'WIFI-ID',
|
113
|
+
desc: 'WIFI-ID is the UUID of the wifi profile'
|
114
|
+
method_option :'wifi-config-file', type: :array, required: true,
|
115
|
+
banner: '<file>',
|
116
|
+
desc: '<file> is JSON file with ' \
|
117
|
+
'parameters required for ' \
|
118
|
+
'configuring the router card.'
|
119
|
+
def update_wifi_configuration
|
120
|
+
try_with_rescue_in_session do
|
121
|
+
json_file_name = options[:'wifi-config-file'].join(' ')
|
122
|
+
# read in the entire file - yes it is slurping the file, but it should
|
123
|
+
# never be bigger than a few KB.
|
124
|
+
content = File.read json_file_name
|
125
|
+
json_hash = JSON.parse(content)
|
126
|
+
|
127
|
+
resp_data = EnfApi::Captive.instance.update_wifi_configuration options[:'wifi-id'], json_hash
|
128
|
+
display_wifi_detail resp_data
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
|
133
|
+
desc 'create-device',
|
134
|
+
'Add a new device to the database. This is only available to Xaptum administrators'
|
135
|
+
method_option :'device-id', type: :string, required: true, banner: 'SERIAL-NUM',
|
136
|
+
desc: 'SERIAL-NUM is the serial number of the device.'
|
137
|
+
method_option :'device-name', type: :string, default: nil, banner: 'DEVICE-NAME',
|
138
|
+
desc: 'User-defined name for the device.'
|
139
|
+
method_option :'mac-addr1', type: :string, required: true, banner: 'MAC-ADDR1',
|
140
|
+
desc: 'MAC address 1 (wifi address on a wifi-enabled device)'
|
141
|
+
method_option :'mac-addr2', type: :string, required: true, banner: 'MAC-ADDR2',
|
142
|
+
desc: 'MAC address 2'
|
143
|
+
method_option :'mac-addr3', type: :string, required: true, banner: 'MAC-ADDR3',
|
144
|
+
desc: 'MAC address 3'
|
145
|
+
method_option :'mac-addr4', type: :string, required: true, banner: 'MAC-ADDR4',
|
146
|
+
desc: 'MAC address 4'
|
147
|
+
method_option :'profile-id', type: :string, default: nil, banner: 'PROFILE_ID',
|
148
|
+
desc: 'UUID of the profile that the device will use. The profile must already exist.'
|
149
|
+
def create_device
|
150
|
+
try_with_rescue_in_session do
|
151
|
+
new_device_hash = {
|
152
|
+
serial_number: options[:'device-id'],
|
153
|
+
mac_address: {
|
154
|
+
:wifi => options[:'mac-addr1'],
|
155
|
+
2 => options[:'mac-addr2'],
|
156
|
+
3 => options[:'mac-addr3'],
|
157
|
+
4 => options[:'mac-addr4']
|
158
|
+
}
|
159
|
+
}
|
160
|
+
dev_name = options[:'device-name']
|
161
|
+
profile = options[:profile_id]
|
162
|
+
new_device_hash[:device_name] = dev_name if dev_name
|
163
|
+
new_device_hash[:profile] = profile if profile
|
164
|
+
|
165
|
+
# send the new device request
|
166
|
+
device_data = EnfApi::Captive.instance.create_device new_device_hash
|
167
|
+
|
168
|
+
# display the response
|
169
|
+
display_device device_data
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
desc 'list-devices',
|
174
|
+
'List basic device information for all devices matching the option specified.'
|
175
|
+
method_option :network, default: nil, type: :string, banner: 'NETWORK',
|
176
|
+
desc: 'NETWORK is the ipv6 subnet used by the device.'
|
177
|
+
def list_devices
|
178
|
+
try_with_rescue_in_session do
|
179
|
+
network = options[:network]
|
180
|
+
data = EnfApi::Captive.instance.list_devices network
|
181
|
+
|
182
|
+
display_device_list data
|
183
|
+
end
|
184
|
+
end
|
185
|
+
|
186
|
+
desc 'get-device',
|
187
|
+
'Get details of the specified device.'
|
188
|
+
method_option :'device-id', required: true, type: :string,
|
189
|
+
banner: 'DEVICE-ID',
|
190
|
+
desc: 'DEVICE-ID is either the device serial number or its ipv6 address.'
|
191
|
+
def get_device
|
192
|
+
try_with_rescue_in_session do
|
193
|
+
device_id = options[:'device-id']
|
194
|
+
data = EnfApi::Captive.instance.get_device device_id
|
195
|
+
|
196
|
+
display_device data
|
197
|
+
end
|
198
|
+
end
|
199
|
+
|
200
|
+
desc 'create-profile',
|
201
|
+
'Create a new profile.'
|
202
|
+
method_option :'profile-name', type: :string, required: true,
|
203
|
+
banner: 'PROFILE-NAME',
|
204
|
+
desc: 'PROFILE-NAME is the user-given name.'
|
205
|
+
method_option :'device-mode', type: :string, required: true,
|
206
|
+
banner: 'DEVICE-MODE',
|
207
|
+
desc: 'secure-host or passthrough'
|
208
|
+
method_option :'wifi-id', type: :string, default: nil, banner: 'WIFI-ID',
|
209
|
+
desc: 'WIFI-ID is the UUID of the wifi record that the profile will use. wifi record must already exist'
|
210
|
+
def create_profile
|
211
|
+
try_with_rescue_in_session do
|
212
|
+
new_profile_hash = {
|
213
|
+
name: options[:'profile-name'],
|
214
|
+
config: {mode: options[:'device-mode']}
|
215
|
+
}
|
216
|
+
|
217
|
+
wifi = options[:'wifi-id']
|
218
|
+
if wifi
|
219
|
+
new_profile_hash[:config][:wifi] = {}
|
220
|
+
new_profile_hash[:config][:wifi][:id] = wifi
|
221
|
+
end
|
222
|
+
|
223
|
+
# send the POST to create a new profile
|
224
|
+
profile = EnfApi::Captive.instance.create_profile new_profile_hash
|
225
|
+
display_profile profile
|
226
|
+
end
|
227
|
+
end
|
228
|
+
|
229
|
+
desc 'list-profiles',
|
230
|
+
'List the existing profiles.'
|
231
|
+
method_option :name, default: nil, type: :string, banner: 'NAME',
|
232
|
+
desc: 'where NAME will match all or part of the user-given profile name.'
|
233
|
+
def list_profiles
|
234
|
+
try_with_rescue_in_session do
|
235
|
+
query_name = options[:name]
|
236
|
+
profiles = EnfApi::Captive.instance.list_profiles query_name
|
237
|
+
display_profile_list profiles
|
238
|
+
end
|
239
|
+
end
|
240
|
+
|
241
|
+
desc 'get-profile',
|
242
|
+
'Get full detail listing of the profile.'
|
243
|
+
method_option :'profile-id', required: true, type: :string,
|
244
|
+
banner: 'PROFILE-ID',
|
245
|
+
desc: 'PROFILE-ID is the UUID of the profile.'
|
246
|
+
# TODO - server doesn't support version yet.
|
247
|
+
# method_option :version, default: nil, type: :integer, banner: 'VERSION',
|
248
|
+
# desc: 'Get a specific version.'
|
249
|
+
def get_profile
|
250
|
+
try_with_rescue_in_session do
|
251
|
+
profile = EnfApi::Captive.instance.get_profile options[:'profile-id']
|
252
|
+
display_profile profile
|
253
|
+
end
|
254
|
+
end
|
255
|
+
|
256
|
+
desc 'update-profile',
|
257
|
+
'Update a previously-created profile specified by PROFILE-ID. At ' \
|
258
|
+
'least one property must be changed.'
|
259
|
+
method_option :'profile-id', required: true, type: :string,
|
260
|
+
banner: 'PROFILE-ID',
|
261
|
+
desc: 'UUID of the device profile.'
|
262
|
+
method_option :'profile-name', type: :string, default: nil,
|
263
|
+
banner: 'PROFILE-NAME',
|
264
|
+
desc: 'PROFILE-NAME is the user-given name.'
|
265
|
+
method_option :'device-mode', type: :string, default: nil,
|
266
|
+
banner: 'DEVICE-MODE',
|
267
|
+
desc: 'secure-host or passthrough'
|
268
|
+
method_option :'wifi-id', type: :string, default: nil, banner: 'WIFI-ID',
|
269
|
+
desc: 'WIFI-ID is the UUID of the wifi record that the profile will use. wifi record must already exist'
|
270
|
+
def update_profile
|
271
|
+
try_with_rescue_in_session do
|
272
|
+
id = options[:'profile-id']
|
273
|
+
name = options[:'profile-name']
|
274
|
+
mode = options[:'device-mode']
|
275
|
+
wifi_id = options[:'wifi-id']
|
276
|
+
|
277
|
+
raise "At least one option needs to change." if name == nil && mode == nil && wifi_id == nil
|
278
|
+
|
279
|
+
update_hash = {}
|
280
|
+
update_hash[:name] = name if name
|
281
|
+
if wifi_id || mode
|
282
|
+
update_hash[:config] = {}
|
283
|
+
update_hash[:config][:mode] = mode if mode
|
284
|
+
update_hash[:config][:wifi] = wifi_id if wifi_id
|
285
|
+
end
|
286
|
+
|
287
|
+
profile = EnfApi::Captive.instance.update_profile id, update_hash
|
288
|
+
display_profile profile
|
289
|
+
|
290
|
+
end
|
291
|
+
end
|
292
|
+
|
293
|
+
|
294
|
+
#########################################################################
|
295
|
+
#
|
296
|
+
# Helper functions
|
297
|
+
#
|
298
|
+
#########################################################################
|
299
|
+
no_commands do
|
300
|
+
# Displays the wifi configuration summary list.
|
301
|
+
# TODO - Does non-Xaptum-admin columns only - add option to do admin list
|
302
|
+
def display_wifi_configs(configs)
|
303
|
+
headings = ['ID', 'Wifi Name', 'Config Vers.']
|
304
|
+
rows = configs.map do |hash|
|
305
|
+
[hash[:id], hash[:name], hash[:version]]
|
306
|
+
end
|
307
|
+
|
308
|
+
render_table(headings, rows)
|
309
|
+
end
|
310
|
+
|
311
|
+
# Display a single wifi configuration in detail
|
312
|
+
def display_wifi_detail(wifi_data)
|
313
|
+
name = wifi_data[:name]
|
314
|
+
wifi_id = wifi_data[:id]
|
315
|
+
desc = wifi_data[:description]
|
316
|
+
configs = wifi_data[:config]
|
317
|
+
|
318
|
+
say "Wifi ID : #{wifi_id}"
|
319
|
+
say "Name : #{name}"
|
320
|
+
say "Description : #{desc}"
|
321
|
+
say "WiFi Networks :"
|
322
|
+
configs.each do |wifi_net|
|
323
|
+
display_wifi_net(wifi_net, 1)
|
324
|
+
end
|
325
|
+
|
326
|
+
end
|
327
|
+
|
328
|
+
def display_wifi_net(wifi_net, tabs)
|
329
|
+
indent = " " * tabs
|
330
|
+
say "Name : #{wifi_net[:name]}"
|
331
|
+
say indent + "SSID :#{wifi_net[:SSID]}"
|
332
|
+
say indent + "SSID type :#{wifi_net[:SSID_type]}"
|
333
|
+
|
334
|
+
auth = wifi_net[:auth]
|
335
|
+
if auth
|
336
|
+
say indent + "auth :#{auth[:type]}"
|
337
|
+
end
|
338
|
+
|
339
|
+
display_ipv4_addr(wifi_net[:IPv4], tabs)
|
340
|
+
display_ipv6_addr(wifi_net[:IPv6], tabs)
|
341
|
+
end
|
342
|
+
|
343
|
+
def display_ipv4_addr (ipv4, tabs)
|
344
|
+
indent = " " * tabs
|
345
|
+
if ipv4.instance_of? String
|
346
|
+
say indent + "IPv4 :#{ipv4}"
|
347
|
+
elsif ipv4.instance_of? NilClass
|
348
|
+
say indent + "IPv4 :off"
|
349
|
+
elsif ipv4.instance_of? Hash
|
350
|
+
addr_type = ipv4[:type]
|
351
|
+
if addr_type == "dhcp"
|
352
|
+
say indent + "IPv4 :#{addr_type}"
|
353
|
+
elsif addr_type == "static"
|
354
|
+
addr = ipv4[:address]
|
355
|
+
mask = ipv4[:mask]
|
356
|
+
gw = ipv4[:gateway]
|
357
|
+
say indent + "IPv4 :#{addr}/#{mask}/#{gw}"
|
358
|
+
else
|
359
|
+
say indent + "*** Invalid ipv4 address. Type is #{addr_type} ***"
|
360
|
+
end
|
361
|
+
else
|
362
|
+
say indent + "*** Invalid ipv4 address. Received as JSON class: #{ipv4.class} ***"
|
363
|
+
end
|
364
|
+
end
|
365
|
+
|
366
|
+
def display_ipv6_addr (ipv6, tabs)
|
367
|
+
indent = " " * tabs
|
368
|
+
if ipv6.instance_of? String
|
369
|
+
say indent + "IPv6 :#{ipv6}"
|
370
|
+
elsif ipv6.instance_of? NilClass
|
371
|
+
say indent + "IPv6 :off"
|
372
|
+
elsif ipv6.instance_of? Hash
|
373
|
+
addr_type = ipv6[:type]
|
374
|
+
if addr_type == "auto"
|
375
|
+
say indent + "IPv6 :#{addr_type}"
|
376
|
+
elsif addr_type == "static"
|
377
|
+
addr = ipv6[:address]
|
378
|
+
pref = ipv6[:prefix_length]
|
379
|
+
gw = ipv6[:gateway]
|
380
|
+
say indent + "IPv6 :#{addr}/#{pref}/#{gw}"
|
381
|
+
else
|
382
|
+
say indent + "*** Invalid ipv6 address. Type is #{addr_type} ***"
|
383
|
+
end
|
384
|
+
else
|
385
|
+
say indent + "*** Invalid ipv6 address. Received as JSON class: #{ipv6.class} ***"
|
386
|
+
end
|
387
|
+
end
|
388
|
+
|
389
|
+
|
390
|
+
|
391
|
+
#
|
392
|
+
# display the device info.
|
393
|
+
# device_data is a hash matching the json structure
|
394
|
+
#
|
395
|
+
def display_device(device_data)
|
396
|
+
name = device_data[:device_name]
|
397
|
+
ctl_addr = device_data[:control_address]
|
398
|
+
dev_addr = device_data[:device_address]
|
399
|
+
mac_addrs = device_data[:mac_address]
|
400
|
+
firmware = device_data[:firmware_version]
|
401
|
+
profile_id = device_data[:profile]
|
402
|
+
status = device_data[:status]
|
403
|
+
|
404
|
+
say "Serial Number : #{device_data[:serial_number]}"
|
405
|
+
say "Device Name : #{name}"
|
406
|
+
say "Control Address : #{ctl_addr}"
|
407
|
+
say "Device Address : #{dev_addr}"
|
408
|
+
say 'Mac Address :'
|
409
|
+
if mac_addrs
|
410
|
+
say " 1 : #{mac_addrs[:wifi]}"
|
411
|
+
say " 2 : #{mac_addrs[:'2']}"
|
412
|
+
say " 3 : #{mac_addrs[:'3']}"
|
413
|
+
say " 4 : #{mac_addrs[:'4']}"
|
414
|
+
end
|
415
|
+
say "Firmware Version : #{firmware}"
|
416
|
+
say "Profile ID : #{profile_id}"
|
417
|
+
say "Status : #{status}"
|
418
|
+
end
|
419
|
+
|
420
|
+
#
|
421
|
+
# display the devices summary list
|
422
|
+
#
|
423
|
+
def display_device_list(devices)
|
424
|
+
headings = ['Serial No', 'Dev Name', 'Dev Addr', 'Router Mode',
|
425
|
+
'Connected', 'SSID']
|
426
|
+
rows = devices.map do |hash|
|
427
|
+
status = hash[:status]
|
428
|
+
if status
|
429
|
+
[hash[:serial_number], hash[:device_name], hash[:device_address],
|
430
|
+
status[:router_mode], status[:wifi][:connected],
|
431
|
+
status[:wifi][:SSID]]
|
432
|
+
else
|
433
|
+
[hash[:serial_number], hash[:device_name], hash[:device_address],
|
434
|
+
nil, nil, nil]
|
435
|
+
end
|
436
|
+
end
|
437
|
+
# [hash[:serial_number], hash[:device_name], hash[:device_address],
|
438
|
+
# hash[:status][:router_mode], hash[:status][:wifi][:connected],
|
439
|
+
# hash[:status][:wifi][:SSID]]
|
440
|
+
|
441
|
+
render_table(headings, rows)
|
442
|
+
end
|
443
|
+
|
444
|
+
#
|
445
|
+
# Display profile detail
|
446
|
+
#
|
447
|
+
def display_profile (profile)
|
448
|
+
say "Name :#{profile[:name]}"
|
449
|
+
say "Profile ID :#{profile[:id]}"
|
450
|
+
say "Configuration version :#{profile[:config][:version]}"
|
451
|
+
say "Mode :#{profile[:config][:mode]}"
|
452
|
+
display_wifi_summary profile[:config][:wifi]
|
453
|
+
end
|
454
|
+
|
455
|
+
#
|
456
|
+
# Display summary of the WIFI configuration info
|
457
|
+
#
|
458
|
+
def display_wifi_summary (wifi)
|
459
|
+
say "Wifi config :"
|
460
|
+
say " id :#{wifi[:id]}"
|
461
|
+
# TODO - make accessible only to Xaptum admins
|
462
|
+
say " domain :#{wifi[:domain]}"
|
463
|
+
say " name :#{wifi[:name]}"
|
464
|
+
desc = wifi[:description]
|
465
|
+
say " description :#{desc}" if desc
|
466
|
+
say " config version :#{wifi[:version]}"
|
467
|
+
end
|
468
|
+
|
469
|
+
#
|
470
|
+
# display the profile summary list
|
471
|
+
#
|
472
|
+
def display_profile_list(profiles)
|
473
|
+
headings = ['Profile Name', 'Profile ID', 'Version']
|
474
|
+
rows = profiles.map do |hash|
|
475
|
+
[hash[:name], hash[:id], hash[:config][:version]]
|
476
|
+
end
|
477
|
+
|
478
|
+
render_table(headings, rows)
|
479
|
+
end
|
480
|
+
|
481
|
+
|
482
|
+
end
|
483
|
+
end
|
484
|
+
end
|
485
|
+
end
|