web_sandbox_console 0.2.0 → 0.3.0

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: 59bdbe60c0c4fa3569ef776901c06e5519cdd804fd905fe1c81e15cfd269157c
4
- data.tar.gz: d71b8d16c04d900fb270f196d1e2bf89b71e77ca2620e48bfdc178158637e5a6
3
+ metadata.gz: c4c806cc25ee28c518675afdae02525a3c36f201b4e7d1e282b0924afba91725
4
+ data.tar.gz: f6cbacc2cea644ca0baf438b2c8080e92ef9b6d28ab10305c0cf897beac04723
5
5
  SHA512:
6
- metadata.gz: a8f4487e10e9796fbaefa3ee6d4f8b3112e493adf165d6cacaefa71a6975d436e849ce65ad97af3ba76ad5b3261787bcc15db205576a284120a0fae50f9ce74e
7
- data.tar.gz: d5395e322921350c55bd3e936782cac340e6f772e075166c81860378c695e79a4711b91016d06590d58384dc812084feef4496a739f64c24acf560b017655343
6
+ metadata.gz: 7ac31b27cb734fe08a57be44e8295f03497a95bfb7720881349497a89b5465ffd39a86700d8a4b2fee2a00c4da318da13e514fe96d646d53572abe80edd682c2
7
+ data.tar.gz: 5c3d3ec3910f929ffba5f6bca090e55752f3c82c162b22ceeb1b7a48c91b321d5d00076602cb8aca45715f8cdafbc4377d2e0db5a59ed4759085798cfe4a984a
data/README.md CHANGED
@@ -18,17 +18,22 @@ gem 'web_sandbox_console'
18
18
  $ bundle
19
19
  ```
20
20
 
21
- 此时,如果是在本地,你访问 `http://localhost:3000/web_sandbox_console` 就能看到web控制台了。
21
+ 此时,如果是在本地,你访问 `http://localhost:3000/web_sandbox_console` 就能看到web控制台了,下面这个样子。
22
+ ![Snip20200625_1.png](https://i.loli.net/2020/06/25/ZD5Ns2HzfME4Whx.png)
22
23
 
23
24
  ## 配置
24
- 在bundle后 你就可以正常使用gem了,如果你需要配置的话,才用来看这步
25
+ ```
26
+ 在bundle后,就可以使用一些基础的功能了;
27
+ 如果你不满足基础的功能、或者需要更高的安全性,很有必要仔细了解配置选项,总之还是很推荐对项目进行适当配置;
28
+ 关于配置的详细介绍,在生成的文件中也有详细的说明,参照说明配置即可;
29
+ ```
25
30
 
26
31
  在 rails 项目路径下,执行:
27
32
  ```bash
28
33
  $ rails g web_sandbox_console
29
34
  ```
30
35
 
31
- 这会在项目路径下,创建如下文件(文件中已详细说明用法)
36
+ 这会在项目路径下,创建如下文件(即配置文件)
32
37
  ```ruby
33
38
  # config/initializers/web_sandbox_console.rb
34
39
 
@@ -50,22 +55,79 @@ WebSandboxConsole.setup do |config|
50
55
  # # 配置 黑名单 实例方法
51
56
  # config.instance_method_blacklist = {Kernel: %i(system exec `),File: %i(chmod chown)}
52
57
 
53
- # 文件黑名单列表 (如果是目录 则目录下所有文件都不用)目录以 / 结尾
58
+ # 文件黑名单列表 (如果是目录 则目录下所有文件都不可用)目录以 / 结尾
54
59
  # 默认都是项目路径下的
55
60
  # config.view_file_blacklist = %w(config/secrets.yml vendor/)
56
61
 
62
+ # 配置 文件权限,是否仅能查看log文件,默开启
63
+ #config.only_view_log_file = false
64
+
65
+ # 通过非对称加密方式 升级权限,授权通过后,可获得执行数据权限(PS: 数据操作不再回滚)
66
+ # config.public_key = "-----BEGIN PUBLIC KEY-----\nMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDMbJOE1vQT1jFpaH1GPYzdRJN/\nLh8VePmzXs5BYOLHB0xIjArL1NlXMbCJ+AS2rv3/oHIOdHhEuZw0tmm9DhG100R8\nRjBpsEKCDI88jl9qRkFmD3CVk8XQXv6c2IkRZCYSTvgDkmnKAlORksfw+p0cR2AQ\nlAtAsNsNviKYBzXKfQIDAQAB\n-----END PUBLIC KEY-----\n"
67
+
57
68
  # # 配置 日志路径 默认路径位于项目下
