onlylogs 0.9.0 → 0.10.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.
@@ -94,7 +94,8 @@ module Onlylogs
94
94
  reader, writer = IO.pipe
95
95
 
96
96
  begin
97
- pid = Process.spawn(*bounded(command_args, timeout), out: writer, err: ::File::NULL, pgroup: true)
97
+ pid = Process.spawn(*deprioritised(bounded(command_args, timeout)),
98
+ out: writer, err: ::File::NULL, pgroup: true)
98
99
  rescue
99
100
  reader.close
100
101
  raise
@@ -116,6 +117,12 @@ module Onlylogs
116
117
  TIMED_OUT_EXIT_STATUS = 124
117
118
  KILL_GRACE_PERIOD = 0.5
118
119
 
120
+ SEARCH_NICENESS = 19
121
+
122
+ def self.deprioritised(command_args)
123
+ ["nice", "-n", SEARCH_NICENESS.to_s, *command_args]
124
+ end
125
+
119
126
  def self.bounded(command_args, timeout)
120
127
  return command_args unless timeout && timeout_command_available?
121
128
 
@@ -161,17 +168,18 @@ module Onlylogs
161
168
  nil
162
169
  end
163
170
 
171
+ MATCH_TIMEOUT = 0.1 # seconds
172
+
164
173
  def self.match_line?(line, string, regexp_mode: false)
165
174
  # Strip ANSI color codes from the line before matching
