morpheus-cli 5.2.1 → 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/Dockerfile +1 -1
- data/lib/morpheus/api/invoices_interface.rb +12 -3
- data/lib/morpheus/cli/budgets_command.rb +389 -319
- data/lib/morpheus/cli/commands/standard/curl_command.rb +25 -10
- data/lib/morpheus/cli/commands/standard/history_command.rb +6 -2
- data/lib/morpheus/cli/dashboard_command.rb +260 -20
- data/lib/morpheus/cli/invoices_command.rb +63 -1
- data/lib/morpheus/cli/jobs_command.rb +94 -92
- data/lib/morpheus/cli/library_option_types_command.rb +5 -3
- data/lib/morpheus/cli/mixins/print_helper.rb +13 -6
- data/lib/morpheus/cli/version.rb +1 -1
- data/lib/morpheus/formatters.rb +21 -11
- metadata +2 -2
@@ -26,8 +26,8 @@ class Morpheus::Cli::CurlCommand
|
|
26
26
|
options = {}
|
27
27
|
optparse = Morpheus::Cli::OptionParser.new do|opts|
|
28
28
|
opts.banner = "Usage: morpheus curl [path] -- [*args]"
|
29
|
-
opts.on( '-p', '--pretty', "Print result as parsed JSON." ) do
|
30
|
-
options[:
|
29
|
+
opts.on( '-p', '--pretty', "Print result as parsed JSON. Alias for -j" ) do
|
30
|
+
options[:json] = true
|
31
31
|
end
|
32
32
|
opts.on( '-X', '--request METHOD', "HTTP request method. Default is GET" ) do |val|
|
33
33
|
curl_method = val
|
@@ -134,29 +134,44 @@ EOT
|
|
134
134
|
print reset
|
135
135
|
return 0
|
136
136
|
end
|
137
|
+
exit_code, err = 0, nil
|
137
138
|
# print cyan
|
138
139
|
# print "#{cyan}#{curl_cmd_str}#{reset}"
|
139
140
|
# print "\n\n"
|
140
141
|
print reset
|
141
142
|
# print result
|
142
143
|
curl_output = `#{curl_cmd}`
|
143
|
-
|
144
|
+
|
145
|
+
if $?.success? != true
|
146
|
+
exit_code = $?.exitstatus
|
147
|
+
err = "curl command exited non-zero"
|
148
|
+
end
|
149
|
+
json_response = {}
|
150
|
+
other_output = nil
|
151
|
+
if options[:json] || options[:yaml] || options[:csv]
|
144
152
|
output_lines = curl_output.split("\n")
|
145
153
|
last_line = output_lines.pop
|
146
154
|
if output_lines.size > 0
|
147
|
-
|
155
|
+
other_output = output_lines.join("\n")
|
148
156
|
end
|
149
157
|
begin
|
150
|
-
|
158
|
+
json_response = JSON.parse(last_line)
|
151
159
|
rescue => ex
|
152
|
-
|
153
|
-
|
160
|
+
puts_error curl_output
|
161
|
+
print_red_alert "failed to parse curl result as JSON data Error: #{ex.message}"
|
162
|
+
|
163
|
+
exit_code = 2
|
164
|
+
err = "failed to parse curl result as JSON data Error: #{ex.message}"
|
165
|
+
return exit_code, err
|
154
166
|
end
|
155
167
|
else
|
156
|
-
|
168
|
+
other_output = curl_output
|
157
169
|
end
|
158
|
-
|
159
|
-
|
170
|
+
curl_object_key = nil # json_response.keys.first
|
171
|
+
render_response(json_response, options, curl_object_key) do
|
172
|
+
puts other_output
|
173
|
+
end
|
174
|
+
return exit_code, err
|
160
175
|
end
|
161
176
|
|
162
177
|
def command_available?(cmd)
|
@@ -15,7 +15,7 @@ class Morpheus::Cli::HistoryCommand
|
|
15
15
|
def handle(args)
|
16
16
|
options = {show_pagination:false}
|
17
17
|
optparse = Morpheus::Cli::OptionParser.new do|opts|
|
18
|
-
opts.banner = "Usage: morpheus #{command_name}"
|
18
|
+
opts.banner = "Usage: morpheus #{command_name} [search]"
|
19
19
|
# -n is a hidden alias for -m
|
20
20
|
opts.on( '-n', '--max-commands MAX', "Alias for -m, --max option." ) do |val|
|
21
21
|
options[:max] = val
|
@@ -35,6 +35,7 @@ The --flush option can be used to purge the history.
|
|
35
35
|
Examples:
|
36
36
|
history
|
37
37
|
history -m 100
|
38
|
+
history "instances list"
|
38
39
|
history --flush
|
39
40
|
|
40
41
|
The most recently executed commands are seen by default. Use --reverse to see the oldest commands.
|
@@ -42,7 +43,10 @@ EOT
|
|
42
43
|
end
|
43
44
|
raw_cmd = "#{command_name} #{args.join(' ')}"
|
44
45
|
optparse.parse!(args)
|
45
|
-
verify_args!(args:args, count: 0, optparse:optparse)
|
46
|
+
# verify_args!(args:args, count: 0, optparse:optparse)
|
47
|
+
if args.count > 0
|
48
|
+
options[:phrase] = args.join(" ")
|
49
|
+
end
|
46
50
|
if options[:do_flush]
|
47
51
|
command_count = Morpheus::Cli::Shell.instance.history_commands_count
|
48
52
|
unless options[:yes] || Morpheus::Cli::OptionTypes.confirm("Are you sure you want to flush your command history (#{format_number(command_count)} #{command_count == 1 ? 'command' : 'commands'})?")
|
@@ -4,8 +4,9 @@ require 'json'
|
|
4
4
|
|
5
5
|
class Morpheus::Cli::DashboardCommand
|
6
6
|
include Morpheus::Cli::CliCommand
|
7
|
+
include Morpheus::Cli::ProvisioningHelper
|
7
8
|
set_command_name :dashboard
|
8
|
-
|
9
|
+
set_command_description "View Morpheus Dashboard"
|
9
10
|
|
10
11
|
def initialize()
|
11
12
|
# @appliance_name, @appliance_url = Morpheus::Cli::Remote.active_appliance
|
@@ -28,34 +29,273 @@ class Morpheus::Cli::DashboardCommand
|
|
28
29
|
options = {}
|
29
30
|
optparse = Morpheus::Cli::OptionParser.new do |opts|
|
30
31
|
opts.banner = usage
|
31
|
-
|
32
|
+
opts.on('-a', '--details', "Display all details: more instance usage stats, etc" ) do
|
33
|
+
options[:details] = true
|
34
|
+
end
|
35
|
+
build_standard_list_options(opts, options)
|
36
|
+
opts.footer = <<-EOT
|
37
|
+
View Morpheus Dashboard.
|
38
|
+
This includes instance and backup counts, favorite instances, monitoring and recent activity.
|
39
|
+
EOT
|
32
40
|
end
|
33
41
|
optparse.parse!(args)
|
34
|
-
|
42
|
+
verify_args!(args:args, optparse:optparse, count:0)
|
35
43
|
connect(options)
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
44
|
+
params = {}
|
45
|
+
params.merge!(parse_list_options(options))
|
46
|
+
@dashboard_interface.setopts(options)
|
47
|
+
if options[:dry_run]
|
48
|
+
print_dry_run @dashboard_interface.dry.get(params)
|
49
|
+
return
|
50
|
+
end
|
51
|
+
json_response = @dashboard_interface.get(params)
|
52
|
+
render_response(json_response, options) do
|
53
|
+
print_h1 "Morpheus Dashboard", [], options
|
54
|
+
|
55
|
+
## STATUS
|
56
|
+
|
57
|
+
status_column_definitions = {
|
58
|
+
"Instances" => lambda {|it|
|
59
|
+
format_number(it['instanceStats']['total']) rescue nil
|
60
|
+
},
|
61
|
+
"Running" => lambda {|it|
|
62
|
+
format_number(it['instanceStats']['running']) rescue nil
|
63
|
+
},
|
64
|
+
# "Used Storage" => lambda {|it|
|
65
|
+
# ((it['instanceStats']['maxStorage'].to_i > 0) ? ((it['instanceStats']['usedStorage'].to_f / it['instanceStats']['maxStorage'].to_f) * 100).round(1) : 0).to_s + '%' rescue nil
|
66
|
+
# },
|
67
|
+
}
|
68
|
+
print as_description_list(json_response, status_column_definitions, options)
|
69
|
+
# print reset,"\n"
|
70
|
+
|
71
|
+
stats = json_response['instanceStats']
|
72
|
+
if stats
|
73
|
+
print_h2 "Instance Usage", options
|
74
|
+
print_stats_usage(stats, {include: [:max_cpu, :avg_cpu, :memory, :storage]})
|
42
75
|
end
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
76
|
+
|
77
|
+
|
78
|
+
|
79
|
+
open_incident_count = json_response['monitoring']['openIncidents'] rescue (json_response['appStatus']['openIncidents'] rescue nil)
|
80
|
+
|
81
|
+
avg_response_time = json_response['monitoring']['avgResponseTime'] rescue nil
|
82
|
+
warning_apps = json_response['monitoring']['warningApps'] rescue 0
|
83
|
+
warning_checks = json_response['monitoring']['warningChecks'] rescue 0
|
84
|
+
fail_checks = json_response['monitoring']['failChecks'] rescue 0
|
85
|
+
fail_apps = json_response['monitoring']['failApps'] rescue 0
|
86
|
+
success_checks = json_response['monitoring']['successChecks'] rescue 0
|
87
|
+
success_apps = json_response['monitoring']['successApps'] rescue 0
|
88
|
+
monitoring_status_color = cyan
|
89
|
+
if fail_checks > 0 || fail_apps > 0
|
90
|
+
monitoring_status_color = red
|
91
|
+
elsif warning_checks > 0 || warning_apps > 0
|
92
|
+
monitoring_status_color = yellow
|
93
|
+
end
|
94
|
+
|
95
|
+
print_h2 "Monitoring"
|
96
|
+
|
97
|
+
monitoring_column_definitions = {
|
98
|
+
"Status" => lambda {|it|
|
99
|
+
if fail_apps > 0 # || fail_checks > 0
|
100
|
+
# check_summary = [fail_apps > 0 ? "#{fail_apps} Apps" : nil,fail_checks > 0 ? "#{fail_checks} Checks" : nil].compact.join(", ")
|
101
|
+
# red + "ERROR" + " (" + check_summary + ")" + cyan
|
102
|
+
red + "ERROR" + cyan
|
103
|
+
elsif warning_apps > 0 || warning_checks > 0
|
104
|
+
# check_summary = [warning_apps > 0 ? "#{warning_apps} Apps" : nil,warning_checks > 0 ? "#{warning_checks} Checks" : nil].compact.join(", ")
|
105
|
+
# red + "WARNING" + " (" + check_summary + ")" + cyan
|
106
|
+
yellow + "WARNING" + cyan
|
107
|
+
else
|
108
|
+
cyan + "HEALTHY" + cyan
|
109
|
+
end
|
110
|
+
},
|
111
|
+
# "Availability" => lambda {|it|
|
112
|
+
# # todo
|
113
|
+
# },
|
114
|
+
"Response Time" => lambda {|it|
|
115
|
+
# format_number(avg_response_time).to_s + "ms"
|
116
|
+
(avg_response_time.round).to_s + "ms"
|
117
|
+
},
|
118
|
+
"Open Incidents" => lambda {|it|
|
119
|
+
monitoring_status_color = cyan
|
120
|
+
# if fail_checks > 0 || fail_apps > 0
|
121
|
+
# monitoring_status_color = red
|
122
|
+
# elsif warning_checks > 0 || warning_apps > 0
|
123
|
+
# monitoring_status_color = yellow
|
124
|
+
# end
|
125
|
+
if open_incident_count.nil?
|
126
|
+
yellow + "n/a" + cyan + "\n"
|
127
|
+
elsif open_incident_count == 0
|
128
|
+
monitoring_status_color + "0 Open Incidents" + cyan
|
129
|
+
elsif open_incident_count == 1
|
130
|
+
monitoring_status_color + "1 Open Incident" + cyan
|
131
|
+
else
|
132
|
+
monitoring_status_color + "#{open_incident_count} Open Incidents" + cyan
|
133
|
+
end
|
134
|
+
}
|
135
|
+
}
|
136
|
+
#print as_description_list(json_response, monitoring_column_definitions, options)
|
137
|
+
print as_pretty_table([json_response], monitoring_column_definitions.upcase_keys!, options)
|
138
|
+
|
139
|
+
|
140
|
+
if json_response['logStats']
|
141
|
+
# todo: should come from monitoring.startMs-endMs
|
142
|
+
log_period_display = "7 Days"
|
143
|
+
print_h2 "Logs (#{log_period_display})", options
|
144
|
+
error_log_data = json_response['logStats']['data'].find {|it| it['key'].to_s.upcase == 'ERROR' }
|
145
|
+
error_count = error_log_data["count"] rescue 0
|
146
|
+
fatal_log_data = json_response['logStats']['data'].find {|it| it['key'].to_s.upcase == 'FATAL' }
|
147
|
+
fatal_count = fatal_log_data["count"] rescue 0
|
148
|
+
# total error is actaully error + fatal
|
149
|
+
total_error_count = error_count + fatal_count
|
150
|
+
# if total_error_count.nil?
|
151
|
+
# print yellow + "n/a" + cyan + "\n"
|
152
|
+
# elsif total_error_count == 0
|
153
|
+
# print cyan + "0 Errors" + cyan + "\n"
|
154
|
+
# elsif total_error_count == 1
|
155
|
+
# print red + "1 Error" + cyan + "\n"
|
156
|
+
# else
|
157
|
+
# print red + "#{total_error_count} Errors" + cyan + "\n"
|
158
|
+
# end
|
159
|
+
if total_error_count == 0
|
160
|
+
print cyan + "(0 Errors)" + cyan + "\n"
|
161
|
+
#print cyan + "0-0-0-0-0-0-0-0 (0 Errors)" + cyan + "\n"
|
162
|
+
end
|
163
|
+
if error_count > 0
|
164
|
+
if error_log_data["values"]
|
165
|
+
log_plot = ""
|
166
|
+
plot_index = 0
|
167
|
+
error_log_data["values"].each do |k, v|
|
168
|
+
if v.to_i == 0
|
169
|
+
log_plot << cyan + v.to_s
|
170
|
+
else
|
171
|
+
log_plot << red + v.to_s
|
172
|
+
end
|
173
|
+
if plot_index != error_log_data["values"].size - 1
|
174
|
+
log_plot << cyan + "-"
|
175
|
+
end
|
176
|
+
plot_index +=1
|
177
|
+
end
|
178
|
+
print log_plot
|
179
|
+
print " "
|
180
|
+
if error_count == 0
|
181
|
+
print cyan + "(0 Errors)" + cyan
|
182
|
+
elsif error_count == 1
|
183
|
+
print red + "(1 Errors)" + cyan
|
184
|
+
else
|
185
|
+
print red + "(#{error_count} Errors)" + cyan
|
186
|
+
end
|
187
|
+
print reset + "\n"
|
188
|
+
end
|
189
|
+
end
|
190
|
+
if fatal_count > 0
|
191
|
+
if fatal_log_data["values"]
|
192
|
+
log_plot = ""
|
193
|
+
plot_index = 0
|
194
|
+
fatal_log_data["values"].each do |k, v|
|
195
|
+
if v.to_i == 0
|
196
|
+
log_plot << cyan + v.to_s
|
197
|
+
else
|
198
|
+
log_plot << red + v.to_s
|
199
|
+
end
|
200
|
+
if plot_index != fatal_log_data["values"].size - 1
|
201
|
+
log_plot << cyan + "-"
|
202
|
+
end
|
203
|
+
plot_index +=1
|
204
|
+
end
|
205
|
+
print log_plot
|
206
|
+
print " "
|
207
|
+
if fatal_count == 0
|
208
|
+
print cyan + "(0 FATAL)" + cyan
|
209
|
+
elsif fatal_count == 1
|
210
|
+
print red + "(1 FATAL)" + cyan
|
211
|
+
else
|
212
|
+
print red + "(#{fatal_count} FATAL)" + cyan
|
213
|
+
end
|
214
|
+
print reset + "\n"
|
215
|
+
end
|
216
|
+
end
|
217
|
+
end
|
218
|
+
|
219
|
+
print_h2 "Backups (7 Days)"
|
220
|
+
backup_status_column_definitions = {
|
221
|
+
# "Total" => lambda {|it|
|
222
|
+
# it['backups']['accountStats']['lastSevenDays']['completed'] rescue nil
|
223
|
+
# },
|
224
|
+
"Successful" => lambda {|it|
|
225
|
+
it['backups']['accountStats']['lastSevenDays']['successful'] rescue nil
|
226
|
+
},
|
227
|
+
"Failed" => lambda {|it|
|
228
|
+
n = it['backups']['accountStats']['lastSevenDays']['failed'] rescue nil
|
229
|
+
if n == 0
|
230
|
+
cyan + n.to_s + reset
|
231
|
+
else
|
232
|
+
red + n.to_s + reset
|
233
|
+
end
|
234
|
+
}
|
235
|
+
}
|
236
|
+
print as_description_list(json_response, backup_status_column_definitions, options)
|
237
|
+
#print as_pretty_table([json_response], backup_status_column_definitions, options)
|
238
|
+
# print reset,"\n"
|
239
|
+
|
240
|
+
favorite_instances = json_response["provisioning"]["favoriteInstances"] || [] rescue []
|
241
|
+
if favorite_instances.empty?
|
242
|
+
# print cyan, "No favorite instances.",reset,"\n"
|
47
243
|
else
|
244
|
+
print_h2 "My Instances"
|
245
|
+
favorite_instances_columns = {
|
246
|
+
"ID" => lambda {|instance|
|
247
|
+
instance['id']
|
248
|
+
},
|
249
|
+
"Name" => lambda {|instance|
|
250
|
+
instance['name']
|
251
|
+
},
|
252
|
+
"Type" => lambda {|instance|
|
253
|
+
instance['instanceType']['name'] rescue nil
|
254
|
+
},
|
255
|
+
"IP/PORT" => lambda {|instance|
|
256
|
+
format_instance_connection_string(instance)
|
257
|
+
},
|
258
|
+
"Status" => lambda {|it| format_instance_status(it) }
|
259
|
+
}
|
260
|
+
#print as_description_list(json_response, status_column_definitions, options)
|
261
|
+
print as_pretty_table(favorite_instances, favorite_instances_columns, options)
|
262
|
+
# print reset,"\n"
|
263
|
+
end
|
48
264
|
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
265
|
+
# RECENT ACTIVITY
|
266
|
+
activity = json_response["activity"] || json_response["recentActivity"] || []
|
267
|
+
print_h2 "Recent Activity", [], options
|
268
|
+
if activity.empty?
|
269
|
+
print cyan, "No activity found.",reset,"\n"
|
270
|
+
else
|
271
|
+
columns = [
|
272
|
+
# {"SEVERITY" => lambda {|record| format_activity_severity(record['severity']) } },
|
273
|
+
{"TYPE" => lambda {|record| record['activityType'] } },
|
274
|
+
{"NAME" => lambda {|record| record['name'] } },
|
275
|
+
{"RESOURCE" => lambda {|record| "#{record['objectType']} #{record['objectId']}" } },
|
276
|
+
{"MESSAGE" => lambda {|record| record['message'] || '' } },
|
277
|
+
{"USER" => lambda {|record| record['user'] ? record['user']['username'] : record['userName'] } },
|
278
|
+
#{"DATE" => lambda {|record| "#{format_duration_ago(record['ts'] || record['timestamp'])}" } },
|
279
|
+
{"DATE" => lambda {|record|
|
280
|
+
# show full time if searching for custom timerange, otherwise the default is to show relative time
|
281
|
+
if params['start'] || params['end'] || params['timeframe']
|
282
|
+
"#{format_local_dt(record['ts'] || record['timestamp'])}"
|
283
|
+
else
|
284
|
+
"#{format_duration_ago(record['ts'] || record['timestamp'])}"
|
285
|
+
end
|
286
|
+
|
287
|
+
} },
|
288
|
+
]
|
289
|
+
print as_pretty_table(activity, columns, options)
|
290
|
+
# print_results_pagination(json_response)
|
291
|
+
# print reset,"\n"
|
53
292
|
|
54
293
|
end
|
55
|
-
|
56
|
-
print_rest_exception(e, options)
|
57
|
-
exit 1
|
294
|
+
|
58
295
|
end
|
296
|
+
print reset,"\n"
|
297
|
+
return 0, nil
|
59
298
|
end
|
60
299
|
|
300
|
+
|
61
301
|
end
|
@@ -8,7 +8,7 @@ class Morpheus::Cli::InvoicesCommand
|
|
8
8
|
|
9
9
|
set_command_name :'invoices'
|
10
10
|
|
11
|
-
register_subcommands :list, :get, :refresh,
|
11
|
+
register_subcommands :list, :get, :update, :refresh,
|
12
12
|
:list_line_items, :get_line_item
|
13
13
|
|
14
14
|
def connect(opts)
|
@@ -591,6 +591,68 @@ EOT
|
|
591
591
|
end
|
592
592
|
end
|
593
593
|
|
594
|
+
def update(args)
|
595
|
+
options = {}
|
596
|
+
params = {}
|
597
|
+
payload = {}
|
598
|
+
optparse = Morpheus::Cli::OptionParser.new do |opts|
|
599
|
+
opts.banner = subcommand_usage("[invoice] [options]")
|
600
|
+
opts.on('--tags LIST', String, "Tags in the format 'name:value, name:value'. This will add and remove tags.") do |val|
|
601
|
+
options[:tags] = val
|
602
|
+
end
|
603
|
+
opts.on('--add-tags TAGS', String, "Add Tags in the format 'name:value, name:value'. This will only add/update tags.") do |val|
|
604
|
+
options[:add_tags] = val
|
605
|
+
end
|
606
|
+
opts.on('--remove-tags TAGS', String, "Remove Tags in the format 'name, name:value'. This removes tags, the :value component is optional and must match if passed.") do |val|
|
607
|
+
options[:remove_tags] = val
|
608
|
+
end
|
609
|
+
build_standard_update_options(opts, options)
|
610
|
+
opts.footer = <<-EOT
|
611
|
+
Update an invoice.
|
612
|
+
[invoice] is required. This is the id of an invoice.
|
613
|
+
EOT
|
614
|
+
end
|
615
|
+
optparse.parse!(args)
|
616
|
+
verify_args!(args:args, optparse:optparse, count:1)
|
617
|
+
connect(options)
|
618
|
+
json_response = @invoices_interface.get(args[0])
|
619
|
+
invoice = json_response['invoice']
|
620
|
+
|
621
|
+
invoice_payload = parse_passed_options(options)
|
622
|
+
if options[:tags]
|
623
|
+
invoice_payload['tags'] = parse_metadata(options[:tags])
|
624
|
+
end
|
625
|
+
if options[:add_tags]
|
626
|
+
invoice_payload['addTags'] = parse_metadata(options[:add_tags])
|
627
|
+
end
|
628
|
+
if options[:remove_tags]
|
629
|
+
invoice_payload['removeTags'] = parse_metadata(options[:remove_tags])
|
630
|
+
end
|
631
|
+
|
632
|
+
payload = {}
|
633
|
+
if options[:payload]
|
634
|
+
payload = options[:payload]
|
635
|
+
payload.deep_merge!({'invoice' => invoice_payload})
|
636
|
+
else
|
637
|
+
payload.deep_merge!({'invoice' => invoice_payload})
|
638
|
+
if invoice_payload.empty?
|
639
|
+
raise_command_error "Specify at least one option to update.\n#{optparse}"
|
640
|
+
end
|
641
|
+
end
|
642
|
+
@invoices_interface.setopts(options)
|
643
|
+
if options[:dry_run]
|
644
|
+
print_dry_run @invoices_interface.dry.update(invoice['id'], payload)
|
645
|
+
return
|
646
|
+
end
|
647
|
+
json_response = @invoices_interface.update(invoice['id'], payload)
|
648
|
+
invoice = json_response['invoice']
|
649
|
+
render_response(json_response, options, 'invoice') do
|
650
|
+
print_green_success "Updated invoice #{invoice['id']}"
|
651
|
+
return _get(invoice["id"], options)
|
652
|
+
end
|
653
|
+
return 0, nil
|
654
|
+
end
|
655
|
+
|
594
656
|
def refresh(args)
|
595
657
|
options = {}
|
596
658
|
params = {}
|