morpheus-cli 4.2.21 → 5.2.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/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={})
|