command_proposal 1.0.11 → 1.0.12
Sign up to get free protection for your applications and to get access to all the features.
- 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 +6 -0
- data/app/assets/stylesheets/command_proposal/terminal.scss +2 -7
- data/app/controllers/command_proposal/iterations_controller.rb +0 -1
- data/app/controllers/command_proposal/runner_controller.rb +1 -0
- data/app/controllers/command_proposal/tasks_controller.rb +2 -0
- data/app/helpers/command_proposal/permissions_helper.rb +7 -3
- data/app/models/command_proposal/iteration.rb +7 -0
- data/app/models/command_proposal/task.rb +12 -2
- 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/_task_detail_table.html.erb +1 -1
- data/lib/command_proposal/services/command_interpreter.rb +3 -2
- data/lib/command_proposal/services/runner.rb +2 -1
- data/lib/command_proposal/version.rb +1 -1
- 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: ab0271a9e0e80bf0df78d8284cc8e13d292ef2254ab57180b1434f50b351dcb7
|
4
|
+
data.tar.gz: 21b077ee6cfa9ede8ea83315523297ff9268bf5236de797c9f3acae9f1ef3910
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 52a46842e394c99d8dbb4527c222ab93e93dc437992996b3b9af3850a9bc6571bac4e472268c7f821d6b541c17074d21954d0b18e5e15d2109141a4f04179491
|
7
|
+
data.tar.gz: eb670b929efd2d6c612997e569276fac67aa494325db38d357a5589496bdba19b0d5335d68696d715022b45e9ed13eae4e2245d4ddc12f53371ad3d611e4987b
|
@@ -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,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,7 +28,6 @@ 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
|
@@ -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)
|
@@ -51,6 +51,7 @@ class ::CommandProposal::TasksController < ::CommandProposal::EngineController
|
|
51
51
|
def create
|
52
52
|
@task = ::CommandProposal::Task.new(task_params.except(:code))
|
53
53
|
@task.user = command_user
|
54
|
+
@task.skip_approval = true unless approval_required?
|
54
55
|
|
55
56
|
# Cannot create the iteration until the task is created, so save then update
|
56
57
|
if @task.save && @task.update(task_params)
|
@@ -69,6 +70,7 @@ class ::CommandProposal::TasksController < ::CommandProposal::EngineController
|
|
69
70
|
def update
|
70
71
|
@task = ::CommandProposal::Task.find_by!(friendly_id: params[:id])
|
71
72
|
@task.user = command_user
|
73
|
+
@task.skip_approval = true unless approval_required?
|
72
74
|
|
73
75
|
if @task.update(task_params)
|
74
76
|
redirect_to cmd_path(@task)
|
@@ -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,9 @@ 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
|
+
|
24
27
|
has_many :comments
|
25
28
|
belongs_to :task
|
26
29
|
external_belongs_to :requester
|
@@ -70,6 +73,10 @@ class ::CommandProposal::Iteration < ApplicationRecord
|
|
70
73
|
(completed_at || stopped_at || Time.current) - started_at
|
71
74
|
end
|
72
75
|
|
76
|
+
def end_time
|
77
|
+
completed_at || stopped_at || Time.current
|
78
|
+
end
|
79
|
+
|
73
80
|
def force_reset
|
74
81
|
# Debugging method. Should never actually be called.
|
75
82
|
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"
|
@@ -79,7 +79,17 @@ class ::CommandProposal::Task < ApplicationRecord
|
|
79
79
|
end
|
80
80
|
|
81
81
|
def code=(new_code)
|
82
|
-
|
82
|
+
if skip_approval
|
83
|
+
iterations.create(
|
84
|
+
code: new_code,
|
85
|
+
requester: user,
|
86
|
+
status: :approved,
|
87
|
+
approver: user,
|
88
|
+
approved_at: Time.current
|
89
|
+
)
|
90
|
+
else
|
91
|
+
iterations.create(code: new_code, requester: user)
|
92
|
+
end
|
83
93
|
end
|
84
94
|
|
85
95
|
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.completed?
|
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 -%>
|
@@ -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>
|
@@ -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
|
@@ -90,7 +90,8 @@ module CommandProposal
|
|
90
90
|
begin
|
91
91
|
# Run bring functions in here so we can capture any string outputs
|
92
92
|
# OR! Run the full runner and instead of saving to an iteration, return the string for prepending here
|
93
|
-
@session.eval("_ = (#{@iteration.code})").inspect # rubocop:disable Security/Eval - Eval is scary, but in this case it's exactly what we need.
|
93
|
+
result = @session.eval("_ = (#{@iteration.code})").inspect # rubocop:disable Security/Eval - Eval is scary, but in this case it's exactly what we need.
|
94
|
+
result = nil unless @iteration.task.console? # Only store final result for consoles
|
94
95
|
status = :success
|
95
96
|
rescue Exception => e # rubocop:disable Lint/RescueException - Yes, rescue full Exception so that we can catch typos in evals as well
|
96
97
|
status = :failed
|
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.12
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Rocco Nicholls
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2021-
|
11
|
+
date: 2021-10-26 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rails
|