command_proposal 1.0.12 → 1.0.15
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/app/assets/javascripts/command_proposal/console.js +73 -35
- data/app/assets/stylesheets/command_proposal/components.scss +17 -0
- data/app/assets/stylesheets/command_proposal/tables.scss +3 -0
- data/app/controllers/command_proposal/iterations_controller.rb +25 -10
- data/app/controllers/command_proposal/tasks_controller.rb +7 -5
- data/app/helpers/command_proposal/params_helper.rb +43 -0
- data/app/helpers/command_proposal/permissions_helper.rb +9 -4
- data/app/jobs/command_proposal/command_runner_job.rb +18 -2
- data/app/models/command_proposal/iteration.rb +10 -0
- data/app/models/command_proposal/task.rb +14 -2
- data/app/views/command_proposal/tasks/_console_lines.html.erb +3 -3
- data/app/views/command_proposal/tasks/_past_iterations_list.html.erb +20 -9
- data/app/views/command_proposal/tasks/index.html.erb +7 -3
- data/lib/command_proposal/configuration.rb +2 -0
- data/lib/command_proposal/services/runner.rb +19 -2
- data/lib/command_proposal/version.rb +1 -1
- data/lib/command_proposal.rb +2 -0
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 321b3627d2af7cf183da1cd1071775e7f13907b0fda6101acce253f9649f8a42
|
4
|
+
data.tar.gz: c9a30e58a53bb372807e05e765a7b92368433ced38325e8d7800e9f6b983eeb8
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ddcd08648f107fb5e50c57ae46a7ff93ddb1f7e79ea900e7a052afd6fb31574fd85fae1bf4a2214313a6fae8b46d79e4406d2be6756714319d928c259b612557
|
7
|
+
data.tar.gz: 629c1bc1f60e363396a7672122dc0a945c042baed8c43339fbc5ae7d80cb71aa645de0d3c199709d923cc1c1376e3d305b273ef3f71275ae53215899e59e3ef1
|
@@ -142,14 +142,16 @@ cmdDocReady(function() {
|
|
142
142
|
|
143
143
|
console_input.textContent = ""
|
144
144
|
|
145
|
-
|
146
|
-
|
145
|
+
if (!(/^[\s\n]*$/.test(line.textContent))) {
|
146
|
+
var result = document.createElement("div")
|
147
|
+
result.classList.add("result")
|
147
148
|
|
148
|
-
|
149
|
-
|
150
|
-
|
149
|
+
var spinner = document.createElement("i")
|
150
|
+
spinner.className = "fa fa-circle-o-notch fa-spin cmd-icon-grey"
|
151
|
+
result.append(spinner)
|
151
152
|
|
152
|
-
|
153
|
+
line.appendChild(result)
|
154
|
+
}
|
153
155
|
|
154
156
|
lines.appendChild(line)
|
155
157
|
stored_entry = undefined
|
@@ -176,41 +178,77 @@ cmdDocReady(function() {
|
|
176
178
|
body: JSON.stringify(params),
|
177
179
|
done: function(res, status, req) {
|
178
180
|
if (status == 200) {
|
179
|
-
|
180
|
-
line.querySelector(".result").remove()
|
181
|
-
|
182
|
-
var result = document.createElement("div")
|
183
|
-
result.classList.add("result")
|
184
|
-
|
185
|
-
if (json.error) {
|
186
|
-
result.classList.add("cmd-error")
|
187
|
-
result.textContent = json.error
|
188
|
-
} else {
|
189
|
-
var truncate = 2000
|
190
|
-
if (json.result.length > truncate-3) {
|
191
|
-
result.textContent = json.result.slice(0, truncate-3) + "..."
|
192
|
-
var encoded = encodeURIComponent(json.result)
|
193
|
-
|
194
|
-
var download = document.createElement("a")
|
195
|
-
download.classList.add("cmd-truncated-download")
|
196
|
-
download.setAttribute("href", "data:application/txt," + encoded)
|
197
|
-
download.setAttribute("download", "result.txt")
|
198
|
-
download.textContent = "Output truncated. Click here to download full result."
|
199
|
-
|
200
|
-
line.insertAdjacentElement("afterend", download)
|
201
|
-
} else {
|
202
|
-
result.textContent = json.result
|
203
|
-
}
|
204
|
-
}
|
205
|
-
|
206
|
-
line.appendChild(result)
|
181
|
+
handleSuccessfulCommand(evt, line, JSON.parse(res))
|
207
182
|
} else {
|
208
183
|
console.log("Error: ", res, req);
|
209
184
|
}
|
210
|
-
evt.finish()
|
211
185
|
}
|
212
186
|
})
|
213
187
|
})
|
214
188
|
}
|
189
|
+
|
190
|
+
function handleSuccessfulCommand(evt, line, json) {
|
191
|
+
if (json.error) {
|
192
|
+
addLineResult(line, json.error, "cmd-error")
|
193
|
+
} else if (json.status != "started") {
|
194
|
+
addLineResult(line, json.result, json.status == "failed" ? "cmd-error" : "")
|
195
|
+
} else {
|
196
|
+
return setTimeout(function() { pollIteration(evt, line, json.results_endpoint) }, 2000)
|
197
|
+
}
|
198
|
+
|
199
|
+
evt.finish()
|
200
|
+
}
|
201
|
+
|
202
|
+
function addLineResult(line, text, result_class) {
|
203
|
+
line.querySelector(".result").remove()
|
204
|
+
|
205
|
+
if (!text || /^[\s\n]*$/.test(text)) { return }
|
206
|
+
|
207
|
+
var result = document.createElement("div")
|
208
|
+
result.classList.add("result")
|
209
|
+
if (result_class) { result.classList.add(result_class) }
|
210
|
+
|
211
|
+
var truncate = 2000
|
212
|
+
if (text.length > truncate-3) {
|
213
|
+
result.textContent = text.slice(0, truncate-3) + "..."
|
214
|
+
var encoded = encodeURIComponent(text)
|
215
|
+
|
216
|
+
var download = document.createElement("a")
|
217
|
+
download.classList.add("cmd-truncated-download")
|
218
|
+
download.setAttribute("href", "data:application/txt," + encoded)
|
219
|
+
download.setAttribute("download", "result.txt")
|
220
|
+
download.textContent = "Output truncated. Click here to download full result."
|
221
|
+
|
222
|
+
line.insertAdjacentElement("afterend", download)
|
223
|
+
} else {
|
224
|
+
result.textContent = text
|
225
|
+
}
|
226
|
+
|
227
|
+
line.appendChild(result)
|
228
|
+
}
|
229
|
+
|
230
|
+
function pollIteration(evt, line, endpoint) {
|
231
|
+
var client = new HttpClient()
|
232
|
+
client.get(endpoint, {
|
233
|
+
headers: {
|
234
|
+
"Content-Type": "application/json",
|
235
|
+
"X-CSRF-Token": $.rails.csrfToken()
|
236
|
+
},
|
237
|
+
done: function(res, status, req) {
|
238
|
+
if (status == 200) {
|
239
|
+
var json = JSON.parse(res)
|
240
|
+
if (json.status == "started") {
|
241
|
+
setTimeout(function() { pollIteration(evt, line, endpoint) }, 2000)
|
242
|
+
} else {
|
243
|
+
addLineResult(line, json.result, json.status == "failed" ? "cmd-error" : "")
|
244
|
+
evt.finish()
|
245
|
+
}
|
246
|
+
} else {
|
247
|
+
console.log("Error: ", res, req);
|
248
|
+
evt.finish()
|
249
|
+
}
|
250
|
+
}
|
251
|
+
})
|
252
|
+
}
|
215
253
|
}
|
216
254
|
})
|
@@ -35,3 +35,20 @@
|
|
35
35
|
background: lightgrey;
|
36
36
|
}
|
37
37
|
}
|
38
|
+
|
39
|
+
.cmd-pagination {
|
40
|
+
text-align: center;
|
41
|
+
margin-top: 10px;
|
42
|
+
|
43
|
+
.cmd-pagination-link {
|
44
|
+
text-decoration: none;
|
45
|
+
|
46
|
+
&:hover {
|
47
|
+
text-decoration: underline;
|
48
|
+
}
|
49
|
+
|
50
|
+
&.current-page {
|
51
|
+
font-weight: bold;
|
52
|
+
}
|
53
|
+
}
|
54
|
+
}
|
@@ -9,6 +9,16 @@ class ::CommandProposal::IterationsController < ::CommandProposal::EngineControl
|
|
9
9
|
|
10
10
|
layout "application"
|
11
11
|
|
12
|
+
def show
|
13
|
+
@iteration = ::CommandProposal::Iteration.find(params[:id])
|
14
|
+
|
15
|
+
render json: {
|
16
|
+
results_endpoint: cmd_path(@iteration),
|
17
|
+
result: @iteration.result,
|
18
|
+
status: @iteration.status
|
19
|
+
}
|
20
|
+
end
|
21
|
+
|
12
22
|
def create
|
13
23
|
return error!("You do not have permission to run commands.") unless can_command?
|
14
24
|
|
@@ -17,27 +27,32 @@ class ::CommandProposal::IterationsController < ::CommandProposal::EngineControl
|
|
17
27
|
return error!("Can only run commands on type: :console") unless @task.console?
|
18
28
|
return error!("Session has not been approved.") unless has_approval?(@task)
|
19
29
|
|
20
|
-
if @task.iterations.
|
21
|
-
runner = ::CommandProposal.sessions["task:#{@task.id}"]
|
22
|
-
elsif @task.iterations.one?
|
30
|
+
if @task.iterations.one?
|
23
31
|
# Track console details in first iteration
|
24
32
|
@task.first_iteration.update(started_at: Time.current, status: :started)
|
25
|
-
runner = ::CommandProposal::Services::Runner.new
|
26
|
-
::CommandProposal.sessions["task:#{@task.id}"] = runner
|
27
33
|
end
|
28
34
|
|
29
|
-
return error!("Session has expired. Please start a new session.") if runner.nil?
|
30
|
-
|
31
35
|
@task.user = command_user # Separate from update to ensure it's set first
|
32
36
|
@task.update(code: params[:code]) # Creates a new iteration
|
33
37
|
@iteration = @task.current_iteration
|
34
38
|
@iteration.update(status: :approved) # Task was already approved, and this is line-by-line
|
35
39
|
|
36
|
-
#
|
37
|
-
|
40
|
+
# async, but wait for the job to finish
|
41
|
+
::CommandProposal::CommandRunnerJob.perform_later(@iteration.id, "task:#{@task.id}")
|
42
|
+
|
43
|
+
max_wait_seconds = 3
|
44
|
+
loop do
|
45
|
+
break unless max_wait_seconds.positive?
|
46
|
+
|
47
|
+
max_wait_seconds -= sleep 0.2
|
48
|
+
|
49
|
+
break if @iteration.reload.complete?
|
50
|
+
end
|
38
51
|
|
39
52
|
render json: {
|
40
|
-
|
53
|
+
results_endpoint: cmd_path(@iteration),
|
54
|
+
result: @iteration.result,
|
55
|
+
status: @iteration.status
|
41
56
|
}
|
42
57
|
end
|
43
58
|
|
@@ -17,7 +17,8 @@ class ::CommandProposal::TasksController < ::CommandProposal::EngineController
|
|
17
17
|
@tasks = ::CommandProposal::Task.includes(:iterations)
|
18
18
|
@tasks = @tasks.order(Arel.sql("COALESCE(command_proposal_tasks.last_executed_at, command_proposal_tasks.created_at) DESC"))
|
19
19
|
@tasks = @tasks.search(params[:search]) if params[:search].present?
|
20
|
-
@tasks = @tasks.
|
20
|
+
@tasks = @tasks.by_session(params[:filter])
|
21
|
+
@tasks = @tasks.cmd_page(params[:page])
|
21
22
|
end
|
22
23
|
|
23
24
|
def show
|
@@ -37,7 +38,8 @@ class ::CommandProposal::TasksController < ::CommandProposal::EngineController
|
|
37
38
|
end
|
38
39
|
|
39
40
|
def new
|
40
|
-
@task = ::CommandProposal::Task.new
|
41
|
+
@task = ::CommandProposal::Task.new
|
42
|
+
@task.session_type = params[:session_type] if params[:session_type].in?(::CommandProposal::Task.session_types)
|
41
43
|
|
42
44
|
render "form"
|
43
45
|
end
|
@@ -51,12 +53,12 @@ class ::CommandProposal::TasksController < ::CommandProposal::EngineController
|
|
51
53
|
def create
|
52
54
|
@task = ::CommandProposal::Task.new(task_params.except(:code))
|
53
55
|
@task.user = command_user
|
54
|
-
@task.skip_approval = true unless approval_required?
|
56
|
+
@task.skip_approval = true unless approval_required?(@task.session_type)
|
55
57
|
|
56
58
|
# Cannot create the iteration until the task is created, so save then update
|
57
59
|
if @task.save && @task.update(task_params)
|
58
60
|
if @task.console?
|
59
|
-
@task.
|
61
|
+
@task.code = nil # Creates a blank iteration to track approval
|
60
62
|
redirect_to cmd_path(@task)
|
61
63
|
else
|
62
64
|
redirect_to cmd_path(:edit, @task)
|
@@ -70,7 +72,7 @@ class ::CommandProposal::TasksController < ::CommandProposal::EngineController
|
|
70
72
|
def update
|
71
73
|
@task = ::CommandProposal::Task.find_by!(friendly_id: params[:id])
|
72
74
|
@task.user = command_user
|
73
|
-
@task.skip_approval = true unless approval_required?
|
75
|
+
@task.skip_approval = true unless approval_required?(@task.session_type)
|
74
76
|
|
75
77
|
if @task.update(task_params)
|
76
78
|
redirect_to cmd_path(@task)
|
@@ -22,6 +22,15 @@ module ::CommandProposal::ParamsHelper
|
|
22
22
|
end
|
23
23
|
end
|
24
24
|
|
25
|
+
def current_path(new_params={})
|
26
|
+
if @task.present?
|
27
|
+
new_params.merge!(iteration: @iteration.id) if @iteration.present? && !@iteration.primary_iteration?
|
28
|
+
cmd_path(@task, new_params)
|
29
|
+
else
|
30
|
+
cmd_path(:tasks, current_params(new_params))
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
25
34
|
def truthy?(val)
|
26
35
|
val.to_s.downcase.in?(["true", "t", "1"])
|
27
36
|
end
|
@@ -60,4 +69,38 @@ module ::CommandProposal::ParamsHelper
|
|
60
69
|
return "< 1s" if str_parts.none?
|
61
70
|
str_parts.join(" ")
|
62
71
|
end
|
72
|
+
|
73
|
+
def div(opts={}, &block)
|
74
|
+
"<div #{opts.map { |k, v| "#{k}=\"#{v}\"" }.join(" ")}>#{yield}</div>".html_safe
|
75
|
+
end
|
76
|
+
|
77
|
+
def command_paginate(paged_collection)
|
78
|
+
collection = paged_collection.unscope(:limit, :offset)
|
79
|
+
|
80
|
+
per = ::CommandProposal::PAGINATION_PER
|
81
|
+
total_pages = (collection.count / per.to_f).ceil
|
82
|
+
current_page = params[:page].presence&.to_i || 1
|
83
|
+
|
84
|
+
return if total_pages <= 1
|
85
|
+
|
86
|
+
div(class: "cmd-pagination") do
|
87
|
+
links = []
|
88
|
+
links << ["<<", { page: 1 }] if current_page > 1
|
89
|
+
links << ["<", { page: current_page - 1 }] if current_page > 1
|
90
|
+
|
91
|
+
(current_page-2..current_page+2).each do |page_window|
|
92
|
+
next if page_window < 1
|
93
|
+
next if page_window > total_pages
|
94
|
+
|
95
|
+
links << [page_window, { page: page_window }]
|
96
|
+
end
|
97
|
+
|
98
|
+
links << [">", page: current_page + 1] if current_page < total_pages
|
99
|
+
links << [">>", page: total_pages] if current_page < total_pages
|
100
|
+
|
101
|
+
links.map do |link_text, link_params|
|
102
|
+
"<a class=\"cmd-pagination-link #{'current-page' if link_params[:page] == current_page}\" href=\"#{current_path(link_params)}\">#{link_text}</a>"
|
103
|
+
end.join("\n")
|
104
|
+
end
|
105
|
+
end
|
63
106
|
end
|
@@ -9,7 +9,7 @@ module CommandProposal
|
|
9
9
|
|
10
10
|
def can_approve?(iteration)
|
11
11
|
return false unless permitted_to_use?
|
12
|
-
return true unless approval_required?
|
12
|
+
return true unless approval_required?(iteration.task.session_type)
|
13
13
|
return if iteration.nil?
|
14
14
|
|
15
15
|
command_user.try("#{cmd_config.role_scope}?") && !current_is_author?(iteration)
|
@@ -17,13 +17,18 @@ module CommandProposal
|
|
17
17
|
|
18
18
|
def has_approval?(task)
|
19
19
|
return false unless permitted_to_use?
|
20
|
-
return true unless approval_required?
|
20
|
+
return true unless approval_required?(task.session_type)
|
21
21
|
|
22
22
|
task&.approved?
|
23
23
|
end
|
24
24
|
|
25
|
-
def approval_required?
|
26
|
-
cmd_config.approval_required?
|
25
|
+
def approval_required?(task_type=nil)
|
26
|
+
return false unless cmd_config.approval_required?
|
27
|
+
return true if task_type.blank?
|
28
|
+
|
29
|
+
skips = cmd_config.skip_approval_for_types.presence || []
|
30
|
+
|
31
|
+
Array.wrap(skips).map(&:to_sym).exclude?(task_type.to_sym)
|
27
32
|
end
|
28
33
|
|
29
34
|
def current_is_author?(iteration)
|
@@ -2,10 +2,26 @@ module CommandProposal
|
|
2
2
|
class CommandRunnerJob < ApplicationJob
|
3
3
|
queue_as :default
|
4
4
|
|
5
|
-
def perform(iteration_id)
|
5
|
+
def perform(iteration_id, runner_key=nil)
|
6
6
|
iteration = ::CommandProposal::Iteration.find(iteration_id)
|
7
|
+
runner = ::CommandProposal.sessions[runner_key] if runner_key.present?
|
7
8
|
|
8
|
-
|
9
|
+
if runner_key.present? && runner.blank?
|
10
|
+
if iteration.task.console? && iteration.task.iterations.count > 2 # 1 for init, and the 1 for current running code
|
11
|
+
return ::CommandProposal::Services::Runner.new.quick_fail(
|
12
|
+
iteration,
|
13
|
+
"Session has expired. Please start a new session."
|
14
|
+
)
|
15
|
+
else
|
16
|
+
runner = ::CommandProposal::Services::Runner.new
|
17
|
+
end
|
18
|
+
|
19
|
+
::CommandProposal.sessions[runner_key] = runner if runner_key.present?
|
20
|
+
else
|
21
|
+
runner ||= ::CommandProposal::Services::Runner.new
|
22
|
+
end
|
23
|
+
|
24
|
+
runner.execute(iteration)
|
9
25
|
end
|
10
26
|
end
|
11
27
|
end
|
@@ -40,6 +40,12 @@ class ::CommandProposal::Iteration < ApplicationRecord
|
|
40
40
|
terminated: 7, # Closed via server restart
|
41
41
|
}
|
42
42
|
|
43
|
+
scope :cmd_page, ->(page=nil) {
|
44
|
+
page = page.presence&.to_i || 1
|
45
|
+
per = ::CommandProposal::PAGINATION_PER
|
46
|
+
limit(per).offset(per * (page - 1))
|
47
|
+
}
|
48
|
+
|
43
49
|
delegate :name, to: :task
|
44
50
|
delegate :description, to: :task
|
45
51
|
delegate :session_type, to: :task
|
@@ -55,6 +61,10 @@ class ::CommandProposal::Iteration < ApplicationRecord
|
|
55
61
|
::CommandProposal::Task.module.where(friendly_id: bring_str.scan(/\s+\:(\w+),?/).flatten)
|
56
62
|
end
|
57
63
|
|
64
|
+
def primary_iteration?
|
65
|
+
task.primary_iteration == self
|
66
|
+
end
|
67
|
+
|
58
68
|
def complete?
|
59
69
|
success? || failed? || cancelled? || terminated?
|
60
70
|
end
|
@@ -15,12 +15,24 @@ class ::CommandProposal::Task < ApplicationRecord
|
|
15
15
|
scope :search, ->(text) {
|
16
16
|
where("name ILIKE :q OR description ILIKE :q", q: "%#{text}%")
|
17
17
|
}
|
18
|
+
scope :by_session, ->(filter) {
|
19
|
+
if filter.present?
|
20
|
+
where(session_type: filter) if filter.to_s.in?(session_types.keys)
|
21
|
+
else
|
22
|
+
where(session_type: :function)
|
23
|
+
end
|
24
|
+
}
|
25
|
+
scope :cmd_page, ->(page=nil) {
|
26
|
+
page = page.presence&.to_i || 1
|
27
|
+
per = ::CommandProposal::PAGINATION_PER
|
28
|
+
limit(per).offset(per * (page - 1))
|
29
|
+
}
|
18
30
|
|
19
31
|
enum session_type: {
|
20
|
-
# Task will have multiple iterations that are all essentially the same just with code changes
|
21
|
-
task: 0,
|
22
32
|
# Function iterations are much like tasks
|
23
33
|
function: 1,
|
34
|
+
# Task will have multiple iterations that are all essentially the same just with code changes
|
35
|
+
task: 0,
|
24
36
|
# Console iterations are actually line by line, so order matters
|
25
37
|
console: 2,
|
26
38
|
# Modules are included in tasks and not run independently
|
@@ -1,18 +1,18 @@
|
|
1
1
|
<% if lines.none? && !(skip_empty ||= false) -%><div class="line"></div><% end
|
2
2
|
-%><% lines.each do |iteration| -%>
|
3
3
|
<div class="line"><%= iteration.code
|
4
|
-
-%><div class="result"><%=
|
4
|
+
-%><div class="result <%= 'cmd-error' if iteration.failed? %>"><%=
|
5
5
|
truncate = ::CommandProposal::Iteration::TRUNCATE_COUNT
|
6
6
|
if iteration.result.present?
|
7
7
|
iteration.result.truncate(truncate)
|
8
|
-
elsif iteration.
|
8
|
+
elsif iteration.complete?
|
9
9
|
"Error: No response"
|
10
10
|
else
|
11
11
|
content_tag :i, nil, class: "fa fa-circle-o-notch fa-spin cmd-icon-grey"
|
12
12
|
end
|
13
13
|
%></div
|
14
14
|
></div><%=
|
15
|
-
if iteration.result.
|
15
|
+
if iteration.result&.length.to_i > truncate
|
16
16
|
link_to("Output truncated. Click here to download full result.", "data:application/txt,#{ERB::Util.url_encode(iteration.result)}", class: "cmd-truncated-download", download: "result.txt")
|
17
17
|
end
|
18
18
|
%><% end -%>
|
@@ -1,27 +1,38 @@
|
|
1
|
-
<% if @task.iterations.
|
2
|
-
<table class="cmd-table">
|
1
|
+
<% if @task.iterations.any? %>
|
2
|
+
<table class="cmd-table cmd-past-iterations">
|
3
3
|
<thead>
|
4
4
|
<th>Timestamp / Link</th>
|
5
5
|
<th>Status</th>
|
6
|
+
<% if @iteration&.params.present? %>
|
7
|
+
<th>Params</th>
|
8
|
+
<% end %>
|
9
|
+
<th>Result</th>
|
6
10
|
<!-- <th># Comments</th> -->
|
7
11
|
<!-- <th>Diff</th> -->
|
8
12
|
</thead>
|
9
13
|
<tbody>
|
10
|
-
<%
|
11
|
-
<%
|
14
|
+
<% paginated_iterations = @task.iterations.cmd_page(params[:page]) %>
|
15
|
+
<% paginated_iterations.order(created_at: :desc).each do |iteration| %>
|
12
16
|
<tr>
|
13
17
|
<td>
|
14
|
-
|
15
|
-
|
16
|
-
<% else %>
|
17
|
-
<%= link_to iteration.created_at.strftime("%b %-d, %Y at %H:%M"), cmd_path(@task, iteration: iteration.id) %>
|
18
|
-
<% end %>
|
18
|
+
<%= ">" if iteration == @iteration %>
|
19
|
+
<%= link_to iteration.created_at.strftime("%b %-d, %Y at %H:%M"), cmd_path(@task, iteration: iteration.id) %>
|
19
20
|
</td>
|
20
21
|
<td><%= iteration.status.capitalize %></td>
|
22
|
+
<% if @iteration&.params.present? %>
|
23
|
+
<td>
|
24
|
+
<% iteration&.args.each do |arg_k, arg_v| %>
|
25
|
+
<span class=cmd-arg""><%= arg_k %>=<%= arg_v.to_s.truncate(50) %></span>
|
26
|
+
<% end %>
|
27
|
+
</td>
|
28
|
+
<% end %>
|
29
|
+
<td><%= iteration.result.to_s.truncate(200) %></td>
|
21
30
|
<!-- <td><%#= iteration.comments.count %></td> -->
|
22
31
|
<!-- <td><%#= link_to "Diff", cmd_path(@task, iteration: @iteration.id, diff: iteration.id) %></td> -->
|
23
32
|
</tr>
|
24
33
|
<% end %>
|
25
34
|
</tbody>
|
26
35
|
</table>
|
36
|
+
|
37
|
+
<%= command_paginate paginated_iterations %>
|
27
38
|
<% end %>
|
@@ -1,9 +1,11 @@
|
|
1
1
|
<div class="cmd-wrapper">
|
2
|
-
|
2
|
+
<% filter_name = params[:filter].presence&.capitalize %>
|
3
|
+
<% filter_name = filter_name.blank? || filter_name == "All" ? "Command" : filter_name %>
|
4
|
+
<%= link_to "New #{filter_name}", cmd_path(:new, :task, session_type: params[:filter] || :task) %> <br>
|
3
5
|
<br>
|
4
|
-
<%= link_to "All",
|
6
|
+
<%= link_to "All", toggled_param(filter: :all), class: "cmd-tab #{:active if params[:filter] == "all"}"
|
5
7
|
%><% ::CommandProposal::Task.session_types.each_with_index do |(session_type, _session_enum), idx| %><%=
|
6
|
-
selected = params[:filter] == session_type.to_s
|
8
|
+
selected = params[:filter] == session_type.to_s || (!params.key?(:filter) && session_type == "function")
|
7
9
|
# Offset closing RB tags to fix spacing issues
|
8
10
|
link_to session_type.capitalize, toggled_param(filter: session_type), class: "cmd-tab #{:active if selected}"
|
9
11
|
%><% end %>
|
@@ -42,4 +44,6 @@
|
|
42
44
|
<% end %>
|
43
45
|
<% end %>
|
44
46
|
</div>
|
47
|
+
|
48
|
+
<%= command_paginate @tasks %>
|
45
49
|
</div>
|
@@ -13,6 +13,7 @@ module CommandProposal
|
|
13
13
|
:approval_callback,
|
14
14
|
:success_callback,
|
15
15
|
:failed_callback,
|
16
|
+
:skip_approval_for_types,
|
16
17
|
)
|
17
18
|
|
18
19
|
def initialize
|
@@ -30,6 +31,7 @@ module CommandProposal
|
|
30
31
|
@approval_callback = nil
|
31
32
|
@success_callback = nil
|
32
33
|
@failed_callback = nil
|
34
|
+
@skip_approval_for_types = nil
|
33
35
|
end
|
34
36
|
|
35
37
|
def user_class
|
@@ -51,6 +51,19 @@ module CommandProposal
|
|
51
51
|
proposal
|
52
52
|
end
|
53
53
|
|
54
|
+
def quick_fail(iteration, msg)
|
55
|
+
@iteration = iteration
|
56
|
+
prepare
|
57
|
+
|
58
|
+
@iteration.status = :failed
|
59
|
+
@iteration.result = msg
|
60
|
+
|
61
|
+
complete
|
62
|
+
proposal = ::CommandProposal::Service::ProposalPresenter.new(@iteration)
|
63
|
+
@iteration = nil
|
64
|
+
proposal
|
65
|
+
end
|
66
|
+
|
54
67
|
def quick_run(friendly_id)
|
55
68
|
task = ::CommandProposal::Task.module.find_by!(friendly_id: friendly_id)
|
56
69
|
iteration = task&.primary_iteration
|
@@ -76,7 +89,11 @@ module CommandProposal
|
|
76
89
|
|
77
90
|
def run
|
78
91
|
begin
|
79
|
-
|
92
|
+
params_str = ""
|
93
|
+
unless @iteration.task.console? # Don't bring params into the console
|
94
|
+
params_str = "params = #{@iteration.args || {}}.with_indifferent_access"
|
95
|
+
end
|
96
|
+
@session.eval("#{bring_function};#{params_str}")
|
80
97
|
rescue Exception => e # rubocop:disable Lint/RescueException - Yes, rescue full Exception so that we can catch typos in evals as well
|
81
98
|
return @iteration.result = results_from_exception(e)
|
82
99
|
end
|
@@ -88,7 +105,7 @@ module CommandProposal
|
|
88
105
|
|
89
106
|
running_thread = Thread.new do
|
90
107
|
begin
|
91
|
-
# Run bring functions in here so we can capture any string outputs
|
108
|
+
# Run `bring` functions in here so we can capture any string outputs
|
92
109
|
# OR! Run the full runner and instead of saving to an iteration, return the string for prepending here
|
93
110
|
result = @session.eval("_ = (#{@iteration.code})").inspect # rubocop:disable Security/Eval - Eval is scary, but in this case it's exactly what we need.
|
94
111
|
result = nil unless @iteration.task.console? # Only store final result for consoles
|
data/lib/command_proposal.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: command_proposal
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0.
|
4
|
+
version: 1.0.15
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Rocco Nicholls
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2022-03-05 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rails
|