vanken 0.1.0 → 0.2.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 +4 -4
- data/CHANGELOG.md +15 -0
- data/README.md +26 -11
- data/data/coloring_rules.yml +67 -0
- data/docs/performance.md +10 -0
- data/docs/releases.md +2 -4
- data/docs/usage.md +51 -0
- data/lib/vanken/app/capture_controller.rb +66 -1
- data/lib/vanken/app/document.rb +102 -39
- data/lib/vanken/app/document_analysis.rb +75 -0
- data/lib/vanken/app/document_jobs.rb +52 -9
- data/lib/vanken/app/expert_info.rb +26 -0
- data/lib/vanken/app/io_graph.rb +47 -0
- data/lib/vanken/app/navigation.rb +77 -0
- data/lib/vanken/app/search_job.rb +62 -0
- data/lib/vanken/app/stats_job.rb +45 -0
- data/lib/vanken/capture/analyzer.rb +3 -1
- data/lib/vanken/capture/analyzer_process.rb +9 -6
- data/lib/vanken/capture/analyzer_worker.rb +2 -2
- data/lib/vanken/capture/filter_worker.rb +8 -2
- data/lib/vanken/capture/helper_main.rb +10 -3
- data/lib/vanken/capture/helper_options.rb +6 -2
- data/lib/vanken/capture/launcher.rb +1 -0
- data/lib/vanken/config/analysis_settings.rb +58 -0
- data/lib/vanken/config/columns.rb +138 -0
- data/lib/vanken/config/messages.rb +105 -0
- data/lib/vanken/config/preferences.rb +34 -8
- data/lib/vanken/config/profiles.rb +74 -0
- data/lib/vanken/config/sessions.rb +69 -0
- data/lib/vanken/config/yaml_file.rb +36 -0
- data/lib/vanken/core/coloring.rb +86 -0
- data/lib/vanken/core/frame_store.rb +2 -2
- data/lib/vanken/core/stores.rb +12 -0
- data/lib/vanken/gateway/detail_builder.rb +1 -1
- data/lib/vanken/gateway/display_capture_filter.rb +102 -0
- data/lib/vanken/gateway/dissector.rb +31 -6
- data/lib/vanken/gateway/exporter.rb +125 -0
- data/lib/vanken/gateway/file_writer.rb +26 -2
- data/lib/vanken/gateway/interfaces.rb +59 -0
- data/lib/vanken/gateway/resolver.rb +131 -0
- data/lib/vanken/gateway/statistics.rb +83 -0
- data/lib/vanken/gateway/stream.rb +159 -0
- data/lib/vanken/ui/actions.rb +54 -28
- data/lib/vanken/ui/analysis_dialogs.rb +371 -0
- data/lib/vanken/ui/application.rb +183 -9
- data/lib/vanken/ui/coloring_operations.rb +104 -0
- data/lib/vanken/ui/column_operations.rb +115 -0
- data/lib/vanken/ui/dialogs.rb +41 -26
- data/lib/vanken/ui/file_operations.rb +17 -12
- data/lib/vanken/ui/filter_operations.rb +43 -15
- data/lib/vanken/ui/main_view.rb +74 -29
- data/lib/vanken/ui/navigation_operations.rb +86 -0
- data/lib/vanken/ui/packet_source.rb +33 -1
- data/lib/vanken/ui/selection.rb +8 -2
- data/lib/vanken/ui/settings_operations.rb +233 -0
- data/lib/vanken/version.rb +1 -1
- data/sig/generated/vanken/app/capture_controller.rbs +16 -0
- data/sig/generated/vanken/app/document.rbs +23 -1
- data/sig/generated/vanken/app/document_analysis.rbs +21 -0
- data/sig/generated/vanken/app/document_jobs.rbs +6 -0
- data/sig/generated/vanken/app/expert_info.rbs +30 -0
- data/sig/generated/vanken/app/io_graph.rbs +45 -0
- data/sig/generated/vanken/app/navigation.rbs +31 -0
- data/sig/generated/vanken/app/search_job.rbs +21 -0
- data/sig/generated/vanken/app/stats_job.rbs +18 -0
- data/sig/generated/vanken/capture/helper_options.rbs +6 -0
- data/sig/generated/vanken/config/analysis_settings.rbs +31 -0
- data/sig/generated/vanken/config/columns.rbs +37 -0
- data/sig/generated/vanken/config/messages.rbs +14 -0
- data/sig/generated/vanken/config/preferences.rbs +1 -1
- data/sig/generated/vanken/config/profiles.rbs +29 -0
- data/sig/generated/vanken/config/sessions.rbs +17 -0
- data/sig/generated/vanken/config/yaml_file.rbs +11 -0
- data/sig/generated/vanken/core/coloring.rbs +52 -0
- data/sig/generated/vanken/core/stores.rbs +4 -0
- data/sig/generated/vanken/gateway/display_capture_filter.rbs +37 -0
- data/sig/generated/vanken/gateway/dissector.rbs +15 -2
- data/sig/generated/vanken/gateway/exporter.rbs +27 -0
- data/sig/generated/vanken/gateway/file_writer.rbs +3 -0
- data/sig/generated/vanken/gateway/interfaces.rbs +12 -0
- data/sig/generated/vanken/gateway/resolver.rbs +52 -0
- data/sig/generated/vanken/gateway/statistics.rbs +104 -0
- data/sig/generated/vanken/gateway/stream.rbs +75 -0
- data/sig/generated/vanken/ui/analysis_dialogs.rbs +71 -0
- data/sig/generated/vanken/ui/application.rbs +23 -1
- data/sig/generated/vanken/ui/coloring_operations.rbs +29 -0
- data/sig/generated/vanken/ui/column_operations.rbs +29 -0
- data/sig/generated/vanken/ui/dialogs.rbs +5 -3
- data/sig/generated/vanken/ui/file_operations.rbs +2 -0
- data/sig/generated/vanken/ui/filter_operations.rbs +3 -1
- data/sig/generated/vanken/ui/main_view.rbs +14 -2
- data/sig/generated/vanken/ui/navigation_operations.rbs +43 -0
- data/sig/generated/vanken/ui/packet_source.rbs +4 -0
- data/sig/generated/vanken/ui/settings_operations.rbs +41 -0
- metadata +51 -3
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../core/coloring"
|
|
4
|
+
|
|
5
|
+
module Vanken
|
|
6
|
+
module UI
|
|
7
|
+
module ColoringOperations
|
|
8
|
+
def coloring_rules = @coloring_rules ||= load_coloring_rules
|
|
9
|
+
def reload_coloring_rules
|
|
10
|
+
@coloring_rules = load_coloring_rules
|
|
11
|
+
@packet_source.reset
|
|
12
|
+
@window.request_frame
|
|
13
|
+
end
|
|
14
|
+
def replace_coloring_rules(payload)
|
|
15
|
+
rules = Core::Coloring::RuleSet.new(payload, catalog: document&.catalog)
|
|
16
|
+
rules.save(File.join(@preferences.directory, "coloring_rules.yml"))
|
|
17
|
+
@coloring_rules = rules
|
|
18
|
+
@packet_source.reset
|
|
19
|
+
@window.request_frame
|
|
20
|
+
rules
|
|
21
|
+
end
|
|
22
|
+
def coloring_enabled? = @preferences.get("packet_list.coloring")
|
|
23
|
+
def toggle_coloring
|
|
24
|
+
@preferences.set("packet_list.coloring", !coloring_enabled?)
|
|
25
|
+
@packet_source.reset
|
|
26
|
+
@window.request_frame
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def coloring_dialog
|
|
30
|
+
@coloring_draft ||= coloring_rules.to_h
|
|
31
|
+
rules = @coloring_draft.fetch("rules")
|
|
32
|
+
@coloring_index = (@coloring_index || 0).clamp(0, [rules.size - 1, 0].max)
|
|
33
|
+
content = Zaniah::Div.new.flex_col.gap(12)
|
|
34
|
+
unless rules.empty?
|
|
35
|
+
content.child(Zaniah::UI::Select.new(rules.each_with_index.map { |rule, index| ["#{index + 1}. #{rule.fetch('name')}", index] }, label: t("ルール"), value: @coloring_index)
|
|
36
|
+
.on_change { |value, *_| @coloring_index = value; coloring_dialog }.test_id("vk.coloring.rule"))
|
|
37
|
+
rule = rules[@coloring_index]
|
|
38
|
+
content.child(Zaniah::UI::TextField.new(rule.fetch("name"), label: t("名前")).on_change { |value, *_| rule["name"] = value }.test_id("vk.coloring.name"))
|
|
39
|
+
content.child(Zaniah::UI::TextField.new(rule.fetch("filter"), label: t("表示フィルタ")).on_change { |value, *_| rule["filter"] = value }.test_id("vk.coloring.filter"))
|
|
40
|
+
content.child(Zaniah::UI::Checkbox.new(t("有効"), value: rule.fetch("enabled")).on_change { |value, *_| rule["enabled"] = value })
|
|
41
|
+
%w[light dark].each do |theme|
|
|
42
|
+
content.child(Zaniah::Div.new.flex_row.gap(12).children(%w[fg bg].map do |key|
|
|
43
|
+
label = t("%{theme} %{part}", theme: t(theme == "light" ? "ライト" : "ダーク"), part: t(key == "fg" ? "文字" : "背景"))
|
|
44
|
+
Zaniah::UI::ColorPicker.new(rule.fetch(theme).fetch(key), label: label).on_change { |value, *_| rule.fetch(theme)[key] = value }.flex_1
|
|
45
|
+
end))
|
|
46
|
+
end
|
|
47
|
+
content.child(Zaniah::Div.new.flex_row.gap(8)
|
|
48
|
+
.child(Zaniah::UI::Button.new(t("上へ移動"), variant: :secondary).disabled(@coloring_index.zero?).on_click { move_coloring_rule(-1) })
|
|
49
|
+
.child(Zaniah::UI::Button.new(t("下へ移動"), variant: :secondary).disabled(@coloring_index == rules.size - 1).on_click { move_coloring_rule(1) })
|
|
50
|
+
.child(Zaniah::UI::Button.new(t("削除"), variant: :secondary).on_click { rules.delete_at(@coloring_index); coloring_dialog }))
|
|
51
|
+
end
|
|
52
|
+
content.child(Zaniah::Div.new.flex_row.gap(8)
|
|
53
|
+
.child(Zaniah::UI::Button.new(t("追加"), variant: :secondary).on_click do
|
|
54
|
+
rules << {"name" => t("新しいルール"), "filter" => "tcp", "enabled" => true, "light" => {"fg" => "#12272E", "bg" => "#E7E6FF"}, "dark" => {"fg" => "#E7E6FF", "bg" => "#24233A"}}
|
|
55
|
+
@coloring_index = rules.size - 1
|
|
56
|
+
coloring_dialog
|
|
57
|
+
end)
|
|
58
|
+
.child(Zaniah::UI::Button.new(t("インポート"), variant: :secondary).on_click { import_coloring_rules })
|
|
59
|
+
.child(Zaniah::UI::Button.new(t("エクスポート"), variant: :secondary).on_click { export_coloring_rules }))
|
|
60
|
+
content.child(Zaniah::Div.new.flex_row.gap(8)
|
|
61
|
+
.child(Zaniah::UI::Button.new(t("適用")).test_id("vk.coloring.apply").on_click do
|
|
62
|
+
replace_coloring_rules(@coloring_draft)
|
|
63
|
+
@coloring_draft = nil
|
|
64
|
+
dismiss_dialog
|
|
65
|
+
rescue StandardError => error
|
|
66
|
+
show_error(error)
|
|
67
|
+
end)
|
|
68
|
+
.child(Zaniah::UI::Button.new(t("キャンセル"), variant: :secondary).on_click { @coloring_draft = nil; dismiss_dialog }))
|
|
69
|
+
viewport = Zaniah::ScrollView.new.h([(@window.content_size.height * 0.9) - 100, 120].max).child(content)
|
|
70
|
+
show_dialog(:coloring, t("色付けルール"), viewport, reopen: -> { coloring_dialog })
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def import_coloring_rules
|
|
74
|
+
path_dialog(t("色付けルールの読み込み先")) do |path|
|
|
75
|
+
@coloring_draft = Core::Coloring::RuleSet.load(path, catalog: document&.catalog).to_h
|
|
76
|
+
@coloring_index = 0
|
|
77
|
+
coloring_dialog
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
def export_coloring_rules
|
|
81
|
+
path_dialog(t("色付けルールの保存先")) do |path|
|
|
82
|
+
Core::Coloring::RuleSet.new(@coloring_draft || coloring_rules.to_h, catalog: document&.catalog).save(path)
|
|
83
|
+
coloring_dialog
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
private
|
|
88
|
+
def load_coloring_rules
|
|
89
|
+
path = File.join(@preferences.directory, "coloring_rules.yml")
|
|
90
|
+
File.file?(path) ? Core::Coloring::RuleSet.load(path, catalog: document&.catalog) : Core::Coloring::RuleSet.defaults(catalog: document&.catalog)
|
|
91
|
+
rescue Vanken::ConfigError => error
|
|
92
|
+
show_error(error)
|
|
93
|
+
Core::Coloring::RuleSet.defaults(catalog: document&.catalog)
|
|
94
|
+
end
|
|
95
|
+
def move_coloring_rule(delta)
|
|
96
|
+
rules = @coloring_draft.fetch("rules")
|
|
97
|
+
target = @coloring_index + delta
|
|
98
|
+
rules[@coloring_index], rules[target] = rules[target], rules[@coloring_index]
|
|
99
|
+
@coloring_index = target
|
|
100
|
+
coloring_dialog
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
end
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../config/columns"
|
|
4
|
+
|
|
5
|
+
module Vanken
|
|
6
|
+
module UI
|
|
7
|
+
module ColumnOperations
|
|
8
|
+
def column_settings
|
|
9
|
+
if !@column_settings || @columns_directory != @preferences.directory
|
|
10
|
+
@columns_directory = @preferences.directory
|
|
11
|
+
@column_settings = Config::Columns.load(directory: @columns_directory, legacy: @preferences.get("layout.columns"))
|
|
12
|
+
end
|
|
13
|
+
@column_settings
|
|
14
|
+
rescue ConfigError => error
|
|
15
|
+
show_error(error)
|
|
16
|
+
@column_settings = Config::Columns.new
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def reload_columns
|
|
20
|
+
@column_settings = nil
|
|
21
|
+
column_settings
|
|
22
|
+
configure_document_columns
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def configure_document_columns
|
|
26
|
+
document.custom_columns = column_settings.custom if document
|
|
27
|
+
@packet_source.reset
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def persist_columns_layout(items)
|
|
31
|
+
column_settings.update_layout(items)
|
|
32
|
+
column_settings.save(@preferences.directory)
|
|
33
|
+
configure_document_columns
|
|
34
|
+
rescue StandardError => error
|
|
35
|
+
show_error(error)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def add_field_column(field, label: field, width: 120)
|
|
39
|
+
if document && !Core::DisplayFilter::FieldResolver.new(document.catalog).resolve(field).known
|
|
40
|
+
raise Vanken::ConfigError, "unknown field: #{field}"
|
|
41
|
+
end
|
|
42
|
+
key = column_settings.add(field, label: label, width: width)
|
|
43
|
+
apply_columns
|
|
44
|
+
key
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def remove_column(key)
|
|
48
|
+
column_settings.remove(key)
|
|
49
|
+
apply_columns
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def field_menu(node)
|
|
53
|
+
menu = selection_menu(node.filter)
|
|
54
|
+
return menu unless node.field
|
|
55
|
+
@app.actions.register(:context_apply_column, title: column_text("列として適用")) { add_field_column(node.field) }
|
|
56
|
+
added = Zaniah::Menu.build { item(:context_apply_column) }
|
|
57
|
+
Zaniah::Menu.new(menu.items + added.items)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def columns_dialog
|
|
61
|
+
rows = Zaniah::Div.new.flex_col.gap(6)
|
|
62
|
+
@table.columns.each_with_index do |column, index|
|
|
63
|
+
key = column[:key]
|
|
64
|
+
width = Zaniah::UI::NumberInput.new(column[:width], min: 40, max: 4096, step: 8, label: column_text("幅"))
|
|
65
|
+
.w(100).test_id("vk.columns.width.#{key}")
|
|
66
|
+
width.on_change do |*_|
|
|
67
|
+
amount = width.number
|
|
68
|
+
if amount && amount.finite? && amount.between?(40, 4096)
|
|
69
|
+
width.status(:none)
|
|
70
|
+
@table.resize_column(key, amount)
|
|
71
|
+
else
|
|
72
|
+
width.status(:error, message: column_text("幅は40から4096の範囲で指定してください"))
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
row = Zaniah::Div.new.flex_row.items_center.gap(6)
|
|
76
|
+
.child(Zaniah::UI::Checkbox.new(column[:label], value: column[:visible]).flex_1
|
|
77
|
+
.on_change { |value, *_| @table.column_visible(key, value) })
|
|
78
|
+
.child(width)
|
|
79
|
+
.child(Zaniah::UI::Button.new(column_text("上へ移動"), size: :sm).disabled(index.zero?).on_click { @table.move_column(key, index - 1); columns_dialog })
|
|
80
|
+
.child(Zaniah::UI::Button.new(column_text("下へ移動"), size: :sm).disabled(index == @table.columns.size - 1).on_click { @table.move_column(key, index + 1); columns_dialog })
|
|
81
|
+
row.child(Zaniah::UI::Button.new(column_text("削除"), size: :sm).on_click { remove_column(key); columns_dialog }) if key.to_s.start_with?("field:")
|
|
82
|
+
rows.child(row)
|
|
83
|
+
end
|
|
84
|
+
field = Zaniah::UI::TextField.new("", label: column_text("フィールド名")).test_id("vk.columns.field")
|
|
85
|
+
label = Zaniah::UI::TextField.new("", label: column_text("列名 (空欄: フィールド名)")).test_id("vk.columns.label")
|
|
86
|
+
content = Zaniah::Div.new.flex_col.gap(8)
|
|
87
|
+
.child(Zaniah::ScrollView.new.h(280).child(rows)).child(field).child(label)
|
|
88
|
+
.child(Zaniah::UI::Button.new(column_text("追加")).test_id("vk.columns.add").on_click do
|
|
89
|
+
name = field.value.strip
|
|
90
|
+
add_field_column(name, label: label.value.empty? ? name : label.value)
|
|
91
|
+
columns_dialog
|
|
92
|
+
rescue StandardError => error
|
|
93
|
+
show_error(error)
|
|
94
|
+
end)
|
|
95
|
+
show_dialog(:columns, column_text("表示する列"), content, reopen: -> { columns_dialog })
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
private
|
|
99
|
+
|
|
100
|
+
def column_text(text) = respond_to?(:t) ? t(text) : text
|
|
101
|
+
|
|
102
|
+
def apply_columns
|
|
103
|
+
column_settings.save(@preferences.directory)
|
|
104
|
+
configure_document_columns
|
|
105
|
+
selected = selected_number
|
|
106
|
+
rebuild_view
|
|
107
|
+
index = document&.displayed_index(selected)
|
|
108
|
+
if index
|
|
109
|
+
@table.select(index)
|
|
110
|
+
@table.scroll_to(index, align: :nearest)
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
end
|
data/lib/vanken/ui/dialogs.rb
CHANGED
|
@@ -5,18 +5,23 @@ module Vanken
|
|
|
5
5
|
module Dialogs
|
|
6
6
|
def dialog = @dialog
|
|
7
7
|
def dismiss_dialog
|
|
8
|
+
close_analysis if respond_to?(:close_analysis)
|
|
8
9
|
@dialog&.close
|
|
9
|
-
@dialog = @dialog_kind = @pending_destructive = nil
|
|
10
|
+
@dialog = @dialog_kind = @pending_destructive = @dialog_reopen = nil
|
|
10
11
|
@window.request_frame
|
|
11
12
|
end
|
|
12
|
-
def
|
|
13
|
+
def reopen_dialog
|
|
14
|
+
@dialog_reopen&.call
|
|
15
|
+
end
|
|
16
|
+
def show_dialog(kind, title, content, reopen: nil)
|
|
13
17
|
dismiss_dialog
|
|
14
18
|
@dialog_kind = kind
|
|
15
|
-
@
|
|
19
|
+
@dialog_reopen = reopen
|
|
20
|
+
@dialog = Zaniah::UI::Dialog.new(content, title: title, close_label: t("閉じる")).on_close { dismiss_dialog }.test_id("vk.#{kind}")
|
|
16
21
|
@window.request_frame
|
|
17
22
|
end
|
|
18
|
-
def path_dialog(title, &operation)
|
|
19
|
-
field = Zaniah::UI::TextField.new(
|
|
23
|
+
def path_dialog(title, value: "", &operation)
|
|
24
|
+
field = Zaniah::UI::TextField.new(value, label: t(title)).test_id("vk.path.input")
|
|
20
25
|
content = Zaniah::Div.new.flex_col.gap(12).child(field)
|
|
21
26
|
.child(Zaniah::Div.new.flex_row.gap(8)
|
|
22
27
|
.child(Zaniah::UI::Button.new("OK").on_click do
|
|
@@ -25,32 +30,42 @@ module Vanken
|
|
|
25
30
|
rescue StandardError => error
|
|
26
31
|
show_error(error)
|
|
27
32
|
end)
|
|
28
|
-
.child(Zaniah::UI::Button.new("キャンセル", variant: :secondary).on_click { dismiss_dialog }))
|
|
29
|
-
show_dialog(:path, title, content)
|
|
33
|
+
.child(Zaniah::UI::Button.new(t("キャンセル"), variant: :secondary).on_click { dismiss_dialog }))
|
|
34
|
+
show_dialog(:path, t(title), content, reopen: -> { path_dialog(title, value: field.value, &operation) })
|
|
30
35
|
end
|
|
31
|
-
def capture_options(interface: nil)
|
|
36
|
+
def capture_options(interface: nil, values: {})
|
|
32
37
|
fields = {}
|
|
33
38
|
infos = @interface_infos || []
|
|
34
39
|
choices = infos.map { |item| ["#{item.fetch(:name)} #{item[:description]}", item.fetch(:name)] }
|
|
35
40
|
selected = choices.any? { |choice| choice.last == interface } ? interface : choices.first&.last
|
|
36
|
-
selector = Zaniah::UI::Select.new(choices, label: "インタフェース", value: selected)
|
|
41
|
+
selector = Zaniah::UI::Select.new(choices, label: t("インタフェース"), value: selected)
|
|
37
42
|
selector.on_change { |value, *_| selected = value }
|
|
38
43
|
content = Zaniah::Div.new.flex_col.gap(10).child(selector)
|
|
39
|
-
[["filter", "キャプチャフィルタ (BPF)", ""], ["snaplen", "Snaplen", @preferences.get("capture.snaplen").to_s],
|
|
40
|
-
["buffer_size", "バッファサイズ (bytes)", @preferences.get("capture.buffer_size").to_s]].each do |key, label, value|
|
|
41
|
-
fields[key] = Zaniah::UI::TextField.new(value, label: label)
|
|
44
|
+
[["filter", t("キャプチャフィルタ (BPF)"), ""], ["snaplen", "Snaplen", @preferences.get("capture.snaplen").to_s],
|
|
45
|
+
["buffer_size", t("バッファサイズ (bytes)"), @preferences.get("capture.buffer_size").to_s]].each do |key, label, value|
|
|
46
|
+
fields[key] = Zaniah::UI::TextField.new(values.fetch(key, value), label: label)
|
|
42
47
|
content.child(fields[key])
|
|
43
48
|
end
|
|
44
|
-
|
|
49
|
+
advanced = Zaniah::Div.new.flex_col.gap(6)
|
|
50
|
+
{"stop_count" => t("停止件数 (0: 無制限)"), "stop_duration" => t("停止までの秒数 (0: 無制限)"), "stop_bytes" => t("停止サイズ (bytes、0: 無制限)"),
|
|
51
|
+
"ring_path" => t("リング保存先 (空欄: 保存しない)"), "ring_max_bytes" => t("リングファイル上限 (bytes)"), "ring_interval" => t("リング切替間隔 (秒)"), "ring_file_count" => t("リングファイル数")}.each do |key, label|
|
|
52
|
+
fields[key] = Zaniah::UI::TextField.new(values.fetch(key, @preferences.get("capture.#{key}").to_s), label: label).test_id("vk.capture.#{key}")
|
|
53
|
+
advanced.child(fields[key])
|
|
54
|
+
end
|
|
55
|
+
content.child(Zaniah::ScrollView.new.h(160).child(advanced))
|
|
56
|
+
promiscuous = Zaniah::UI::Checkbox.new(t("プロミスキャスモード"), value: values.fetch("promiscuous", @preferences.get("capture.promiscuous")))
|
|
45
57
|
content.child(promiscuous)
|
|
46
|
-
direction = @preferences.get("capture.direction")
|
|
47
|
-
content.child(Zaniah::UI::Select.new([["送受信", "inout"], ["受信", "in"], ["送信", "out"]], label: "方向", value: direction).on_change { |value, *_| direction = value })
|
|
48
|
-
status = Zaniah::UI::Label.new(infos.empty? ? (@interface_error || "インタフェースを取得中です。") : "GUI は通常ユーザーで動作し、取得ヘルパーに必要な権限を渡します。", wrap: :word, tone: :muted)
|
|
58
|
+
direction = values.fetch("direction", @preferences.get("capture.direction"))
|
|
59
|
+
content.child(Zaniah::UI::Select.new([[t("送受信"), "inout"], [t("受信"), "in"], [t("送信"), "out"]], label: t("方向"), value: direction).on_change { |value, *_| direction = value })
|
|
60
|
+
status = Zaniah::UI::Label.new(infos.empty? ? (@interface_error || t("インタフェースを取得中です。")) : t("GUI は通常ユーザーで動作し、取得ヘルパーに必要な権限を渡します。"), wrap: :word, tone: :muted)
|
|
49
61
|
content.child(status)
|
|
50
|
-
content.child(Zaniah::UI::Button.new("開始").test_id("vk.capture.start").disabled(infos.empty?).on_click do
|
|
62
|
+
content.child(Zaniah::UI::Button.new(t("開始")).test_id("vk.capture.start").disabled(infos.empty?).on_click do
|
|
51
63
|
options = {"interface" => selected, "snaplen" => Integer(fields.fetch("snaplen").value, 10),
|
|
52
64
|
"buffer_size" => Integer(fields.fetch("buffer_size").value, 10), "filter" => fields.fetch("filter").value,
|
|
53
65
|
"promiscuous" => promiscuous.value, "direction" => direction, "backend" => @preferences.get("capture.backend")}
|
|
66
|
+
%w[stop_count stop_bytes ring_max_bytes ring_file_count].each { |key| options[key] = Integer(fields.fetch(key).value, 10) }
|
|
67
|
+
%w[stop_duration ring_interval].each { |key| options[key] = Float(fields.fetch(key).value) }
|
|
68
|
+
options["ring_path"] = fields.fetch("ring_path").value
|
|
54
69
|
# Compilation and privileged helper startup both happen away from the UI thread.
|
|
55
70
|
@app.executor.background do
|
|
56
71
|
metadata = infos.find { |item| item[:name] == selected }
|
|
@@ -62,27 +77,27 @@ module Vanken
|
|
|
62
77
|
rescue ArgumentError => error
|
|
63
78
|
show_error(error)
|
|
64
79
|
end)
|
|
65
|
-
show_dialog(:capture_options, "キャプチャオプション", content)
|
|
80
|
+
show_dialog(:capture_options, t("キャプチャオプション"), content, reopen: -> { capture_options(interface: selected, values: fields.transform_values(&:value).merge("promiscuous" => promiscuous.value, "direction" => direction)) })
|
|
66
81
|
end
|
|
67
82
|
def preferences_dialog
|
|
68
|
-
theme = Zaniah::UI::Select.new(
|
|
83
|
+
theme = Zaniah::UI::Select.new({"システム設定" => "system", "ダーク" => "dark", "ライト" => "light", "ハイコントラスト" => "high_contrast"}.map { |label, value| [t(label), value] }, label: t("テーマ"), value: @preferences.get("appearance.theme"))
|
|
69
84
|
.on_change { |value, *_| change_theme(value) }
|
|
70
|
-
time = Zaniah::UI::Select.new(
|
|
85
|
+
time = Zaniah::UI::Select.new({"相対時刻" => "relative", "絶対時刻" => "absolute", "前パケットとの差" => "delta", "前表示パケットとの差" => "delta_displayed", "エポック時刻" => "epoch"}.map { |label, value| [t(label), value] }, label: t("時刻表示"), value: @preferences.get("packet_list.time_format"))
|
|
71
86
|
.on_change { |value, *_| time_format(value) }
|
|
72
87
|
content = Zaniah::Div.new.flex_col.gap(12).child(theme).child(time)
|
|
73
|
-
.child(Zaniah::Div.new.flex_row.gap(8).child(Zaniah::UI::Button.new("文字を小さく").on_click { zoom(-1) })
|
|
74
|
-
.child(Zaniah::UI::Button.new("文字を大きく").on_click { zoom(1) }))
|
|
75
|
-
show_dialog(:preferences, "設定", content)
|
|
88
|
+
.child(Zaniah::Div.new.flex_row.gap(8).child(Zaniah::UI::Button.new(t("文字を小さく")).on_click { zoom(-1) })
|
|
89
|
+
.child(Zaniah::UI::Button.new(t("文字を大きく")).on_click { zoom(1) }))
|
|
90
|
+
show_dialog(:preferences, t("設定"), content, reopen: -> { preferences_dialog })
|
|
76
91
|
end
|
|
77
92
|
def columns_dialog
|
|
78
93
|
content = Zaniah::Div.new.flex_col.gap(8)
|
|
79
94
|
@table.columns.each_with_index do |column, index|
|
|
80
95
|
content.child(Zaniah::Div.new.flex_row.gap(8)
|
|
81
96
|
.child(Zaniah::UI::Checkbox.new(column[:label], value: column[:visible]).on_change { |value, *_| @table.column_visible(column[:key], value) }.flex_1)
|
|
82
|
-
.child(Zaniah::UI::Button.new("
|
|
83
|
-
.child(Zaniah::UI::Button.new("
|
|
97
|
+
.child(Zaniah::UI::Button.new(t("上へ移動"), size: :sm).disabled(index.zero?).on_click { @table.move_column(column[:key], index - 1); columns_dialog })
|
|
98
|
+
.child(Zaniah::UI::Button.new(t("下へ移動"), size: :sm).disabled(index == @table.columns.size - 1).on_click { @table.move_column(column[:key], index + 1); columns_dialog }))
|
|
84
99
|
end
|
|
85
|
-
show_dialog(:columns, "表示する列", content)
|
|
100
|
+
show_dialog(:columns, t("表示する列"), content, reopen: -> { columns_dialog })
|
|
86
101
|
end
|
|
87
102
|
end
|
|
88
103
|
end
|
|
@@ -5,11 +5,13 @@ module Vanken
|
|
|
5
5
|
module FileOperations
|
|
6
6
|
CAPTURE_FILTERS = [{label: "Capture files", patterns: %w[*.pcapng *.pcap]}].freeze
|
|
7
7
|
|
|
8
|
+
def capture_filters = CAPTURE_FILTERS.map { |filter| filter.merge(label: t("キャプチャファイル")) }
|
|
9
|
+
|
|
8
10
|
def open_dialog
|
|
9
11
|
if @backend == :tui
|
|
10
|
-
path_dialog("ファイルを開く") { |path| open_file(path) }
|
|
12
|
+
path_dialog(t("ファイルを開く")) { |path| open_file(path) }
|
|
11
13
|
else
|
|
12
|
-
paths = @window.prompt_for_paths(filters:
|
|
14
|
+
paths = @window.prompt_for_paths(filters: capture_filters)
|
|
13
15
|
open_file(paths.first) if paths && !paths.empty?
|
|
14
16
|
end
|
|
15
17
|
rescue StandardError => error
|
|
@@ -46,8 +48,8 @@ module Vanken
|
|
|
46
48
|
def save_dialog(after: nil)
|
|
47
49
|
return unless document
|
|
48
50
|
path = @backend == :tui ? nil : @window.prompt_for_paths(save: true,
|
|
49
|
-
default_name: "capture.pcapng", filters:
|
|
50
|
-
return path_dialog("名前を付けて保存") { |value| save_file(value, after: after) } if @backend == :tui
|
|
51
|
+
default_name: "capture.pcapng", filters: capture_filters)&.first
|
|
52
|
+
return path_dialog(t("名前を付けて保存")) { |value| save_file(value, after: after) } if @backend == :tui
|
|
51
53
|
save_file(path, after: after) if path
|
|
52
54
|
rescue StandardError => error
|
|
53
55
|
show_error(error)
|
|
@@ -77,28 +79,31 @@ module Vanken
|
|
|
77
79
|
def reload_file = document&.source == :file && document.path && open_file(document.path)
|
|
78
80
|
|
|
79
81
|
def request_destructive(&operation)
|
|
82
|
+
dismiss_dialog
|
|
80
83
|
if @capture.running?
|
|
84
|
+
@dialog_reopen = -> { request_destructive(&operation) }
|
|
81
85
|
@dialog_kind = :capturing
|
|
82
|
-
content = Zaniah::Div.new.gap(12).child(Zaniah::UI::Label.new("キャプチャを停止してから操作してください。"))
|
|
83
|
-
.child(Zaniah::UI::Button.new("停止").on_click do
|
|
86
|
+
content = Zaniah::Div.new.gap(12).child(Zaniah::UI::Label.new(t("キャプチャを停止してから操作してください。")))
|
|
87
|
+
.child(Zaniah::UI::Button.new(t("停止")).on_click do
|
|
84
88
|
@capture.stop
|
|
85
89
|
dismiss_dialog
|
|
86
90
|
@app.executor.background { @capture.wait; @app.executor.post { request_destructive(&operation) unless @closing } }
|
|
87
91
|
end)
|
|
88
|
-
@dialog = Zaniah::UI::Dialog.new(content, title: "キャプチャ中").test_id("vk.capture.confirm")
|
|
92
|
+
@dialog = Zaniah::UI::Dialog.new(content, title: t("キャプチャ中"), close_label: t("閉じる")).on_close { dismiss_dialog }.test_id("vk.capture.confirm")
|
|
89
93
|
@window.request_frame
|
|
90
94
|
return false
|
|
91
95
|
end
|
|
92
96
|
return operation.call unless document&.dirty?
|
|
97
|
+
@dialog_reopen = -> { request_destructive(&operation) }
|
|
93
98
|
@pending_destructive = operation
|
|
94
99
|
@dialog_kind = :unsaved
|
|
95
100
|
content = Zaniah::Div.new.flex_col.gap(12)
|
|
96
|
-
.child(Zaniah::UI::Label.new("このキャプチャはまだ保存されていません。"))
|
|
101
|
+
.child(Zaniah::UI::Label.new(t("このキャプチャはまだ保存されていません。")))
|
|
97
102
|
.child(Zaniah::Div.new.flex_row.gap(8)
|
|
98
|
-
.child(Zaniah::UI::Button.new("保存").test_id("vk.unsaved.save").on_click { save_dialog(after: @pending_destructive) })
|
|
99
|
-
.child(Zaniah::UI::Button.new("破棄", variant: :danger).test_id("vk.unsaved.discard").on_click { discard_changes })
|
|
100
|
-
.child(Zaniah::UI::Button.new("キャンセル", variant: :secondary).test_id("vk.unsaved.cancel").on_click { dismiss_dialog }))
|
|
101
|
-
@dialog = Zaniah::UI::Dialog.new(content, title: "キャプチャを保存しますか?", close_on_scrim: false).test_id("vk.unsaved")
|
|
103
|
+
.child(Zaniah::UI::Button.new(t("保存")).test_id("vk.unsaved.save").on_click { save_dialog(after: @pending_destructive) })
|
|
104
|
+
.child(Zaniah::UI::Button.new(t("破棄"), variant: :danger).test_id("vk.unsaved.discard").on_click { discard_changes })
|
|
105
|
+
.child(Zaniah::UI::Button.new(t("キャンセル"), variant: :secondary).test_id("vk.unsaved.cancel").on_click { dismiss_dialog }))
|
|
106
|
+
@dialog = Zaniah::UI::Dialog.new(content, title: t("キャプチャを保存しますか?"), close_on_scrim: false, close_label: t("閉じる")).on_close { dismiss_dialog }.test_id("vk.unsaved")
|
|
102
107
|
@window.request_frame
|
|
103
108
|
false
|
|
104
109
|
end
|
|
@@ -42,7 +42,7 @@ module Vanken
|
|
|
42
42
|
message = program.warnings.join("\n")
|
|
43
43
|
rescue Core::DisplayFilter::SyntaxError => error
|
|
44
44
|
status = :error
|
|
45
|
-
message = "
|
|
45
|
+
message = t("%{message} (位置 %{position})", message: error.message, position: error.position + 1)
|
|
46
46
|
end
|
|
47
47
|
@app.executor.post do
|
|
48
48
|
@filter_field.status(status, message: message) if generation == @filter_generation && !@closing
|
|
@@ -51,6 +51,7 @@ module Vanken
|
|
|
51
51
|
end
|
|
52
52
|
|
|
53
53
|
def apply_filter
|
|
54
|
+
cancel_packet_search
|
|
54
55
|
doc = document
|
|
55
56
|
return unless doc
|
|
56
57
|
expression = @filter_field.value
|
|
@@ -60,7 +61,7 @@ module Vanken
|
|
|
60
61
|
@packet_source.reset
|
|
61
62
|
@window.request_frame
|
|
62
63
|
rescue Core::DisplayFilter::SyntaxError => error
|
|
63
|
-
@filter_field.status(:error, message: "
|
|
64
|
+
@filter_field.status(:error, message: t("%{message} (位置 %{position})", message: error.message, position: error.position + 1))
|
|
64
65
|
end
|
|
65
66
|
def initial_filter(text)
|
|
66
67
|
set_filter(text)
|
|
@@ -85,25 +86,51 @@ module Vanken
|
|
|
85
86
|
set_filter(expression)
|
|
86
87
|
apply_filter unless prepare
|
|
87
88
|
end
|
|
88
|
-
def selection_menu(expression)
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
%i[
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
89
|
+
def selection_menu(expression, number: selected_number)
|
|
90
|
+
filters = Zaniah::Menu.new([])
|
|
91
|
+
if expression
|
|
92
|
+
%i[filter prepare].each do |kind|
|
|
93
|
+
%i[selected not and and_not or or_not].each do |mode|
|
|
94
|
+
name = :"#{kind}_#{mode}"
|
|
95
|
+
@app.actions.register(:"context_#{name}", title: @app.actions.command(name).title) do
|
|
96
|
+
filter_from_expression(expression, mode, prepare: kind == :prepare)
|
|
97
|
+
end
|
|
95
98
|
end
|
|
96
99
|
end
|
|
100
|
+
@app.actions.register(:context_copy_filter, title: t("フィルタとしてコピー")) { copy_text(expression) }
|
|
101
|
+
ui = self
|
|
102
|
+
filters = Zaniah::Menu.build do
|
|
103
|
+
submenu(ui.t("フィルタとして適用")) { %i[selected not and and_not or or_not].each { |mode| item(:"context_filter_#{mode}") } }
|
|
104
|
+
submenu(ui.t("フィルタを準備")) { %i[selected not and and_not or or_not].each { |mode| item(:"context_prepare_#{mode}") } }
|
|
105
|
+
item(:context_copy_filter)
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
Zaniah::Menu.new(filters.items + packet_context_menu(number).items)
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def packet_context_menu(number)
|
|
112
|
+
doc = document
|
|
113
|
+
return Zaniah::Menu.new([]) unless doc && number.is_a?(Integer) && number.between?(1, doc.count)
|
|
114
|
+
enabled = ->(*) { document.equal?(doc) }
|
|
115
|
+
{
|
|
116
|
+
context_mark_packet: [:marked, :toggle_mark, "マーク", "マークを解除"],
|
|
117
|
+
context_ignore_packet: [:ignored, :toggle_ignore, "無視", "無視を解除"],
|
|
118
|
+
context_time_reference: [:time_references, :toggle_time_reference, "時刻基準にする", "時刻基準を解除"]
|
|
119
|
+
}.each do |action, (state, operation, label, clear_label)|
|
|
120
|
+
@app.actions.register(action, title: t(doc.public_send(state).include?(number) ? clear_label : label), enabled: enabled) do
|
|
121
|
+
update_packet_state(operation, number: number) if enabled.call
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
stream_enabled = ->(*) { enabled.call && doc.annotations[number][:tcp_stream] >= 0 }
|
|
125
|
+
@app.actions.register(:context_follow_tcp_stream, title: t("TCP ストリームを追跡"), enabled: stream_enabled) do
|
|
126
|
+
follow_tcp_stream(doc.annotations[number][:tcp_stream]) if stream_enabled.call
|
|
97
127
|
end
|
|
98
|
-
@app.actions.register(:context_copy_filter, title: "フィルタとしてコピー") { copy_text(expression) }
|
|
99
128
|
Zaniah::Menu.build do
|
|
100
|
-
|
|
101
|
-
submenu("フィルタを準備") { %i[selected not and and_not or or_not].each { |mode| item(:"context_prepare_#{mode}") } }
|
|
102
|
-
item(:context_copy_filter)
|
|
129
|
+
%i[context_mark_packet context_ignore_packet context_time_reference context_follow_tcp_stream].each { |action| item(action) }
|
|
103
130
|
end
|
|
104
131
|
end
|
|
105
132
|
def bookmark_filter
|
|
106
|
-
path_dialog("フィルタの名前") do |name|
|
|
133
|
+
path_dialog(t("フィルタの名前")) do |name|
|
|
107
134
|
@preferences.bookmark(name, @filter_field.value) unless name.empty?
|
|
108
135
|
dismiss_dialog
|
|
109
136
|
end
|
|
@@ -113,7 +140,8 @@ module Vanken
|
|
|
113
140
|
content = Zaniah::Div.new.flex_col.gap(4).children(items.first(60).map do |label, expression|
|
|
114
141
|
Zaniah::UI::Button.new(label, variant: :ghost).on_click { set_filter(expression); apply_filter; dismiss_dialog }
|
|
115
142
|
end)
|
|
116
|
-
|
|
143
|
+
content.child(Zaniah::UI::Label.new(t("フィルタ履歴はまだありません。"), tone: :muted)) if items.empty?
|
|
144
|
+
show_dialog(:history, t("表示フィルタ"), content, reopen: -> { filter_history })
|
|
117
145
|
end
|
|
118
146
|
end
|
|
119
147
|
end
|