web_sandbox_console 0.3.0 → 0.4.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: c4c806cc25ee28c518675afdae02525a3c36f201b4e7d1e282b0924afba91725
4
- data.tar.gz: f6cbacc2cea644ca0baf438b2c8080e92ef9b6d28ab10305c0cf897beac04723
3
+ metadata.gz: 92242034900727caa3f1920df25ef3fc88b886b647d0444431708016785d422a
4
+ data.tar.gz: 1216442a9f5408aa82218e3dc8ad73e5e896a03b2c9caaa59bb90dc64c6cc09a
5
5
  SHA512:
6
- metadata.gz: 7ac31b27cb734fe08a57be44e8295f03497a95bfb7720881349497a89b5465ffd39a86700d8a4b2fee2a00c4da318da13e514fe96d646d53572abe80edd682c2
7
- data.tar.gz: 5c3d3ec3910f929ffba5f6bca090e55752f3c82c162b22ceeb1b7a48c91b321d5d00076602cb8aca45715f8cdafbc4377d2e0db5a59ed4759085798cfe4a984a
6
+ metadata.gz: 0bc65c60e3e7657b4475b54c93fbf533c9468ffdb5b3af468bf3d4a9f7aef37dc04c938076b00908e37c41d672a99a38a7de81683649004483af21e162fec721
7
+ data.tar.gz: 98d09ce9a324f83fb7be0ca158d8090252b6b7ef359b32ff595ad43aa96052b7d40334d679d8a2a6bf9312756d421391418fb11cae18c6cb42b9a593c1699ef7
data/README.md CHANGED
@@ -19,7 +19,7 @@ $ bundle
19
19
  ```
20
20
 
21
21
  此时,如果是在本地,你访问 `http://localhost:3000/web_sandbox_console` 就能看到web控制台了,下面这个样子。
