censor_bear 0.1.12 → 0.1.17
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/README.md +19 -1
- data/app/assets/stylesheets/censor_bear/home.css +4 -0
- data/app/controllers/censor_bear/home_controller.rb +31 -0
- data/app/controllers/censor_bear/logs_controller.rb +18 -0
- data/app/controllers/censor_bear/mod_logs_controller.rb +24 -22
- data/app/controllers/censor_bear/stop_words_controller.rb +11 -2
- data/app/helpers/censor_bear/home_helper.rb +4 -0
- data/app/models/censor_bear/log.rb +18 -0
- data/app/models/censor_bear/mod_log.rb +57 -16
- data/app/models/censor_bear/stop_word.rb +2 -2
- data/app/views/censor_bear/home/debug.turbo_stream.haml +8 -0
- data/app/views/censor_bear/home/debugger.html.haml +9 -0
- data/app/views/censor_bear/home/index.html.erb +2 -0
- data/app/views/censor_bear/logs/_log.html.haml +8 -2
- data/app/views/censor_bear/logs/index.html.haml +6 -1
- data/app/views/censor_bear/logs/show.html.haml +29 -0
- data/app/views/censor_bear/mod_logs/_mod_log.html.haml +41 -37
- data/app/views/censor_bear/mod_logs/index.html.haml +12 -2
- data/app/views/censor_bear/stop_words/index.html.haml +2 -1
- data/app/views/layouts/censor_bear/application.html.haml +3 -2
- data/config/locales/censor_bear.yml +7 -2
- data/config/routes.rb +12 -5
- data/db/migrate/20211129110218_create_censor_bear_mod_logs.rb +0 -1
- data/db/migrate/20211209093050_add_response_to_censor_bear_logs.rb +5 -0
- data/db/migrate/20211209113710_add_aasm_state_to_censor_bear_mod_logs.rb +5 -0
- data/lib/censor_bear/censor.rb +27 -11
- data/lib/censor_bear/configuration.rb +2 -0
- data/lib/censor_bear/version.rb +1 -1
- data/lib/censor_bear.rb +5 -0
- metadata +25 -3
- data/app/views/censor_bear/logs/show.html.erb +0 -34
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: da82f8fafa1a258c9009099fc66621805e19ac0750662f92153eb3a1a8ba5b01
|
4
|
+
data.tar.gz: 4a51f1c4260ba69397dfc583093fb59417ba0f9b95b458243d2b28ec58087454
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 0fed9c06844c55e4fca2c9d997d8f7af7a1bb04c33c0cc73f7fe1d1cbf4c1f0bce029a239f058a0eda4bd8bd11c9b670ad4019d8f3d75e9916d00f8d66a6ddf2
|
7
|
+
data.tar.gz: 11a70e93051a636a39240dd09327ef38778c82fc22af53b325e28e1635036de93f9ab85ab4b66d6cc8e78d1c04ae6b74ad31b7fa31ee5a958a75b9ac09dcccb7
|
data/README.md
CHANGED
@@ -50,10 +50,12 @@ rails censor_bear:install:migrations
|
|
50
50
|
|
51
51
|
## 🦆 Methods
|
52
52
|
```ruby
|
53
|
-
|
53
|
+
censor_remove # 删除内容
|
54
|
+
censor_cancel # 撤销删除
|
54
55
|
censor_approve # 通过审核
|
55
56
|
censor_show_path # 详情路由
|
56
57
|
censor_ban_user # 封禁用户
|
58
|
+
|
57
59
|
```
|
58
60
|
|
59
61
|
## Exceptions
|
@@ -63,6 +65,22 @@ censor_ban_user # 封禁用户
|
|
63
65
|
class NotPassedException < StandardError; end
|
64
66
|
```
|
65
67
|
|
68
|
+
## 初始化配置
|
69
|
+
详细配置项可在`lib/censor_bear/configuration.rb`中查看。
|
70
|
+
|
71
|
+
```ruby
|
72
|
+
CensorBear.configure do |config|
|
73
|
+
config.user_class = 'User'
|
74
|
+
config.user_name_method = 'nickname'
|
75
|
+
|
76
|
+
config.aliyun_green_access_key_id = 'access key id'
|
77
|
+
config.aliyun_green_access_key_secret = 'access key secret'
|
78
|
+
config.aliyun_green_enable_internal = false
|
79
|
+
|
80
|
+
config.main_app_root_path_method = 'root_path'
|
81
|
+
end
|
82
|
+
```
|
83
|
+
|
66
84
|
## 注意事项
|
67
85
|
1. 为 MOD 时,表示需要人工审核,需要被审查对象(UGC内容)支持`is_approved`字段(默认为`true`),此时内容自己能看到,别人暂时看不到,需审核通过后可见。
|
68
86
|
2. 被审查对象可以通过软删实现,隐藏(目的是防止错判,撤销),删除的内容对自己和别人都是不可见的。
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require_dependency 'censor_bear/application_controller'
|
2
|
+
|
3
|
+
module CensorBear
|
4
|
+
class HomeController < ApplicationController
|
5
|
+
def index; end
|
6
|
+
|
7
|
+
def debugger; end
|
8
|
+
|
9
|
+
def debug
|
10
|
+
content = params[:content]
|
11
|
+
type = params[:type]
|
12
|
+
|
13
|
+
case params[:stage]
|
14
|
+
when 'aliyun_check'
|
15
|
+
@r = AliyunGreen::Text.scan(content)
|
16
|
+
when 'check_text'
|
17
|
+
@r = CensorBear.check_text(content, type.to_sym)
|
18
|
+
when 'check_search'
|
19
|
+
# TODO: 搜索前置检查
|
20
|
+
@r = {}
|
21
|
+
when 'check_image'
|
22
|
+
# TODO: 检查图片
|
23
|
+
@r = {}
|
24
|
+
when 'qq_regex'
|
25
|
+
@r = content.match(Censor::QQ_REG)
|
26
|
+
when 'wx_regex'
|
27
|
+
@r = content.match(Censor::WX_REG)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -52,9 +52,27 @@ module CensorBear
|
|
52
52
|
end
|
53
53
|
end
|
54
54
|
|
55
|
+
def remove_data
|
56
|
+
builder = log_scope
|
57
|
+
case params[:type]
|
58
|
+
when '3days'
|
59
|
+
builder = builder.where("created_at >= ?", (Time.now - 3.days).beginning_of_day)
|
60
|
+
when '7days'
|
61
|
+
builder = builder.where("created_at >= ?", (Time.now - 7.days).beginning_of_day)
|
62
|
+
when '30days'
|
63
|
+
builder = builder.where("created_at >= ?", (Time.now - 30.days).beginning_of_day)
|
64
|
+
when 'all'
|
65
|
+
builder
|
66
|
+
end
|
67
|
+
builder.delete_all
|
68
|
+
|
69
|
+
redirect_to logs_url, notice: "Mod log was successfully destroyed."
|
70
|
+
end
|
71
|
+
|
55
72
|
def load_logs
|
56
73
|
builder ||= log_scope
|
57
74
|
builder = builder.where("original_content ilike ?", "%#{params[:q]}%") if params[:q].present?
|
75
|
+
builder = builder.where(stage: params[:stage]) if params[:stage].present?
|
58
76
|
builder = builder.order(id: :desc)
|
59
77
|
|
60
78
|
@pagy, @logs = pagy(builder)
|
@@ -1,28 +1,23 @@
|
|
1
|
-
require_dependency
|
1
|
+
require_dependency 'censor_bear/application_controller'
|
2
2
|
|
3
3
|
module CensorBear
|
4
4
|
class ModLogsController < ApplicationController
|
5
|
-
before_action :set_mod_log, only: %i[show edit update destroy
|
5
|
+
before_action :set_mod_log, only: %i[show edit update destroy suspend ban pass undo reject]
|
6
6
|
|
7
|
-
# GET /mod_logs
|
8
7
|
def index
|
9
8
|
load_mod_logs
|
10
9
|
end
|
11
10
|
|
12
|
-
# GET /mod_logs/1
|
13
11
|
def show
|
14
12
|
end
|
15
13
|
|
16
|
-
# GET /mod_logs/new
|
17
14
|
def new
|
18
15
|
@mod_log = ModLog.new
|
19
16
|
end
|
20
17
|
|
21
|
-
# GET /mod_logs/1/edit
|
22
18
|
def edit
|
23
19
|
end
|
24
20
|
|
25
|
-
# POST /mod_logs
|
26
21
|
def create
|
27
22
|
@mod_log = ModLog.new(mod_log_params)
|
28
23
|
|
@@ -33,7 +28,6 @@ module CensorBear
|
|
33
28
|
end
|
34
29
|
end
|
35
30
|
|
36
|
-
# PATCH/PUT /mod_logs/1
|
37
31
|
def update
|
38
32
|
if @mod_log.update(mod_log_params)
|
39
33
|
redirect_to @mod_log, notice: 'Mod log was successfully updated.'
|
@@ -42,7 +36,6 @@ module CensorBear
|
|
42
36
|
end
|
43
37
|
end
|
44
38
|
|
45
|
-
# DELETE /mod_logs/1
|
46
39
|
def destroy
|
47
40
|
@mod_log.destroy
|
48
41
|
|
@@ -52,24 +45,32 @@ module CensorBear
|
|
52
45
|
end
|
53
46
|
end
|
54
47
|
|
55
|
-
|
56
|
-
def ignore
|
48
|
+
def suspend
|
57
49
|
@mod_log.suspend!
|
58
50
|
|
59
51
|
redirect_to mod_logs_url, notice: 'Mod log was successfully ignored.'
|
60
52
|
end
|
61
53
|
|
62
|
-
def
|
63
|
-
|
64
|
-
@mod_log.delete(params[:reason])
|
54
|
+
def pass
|
55
|
+
@mod_log.approve
|
65
56
|
|
66
|
-
redirect_to mod_logs_url, notice: 'Mod log was successfully
|
57
|
+
redirect_to mod_logs_url, notice: 'Mod log was successfully ignored.'
|
67
58
|
end
|
68
59
|
|
69
|
-
def
|
70
|
-
@mod_log.
|
60
|
+
def undo
|
61
|
+
if @mod_log.passed?
|
62
|
+
@mod_log.undo_approve
|
63
|
+
elsif @mod_log.rejected?
|
64
|
+
@mod_log.undo_remove
|
65
|
+
end
|
66
|
+
|
67
|
+
redirect_to mod_logs_url, notice: 'Mod log was successfully ignored.'
|
68
|
+
end
|
69
|
+
|
70
|
+
def reject
|
71
|
+
@mod_log.remove(params[:reason])
|
71
72
|
|
72
|
-
redirect_to mod_logs_url, notice: 'Mod log was successfully
|
73
|
+
redirect_to mod_logs_url, notice: 'Mod log was successfully ignored.'
|
73
74
|
end
|
74
75
|
|
75
76
|
def ban
|
@@ -80,8 +81,11 @@ module CensorBear
|
|
80
81
|
|
81
82
|
def load_mod_logs
|
82
83
|
builder ||= mod_log_scope.includes(:record)
|
83
|
-
builder = builder.where(
|
84
|
-
|
84
|
+
builder = builder.where('content ilike ?', "%#{params[:q]}%") if params[:q].present?
|
85
|
+
if params[:filter] != 'all'
|
86
|
+
builder = builder.pending
|
87
|
+
builder = builder.unscope(where: :aasm_state).where(aasm_state: params[:state]) if params[:state].present?
|
88
|
+
end
|
85
89
|
builder = builder.order(id: :desc)
|
86
90
|
|
87
91
|
@pagy, @mod_logs = pagy(builder)
|
@@ -89,7 +93,6 @@ module CensorBear
|
|
89
93
|
|
90
94
|
private
|
91
95
|
|
92
|
-
# Use callbacks to share common setup or constraints between actions.
|
93
96
|
def set_mod_log
|
94
97
|
@mod_log = ModLog.find(params[:id] || params[:mod_log_id])
|
95
98
|
end
|
@@ -98,7 +101,6 @@ module CensorBear
|
|
98
101
|
ModLog
|
99
102
|
end
|
100
103
|
|
101
|
-
# Only allow a list of trusted parameters through.
|
102
104
|
def mod_log_params
|
103
105
|
params.require(:mod_log).permit(:record_id, :record_type, :mod_words, :user_id, :reason)
|
104
106
|
end
|
@@ -1,4 +1,4 @@
|
|
1
|
-
require_dependency
|
1
|
+
require_dependency 'censor_bear/application_controller'
|
2
2
|
|
3
3
|
module CensorBear
|
4
4
|
class StopWordsController < ApplicationController
|
@@ -46,7 +46,16 @@ module CensorBear
|
|
46
46
|
|
47
47
|
def load_stop_words
|
48
48
|
builder ||= stop_word_scope
|
49
|
-
builder = builder.where(
|
49
|
+
builder = builder.where('key ilike ?', "%#{params[:q]}%") if params[:q].present?
|
50
|
+
case params[:filter]
|
51
|
+
when 'size_one'
|
52
|
+
builder = builder.where('length(key) = 1')
|
53
|
+
when 'size_two'
|
54
|
+
builder = builder.where('length(key) = 2')
|
55
|
+
when 'size_three'
|
56
|
+
builder = builder.where('length(key) = 3')
|
57
|
+
end
|
58
|
+
builder = builder.where('key ilike ?', "%#{params[:q]}%")
|
50
59
|
builder = builder.order(id: :desc)
|
51
60
|
|
52
61
|
@pagy, @stop_words = pagy(builder)
|
@@ -1,5 +1,23 @@
|
|
1
1
|
module CensorBear
|
2
2
|
class Log < ApplicationRecord
|
3
3
|
belongs_to :user, class_name: CensorBear.config.user_class, optional: true
|
4
|
+
|
5
|
+
def suggestion
|
6
|
+
return nil if response.blank?
|
7
|
+
|
8
|
+
response['data']&.first['results']&.first['suggestion']
|
9
|
+
end
|
10
|
+
|
11
|
+
def rate
|
12
|
+
return nil if response.blank?
|
13
|
+
|
14
|
+
response['data']&.first['results']&.first['rate']
|
15
|
+
end
|
16
|
+
|
17
|
+
def label
|
18
|
+
return nil if response.blank?
|
19
|
+
|
20
|
+
response['data']&.first['results']&.first['label']
|
21
|
+
end
|
4
22
|
end
|
5
23
|
end
|
@@ -1,22 +1,42 @@
|
|
1
1
|
module CensorBear
|
2
2
|
class ModLog < ApplicationRecord
|
3
|
-
|
3
|
+
include AASM
|
4
|
+
|
5
|
+
REASONS = %w(无 广告/SPAM 恶意灌水 违规内容 文不对题 重复发布 其它).freeze
|
4
6
|
belongs_to :record, polymorphic: true, required: false
|
5
7
|
|
8
|
+
validates :record_id, uniqueness: { scope: [:record_type] }
|
9
|
+
|
10
|
+
def images
|
11
|
+
return [] if record.blank?
|
12
|
+
|
13
|
+
raise NoMethodError, 'undefined censor_images for record' unless record.respond_to?(:censor_images)
|
14
|
+
|
15
|
+
record.censor_images
|
16
|
+
end
|
17
|
+
|
6
18
|
def record_path
|
7
19
|
return '#' if record.blank?
|
8
20
|
|
9
21
|
record.censor_show_path
|
10
22
|
end
|
11
23
|
|
12
|
-
def
|
24
|
+
def remove(reason)
|
13
25
|
return if record.blank?
|
14
26
|
return if record.discarded?
|
15
27
|
# ugc内容需要开软删,便于撤销&查看统计,可以定时删除统计并删除软删内容,类似回收站机制
|
16
|
-
raise NoMethodError
|
28
|
+
raise NoMethodError, 'undefined censor_remove for record' unless record.respond_to?(:censor_remove)
|
17
29
|
|
18
|
-
ret = record.
|
19
|
-
|
30
|
+
ret = record.censor_remove
|
31
|
+
reject!(reason) if ret
|
32
|
+
end
|
33
|
+
|
34
|
+
def undo_remove
|
35
|
+
return if record.blank?
|
36
|
+
raise NoMethodError unless record.respond_to?(:censor_undo_remove)
|
37
|
+
|
38
|
+
ret = record.censor_undo_remove
|
39
|
+
undo! if ret
|
20
40
|
end
|
21
41
|
|
22
42
|
def approve
|
@@ -24,7 +44,15 @@ module CensorBear
|
|
24
44
|
raise NoMethodError unless record.respond_to?(:censor_approve)
|
25
45
|
|
26
46
|
ret = record.censor_approve
|
27
|
-
|
47
|
+
pass! if ret
|
48
|
+
end
|
49
|
+
|
50
|
+
def undo_approve
|
51
|
+
return if record.blank?
|
52
|
+
raise NoMethodError unless record.respond_to?(:censor_undo_approve)
|
53
|
+
|
54
|
+
ret = record.censor_undo_approve
|
55
|
+
undo! if ret
|
28
56
|
end
|
29
57
|
|
30
58
|
def ban_user
|
@@ -32,23 +60,36 @@ module CensorBear
|
|
32
60
|
raise NoMethodError unless record.respond_to?(:censor_ban_user)
|
33
61
|
|
34
62
|
ret = record.censor_ban_user
|
35
|
-
|
63
|
+
reject!('已被封禁该用户') if ret
|
36
64
|
end
|
37
65
|
|
38
|
-
|
39
|
-
|
40
|
-
|
66
|
+
aasm do
|
67
|
+
state :pending, initial: true
|
68
|
+
state :rejected, :passed, :suspended
|
69
|
+
|
70
|
+
event :reject do
|
71
|
+
transitions from: %i[pending suspended], to: :rejected, after: Proc.new {|*args| set_reason(*args) }
|
72
|
+
end
|
73
|
+
|
74
|
+
event :pass do
|
75
|
+
transitions from: %i[pending suspended], to: :passed
|
76
|
+
end
|
77
|
+
|
78
|
+
event :suspend do
|
79
|
+
transitions from: :pending, to: :suspended, after: :unset_reason
|
80
|
+
end
|
41
81
|
|
42
|
-
|
43
|
-
|
82
|
+
event :undo do
|
83
|
+
transitions from: %i[passed rejected], to: :pending, after: :unset_reason
|
84
|
+
end
|
44
85
|
end
|
45
86
|
|
46
|
-
def
|
47
|
-
update_columns(
|
87
|
+
def unset_reason
|
88
|
+
update_columns(reason: nil)
|
48
89
|
end
|
49
90
|
|
50
|
-
def
|
51
|
-
update_columns(
|
91
|
+
def set_reason(reason)
|
92
|
+
update_columns(reason: reason)
|
52
93
|
end
|
53
94
|
end
|
54
95
|
end
|
@@ -1,7 +1,7 @@
|
|
1
1
|
module CensorBear
|
2
2
|
class StopWord < ApplicationRecord
|
3
|
-
FIELDS = %w(ugc username nickname signature dialog)
|
4
|
-
ACTIONS_MAP = %w(ignore mod banned replace)
|
3
|
+
FIELDS = %w(ugc username nickname signature dialog).freeze
|
4
|
+
ACTIONS_MAP = %w(ignore mod banned replace).freeze
|
5
5
|
belongs_to :user, class_name: CensorBear.config.user_class
|
6
6
|
end
|
7
7
|
end
|
@@ -0,0 +1,8 @@
|
|
1
|
+
= turbo_stream.update(:result) do
|
2
|
+
%h3{class: "text-lg"} 检测结果
|
3
|
+
%div{class: "bg-yellow-100 text-yellow-700 p-4 rounded-lg"}
|
4
|
+
%pre{class: "whitespace-pre-wrap"}
|
5
|
+
- if @r.is_a?(MatchData)
|
6
|
+
= @r.pretty_inspect
|
7
|
+
- else
|
8
|
+
= JSON.pretty_generate(@r.is_a?(CensorBear::Result) ? JSON.parse(@r.to_json) : @r)
|
@@ -0,0 +1,9 @@
|
|
1
|
+
%div{class: "flex flex-col space-y-2 rounded-lg border p-4"}
|
2
|
+
%h2{class: "text-xl font-medium text-center"} 在线调试
|
3
|
+
%div{class: "flex justify-center"}
|
4
|
+
= form_with(url: debug_path, method: :post, class: "flex flex-col items-center w-full space-y-4") do |f|
|
5
|
+
= f.select :stage, options_for_select([%w[check_text check_text], %w[aliyun_check aliyun_check], %w[check_search check_search], %w[qq_regex qq_regex], %w[wx_regex wx_regex]], params[:stage]), {}, class: "border rounded-md m py-0.5 px-1"
|
6
|
+
= f.select :type, options_for_select(CensorBear::StopWord::FIELDS.map{|f| [f.upcase, f]}, params[:type]), {}, class: "border rounded-md m py-0.5 px-1"
|
7
|
+
= f.text_area :content, value: params[:content], placeholder: "输入正文", rows: 6, class: "bg-gray-100 rounded-lg p-4 w-full"
|
8
|
+
= f.submit "检测", class: "rounded-lg px-10 py-1 bg-black text-white cursor-pointer"
|
9
|
+
#result
|
@@ -9,6 +9,12 @@
|
|
9
9
|
%span{class: "text-sm bg-pink-100 text-pink-600 py-1 px-2 rounded-lg"}=t("censor_log.stage.#{log.stage}")
|
10
10
|
- unless log.action.blank?
|
11
11
|
%span{class: "text-sm bg-yellow-100 text-yellow-600 py-1 px-2 rounded-lg"}=t("censor_log.action.#{log.action.downcase}")
|
12
|
+
- unless log.suggestion.blank?
|
13
|
+
%span{class: "text-sm bg-green-100 text-green-600 py-1 px-2 rounded-lg"}=log.suggestion.upcase
|
14
|
+
- unless log.rate.blank?
|
15
|
+
%span{class: "text-sm bg-green-100 text-green-600 py-1 px-2 rounded-lg"}=log.rate
|
16
|
+
- unless log.label.blank?
|
17
|
+
%span{class: "text-sm bg-green-100 text-green-600 py-1 px-2 rounded-lg"}=log.label.upcase
|
12
18
|
- unless log.user_id.blank?
|
13
19
|
%span{class: "text-sm bg-gray-100 text-gray-600 py-1 px-2 rounded-lg font-bold"}="##{log.user_id}"
|
14
20
|
- unless log.ip.blank?
|
@@ -19,7 +25,7 @@
|
|
19
25
|
%span{class: "text-sm bg-gray-100 text-gray-600 py-1 px-2 rounded-lg"}= log.created_at
|
20
26
|
%div{class: "flex space-x-2 items-center"}
|
21
27
|
%span
|
22
|
-
= link_to '详情', log
|
28
|
+
= link_to '详情', log, data: { turbo: false }
|
23
29
|
%span
|
24
30
|
= button_to '删除', log, method: :delete, data: { confirm: 'Are you sure?' }, class: "text-red-600 rounded-md px-2 py-0.5"
|
25
31
|
%div{class: "flex justify-between items-center"}
|
@@ -32,5 +38,5 @@
|
|
32
38
|
%div{class: "text-yellow-500 font-meidum text-xl"}
|
33
39
|
- log.mod_words.each do |word|
|
34
40
|
%span [
|
35
|
-
%span= link_to word, stop_words_path(q: word), data: { turbo: false }
|
41
|
+
%span= link_to word, stop_words_path(q: word), data: { turbo: false }
|
36
42
|
%span ]
|
@@ -4,10 +4,15 @@
|
|
4
4
|
= form_with(url: logs_path, method: :get, class: "flex items-center") do |f|
|
5
5
|
%div{class: "space-x-0.5"}
|
6
6
|
= f.text_field :q, value: params[:q], placeholder: "关键词", class: "border rounded-md m py-0.5 px-1"
|
7
|
+
= f.select :stage, options_for_select([%w[aliyun_check aliyun_check], %w[check_search check_search], %w[qq_regex qq_regex], %w[wx_regex wx_regex], %w[local_check local_check]], params[:stage]), {include_blank: "-- 按Stage筛选 --"}, class: "border rounded-md m py-0.5 px-1"
|
7
8
|
= f.submit "检索", class: "rounded-md px-2 py-1 text-sm bg-black text-white cursor-pointer"
|
8
9
|
= link_to "重置", logs_path, class: "text-sm"
|
9
10
|
|
10
|
-
%div
|
11
|
+
%div{class: "flex space-x-2 items-center"}
|
12
|
+
%div{class: "text-sm text-gray-600"}="共 #{@pagy.count} 条"
|
13
|
+
= form_with(url: remove_data_logs_path, method: :delete, class: "flex items-center space-x-2") do |f|
|
14
|
+
= f.select :type, options_for_select([%w[最近3天 3days], %w[最近7天 7days], %w[最近30天 30days], %w[全部 all]], params[:type]), {include_blank: "-- 选择删除范围 --"}, class: "border text-red-600 border-red-600 rounded-md m py-0.5 px-1"
|
15
|
+
= f.submit "删除日志", class: "rounded-md px-2 py-1 text-xs bg-red-600 text-white cursor-pointer"
|
11
16
|
%div
|
12
17
|
- if @logs.blank?
|
13
18
|
%div{class: "flex justify-center text-gray-500 p-8 border rounded-md"} 空空如也
|
@@ -0,0 +1,29 @@
|
|
1
|
+
%div{class: "flex flex-col space-y-2"}
|
2
|
+
%div
|
3
|
+
%div{class: "font-bold"} 源内容:
|
4
|
+
%div= @log.original_content
|
5
|
+
%div
|
6
|
+
%div{class: "font-bold"} 过滤后内容:
|
7
|
+
%div= @log.filtered_content
|
8
|
+
%div
|
9
|
+
%div{class: "font-bold"} 动作:
|
10
|
+
%div= @log.action
|
11
|
+
%div
|
12
|
+
%div{class: "font-bold"} 场景:
|
13
|
+
%div= @log.scenario
|
14
|
+
%div
|
15
|
+
%div{class: "font-bold"} 检测阶段:
|
16
|
+
%div= @log.stage
|
17
|
+
%div
|
18
|
+
%div{class: "font-bold"} 命中敏感词:
|
19
|
+
%div= @log.mod_words
|
20
|
+
%div
|
21
|
+
%div{class: "font-bold"} 云检测返回标签:
|
22
|
+
%div= @log.labels
|
23
|
+
%div
|
24
|
+
%div{class: "font-bold"} 云检测返回标签:
|
25
|
+
%div= @log.labels
|
26
|
+
%div
|
27
|
+
%div{class: "font-bold"} 云检测返回:
|
28
|
+
%div{class: "p-1 bg-gray-100 rounded-lg"}
|
29
|
+
%pre= JSON.pretty_generate(@log.response)
|
@@ -2,44 +2,48 @@
|
|
2
2
|
%div{class: "flex flex-col"}
|
3
3
|
%div{class: "border rounded-md mb-2 p-2 flex flex-col space-y-1"}
|
4
4
|
%div{class: "flex justify-between"}
|
5
|
-
%div{class: ""}
|
5
|
+
%div{class: "text-sm"}
|
6
6
|
= link_to mod_log.record_path, data: { turbo: false }, target: "_blank" do
|
7
|
-
%span{class: "
|
8
|
-
-
|
9
|
-
|
10
|
-
%span{class: "text-sm bg-red-100 text-red-600 py-1 px-2 rounded-lg"}=t("censor_log.label.#{label}")
|
11
|
-
- unless mod_log.mod_words.blank?
|
12
|
-
- mod_log.mod_words.each do |word|
|
13
|
-
%span{class: "text-sm bg-yellow-100 text-yellow-600 py-1 px-2 rounded-lg"}="##{word}"
|
14
|
-
%span{class: "text-sm bg-gray-100 text-gray-600 py-1 px-2 rounded-lg"}= mod_log.created_at
|
15
|
-
%div{class: "flex space-x-2 items-center"}
|
7
|
+
%span{class: "bg-indigo-100 text-indigo-600 py-1 px-2 rounded-lg"}="#{mod_log.record_type.upcase}##{mod_log.record_id}"
|
8
|
+
%span{class: "bg-yellow-100 text-yellow-600 rounded-md p-1"}=t("censor_log.aasm_state.#{mod_log.aasm_state}")
|
9
|
+
%div{class: "flex space-x-2 items-center text-sm"}
|
16
10
|
- if mod_log.reason
|
17
11
|
%span{class: "bg-red-100 text-red-600 p-1 rounded-lg"}
|
18
12
|
= mod_log.reason
|
19
|
-
%span{class: "bg-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
13
|
+
%span{class: "bg-gray-100 text-gray-600 py-1 px-2 rounded-lg"}= mod_log.created_at
|
14
|
+
- if current_user.admin?
|
15
|
+
= button_to '删除', mod_log, method: :delete, data: { confirm: 'Are you sure?'}, class: "bg-red-600 text-white rounded-md px-2 py-0.5 cursor-pointer"
|
16
|
+
%div{class: "flex justify-between items-center py-2 w-full"}
|
17
|
+
%div{class: "flex flex-col rounded-lg bg-gray-50 p-3 text-sm space-y-4 w-full"}
|
18
|
+
-unless mod_log.record
|
19
|
+
%h3{class: "text-2xl text-red-600"} 对应条目已被永久删除,当前审核请求已无效,请删除
|
20
|
+
- else
|
21
|
+
%div
|
22
|
+
%span{class: "border border-pink-600 text-pink-600 rounded-lg text-xs p-0.5"}=mod_log.record.is_private ? "隐私" : "公开"
|
23
|
+
%span{class: "border border-blue-600 text-blue-600 rounded-lg text-xs p-0.5"}=mod_log.record.is_approved ? "审核通过" : "审核中..."
|
24
|
+
- unless mod_log.labels.blank?
|
25
|
+
- mod_log.labels.each do |label|
|
26
|
+
%span{class: "border border-yellow-600 text-yellow-600 rounded-lg text-xs p-0.5"}=t("censor_log.label.#{label}")
|
27
|
+
- unless mod_log.mod_words.blank?
|
28
|
+
- mod_log.mod_words.each do |word|
|
29
|
+
%span{class: "border border-red-600 text-red-600 rounded-lg text-xs p-0.5"}="##{word}"
|
30
|
+
- if mod_log.record.discarded?
|
31
|
+
%div{class: "text-gray-900 leading-relaxed"}
|
32
|
+
%del= mod_log.record.content
|
33
|
+
- else
|
34
|
+
%div{class: "text-gray-900 leading-relaxed"}= mod_log.record.content
|
35
|
+
%div
|
36
|
+
- mod_log.images.each do |img|
|
37
|
+
= image_tag img[:src], class: "w-16 rounded-md"
|
38
|
+
%div{class: "flex space-x-2 items-center justify-end"}
|
39
|
+
- if mod_log.may_suspend?
|
40
|
+
= link_to "忽略", mod_log_suspend_path(mod_log), class: "text-yellow-600 cursor-pointer"
|
41
|
+
- if mod_log.may_pass?
|
42
|
+
= link_to "通过", mod_log_pass_path(mod_log), class: "text-green-600 cursor-pointer"
|
43
|
+
- if mod_log.may_undo?
|
44
|
+
= link_to "撤销", mod_log_undo_path(mod_log), class: "text-red-600 cursor-pointer"
|
45
|
+
- if mod_log.may_reject?
|
46
|
+
= form_with(url: mod_log_reject_path(mod_log), method: :get, class: "space-x-1 flex items-center") do |f|
|
47
|
+
= f.submit "拒绝", class: "text-red-600 bg-transparent cursor-pointer"
|
48
|
+
= f.text_field :reason, value: mod_log.reason, placeholder: "不通过原因", class: "border rounded-md m py-0.5 px-1 text-sm"
|
49
|
+
|
@@ -6,8 +6,18 @@
|
|
6
6
|
= f.text_field :q, value: params[:q], placeholder: "关键词", class: "border rounded-md m py-0.5 px-1"
|
7
7
|
= f.submit "检索", class: "rounded-md px-2 py-1 text-sm bg-black text-white cursor-pointer"
|
8
8
|
= link_to "重置", mod_logs_path, class: "text-sm"
|
9
|
-
|
10
|
-
|
9
|
+
%div{class: "flex items-center space-x-2"}
|
10
|
+
%div{class: "text-sm text-gray-600"}="共 #{@pagy.count} 条"
|
11
|
+
%div{class: "bg-gray-200 rounded-full px-3 py-1 text-xs #{params[:filter] == "all" ? 'font-bold' : ''}"}
|
12
|
+
= link_to "全部", request.params.merge(filter: "all", state: "")
|
13
|
+
%div{class: "bg-gray-200 rounded-full px-3 py-1 text-xs #{params[:state].blank? && params[:filter].blank? ? 'font-bold' : ''}"}
|
14
|
+
= link_to "待处理", request.params.merge(state: "", filter: "")
|
15
|
+
%div{class: "bg-gray-200 rounded-full px-3 py-1 text-xs #{params[:state] == 'rejected' ? 'font-bold' : ''}"}
|
16
|
+
= link_to "被驳回", request.params.merge(state: "rejected", filter: "")
|
17
|
+
%div{class: "bg-gray-200 rounded-full px-3 py-1 text-xs #{params[:state] == 'passed' ? 'font-bold' : ''}"}
|
18
|
+
= link_to "通过", request.params.merge(state: "passed", filter: "")
|
19
|
+
%div{class: "bg-gray-200 rounded-full px-3 py-1 text-xs #{params[:state] == 'suspended' ? 'font-bold' : ''}"}
|
20
|
+
= link_to "暂缓处理", request.params.merge(state: "suspended", filter: "")
|
11
21
|
%div
|
12
22
|
- if @mod_logs.blank?
|
13
23
|
%div{class: "flex justify-center text-gray-500 p-8 border rounded-md"} 空空如也
|
@@ -4,10 +4,11 @@
|
|
4
4
|
= form_with(url: stop_words_path, method: :get, class: "flex items-center") do |f|
|
5
5
|
%div{class: "space-x-0.5"}
|
6
6
|
= f.text_field :q, value: params[:q], placeholder: "关键词", class: "border rounded-md m py-0.5 px-1"
|
7
|
+
= f.select :filter, options_for_select([%w[长度为1 size_one], %w[长度为2 size_two], %w[长度为3 size_three]], params[:filter]), {include_blank: "-- 按敏感词长度筛选 --"}, class: "border rounded-md m py-0.5 px-1"
|
7
8
|
= f.submit "检索", class: "rounded-md px-2 py-1 text-sm bg-black text-white cursor-pointer"
|
8
9
|
= link_to "重置", stop_words_path, class: "text-sm"
|
9
|
-
|
10
10
|
%div
|
11
|
+
%span{class: "text-sm text-gray-600"}="共 #{@pagy.count} 条"
|
11
12
|
= link_to '批量导入', "#", class: "text-sm"
|
12
13
|
= link_to '新增', new_stop_word_path, class: "rounded-md px-2 py-1 text-sm bg-black text-white cursor-pointer"
|
13
14
|
%div
|
@@ -10,9 +10,10 @@
|
|
10
10
|
%body{class: "flex flex-col space-y-6 p-4"}
|
11
11
|
%ul{class: "flex justify-end items-center space-x-2 text-blue-600"}
|
12
12
|
%li= link_to "审查(#{CensorBear::ModLog.pending.count})", mod_logs_path
|
13
|
-
%li= link_to "敏感词", stop_words_path
|
14
13
|
%li= link_to "日志", logs_path
|
15
|
-
%li= link_to "
|
14
|
+
%li= link_to "敏感词", stop_words_path
|
15
|
+
%li= link_to "调试", debugger_path
|
16
|
+
%li= link_to "控制台", main_app.send(CensorBear.config.main_app_root_path_method.to_sym)
|
16
17
|
%li= current_user.email
|
17
18
|
%div
|
18
19
|
= yield
|
@@ -1,4 +1,4 @@
|
|
1
|
-
|
1
|
+
"zh-CN":
|
2
2
|
censor_log:
|
3
3
|
action:
|
4
4
|
mod: 审核
|
@@ -8,7 +8,12 @@ en:
|
|
8
8
|
block: 禁用
|
9
9
|
pass: 忽略
|
10
10
|
review: 审核
|
11
|
+
aasm_state:
|
12
|
+
pending: 待审
|
13
|
+
handled: 已处理
|
14
|
+
suspended: 暂缓处理
|
11
15
|
stage:
|
16
|
+
check_search: 搜索检查
|
12
17
|
aliyun_check: 阿里云检查
|
13
18
|
tencent_check: 腾讯云检查
|
14
19
|
local_check: 本地检查
|
@@ -26,4 +31,4 @@ en:
|
|
26
31
|
contraband: 违禁
|
27
32
|
meaningless: 无意义
|
28
33
|
harmful: 不良场景
|
29
|
-
customized: 自定义
|
34
|
+
customized: 自定义
|
data/config/routes.rb
CHANGED
@@ -1,12 +1,19 @@
|
|
1
1
|
CensorBear::Engine.routes.draw do
|
2
2
|
resources :mod_logs do
|
3
|
-
get :
|
4
|
-
get :
|
5
|
-
get :
|
3
|
+
get :suspend
|
4
|
+
get :reject
|
5
|
+
get :pass
|
6
|
+
get :undo
|
6
7
|
get :ban
|
7
8
|
end
|
8
|
-
resources :logs
|
9
|
+
resources :logs do
|
10
|
+
delete :remove_data, on: :collection
|
11
|
+
end
|
9
12
|
resources :stop_words
|
10
13
|
|
11
|
-
|
14
|
+
get '/welcome', to: 'home#index'
|
15
|
+
get '/debugger', to: 'home#debugger'
|
16
|
+
post '/debug', to: 'home#debug'
|
17
|
+
|
18
|
+
root to: 'mod_logs#index'
|
12
19
|
end
|
data/lib/censor_bear/censor.rb
CHANGED
@@ -115,32 +115,46 @@ module CensorBear
|
|
115
115
|
rate = r['rate']
|
116
116
|
@mod_words = concat_words(r['details'])
|
117
117
|
@labels = concat_labels(r['details'])
|
118
|
-
if action == 'block' && rate >= 70 && %w[politics terrorism
|
118
|
+
# if action == 'block' && rate >= 70 && %w[politics terrorism].include?(r['label'])
|
119
|
+
# @is_mod = true
|
120
|
+
# CensorBear.info(
|
121
|
+
# d['content'], @type, 'mod', 'aliyun_check',
|
122
|
+
# filtered_content: d['filteredContent'],
|
123
|
+
# mod_words: @mod_words,
|
124
|
+
# labels: @labels,
|
125
|
+
# ip: @ip, user_id: @user_id,
|
126
|
+
# response: response
|
127
|
+
# )
|
128
|
+
# # raise NotPassedException
|
129
|
+
# elsif action == 'block' && rate >= 90
|
130
|
+
if action == 'block'
|
131
|
+
@is_mod = true
|
119
132
|
CensorBear.info(
|
120
|
-
d['content'], @type,
|
133
|
+
d['content'], @type, 'mod', 'aliyun_check',
|
121
134
|
filtered_content: d['filteredContent'],
|
122
135
|
mod_words: @mod_words,
|
123
136
|
labels: @labels,
|
124
|
-
ip: @ip, user_id: @user_id
|
137
|
+
ip: @ip, user_id: @user_id,
|
138
|
+
response: response
|
125
139
|
)
|
126
|
-
|
127
|
-
elsif action == 'block'
|
140
|
+
elsif action == 'review'
|
128
141
|
@is_mod = true
|
129
142
|
CensorBear.info(
|
130
|
-
d['content'], @type,
|
143
|
+
d['content'], @type, 'mod', 'aliyun_check',
|
131
144
|
filtered_content: d['filteredContent'],
|
132
145
|
mod_words: @mod_words,
|
133
146
|
labels: @labels,
|
134
|
-
ip: @ip, user_id: @user_id
|
147
|
+
ip: @ip, user_id: @user_id,
|
148
|
+
response: response
|
135
149
|
)
|
136
|
-
|
137
|
-
@is_mod = true
|
150
|
+
else
|
138
151
|
CensorBear.info(
|
139
|
-
d['content'], @type,
|
152
|
+
d['content'], @type, 'ignore', 'aliyun_check',
|
140
153
|
filtered_content: d['filteredContent'],
|
141
154
|
mod_words: @mod_words,
|
142
155
|
labels: @labels,
|
143
|
-
ip: @ip, user_id: @user_id
|
156
|
+
ip: @ip, user_id: @user_id,
|
157
|
+
response: response
|
144
158
|
)
|
145
159
|
end
|
146
160
|
end
|
@@ -149,6 +163,7 @@ module CensorBear
|
|
149
163
|
|
150
164
|
def concat_labels(data)
|
151
165
|
return [] if data.blank?
|
166
|
+
|
152
167
|
labels = []
|
153
168
|
data.map do |d|
|
154
169
|
next if d['label'].blank?
|
@@ -160,6 +175,7 @@ module CensorBear
|
|
160
175
|
|
161
176
|
def concat_words(data)
|
162
177
|
return [] if data.blank?
|
178
|
+
|
163
179
|
words = []
|
164
180
|
data.each do |d|
|
165
181
|
next if d['contexts'].blank?
|
data/lib/censor_bear/version.rb
CHANGED
data/lib/censor_bear.rb
CHANGED
@@ -3,6 +3,7 @@ require 'censor_bear/engine'
|
|
3
3
|
require 'censor_bear/censor'
|
4
4
|
require 'censor_bear/configuration'
|
5
5
|
|
6
|
+
require 'aasm'
|
6
7
|
require 'hamlit'
|
7
8
|
require 'pagy'
|
8
9
|
require 'aliyun_green'
|
@@ -24,6 +25,8 @@ module CensorBear
|
|
24
25
|
@config.aliyun_green_access_key_id = ''
|
25
26
|
@config.aliyun_green_access_key_secret = ''
|
26
27
|
@config.aliyun_green_enable_internal = false
|
28
|
+
@config.main_app_root_path_method = 'root_path'
|
29
|
+
|
27
30
|
@config
|
28
31
|
end
|
29
32
|
|
@@ -45,6 +48,7 @@ module CensorBear
|
|
45
48
|
labels = options[:labels] || []
|
46
49
|
ip = options[:ip] || nil
|
47
50
|
user_id = options[:user_id] || nil
|
51
|
+
response = options[:response] || {}
|
48
52
|
|
49
53
|
CensorBear::Log.create(
|
50
54
|
original_content: content,
|
@@ -56,6 +60,7 @@ module CensorBear
|
|
56
60
|
labels: labels,
|
57
61
|
ip: ip,
|
58
62
|
user_id: user_id,
|
63
|
+
response: response
|
59
64
|
)
|
60
65
|
end
|
61
66
|
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: censor_bear
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.17
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- 42up
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2021-12-
|
11
|
+
date: 2021-12-10 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rails
|
@@ -30,6 +30,20 @@ dependencies:
|
|
30
30
|
- - ">="
|
31
31
|
- !ruby/object:Gem::Version
|
32
32
|
version: 6.0.4.1
|
33
|
+
- !ruby/object:Gem::Dependency
|
34
|
+
name: aasm
|
35
|
+
requirement: !ruby/object:Gem::Requirement
|
36
|
+
requirements:
|
37
|
+
- - ">="
|
38
|
+
- !ruby/object:Gem::Version
|
39
|
+
version: '0'
|
40
|
+
type: :runtime
|
41
|
+
prerelease: false
|
42
|
+
version_requirements: !ruby/object:Gem::Requirement
|
43
|
+
requirements:
|
44
|
+
- - ">="
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
version: '0'
|
33
47
|
- !ruby/object:Gem::Dependency
|
34
48
|
name: aliyun_green
|
35
49
|
requirement: !ruby/object:Gem::Requirement
|
@@ -112,16 +126,19 @@ files:
|
|
112
126
|
- Rakefile
|
113
127
|
- app/assets/config/censor_bear_manifest.js
|
114
128
|
- app/assets/stylesheets/censor_bear/application.css
|
129
|
+
- app/assets/stylesheets/censor_bear/home.css
|
115
130
|
- app/assets/stylesheets/censor_bear/logs.css
|
116
131
|
- app/assets/stylesheets/censor_bear/mod_logs.css
|
117
132
|
- app/assets/stylesheets/censor_bear/stop_words.css
|
118
133
|
- app/assets/stylesheets/censor_bear/tailwind.min.css
|
119
134
|
- app/assets/stylesheets/scaffold.css
|
120
135
|
- app/controllers/censor_bear/application_controller.rb
|
136
|
+
- app/controllers/censor_bear/home_controller.rb
|
121
137
|
- app/controllers/censor_bear/logs_controller.rb
|
122
138
|
- app/controllers/censor_bear/mod_logs_controller.rb
|
123
139
|
- app/controllers/censor_bear/stop_words_controller.rb
|
124
140
|
- app/helpers/censor_bear/application_helper.rb
|
141
|
+
- app/helpers/censor_bear/home_helper.rb
|
125
142
|
- app/helpers/censor_bear/logs_helper.rb
|
126
143
|
- app/helpers/censor_bear/mod_logs_helper.rb
|
127
144
|
- app/helpers/censor_bear/stop_words_helper.rb
|
@@ -131,13 +148,16 @@ files:
|
|
131
148
|
- app/models/censor_bear/log.rb
|
132
149
|
- app/models/censor_bear/mod_log.rb
|
133
150
|
- app/models/censor_bear/stop_word.rb
|
151
|
+
- app/views/censor_bear/home/debug.turbo_stream.haml
|
152
|
+
- app/views/censor_bear/home/debugger.html.haml
|
153
|
+
- app/views/censor_bear/home/index.html.erb
|
134
154
|
- app/views/censor_bear/logs/_form.html.erb
|
135
155
|
- app/views/censor_bear/logs/_log.html.haml
|
136
156
|
- app/views/censor_bear/logs/destroy.turbo_stream.haml
|
137
157
|
- app/views/censor_bear/logs/edit.html.erb
|
138
158
|
- app/views/censor_bear/logs/index.html.haml
|
139
159
|
- app/views/censor_bear/logs/new.html.erb
|
140
|
-
- app/views/censor_bear/logs/show.html.
|
160
|
+
- app/views/censor_bear/logs/show.html.haml
|
141
161
|
- app/views/censor_bear/mod_logs/_form.html.erb
|
142
162
|
- app/views/censor_bear/mod_logs/_mod_log.html.haml
|
143
163
|
- app/views/censor_bear/mod_logs/destroy.turbo_stream.haml
|
@@ -162,6 +182,8 @@ files:
|
|
162
182
|
- db/migrate/20211129110218_create_censor_bear_mod_logs.rb
|
163
183
|
- db/migrate/20211206092626_add_ip_to_censor_bear_logs.rb
|
164
184
|
- db/migrate/20211208041114_add_labels_to_censor_bear_logs.rb
|
185
|
+
- db/migrate/20211209093050_add_response_to_censor_bear_logs.rb
|
186
|
+
- db/migrate/20211209113710_add_aasm_state_to_censor_bear_mod_logs.rb
|
165
187
|
- lib/censor_bear.rb
|
166
188
|
- lib/censor_bear/censor.rb
|
167
189
|
- lib/censor_bear/configuration.rb
|
@@ -1,34 +0,0 @@
|
|
1
|
-
<p id="notice"><%= notice %></p>
|
2
|
-
|
3
|
-
<p>
|
4
|
-
<strong>Orginal content:</strong>
|
5
|
-
<%= @log.original_content %>
|
6
|
-
</p>
|
7
|
-
|
8
|
-
<p>
|
9
|
-
<strong>Action:</strong>
|
10
|
-
<%= @log.action %>
|
11
|
-
</p>
|
12
|
-
|
13
|
-
<p>
|
14
|
-
<strong>Scenario:</strong>
|
15
|
-
<%= @log.scenario %>
|
16
|
-
</p>
|
17
|
-
|
18
|
-
<p>
|
19
|
-
<strong>Stage:</strong>
|
20
|
-
<%= @log.stage %>
|
21
|
-
</p>
|
22
|
-
|
23
|
-
<p>
|
24
|
-
<strong>Filtered content:</strong>
|
25
|
-
<%= @log.filtered_content %>
|
26
|
-
</p>
|
27
|
-
|
28
|
-
<p>
|
29
|
-
<strong>Mod words:</strong>
|
30
|
-
<%= @log.mod_words %>
|
31
|
-
</p>
|
32
|
-
|
33
|
-
<%= link_to 'Edit', edit_log_path(@log) %> |
|
34
|
-
<%= link_to 'Back', logs_path %>
|