58
69
  # config.console_log_path = "log/web_sandbox_console.log"
59
70
  end
60
71
  ```
61
72
 
62
- ## other
63
- 查看目录或文件路径: `/web_sandbox_console/view_file`
64
- 除了可以查询一些数据外,还可以查看项目路径下任何文件或目录,如果你输入的是一个目录,那么会查出此目录下的文件/目录;如果你输入的是文件,那么将查出文件的内容,默认查看文件的第一行 到 100行,当然你也可以指定你需要查找的行;
73
+ ## 深入了解
74
+ 主要包含两大功能快:代码执行、文件查看,下面分别介绍
75
+
76
+ ### 代码执行
77
+ 1. 提交和异步执行
78
+
79
+ > 提交后代码会立即执行;如果点击异步执行,则代码会在后台异步执行,这对于需要执行非常耗时的代码,强烈建议异步执行;
80
+
81
+ 2. 升级权限
82
+ ![Snip20200625_2.png](https://i.loli.net/2020/06/25/GQXlpwLryjtVfdO.png)
83
+ ```
84
+ 通常你能做的操作就是,查查数据等,数据的所有操作都不会写入到数据库,这样很安全;但是每当你需要修改数据时,还是需要让运维处理;为了满足可以数据写入、和安全性的要求;开发了升级权限这个功能。
85
+
86
+ 升级权限你需要在配置文件中配置公钥,自己保存私匙;整个过程采用非对称加密的方式,进行授权,还是比较安全的。
87
+ 升级授权成功后,代码执行将不再回滚,会直接写入数据库。
88
+ ```
89
+
90
+ 升级权限流程如下:
91
+ > - `config/initializers/web_sandbox_console.rb`配置公钥
92
+ > - 进入授权页面,点击获取令牌 `web_sandbox_console/auth_page`
93
+ > - 用私钥对令牌加密,然后用base64加密
94
+ > - 将加密的打印结果(注意是puts 文本),输入加密密文框,提交
95
+
96
+ ```ruby
97
+ # 本地生成 加密密文代码
98
+ require 'openssl'
99
+ require 'base64'
100
+
101
+ private_key = "你的私钥"
102
+ p_key = OpenSSL::PKey::RSA.new private_key
103
+ secret_text = p_key.private_encrypt("你的令牌")
104
+ encode_text = Base64.encode64(secret_text)
105
+ puts encode_text
106
+ ```
107
+
108
+ ### 文件查看
109
+ ![Snip20200625_3.png](https://i.loli.net/2020/06/25/ZtaeGlUpc4fJFgW.png)
110
+ 1. 目录和文件
111
+
112
+ > 你可以查看一个目录下有哪些文件夹或文件,你也可以直接查看文件的内容,默认返回一个文件的前100行
113
+
114
+ 2. 指定行数
115
+ ```
116
+ 你可以根据文件总行数,指定查看文件的开始行数、结束行数。
117
+ PS: a. 在过滤文件(过滤内容/过滤时间)的时候,此时指定行数将被忽略
118
+ b. 对于大文件(默认超过10M),处于性能考虑,此时指定行数也会被忽略
119
+ ```
120
+
121
+ 3. 过滤
122
+ ```
123
+ 需要特别说明的是,过滤时间是针对日志文件写的,因此如果是非日志文件,将不会有任何效果。
124
+ 建议:出于性能考虑,过滤时候尽量缩小时间范围
125
+ ```
126
+
127
+ 4. 权限
65
128
 
66
- 如果一个文件特别大,超过10M,此时将查找文件的最后200行,指定行数将无效;当然也可在配置文件中,配置哪些文件不可查看,哪些目录不可以查看,如果指定的是目录,那么目录下的所有子文件或目录都无权查看.
129
+ > 默认情况,只能查看日志文件或目录,当然你也可以去配置做调整。
67
130
 
68
- ![Snip20200508_1.png](https://i.loli.net/2020/05/08/3N8Q7pnzroq9Byw.png)
69
131
  ## Contributing
70
132
  Contribution directions go here.
71
133
 
@@ -18,6 +18,47 @@
18
18
  line-height: 18px;
19
19
  }
20
20
 
21
+ .tip {
22
+ color: red;
23
+ font-size: 12px;
24
+ }
25
+
26
+ .fr {
27
+ float: right;
28
+ }
29
+
30
+ .fl {
31
+ float: left;
32
+ }
33
+
34
+ .h250{
35
+ height: 250px;
36
+ }
37
+
38
+ .margin-t-10{
39
+ margin-top: 10px;
40
+ }
41
+
42
+ .margin-t-100{
43
+ margin-top: 100px;
44
+ }
45
+
46
+ .margin-b-20{
47
+ margin-bottom: 20px;
48
+ }
49
+
50
+ .text_area_div {
51
+ height: 250px;
52
+ width: 88%;
53
+ display: inline-block;
54
+ }
55
+
56
+ .submit_div {
57
+ height: 250px;
58
+ width: 9%;
59
+ display: inline-block;
60
+ }
61
+
21
62
  .container{
22
63
  margin: 20px 15px;
23
64
  }
@@ -35,9 +76,10 @@
35
76
 
36
77
  .text_area_box{
37
78
  font-size: 15px;
38
- height: 150px;
39
- width: 500px;
79
+ height: 100%;
80
+ width: 95%;
40
81
  border-width: 2px;
82
+ overflow: auto;
41
83
  }
42
84
 
43
85
  .clear-button{
@@ -48,6 +90,7 @@
48
90
  color: white;
49
91
  cursor: pointer;
50
92
  border-radius: 5px;
93
+ border: red;
51
94
  }
52
95
 
53
96
  .send-button{
@@ -66,4 +109,15 @@
66
109
  font-size: 15px;
67
110
  color: 15px;
68
111
  cursor: pointer;
69
- }
112
+ border: red;
113
+ }
114
+
115
+ button,input{
116
+ outline:none;
117
+ }
118
+
119
+ button:focus,input:focus,textarea:focus {
120
+ outline: none;
121
+ border: 1px solid #272822;
122
+ }
123
+
@@ -5,7 +5,7 @@
5
5
 
6
6
 
7
7
  .file_or_dir{
8
- width: 500px;
8
+ width: 450px;
9
9
  height: 30px;
10
10
  }
11
11
 
@@ -0,0 +1,82 @@
1
+ require_dependency "web_sandbox_console/application_controller"
2
+
3
+ module WebSandboxConsole
4
+ class AuthorizationController < ApplicationController
5
+ before_action :restrict_ip
6
+ http_basic_authenticate_with name: WebSandboxConsole.http_basic_auth[:name], password: WebSandboxConsole.http_basic_auth[:password] if WebSandboxConsole.http_basic_auth.present?
7
+ before_action :restrict_fetch_token_times, only: :fetch_token
8
+
9
+ # 获取令牌
10
+ def fetch_token
11
+ @token = SecureRandom.uuid
12
+ save_cache_token(@token)
13
+ end
14
+
15
+ # 授权
16
+ def auth
17
+ if params[:secret_text].blank?
18
+ flash[:notice] = '密文为空'
19
+ return redirect_to root_path
20
+ end
21
+
22
+ if public_key.blank?
23
+ flash[:notice] = '公钥未配置'
24
+ return redirect_to root_path
25
+ end
26
+
27
+ result_hash = decrypt_secret_text(params[:secret_text])
28
+ token = result_hash[:content]
29
+ if result_hash[:success] && fetch_cache_token(token)
30
+ flash[:notice] = "授权成功"
31
+ session[:pass_auth] = true
32
+ else
33
+ flash[:notice] = "授权失败:#{result_hash[:content]}"
34
+ end
35
+
36
+ redirect_to root_path
37
+ end
38
+
39
+ private
40
+ # 限制获取token次数 一天内不允许超过20次
41
+ def restrict_fetch_token_times
42
+ cache = Rails.cache
43
+ times = cache.fetch('fetch_token_times', expires_in: 1.day) {0}
44
+
45
+ if times > 20
46
+ flash[:notice] = '一天内获取令牌不允许超过20次'
47
+ rediect_to root_path
48
+ end
49
+ cache.write('fetch_token_times', times + 1)
50
+ end
51
+
52
+ # 保存token 到缓存
53
+ def save_cache_token(key, value = nil)
54
+ Rails.cache.write(key.to_s, value.presence || key.to_s, expires_in: 5.minutes)
55
+ end
56
+
57
+ # 获取 缓存中 token
58
+ def fetch_cache_token(key)
59
+ Rails.cache.read(key.to_s)
60
+ end
61
+
62
+ # 公钥
63
+ def public_key
64
+ WebSandboxConsole.public_key
65
+ end
66
+
67
+ # 解密
68
+ def decrypt_secret_text(secret_text)
69
+ begin
70
+ base_text = Base64.decode64(secret_text)
71
+ p_key = OpenSSL::PKey::RSA.new public_key
72
+ text = p_key.public_decrypt(base_text)
73
+ {success: true, content: text}
74
+ rescue OpenSSL::PKey::RSAError
75
+ {success: false, content: "密钥匹配失败"}
76
+ rescue Exception => e
77
+ {success: false, content: "发生未知错误: #{e.inspect};#{e.backtrace[0..2].join('\r\n')}"}
78
+ end
79
+ end
80
+
81
+ end
82
+ end
@@ -10,7 +10,13 @@ module WebSandboxConsole
10
10
 
11
11
  # 执行代码
12
12
  def eval_code
13
- @results = Sandbox.new(params[:code]).evalotor
13
+ sandbox = Sandbox.new(params[:code], session[:pass_auth])
14
+
15
+ @results = if params[:commit] == '异步执行'
16
+ sandbox.asyn_evalotor
17
+ else
18
+ sandbox.evalotor
19
+ end
14
20
  end
15
21
 
16
22
  def view_file
@@ -18,7 +24,9 @@ module WebSandboxConsole
18
24
 
19
25
  # 查看文件
20
26
  def do_view_file
21
- @results = ViewFile.new(params).view
27
+ results = ViewFile.new(params).view
28
+ @lines = results[:lines]
29
+ @total_line_num = results[:total_line_num]
22
30
  end
23
31
  end
24
32
  end
@@ -12,3 +12,10 @@
12
12
  </div>
13
13
  </body>
14
14
  </html>
15
+
16
+ <script type="text/javascript">
17
+ <% flash.each do |type, msg| %>
18
+ <% tip = type == 'notice' ? '提示' : '其它' %>
19
+ alert("<%= tip %>: <%= msg %>");
20
+ <% end %>
21
+ </script>
@@ -0,0 +1,35 @@
1
+ # 授权
2
+ <p>
3
+ <%= link_to '控制台', web_sandbox_console.root_path %>
4
+ <%= link_to '获取令牌', web_sandbox_console.fetch_token_path(:format => "js"), remote: true %>
5
+ <span> PS: 注意获取令牌一天最多20次,有效期仅5分钟</span>
6
+ </p>
7
+
8
+
9
+ <h2>授权申请</h1>
10
+ <div class="">
11
+ <%= form_tag web_sandbox_console.auth_path, method: :post do %>
12
+ <table>
13
+ <tr>
14
+ <td>加密密文</td>
15
+ <td>
16
+ <%= text_area_tag :secret_text, '', style: "width: 300px;height: 100px;" %>
17
+ </td>
18
+ <td><%= submit_tag '提交', data: { disable_with: "已发送,处理中..." }, class: 'view-file-button' %></td>
19
+ </table>
20
+ <% end %>
21
+ </div>
22
+
23
+ <h4>说明令牌使用说明:</h4>
24
+ <div>
25
+ <pre>
26
+ require 'openssl'
27
+ require 'base64'
28
+
29
+ private_key = "你的私钥"
30
+ p_key = OpenSSL::PKey::RSA.new private_key
31
+ secret_text = p_key.private_encrypt("你的令牌")
32
+ encode_text = Base64.encode64(secret_text)
33
+ puts encode_text
34
+ </pre>
35
+ </div>
@@ -0,0 +1,3 @@
1
+ alert("令牌: <%= @token %>,此令牌先保存,后续使用。");
2
+
3
+
@@ -1,5 +1,7 @@
1
1
  $(".output-content").empty();
2
- $(".output-content").append('<p>============ 查找结果如下 ===============</p>');
3
- <% @results.each do |result| %>
2
+ <% line_num_show = @total_line_num ? "原始文件总行数:#{@total_line_num}" : "" %>
3
+
4
+ $(".output-content").append("<p>============ <%= Time.current %> 查找结果如下 <%= line_num_show %> ===============</p>");
5
+ <% @lines.each do |result| %>
4
6
  $(".output-content").append('<pre><%= escape_javascript result %></pre>');
5
7
  <% end %>
@@ -1,4 +1,4 @@
1
- $(".output-content").append('<p>============ 本次输出如下(仅代表最后行输出)===============</p>');
1
+ $(".output-content").append("<p>============ <%= Time.current %> 本次输出如下 ===============</p>");
2
2
  <% @results.each do |result| %>
3
3
  $(".output-content").append('<p><%= escape_javascript result %></p>');
4
4
  <% end %>
@@ -1,13 +1,28 @@
1
1
  控制台 index
2
2
  <%= link_to '查看文件', web_sandbox_console.view_file_path %>
3
+ <% unless session[:pass_auth] %>
4
+ <%= link_to '升级授权', web_sandbox_console.auth_page_path %>
5
+ <% end %>
3
6
 
4
- <div class="console">
7
+ <div class="console margin-b-20">
5
8
  <%= form_tag(web_sandbox_console.eval_code_path, remote: true, class: 'form') do %>
6
- <%= text_area_tag :code, '', class: 'text_area_box'%>
7
- <input type="reset" value="重置" class='reset-button'>
8
- <%= submit_tag '提交', data: { disable_with: "已发送,处理中..." }, class: 'send-button' %>
9
+ <div class= "fl text_area_div">
10
+ <%= text_area_tag :code, '', class: 'text_area_box'%>
11
+ </div>
12
+
13
+ <div class="submit_div">
14
+ <div class="fr">
15
+ <div class="margin-t-100"><input type="reset" value="重置" class='reset-button' %></div>
16
+
17
+ <div class="margin-t-10">
18
+ <% if session[:pass_auth] %>
19
+ <span class="tip">授权成功(当前修改将写入数据库)</span>
20
+ <% end %>
21
+ <%= submit_tag '提交', data: { disable_with: "已发送,处理中..." }, class: 'send-button' %></div>
22
+ <div class="margin-t-10"><%= submit_tag '异步执行', data: { disable_with: "已发送,处理中..." }, class: 'send-button' %></div>
23
+ </div>
24
+ </div>
9
25
  <% end %>
10
-
11
26
  </div>
12
27
 
13
28
  <%= button_tag '清除输出', type: 'button', onclick: "$('.output-content').empty();", class: 'clear-button' %>
@@ -12,12 +12,27 @@
12
12
  <td><%= text_field_tag :start_line_num, nil, class: 'h30', placeholder: '默认从第1行开始...' %></td>
13
13
  <td>结束行数</td>
14
14
  <td><%= text_field_tag :end_line_num, nil, class: 'h30', placeholder: '默认从第100行结束...' %></td>
15
+
16
+ </tr>
17
+ <tr>
18
+ <td>过滤内容</td>
19
+ <td>
20
+ <%= text_field_tag :grep_content, nil, class: 'file_or_dir' %>
21
+ </td>
22
+ <td>过滤开始时间</td>
23
+ <td><%= text_field_tag :sed_start_time, nil, class: 'h30', placeholder: Time.current.strftime("%F %T") %></td>
24
+
25
+ <td>过滤结束时间</td>
26
+ <td><%= text_field_tag :sed_end_time, nil, class: 'h30', placeholder: Time.current.strftime("%F %T") %></td>
15
27
  <td><%= submit_tag '提交', data: { disable_with: "已发送,处理中..." }, class: 'view-file-button' %></td>
28
+ </tr>
16
29
  </table>
17
30
  <% end %>
31
+ <span class="tip">注意:过滤时将忽略筛选行数</span>
18
32
  </div>
19
33
 
20
34
  <div class="output-content">
35
+
21
36
  </div>
22
37
 
23
38
  <style type="text/css">
@@ -4,4 +4,10 @@ WebSandboxConsole::Engine.routes.draw do
4
4
  post :eval_code, to: "home#eval_code"
5
5
  get :view_file, to: "home#view_file"
6
6
  post :do_view_file, to: "home#do_view_file"
7
+
8
+ # 授权相关
9
+ get :fetch_token, to: "authorization#fetch_token"
10
+ get :auth_page, to: "authorization#auth_page"
11
+ post :auth, to: "authorization#auth"
7
12
  end
13
+
@@ -16,10 +16,16 @@ WebSandboxConsole.setup do |config|
16
16
  # # 配置 黑名单 实例方法
17
17
  # config.instance_method_blacklist = {Kernel: %i(system exec `),File: %i(chmod chown)}