22
- ![Snip20200625_1.png](https://i.loli.net/2020/06/25/ZD5Ns2HzfME4Whx.png)
22
+ ![Snip20200703_1.png](https://i.loli.net/2020/07/03/62JD5ErcPbAwHSn.png)
23
23
 
24
24
  ## 配置
25
25
  ```
@@ -33,10 +33,15 @@ $ bundle
33
33
  $ rails g web_sandbox_console
34
34
  ```
35
35
 
36
- 这会在项目路径下,创建如下文件(即配置文件)
36
+ 这会在项目路径下,创建两个配置文件
37
37
  ```ruby
38
38
  # config/initializers/web_sandbox_console.rb
39
39
 
40
+ require 'yaml'
41
+
42
+ config_file_path = "#{Rails.root}/config/web_sandbox_console.yml"
43
+ config_hash = File.exists?(config_file_path) ? YAML.load_file(config_file_path).with_indifferent_access[:web_sandbox_console] : {}
44
+
40
45
  # web_sandbox_console 配置文件
41
46
  # 以下配置 都是可选的 缺少的情况下用默认值 或者 不生效
42
47
  WebSandboxConsole.setup do |config|
@@ -46,8 +51,10 @@ WebSandboxConsole.setup do |config|
46
51
  # 配置 ip 白名单
47
52
  # config.ip_whitelist = %w(192.168.23.12 192.145.2.0/24)
48
53
 
49
- # # 配置 基本认证
50
- # config.http_basic_auth = {name: 'dmy', password: '123456'}
54
+ # 配置 基本认证 在 config/web_sandbox_console.yml中配置
55
+ # PS: 1. 即使config/web_sandbox_console.yml文件不存在,也不会有任何使用上的影响,效果相当于没有开启
56
+ # 2. 下面这行不用注释掉,只要不配置yml文件就行
57
+ config.http_basic_auth = config_hash[:http_basic_auth]
51
58
 
52
59
  # # 配置 黑名单 类方法
53
60
  # config.class_method_blacklist = {File: %i(delete read write),Dir: %i(new delete mkdir)}
@@ -59,19 +66,32 @@ WebSandboxConsole.setup do |config|
59
66
  # 默认都是项目路径下的
60
67
  # config.view_file_blacklist = %w(config/secrets.yml vendor/)
61
68
 
62
- # 配置 文件权限,是否仅能查看log文件,默开启
69
+ # 配置 文件权限,是否仅能查看log文件,默认开启
63
70
  #config.only_view_log_file = false
64
71
 
65
72
  # 通过非对称加密方式 升级权限,授权通过后,可获得执行数据权限(PS: 数据操作不再回滚)
66
- # config.public_key = "-----BEGIN PUBLIC KEY-----\nMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDMbJOE1vQT1jFpaH1GPYzdRJN/\nLh8VePmzXs5BYOLHB0xIjArL1NlXMbCJ+AS2rv3/oHIOdHhEuZw0tmm9DhG100R8\nRjBpsEKCDI88jl9qRkFmD3CVk8XQXv6c2IkRZCYSTvgDkmnKAlORksfw+p0cR2AQ\nlAtAsNsNviKYBzXKfQIDAQAB\n-----END PUBLIC KEY-----\n"
73
+ # PS:配置同 http_basic_auth
74
+ config.public_key = config_hash[:public_key]
67
75
 
68
76
  # # 配置 日志路径 默认路径位于项目下
69
77
  # config.console_log_path = "log/web_sandbox_console.log"
70
78
  end
71
79
  ```
72
80
 
81
+ 主要用于配置 http_basic_auth 和 public_key,如需使用基本授权、升级授权,参照example文件创建yml文件即可
82
+ ```ruby
83
+ # config/web_sandbox_console.yml.example
84
+
85
+ web_sandbox_console:
86
+ http_basic_auth:
87
+ name: dmy
88
+ password: 123456
89
+ public_key: "-----BEGIN PUBLIC KEY-----\nMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDMbJOE1vQT1jFpaH1GPYzdRJN/\nLh8VePmzXs5BYOLHB0xIjArL1NlXMbCJ+AS2rv3/oHIOdHhEuZw0tmm9DhG100R8\nRjBpsEKCDI88jl9qRkFmD3CVk8XQXv6c2IkRZCYSTvgDkmnKAlORksfw+p0cR2AQ\nlAtAsNsNviKYBzXKfQIDAQAB\n-----END PUBLIC KEY-----\n"
90
+
91
+ ```
92
+
73
93
  ## 深入了解
74
- 主要包含两大功能快:代码执行、文件查看,下面分别介绍
94
+ 主要包含三大功能块:代码执行、文件查看、日志下载,下面分别介绍
75
95
 
76
96
  ### 代码执行
77
97
  1. 提交和异步执行
@@ -79,16 +99,17 @@ end
79
99
  > 提交后代码会立即执行;如果点击异步执行,则代码会在后台异步执行,这对于需要执行非常耗时的代码,强烈建议异步执行;
80
100
 
81
101
  2. 升级权限
82
- ![Snip20200625_2.png](https://i.loli.net/2020/06/25/GQXlpwLryjtVfdO.png)
102
+ ![Snip20200703_1.png](https://i.loli.net/2020/07/03/26zf5WOBFqmaiHC.png)
103
+
83
104
  ```
84
105
  通常你能做的操作就是,查查数据等,数据的所有操作都不会写入到数据库,这样很安全;但是每当你需要修改数据时,还是需要让运维处理;为了满足可以数据写入、和安全性的要求;开发了升级权限这个功能。
85
106
 
86
- 升级权限你需要在配置文件中配置公钥,自己保存私匙;整个过程采用非对称加密的方式,进行授权,还是比较安全的。
107
+ 升级权限你需要在配置文件中配置公钥,自己保存私钥;整个过程采用非对称加密的方式,进行授权,还是比较安全的。
87
108
  升级授权成功后,代码执行将不再回滚,会直接写入数据库。
88
109
  ```
89
110
 
90
111
  升级权限流程如下:
91
- > - `config/initializers/web_sandbox_console.rb`配置公钥
112
+ > - `config/web_sandbox_console.yml`中配置公钥
92
113
  > - 进入授权页面,点击获取令牌 `web_sandbox_console/auth_page`
93
114
  > - 用私钥对令牌加密,然后用base64加密
94
115
  > - 将加密的打印结果(注意是puts 文本),输入加密密文框,提交
@@ -106,7 +127,7 @@ puts encode_text
106
127
  ```
107
128
 
108
129
  ### 文件查看
109
- ![Snip20200625_3.png](https://i.loli.net/2020/06/25/ZtaeGlUpc4fJFgW.png)
130
+ ![Snip20200703_2.png](https://i.loli.net/2020/07/03/zFMjpRSX8fCDQ2i.png)
110
131
  1. 目录和文件
111
132
 
112
133
  > 你可以查看一个目录下有哪些文件夹或文件,你也可以直接查看文件的内容,默认返回一个文件的前100行
@@ -128,6 +149,19 @@ PS: a. 在过滤文件(过滤内容/过滤时间)的时候,此时指定行
128
149
 
129
150
  > 默认情况,只能查看日志文件或目录,当然你也可以去配置做调整。
130
151
 
152
+ ### 日志下载
153
+ ![Snip20200703_3.png](https://i.loli.net/2020/07/03/csW5OfEhPVeSbJz.png)
154
+ 你可以直接输入文件名(需要带后缀)下载日志
155
+
156
+ ### 关于数据导出
157
+ 在gem 中你可用`CSV.open`的方式导出数据,出于安全考虑,这里open方法是复写之后的,内部没有调用`File.open`方法;
158
+
159
+ 有一下几点需注意:
160
+ > 1. 你写入你任何路径,最终都只会在log目录下创建csv文件
161
+ > 2. 文件名以你路径中(/分隔)的最后一个名称为准
162
+ > 3. csv 文件下载后,会自动删除掉,因此只能下载一次
163
+
164
+
131
165
  ## Contributing
132
166
  Contribution directions go here.
133
167
 
@@ -12,5 +12,4 @@
12
12
  //
13
13
  //= require jquery
14
14
  //= require jquery_ujs
15
- //= require turbolinks
16
15
  //= require_tree .
@@ -19,7 +19,7 @@
19
19
  }
20
20
 
21
21
  .tip {
22
- color: red;
22
+ color: #E35520;
23
23
  font-size: 12px;
24
24
  }
25
25
 
@@ -64,7 +64,8 @@
64
64
  }
65
65
 
66
66
  .output-content{
67
- height: 600px;
67
+ min-height: 800px;
68
+ height: 100%;
68
69
  font-size: 14px;
69
70
  background-color: #272822;
70
71
  color: #F8F8F2;
@@ -83,14 +84,14 @@
83
84
  }
84
85
 
85
86
  .clear-button{
86
- background: red;
87
+ background: #20aee3;
87
88
  height: 30px;
88
89
  width: 100px;
89
90
  font-size: 15px;
90
91
  color: white;
91
92
  cursor: pointer;
92
93
  border-radius: 5px;
93
- border: red;
94
+ border: #20aee3;
94
95
  }
95
96
 
96
97
  .send-button{
@@ -103,19 +104,21 @@
103
104
  }
104
105
 
105
106
  .reset-button{
106
- background: red;
107
+ background: #20aee3;
107
108
  color: white;
108
109
  height: 30px;
109
110
  font-size: 15px;
110
111
  color: 15px;
111
112
  cursor: pointer;
112
- border: red;
113
+ border: #20aee3;
113
114
  }
114
115
 
115
116
  button,input{
116
117
  outline:none;
117
118
  }
118
119
 
120
+
121
+
119
122
  button:focus,input:focus,textarea:focus {
120
123
  outline: none;
121
124
  border: 1px solid #272822;
@@ -0,0 +1,218 @@
1
+ body {
2
+ background: #fff;
3
+ font-family: "Montserrat", sans-serif;
4
+ margin: 0;
5
+ overflow-x: hidden;
6
+ color: #67757c;
7
+ font-weight: 300;
8
+ font-size: 14px;
9
+ }
10
+
11
+ * {
12
+ outline: none;
13
+ }
14
+
15
+ a{ text-decoration:none}
16
+
17
+ #main-wrapper {
18
+ width: 100%;
19
+ overflow: hidden;
20
+ }
21
+
22
+ .topbar {
23
+ position: fixed;
24
+ width: 100%;
25
+ z-index: 50;
26
+ background: #ffffff;
27
+ }
28
+
29
+ .topbar .top-navbar {
30
+ min-height: 70px;
31
+ padding: 0px;
32
+ }
33
+
34
+ .topbar .top-navbar .navbar-header {
35
+ line-height: 72px;
36
+ padding-left: 10px;
37
+ border-right: 1px solid rgba(120, 130, 140, 0.13);
38
+ }
39
+
40
+ .navbar-header {
41
+ width: 200px;
42
+ flex-shrink: 0;
43
+ }
44
+
45
+ .topbar .top-navbar .navbar-header .navbar-brand {
46
+ margin-right: 0px;
47
+ padding-bottom: 0px;
48
+ padding-top: 0px;
49
+ }
50
+
51
+ .navbar-brand {
52
+ display: inline-block;
53
+ padding-top: .3125rem;
54
+ padding-bottom: .3125rem;
55
+ margin-right: 1rem;
56
+ font-size: 1.25rem;
57
+ line-height: inherit;
58
+ white-space: nowrap;
59
+ color: rgba(0,0,0,.9);
60
+ }
61
+
62
+ .navbar-light .navbar-brand {
63
+ color: rgba(0,0,0,.9);
64
+ }
65
+
66
+ .topbar .navbar-collapse {
67
+ padding: 0px;
68
+ border-bottom: 1px solid rgba(120, 130, 140, 0.13);
69
+ display: flex!important;
70
+ align-items: center;
71
+ }
72
+
73
+ .left-sidebar {
74
+ position: fixed;
75
+ width: 210px;
76
+ height: 100%;
77
+ top: 0px;
78
+ z-index: 20;
79
+ padding-top: 70px;
80
+ background: #fff;
81
+ border-right: 1px solid rgba(120, 130, 140, 0.13);
82
+ }
83
+
84
+ .ps {
85
+ -ms-touch-action: auto;
86
+ touch-action: auto;
87
+ overflow: hidden !important;
88
+ -ms-overflow-style: none;
89
+ }
90
+
91
+ .scroll-sidebar {
92
+ height: 100%;
93
+ }
94
+
95
+ .sidebar-nav {
96
+ background: #fff;
97
+ padding: 15px 0 0 0px;
98
+ }
99
+
100
+ .sidebar-nav ul {
101
+ margin: 0px;
102
+ padding: 0px;
103
+ }
104
+
105
+ .sidebar-nav > ul > li {
106
+ margin-bottom: 8px;
107
+ margin-top: 8px;
108
+ }
109
+
110
+ .sidebar-nav ul li {
111
+ list-style: none;
112
+ }
113
+
114
+ .sidebar-nav > ul > li > a {
115
+ border-left: 3px solid transparent;
116
+ }
117
+
118
+ .sidebar-nav > ul > li > a:hover {
119
+ color: #20aee3;
120
+ font-size: 16px;
121
+ }
122
+
123
+ .menu-active {
124
+ color: #20aee3;
125
+ font-size: 16px;
126
+ }
127
+
128
+ .sidebar-nav ul li a {
129
+ color: #8d97ad;
130
+ padding: 10px 35px 10px 15px;
131
+ display: block;
132
+ font-size: 15px;
133
+ font-weight: 400;
134
+ }
135
+
136
+ .waves-effect {
137
+ position: relative;
138
+ cursor: pointer;
139
+ display: inline-block;
140
+ overflow: hidden;
141
+ -webkit-user-select: none;
142
+ -moz-user-select: none;
143
+ -ms-user-select: none;
144
+ user-select: none;
145
+ -webkit-tap-highlight-color: transparent;
146
+ vertical-align: middle;
147
+ z-index: 1;
148
+ will-change: opacity, transform;
149
+ }
150
+
151
+ .topbar .top-navbar .navbar-header .navbar-brand b {
152
+ line-height: 70px;
153
+ display: inline-block;
154
+ font-weight: bolder;
155
+ }
156
+
157
+ img {
158
+ vertical-align: middle;
159
+ border-style: none;
160
+ }
161
+
162
+ .sidebar-nav > ul > li > a i {
163
+ color: #787f91;
164
+ }
165
+
166
+ .sidebar-nav > ul > li > a i {
167
+ width: 38px;
168
+ font-size: 24px;
169
+ display: inline-block;
170
+ vertical-align: middle;
171
+ color: #787f91;
172
+ }
173
+
174
+ .fa {
175
+ display: inline-block;
176
+ font: normal normal normal 14px/1 FontAwesome;
177
+ font-size: inherit;
178
+ text-rendering: auto;
179
+ -webkit-font-smoothing: antialiased;
180
+ -moz-osx-font-smoothing: grayscale;
181
+ }
182
+
183
+ .page-wrapper {
184
+ background: #f6f9fa;
185
+ padding-bottom: 60px;
186
+ position: relative;
187
+ margin-left: 200px;
188
+ /*padding-top: 70px;*/
189
+ z-index: 100
190
+ }
191
+
192
+ .container-fluid {
193
+ padding: 25px 25px;
194
+ }
195
+
196
+ .container-fluid {
197
+ width: 100%;
198
+ margin-right: auto;
199
+ margin-left: auto;
200
+ padding-right: 15px;
201
+ padding-left: 15px;
202
+ width: 100%;
203
+ }
204
+
205
+ .navbar-nav {
206
+ display: -ms-flexbox;
207
+ display: flex;
208
+ -ms-flex-direction: column;
209
+ flex-direction: column;
210
+ padding-left: 0;
211
+ margin-bottom: 0;
212
+ list-style: none;
213
+ flex-direction: row;
214
+ }
215
+
216
+ .mr-auto {
217
+ margin-right: auto!important;
218
+ }
@@ -1,6 +1,10 @@
1
1
  module WebSandboxConsole
2
2
  class ApplicationController < ActionController::Base
3
3
  protect_from_forgery with: :exception
4
+
5
+ before_action :restrict_ip
6
+ http_basic_authenticate_with name: WebSandboxConsole.http_basic_auth[:name].to_s, password: WebSandboxConsole.http_basic_auth[:password].to_s if WebSandboxConsole.http_basic_auth.present?
7
+
4
8
 
5
9
  # 限制ip
6
10
  def restrict_ip
@@ -2,8 +2,6 @@ require_dependency "web_sandbox_console/application_controller"
2
2
 
3
3
  module WebSandboxConsole
4
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
5
  before_action :restrict_fetch_token_times, only: :fetch_token
8
6
 
9
7
  # 获取令牌
@@ -2,9 +2,7 @@ require_dependency "web_sandbox_console/application_controller"
2
2
 
3
3
  module WebSandboxConsole
4
4
  class HomeController < 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
-
5
+
8
6
  def index
9
7
  end
10
8
 
@@ -24,9 +22,37 @@ module WebSandboxConsole
24
22
 
25
23
  # 查看文件
26
24
  def do_view_file
27
- results = ViewFile.new(params).view
28
- @lines = results[:lines]
29
- @total_line_num = results[:total_line_num]
25
+ results = ViewFile.new(params).view
26
+ @lines = results[:lines]
27
+ @total_line_num = results[:total_line_num]
28
+ @touch_grep_protect = results[:touch_grep_protect]
29
+ @content_is_trimed = results[:content_is_trimed]
30
+ end
31
+
32
+ # 下载文件页面
33
+ def download_page
30
34
  end
35
+
36
+ # 下载文件
37
+ def download
38
+ if params[:file_name].blank?
39
+ flash[:notice] = "文件名不能为空"
40
+ return redirect_to download_page_path
41
+ end
42
+
43
+ file_full_path = "#{Rails.root}/log/#{params[:file_name]}"
44
+ unless File.exists?(file_full_path)
45
+ flash[:notice] = '文件不存在,请检查文件名;或在其它服务器请多次尝试'
46
+ return redirect_to download_page_path
47
+ end
48
+
49
+ # 打包
50
+ `tar czf #{file_full_path}.tar.gz #{file_full_path}`
51
+ # 如果是csv文件,需删除
52
+ File.delete(file_full_path) if file_full_path.split(".").last == 'csv'
53
+
54
+ send_file "#{file_full_path}.tar.gz"
55
+ end
56
+
31
57
  end
32
58
  end
@@ -1,16 +1,80 @@
1
1
  <!DOCTYPE html>
2
2
  <html>
3
- <head>
4
- <title>Web sandbox console</title>
5
- <%= stylesheet_link_tag "web_sandbox_console/application", media: "all" %>
6
- <%= javascript_include_tag "web_sandbox_console/application" %>
7
- <%= csrf_meta_tags %>
8
- </head>
9
- <body>
10
- <div class="container">
11
- <%= yield %>
12
- </div>
13
- </body>
3
+ <head>
4
+ <title>Web sandbox console</title>
5
+ <%= stylesheet_link_tag "web_sandbox_console/application", media: "all" %>
6
+ <%= javascript_include_tag "web_sandbox_console/application" %>
7
+ <%= csrf_meta_tags %>
8
+ </head>
9
+ <body>
10
+ <div id="main-wrapper">
11
+ <header class="topbar">
12
+ <nav class="navbar top-navbar">
13
+ <div class="navbar-header">
14
+ <a class="navbar-brand" href="/web_sandbox_console">
15
+ <b>
16
+ <img src="/assets/web_sandbox_console/logo-icon.png" />
17
+ </b>
18
+ <span style="left-padding: 10px;">运维控制台</span>
19
+ </a>
20
+ </div>
21
+
22
+ <div class="navbar-collapse">
23
+ </div>
24
+ </nav>
25
+ </header>
26
+
27
+ <aside class="left-sidebar">
28
+ <div class="scroll-sidebar ps">
29
+ <nav class="sidebar-nav">
30
+ <ul id="sidebar-nav">
31
+ <li>
32
+ <%= link_to root_path, class: 'waves-effect waves-dark' do %>
33
+ <i class="fa fa-bookmark-o"></i>
34
+ <span class="hide-menu <%= action_name == 'index' ? 'menu-active' : '' %>">执行代码</span>
35
+ <% end %>
36
+ </li>
37
+
38
+ <li>
39
+ <%= link_to web_sandbox_console.view_file_path, class: 'waves-effect waves-dark' do %>
40
+ <i class="fa fa-bookmark-o"></i>
41
+ <span class="hide-menu <%= action_name == 'view_file' ? 'menu-active' : '' %>">查看文件</span>
42
+ <% end %>
43
+ </li>
44
+
45
+ <li>
46
+ <%= link_to web_sandbox_console.download_page_path, class: 'waves-effect waves-dark' do %>
47
+ <i class="fa fa-bookmark-o"></i>
48
+ <span class="hide-menu <%= action_name == 'download_page' ? 'menu-active' : '' %>">下载文件</span>
49
+ <% end %>
50
+ </li>
51
+
52
+ <li>
53
+ <%= link_to web_sandbox_console.auth_page_path, class: 'waves-effect waves-dark' do %>
54
+ <i class="fa fa-bookmark-o"></i>
55
+ <span class="hide-menu <%= action_name == 'auth_page' ? 'menu-active' : '' %>">升级授权</span>
56
+ <% end %>
57
+ </li>
58
+
59
+ <li>
60
+ <%= link_to "https://github.com/dongmy54/web_sandbox_console", target: "_blank", class: 'waves-effect waves-dark' do %>
61
+ <i class="fa fa-bookmark-o"></i>
62
+ <span class="hide-menu <%= action_name == 'auth_page' ? 'menu-active' : '' %>">说明文档</span>
63
+ <% end %>
64
+ </li>
65
+ </ul>
66
+ </nav>
67
+
68
+ </div>
69
+ </aside>
70
+
71
+ <div class="page-wrapper" style="min-height: 800px;">
72
+ <div class="container-fluid">
73
+ <%= yield %>
74
+ </div>
75
+ </div>
76
+ </div>
77
+ </body>
14
78
  </html>
15
79
 
16
80
  <script type="text/javascript">
@@ -1,12 +1,10 @@
1
- # 授权
1
+ <h3>升级授权</h3>
2
+
2
3
  <p>
3
- <%= link_to '控制台', web_sandbox_console.root_path %>
4
- <%= link_to '获取令牌', web_sandbox_console.fetch_token_path(:format => "js"), remote: true %>
4
+ <%= link_to '获取令牌', fetch_token_path,format: 'json', remote: true %>
5
5
  <span> PS: 注意获取令牌一天最多20次,有效期仅5分钟</span>
6
6
  </p>
7
7
 
8
-
9
- <h2>授权申请</h1>
10
8
  <div class="">
11
9
  <%= form_tag web_sandbox_console.auth_path, method: :post do %>
12
10
  <table>
@@ -2,6 +2,15 @@ $(".output-content").empty();
2
2
  <% line_num_show = @total_line_num ? "原始文件总行数:#{@total_line_num}" : "" %>
3
3
 
4
4
  $(".output-content").append("<p>============ <%= Time.current %> 查找结果如下 <%= line_num_show %> ===============</p>");
5
+
6
+ <% if @touch_grep_protect %>
7
+ $(".output-content").append("<p>PS: 由于过滤执行时间太长,已触发过滤保护,返回内容为最后1000行</p>");
8
+ <% end %>
9
+
10
+ <% if @content_is_trimed %>
11
+ $(".output-content").append("<p>PS: 当前返回内容太多,仅展示满足条件的1000行</p>");
12
+ <% end %>
13
+
5
14
  <% @lines.each do |result| %>
6
15
  $(".output-content").append('<pre><%= escape_javascript result %></pre>');
7
16
  <% end %>
@@ -0,0 +1,22 @@
1
+ <h3>下载文件</h3>
2
+ <div class="">
3
+ <%= form_tag web_sandbox_console.download_path, method: :get do %>
4
+ <table>
5
+ <tr>
6
+ <td>文件名</td>
7
+ <td>
8
+ <%= text_field_tag :file_name, '', style: "width: 400px;height: 35px;font-size: 15px;" %>
9
+ </td>
10
+ <td><%= submit_tag '提交', data: { disable_with: "已发送,处理中..." }, class: 'view-file-button' %></td>
11
+ </table>
12
+ <% end %>
13
+ </div>
14
+
15
+ <h4>说明:</h4>
16
+ <div>
17
+ <pre>
18
+ 1. 只可下载log目录下文件
19
+ 2. 下载时文件名需要带上后缀如:production.log
20
+ 3. csv文件下载后会被删除,不能重复下载
21
+ </pre>
22
+ </div>
@@ -1,8 +1,4 @@
1
- 控制台 index
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 %>
1
+ <h3>执行代码</h3>
6
2
 
7
3
  <div class="console margin-b-20">
8
4
  <%= form_tag(web_sandbox_console.eval_code_path, remote: true, class: 'form') do %>
@@ -1,5 +1,4 @@
1
- # view_file
2
- <%= link_to '控制台', web_sandbox_console.root_path %>
1
+ <h3>查看文件</h3>
3
2
  <div class="view_file">
4
3
  <%= form_tag do_view_file_path, method: :post, remote: true do %>
5
4
  <table>
@@ -28,7 +27,7 @@
28
27
  </tr>
29
28
  </table>
30
29
  <% end %>
31
- <span class="tip">注意:过滤时将忽略筛选行数</span>
30
+ <span class="tip">注意:过滤时将忽略筛选行数;过滤内容中不能出现单引号</span>
32
31
  </div>
33
32
 
34
33
  <div class="output-content">
@@ -9,5 +9,9 @@ WebSandboxConsole::Engine.routes.draw do
9
9
  get :fetch_token, to: "authorization#fetch_token"
10
10
  get :auth_page, to: "authorization#auth_page"
11
11
  post :auth, to: "authorization#auth"
12
+
13
+ # 文件下载
14
+ get :download_page, to: "home#download_page"
15
+ get :download, to: "home#download"
12
16
  end
13
17
 
@@ -1,3 +1,8 @@
1
+ require 'yaml'
2
+
3
+ config_file_path = "#{Rails.root}/config/web_sandbox_console.yml"
4
+ config_hash = File.exists?(config_file_path) ? YAML.load_file(config_file_path).with_indifferent_access[:web_sandbox_console] : {}
5
+
1
6
  # web_sandbox_console 配置文件
2
7
  # 以下配置 都是可选的 缺少的情况下用默认值 或者 不生效
3
8
  WebSandboxConsole.setup do |config|
@@ -7,8 +12,10 @@ WebSandboxConsole.setup do |config|
7
12
  # 配置 ip 白名单
8
13
  # config.ip_whitelist = %w(192.168.23.12 192.145.2.0/24)
9
14
 
10
- # # 配置 基本认证
11
- # config.http_basic_auth = {name: 'dmy', password: '123456'}
15
+ # 配置 基本认证 在 config/web_sandbox_console.yml中配置
16
+ # PS: 1. 即使config/web_sandbox_console.yml文件不存在,也不会有任何使用上的影响,效果相当于没有开启
17
+ # 2. 下面这行不用注释掉,只要不配置yml文件就行
18
+ config.http_basic_auth = config_hash[:http_basic_auth]
12
19
 
13
20
  # # 配置 黑名单 类方法
14
21
  # config.class_method_blacklist = {File: %i(delete read write),Dir: %i(new delete mkdir)}
@@ -20,13 +27,13 @@ WebSandboxConsole.setup do |config|
20
27
  # 默认都是项目路径下的
21
28
  # config.view_file_blacklist = %w(config/secrets.yml vendor/)
22
29
 
23
- # 配置 文件权限,是否仅能查看log文件,默开启
30
+ # 配置 文件权限,是否仅能查看log文件,默认开启
24
31
  #config.only_view_log_file = false
25
32
 
26
33
  # 通过非对称加密方式 升级权限,授权通过后,可获得执行数据权限(PS: 数据操作不再回滚)
27
- # config.public_key = "-----BEGIN PUBLIC KEY-----\nMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDMbJOE1vQT1jFpaH1GPYzdRJN/\nLh8VePmzXs5BYOLHB0xIjArL1NlXMbCJ+AS2rv3/oHIOdHhEuZw0tmm9DhG100R8\nRjBpsEKCDI88jl9qRkFmD3CVk8XQXv6c2IkRZCYSTvgDkmnKAlORksfw+p0cR2AQ\nlAtAsNsNviKYBzXKfQIDAQAB\n-----END PUBLIC KEY-----\n"
34
+ # PS:配置同 http_basic_auth
35
+ config.public_key = config_hash[:public_key]
28
36
 
29
37
  # # 配置 日志路径 默认路径位于项目下
30
38
  # config.console_log_path = "log/web_sandbox_console.log"
31
39
  end
32
-
@@ -0,0 +1,5 @@
1
+ web_sandbox_console:
2
+ http_basic_auth:
3
+ name: dmy
4
+ password: 123456
5
+ public_key: "-----BEGIN PUBLIC KEY-----\nMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDMbJOE1vQT1jFpaH1GPYzdRJN/\nLh8VePmzXs5BYOLHB0xIjArL1NlXMbCJ+AS2rv3/oHIOdHhEuZw0tmm9DhG100R8\nRjBpsEKCDI88jl9qRkFmD3CVk8XQXv6c2IkRZCYSTvgDkmnKAlORksfw+p0cR2AQ\nlAtAsNsNviKYBzXKfQIDAQAB\n-----END PUBLIC KEY-----\n"
@@ -5,4 +5,9 @@ class WebSandboxConsoleGenerator < Rails::Generators::Base
5
5
  # 源文件 目标位置
6
6
  copy_file "web_sandbox_console.rb", "config/initializers/web_sandbox_console.rb"
7
7
  end
8
+
9
+ # 生成配置yaml文件
10
+ def copy_config_file
11
+ copy_file "web_sandbox_console.yml", "config/web_sandbox_console.yml.example"
12
+ end
8
13
  end
@@ -34,7 +34,7 @@ module WebSandboxConsole
34
34
  # 内置 类方法 黑名单
35
35
  CLASS_METHOD_BUILT_IN_BLACKLIST = {
36
36
  Kernel: %i(system exec `),
37
- File: %i(chmod chown new open delete read write),
37
+ File: %i(chmod chown new delete read write open),
38
38
  Dir: %i(new delete mkdir)
39
39
  }
40
40
 
@@ -5,6 +5,8 @@ module WebSandboxConsole
5
5
  sanitize_constants
6
6
  sanitize_instance_methods
7
7
  sanitize_class_methods
8
+ sanitize_logger_new
9
+ sanitize_csv
8
10
  end
9
11
 
10
12
  # 净化 类方法
@@ -66,5 +68,42 @@ module WebSandboxConsole
66
68
  hash1
67
69
  end
68
70
 
71
+ # 发现代码 中有 Logger.new(Rails.root.join('log', 'hubar')) 写法, 会 触发 File.open方法
72
+ # 封装后避免调用 File.open(禁用)
73
+ def sanitize_logger_new
74
+ Logger.instance_eval do
75
+ def new(logdev, shift_age = 0, shift_size = 1048576)
76
+ instance = allocate
77
+ instance.send(:initialize, logdev.to_s, shift_age, shift_size)
78
+ instance
79
+ end
80
+ end
81
+ end
82
+
83
+ # 净化 csv
84
+ def sanitize_csv
85
+ require 'csv' unless defined? CSV
86
+
87
+ CSV.instance_eval do
88
+ # 重写方法 以写日志方式 写数据
89
+ def open(filename, mode="r", **options)
90
+ # 无论输入什么路径 都只会在log下创建文件
91
+ basename = File.basename(filename, ".*")
92
+ file_path = "#{Rails.root}/log/#{basename}.csv"
93
+ logger = Logger.new(file_path)
94
+ logger.formatter = proc {|severity, datetime, progname, msg| msg}
95
+
96
+ logger.instance_exec do
97
+ # 支持类型 csv 数据写入方式
98
+ def << (data_arr)
99
+ self.info data_arr.join(",") + "\n"
100
+ end
101
+ end
102
+
103
+ yield(logger)
104
+ end
105
+ end
106
+ end
107
+
69
108
  end
70
109
  end
@@ -46,10 +46,7 @@ module WebSandboxConsole
46
46
  WebSandboxConsole.init_safe_env
47
47
  result = nil
48
48
  begin
49
- ActiveRecord::Base.transaction(requires_new: true) do
50
- result = eval(#{self.code.inspect})
51
- raise ActiveRecord::Rollback unless #{self.pass_auth}
52
- end
49
+ #{self.pass_auth ? no_rollback_code : rollback_code}
53
50
  rescue Exception => e
54
51
  WebSandboxConsole.log_p(e, "#{self.uuid}")
55
52
  rescue SyntaxError => e
@@ -58,6 +55,23 @@ module WebSandboxConsole
58
55
  WebSandboxConsole.log_p(result, "#{self.uuid}")
59
56
  CODE
60
57
  end
58
+
59
+ # 回滚code
60
+ def rollback_code
61
+ <<-EOF
62
+ ActiveRecord::Base.transaction(requires_new: true) do
63
+ result = eval(#{self.code.inspect})
64
+ raise ActiveRecord::Rollback
65
+ end
66
+ EOF
67
+ end
68
+
69
+ # 不回滚code
70
+ def no_rollback_code
71
+ <<-EOF
72
+ result = eval(#{self.code.inspect})
73
+ EOF
74
+ end
61
75
 
62
76
  # 临时文件目录
63
77
  def tmp_file_dir
@@ -102,7 +116,7 @@ module WebSandboxConsole
102
116
 
103
117
  # 运行rails runner
104
118
  def exec_rails_runner
105
- @stdout = `bundle exec rails runner #{self.exe_tmp_file}`
119
+ @stdout = `RAILS_ENV=#{Rails.env} bundle exec rails runner #{self.exe_tmp_file}`
106
120
  end
107
121
 
108
122
  # 返回结果
@@ -1,3 +1,3 @@
1
1
  module WebSandboxConsole
2
- VERSION = '0.3.0'
2
+ VERSION = '0.4.0'
3
3
  end
@@ -1,11 +1,13 @@
1
1
  module WebSandboxConsole
2
2
  class ViewFile
3
- attr_accessor :file_or_dir # 文件或目录
4
- attr_accessor :start_line_num # 起始行数
5
- attr_accessor :end_line_num # 结束行数
6
- attr_accessor :sed_start_time # 开始时间
7
- attr_accessor :sed_end_time # 结束时间
8
- attr_accessor :grep_content # 过滤内容
3
+ attr_accessor :file_or_dir # 文件或目录
4
+ attr_accessor :start_line_num # 起始行数
5
+ attr_accessor :end_line_num # 结束行数
6
+ attr_accessor :sed_start_time # 开始时间
7
+ attr_accessor :sed_end_time # 结束时间
8
+ attr_accessor :grep_content # 过滤内容
9
+ attr_accessor :touch_grep_protect # 是否触发过滤保护
10
+ attr_accessor :content_is_trimed # 内容是否有裁剪
9
11
 
10
12
  def initialize(opts = {})
11
13
  @file_or_dir = opts[:file_or_dir]
@@ -14,6 +16,8 @@ module WebSandboxConsole
14
16
  @sed_start_time = parse_time(opts[:sed_start_time])
15
17
  @sed_end_time = parse_time(opts[:sed_end_time])
16
18
  @grep_content = opts[:grep_content]
19
+ @touch_grep_protect = false
20
+ @content_is_trimed = false
17
21
  end
18
22
 
19
23
  def view
@@ -31,6 +35,7 @@ module WebSandboxConsole
31
35
  # 检查参数
32
36
  def check_param
33
37
  raise ViewFileError, '文件或目录参数不能为空' if file_or_dir.blank?
38
+ raise ViewFileError, "过滤内容中不能出现单引号" if grep_content.to_s.include?("'")
34
39
  end
35
40
 
36
41
  # 转换成项目路径
@@ -119,8 +124,15 @@ module WebSandboxConsole
119
124
  else
120
125
  special_line_content
121
126
  end
122
-
123
- {lines: add_line_num(lines), total_line_num: cal_file_total_line_num}
127
+ # 修剪行数
128
+ lines = content_trim(lines)
129
+
130
+ {
131
+ lines: add_line_num(lines),
132
+ total_line_num: cal_file_total_line_num,
133
+ touch_grep_protect: @touch_grep_protect,
134
+ content_is_trimed: @content_is_trimed
135
+ }
124
136
  end
125
137
  end
126
138
 
@@ -129,33 +141,71 @@ module WebSandboxConsole
129
141
  `wc -l < #{file_or_dir_path}`.to_i
130
142
  end
131
143
 
132
- # 最后 xx 行内容
144
+ # 最后 xx
133
145
  def tail_any_line(num)
134
- (`tail -n #{num} #{file_or_dir_path}`).split(/[\r,\r\n]/)
146
+ tail_any_line_content(num).split("\n")
147
+ end
148
+
149
+ # 最后多少行内容
150
+ def tail_any_line_content(num)
151
+ `tail -n #{num} #{file_or_dir_path}`
135
152
  end
136
153
 
137
154
  # 按指定行返回
138
155
  def special_line_content
139
156
  File.readlines(file_or_dir_path)[(start_line_num - 1)..(end_line_num - 1)]
140
157
  end
158
+
159
+ # 过滤超时保护
160
+ def grep_timeout_protect
161
+ begin
162
+ Timeout::timeout(8) {yield}
163
+ rescue Timeout::Error => e
164
+ # 触发过滤保护
165
+ @touch_grep_protect = true
166
+ tail_any_line_content(1000)
167
+ end
168
+ end
141
169
 
142
170
  # 过滤文件
143
171
  def grep_file_content
144
172
  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}`
173
+ grep_timeout_protect {`sed -n '/#{@sed_start_time}/,/#{@sed_end_time}/p' #{file_or_dir_path} | fgrep '#{@grep_content}'`}
146
174
  elsif @sed_start_time
147
- `sed -n '/#{@sed_start_time}/,/#{@sed_end_time}/p' #{file_or_dir_path}`
175
+ grep_timeout_protect {`sed -n '/#{@sed_start_time}/,/#{@sed_end_time}/p' #{file_or_dir_path}`}
148
176
  else
149
- `grep #{@grep_content} #{file_or_dir_path}`
177
+ grep_timeout_protect {`fgrep '#{@grep_content}' #{file_or_dir_path}`}
178
+ end
179
+ content.split("\n")
180
+ end
181
+
182
+ # 修剪过滤内容
183
+ def content_trim(lines)
184
+ if lines.length > 1000
185
+ @content_is_trimed = true
186
+ # 内容太多时 只返回前1000行
187
+ lines.first(1000)
188
+ else
189
+ lines
150
190
  end
151
-
152
- content.split(/[\r,\r\n]/)
153
191
  end
154
192
 
155
193
  # 添加行号
156
194
  def add_line_num(lines)
157
195
  start_num = is_big_file? ? 1 : start_line_num
158
- lines.each_with_index.map{|line, index| "#{index + start_num}: #{line}"}
196
+ lines.each_with_index.map do |line, index|
197
+ line = "#{index + start_num}: #{line}"
198
+ hightlight_grep_content(line)
199
+ end
200
+ end
201
+
202
+ # 高亮内容
203
+ def hightlight_grep_content(content)
204
+ if @grep_content.present?
205
+ content.gsub(@grep_content.to_s, "<span style='color: #E35520;'> #{@grep_content}</span>").html_safe
206
+ else
207
+ content
208
+ end
159
209
  end
160
210
 
161
211
  private
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.3.0
4
+ version: 0.4.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-06-25 00:00:00.000000000 Z
11
+ date: 2020-07-09 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -50,9 +50,11 @@ files:
50
50
  - README.md
51
51
  - Rakefile
52
52
  - app/assets/config/web_sandbox_console_manifest.js
53
+ - app/assets/images/web_sandbox_console/logo-icon.png
53
54
  - app/assets/javascripts/web_sandbox_console/application.js
54
55
  - app/assets/javascripts/web_sandbox_console/home.js
55
56
  - app/assets/stylesheets/web_sandbox_console/application.css
57
+ - app/assets/stylesheets/web_sandbox_console/common.css
56
58
  - app/assets/stylesheets/web_sandbox_console/home.css
57
59
  - app/controllers/web_sandbox_console/application_controller.rb
58
60
  - app/controllers/web_sandbox_console/authorization_controller.rb
@@ -65,12 +67,14 @@ files:
65
67
  - app/views/web_sandbox_console/authorization/auth_page.html.erb
66
68
  - app/views/web_sandbox_console/authorization/fetch_token.js.erb
67
69
  - app/views/web_sandbox_console/home/do_view_file.js.erb
70
+ - app/views/web_sandbox_console/home/download_page.html.erb
68
71
  - app/views/web_sandbox_console/home/eval_code.js.erb
69
72
  - app/views/web_sandbox_console/home/index.html.erb
70
73
  - app/views/web_sandbox_console/home/view_file.html.erb
71
74
  - config/routes.rb
72
75
  - lib/generators/web_sandbox_console/USAGE
73
76
  - lib/generators/web_sandbox_console/templates/web_sandbox_console.rb
77
+ - lib/generators/web_sandbox_console/templates/web_sandbox_console.yml
74
78
  - lib/generators/web_sandbox_console/web_sandbox_console_generator.rb
75
79
  - lib/tasks/web_sandbox_console_tasks.rake
76
80
  - lib/web_sandbox_console.rb