censor_bear 0.1.12 → 0.1.17
Sign up to get free protection for your applications and to get access to all the features.
- 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 %>
|