morpheus-cli 5.0.0 → 5.2.2
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/.gitignore +1 -0
- data/Dockerfile +1 -1
- data/lib/morpheus/api/api_client.rb +16 -0
- data/lib/morpheus/api/billing_interface.rb +1 -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/invoices_interface.rb +12 -3
- 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 +6 -2
- data/lib/morpheus/cli/apps.rb +3 -23
- data/lib/morpheus/cli/budgets_command.rb +389 -319
- data/lib/morpheus/cli/{catalog_command.rb → catalog_item_types_command.rb} +182 -67
- data/lib/morpheus/cli/cli_command.rb +51 -10
- data/lib/morpheus/cli/commands/standard/curl_command.rb +26 -13
- data/lib/morpheus/cli/commands/standard/history_command.rb +9 -3
- data/lib/morpheus/cli/commands/standard/man_command.rb +74 -40
- data/lib/morpheus/cli/containers_command.rb +0 -24
- data/lib/morpheus/cli/cypher_command.rb +6 -2
- data/lib/morpheus/cli/dashboard_command.rb +260 -20
- 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 +271 -39
- data/lib/morpheus/cli/instances.rb +228 -129
- data/lib/morpheus/cli/invoices_command.rb +100 -20
- data/lib/morpheus/cli/jobs_command.rb +94 -92
- data/lib/morpheus/cli/library_option_lists_command.rb +1 -1
- data/lib/morpheus/cli/library_option_types_command.rb +10 -5
- data/lib/morpheus/cli/logs_command.rb +9 -6
- data/lib/morpheus/cli/mixins/accounts_helper.rb +5 -1
- data/lib/morpheus/cli/mixins/deployments_helper.rb +31 -2
- data/lib/morpheus/cli/mixins/print_helper.rb +13 -27
- data/lib/morpheus/cli/mixins/provisioning_helper.rb +108 -5
- data/lib/morpheus/cli/option_types.rb +271 -22
- data/lib/morpheus/cli/remote.rb +35 -10
- data/lib/morpheus/cli/reports_command.rb +99 -30
- data/lib/morpheus/cli/roles.rb +193 -155
- data/lib/morpheus/cli/search_command.rb +182 -0
- data/lib/morpheus/cli/service_catalog_command.rb +1474 -0
- data/lib/morpheus/cli/setup.rb +1 -1
- data/lib/morpheus/cli/shell.rb +33 -11
- data/lib/morpheus/cli/tasks.rb +29 -32
- data/lib/morpheus/cli/usage_command.rb +64 -11
- 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 +33 -40
- data/lib/morpheus/formatters.rb +75 -18
- data/lib/morpheus/terminal.rb +6 -2
- metadata +10 -4
- data/lib/morpheus/cli/mixins/catalog_helper.rb +0 -66
@@ -270,7 +270,7 @@ class Morpheus::Cli::LibraryOptionListsCommand
|
|
270
270
|
else
|
271
271
|
payload = {}
|
272
272
|
payload.deep_merge!({'optionTypeList' => parse_passed_options(options)})
|
273
|
-
list_payload = Morpheus::Cli::OptionTypes.no_prompt(
|
273
|
+
list_payload = Morpheus::Cli::OptionTypes.no_prompt(update_option_type_list_option_types(), options[:options], @api_client)
|
274
274
|
if list_payload['type'] == 'rest'
|
275
275
|
# parse Source Headers
|
276
276
|
if !(payload['optionTypeList']['config'] && payload['optionTypeList']['config']['sourceHeaders'])
|
@@ -129,7 +129,7 @@ class Morpheus::Cli::LibraryOptionTypesCommand
|
|
129
129
|
|
130
130
|
print_h1 "Option Type Details"
|
131
131
|
print cyan
|
132
|
-
|
132
|
+
columns = {
|
133
133
|
"ID" => 'id',
|
134
134
|
"Name" => 'name',
|
135
135
|
"Description" => 'description',
|
@@ -138,11 +138,15 @@ class Morpheus::Cli::LibraryOptionTypesCommand
|
|
138
138
|
# "Field Name" => 'fieldName',
|
139
139
|
"Full Field Name" => lambda {|it| [it['fieldContext'], it['fieldName']].select {|it| !it.to_s.empty? }.join('.') },
|
140
140
|
"Type" => lambda {|it| it['type'].to_s.capitalize },
|
141
|
+
"Option List" => lambda {|it| it['optionList'] ? it['optionList']['name'] : nil },
|
141
142
|
"Placeholder" => 'placeHolder',
|
143
|
+
"Help Block" => 'helpBlock',
|
142
144
|
"Default Value" => 'defaultValue',
|
143
145
|
"Required" => lambda {|it| format_boolean(it['required']) },
|
144
146
|
"Export As Tag" => lambda {|it| it['exportMeta'].nil? ? '' : format_boolean(it['exportMeta']) },
|
145
|
-
}
|
147
|
+
}
|
148
|
+
columns.delete("Option List") if option_type['optionList'].nil?
|
149
|
+
print as_description_list(option_type, columns, options)
|
146
150
|
print reset,"\n"
|
147
151
|
return 0
|
148
152
|
rescue RestClient::Exception => e
|
@@ -294,9 +298,10 @@ class Morpheus::Cli::LibraryOptionTypesCommand
|
|
294
298
|
{'fieldName' => 'optionList', 'fieldLabel' => 'Option List', 'type' => 'select', 'optionSource' => 'optionTypeLists', 'required' => true, 'dependsOnCode' => 'optionType.type:select', 'description' => "The Option List to be the source of options when type is 'select'.", 'displayOrder' => 5},
|
295
299
|
{'fieldName' => 'fieldLabel', 'fieldLabel' => 'Field Label', 'type' => 'text', 'required' => true, 'description' => 'This is the input label that shows typically to the left of a custom option.', 'displayOrder' => 6},
|
296
300
|
{'fieldName' => 'placeHolder', 'fieldLabel' => 'Placeholder', 'type' => 'text', 'displayOrder' => 7},
|
297
|
-
{'fieldName' => '
|
298
|
-
{'fieldName' => '
|
299
|
-
{'fieldName' => '
|
301
|
+
{'fieldName' => 'helpBlock', 'fieldLabel' => 'Help Block', 'type' => 'text', 'description' => 'This is the explaination of the input that shows typically underneath the option.', 'displayOrder' => 8},
|
302
|
+
{'fieldName' => 'defaultValue', 'fieldLabel' => 'Default Value', 'type' => 'text', 'displayOrder' => 9},
|
303
|
+
{'fieldName' => 'required', 'fieldLabel' => 'Required', 'type' => 'checkbox', 'defaultValue' => false, 'displayOrder' => 10},
|
304
|
+
{'fieldName' => 'exportMeta', 'fieldLabel' => 'Export As Tag', 'type' => 'checkbox', 'defaultValue' => false, 'description' => 'Export as Tag.', 'displayOrder' => 11},
|
300
305
|
]
|
301
306
|
end
|
302
307
|
|
@@ -34,7 +34,7 @@ class Morpheus::Cli::LogsCommand
|
|
34
34
|
options = {}
|
35
35
|
params = {}
|
36
36
|
optparse = Morpheus::Cli::OptionParser.new do |opts|
|
37
|
-
opts.banner = subcommand_usage("[
|
37
|
+
opts.banner = subcommand_usage("[search]")
|
38
38
|
opts.on('--hosts HOSTS', String, "Filter logs to specific Host ID(s)") do |val|
|
39
39
|
params['servers'] = val.to_s.split(",").collect {|it| it.to_s.strip }.select {|it| it }.compact
|
40
40
|
end
|
@@ -72,18 +72,21 @@ class Morpheus::Cli::LogsCommand
|
|
72
72
|
options[:details] = true
|
73
73
|
end
|
74
74
|
build_common_options(opts, options, [:list, :query, :json, :yaml, :csv, :fields, :dry_run, :remote])
|
75
|
-
opts.footer = "List logs for
|
76
|
-
"[id] is required. This is the id of a container."
|
75
|
+
opts.footer = "List logs for all hosts and containers."
|
77
76
|
end
|
78
77
|
optparse.parse!(args)
|
79
|
-
if args.count
|
80
|
-
|
78
|
+
if args.count > 0
|
79
|
+
options[:phrase] = args.join(" ")
|
81
80
|
end
|
82
81
|
connect(options)
|
83
82
|
begin
|
84
83
|
params['level'] = params['level'].collect {|it| it.to_s.upcase }.join('|') if params['level'] # api works with INFO|WARN
|
85
84
|
params.merge!(parse_list_options(options))
|
86
|
-
|
85
|
+
if params['phrase']
|
86
|
+
options.delete(:phrase)
|
87
|
+
search_phrase = params.delete('phrase')
|
88
|
+
params['query'] = search_phrase
|
89
|
+
end
|
87
90
|
params['order'] = params['direction'] unless params['direction'].nil? # old api version expects order instead of direction
|
88
91
|
params['startMs'] = (options[:start].to_i * 1000) if options[:start]
|
89
92
|
params['endMs'] = (options[:end].to_i * 1000) if options[:end]
|
@@ -401,7 +401,7 @@ module Morpheus::Cli::AccountsHelper
|
|
401
401
|
end
|
402
402
|
|
403
403
|
def get_access_string(access, return_color=cyan)
|
404
|
-
get_access_color(access) + access + return_color
|
404
|
+
get_access_color(access) + access.to_s + return_color.to_s
|
405
405
|
# access ||= 'none'
|
406
406
|
# if access == 'none'
|
407
407
|
# "#{white}#{access.to_s}#{return_color}"
|
@@ -426,10 +426,14 @@ module Morpheus::Cli::AccountsHelper
|
|
426
426
|
# Examples: format_permission_access("read")
|
427
427
|
# format_permission_access("custom", "full,custom,none")
|
428
428
|
def format_access_string(access, access_levels=nil, return_color=cyan)
|
429
|
+
# nevermind all this, just colorized access level
|
430
|
+
return get_access_string(access, return_color)
|
431
|
+
|
429
432
|
access = access.to_s.downcase.strip
|
430
433
|
if access.empty?
|
431
434
|
access = "none"
|
432
435
|
end
|
436
|
+
|
433
437
|
if access_levels.nil?
|
434
438
|
access_levels = ["none","read","user","full"]
|
435
439
|
elsif access_levels.is_a?(Array)
|
@@ -51,9 +51,10 @@ module Morpheus::Cli::DeploymentsHelper
|
|
51
51
|
return nil
|
52
52
|
elsif deployments.size > 1
|
53
53
|
print_red_alert "#{deployments.size} deployments found by name '#{name}'"
|
54
|
+
print_error "\n"
|
54
55
|
puts_error as_pretty_table(deployments, [:id, :name], {color:red})
|
55
56
|
print_red_alert "Try using ID instead"
|
56
|
-
|
57
|
+
print_error reset,"\n"
|
57
58
|
return nil
|
58
59
|
else
|
59
60
|
return deployments[0]
|
@@ -122,13 +123,41 @@ module Morpheus::Cli::DeploymentsHelper
|
|
122
123
|
return nil
|
123
124
|
elsif deployment_versions.size > 1
|
124
125
|
print_red_alert "#{deployment_versions.size} deployment versions found by version '#{name}'"
|
126
|
+
print_error "\n"
|
125
127
|
puts_error as_pretty_table(deployment_versions, {"ID" => 'id', "VERSION" => 'userVersion'}, {color:red})
|
126
128
|
print_red_alert "Try using ID instead"
|
127
|
-
|
129
|
+
print_error reset,"\n"
|
128
130
|
return nil
|
129
131
|
else
|
130
132
|
return deployment_versions[0]
|
131
133
|
end
|
132
134
|
end
|
133
135
|
|
136
|
+
def format_deployment_version_number(deployment_version)
|
137
|
+
if deployment_version
|
138
|
+
deployment_version['userVersion'] || deployment_version['version'] || ''
|
139
|
+
else
|
140
|
+
''
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
def format_app_deploy_status(status, return_color=cyan)
|
145
|
+
out = ""
|
146
|
+
s = status.to_s.downcase
|
147
|
+
if s == 'deployed' || s == 'committed'
|
148
|
+
out << "#{green}#{s.upcase}#{return_color}"
|
149
|
+
elsif s == 'open' || s == 'archived'
|
150
|
+
out << "#{cyan}#{s.upcase}#{return_color}"
|
151
|
+
elsif s == 'failed'
|
152
|
+
out << "#{red}#{s.upcase}#{return_color}"
|
153
|
+
else
|
154
|
+
out << "#{yellow}#{s.upcase}#{return_color}"
|
155
|
+
end
|
156
|
+
out
|
157
|
+
end
|
158
|
+
|
159
|
+
def format_deploy_type(val)
|
160
|
+
return val
|
161
|
+
end
|
162
|
+
|
134
163
|
end
|
@@ -448,7 +448,8 @@ module Morpheus::Cli::PrintHelper
|
|
448
448
|
|
449
449
|
if opts[:bar_color] == :rainbow
|
450
450
|
rainbow_bar = ""
|
451
|
-
cur_rainbow_color =
|
451
|
+
cur_rainbow_color = reset # default terminal color
|
452
|
+
rainbow_bar << cur_rainbow_color
|
452
453
|
bars.each_with_index {|bar, i|
|
453
454
|
reached_percent = (i / max_bars.to_f) * 100
|
454
455
|
new_bar_color = cur_rainbow_color
|
@@ -458,6 +459,8 @@ module Morpheus::Cli::PrintHelper
|
|
458
459
|
new_bar_color = yellow
|
459
460
|
elsif reached_percent > 10
|
460
461
|
new_bar_color = cyan
|
462
|
+
else
|
463
|
+
new_bar_color = reset
|
461
464
|
end
|
462
465
|
if cur_rainbow_color != new_bar_color
|
463
466
|
cur_rainbow_color = new_bar_color
|
@@ -471,7 +474,7 @@ module Morpheus::Cli::PrintHelper
|
|
471
474
|
#rainbow_bar << " " * padding
|
472
475
|
end
|
473
476
|
rainbow_bar << reset
|
474
|
-
bar_display =
|
477
|
+
bar_display = cyan + "[" + rainbow_bar + cyan + "]" + " #{cur_rainbow_color}#{percent_label}#{reset}"
|
475
478
|
out << bar_display
|
476
479
|
elsif opts[:bar_color] == :solid
|
477
480
|
bar_color = cyan
|
@@ -479,12 +482,16 @@ module Morpheus::Cli::PrintHelper
|
|
479
482
|
bar_color = red
|
480
483
|
elsif percent > 50
|
481
484
|
bar_color = yellow
|
485
|
+
elsif percent > 10
|
486
|
+
bar_color = cyan
|
487
|
+
else
|
488
|
+
bar_color = reset
|
482
489
|
end
|
483
|
-
bar_display =
|
490
|
+
bar_display = cyan + "[" + bar_color + bars.join.ljust(max_bars, ' ') + cyan + "]" + " #{percent_label}" + reset
|
484
491
|
out << bar_display
|
485
492
|
else
|
486
|
-
bar_color = opts[:bar_color] ||
|
487
|
-
bar_display =
|
493
|
+
bar_color = opts[:bar_color] || reset
|
494
|
+
bar_display = cyan + "[" + bar_color + bars.join.ljust(max_bars, ' ') + cyan + "]" + " #{percent_label}" + reset
|
488
495
|
out << bar_display
|
489
496
|
end
|
490
497
|
return out
|
@@ -504,7 +511,7 @@ module Morpheus::Cli::PrintHelper
|
|
504
511
|
out << cyan + "Max CPU".rjust(label_width, ' ') + ": " + generate_usage_bar(cpu_usage.to_f, 100) + "\n"
|
505
512
|
end
|
506
513
|
if opts[:include].include?(:avg_cpu)
|
507
|
-
cpu_usage = stats['cpuUsageAvg']
|
514
|
+
cpu_usage = stats['cpuUsageAvg'] || stats['cpuUsageAverage']
|
508
515
|
out << cyan + "Avg. CPU".rjust(label_width, ' ') + ": " + generate_usage_bar(cpu_usage.to_f, 100) + "\n"
|
509
516
|
end
|
510
517
|
if opts[:include].include?(:cpu)
|
@@ -1151,27 +1158,6 @@ module Morpheus::Cli::PrintHelper
|
|
1151
1158
|
out
|
1152
1159
|
end
|
1153
1160
|
|
1154
|
-
def format_list(items, conjunction="and", limit=nil)
|
1155
|
-
items = items ? items.clone : []
|
1156
|
-
if limit
|
1157
|
-
items = items.first(limit)
|
1158
|
-
end
|
1159
|
-
last_item = items.pop
|
1160
|
-
if items.empty?
|
1161
|
-
return "#{last_item}"
|
1162
|
-
else
|
1163
|
-
return items.join(", ") + (conjunction.to_s.empty? ? ", " : " #{conjunction} ") + "#{last_item}" + ((limit && limit < (items.size+1)) ? " ..." : "")
|
1164
|
-
end
|
1165
|
-
end
|
1166
|
-
|
1167
|
-
def anded_list(items, limit=nil)
|
1168
|
-
format_list(items, "and", limit)
|
1169
|
-
end
|
1170
|
-
|
1171
|
-
def ored_list(items, limit=nil)
|
1172
|
-
format_list(items, "or", limit)
|
1173
|
-
end
|
1174
|
-
|
1175
1161
|
def sleep_with_dots(sleep_seconds, dots=3, dot_chr=".")
|
1176
1162
|
dot_interval = (sleep_seconds.to_f / dots.to_i)
|
1177
1163
|
dots.to_i.times do |dot_index|
|
@@ -596,12 +596,12 @@ module Morpheus::Cli::ProvisioningHelper
|
|
596
596
|
v_prompt = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'environment', 'fieldLabel' => 'Environment', 'type' => 'select', 'required' => false, 'selectOptions' => get_available_environments()}], options[:options])
|
597
597
|
payload['instance']['instanceContext'] = v_prompt['environment'] if !v_prompt['environment'].empty?
|
598
598
|
|
599
|
-
# Labels (tags)
|
600
|
-
if options[:
|
601
|
-
payload['instance']['
|
599
|
+
# Labels (used to be called tags)
|
600
|
+
if options[:labels]
|
601
|
+
payload['instance']['labels'] = options[:labels].is_a?(Array) ? options[:labels] : options[:labels].to_s.split(',').collect {|it| it.to_s.strip }.compact.uniq
|
602
602
|
else
|
603
|
-
v_prompt = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => '
|
604
|
-
payload['instance']['
|
603
|
+
v_prompt = Morpheus::Cli::OptionTypes.prompt([{'fieldName' => 'labels', 'fieldLabel' => 'Labels', 'type' => 'text', 'required' => false}], options[:options])
|
604
|
+
payload['instance']['labels'] = v_prompt['labels'].split(',').collect {|it| it.to_s.strip }.compact.uniq if !v_prompt['labels'].empty?
|
605
605
|
end
|
606
606
|
|
607
607
|
# Version and Layout
|
@@ -976,6 +976,9 @@ module Morpheus::Cli::ProvisioningHelper
|
|
976
976
|
metadata_list = options[:metadata].split(",").select {|it| !it.to_s.empty? }
|
977
977
|
metadata_list = metadata_list.collect do |it|
|
978
978
|
metadata_pair = it.split(":")
|
979
|
+
if metadata_pair.size < 2 && it.include?("=")
|
980
|
+
metadata_pair = it.split("=")
|
981
|
+
end
|
979
982
|
row = {}
|
980
983
|
row['name'] = metadata_pair[0].to_s.strip
|
981
984
|
row['value'] = metadata_pair[1].to_s.strip
|
@@ -1688,6 +1691,15 @@ module Morpheus::Cli::ProvisioningHelper
|
|
1688
1691
|
row = {}
|
1689
1692
|
row['name'] = metadata_pair[0].to_s.strip
|
1690
1693
|
row['value'] = metadata_pair[1].to_s.strip
|
1694
|
+
# hacky way to set masked flag to true of false to (masked) in the value itself
|
1695
|
+
if(row['value'].include?("(masked)"))
|
1696
|
+
row['value'] = row['value'].gsub("(masked)", "").strip
|
1697
|
+
row['masked'] = true
|
1698
|
+
end
|
1699
|
+
if(row['value'].include?("(unmasked)"))
|
1700
|
+
row['value'] = row['value'].gsub("(unmasked)", "").strip
|
1701
|
+
row['masked'] = false
|
1702
|
+
end
|
1691
1703
|
row
|
1692
1704
|
end
|
1693
1705
|
metadata = metadata_list
|
@@ -2175,6 +2187,82 @@ module Morpheus::Cli::ProvisioningHelper
|
|
2175
2187
|
return ports
|
2176
2188
|
end
|
2177
2189
|
|
2190
|
+
def format_instance_status(instance, return_color=cyan)
|
2191
|
+
out = ""
|
2192
|
+
status_string = instance['status'].to_s
|
2193
|
+
if status_string == 'running'
|
2194
|
+
out << "#{green}#{status_string.upcase}#{return_color}"
|
2195
|
+
elsif status_string == 'provisioning'
|
2196
|
+
out << "#{cyan}#{status_string.upcase}#{return_color}"
|
2197
|
+
elsif status_string == 'stopped' or status_string == 'failed'
|
2198
|
+
out << "#{red}#{status_string.upcase}#{return_color}"
|
2199
|
+
else
|
2200
|
+
out << "#{yellow}#{status_string.upcase}#{return_color}"
|
2201
|
+
end
|
2202
|
+
out
|
2203
|
+
end
|
2204
|
+
|
2205
|
+
def format_instance_connection_string(instance)
|
2206
|
+
if !instance['connectionInfo'].nil? && instance['connectionInfo'].empty? == false
|
2207
|
+
connection_string = "#{instance['connectionInfo'][0]['ip']}:#{instance['connectionInfo'][0]['port']}"
|
2208
|
+
end
|
2209
|
+
end
|
2210
|
+
|
2211
|
+
def format_app_status(app, return_color=cyan)
|
2212
|
+
out = ""
|
2213
|
+
status_string = app['status'] || app['appStatus'] || ''
|
2214
|
+
if status_string == 'running'
|
2215
|
+
out = "#{green}#{status_string.upcase}#{return_color}"
|
2216
|
+
elsif status_string == 'provisioning'
|
2217
|
+
out = "#{cyan}#{status_string.upcase}#{cyan}"
|
2218
|
+
elsif status_string == 'stopped' or status_string == 'failed'
|
2219
|
+
out = "#{red}#{status_string.upcase}#{return_color}"
|
2220
|
+
elsif status_string == 'unknown'
|
2221
|
+
out = "#{yellow}#{status_string.upcase}#{return_color}"
|
2222
|
+
elsif status_string == 'warning' && app['instanceCount'].to_i == 0
|
2223
|
+
# show this instead of WARNING
|
2224
|
+
out = "#{cyan}EMPTY#{return_color}"
|
2225
|
+
else
|
2226
|
+
out = "#{yellow}#{status_string.upcase}#{return_color}"
|
2227
|
+
end
|
2228
|
+
out
|
2229
|
+
end
|
2230
|
+
|
2231
|
+
def format_container_status(container, return_color=cyan)
|
2232
|
+
out = ""
|
2233
|
+
status_string = container['status'].to_s
|
2234
|
+
if status_string == 'running'
|
2235
|
+
out << "#{green}#{status_string.upcase}#{return_color}"
|
2236
|
+
elsif status_string == 'provisioning'
|
2237
|
+
out << "#{cyan}#{status_string.upcase}#{return_color}"
|
2238
|
+
elsif status_string == 'stopped' or status_string == 'failed'
|
2239
|
+
out << "#{red}#{status_string.upcase}#{return_color}"
|
2240
|
+
else
|
2241
|
+
out << "#{yellow}#{status_string.upcase}#{return_color}"
|
2242
|
+
end
|
2243
|
+
out
|
2244
|
+
end
|
2245
|
+
|
2246
|
+
def format_container_connection_string(container)
|
2247
|
+
if !container['ports'].nil? && container['ports'].empty? == false
|
2248
|
+
connection_string = "#{container['ip']}:#{container['ports'][0]['external']}"
|
2249
|
+
else
|
2250
|
+
# eh? more logic needed here i think, see taglib morph:containerLocationMenu
|
2251
|
+
connection_string = "#{container['ip']}"
|
2252
|
+
end
|
2253
|
+
end
|
2254
|
+
|
2255
|
+
def format_instance_container_display_name(instance, plural=false)
|
2256
|
+
#<span class="info-label">${[null,'docker'].contains(instance.layout?.provisionType?.code) ? 'Containers' : 'Virtual Machines'}:</span> <span class="info-value">${instance.containers?.size()}</span>
|
2257
|
+
v = plural ? "Containers" : "Container"
|
2258
|
+
if instance && instance['layout'] && instance['layout'].key?("provisionTypeCode")
|
2259
|
+
if [nil, 'docker'].include?(instance['layout']["provisionTypeCode"])
|
2260
|
+
v = plural ? "Virtual Machines" : "Virtual Machine"
|
2261
|
+
end
|
2262
|
+
end
|
2263
|
+
return v
|
2264
|
+
end
|
2265
|
+
|
2178
2266
|
def format_blueprint_type(type_code)
|
2179
2267
|
return type_code.to_s # just show it as is
|
2180
2268
|
if type_code.to_s.empty?
|
@@ -2200,4 +2288,19 @@ module Morpheus::Cli::ProvisioningHelper
|
|
2200
2288
|
return type_code.to_s.downcase
|
2201
2289
|
end
|
2202
2290
|
end
|
2291
|
+
|
2292
|
+
def format_snapshot_status(snapshot, return_color=cyan)
|
2293
|
+
out = ""
|
2294
|
+
status_string = snapshot['status'].to_s
|
2295
|
+
if status_string == 'complete'
|
2296
|
+
out << "#{green}#{status_string.upcase}#{return_color}"
|
2297
|
+
elsif status_string == 'creating'
|
2298
|
+
out << "#{cyan}#{status_string.upcase}#{return_color}"
|
2299
|
+
elsif status_string == 'failed'
|
2300
|
+
out << "#{red}#{status_string.upcase}#{return_color}"
|
2301
|
+
else
|
2302
|
+
out << "#{yellow}#{status_string.upcase}#{return_color}"
|
2303
|
+
end
|
2304
|
+
out
|
2305
|
+
end
|
2203
2306
|
end
|
@@ -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={})
|