18
18
 
19
- # 文件黑名单列表 (如果是目录 则目录下所有文件都不用)目录以 / 结尾
19
+ # 文件黑名单列表 (如果是目录 则目录下所有文件都不可用)目录以 / 结尾
20
20
  # 默认都是项目路径下的
21
21
  # config.view_file_blacklist = %w(config/secrets.yml vendor/)
22
22
 
23
+ # 配置 文件权限,是否仅能查看log文件,默开启
24
+ #config.only_view_log_file = false
25
+
26
+ # 通过非对称加密方式 升级权限,授权通过后,可获得执行数据权限(PS: 数据操作不再回滚)
27
+ # config.public_key = "-----BEGIN PUBLIC KEY-----\nMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDMbJOE1vQT1jFpaH1GPYzdRJN/\nLh8VePmzXs5BYOLHB0xIjArL1NlXMbCJ+AS2rv3/oHIOdHhEuZw0tmm9DhG100R8\nRjBpsEKCDI88jl9qRkFmD3CVk8XQXv6c2IkRZCYSTvgDkmnKAlORksfw+p0cR2AQ\nlAtAsNsNviKYBzXKfQIDAQAB\n-----END PUBLIC KEY-----\n"
28
+
23
29
  # # 配置 日志路径 默认路径位于项目下
