vanken 0.1.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +24 -0
- data/README.md +28 -11
- data/data/coloring_rules.yml +67 -0
- data/docs/performance.md +53 -0
- data/docs/releases.md +2 -4
- data/docs/usage.md +51 -0
- data/exe/vanken-setup-permissions +6 -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/capture/permission_setup.rb +201 -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/packaging/README.md +41 -1
- data/packaging/linux/org.vanken.capture.policy +14 -0
- data/packaging/linux/vanken-capture.wrapper +6 -0
- data/packaging/linux/vanken.desktop +9 -0
- data/packaging/linux/vanken.sudoers +2 -0
- data/packaging/macos/chmod-bpf +6 -0
- data/packaging/macos/org.vanken.chmod-bpf.plist +8 -0
- 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/capture/permission_setup.rbs +43 -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 +61 -3
|
@@ -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
|
data/lib/vanken/ui/main_view.rb
CHANGED
|
@@ -1,34 +1,55 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require_relative "../config/columns"
|
|
4
|
+
|
|
3
5
|
module Vanken
|
|
4
6
|
module UI
|
|
5
7
|
class MainView < Zaniah::UI::Component
|
|
6
|
-
COLUMNS =
|
|
7
|
-
|
|
8
|
+
COLUMNS = Config::Columns::BUILTINS
|
|
9
|
+
|
|
10
|
+
class ExpertBadge < Zaniah::UI::Badge
|
|
11
|
+
def initialize(label, variant:, &activate)
|
|
12
|
+
super(label, variant: variant)
|
|
13
|
+
@label, @activate = label, activate
|
|
14
|
+
end
|
|
15
|
+
def build(cx)
|
|
16
|
+
super.cursor(:pointer).on_click { @activate.call }
|
|
17
|
+
.focusable(context: {in_button: true}) { |action| action == :activate && (@activate.call; true) }
|
|
18
|
+
end
|
|
19
|
+
def accessibility_node(_cx) = node(:button, label: @label, actions: [:press])
|
|
20
|
+
def accessibility_action(_node, action) = action == :press && (@activate.call; true)
|
|
21
|
+
end
|
|
8
22
|
|
|
9
23
|
def initialize(ui)
|
|
10
24
|
super()
|
|
11
25
|
@ui = ui
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
end
|
|
26
|
+
@filter_container = Zaniah::Div.new.flex_1.focusable(context: {in_display_filter: true}).child(ui.filter_field)
|
|
27
|
+
@menu_bar = Zaniah::UI::MenuBar.from(ui.app.menu_bar)
|
|
28
|
+
settings = ui.respond_to?(:column_settings) ? ui.column_settings : Config::Columns.load(directory: ui.preferences.directory, legacy: ui.preferences.get("layout.columns"))
|
|
29
|
+
columns = settings.table_columns.map do |column|
|
|
30
|
+
default = COLUMNS.find { |key, *_| key == column[:key] }
|
|
31
|
+
default && column[:label] == default[1] ? column.merge(label: text(column[:label])) : column
|
|
19
32
|
end
|
|
20
|
-
|
|
33
|
+
ui.document.custom_columns = settings.custom if ui.document
|
|
34
|
+
table = Zaniah::UI::VirtualTable.new(ui.packet_source, columns: columns, follow_tail: ui.autoscroll?, label: text("パケット一覧"))
|
|
21
35
|
.test_id("vk.packet_list").on_select { |index, *_| number = ui.packet_source.row_id(index); ui.select_packet(number) if number.is_a?(Integer) }
|
|
22
36
|
.on_sort { |key, direction, _| ui.document&.sort(key, direction) }
|
|
23
|
-
.on_columns_change
|
|
24
|
-
|
|
25
|
-
|
|
37
|
+
.on_columns_change do |items, _|
|
|
38
|
+
if ui.respond_to?(:persist_columns_layout)
|
|
39
|
+
ui.persist_columns_layout(items)
|
|
40
|
+
else
|
|
41
|
+
settings.update_layout(items)
|
|
42
|
+
settings.save(ui.preferences.directory)
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
tree = Zaniah::UI::TreeView.new([], label: text("パケット詳細")).test_id("vk.packet_details").on_select { |node, *_| ui.select_detail(node) }
|
|
46
|
+
.on_context_menu { |node, _| ui.respond_to?(:field_menu) ? ui.field_menu(node) : ui.selection_menu(node.filter) }
|
|
26
47
|
table.on_row_context_menu do |index, _|
|
|
27
48
|
number = ui.packet_source.row_id(index)
|
|
28
49
|
table.select(index) if number.is_a?(Integer)
|
|
29
|
-
ui.selection_menu(number.is_a?(Integer) ? "frame.number == #{number}" : nil)
|
|
50
|
+
ui.selection_menu(number.is_a?(Integer) ? "frame.number == #{number}" : nil, number: number.is_a?(Integer) ? number : nil)
|
|
30
51
|
end
|
|
31
|
-
hex = Zaniah::UI::HexView.new("".b).test_id("vk.packet_bytes").on_select { |range, _| ui.select_bytes(range) }
|
|
52
|
+
hex = Zaniah::UI::HexView.new("".b).accessibility_label(text("パケットバイト")).test_id("vk.packet_bytes").on_select { |range, _| ui.select_bytes(range) }
|
|
32
53
|
.on_copy { |range, _| ui.copy_text(ui.selected_bytes.byteslice(range).unpack1("H*").scan(/../).join(" ")) }
|
|
33
54
|
ui.install_panes(table, tree, hex)
|
|
34
55
|
ratios = ui.preferences.get("layout.ratios")
|
|
@@ -39,6 +60,7 @@ module Vanken
|
|
|
39
60
|
end
|
|
40
61
|
|
|
41
62
|
def build(cx)
|
|
63
|
+
@previous_buttons, @buttons = @buttons || {}, {}
|
|
42
64
|
ui = @ui
|
|
43
65
|
doc = ui.document
|
|
44
66
|
doc.frame_latency = cx.window.frame_stats.fetch(:frame_ms, 0) / 1000 if doc
|
|
@@ -53,7 +75,7 @@ module Vanken
|
|
|
53
75
|
@heights = heights
|
|
54
76
|
end
|
|
55
77
|
root = Zaniah::Div.new.w_full.h_full.flex_col.bg(cx.theme.colors.background)
|
|
56
|
-
root.child(
|
|
78
|
+
root.child(@menu_bar) unless RUBY_PLATFORM.include?("darwin") && ui.native?
|
|
57
79
|
toolbar_items = [
|
|
58
80
|
button("開く", "vk.open") { ui.open_dialog },
|
|
59
81
|
button("保存", "vk.save", disabled: !doc) { ui.save_dialog },
|
|
@@ -63,13 +85,22 @@ module Vanken
|
|
|
63
85
|
if doc&.loading? && doc.source == :file
|
|
64
86
|
toolbar_items << button("読み込みを中止", "vk.file.cancel") { doc.cancel }
|
|
65
87
|
end
|
|
66
|
-
root.child(Zaniah::UI::Toolbar.new(*toolbar_items))
|
|
88
|
+
root.child(Zaniah::UI::Toolbar.new(*toolbar_items).accessibility_label(text("キャプチャ操作")))
|
|
67
89
|
root.child(Zaniah::Div.new.flex_row.items_center.gap(6).p([4, 8])
|
|
68
|
-
.child(
|
|
90
|
+
.child(@filter_container).child(button("適用", "vk.filter.apply", disabled: !doc) { ui.apply_filter })
|
|
69
91
|
.child(button("クリア", "vk.filter.clear") { ui.clear_filter })
|
|
70
92
|
.child(button("履歴", "vk.filter.history") { ui.filter_history }))
|
|
71
93
|
root.child(doc ? @split.flex_1 : welcome(cx).flex_1)
|
|
72
|
-
|
|
94
|
+
status = [Zaniah::UI::Label.new(ui.status_text, size: :xs).flex_1]
|
|
95
|
+
summary = doc.respond_to?(:expert_summary) ? doc.expert_summary : nil
|
|
96
|
+
if summary && summary[:count].positive?
|
|
97
|
+
severity = %w[note note warning error].fetch(summary[:severity])
|
|
98
|
+
caption = {"note" => "注記", "warning" => "警告", "error" => "エラー"}.fetch(severity)
|
|
99
|
+
badge = ExpertBadge.new("#{text(caption)}: #{summary[:count]}", variant: {"note" => :neutral, "warning" => :warning, "error" => :danger}.fetch(severity)) { ui.expert_info }
|
|
100
|
+
.test_id("vk.expert.badge")
|
|
101
|
+
status << badge
|
|
102
|
+
end
|
|
103
|
+
root.child(Zaniah::UI::StatusBar.new(*status).test_id("vk.status"))
|
|
73
104
|
root.child(ui.dialog) if ui.dialog
|
|
74
105
|
root.on_drop(types: ["text/uri-list"]) do |event, _|
|
|
75
106
|
event.content.formats.fetch("text/uri-list", "").each_line do |line|
|
|
@@ -90,23 +121,37 @@ module Vanken
|
|
|
90
121
|
|
|
91
122
|
private
|
|
92
123
|
|
|
93
|
-
def
|
|
94
|
-
def
|
|
95
|
-
def
|
|
124
|
+
def text(value) = @ui.respond_to?(:t) ? @ui.t(value) : value
|
|
125
|
+
def pane(title, body) = Zaniah::Div.new.w_full.h_full.flex_col.child(Zaniah::UI::Label.new(text(title), size: :xs, tone: :muted).p([4, 8])).child(body)
|
|
126
|
+
def button(label, id, disabled: false, size: :sm, variant: :secondary, &block)
|
|
127
|
+
caption = text(label)
|
|
128
|
+
key = [id, caption, size, variant]
|
|
129
|
+
@buttons[key] = (@previous_buttons[key] || Zaniah::UI::Button.new(caption, size: size, variant: variant).test_id(id)).disabled(disabled).on_click(&block)
|
|
130
|
+
end
|
|
131
|
+
def welcome(cx)
|
|
96
132
|
content = Zaniah::Div.new.flex_col.p(24).gap(12).test_id("vk.welcome")
|
|
97
133
|
.child(Zaniah::UI::Label.new("Vanken", size: :xl))
|
|
98
|
-
.child(Zaniah::UI::Label.new("パケットキャプチャを開くか、インタフェースを選択してキャプチャを開始します。", tone: :muted, wrap: :word))
|
|
99
|
-
.child(
|
|
100
|
-
.child(
|
|
134
|
+
.child(Zaniah::UI::Label.new(text("パケットキャプチャを開くか、インタフェースを選択してキャプチャを開始します。"), tone: :muted, wrap: :word))
|
|
135
|
+
.child(button("キャプチャファイルを開く", "vk.welcome.open", size: :md, variant: :primary) { @ui.open_dialog })
|
|
136
|
+
.child(button("インタフェースを選択", "vk.welcome.capture", size: :md, variant: :primary) { @ui.capture_options })
|
|
101
137
|
@ui.preferences.recent_files.first(10).each do |path|
|
|
102
|
-
content.child(
|
|
138
|
+
content.child(button(path, "vk.recent.#{path}", variant: :ghost) { @ui.open_file(path) })
|
|
103
139
|
end
|
|
104
140
|
(@ui.interface_infos || []).each do |info|
|
|
105
141
|
name = info.fetch(:name)
|
|
106
|
-
|
|
107
|
-
|
|
142
|
+
traffic = @ui.respond_to?(:interface_traffic) ? (@ui.interface_traffic || {}) : {}
|
|
143
|
+
values = (traffic[:history] || {}).fetch(name, [])
|
|
144
|
+
values = [0] if values.empty?
|
|
145
|
+
rate = (traffic[:rates] || {}).fetch(name, 0)
|
|
146
|
+
row = Zaniah::Div.new.flex_row.items_center.gap(8)
|
|
147
|
+
.child(button("#{name} #{info[:description]}", "vk.interface.#{name}", variant: :ghost) { @ui.capture_options(interface: name) }.flex_1)
|
|
148
|
+
.child(Zaniah::UI::Sparkline.new(values, width: 100, height: 24, label: "#{name} #{text('トラフィック')}").test_id("vk.interface.traffic.#{name}"))
|
|
149
|
+
.child(Zaniah::UI::Label.new("#{rate} B/s", size: :xs, tone: :muted))
|
|
150
|
+
content.child(row)
|
|
108
151
|
end
|
|
109
|
-
|
|
152
|
+
@welcome_scroll ||= Zaniah::ScrollView.new
|
|
153
|
+
@welcome_scroll.children.clear
|
|
154
|
+
@welcome_scroll.h([cx.window.content_size.height - 132, 120].max).child(content)
|
|
110
155
|
end
|
|
111
156
|
end
|
|
112
157
|
end
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Vanken
|
|
4
|
+
module UI
|
|
5
|
+
module NavigationOperations
|
|
6
|
+
def mark_packet = update_packet_state(:toggle_mark)
|
|
7
|
+
def ignore_packet = update_packet_state(:toggle_ignore)
|
|
8
|
+
def time_reference = update_packet_state(:toggle_time_reference)
|
|
9
|
+
def mark_all_displayed = update_packet_state(:mark_all_displayed, selected: false)
|
|
10
|
+
def unmark_all = update_packet_state(:unmark_all, selected: false)
|
|
11
|
+
def prev_in_conversation = navigate_conversation(-1)
|
|
12
|
+
def next_in_conversation = navigate_conversation(1)
|
|
13
|
+
|
|
14
|
+
def find_packet
|
|
15
|
+
mode = @search_mode || :filter
|
|
16
|
+
target = @search_target || :bytes
|
|
17
|
+
field = Zaniah::UI::TextField.new(@search_query || "", label: t("検索内容")).test_id("vk.search.query")
|
|
18
|
+
modes = Zaniah::UI::SegmentedControl.new([[t("表示フィルタ"), :filter], [t("16 進"), :hex], [t("文字列"), :string], [t("正規表現"), :regex]], value: mode, label: t("検索形式"))
|
|
19
|
+
.on_change { |value, *_| mode = value }.test_id("vk.search.mode")
|
|
20
|
+
targets = Zaniah::UI::Select.new([[t("パケット一覧"), :list], [t("パケット詳細"), :details], [t("パケットバイト"), :bytes]], label: t("検索対象"), value: target)
|
|
21
|
+
.on_change { |value, *_| target = value }.test_id("vk.search.target")
|
|
22
|
+
content = Zaniah::Div.new.flex_col.gap(12).child(modes).child(targets).child(field)
|
|
23
|
+
.child(Zaniah::Div.new.flex_row.gap(8)
|
|
24
|
+
.child(Zaniah::UI::Button.new(t("次を検索")).test_id("vk.search.next").on_click { search_packets(field.value, mode: mode, target: target) })
|
|
25
|
+
.child(Zaniah::UI::Button.new(t("前を検索"), variant: :secondary).on_click { search_packets(field.value, mode: mode, target: target, direction: -1) })
|
|
26
|
+
.child(Zaniah::UI::Button.new(t("キャンセル"), variant: :secondary).on_click { cancel_packet_search; dismiss_dialog }))
|
|
27
|
+
show_dialog(:search, t("パケット検索"), content, reopen: -> { @search_query, @search_mode, @search_target = field.value, mode, target; find_packet })
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def search_result_dialog
|
|
31
|
+
show_dialog(:search_result, t("パケット検索"), Zaniah::UI::Label.new(t("一致するパケットはありません。"), wrap: :word), reopen: -> { search_result_dialog })
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def find_next = repeat_packet_search(1)
|
|
35
|
+
def find_prev = repeat_packet_search(-1)
|
|
36
|
+
def cancel_packet_search
|
|
37
|
+
@search_generation = (@search_generation || 0) + 1
|
|
38
|
+
document&.cancel_search
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def search_packets(query, mode: :filter, target: :bytes, direction: 1)
|
|
42
|
+
doc = document
|
|
43
|
+
return unless doc
|
|
44
|
+
cancel_packet_search
|
|
45
|
+
generation = @search_generation
|
|
46
|
+
@search_query, @search_mode, @search_target = query, mode, target
|
|
47
|
+
doc.search(query, mode: mode, target: target, from: selected_number, direction: direction) do |number, error|
|
|
48
|
+
@app.executor.post do
|
|
49
|
+
next unless !@closing && generation == @search_generation && document.equal?(doc)
|
|
50
|
+
if error
|
|
51
|
+
show_error(error)
|
|
52
|
+
elsif number
|
|
53
|
+
select_found_packet(number)
|
|
54
|
+
else
|
|
55
|
+
search_result_dialog
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
dismiss_dialog if @dialog_kind == :search
|
|
60
|
+
rescue StandardError => error
|
|
61
|
+
show_error(error)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
private
|
|
65
|
+
def repeat_packet_search(direction)
|
|
66
|
+
@search_query && !@search_query.empty? ? search_packets(@search_query, mode: @search_mode, target: @search_target, direction: direction) : find_packet
|
|
67
|
+
end
|
|
68
|
+
def select_found_packet(number)
|
|
69
|
+
index = document&.displayed_index(number)
|
|
70
|
+
return unless index
|
|
71
|
+
@table.select(index)
|
|
72
|
+
@table.scroll_to(index, align: :center)
|
|
73
|
+
end
|
|
74
|
+
def navigate_conversation(direction)
|
|
75
|
+
number = document&.conversation_neighbor(selected_number, direction)
|
|
76
|
+
select_found_packet(number) if number
|
|
77
|
+
end
|
|
78
|
+
def update_packet_state(operation, selected: true, number: selected_number)
|
|
79
|
+
return unless document && (!selected || number)
|
|
80
|
+
selected ? document.public_send(operation, number) : document.public_send(operation)
|
|
81
|
+
@packet_source.reset
|
|
82
|
+
@window.request_frame
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
|
@@ -40,6 +40,14 @@ module Vanken
|
|
|
40
40
|
|
|
41
41
|
def count = @count
|
|
42
42
|
def packet_count = @packet_count
|
|
43
|
+
def invalidate_rows(numbers)
|
|
44
|
+
@generation += 1
|
|
45
|
+
@pending, @requests = {}, {}
|
|
46
|
+
@batch_scheduled = false
|
|
47
|
+
@refresh_pending = @refresh_again = nil
|
|
48
|
+
numbers.each { |number| @rows.delete(number) }
|
|
49
|
+
@ui.window.request_frame
|
|
50
|
+
end
|
|
43
51
|
def row_id(index)
|
|
44
52
|
return [:pending, index] unless index.is_a?(Integer) && index.between?(0, @count - 1)
|
|
45
53
|
@numbers[index] ||= @document.number_at(index)
|
|
@@ -51,7 +59,21 @@ module Vanken
|
|
|
51
59
|
text = value(index, key)
|
|
52
60
|
return nil if text.nil?
|
|
53
61
|
theme = @ui.app.global(:theme)
|
|
54
|
-
|
|
62
|
+
colors = row_style(index)
|
|
63
|
+
Zaniah::Text.new(text.to_s, font: @ui.monospace_font, size: theme.typography.size_sm, color: colors.fetch(:foreground, theme.colors.text))
|
|
64
|
+
end
|
|
65
|
+
def row_style(index)
|
|
66
|
+
number = row_id(index)
|
|
67
|
+
return {} unless number.is_a?(Integer)
|
|
68
|
+
if @ui.table.respond_to?(:selection) && @ui.table.selection.include?(number)
|
|
69
|
+
theme = @ui.app.global(:theme)
|
|
70
|
+
return {foreground: theme.colors.text, strikethrough: @document.ignored.include?(number)}
|
|
71
|
+
end
|
|
72
|
+
if @document.ignored.include?(number)
|
|
73
|
+
return {foreground: @ui.app.global(:theme).colors.text_muted, strikethrough: true}
|
|
74
|
+
end
|
|
75
|
+
return {foreground: Zaniah::Color.parse("#ffffff"), background: Zaniah::Color.parse("#000000")} if @document.marked.include?(number)
|
|
76
|
+
@rows[number]&.fetch(:style, {}) || {}
|
|
55
77
|
end
|
|
56
78
|
def value(index, key)
|
|
57
79
|
number = row_id(index)
|
|
@@ -139,7 +161,17 @@ module Vanken
|
|
|
139
161
|
def row_values(document, number, index, format, precision, previous = :current)
|
|
140
162
|
row = document.row(number)
|
|
141
163
|
values = row.slice(:source, :destination, :protocol, :length, :info).transform_values(&:to_s)
|
|
164
|
+
if @ui.respond_to?(:resolve_address)
|
|
165
|
+
%i[source destination].each do |key|
|
|
166
|
+
values[key] = @ui.resolve_address(values[key], document: document, number: number)
|
|
167
|
+
end
|
|
168
|
+
end
|
|
169
|
+
values.merge!(row.select { |key, _| key.to_s.start_with?("field:") })
|
|
142
170
|
values[:time] = format_time(document, row, number, index, format, precision, previous)
|
|
171
|
+
if @ui.respond_to?(:coloring_enabled?) && @ui.coloring_enabled?
|
|
172
|
+
colors = @ui.coloring_rules.match(document.view(number), theme: @ui.app.global(:theme).name) || {}
|
|
173
|
+
values[:style] = colors.transform_values { |color| Zaniah::Color.parse(color) }
|
|
174
|
+
end
|
|
143
175
|
values
|
|
144
176
|
rescue StandardError
|
|
145
177
|
nil
|
data/lib/vanken/ui/selection.rb
CHANGED
|
@@ -8,6 +8,10 @@ module Vanken
|
|
|
8
8
|
return unless doc && number.is_a?(Integer) && number.between?(1, doc.count)
|
|
9
9
|
@selection_generation += 1
|
|
10
10
|
generation = @selection_generation
|
|
11
|
+
expanded = @pending_tree_expansion ||= @tree.expanded.dup
|
|
12
|
+
update { |state| state.merge!(number: number, node: nil, details: [], bytes: "".b) }
|
|
13
|
+
@tree.replace([])
|
|
14
|
+
@hex.bytes = "".b
|
|
11
15
|
@app.executor.background do
|
|
12
16
|
nodes = doc.details(number)
|
|
13
17
|
bytes = doc.store.read(number).bytes
|
|
@@ -15,6 +19,8 @@ module Vanken
|
|
|
15
19
|
next unless generation == @selection_generation && document.equal?(doc)
|
|
16
20
|
update { |state| state.merge!(number: number, node: nil, details: nodes, bytes: bytes) }
|
|
17
21
|
@tree.replace(nodes)
|
|
22
|
+
expanded.each { |id| @tree.expand(id) }
|
|
23
|
+
@pending_tree_expansion = nil
|
|
18
24
|
@hex.bytes = bytes
|
|
19
25
|
end
|
|
20
26
|
rescue StandardError => error
|
|
@@ -65,10 +71,10 @@ module Vanken
|
|
|
65
71
|
@table.scroll_to(document.displayed_count - 1)
|
|
66
72
|
end
|
|
67
73
|
def go_to_packet
|
|
68
|
-
path_dialog("パケットへ移動") do |value|
|
|
74
|
+
path_dialog(t("パケットへ移動")) do |value|
|
|
69
75
|
number = Integer(value, 10)
|
|
70
76
|
index = document&.display_numbers&.index(number)
|
|
71
|
-
raise Vanken::Error, "指定したパケットは表示されていません" unless index
|
|
77
|
+
raise Vanken::Error, t("指定したパケットは表示されていません") unless index
|
|
72
78
|
@table.select(index)
|
|
73
79
|
@table.scroll_to(index, align: :center)
|
|
74
80
|
dismiss_dialog
|