command_proposal 1.0.11 → 1.0.12

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3b612ae41c1fc8d8b0026ece90e73dae10192904a4ff739a8e11b2185e12c6f4
4
- data.tar.gz: ae0ff52ce13d86c9fa32210d1d0121ac52a891aa87e5a951ee6c3e7796fe1a06
3
+ metadata.gz: ab0271a9e0e80bf0df78d8284cc8e13d292ef2254ab57180b1434f50b351dcb7
4
+ data.tar.gz: 21b077ee6cfa9ede8ea83315523297ff9268bf5236de797c9f3acae9f1ef3910
5
5
  SHA512:
6
- metadata.gz: 0f5742d0fb9bbf6b642e978a3ce6f017dfdeca31b9c3776ee720b1b8e27d4ade589bc9f776ef4fb0a4c16891c4617aa5c21b375760d75e2d3ce3d1172261fca2
7
- data.tar.gz: 17a14923c1c3b23942005d8d448bb621e4aafa72c4bf4ae75a8a9bb863e682b05f2fd0d0caf55ca70b087cb376598615c81574bec62a96fdaac6f5afee018402
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
- result.textContent = json.result
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
  }
@@ -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 cmd_config.approval_required?
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 cmd_config.approval_required?
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 cmd_config.approval_required?
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
- iterations.create(code: new_code, requester: user)
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
- if iteration.result.present?
5
- -%><div class="result"><%= iteration.result %></div><%
6
- end
7
- -%></div>
8
- <% end -%>
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
- -%><div class="line"><%= line -%></div><%
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.first_iteration.update(status: :terminated, completed_at: Time.current)
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
@@ -1,3 +1,3 @@
1
1
  module CommandProposal
2
- VERSION = "1.0.11"
2
+ VERSION = "1.0.12"
3
3
  end
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.11
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-09-25 00:00:00.000000000 Z
11
+ date: 2021-10-26 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails