command_proposal 1.0.10 → 1.0.14
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 +27 -1
- data/app/assets/javascripts/command_proposal/feed.js +1 -0
- data/app/assets/javascripts/command_proposal/terminal.js +3 -3
- data/app/assets/stylesheets/command_proposal/components.scss +23 -0
- data/app/assets/stylesheets/command_proposal/terminal.scss +2 -7
- data/app/controllers/command_proposal/iterations_controller.rb +7 -3
- data/app/controllers/command_proposal/runner_controller.rb +1 -0
- data/app/controllers/command_proposal/tasks_controller.rb +6 -2
- data/app/helpers/command_proposal/params_helper.rb +43 -0
- data/app/helpers/command_proposal/permissions_helper.rb +7 -3
- data/app/models/command_proposal/iteration.rb +18 -0
- data/app/models/command_proposal/task.rb +26 -4
- data/app/views/command_proposal/tasks/_console_lines.html.erb +16 -6
- data/app/views/command_proposal/tasks/_lines.html.erb +5 -1
- data/app/views/command_proposal/tasks/_past_iterations_list.html.erb +6 -7
- data/app/views/command_proposal/tasks/_task_detail_table.html.erb +1 -1
- data/app/views/command_proposal/tasks/index.html.erb +7 -3
- data/lib/command_proposal/services/command_interpreter.rb +3 -2
- data/lib/command_proposal/services/runner.rb +26 -5
- 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: 49fcf371fa04144d6c3a47f4cf8170bd30c1571c61f44d636df41591227f4d66
|
4
|
+
data.tar.gz: 990a726d0441c95757ec17679e495b1300f4de2e5ac284df842bf82180c6912f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3d9d03fbe537ce4da08d6229829148ef8dbdbe1433ae56db6b08f97313a6329d41b2f63c8f433a197f34559521e37a652bad931f9be3a2eb0f4b12265184f1bf
|
7
|
+
data.tar.gz: 24ba8d9ad1147fa9d6ee21298d547e774fd444ee0370bc79e629c1ad63fefb9d022aa95d9d78e56b1f58db7cf8d2e191731dd4970b88fd60484395c9ec376ccb
|
@@ -141,6 +141,16 @@ cmdDocReady(function() {
|
|
141
141
|
line.textContent = console_input.textContent
|
142
142
|
|
143
143
|
console_input.textContent = ""
|
144
|
+
|
145
|
+
var result = document.createElement("div")
|
146
|
+
result.classList.add("result")
|
147
|
+
|
148
|
+
var spinner = document.createElement("i")
|
149
|
+
spinner.className = "fa fa-circle-o-notch fa-spin cmd-icon-grey"
|
150
|
+
result.append(spinner)
|
151
|
+
|
152
|
+
line.appendChild(result)
|
153
|
+
|
144
154
|
lines.appendChild(line)
|
145
155
|
stored_entry = undefined
|
146
156
|
history_cmd_idx = undefined
|
@@ -167,6 +177,8 @@ cmdDocReady(function() {
|
|
167
177
|
done: function(res, status, req) {
|
168
178
|
if (status == 200) {
|
169
179
|
var json = JSON.parse(res)
|
180
|
+
line.querySelector(".result").remove()
|
181
|
+
|
170
182
|
var result = document.createElement("div")
|
171
183
|
result.classList.add("result")
|
172
184
|
|
@@ -174,7 +186,21 @@ cmdDocReady(function() {
|
|
174
186
|
result.classList.add("cmd-error")
|
175
187
|
result.textContent = json.error
|
176
188
|
} else {
|
177
|
-
|
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
|
+
}
|
178
204
|
}
|
179
205
|
|
180
206
|
line.appendChild(result)
|
@@ -29,6 +29,7 @@ cmdDocReady(function() {
|
|
29
29
|
}
|
30
30
|
document.querySelector("td[data-iteration-status]").innerText = json.status
|
31
31
|
document.querySelector("td[data-iteration-duration]").innerText = json.duration
|
32
|
+
document.querySelector("td[data-iteration-started]").innerText = json.started_at
|
32
33
|
|
33
34
|
if (continue_statuses.includes(json.status)) {
|
34
35
|
setTimeout(function() { pingFeed(terminal) }, 1000)
|
@@ -4,13 +4,13 @@ cmdDocReady(function() {
|
|
4
4
|
function setReadOnlyUI(cm) {
|
5
5
|
cm.getWrapperElement().classList.add("CodeMirror-readonly")
|
6
6
|
|
7
|
-
pencil = document.createElement("i")
|
7
|
+
var pencil = document.createElement("i")
|
8
8
|
pencil.className = "fa fa-pencil fa-stack-1x"
|
9
9
|
|
10
|
-
ban = document.createElement("i")
|
10
|
+
var ban = document.createElement("i")
|
11
11
|
ban.className = "fa fa-ban fa-stack-2x fa-flip-horizontal"
|
12
12
|
|
13
|
-
stack = document.createElement("span")
|
13
|
+
var stack = document.createElement("span")
|
14
14
|
stack.className = "fa-stack fa-2x"
|
15
15
|
stack.append(pencil)
|
16
16
|
stack.append(ban)
|
@@ -6,6 +6,12 @@
|
|
6
6
|
width: 100%;
|
7
7
|
}
|
8
8
|
|
9
|
+
.cmd-truncated-download {
|
10
|
+
font-size: 12px;
|
11
|
+
color: grey;
|
12
|
+
text-decoration: underline;
|
13
|
+
}
|
14
|
+
|
9
15
|
.cmd-flex-row {
|
10
16
|
display: flex;
|
11
17
|
}
|
@@ -29,3 +35,20 @@
|
|
29
35
|
background: lightgrey;
|
30
36
|
}
|
31
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
|
+
}
|
@@ -6,14 +6,12 @@
|
|
6
6
|
background: #112435;
|
7
7
|
padding: 10px 20px;
|
8
8
|
padding-left: 40px;
|
9
|
-
overflow-x: auto;
|
10
|
-
overflow-y: visible;
|
11
9
|
color: lime;
|
12
10
|
font-family: monospace;
|
13
11
|
text-align: left;
|
14
|
-
white-space: nowrap;
|
15
|
-
white-space: pre;
|
16
12
|
counter-reset: line-count;
|
13
|
+
white-space: pre-wrap;
|
14
|
+
word-wrap: break-word;
|
17
15
|
|
18
16
|
.line {
|
19
17
|
min-height: 18px;
|
@@ -67,6 +65,3 @@
|
|
67
65
|
color: black;
|
68
66
|
}
|
69
67
|
}
|
70
|
-
.cmd-terminal {
|
71
|
-
white-space: pre-wrap;
|
72
|
-
}
|
@@ -28,14 +28,18 @@ class ::CommandProposal::IterationsController < ::CommandProposal::EngineControl
|
|
28
28
|
|
29
29
|
return error!("Session has expired. Please start a new session.") if runner.nil?
|
30
30
|
|
31
|
-
|
32
31
|
@task.user = command_user # Separate from update to ensure it's set first
|
33
32
|
@task.update(code: params[:code]) # Creates a new iteration
|
34
33
|
@iteration = @task.current_iteration
|
35
34
|
@iteration.update(status: :approved) # Task was already approved, and this is line-by-line
|
36
35
|
|
37
|
-
#
|
38
|
-
|
36
|
+
# async, but wait for the job to finish
|
37
|
+
::CommandProposal::CommandRunnerJob.perform_later(@iteration.id)
|
38
|
+
loop do
|
39
|
+
sleep 0.2
|
40
|
+
|
41
|
+
break if @iteration.reload.complete?
|
42
|
+
end
|
39
43
|
|
40
44
|
render json: {
|
41
45
|
result: @iteration.result
|
@@ -65,6 +65,7 @@ class ::CommandProposal::RunnerController < ::CommandProposal::EngineController
|
|
65
65
|
result: @iteration.result,
|
66
66
|
status: @iteration.status,
|
67
67
|
duration: humanized_duration(@iteration.duration),
|
68
|
+
started_at: @iteration.started_at&.strftime("%b %-d '%y, %-l:%M%P")
|
68
69
|
}.tap do |response|
|
69
70
|
if @iteration.started?
|
70
71
|
response[:endpoint] = runner_url(@task, @iteration)
|
@@ -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,6 +53,7 @@ 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
|
56
|
+
@task.skip_approval = true unless approval_required?
|
54
57
|
|
55
58
|
# Cannot create the iteration until the task is created, so save then update
|
56
59
|
if @task.save && @task.update(task_params)
|
@@ -69,6 +72,7 @@ class ::CommandProposal::TasksController < ::CommandProposal::EngineController
|
|
69
72
|
def update
|
70
73
|
@task = ::CommandProposal::Task.find_by!(friendly_id: params[:id])
|
71
74
|
@task.user = command_user
|
75
|
+
@task.skip_approval = true unless approval_required?
|
72
76
|
|
73
77
|
if @task.update(task_params)
|
74
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
|
@@ -2,14 +2,14 @@ module CommandProposal
|
|
2
2
|
module PermissionsHelper
|
3
3
|
def can_command?(user=command_user)
|
4
4
|
return false unless permitted_to_use?
|
5
|
-
return true unless
|
5
|
+
return true unless approval_required?
|
6
6
|
|
7
7
|
command_user.try("#{cmd_config.role_scope}?")
|
8
8
|
end
|
9
9
|
|
10
10
|
def can_approve?(iteration)
|
11
11
|
return false unless permitted_to_use?
|
12
|
-
return true unless
|
12
|
+
return true unless approval_required?
|
13
13
|
return if iteration.nil?
|
14
14
|
|
15
15
|
command_user.try("#{cmd_config.role_scope}?") && !current_is_author?(iteration)
|
@@ -17,11 +17,15 @@ module CommandProposal
|
|
17
17
|
|
18
18
|
def has_approval?(task)
|
19
19
|
return false unless permitted_to_use?
|
20
|
-
return true unless
|
20
|
+
return true unless approval_required?
|
21
21
|
|
22
22
|
task&.approved?
|
23
23
|
end
|
24
24
|
|
25
|
+
def approval_required?
|
26
|
+
cmd_config.approval_required?
|
27
|
+
end
|
28
|
+
|
25
29
|
def current_is_author?(iteration)
|
26
30
|
return false unless permitted_to_use?
|
27
31
|
|
@@ -21,6 +21,10 @@ class ::CommandProposal::Iteration < ApplicationRecord
|
|
21
21
|
serialize :args, ::CommandProposal::Service::JSONWrapper
|
22
22
|
include ::CommandProposal::Service::ExternalBelong
|
23
23
|
|
24
|
+
TRUNCATE_COUNT = 2000
|
25
|
+
# Also hardcoded in JS: app/assets/javascripts/command_proposal/console.js
|
26
|
+
PAGINATION_PER = 2
|
27
|
+
|
24
28
|
has_many :comments
|
25
29
|
belongs_to :task
|
26
30
|
external_belongs_to :requester
|
@@ -37,6 +41,12 @@ class ::CommandProposal::Iteration < ApplicationRecord
|
|
37
41
|
terminated: 7, # Closed via server restart
|
38
42
|
}
|
39
43
|
|
44
|
+
scope :cmd_page, ->(page=nil) {
|
45
|
+
page = page.presence&.to_i || 1
|
46
|
+
per = ::CommandProposal::PAGINATION_PER
|
47
|
+
limit(per).offset(per * (page - 1))
|
48
|
+
}
|
49
|
+
|
40
50
|
delegate :name, to: :task
|
41
51
|
delegate :description, to: :task
|
42
52
|
delegate :session_type, to: :task
|
@@ -52,6 +62,10 @@ class ::CommandProposal::Iteration < ApplicationRecord
|
|
52
62
|
::CommandProposal::Task.module.where(friendly_id: bring_str.scan(/\s+\:(\w+),?/).flatten)
|
53
63
|
end
|
54
64
|
|
65
|
+
def primary_iteration?
|
66
|
+
task.primary_iteration == self
|
67
|
+
end
|
68
|
+
|
55
69
|
def complete?
|
56
70
|
success? || failed? || cancelled? || terminated?
|
57
71
|
end
|
@@ -70,6 +84,10 @@ class ::CommandProposal::Iteration < ApplicationRecord
|
|
70
84
|
(completed_at || stopped_at || Time.current) - started_at
|
71
85
|
end
|
72
86
|
|
87
|
+
def end_time
|
88
|
+
completed_at || stopped_at || Time.current
|
89
|
+
end
|
90
|
+
|
73
91
|
def force_reset
|
74
92
|
# Debugging method. Should never actually be called.
|
75
93
|
update(status: :approved, result: nil, completed_at: nil, stopped_at: nil, started_at: nil)
|
@@ -7,7 +7,7 @@
|
|
7
7
|
|
8
8
|
class ::CommandProposal::Task < ApplicationRecord
|
9
9
|
self.table_name = :command_proposal_tasks
|
10
|
-
attr_accessor :user
|
10
|
+
attr_accessor :user, :skip_approval
|
11
11
|
|
12
12
|
has_many :iterations
|
13
13
|
has_many :ordered_iterations, -> { order(created_at: :desc) }, class_name: "CommandProposal::Iteration"
|
@@ -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
|
@@ -79,7 +91,17 @@ class ::CommandProposal::Task < ApplicationRecord
|
|
79
91
|
end
|
80
92
|
|
81
93
|
def code=(new_code)
|
82
|
-
|
94
|
+
if skip_approval
|
95
|
+
iterations.create(
|
96
|
+
code: new_code,
|
97
|
+
requester: user,
|
98
|
+
status: :approved,
|
99
|
+
approver: user,
|
100
|
+
approved_at: Time.current
|
101
|
+
)
|
102
|
+
else
|
103
|
+
iterations.create(code: new_code, requester: user)
|
104
|
+
end
|
83
105
|
end
|
84
106
|
|
85
107
|
private
|
@@ -1,8 +1,18 @@
|
|
1
1
|
<% if lines.none? && !(skip_empty ||= false) -%><div class="line"></div><% end
|
2
2
|
-%><% lines.each do |iteration| -%>
|
3
|
-
<div class="line"><%= iteration.code
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
3
|
+
<div class="line"><%= iteration.code
|
4
|
+
-%><div class="result"><%=
|
5
|
+
truncate = ::CommandProposal::Iteration::TRUNCATE_COUNT
|
6
|
+
if iteration.result.present?
|
7
|
+
iteration.result.truncate(truncate)
|
8
|
+
elsif iteration.complete?
|
9
|
+
"Error: No response"
|
10
|
+
else
|
11
|
+
content_tag :i, nil, class: "fa fa-circle-o-notch fa-spin cmd-icon-grey"
|
12
|
+
end
|
13
|
+
%></div
|
14
|
+
></div><%=
|
15
|
+
if iteration.result.length > truncate
|
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
|
+
end
|
18
|
+
%><% end -%>
|
@@ -1,4 +1,8 @@
|
|
1
1
|
<% if lines.blank? && !(skip_empty ||= false) -%><div class="line"></div><% end
|
2
2
|
-%><% lines&.split("\n").each do |line|
|
3
|
-
|
3
|
+
truncate = ::CommandProposal::Iteration::TRUNCATE_COUNT
|
4
|
+
-%><div class="line"><%= line.truncate(truncate) -%></div><%=
|
5
|
+
if line.length > truncate
|
6
|
+
link_to("Output truncated. Click here to download full result.", "data:application/txt,#{ERB::Util.url_encode(line)}", class: "cmd-truncated-download", download: "result.txt")
|
7
|
+
end -%><%
|
4
8
|
end -%>
|
@@ -7,15 +7,12 @@
|
|
7
7
|
<!-- <th>Diff</th> -->
|
8
8
|
</thead>
|
9
9
|
<tbody>
|
10
|
-
<%
|
11
|
-
<%
|
10
|
+
<% paginated_iterations = @task.iterations.cmd_page(params[:page]) %>
|
11
|
+
<% paginated_iterations.order(created_at: :desc).each do |iteration| %>
|
12
12
|
<tr>
|
13
13
|
<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 %>
|
14
|
+
<%= ">" if iteration == @iteration %>
|
15
|
+
<%= link_to iteration.created_at.strftime("%b %-d, %Y at %H:%M"), cmd_path(@task, iteration: iteration.id) %>
|
19
16
|
</td>
|
20
17
|
<td><%= iteration.status.capitalize %></td>
|
21
18
|
<!-- <td><%#= iteration.comments.count %></td> -->
|
@@ -24,4 +21,6 @@
|
|
24
21
|
<% end %>
|
25
22
|
</tbody>
|
26
23
|
</table>
|
24
|
+
|
25
|
+
<%= command_paginate paginated_iterations %>
|
27
26
|
<% end %>
|
@@ -10,7 +10,7 @@
|
|
10
10
|
<tr>
|
11
11
|
<td><%= @iteration&.requester_name.presence || "ID: #{@iteration&.requester_id}" if @iteration&.requester_id.present? %></td>
|
12
12
|
<td><%= @iteration&.approver_name.presence || "ID: #{@iteration&.approver_id}" if @iteration&.approver_id.present? %></td>
|
13
|
-
<td><%= @iteration&.started_at&.strftime("%b %-d '%y, %-l:%M%P") %></td>
|
13
|
+
<td data-iteration-started><%= @iteration&.started_at&.strftime("%b %-d '%y, %-l:%M%P") %></td>
|
14
14
|
<td data-iteration-status><%= @iteration&.status&.capitalize %></td>
|
15
15
|
<td data-iteration-duration><%= humanized_duration(@iteration&.duration) %></td>
|
16
16
|
</tr>
|
@@ -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>
|
@@ -66,7 +66,7 @@ module CommandProposal
|
|
66
66
|
# Rollback the create/update if anything fails
|
67
67
|
ActiveRecord::Base.transaction do
|
68
68
|
command_request if @task.function? && @iteration.approved_at? && @iteration.complete?
|
69
|
-
@iteration.update(@params)
|
69
|
+
@iteration.update(@params.merge(requester: @user))
|
70
70
|
|
71
71
|
error!("Cannot run without approval.") unless has_approval?(@task)
|
72
72
|
end
|
@@ -91,7 +91,8 @@ module CommandProposal
|
|
91
91
|
if ::CommandProposal.sessions.key?("task:#{@task.id}")
|
92
92
|
@task.first_iteration.update(status: :success, completed_at: Time.current)
|
93
93
|
else
|
94
|
-
@task.
|
94
|
+
ended_at = @task.iterations.last&.end_time || Time.current
|
95
|
+
@task.first_iteration.update(status: :terminated, completed_at: ended_at)
|
95
96
|
end
|
96
97
|
::CommandProposal.sessions.delete("task:#{@task.id}")
|
97
98
|
end
|
@@ -11,12 +11,28 @@ module CommandProposal
|
|
11
11
|
end
|
12
12
|
|
13
13
|
def self.command(friendly_id, user, params={})
|
14
|
-
|
14
|
+
# Hack magic because requires are not playing well with spring
|
15
|
+
require "command_proposal/services/command_interpreter"
|
16
|
+
|
17
|
+
params = params.to_unsafe_h if params.is_a?(ActionController::Parameters)
|
18
|
+
|
19
|
+
iteration = ::CommandProposal::Services::CommandInterpreter.command(
|
15
20
|
::CommandProposal::Task.find_by!(friendly_id: friendly_id).primary_iteration,
|
16
21
|
:run,
|
17
22
|
user,
|
18
|
-
|
23
|
+
{ args: params }
|
19
24
|
)
|
25
|
+
|
26
|
+
start = Time.current
|
27
|
+
wait_time = 5 # seconds
|
28
|
+
loop do
|
29
|
+
sleep 0.4
|
30
|
+
|
31
|
+
break if iteration.reload.complete?
|
32
|
+
break if Time.current - start > wait_time
|
33
|
+
end
|
34
|
+
|
35
|
+
iteration
|
20
36
|
end
|
21
37
|
|
22
38
|
def initialize
|
@@ -60,7 +76,11 @@ module CommandProposal
|
|
60
76
|
|
61
77
|
def run
|
62
78
|
begin
|
63
|
-
|
79
|
+
params_str = ""
|
80
|
+
unless @iteration.task.console? # Don't bring params into the console
|
81
|
+
params_str = "params = #{@iteration.args || {}}.with_indifferent_access"
|
82
|
+
end
|
83
|
+
@session.eval("#{bring_function};#{params_str}")
|
64
84
|
rescue Exception => e # rubocop:disable Lint/RescueException - Yes, rescue full Exception so that we can catch typos in evals as well
|
65
85
|
return @iteration.result = results_from_exception(e)
|
66
86
|
end
|
@@ -72,9 +92,10 @@ module CommandProposal
|
|
72
92
|
|
73
93
|
running_thread = Thread.new do
|
74
94
|
begin
|
75
|
-
# Run bring functions in here so we can capture any string outputs
|
95
|
+
# Run `bring` functions in here so we can capture any string outputs
|
76
96
|
# OR! Run the full runner and instead of saving to an iteration, return the string for prepending here
|
77
97
|
result = @session.eval("_ = (#{@iteration.code})").inspect # rubocop:disable Security/Eval - Eval is scary, but in this case it's exactly what we need.
|
98
|
+
result = nil unless @iteration.task.console? # Only store final result for consoles
|
78
99
|
status = :success
|
79
100
|
rescue Exception => e # rubocop:disable Lint/RescueException - Yes, rescue full Exception so that we can catch typos in evals as well
|
80
101
|
status = :failed
|
@@ -104,7 +125,7 @@ module CommandProposal
|
|
104
125
|
|
105
126
|
$stdout = stored_stdout
|
106
127
|
@iteration.status = status
|
107
|
-
@iteration.result = [output,
|
128
|
+
@iteration.result = [output, result].compact.join("\n")
|
108
129
|
end
|
109
130
|
|
110
131
|
def bring_function
|
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.14
|
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-01-17 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rails
|