24
30
  # config.console_log_path = "log/web_sandbox_console.log"
25
31
  end
@@ -11,14 +11,19 @@ module WebSandboxConsole
11
11
  mattr_accessor :instance_method_blacklist
12
12
  # 文件列表 黑名单
13
13
  mattr_accessor :view_file_blacklist
14
+ # 仅能查看日志文件
15
+ mattr_accessor :only_view_log_file
14
16
  # 日志路径
15
17
  mattr_accessor :console_log_path
16
18
  # 挂载 引擎路由位置
17
19
  mattr_accessor :mount_engine_route_path
20
+ # 公钥
21
+ mattr_accessor :public_key
18
22
 
19
23
  # 默认 引擎路由位置
20
24
  @@mount_engine_route_path = '/web_sandbox_console'
21
-
25
+ # 默认 开启仅可查看日志
26
+ @@only_view_log_file = true
22
27
 
23
28
  # 内置 实例方法 黑名单
24
29
  INSTANT_METOD_BUILT_IN_BLACKLIST = {
@@ -2,22 +2,42 @@ module WebSandboxConsole
2
2
  class Sandbox
3
3
  attr_accessor :code # 代码
4
4
  attr_accessor :uuid # 唯一标识
5
+ attr_accessor :pass_auth # 是否通过授权
5
6
  attr_accessor :exe_tmp_file # 执行临时文件(由code 组成的运行代码)
6
7
 
7
- def initialize(code = nil)
8
+ def initialize(code = nil, pass_auth = false)
8
9
  @code = code
9
10
  @uuid = SecureRandom.uuid
11
+ @pass_auth = pass_auth.presence || false
10
12
  @exe_tmp_file = "#{Rails.root}/tmp/sandbox/#{uuid}.rb"
11
13
  end
12
14
 
15
+ # 同步执行
13
16
  def evalotor
17
+ evalotor_block do
18
+ exec_rails_runner
19
+ get_result
20
+ end
21
+ end
22
+
23
+ # 异步后台执行
24
+ def asyn_evalotor
25
+ evalotor_block do
26
+ Thread.new {exec_rails_runner}
27
+ ["已在后台执行,请耐心等待😊"]
28
+ end
29
+ end
30
+
31
+ # 执行结构块
32
+ def evalotor_block
14
33
  begin
15
34
  check_syntax
16
35
  write_exe_tmp_file
17
- exec_rails_runner
18
- get_result
36
+ yield
19
37
  rescue SandboxError => e
20
38
  [e.message]
39
+ rescue Exception => e
40
+ ["发生未知错误: #{e.inspect};#{e.backtrace[0..2].join('\r\n')}"]
21
41
  end
22
42
  end
23
43
 
@@ -28,7 +48,7 @@ module WebSandboxConsole
28
48
  begin
29
49
  ActiveRecord::Base.transaction(requires_new: true) do
30
50
  result = eval(#{self.code.inspect})
31
- raise ActiveRecord::Rollback
51
+ raise ActiveRecord::Rollback unless #{self.pass_auth}
32
52
  end
33
53
  rescue Exception => e
34
54
  WebSandboxConsole.log_p(e, "#{self.uuid}")
@@ -82,11 +102,11 @@ module WebSandboxConsole
82
102
 
83
103
  # 运行rails runner
84
104
  def exec_rails_runner
85
- `bundle exec rails runner #{self.exe_tmp_file}`
105
+ @stdout = `bundle exec rails runner #{self.exe_tmp_file}`
86
106
  end
87
107
 
88
- # 获取 执行结果
89
- def get_result
108
+ # 返回结果
109
+ def return_result_arr
90
110
  last_10_lines = `tail -n 10 #{WebSandboxConsole.log_path} | grep #{self.uuid}`
91
111
 
92
112
  last_10_lines.split("\n").map do |line|
@@ -94,5 +114,16 @@ module WebSandboxConsole
94
114
  end.flatten
95
115
  end
96
116
 
117
+ # 最终结果
118
+ def get_result
119
+ if @stdout.present?
120
+ stdout_arr = @stdout.to_s.split("\n")
121
+ stdout_arr << '------------ 返回值 ----------'
122
+ stdout_arr.concat(return_result_arr)
123
+ else
124
+ return_result_arr
125
+ end
126
+ end
127
+
97
128
  end
98
129
  end
@@ -1,3 +1,3 @@
1
1
  module WebSandboxConsole
2
- VERSION = '0.2.0'
2
+ VERSION = '0.3.0'
3
3
  end
@@ -3,11 +3,17 @@ module WebSandboxConsole
3
3
  attr_accessor :file_or_dir # 文件或目录
4
4
  attr_accessor :start_line_num # 起始行数
5
5
  attr_accessor :end_line_num # 结束行数
6
+ attr_accessor :sed_start_time # 开始时间
7
+ attr_accessor :sed_end_time # 结束时间
8
+ attr_accessor :grep_content # 过滤内容
6
9
 
7
10
  def initialize(opts = {})
8
11
  @file_or_dir = opts[:file_or_dir]
9
- @start_line_num = opts[:start_line_num].present? ? opts[:start_line_num].to_i : 1
10
- @end_line_num = opts[:end_line_num].present? ? opts[:end_line_num].to_i : 100
12
+ @start_line_num = (opts[:start_line_num].presence || 1).to_i
13
+ @end_line_num = (opts[:end_line_num].presence || 100).to_i
14
+ @sed_start_time = parse_time(opts[:sed_start_time])
15
+ @sed_end_time = parse_time(opts[:sed_end_time])
16
+ @grep_content = opts[:grep_content]
11
17
  end
12
18
 
13
19
  def view
@@ -15,9 +21,10 @@ module WebSandboxConsole
15
21
  check_param
16
22
  file_or_dir_exists
17
23
  check_blacklist
24
+ check_only_view_log
18
25
  view_file
19
26
  rescue ViewFileError => e
20
- [e.message]
27
+ {lines: [e.message]}
21
28
  end
22
29
  end
23
30
 
@@ -74,12 +81,20 @@ module WebSandboxConsole
74
81
  raise ViewFileError, '文件或目录无权限查看' if black_lists.include?(file_or_dir_path) || black_lists.include?(file_or_dir_path + '/')
75
82
  end
76
83
 
84
+ # 检查 仅能查看日志
85
+ def check_only_view_log
86
+ if WebSandboxConsole.only_view_log_file
87
+ raise ViewFileError, '仅可查看日志目录/文件' unless file_or_dir.split("/")[0] == 'log'
88
+ end
89
+ end
90
+
77
91
  # 目录下文件
78
92
  def files_in_dir
79
- Dir["#{file_or_dir_path}/*"].map do |path|
93
+ lines = Dir["#{file_or_dir_path}/*"].map do |path|
80
94
  path += is_directory?(path) ? '(目录)' : '(文件)'
81
95
  path[file_or_dir_path.length..-1]
82
96
  end
97
+ {lines: lines}
83
98
  end
84
99
 
85
100
  # 是否为大文件
@@ -87,19 +102,36 @@ module WebSandboxConsole
87
102
  File.new(file_or_dir_path).size > 10.megabytes
88
103
  end
89
104
 
105
+ # 是否需要过滤
106
+ def need_grep?
107
+ @sed_start_time || @grep_content.present?
108
+ end
109
+
90
110
  # 查看文件/目录
91
111
  def view_file
92
112
  if is_directory?(file_or_dir_path)
93
113
  files_in_dir
94
- else
95
- lines = is_big_file? ? tail_200_line : special_line_content
96
- add_line_num(lines)
114
+ else # 文件
115
+ lines = if need_grep?
116
+ grep_file_content
117
+ elsif is_big_file?
118
+ tail_any_line(1000)
119
+ else
120
+ special_line_content
121
+ end
122
+
123
+ {lines: add_line_num(lines), total_line_num: cal_file_total_line_num}
97
124
  end
98
125
  end
99
126
 
100
- # 最后 200 行内容
101
- def tail_200_line
102
- (`tail -n 200 #{file_or_dir_path}`).split(/[\r,\r\n]/)
127
+ # 计算文件总行数
128
+ def cal_file_total_line_num
129
+ `wc -l < #{file_or_dir_path}`.to_i
130
+ end
131
+
132
+ # 最后 xx 行内容
133
+ def tail_any_line(num)
134
+ (`tail -n #{num} #{file_or_dir_path}`).split(/[\r,\r\n]/)
103
135
  end
104
136
 
105
137
  # 按指定行返回
@@ -107,11 +139,31 @@ module WebSandboxConsole
107
139
  File.readlines(file_or_dir_path)[(start_line_num - 1)..(end_line_num - 1)]
108
140
  end
109
141
 
142
+ # 过滤文件
143
+ def grep_file_content
144
+ content = if @sed_start_time && @grep_content.present?
145
+ `sed -n '/#{@sed_start_time}/,/#{@sed_end_time}/p' #{file_or_dir_path} | grep #{@grep_content}`
146
+ elsif @sed_start_time
147
+ `sed -n '/#{@sed_start_time}/,/#{@sed_end_time}/p' #{file_or_dir_path}`
148
+ else
149
+ `grep #{@grep_content} #{file_or_dir_path}`
150
+ end
151
+
152
+ content.split(/[\r,\r\n]/)
153
+ end
154
+
110
155
  # 添加行号
111
156
  def add_line_num(lines)
112
157
  start_num = is_big_file? ? 1 : start_line_num
113
158
  lines.each_with_index.map{|line, index| "#{index + start_num}: #{line}"}
114
159
  end
115
160
 
161
+ private
162
+ # 解析时间
163
+ def parse_time(str)
164
+ DateTime.parse(str).strftime("%FT%T") rescue nil
165
+ end
166
+
167
+
116
168
  end
117
169
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: web_sandbox_console
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - dongmingyan
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-05-08 00:00:00.000000000 Z
11
+ date: 2020-06-25 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -55,13 +55,15 @@ files:
55
55
  - app/assets/stylesheets/web_sandbox_console/application.css
56
56
  - app/assets/stylesheets/web_sandbox_console/home.css
57
57
  - app/controllers/web_sandbox_console/application_controller.rb
58
+ - app/controllers/web_sandbox_console/authorization_controller.rb
58
59
  - app/controllers/web_sandbox_console/home_controller.rb
59
60
  - app/helpers/web_sandbox_console/application_helper.rb
60
61
  - app/helpers/web_sandbox_console/home_helper.rb
61
- - app/jobs/web_sandbox_console/application_job.rb
62
62
  - app/mailers/web_sandbox_console/application_mailer.rb
63
63
  - app/models/web_sandbox_console/application_record.rb
64
64
  - app/views/layouts/web_sandbox_console/application.html.erb
65
+ - app/views/web_sandbox_console/authorization/auth_page.html.erb
66
+ - app/views/web_sandbox_console/authorization/fetch_token.js.erb
65
67
  - app/views/web_sandbox_console/home/do_view_file.js.erb
66
68
  - app/views/web_sandbox_console/home/eval_code.js.erb
67
69
  - app/views/web_sandbox_console/home/index.html.erb
@@ -1,4 +0,0 @@
1
- module WebSandboxConsole
2
- class ApplicationJob < ActiveJob::Base
3
- end
4
- end