166
175
  stripped_line = line.gsub(/\e\[[0-9;]*m/, "")
167
176
  # Normalize multiple spaces to single spaces
168
177
  normalized_line = stripped_line.gsub(/\s+/, " ")
169
178
 
170
- if regexp_mode
171
- normalized_line.match?(string)
172
- else
173
- normalized_line.match?(Regexp.escape(string))
174
- end
179
+ pattern = regexp_mode ? string : Regexp.escape(string)
180
+ normalized_line.match?(Regexp.new(pattern, timeout: MATCH_TIMEOUT))
181
+ rescue ::RegexpError
182
+ false
175
183
  end
176
184
  end
177
185
  end
@@ -1,22 +1,42 @@
1
1
  <style>
2
2
  .grid {
3
3
  display: grid;
4
- grid-template-rows: 32px 1fr;
4
+ grid-template-rows: 38px 1fr;
5
5
  height: 100vh;
6
+ font-family: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, Courier, monospace;
7
+ font-size: 0.8rem;
6
8
  }
7
9
 
8
10
  .grid-header {
9
11
  display: flex;
10
12
  align-items: center;
11
- border-bottom: 1px solid lightgray;
13
+ gap: 0.5rem;
14
+ padding: 0 0.65rem;
15
+ background-color: #f3f5f9;
16
+ border-bottom: 1px solid #dde3ec;
17
+ color: #58626f;
12
18
  }
13
19
 
14
- .log-file-dropdown {
15
- margin-left: 0.5rem;
20
+ .log-file-dropdown select {
21
+ font: inherit;
22
+ padding: 0.2rem 0.4rem;
23
+ border: 1px solid #b9c4d3;
24
+ border-radius: 3px;
25
+ background-color: #fff;
26
+ color: #12171f;
16
27
  }
17
28
 
18
29
  .download-link {
19
- margin-left: 0.5rem;
30
+ color: #1a4fc4;
31
+ text-decoration: none;
32
+ padding: 0.2rem 0.45rem;
33
+ border: 1px solid #9dbcf7;
34
+ border-radius: 3px;
35
+ background-color: #e8f0fe;
36
+ }
37
+
38
+ .download-link:hover {
39
+ background-color: #d7e5fd;
20
40
  }
21
41
  </style>
22
42
  <div class="grid">
@@ -28,13 +28,20 @@
28
28
  data-log-streamer-regexp-mode-value="<%= regexp_mode %>"
29
29
  data-log-streamer-mode-value="<%= mode %>"
30
30
  data-log-streamer-file-size-value="<%= file_size %>"
31
- data-action="text-selection:start->log-streamer#pauseForSelection"
31
+ data-action="text-selection:start->log-streamer#pauseForSelection text-selection:search->log-streamer#switchToSearch"
32
32
  class="onlylogs-log-container" >
33
33
  <div data-log-streamer-target="logLines" data-text-selection-target="logLines" id="scrollArea" class="onlylogs-log-lines clusterize-scroll">
34
34
  <div id="contentArea" class="clusterize-content">
35
35
  </div>
36
36
  </div>
37
37
 
38
+ <%# Search with an empty query would stream the entire file, so it is not run
39
+ at all until something is typed. This says so instead of looking idle. %>
40
+ <div class="onlylogs-search-placeholder" data-log-streamer-target="searchPlaceholder" hidden>
41
+ <p class="onlylogs-search-placeholder__title">Nothing to search for yet</p>
42
+ <p class="onlylogs-search-placeholder__hint">Type a query to search this file, or switch to Live to follow it as it is written.</p>
43
+ </div>
44
+
38
45
  <button type="button"
39
46
  data-text-selection-target="button"
40
47
  class="onlylogs-context-menu hidden-button"
@@ -44,32 +51,38 @@
44
51
  </button>
45
52
 
46
53
  <div class="onlylogs-log-toolbar">
47
- <div>
48
- <label class="toolbar-label">
49
- <input id="liveMode" type="checkbox" <%= mode == "live" ? "checked" : "" %> name="liveMode" data-log-streamer-target="liveMode" data-keyboard-shortcuts-target="liveMode" data-action="change->log-streamer#toggleLiveMode">
50
- <u>L</u>ive Mode
51
- </label>
52
- </div>
53
- <div>
54
- <label class="toolbar-label">
55
- <input id="autoscroll" type="checkbox" <%= autoscroll ? "checked" : "" %> name="autoscroll" data-log-streamer-target="autoscroll" data-keyboard-shortcuts-target="autoscroll" data-action="change->log-streamer#toggleAutoScroll">
56
- <u>A</u>utoscroll
57
- </label>
58
- </div>
59
- <div>
60
- <label class="toolbar-label">
61
- <input id="regexpMode" type="checkbox" <%= regexp_mode ? "checked" : "" %> name="regexpMode" data-log-streamer-target="regexpMode" data-text-selection-target="regexpMode" data-action="change->log-streamer#toggleRegexpMode">
62
- <u>R</u>egexp Mode
63
- </label>
54
+ <div class="onlylogs-toolbar-row onlylogs-toolbar-row--controls">
55
+ <div class="onlylogs-mode-switch" role="group" aria-label="Log mode">
56
+ <button type="button"
57
+ class="onlylogs-mode-switch__button"
58
+ data-onlylogs-mode-button="live"
59
+ data-log-streamer-target="liveButton"
60
+ data-keyboard-shortcuts-target="liveButton"
61
+ data-action="click->log-streamer#switchToLive"
62
+ aria-pressed="<%= mode == "live" %>"
63
+ title="Follow the file as it is written (L)">
64
+ <span>▶ <u>L</u>ive</span>
65
+ </button>
66
+ <button type="button"
67
+ class="onlylogs-mode-switch__button"
68
+ data-onlylogs-mode-button="static"
69
+ data-log-streamer-target="searchButton"
70
+ data-keyboard-shortcuts-target="searchButton"
71
+ data-action="click->log-streamer#switchToSearch"
72
+ aria-pressed="<%= mode != "live" %>"
73
+ title="Search what is already in the file (S)">
74
+ <span>🔍 <u>S</u>earch</span>
75
+ </button>
64
76
  </div>
65
77
  <div>
66
78
  <label class="toolbar-label">
67
- Filter:
79
+ <span data-onlylogs-mode="live">Follow:</span>
80
+ <span data-onlylogs-mode="static">Query:</span>
68
81
  <div class="filter-input-wrapper">
69
82
  <input type="text"
70
83
  name="filter"
71
84
  value="<%= filter %>"
72
- placeholder="Enter filter text..."
85
+ placeholder="<%= mode == "live" ? "follow lines matching…" : "search the whole file…" %>"
73
86
  data-log-streamer-target="filterInput"
74
87
  data-text-selection-target="filterInput"
75
88
  data-action="input->log-streamer#applyFilter">
@@ -82,7 +95,28 @@
82
95
  </div>
83
96
  </label>
84
97
  </div>
85
- <div class="range-slider-wrapper">
98
+ <div>
99
+ <label class="toolbar-label">
100
+ <input id="regexpMode" type="checkbox" <%= regexp_mode ? "checked" : "" %> name="regexpMode" data-log-streamer-target="regexpMode" data-text-selection-target="regexpMode" data-action="change->log-streamer#toggleRegexpMode">
101
+ <span><u>R</u>egexp</span>
102
+ </label>
103
+ </div>
104
+ <div data-onlylogs-mode="live">
105
+ <label class="toolbar-label">
106
+ <input id="autoscroll" type="checkbox" <%= autoscroll ? "checked" : "" %> name="autoscroll" data-log-streamer-target="autoscroll" data-keyboard-shortcuts-target="autoscroll" data-action="change->log-streamer#toggleAutoScroll">
107
+ <span><u>A</u>utoscroll</span>
108
+ </label>
109
+ </div>
110
+ <div data-onlylogs-mode="live">
111
+ <button type="button"
112
+ data-log-streamer-target="searchWholeFileButton"
113
+ class="search-whole-file-button"
114
+ data-action="click->log-streamer#searchWholeFile"
115
+ title="Search this text across the whole file">
116
+ Search whole file →
117
+ </button>
118
+ </div>
119
+ <div class="range-slider-wrapper" data-onlylogs-mode="static">
86
120
  <%= render "onlylogs/shared/range_slider",
87
121
  start_name: "start_position",
88
122
  end_name: "end_position",
@@ -105,28 +139,30 @@
105
139
  class="stop-search-button hidden-button"
106
140
  data-action="click->log-streamer#stopSearch"
107
141
  title="Stop current search">
108
- ⏹️ Stop
142
+ Stop
109
143
  </button>
110
144
  </div>
111
- <div>
112
- <span data-log-streamer-target="results" class="results-text">Results: 0</span>
113
- </div>
114
- <div data-log-streamer-target="message"></div>
115
- <% unless Onlylogs.ripgrep_enabled? %>
116
- <span class="grep-warning" title="Search is slow. Install ripgrep for better performance">⚠️</span>
117
- <% end %>
118
- <div class="file-size" title="File size: <%= number_to_human_size(File.size(log_file_path)) %>">
119
- <%= number_to_human_size(File.size(log_file_path)) %>
120
- </div>
121
- <div class="clear-button-wrapper">
145
+ </div>
146
+
147
+ <div class="onlylogs-toolbar-row onlylogs-toolbar-row--status">
148
+ <span data-log-streamer-target="results" class="results-text">Results: 0</span>
149
+ <span data-log-streamer-target="message" class="status-message"></span>
150
+ <div class="onlylogs-toolbar-row__end">
151
+ <% unless Onlylogs.ripgrep_enabled? %>
152
+ <span class="grep-warning" title="Search is slow. Install ripgrep for better performance">⚠️</span>
153
+ <% end %>
154
+ <span class="file-size" title="File size: <%= number_to_human_size(File.size(log_file_path)) %>">
155
+ <%= number_to_human_size(File.size(log_file_path)) %>
156
+ </span>
122
157
  <button type="button"
123
158
  data-log-streamer-target="clearButton"
124
159
  class="clear-logs-button"
125
160
  data-action="click->log-streamer#clearLogs"
161
+ aria-label="Clear all log lines"
126
162
  title="Clear all log lines">
127
- 🗑️ Clear
163
+ 🗑
128
164
  </button>
165
+ <span data-log-streamer-target="websocketStatus" class="websocket-status websocket-status--disconnected" title="WebSocket Disconnected">🔴</span>
129
166
  </div>
130
- <div data-log-streamer-target="websocketStatus" class="websocket-status websocket-status--disconnected" title="WebSocket Disconnected">🔴</div>
131
167
  </div>
132
168
  </div>