sumologic-query 1.3.0 → 1.3.2
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 +8 -0
- data/README.md +1 -0
- data/lib/sumologic/interactive/fzf_viewer/config.rb +38 -0
- data/lib/sumologic/interactive/fzf_viewer/formatter.rb +50 -0
- data/lib/sumologic/interactive/fzf_viewer/fzf_config.rb +74 -0
- data/lib/sumologic/interactive/fzf_viewer/header_builder.rb +42 -0
- data/lib/sumologic/interactive/fzf_viewer/searchable_builder.rb +40 -0
- data/lib/sumologic/interactive/fzf_viewer.rb +39 -114
- data/lib/sumologic/version.rb +1 -1
- metadata +7 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: fff9b3ee00ddf6f3adfd2bb84fb8488068149fc8e7bfd94a3aa1b9854f7b74f2
|
|
4
|
+
data.tar.gz: 898d75c6c4ca00c9e78ef21301d1345ec4e2c3806f33a8554b485486f314214a
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: b9d264177ec993228a116d3c17f77cf1ac0b0e16c48d19a3de2b6b6502fcf9d89bfa90dc1046609f5819c18bcccb0a30eeb3c57b7ba00363a94f4e08f2705318
|
|
7
|
+
data.tar.gz: 252af2f57e025938cbe9607eb5ae32b23140d0e84bbc8c8ecaceb10c06110436e37a096c9c6d21480c2408cbfb3f29a5f322e71655622954c5c7fc5881ac959f
|
data/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,14 @@
|
|
|
6
6
|
|
|
7
7
|
|
|
8
8
|
|
|
9
|
+
## [1.3.2](https://github.com/patrick204nqh/sumologic-query/compare/v1.3.1...v1.3.2) (2025-11-16)
|
|
10
|
+
|
|
11
|
+
<!-- Release notes generated using configuration in .github/release.yml at main -->
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
**Full Changelog**: https://github.com/patrick204nqh/sumologic-query/compare/v1.3.1...v1.3.2
|
|
16
|
+
|
|
9
17
|
# [1.2.0](https://github.com/patrick204nqh/sumologic-query/compare/v1.1.2...v1.2.0) (2025-11-14)
|
|
10
18
|
|
|
11
19
|
|
data/README.md
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
> A lightweight Ruby CLI for querying Sumo Logic logs and metadata. Simple, fast, read-only access to your logs.
|
|
4
4
|
|
|
5
5
|
[](https://badge.fury.io/rb/sumologic-query)
|
|
6
|
+
[](https://rubygems.org/gems/sumologic-query)
|
|
6
7
|
[](https://opensource.org/licenses/MIT)
|
|
7
8
|
|
|
8
9
|
## Why This Tool?
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Sumologic
|
|
4
|
+
module Interactive
|
|
5
|
+
class FzfViewer
|
|
6
|
+
module Config
|
|
7
|
+
# Display configuration
|
|
8
|
+
TIME_WIDTH = 8
|
|
9
|
+
LEVEL_WIDTH = 7
|
|
10
|
+
SOURCE_WIDTH = 25
|
|
11
|
+
MESSAGE_PREVIEW_LENGTH = 80
|
|
12
|
+
SEARCHABLE_PADDING = 5
|
|
13
|
+
|
|
14
|
+
# Searchable field names
|
|
15
|
+
SEARCHABLE_FIELDS = %w[
|
|
16
|
+
_source
|
|
17
|
+
_sourcecategory
|
|
18
|
+
_sourcename
|
|
19
|
+
_collector
|
|
20
|
+
_sourcehost
|
|
21
|
+
region
|
|
22
|
+
_group
|
|
23
|
+
_tier
|
|
24
|
+
_view
|
|
25
|
+
].freeze
|
|
26
|
+
|
|
27
|
+
# ANSI color codes
|
|
28
|
+
COLORS = {
|
|
29
|
+
red: "\e[31m",
|
|
30
|
+
yellow: "\e[33m",
|
|
31
|
+
cyan: "\e[36m",
|
|
32
|
+
gray: "\e[90m",
|
|
33
|
+
reset: "\e[0m"
|
|
34
|
+
}.freeze
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Sumologic
|
|
4
|
+
module Interactive
|
|
5
|
+
class FzfViewer
|
|
6
|
+
module Formatter
|
|
7
|
+
module_function
|
|
8
|
+
|
|
9
|
+
def format_time(timestamp_ms)
|
|
10
|
+
return 'N/A' unless timestamp_ms
|
|
11
|
+
|
|
12
|
+
Time.at(timestamp_ms.to_i / 1000).strftime('%H:%M:%S')
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def format_level(level)
|
|
16
|
+
level_str = level.to_s.upcase.ljust(Config::LEVEL_WIDTH)
|
|
17
|
+
colorize_level(level_str)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def colorize_level(level_str)
|
|
21
|
+
case level_str.strip
|
|
22
|
+
when 'ERROR', 'FATAL', 'CRITICAL'
|
|
23
|
+
"#{Config::COLORS[:red]}#{level_str}#{Config::COLORS[:reset]}"
|
|
24
|
+
when 'WARN', 'WARNING'
|
|
25
|
+
"#{Config::COLORS[:yellow]}#{level_str}#{Config::COLORS[:reset]}"
|
|
26
|
+
when 'INFO'
|
|
27
|
+
"#{Config::COLORS[:cyan]}#{level_str}#{Config::COLORS[:reset]}"
|
|
28
|
+
when 'DEBUG', 'TRACE'
|
|
29
|
+
"#{Config::COLORS[:gray]}#{level_str}#{Config::COLORS[:reset]}"
|
|
30
|
+
else
|
|
31
|
+
level_str
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def sanitize(text)
|
|
36
|
+
text.to_s.gsub(/[\n\r\t]/, ' ').squeeze(' ')
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def truncate(text, length)
|
|
40
|
+
text = text.to_s
|
|
41
|
+
text.length > length ? "#{text[0...(length - 3)]}..." : text
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def pad(text, width)
|
|
45
|
+
text.ljust(width)
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'shellwords'
|
|
4
|
+
|
|
5
|
+
module Sumologic
|
|
6
|
+
module Interactive
|
|
7
|
+
class FzfViewer
|
|
8
|
+
module FzfConfig
|
|
9
|
+
module_function
|
|
10
|
+
|
|
11
|
+
def build_fzf_args(input_path, preview_path, header_text)
|
|
12
|
+
[
|
|
13
|
+
'fzf',
|
|
14
|
+
*search_options,
|
|
15
|
+
*display_options(preview_path, header_text),
|
|
16
|
+
*keybinding_options(input_path, preview_path)
|
|
17
|
+
]
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def search_options
|
|
21
|
+
[
|
|
22
|
+
'--ansi',
|
|
23
|
+
'--multi',
|
|
24
|
+
'--exact', # Exact substring matching
|
|
25
|
+
'-i', # Case-insensitive
|
|
26
|
+
'--no-hscroll' # Prevent horizontal scrolling
|
|
27
|
+
]
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def display_options(preview_path, header_text)
|
|
31
|
+
[
|
|
32
|
+
"--header=#{header_text}",
|
|
33
|
+
"--preview=#{build_preview_command(preview_path)}",
|
|
34
|
+
'--preview-window=right:60%:wrap:follow',
|
|
35
|
+
'--height=100%'
|
|
36
|
+
]
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def keybinding_options(input_path, preview_path)
|
|
40
|
+
[
|
|
41
|
+
'--bind=enter:toggle',
|
|
42
|
+
"--bind=tab:execute(#{build_view_command(preview_path)})",
|
|
43
|
+
'--bind=ctrl-a:select-all',
|
|
44
|
+
'--bind=ctrl-d:deselect-all',
|
|
45
|
+
'--bind=ctrl-s:execute-silent(echo {+} > sumo-selected.txt)+abort',
|
|
46
|
+
'--bind=ctrl-y:execute-silent(echo {+} | pbcopy || ' \
|
|
47
|
+
'echo {+} | xclip -selection clipboard 2>/dev/null)+abort',
|
|
48
|
+
'--bind=ctrl-e:execute-silent(echo {+} > sumo-export.jsonl)+abort',
|
|
49
|
+
'--bind=ctrl-/:toggle-preview',
|
|
50
|
+
"--bind=ctrl-r:reload(cat #{input_path})",
|
|
51
|
+
'--bind=ctrl-t:toggle-search',
|
|
52
|
+
'--bind=ctrl-q:abort'
|
|
53
|
+
]
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def build_view_command(preview_path)
|
|
57
|
+
# FZF {n} is 0-indexed, sed is 1-indexed
|
|
58
|
+
'LINE=$(({n} + 1)); ' \
|
|
59
|
+
"sed -n \"$LINE\"p #{Shellwords.escape(preview_path)} | jq -C . | less -R"
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def build_preview_command(preview_path)
|
|
63
|
+
escaped_path = Shellwords.escape(preview_path)
|
|
64
|
+
|
|
65
|
+
calc = "LINE=$(({n} + 1)); TOTAL=$(wc -l < #{escaped_path}); "
|
|
66
|
+
display = 'echo "Message $LINE of $TOTAL"; echo ""; '
|
|
67
|
+
extract = "sed -n \"$LINE\"p #{escaped_path}"
|
|
68
|
+
|
|
69
|
+
"#{calc}#{display}#{extract} | jq -C . || #{extract}"
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Sumologic
|
|
4
|
+
module Interactive
|
|
5
|
+
class FzfViewer
|
|
6
|
+
module HeaderBuilder
|
|
7
|
+
module_function
|
|
8
|
+
|
|
9
|
+
def build_header_text(results, messages)
|
|
10
|
+
[
|
|
11
|
+
build_column_headers,
|
|
12
|
+
build_info_line(results, messages),
|
|
13
|
+
build_search_tips,
|
|
14
|
+
build_keybindings_help
|
|
15
|
+
].join("\n")
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def build_column_headers
|
|
19
|
+
"#{Formatter.pad('TIME', Config::TIME_WIDTH)} " \
|
|
20
|
+
"#{Formatter.pad('LEVEL', Config::LEVEL_WIDTH)} " \
|
|
21
|
+
"#{Formatter.pad('SOURCE', Config::SOURCE_WIDTH)} MESSAGE"
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def build_info_line(results, messages)
|
|
25
|
+
query = results['query'] || 'N/A'
|
|
26
|
+
count = messages.size
|
|
27
|
+
sources = messages.map { |m| m['map']['_source'] }.compact.uniq.size
|
|
28
|
+
|
|
29
|
+
"#{count} msgs | #{sources} sources | Query: #{Formatter.truncate(query, 40)}"
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def build_search_tips
|
|
33
|
+
'💡 Simple text search (case-insensitive) - searches all JSON fields and log content'
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def build_keybindings_help
|
|
37
|
+
'Enter=select Tab=view Ctrl-T=toggle-search Ctrl-S=save Ctrl-Y=copy Ctrl-E=export Ctrl-Q=quit'
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Sumologic
|
|
4
|
+
module Interactive
|
|
5
|
+
class FzfViewer
|
|
6
|
+
module SearchableBuilder
|
|
7
|
+
module_function
|
|
8
|
+
|
|
9
|
+
def build_searchable_content(map)
|
|
10
|
+
parts = []
|
|
11
|
+
|
|
12
|
+
add_primary_content(parts, map)
|
|
13
|
+
add_standard_fields(parts, map)
|
|
14
|
+
add_custom_fields(parts, map)
|
|
15
|
+
|
|
16
|
+
parts.compact.join(' ')
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def add_primary_content(parts, map)
|
|
20
|
+
parts << Formatter.sanitize(map['_raw'] || map['message'] || '')
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def add_standard_fields(parts, map)
|
|
24
|
+
Config::SEARCHABLE_FIELDS.each do |field|
|
|
25
|
+
parts << map[field] if map[field]
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def add_custom_fields(parts, map)
|
|
30
|
+
map.each do |key, value|
|
|
31
|
+
next if key.start_with?('_')
|
|
32
|
+
next if value.nil? || value.to_s.empty?
|
|
33
|
+
|
|
34
|
+
parts << "#{key}:#{value}"
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
@@ -4,13 +4,15 @@ require 'json'
|
|
|
4
4
|
require 'tempfile'
|
|
5
5
|
require 'time'
|
|
6
6
|
require 'open3'
|
|
7
|
-
|
|
7
|
+
require_relative 'fzf_viewer/config'
|
|
8
|
+
require_relative 'fzf_viewer/formatter'
|
|
9
|
+
require_relative 'fzf_viewer/searchable_builder'
|
|
10
|
+
require_relative 'fzf_viewer/fzf_config'
|
|
11
|
+
require_relative 'fzf_viewer/header_builder'
|
|
8
12
|
|
|
9
13
|
module Sumologic
|
|
10
14
|
module Interactive
|
|
11
15
|
class FzfViewer
|
|
12
|
-
DELIMITER = '||'
|
|
13
|
-
|
|
14
16
|
def initialize(results)
|
|
15
17
|
@results = results
|
|
16
18
|
@messages = results['messages'] || []
|
|
@@ -31,74 +33,56 @@ module Sumologic
|
|
|
31
33
|
|
|
32
34
|
private
|
|
33
35
|
|
|
36
|
+
# ============================================================
|
|
37
|
+
# Data Preparation
|
|
38
|
+
# ============================================================
|
|
39
|
+
|
|
34
40
|
def prepare_data(input_file, preview_file)
|
|
35
|
-
|
|
41
|
+
write_input_file(input_file)
|
|
42
|
+
write_preview_file(preview_file)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def write_input_file(input_file)
|
|
36
46
|
File.open(input_file, 'w') do |f|
|
|
37
|
-
@messages.each
|
|
38
|
-
f.puts format_line(msg)
|
|
39
|
-
end
|
|
47
|
+
@messages.each { |msg| f.puts format_line(msg) }
|
|
40
48
|
end
|
|
49
|
+
end
|
|
41
50
|
|
|
42
|
-
|
|
51
|
+
def write_preview_file(preview_file)
|
|
43
52
|
File.open(preview_file, 'w') do |f|
|
|
44
|
-
@messages.each
|
|
45
|
-
f.puts JSON.generate(msg['map'])
|
|
46
|
-
end
|
|
53
|
+
@messages.each { |msg| f.puts JSON.generate(msg['map']) }
|
|
47
54
|
end
|
|
48
55
|
end
|
|
49
56
|
|
|
57
|
+
# ============================================================
|
|
58
|
+
# Line Formatting
|
|
59
|
+
# ============================================================
|
|
60
|
+
|
|
50
61
|
def format_line(msg)
|
|
51
62
|
map = msg['map']
|
|
63
|
+
display = build_display_line(map)
|
|
64
|
+
searchable = SearchableBuilder.build_searchable_content(map)
|
|
52
65
|
|
|
53
|
-
|
|
54
|
-
level = format_level(map['level'] || map['severity'] || 'INFO')
|
|
55
|
-
source = truncate(map['_sourceCategory'] || '-', 25)
|
|
56
|
-
message = truncate(sanitize(map['_raw'] || map['message'] || ''), 80)
|
|
57
|
-
|
|
58
|
-
# No index in display - use FZF line number instead
|
|
59
|
-
"#{time} #{level} #{source.ljust(25)} #{message}"
|
|
66
|
+
"#{display}#{' ' * Config::SEARCHABLE_PADDING}#{searchable}"
|
|
60
67
|
end
|
|
61
68
|
|
|
62
|
-
def
|
|
63
|
-
|
|
69
|
+
def build_display_line(map)
|
|
70
|
+
time = Formatter.format_time(map['_messagetime'])
|
|
71
|
+
level = Formatter.format_level(map['level'] || map['severity'] || 'INFO')
|
|
72
|
+
source = Formatter.truncate(map['_source'] || map['_sourcecategory'] || '-', Config::SOURCE_WIDTH)
|
|
73
|
+
message = Formatter.truncate(Formatter.sanitize(map['_raw'] || map['message'] || ''), Config::MESSAGE_PREVIEW_LENGTH)
|
|
64
74
|
|
|
65
|
-
|
|
75
|
+
"#{time} #{level} #{source.ljust(Config::SOURCE_WIDTH)} #{message}"
|
|
66
76
|
end
|
|
67
77
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
case level_str.strip
|
|
72
|
-
when 'ERROR', 'FATAL', 'CRITICAL'
|
|
73
|
-
"\e[31m#{level_str}\e[0m" # Red
|
|
74
|
-
when 'WARN', 'WARNING'
|
|
75
|
-
"\e[33m#{level_str}\e[0m" # Yellow
|
|
76
|
-
when 'INFO'
|
|
77
|
-
"\e[36m#{level_str}\e[0m" # Cyan
|
|
78
|
-
when 'DEBUG', 'TRACE'
|
|
79
|
-
"\e[90m#{level_str}\e[0m" # Gray
|
|
80
|
-
else
|
|
81
|
-
level_str
|
|
82
|
-
end
|
|
83
|
-
end
|
|
84
|
-
|
|
85
|
-
def sanitize(text)
|
|
86
|
-
text.to_s.gsub(/[\n\r\t]/, ' ').squeeze(' ')
|
|
87
|
-
end
|
|
88
|
-
|
|
89
|
-
def truncate(text, length)
|
|
90
|
-
text = text.to_s
|
|
91
|
-
text.length > length ? "#{text[0...(length - 3)]}..." : text
|
|
92
|
-
end
|
|
93
|
-
|
|
94
|
-
def colorize_json(data)
|
|
95
|
-
JSON.pretty_generate(data)
|
|
96
|
-
end
|
|
78
|
+
# ============================================================
|
|
79
|
+
# FZF Execution
|
|
80
|
+
# ============================================================
|
|
97
81
|
|
|
98
82
|
def execute_fzf(input_path, preview_path)
|
|
99
|
-
|
|
83
|
+
header_text = HeaderBuilder.build_header_text(@results, @messages)
|
|
84
|
+
fzf_args = FzfConfig.build_fzf_args(input_path, preview_path, header_text)
|
|
100
85
|
|
|
101
|
-
# Use IO.popen with array to avoid shell escaping issues
|
|
102
86
|
result = IO.popen(fzf_args, 'r+') do |io|
|
|
103
87
|
File.readlines(input_path).each { |line| io.puts line }
|
|
104
88
|
io.close_write
|
|
@@ -108,70 +92,11 @@ module Sumologic
|
|
|
108
92
|
result.strip
|
|
109
93
|
end
|
|
110
94
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
header_text = build_header_text
|
|
115
|
-
|
|
116
|
-
[
|
|
117
|
-
'fzf',
|
|
118
|
-
'--ansi',
|
|
119
|
-
'--multi',
|
|
120
|
-
"--header=#{header_text}",
|
|
121
|
-
"--preview=#{preview_cmd}",
|
|
122
|
-
'--preview-window=right:60%:wrap:follow',
|
|
123
|
-
'--bind=enter:toggle',
|
|
124
|
-
"--bind=tab:execute(#{view_cmd})",
|
|
125
|
-
'--bind=ctrl-a:select-all',
|
|
126
|
-
'--bind=ctrl-d:deselect-all',
|
|
127
|
-
'--bind=ctrl-s:execute-silent(echo {+} > sumo-selected.txt)+abort',
|
|
128
|
-
'--bind=ctrl-y:execute-silent(echo {+} | pbcopy || echo {+} | xclip -selection clipboard 2>/dev/null)+abort',
|
|
129
|
-
'--bind=ctrl-e:execute-silent(echo {+} > sumo-export.jsonl)+abort',
|
|
130
|
-
'--bind=ctrl-/:toggle-preview',
|
|
131
|
-
"--bind=ctrl-r:reload(cat #{input_path})",
|
|
132
|
-
'--bind=ctrl-q:abort',
|
|
133
|
-
'--height=100%'
|
|
134
|
-
]
|
|
135
|
-
end
|
|
136
|
-
|
|
137
|
-
def build_view_command(preview_path)
|
|
138
|
-
# FZF {n} is 0-indexed! Add 1 to get sed line number (1-indexed)
|
|
139
|
-
'LINE=$(({n} + 1)); ' \
|
|
140
|
-
"sed -n \"$LINE\"p #{Shellwords.escape(preview_path)} | jq -C . | less -R"
|
|
141
|
-
end
|
|
142
|
-
|
|
143
|
-
def build_preview_command(preview_path)
|
|
144
|
-
# FZF {n} is 0-indexed! Add 1 to get JSONL line number (1-indexed)
|
|
145
|
-
escaped_path = Shellwords.escape(preview_path)
|
|
146
|
-
calc = "LINE=$(({n} + 1)); TOTAL=$(wc -l < #{escaped_path}); "
|
|
147
|
-
display = 'echo "Message $LINE of $TOTAL"; echo ""; '
|
|
148
|
-
extract = "sed -n \"$LINE\"p #{escaped_path}"
|
|
149
|
-
|
|
150
|
-
calc + display + "#{extract} | jq -C . || #{extract}"
|
|
151
|
-
end
|
|
152
|
-
|
|
153
|
-
def build_header_text
|
|
154
|
-
query = @results['query'] || 'N/A'
|
|
155
|
-
count = @messages.size
|
|
156
|
-
sources = @messages.map { |m| m['map']['_sourceCategory'] }.compact.uniq.size
|
|
157
|
-
|
|
158
|
-
# Column headers
|
|
159
|
-
columns = "#{pad('TIME', 8)} #{pad('LEVEL', 7)} #{pad('SOURCE', 25)} MESSAGE"
|
|
160
|
-
# Info and keys on second line
|
|
161
|
-
info = "#{count} msgs | #{sources} sources | Query: #{truncate(query, 40)}"
|
|
162
|
-
keys = 'Enter=select Tab=view Ctrl-S=save Ctrl-Y=copy Ctrl-E=export Ctrl-Q=quit'
|
|
163
|
-
|
|
164
|
-
"#{columns}\n#{info} | #{keys}"
|
|
165
|
-
end
|
|
166
|
-
|
|
167
|
-
def pad(text, width)
|
|
168
|
-
text.ljust(width)
|
|
169
|
-
end
|
|
95
|
+
# ============================================================
|
|
96
|
+
# Selection Handling
|
|
97
|
+
# ============================================================
|
|
170
98
|
|
|
171
99
|
def handle_selection(selected)
|
|
172
|
-
# Selected contains the actual display lines (no index field)
|
|
173
|
-
# We don't show them since user already saw in FZF
|
|
174
|
-
# The keybindings (Ctrl-S, Ctrl-Y, Ctrl-E) handle the export
|
|
175
100
|
return if selected.empty?
|
|
176
101
|
|
|
177
102
|
puts "\n#{'═' * 80}"
|
data/lib/sumologic/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: sumologic-query
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.3.
|
|
4
|
+
version: 1.3.2
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- patrick204nqh
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2025-11-
|
|
11
|
+
date: 2025-11-16 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: base64
|
|
@@ -104,6 +104,11 @@ files:
|
|
|
104
104
|
- lib/sumologic/http/connection_pool.rb
|
|
105
105
|
- lib/sumologic/interactive.rb
|
|
106
106
|
- lib/sumologic/interactive/fzf_viewer.rb
|
|
107
|
+
- lib/sumologic/interactive/fzf_viewer/config.rb
|
|
108
|
+
- lib/sumologic/interactive/fzf_viewer/formatter.rb
|
|
109
|
+
- lib/sumologic/interactive/fzf_viewer/fzf_config.rb
|
|
110
|
+
- lib/sumologic/interactive/fzf_viewer/header_builder.rb
|
|
111
|
+
- lib/sumologic/interactive/fzf_viewer/searchable_builder.rb
|
|
107
112
|
- lib/sumologic/metadata/collector.rb
|
|
108
113
|
- lib/sumologic/metadata/collector_source_fetcher.rb
|
|
109
114
|
- lib/sumologic/metadata/source.rb
|