morpheus-cli 4.2.21 → 5.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Dockerfile +1 -1
- data/lib/morpheus/api/api_client.rb +30 -0
- data/lib/morpheus/api/billing_interface.rb +34 -0
- data/lib/morpheus/api/catalog_item_types_interface.rb +9 -0
- data/lib/morpheus/api/deploy_interface.rb +1 -1
- data/lib/morpheus/api/deployments_interface.rb +20 -1
- data/lib/morpheus/api/forgot_password_interface.rb +17 -0
- data/lib/morpheus/api/instances_interface.rb +16 -2
- data/lib/morpheus/api/rest_interface.rb +0 -6
- data/lib/morpheus/api/roles_interface.rb +14 -0
- data/lib/morpheus/api/search_interface.rb +13 -0
- data/lib/morpheus/api/servers_interface.rb +14 -0
- data/lib/morpheus/api/service_catalog_interface.rb +89 -0
- data/lib/morpheus/api/usage_interface.rb +18 -0
- data/lib/morpheus/cli.rb +7 -3
- data/lib/morpheus/cli/apps.rb +6 -27
- data/lib/morpheus/cli/backup_jobs_command.rb +3 -0
- data/lib/morpheus/cli/backups_command.rb +3 -0
- data/lib/morpheus/cli/catalog_item_types_command.rb +622 -0
- data/lib/morpheus/cli/cli_command.rb +70 -21
- data/lib/morpheus/cli/commands/standard/curl_command.rb +26 -12
- data/lib/morpheus/cli/commands/standard/history_command.rb +3 -1
- data/lib/morpheus/cli/commands/standard/man_command.rb +74 -40
- data/lib/morpheus/cli/commands/standard/source_command.rb +1 -1
- data/lib/morpheus/cli/commands/standard/update_command.rb +76 -0
- data/lib/morpheus/cli/containers_command.rb +14 -24
- data/lib/morpheus/cli/cypher_command.rb +6 -2
- data/lib/morpheus/cli/deploy.rb +199 -90
- data/lib/morpheus/cli/deployments.rb +341 -28
- data/lib/morpheus/cli/deploys.rb +206 -41
- data/lib/morpheus/cli/error_handler.rb +7 -0
- data/lib/morpheus/cli/forgot_password.rb +133 -0
- data/lib/morpheus/cli/groups.rb +1 -1
- data/lib/morpheus/cli/health_command.rb +59 -2
- data/lib/morpheus/cli/hosts.rb +265 -34
- data/lib/morpheus/cli/instances.rb +186 -100
- data/lib/morpheus/cli/invoices_command.rb +33 -16
- data/lib/morpheus/cli/jobs_command.rb +28 -6
- data/lib/morpheus/cli/library_option_lists_command.rb +15 -7
- data/lib/morpheus/cli/library_option_types_command.rb +5 -2
- data/lib/morpheus/cli/logs_command.rb +9 -6
- data/lib/morpheus/cli/mixins/accounts_helper.rb +12 -7
- data/lib/morpheus/cli/mixins/backups_helper.rb +2 -4
- data/lib/morpheus/cli/mixins/deployments_helper.rb +31 -3
- data/lib/morpheus/cli/mixins/option_source_helper.rb +1 -1
- data/lib/morpheus/cli/mixins/print_helper.rb +46 -21
- data/lib/morpheus/cli/mixins/provisioning_helper.rb +100 -4
- data/lib/morpheus/cli/network_pools_command.rb +14 -6
- data/lib/morpheus/cli/option_types.rb +271 -22
- data/lib/morpheus/cli/ping.rb +0 -1
- data/lib/morpheus/cli/remote.rb +35 -12
- data/lib/morpheus/cli/reports_command.rb +99 -30
- data/lib/morpheus/cli/roles.rb +453 -113
- data/lib/morpheus/cli/search_command.rb +182 -0
- data/lib/morpheus/cli/service_catalog_command.rb +1474 -0
- data/lib/morpheus/cli/service_plans_command.rb +2 -2
- data/lib/morpheus/cli/setup.rb +1 -1
- data/lib/morpheus/cli/shell.rb +33 -11
- data/lib/morpheus/cli/storage_providers_command.rb +40 -56
- data/lib/morpheus/cli/tasks.rb +29 -32
- data/lib/morpheus/cli/usage_command.rb +203 -0
- data/lib/morpheus/cli/user_settings_command.rb +1 -0
- data/lib/morpheus/cli/users.rb +12 -1
- data/lib/morpheus/cli/version.rb +1 -1
- data/lib/morpheus/cli/virtual_images.rb +429 -254
- data/lib/morpheus/cli/whoami.rb +6 -6
- data/lib/morpheus/cli/workflows.rb +34 -41
- data/lib/morpheus/formatters.rb +75 -7
- data/lib/morpheus/terminal.rb +6 -2
- metadata +14 -2
@@ -596,22 +596,28 @@ class Morpheus::Cli::NetworkPoolsCommand
|
|
596
596
|
def add_ip(args)
|
597
597
|
options = {}
|
598
598
|
params = {}
|
599
|
+
next_free_ip = false
|
599
600
|
optparse = Morpheus::Cli::OptionParser.new do |opts|
|
600
|
-
opts.banner = subcommand_usage("[network-pool] [ip]")
|
601
|
+
opts.banner = subcommand_usage("[network-pool] [ip] [--next]")
|
601
602
|
opts.on('--ip-address VALUE', String, "IP Address for this network pool IP") do |val|
|
602
603
|
options[:options]['ipAddress'] = val
|
603
604
|
end
|
605
|
+
opts.on('--next-free-ip', '--next-free-ip', "Use the next available ip address. This can be used instead of specifying an ip address") do
|
606
|
+
next_free_ip = true
|
607
|
+
end
|
604
608
|
opts.on('--hostname VALUE', String, "Hostname for this network pool IP") do |val|
|
605
609
|
options[:options]['hostname'] = val
|
606
610
|
end
|
607
611
|
build_common_options(opts, options, [:options, :payload, :json, :dry_run, :quiet, :remote])
|
608
612
|
opts.footer = "Create a new network pool IP." + "\n" +
|
609
613
|
"[network-pool] is required. This is the name or id of a network pool.\n" +
|
610
|
-
"[ip] is required
|
614
|
+
"[ip] is required or --next-free-ip to use the next available address instead."
|
611
615
|
end
|
612
616
|
optparse.parse!(args)
|
613
|
-
if
|
614
|
-
|
617
|
+
if next_free_ip
|
618
|
+
verify_args!(args:args, count:1, optparse:optparse)
|
619
|
+
else
|
620
|
+
verify_args!(args:args, min:1, max:2, optparse:optparse)
|
615
621
|
end
|
616
622
|
connect(options)
|
617
623
|
begin
|
@@ -639,8 +645,10 @@ class Morpheus::Cli::NetworkPoolsCommand
|
|
639
645
|
payload['networkPoolIp'].deep_merge!(options[:options].reject {|k,v| k.is_a?(Symbol) }) if options[:options]
|
640
646
|
|
641
647
|
# IP Address
|
642
|
-
|
643
|
-
|
648
|
+
unless next_free_ip
|
649
|
+
v_prompt = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'ipAddress', 'fieldLabel' => 'IP Address', 'type' => 'text', 'required' => true, 'description' => 'IP Address for this network pool IP.'}], options[:options])
|
650
|
+
payload['networkPoolIp']['ipAddress'] = v_prompt['ipAddress'] unless v_prompt['ipAddress'].to_s.empty?
|
651
|
+
end
|
644
652
|
|
645
653
|
# Hostname
|
646
654
|
v_prompt = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'hostname', 'fieldLabel' => 'Hostname', 'type' => 'text', 'required' => true, 'description' => 'Hostname for this network pool IP.'}], options[:options])
|
@@ -5,6 +5,7 @@ module Morpheus
|
|
5
5
|
module Cli
|
6
6
|
module OptionTypes
|
7
7
|
include Term::ANSIColor
|
8
|
+
# include Morpheus::Cli::PrintHelper
|
8
9
|
|
9
10
|
def self.confirm(message,options={})
|
10
11
|
if options[:yes] == true
|
@@ -72,9 +73,15 @@ module Morpheus
|
|
72
73
|
field_name = namespaces.pop
|
73
74
|
|
74
75
|
# respect optionType.dependsOnCode
|
75
|
-
|
76
|
+
# i guess this switched to visibleOnCode, respect one or the other
|
77
|
+
visible_option_check_value = option_type['dependsOnCode']
|
78
|
+
if !option_type['visibleOnCode'].to_s.empty?
|
79
|
+
visible_option_check_value = option_type['visibleOnCode']
|
80
|
+
end
|
81
|
+
if !visible_option_check_value.to_s.empty?
|
76
82
|
# support formats code=value or code:value OR code:(value|value2|value3)
|
77
|
-
|
83
|
+
# OR fieldContext.fieldName=value
|
84
|
+
parts = visible_option_check_value.include?("=") ? visible_option_check_value.split("=") : visible_option_check_value.split(":")
|
78
85
|
depends_on_code = parts[0]
|
79
86
|
depends_on_value = parts[1].to_s.strip
|
80
87
|
depends_on_values = []
|
@@ -87,14 +94,29 @@ module Morpheus
|
|
87
94
|
depends_on_values = depends_on_value.split("|").collect { |it| it.strip }
|
88
95
|
end
|
89
96
|
depends_on_option_type = option_types.find {|it| it["code"] == depends_on_code }
|
90
|
-
|
91
|
-
|
97
|
+
if !depends_on_option_type
|
98
|
+
depends_on_option_type = option_types.find {|it|
|
99
|
+
(it['fieldContext'] ? "#{it['fieldContext']}.#{it['fieldName']}" : it['fieldName']) == depends_on_code
|
100
|
+
}
|
101
|
+
end
|
102
|
+
if depends_on_option_type
|
92
103
|
# dependent option type has a different value
|
93
104
|
depends_on_field_key = depends_on_option_type['fieldContext'] ? "#{depends_on_option_type['fieldContext']}.#{depends_on_option_type['fieldName']}" : "#{depends_on_option_type['fieldName']}"
|
94
105
|
found_dep_value = get_object_value(results, depends_on_field_key) || get_object_value(options, depends_on_field_key)
|
95
|
-
if depends_on_values.size > 0
|
96
|
-
|
106
|
+
if depends_on_values.size > 0
|
107
|
+
# must be in the specified values
|
108
|
+
# todo: uhh this actually needs to change to parse regex
|
109
|
+
if !depends_on_values.include?(found_dep_value)
|
110
|
+
next
|
111
|
+
end
|
112
|
+
else
|
113
|
+
# no value found
|
114
|
+
if found_dep_value.to_s.empty?
|
115
|
+
next
|
116
|
+
end
|
97
117
|
end
|
118
|
+
else
|
119
|
+
# could not find the dependent option type, proceed and prompt
|
98
120
|
end
|
99
121
|
end
|
100
122
|
|
@@ -115,22 +137,32 @@ module Morpheus
|
|
115
137
|
# use the value passed in the options map
|
116
138
|
if cur_namespace.respond_to?('key?') && cur_namespace.key?(field_name)
|
117
139
|
value = cur_namespace[field_name]
|
118
|
-
input_value = ['select', 'multiSelect'].include?(option_type['type']) && option_type['fieldInput'] ? cur_namespace[option_type['fieldInput']] : nil
|
140
|
+
input_value = ['select', 'multiSelect','typeahead', 'multiTypeahead'].include?(option_type['type']) && option_type['fieldInput'] ? cur_namespace[option_type['fieldInput']] : nil
|
119
141
|
if option_type['type'] == 'number'
|
120
142
|
value = value.to_s.include?('.') ? value.to_f : value.to_i
|
121
143
|
# these select prompts should just fall down through below, with the extra params no_prompt, use_value
|
122
144
|
elsif option_type['type'] == 'select'
|
123
|
-
value = select_prompt(option_type.merge({'defaultValue' => value, 'defaultInputValue' => input_value}), api_client, (api_params || {}).merge(results), true)
|
145
|
+
value = select_prompt(option_type.merge({'defaultValue' => value, 'defaultInputValue' => input_value}), api_client, (option_type['noParams'] ? {} : (api_params || {}).merge(results)), true)
|
124
146
|
elsif option_type['type'] == 'multiSelect'
|
125
147
|
# support value as csv like "thing1, thing2"
|
126
148
|
value_list = value.is_a?(String) ? value.parse_csv.collect {|v| v ? v.to_s.strip : v } : [value].flatten
|
127
149
|
input_value_list = input_value.is_a?(String) ? input_value.parse_csv.collect {|v| v ? v.to_s.strip : v } : [input_value].flatten
|
128
150
|
select_value_list = []
|
129
151
|
value_list.each_with_index do |v, i|
|
130
|
-
select_value_list << select_prompt(option_type.merge({'defaultValue' => v, 'defaultInputValue' => input_value_list[i]}), api_client, (api_params || {}).merge(results), true)
|
152
|
+
select_value_list << select_prompt(option_type.merge({'defaultValue' => v, 'defaultInputValue' => input_value_list[i]}), api_client, (option_type['noParams'] ? {} : (api_params || {}).merge(results)), true)
|
153
|
+
end
|
154
|
+
value = select_value_list
|
155
|
+
elsif option_type['type'] == 'typeahead'
|
156
|
+
value = typeahead_prompt(option_type.merge({'defaultValue' => value, 'defaultInputValue' => input_value}), api_client, (option_type['noParams'] ? {} : (api_params || {}).merge(results)), true)
|
157
|
+
elsif option_type['type'] == 'multiTypeahead'
|
158
|
+
# support value as csv like "thing1, thing2"
|
159
|
+
value_list = value.is_a?(String) ? value.parse_csv.collect {|v| v ? v.to_s.strip : v } : [value].flatten
|
160
|
+
input_value_list = input_value.is_a?(String) ? input_value.parse_csv.collect {|v| v ? v.to_s.strip : v } : [input_value].flatten
|
161
|
+
select_value_list = []
|
162
|
+
value_list.each_with_index do |v, i|
|
163
|
+
select_value_list << typeahead_prompt(option_type.merge({'defaultValue' => v, 'defaultInputValue' => input_value_list[i]}), api_client, (option_type['noParams'] ? {} : (api_params || {}).merge(results)), true)
|
131
164
|
end
|
132
165
|
value = select_value_list
|
133
|
-
|
134
166
|
end
|
135
167
|
if options[:always_prompt] != true
|
136
168
|
value_found = true
|
@@ -147,7 +179,7 @@ module Morpheus
|
|
147
179
|
no_prompt = no_prompt || options[:no_prompt]
|
148
180
|
if no_prompt
|
149
181
|
if !value_found
|
150
|
-
if option_type['defaultValue'] != nil && !['select', 'multiSelect'].include?(option_type['type'])
|
182
|
+
if option_type['defaultValue'] != nil && !['select', 'multiSelect','typeahead','multiTypeahead'].include?(option_type['type'])
|
151
183
|
value = option_type['defaultValue']
|
152
184
|
value_found = true
|
153
185
|
end
|
@@ -155,7 +187,11 @@ module Morpheus
|
|
155
187
|
# select type is special because it supports skipSingleOption
|
156
188
|
# and prints the available options on error
|
157
189
|
if ['select', 'multiSelect'].include?(option_type['type'])
|
158
|
-
value = select_prompt(option_type, api_client, (api_params || {}).merge(results), true)
|
190
|
+
value = select_prompt(option_type, api_client, (option_type['noParams'] ? {} : (api_params || {}).merge(results)), true)
|
191
|
+
value_found = !!value
|
192
|
+
end
|
193
|
+
if ['typeahead', 'multiTypeahead'].include?(option_type['type'])
|
194
|
+
value = typeahead_prompt(option_type, api_client, (option_type['noParams'] ? {} : (api_params || {}).merge(results)), true)
|
159
195
|
value_found = !!value
|
160
196
|
end
|
161
197
|
if !value_found
|
@@ -192,11 +228,23 @@ module Morpheus
|
|
192
228
|
# I suppose the entered value should take precedence
|
193
229
|
# api_params = api_params.merge(options) # this might be good enough
|
194
230
|
# dup it
|
195
|
-
value = select_prompt(option_type, api_client, (api_params || {}).merge(results), options[:no_prompt], nil, paging_enabled)
|
231
|
+
value = select_prompt(option_type, api_client, (option_type['noParams'] ? {} : (api_params || {}).merge(results)), options[:no_prompt], nil, paging_enabled)
|
196
232
|
if value && option_type['type'] == 'multiSelect'
|
197
233
|
value = [value]
|
198
234
|
while self.confirm("Add another #{option_type['fieldLabel']}?", {:default => false}) do
|
199
|
-
if addn_value = select_prompt(option_type, api_client, (api_params || {}).merge(results), options[:no_prompt], nil, paging_enabled)
|
235
|
+
if addn_value = select_prompt(option_type, api_client, (option_type['noParams'] ? {} : (api_params || {}).merge(results)), options[:no_prompt], nil, paging_enabled)
|
236
|
+
value << addn_value
|
237
|
+
else
|
238
|
+
break
|
239
|
+
end
|
240
|
+
end
|
241
|
+
end
|
242
|
+
elsif ['typeahead', 'multiTypeahead'].include?(option_type['type'])
|
243
|
+
value = typeahead_prompt(option_type, api_client, (option_type['noParams'] ? {} : (api_params || {}).merge(results)), options[:no_prompt], nil, paging_enabled)
|
244
|
+
if value && option_type['type'] == 'multiTypeahead'
|
245
|
+
value = [value]
|
246
|
+
while self.confirm("Add another #{option_type['fieldLabel']}?", {:default => false}) do
|
247
|
+
if addn_value = typeahead_prompt(option_type, api_client, (option_type['noParams'] ? {} : (api_params || {}).merge(results)), options[:no_prompt], nil, paging_enabled)
|
200
248
|
value << addn_value
|
201
249
|
else
|
202
250
|
break
|
@@ -310,11 +358,12 @@ module Morpheus
|
|
310
358
|
value_field = (option_type['config'] ? option_type['config']['valueField'] : nil) || 'value'
|
311
359
|
default_value = option_type['defaultValue']
|
312
360
|
default_value = default_value['id'] if default_value && default_value.is_a?(Hash) && !default_value['id'].nil?
|
361
|
+
api_params ||= {}
|
313
362
|
# local array of options
|
314
363
|
if option_type['selectOptions']
|
315
364
|
# calculate from inline lambda
|
316
365
|
if option_type['selectOptions'].is_a?(Proc)
|
317
|
-
select_options = option_type['selectOptions'].call()
|
366
|
+
select_options = option_type['selectOptions'].call(api_client, grails_params(api_params || {}))
|
318
367
|
else
|
319
368
|
# todo: better type validation
|
320
369
|
select_options = option_type['selectOptions']
|
@@ -325,7 +374,7 @@ module Morpheus
|
|
325
374
|
select_options = option_type['optionSource'].call(api_client, grails_params(api_params || {}))
|
326
375
|
elsif option_type['optionSource'] == 'list'
|
327
376
|
# /api/options/list is a special action for custom OptionTypeLists, just need to pass the optionTypeId parameter
|
328
|
-
select_options = load_source_options(option_type['optionSource'], api_client, {'optionTypeId' => option_type['id']})
|
377
|
+
select_options = load_source_options(option_type['optionSource'], api_client, grails_params(api_params || {}).merge({'optionTypeId' => option_type['id']}))
|
329
378
|
else
|
330
379
|
# remote optionSource aka /api/options/$optionSource?
|
331
380
|
select_options = load_source_options(option_type['optionSource'], api_client, grails_params(api_params || {}))
|
@@ -345,7 +394,7 @@ module Morpheus
|
|
345
394
|
print Term::ANSIColor.red, " * #{option_type['fieldLabel']} [-O #{option_type['fieldContext'] ? (option_type['fieldContext']+'.') : ''}#{option_type['fieldName']}=] - #{option_type['description']}\n", Term::ANSIColor.reset
|
346
395
|
if select_options && select_options.size > 10
|
347
396
|
display_select_options(option_type, select_options.first(10))
|
348
|
-
puts " (#{select_options.size-
|
397
|
+
puts " (#{select_options.size-10} more)"
|
349
398
|
else
|
350
399
|
display_select_options(option_type, select_options)
|
351
400
|
end
|
@@ -388,7 +437,7 @@ module Morpheus
|
|
388
437
|
print Term::ANSIColor.red, " * #{option_type['fieldLabel']} [-O #{help_field_key}=] - #{option_type['description']}\n", Term::ANSIColor.reset
|
389
438
|
if select_options && select_options.size > 10
|
390
439
|
display_select_options(option_type, select_options.first(10))
|
391
|
-
puts " (#{select_options.size-
|
440
|
+
puts " (#{select_options.size-10} more)"
|
392
441
|
else
|
393
442
|
display_select_options(option_type, select_options)
|
394
443
|
end
|
@@ -458,6 +507,154 @@ module Morpheus
|
|
458
507
|
value
|
459
508
|
end
|
460
509
|
|
510
|
+
# this works like select_prompt, but refreshes options with ?query=value between inputs
|
511
|
+
# paging_enabled is ignored right now
|
512
|
+
def self.typeahead_prompt(option_type,api_client, api_params={}, no_prompt=false, use_value=nil, paging_enabled=false)
|
513
|
+
select_options = []
|
514
|
+
field_key = [option_type['fieldContext'], option_type['fieldName']].select {|it| it && it != '' }.join('.')
|
515
|
+
help_field_key = option_type[:help_field_prefix] ? "#{option_type[:help_field_prefix]}.#{field_key}" : field_key
|
516
|
+
input = ""
|
517
|
+
value_found = false
|
518
|
+
value = nil
|
519
|
+
value_field = (option_type['config'] ? option_type['config']['valueField'] : nil) || 'value'
|
520
|
+
default_value = option_type['defaultValue']
|
521
|
+
default_value = default_value['id'] if default_value && default_value.is_a?(Hash) && !default_value['id'].nil?
|
522
|
+
|
523
|
+
while !value_found do
|
524
|
+
# ok get input, refresh options and see if it matches
|
525
|
+
# if matches one, cool otherwise print matches and reprompt or error
|
526
|
+
if use_value
|
527
|
+
input = use_value
|
528
|
+
elsif no_prompt
|
529
|
+
input = default_value
|
530
|
+
else
|
531
|
+
Readline.completion_append_character = ""
|
532
|
+
Readline.basic_word_break_characters = ''
|
533
|
+
Readline.completion_proc = proc {|s|
|
534
|
+
matches = []
|
535
|
+
available_options = (select_options || [])
|
536
|
+
available_options.each{|option|
|
537
|
+
if option['name'] && option['name'] =~ /^#{Regexp.escape(s)}/
|
538
|
+
matches << option['name']
|
539
|
+
# elsif option['id'] && option['id'].to_s =~ /^#{Regexp.escape(s)}/
|
540
|
+
elsif option[value_field] && option[value_field].to_s == s
|
541
|
+
matches << option['name']
|
542
|
+
end
|
543
|
+
}
|
544
|
+
matches
|
545
|
+
}
|
546
|
+
# prompt for typeahead input value
|
547
|
+
input = Readline.readline("#{option_type['fieldLabel']}#{option_type['fieldAddOn'] ? ('(' + option_type['fieldAddOn'] + ') ') : '' }#{!option_type['required'] ? ' (optional)' : ''}#{!default_value.to_s.empty? ? ' ['+default_value.to_s+']' : ''} ['?' for options]: ", false).to_s
|
548
|
+
input = input.chomp.strip
|
549
|
+
end
|
550
|
+
|
551
|
+
# just hit enter, use [default] if set
|
552
|
+
if input.empty? && default_value
|
553
|
+
input = default_value.to_s
|
554
|
+
end
|
555
|
+
|
556
|
+
# not required and no value? ok proceed
|
557
|
+
if input.to_s == "" && option_type['required'] != true
|
558
|
+
value_found = true
|
559
|
+
value = nil # or "" # hmm
|
560
|
+
#next
|
561
|
+
break
|
562
|
+
end
|
563
|
+
|
564
|
+
# required and no value? you need help
|
565
|
+
# if input.to_s == "" && option_type['required'] == true
|
566
|
+
# help_prompt(option_type)
|
567
|
+
# display_select_options(option_type, select_options) unless select_options.empty?
|
568
|
+
# next
|
569
|
+
# end
|
570
|
+
|
571
|
+
# looking for help with this input
|
572
|
+
if input == '?'
|
573
|
+
help_prompt(option_type)
|
574
|
+
display_select_options(option_type, select_options) unless select_options.empty?
|
575
|
+
next
|
576
|
+
end
|
577
|
+
|
578
|
+
# just hit enter? scram
|
579
|
+
# looking for help with this input
|
580
|
+
# if input == ""
|
581
|
+
# help_prompt(option_type)
|
582
|
+
# display_select_options(option_type, select_options)
|
583
|
+
# next
|
584
|
+
# end
|
585
|
+
|
586
|
+
# this is how typeahead works, it keeps refreshing the options with a new ?query={value}
|
587
|
+
# query_value = (value || use_value || default_value || '')
|
588
|
+
query_value = (input || '')
|
589
|
+
api_params ||= {}
|
590
|
+
api_params['query'] = query_value
|
591
|
+
# skip refresh if you just hit enter
|
592
|
+
if !query_value.empty?
|
593
|
+
select_options = load_options(option_type, api_client, api_params, query_value)
|
594
|
+
end
|
595
|
+
|
596
|
+
# match input to option name or value
|
597
|
+
# actually that is redundant, it should already be filtered to matches
|
598
|
+
# and can just do this:
|
599
|
+
# select_option = select_options.size == 1 ? select_options[0] : nil
|
600
|
+
select_option = select_options.find{|b| (b[value_field] && (b[value_field].to_s == input.to_s)) || ((b[value_field].nil? || b[value_field].empty?) && (input == "")) }
|
601
|
+
if select_option.nil?
|
602
|
+
select_option = select_options.find{|b| b['name'] && b['name'] == input }
|
603
|
+
end
|
604
|
+
|
605
|
+
# found matching value, else did not find a value, show matching options and prompt again or error
|
606
|
+
if select_option
|
607
|
+
value = select_option[value_field]
|
608
|
+
set_last_select(select_option)
|
609
|
+
value_found = true
|
610
|
+
else
|
611
|
+
if use_value || no_prompt
|
612
|
+
# todo: make this nicer
|
613
|
+
# help_prompt(option_type)
|
614
|
+
print Term::ANSIColor.red, "\nMissing Required Option\n\n", Term::ANSIColor.reset
|
615
|
+
print Term::ANSIColor.red, " * #{option_type['fieldLabel']} [-O #{help_field_key}=] - #{option_type['description']}\n", Term::ANSIColor.reset
|
616
|
+
if select_options && select_options.size > 10
|
617
|
+
display_select_options(option_type, select_options.first(10))
|
618
|
+
puts " (#{select_options.size-10} more)"
|
619
|
+
else
|
620
|
+
display_select_options(option_type, select_options)
|
621
|
+
end
|
622
|
+
print "\n"
|
623
|
+
if select_options.empty?
|
624
|
+
print "The value '#{input}' matched 0 options.\n"
|
625
|
+
# print "Please try again.\n"
|
626
|
+
else
|
627
|
+
print "The value '#{input}' matched #{select_options.size()} options.\n"
|
628
|
+
print "Perhaps you meant one of these? #{ored_list(select_options.collect {|i|i['name']}, 3)}\n"
|
629
|
+
# print "Please try again.\n"
|
630
|
+
end
|
631
|
+
print "\n"
|
632
|
+
exit 1
|
633
|
+
else
|
634
|
+
#help_prompt(option_type)
|
635
|
+
display_select_options(option_type, select_options)
|
636
|
+
print "\n"
|
637
|
+
if select_options.empty?
|
638
|
+
print "The value '#{input}' matched 0 options.\n"
|
639
|
+
print "Please try again.\n"
|
640
|
+
else
|
641
|
+
print "The value '#{input}' matched #{select_options.size()} options.\n"
|
642
|
+
print "Perhaps you meant one of these? #{ored_list(select_options.collect {|i|i['name']}, 3)}\n"
|
643
|
+
print "Please try again.\n"
|
644
|
+
end
|
645
|
+
print "\n"
|
646
|
+
# reprompting now...
|
647
|
+
end
|
648
|
+
end
|
649
|
+
end # end while !value_found
|
650
|
+
|
651
|
+
# wrap in object when using fieldInput
|
652
|
+
if value && !option_type['fieldInput'].nil?
|
653
|
+
value = {option_type['fieldName'].split('.').last => value, option_type['fieldInput'] => (no_prompt ? option_type['defaultInputValue'] : field_input_prompt(option_type))}
|
654
|
+
end
|
655
|
+
value
|
656
|
+
end
|
657
|
+
|
461
658
|
# this is a funky one, the user is prompted for yes/no
|
462
659
|
# but the return value is 'on','off',nil
|
463
660
|
# todo: maybe make this easier to use, and have the api's be flexible too..
|
@@ -544,6 +741,9 @@ module Morpheus
|
|
544
741
|
# value = input.empty? ? option_type['defaultValue'] : input
|
545
742
|
if input == '?' && value.nil?
|
546
743
|
help_prompt(option_type)
|
744
|
+
elsif input.chomp == '' && value.nil?
|
745
|
+
# just hit enter right away to skip this
|
746
|
+
value_found = true
|
547
747
|
elsif input.chomp == 'EOF'
|
548
748
|
value_found = true
|
549
749
|
else
|
@@ -682,6 +882,42 @@ module Morpheus
|
|
682
882
|
return file_params
|
683
883
|
end
|
684
884
|
|
885
|
+
def self.load_options(option_type, api_client, api_params, query_value=nil)
|
886
|
+
select_options = []
|
887
|
+
# local array of options
|
888
|
+
if option_type['selectOptions']
|
889
|
+
# calculate from inline lambda
|
890
|
+
if option_type['selectOptions'].is_a?(Proc)
|
891
|
+
select_options = option_type['selectOptions'].call(api_client, grails_params(api_params || {}))
|
892
|
+
else
|
893
|
+
select_options = option_type['selectOptions']
|
894
|
+
end
|
895
|
+
# filter options ourselves
|
896
|
+
if query_value.to_s != ""
|
897
|
+
filtered_options = select_options.select { |it| it['value'].to_s == query_value.to_s }
|
898
|
+
if filtered_options.empty?
|
899
|
+
filtered_options = select_options.select { |it| it['name'].to_s == query_value.to_s }
|
900
|
+
end
|
901
|
+
select_options = filtered_options
|
902
|
+
end
|
903
|
+
elsif option_type['optionSource']
|
904
|
+
# calculate from inline lambda
|
905
|
+
if option_type['optionSource'].is_a?(Proc)
|
906
|
+
select_options = option_type['optionSource'].call(api_client, grails_params(api_params || {}))
|
907
|
+
elsif option_type['optionSource'] == 'list'
|
908
|
+
# /api/options/list is a special action for custom OptionTypeLists, just need to pass the optionTypeId parameter
|
909
|
+
select_options = load_source_options(option_type['optionSource'], api_client, grails_params(api_params || {}).merge({'optionTypeId' => option_type['id']}))
|
910
|
+
else
|
911
|
+
# remote optionSource aka /api/options/$optionSource?
|
912
|
+
select_options = load_source_options(option_type['optionSource'], api_client, grails_params(api_params || {}))
|
913
|
+
end
|
914
|
+
else
|
915
|
+
raise "option '#{field_key}' is type: 'typeahead' and missing selectOptions or optionSource!"
|
916
|
+
end
|
917
|
+
|
918
|
+
return select_options
|
919
|
+
end
|
920
|
+
|
685
921
|
def self.help_prompt(option_type)
|
686
922
|
field_key = [option_type['fieldContext'], option_type['fieldName']].select {|it| it && it != '' }.join('.')
|
687
923
|
help_field_key = option_type[:help_field_prefix] ? "#{option_type[:help_field_prefix]}.#{field_key}" : field_key
|
@@ -691,6 +927,11 @@ module Morpheus
|
|
691
927
|
else
|
692
928
|
print Term::ANSIColor.green," * #{option_type['fieldLabel']} [-O #{help_field_key}=] - ", Term::ANSIColor.reset , "#{option_type['description']}\n"
|
693
929
|
end
|
930
|
+
if option_type['type'].to_s == 'typeahead'
|
931
|
+
print "This is a typeahead input. Enter the name or value of an option.\n"
|
932
|
+
print "If the specified input matches more than one option, they will be printed and you will be prompted again.\n"
|
933
|
+
print "the matching options will be shown and you can try again.\n"
|
934
|
+
end
|
694
935
|
end
|
695
936
|
|
696
937
|
|
@@ -698,7 +939,8 @@ module Morpheus
|
|
698
939
|
api_client.options.options_for_source(source,params)['data']
|
699
940
|
end
|
700
941
|
|
701
|
-
def self.
|
942
|
+
def self.format_select_options_help(opt, select_options = [], paging = nil)
|
943
|
+
out = ""
|
702
944
|
header = opt['fieldLabel'] ? "#{opt['fieldLabel']} Options" : "Options"
|
703
945
|
if paging
|
704
946
|
offset = paging[:cur_page] * paging[:page_size]
|
@@ -706,11 +948,18 @@ module Morpheus
|
|
706
948
|
header = "#{header} (#{offset+1}-#{limit+1} of #{paging[:total]})"
|
707
949
|
select_options = select_options[(offset)..(limit)]
|
708
950
|
end
|
709
|
-
|
710
|
-
|
951
|
+
out = ""
|
952
|
+
out << "\n"
|
953
|
+
out << "#{header}\n"
|
954
|
+
out << "===============\n"
|
711
955
|
select_options.each do |option|
|
712
|
-
|
956
|
+
out << " * #{option['name']} [#{option['value']}]\n"
|
713
957
|
end
|
958
|
+
return out
|
959
|
+
end
|
960
|
+
|
961
|
+
def self.display_select_options(opt, select_options = [], paging = nil)
|
962
|
+
puts format_select_options_help(opt, select_options, paging)
|
714
963
|
end
|
715
964
|
|
716
965
|
def self.format_option_types_help(option_types, opts={})
|