onlylogs 0.10.0 → 0.12.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.
Files changed (38) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +270 -69
  3. data/app/assets/images/onlylogs/favicon/apple-touch-icon.png +0 -0
  4. data/app/assets/images/onlylogs/favicon/favicon-96x96.png +0 -0
  5. data/app/assets/images/onlylogs/favicon/favicon.ico +0 -0
  6. data/app/assets/images/onlylogs/favicon/favicon.svg +1 -3
  7. data/app/assets/images/onlylogs/favicon/web-app-manifest-192x192.png +0 -0
  8. data/app/assets/images/onlylogs/favicon/web-app-manifest-512x512.png +0 -0
  9. data/app/assets/images/onlylogs/logo.png +0 -0
  10. data/app/assets/javascripts/onlylogs/clusterize.js +26 -1
  11. data/app/channels/onlylogs/logs_channel.rb +22 -9
  12. data/app/controllers/concerns/onlylogs/authentication.rb +26 -0
  13. data/app/controllers/onlylogs/application_controller.rb +1 -18
  14. data/app/controllers/onlylogs/queries_controller.rb +70 -0
  15. data/app/controllers/onlylogs/viewer_controller.rb +7 -0
  16. data/app/javascript/onlylogs/controllers/keyboard_shortcuts_controller.js +131 -21
  17. data/app/javascript/onlylogs/controllers/log_cursor_controller.js +286 -0
  18. data/app/javascript/onlylogs/controllers/log_streamer_controller.js +52 -6
  19. data/app/javascript/onlylogs/controllers/match_highlight_controller.js +82 -0
  20. data/app/javascript/onlylogs/controllers/queries_controller.js +470 -0
  21. data/app/javascript/onlylogs/controllers/range_slider_controller.js +5 -4
  22. data/app/javascript/onlylogs/controllers/text_selection_controller.js +136 -15
  23. data/app/models/onlylogs/file.rb +27 -7
  24. data/app/models/onlylogs/grep.rb +13 -6
  25. data/app/models/onlylogs/queries_record.rb +28 -0
  26. data/app/models/onlylogs/query.rb +29 -0
  27. data/app/views/onlylogs/logs/index.html.erb +1 -1
  28. data/app/views/onlylogs/shared/_log_container.html.erb +179 -30
  29. data/app/views/onlylogs/shared/_log_container_styles.html.erb +731 -45
  30. data/app/views/onlylogs/shared/_shortcuts_dialog.html.erb +62 -0
  31. data/bin/super_grep +7 -0
  32. data/bin/super_ripgrep +5 -0
  33. data/config/routes.rb +1 -0
  34. data/lib/onlylogs/configuration.rb +14 -1
  35. data/lib/onlylogs/http_device.rb +28 -13
  36. data/lib/onlylogs/version.rb +1 -1
  37. data/lib/onlylogs.rb +4 -0
  38. metadata +38 -1
@@ -1,4 +1,4 @@
1
- <%# locals: (log_file_path:) %>
1
+ <%# locals: (log_file_path:, live_enabled: true) %>
2
2
  <script nonce="<%= request.content_security_policy_nonce %>"
3
3
  src="<%= asset_path('onlylogs/clusterize.js') %>"></script>
4
4
  <link rel="stylesheet" href="<%= asset_path('onlylogs/clusterize.css') %>" />
@@ -8,7 +8,10 @@
8
8
  autoscroll = local_assigns[:autoscroll] != false
9
9
  filter = local_assigns[:filter] || ""
10
10
  regexp_mode = local_assigns[:regexp_mode] == true
11
- mode = local_assigns[:mode] || "live"
11
+ case_insensitive = local_assigns[:case_insensitive] == true
12
+ # A file nothing writes to any more (no drain attached, a rotated log) has nothing
13
+ # to follow, so live mode is not offered at all and the viewer opens in search.
14
+ mode = live_enabled ? (local_assigns[:mode] || "live") : "static"
12
15
  file_size = File.size(log_file_path)
13
16
  start_position = local_assigns[:start_position]
14
17
  end_position = local_assigns[:end_position]
@@ -17,20 +20,35 @@
17
20
  raise SecurityError, "File path not allowed" unless Onlylogs.file_path_permitted?(log_file_path)
18
21
 
19
22
  encrypted_log_file_path = Onlylogs::SecureFilePath.encrypt(log_file_path)
23
+
24
+ shortcut_modifier = request.user_agent.to_s.match?(/Macintosh|iPhone|iPad/) ? "control" : "alt"
25
+ shortcut_modifier_label = shortcut_modifier == "control" ? "⌃ Control" : "Alt"
20
26
  %>
21
27
 
22
- <div data-controller="log-streamer text-selection keyboard-shortcuts"
28
+ <div data-controller="log-streamer text-selection keyboard-shortcuts log-cursor match-highlight queries"
29
+ data-queries-log-file-path-value="<%= encrypted_log_file_path %>"
30
+ data-queries-endpoint-value="<%= onlylogs.queries_path %>"
31
+ data-keyboard-shortcuts-modifier-value="<%= shortcut_modifier %>"
23
32
  data-log-streamer-file-path-value="<%= encrypted_log_file_path %>"
24
33
  <% if start_position.present? %>data-log-streamer-start-position-value="<%= start_position %>"<% end %>
25
34
  <% if end_position.present? %>data-log-streamer-end-position-value="<%= end_position %>"<% end %>
26
35
  data-log-streamer-filter-value="<%= filter %>"
27
36
  data-log-streamer-auto-scroll-value="<%= autoscroll %>"
28
37
  data-log-streamer-regexp-mode-value="<%= regexp_mode %>"
38
+ data-log-streamer-case-insensitive-value="<%= case_insensitive %>"
29
39
  data-log-streamer-mode-value="<%= mode %>"
40
+ data-log-streamer-live-enabled-value="<%= live_enabled %>"
30
41
  data-log-streamer-file-size-value="<%= file_size %>"
31
- data-action="text-selection:start->log-streamer#pauseForSelection text-selection:search->log-streamer#switchToSearch"
42
+ data-action="text-selection:start->log-streamer#pauseForSelection text-selection:search->log-streamer#switchToSearch
43
+ log-streamer:cleared->log-cursor#reset log-streamer:cleared->text-selection#forgetSelection log-streamer:context-line->log-cursor#placeAt
44
+ log-streamer:rendering->text-selection#saveSelection log-streamer:rendered->text-selection#restoreSelection
45
+ log-cursor:move->log-streamer#pauseForSelection log-cursor:select->text-selection#showForSelection
46
+ log-cursor:search->text-selection#searchSelectedText keyboard-shortcuts:lines->log-cursor#focus
47
+ log-streamer:context-line->queries#cleared"
32
48
  class="onlylogs-log-container" >
33
- <div data-log-streamer-target="logLines" data-text-selection-target="logLines" id="scrollArea" class="onlylogs-log-lines clusterize-scroll">
49
+ <div data-log-streamer-target="logLines" data-text-selection-target="logLines" data-log-cursor-target="lines" data-match-highlight-target="lines"
50
+ data-action="keydown->log-cursor#navigate mousedown->log-cursor#pointAt focusin->log-cursor#activate"
51
+ id="scrollArea" class="onlylogs-log-lines clusterize-scroll">
34
52
  <div id="contentArea" class="clusterize-content">
35
53
  </div>
36
54
  </div>
@@ -39,84 +57,203 @@
39
57
  at all until something is typed. This says so instead of looking idle. %>
40
58
  <div class="onlylogs-search-placeholder" data-log-streamer-target="searchPlaceholder" hidden>
41
59
  <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>
60
+ <p class="onlylogs-search-placeholder__hint">
61
+ <% if live_enabled %>
62
+ Type a query to search this file, or switch to Live to follow it as it is written.
63
+ <% else %>
64
+ Type a query to search this file.
65
+ <% end %>
66
+ </p>
43
67
  </div>
44
68
 
45
69
  <button type="button"
46
70
  data-text-selection-target="button"
47
71
  class="onlylogs-context-menu hidden-button"
48
72
  data-action="click->text-selection#searchSelectedText"
49
- title="Search selected text">
50
- 🔍 Search
73
+ title="Search selected text (Enter)">
74
+ 🔍 Search <kbd>↵</kbd>
51
75
  </button>
52
76
 
53
77
  <div class="onlylogs-log-toolbar">
54
78
  <div class="onlylogs-toolbar-row onlylogs-toolbar-row--controls">
79
+ <% if live_enabled %>
55
80
  <div class="onlylogs-mode-switch" role="group" aria-label="Log mode">
56
81
  <button type="button"
57
82
  class="onlylogs-mode-switch__button"
58
83
  data-onlylogs-mode-button="live"
84
+ data-onlylogs-shortcut="l"
59
85
  data-log-streamer-target="liveButton"
60
- data-keyboard-shortcuts-target="liveButton"
61
86
  data-action="click->log-streamer#switchToLive"
62
87
  aria-pressed="<%= mode == "live" %>"
63
- title="Follow the file as it is written (L)">
64
- <span>▶ <u>L</u>ive</span>
88
+ title="Follow the file as it is written">
89
+ <span>▶ Live</span>
65
90
  </button>
66
91
  <button type="button"
67
92
  class="onlylogs-mode-switch__button"
68
93
  data-onlylogs-mode-button="static"
94
+ data-onlylogs-shortcut="s"
69
95
  data-log-streamer-target="searchButton"
70
- data-keyboard-shortcuts-target="searchButton"
71
96
  data-action="click->log-streamer#switchToSearch"
72
97
  aria-pressed="<%= mode != "live" %>"
73
- title="Search what is already in the file (S)">
74
- <span>🔍 <u>S</u>earch</span>
98
+ title="Search what is already in the file">
99
+ <span>🔍 Search</span>
75
100
  </button>
76
101
  </div>
77
- <div>
78
- <label class="toolbar-label">
102
+ <% end %>
103
+ <div class="onlylogs-query">
104
+ <label class="toolbar-label" for="onlylogs-filter">
79
105
  <span data-onlylogs-mode="live">Follow:</span>
80
106
  <span data-onlylogs-mode="static">Query:</span>
81
- <div class="filter-input-wrapper">
107
+ </label>
108
+ <div class="saved-searches" data-queries-target="box" data-action="keydown->queries#navigate">
109
+ <div class="filter-input-wrapper" data-onlylogs-shortcut="q">
82
110
  <input type="text"
111
+ id="onlylogs-filter"
83
112
  name="filter"
84
113
  value="<%= filter %>"
85
114
  placeholder="<%= mode == "live" ? "follow lines matching…" : "search the whole file…" %>"
115
+ spellcheck="false"
116
+ autocomplete="off"
86
117
  data-log-streamer-target="filterInput"
87
118
  data-text-selection-target="filterInput"
88
- data-action="input->log-streamer#applyFilter">
119
+ data-keyboard-shortcuts-target="filterInput"
120
+ data-match-highlight-target="filterInput"
121
+ data-action="input->log-streamer#applyFilter input->queries#refresh keydown.enter->log-cursor#focus keydown.esc->log-cursor#focus">
122
+ <button type="button"
123
+ data-action="click->log-streamer#clearFilter click->queries#cleared"
124
+ data-onlylogs-shortcut="x"
125
+ class="filter-input-button clear-filter-button"
126
+ aria-label="Clear the query"
127
+ title="Clear the query">
128
+ <svg width="10" height="10" viewBox="0 0 10 10" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" aria-hidden="true"><path d="M2 2l6 6M8 2l-6 6"/></svg>
129
+ </button>
130
+ <button type="button"
131
+ class="filter-input-button saved-searches__toggle"
132
+ data-queries-target="toggle"
133
+ data-onlylogs-shortcut="v"
134
+ data-action="queries#toggle"
135
+ aria-haspopup="true"
136
+ aria-expanded="false"
137
+ aria-controls="onlylogs-saved-searches-panel"
138
+ aria-label="Saved searches"
139
+ title="Saved searches">
140
+ <svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" aria-hidden="true"><path d="M9 6h11M9 12h11M9 18h11"/><circle cx="4.5" cy="6" r="1" fill="currentColor"/><circle cx="4.5" cy="12" r="1" fill="currentColor"/><circle cx="4.5" cy="18" r="1" fill="currentColor"/></svg>
141
+ <span class="saved-searches__count" data-queries-target="count"></span>
142
+ </button>
89
143
  <button type="button"
90
- data-action="click->log-streamer#clearFilter"
91
- class="clear-filter-button"
92
- title="Clear filter">
93
- ×
144
+ class="filter-input-button saved-searches__star"
145
+ data-queries-target="star"
146
+ data-onlylogs-shortcut="f"
147
+ data-action="queries#toggleSave"
148
+ aria-haspopup="true"
149
+ aria-expanded="false"
150
+ aria-controls="onlylogs-save-search-panel"
151
+ disabled>
152
+ <svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linejoin="round" aria-hidden="true"><path d="M12 3.5l2.6 5.3 5.9.9-4.3 4.1 1 5.8L12 16.9l-5.2 2.7 1-5.8-4.3-4.1 5.9-.9z"/></svg>
94
153
  </button>
95
154
  </div>
155
+
156
+ <div id="onlylogs-saved-searches-panel" class="saved-searches__panel" data-queries-target="panel" hidden>
157
+ <div class="saved-searches__header">
158
+ <span class="saved-searches__title">Saved searches</span>
159
+ <span class="saved-searches__keys">↑↓ move · ↵ apply · esc</span>
160
+ </div>
161
+ <ul class="saved-searches__list" data-queries-target="list" aria-label="Saved searches"></ul>
162
+ <div class="saved-searches__empty" data-queries-target="empty" hidden>
163
+ <strong>No saved searches yet</strong>
164
+ <span>Type a pattern, then click the star in the field to keep it here.</span>
165
+ </div>
166
+ <div class="saved-searches__footer" data-queries-target="footer" hidden>
167
+ <button type="button" class="saved-searches__footer-button" data-action="queries#openSave">
168
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linejoin="round" aria-hidden="true"><path d="M12 3.5l2.6 5.3 5.9.9-4.3 4.1 1 5.8L12 16.9l-5.2 2.7 1-5.8-4.3-4.1 5.9-.9z"/></svg>
169
+ <span data-queries-target="footerLabel"></span>
170
+ </button>
171
+ </div>
172
+ </div>
173
+
174
+ <div id="onlylogs-save-search-panel" class="saved-searches__panel saved-searches__panel--save" data-queries-target="savePanel" hidden>
175
+ <div class="save-search__summary">
176
+ <span class="save-search__title" data-queries-target="saveTitle"></span>
177
+ <div class="save-search__current">
178
+ <code data-queries-target="saveFilter"></code>
179
+ <span data-queries-target="saveFlags"></span>
180
+ </div>
181
+ </div>
182
+ <form class="save-search__form" data-action="queries#save">
183
+ <label for="onlylogs-save-search-name">Name</label>
184
+ <div class="save-search__row">
185
+ <input type="text"
186
+ id="onlylogs-save-search-name"
187
+ maxlength="255"
188
+ placeholder="e.g. Errors"
189
+ autocomplete="off"
190
+ data-queries-target="name"
191
+ data-action="input->queries#nameChanged">
192
+ <button type="submit" data-queries-target="saveButton" disabled>Save</button>
193
+ </div>
194
+ <p class="save-search__hint" data-queries-target="hint"></p>
195
+ </form>
196
+ <div class="save-search__saved" data-queries-target="savedRow" hidden>
197
+ <span>Already in your saved searches.</span>
198
+ <button type="button" data-action="queries#removeCurrent">Remove</button>
199
+ </div>
200
+ </div>
201
+
202
+ <template data-queries-target="rowTemplate">
203
+ <li class="saved-search">
204
+ <button type="button" class="saved-search__apply" data-action="queries#apply">
205
+ <span class="saved-search__line">
206
+ <span class="saved-search__name"></span>
207
+ <svg class="saved-search__check" width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round" aria-label="Current search"><path d="M5 12.5l4.5 4.5L19 7.5"/></svg>
208
+ <span class="saved-search__edited">edited</span>
209
+ </span>
210
+ <span class="saved-search__line">
211
+ <code class="saved-search__filter"></code>
212
+ <span class="saved-search__flags"></span>
213
+ </span>
214
+ </button>
215
+ <input type="text" class="saved-search__rename-input" maxlength="255" autocomplete="off" hidden
216
+ data-action="keydown->queries#renameKey blur->queries#commitRename">
217
+ <button type="button" class="saved-search__action saved-search__rename" data-action="queries#rename">
218
+ <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M4 20h4L19 9l-4-4L4 16z"/></svg>
219
+ </button>
220
+ <button type="button" class="saved-search__action saved-search__delete" data-action="queries#destroy">
221
+ <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M4 7h16M9 7V5h6v2M6 7l1 13h10l1-13"/></svg>
222
+ <span class="saved-search__delete-label">Delete?</span>
223
+ </button>
224
+ </li>
225
+ </template>
226
+ </div>
227
+ </div>
228
+ <div>
229
+ <label class="toolbar-label" data-onlylogs-shortcut="r">
230
+ <input id="regexpMode" type="checkbox" <%= regexp_mode ? "checked" : "" %> name="regexpMode" data-log-streamer-target="regexpMode" data-text-selection-target="regexpMode" data-match-highlight-target="regexpMode" data-action="change->log-streamer#toggleRegexpMode change->queries#refresh">
231
+ <span>Regexp</span>
96
232
  </label>
97
233
  </div>
98
234
  <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>
235
+ <label class="toolbar-label" data-onlylogs-shortcut="i">
236
+ <input id="caseInsensitive" type="checkbox" <%= case_insensitive ? "checked" : "" %> name="caseInsensitive" data-log-streamer-target="caseInsensitive" data-match-highlight-target="caseInsensitive" data-action="change->log-streamer#toggleCaseInsensitive change->queries#refresh">
237
+ <span>Ignore case</span>
102
238
  </label>
103
239
  </div>
104
240
  <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>
241
+ <label class="toolbar-label" data-onlylogs-shortcut="a">
242
+ <input id="autoscroll" type="checkbox" <%= autoscroll ? "checked" : "" %> name="autoscroll" data-log-streamer-target="autoscroll" data-action="change->log-streamer#toggleAutoScroll">
243
+ <span>Autoscroll</span>
108
244
  </label>
109
245
  </div>
110
246
  <div data-onlylogs-mode="live">
111
247
  <button type="button"
112
248
  data-log-streamer-target="searchWholeFileButton"
113
249
  class="search-whole-file-button"
250
+ data-onlylogs-shortcut="w"
114
251
  data-action="click->log-streamer#searchWholeFile"
115
252
  title="Search this text across the whole file">
116
253
  Search whole file →
117
254
  </button>
118
255
  </div>
119
- <div class="range-slider-wrapper" data-onlylogs-mode="static">
256
+ <div class="range-slider-wrapper" data-onlylogs-mode="static" data-onlylogs-shortcut="g">
120
257
  <%= render "onlylogs/shared/range_slider",
121
258
  start_name: "start_position",
122
259
  end_name: "end_position",
@@ -129,6 +266,7 @@
129
266
  <button type="button"
130
267
  data-action="click->log-streamer#resetRange"
131
268
  class="reset-range-button"
269
+ data-onlylogs-shortcut="0"
132
270
  title="Reset to full file">
133
271
  ↻
134
272
  </button>
@@ -137,6 +275,7 @@
137
275
  <button type="button"
138
276
  data-log-streamer-target="stopButton"
139
277
  class="stop-search-button hidden-button"
278
+ data-onlylogs-shortcut="."
140
279
  data-action="click->log-streamer#stopSearch"
141
280
  title="Stop current search">
142
281
  ■ Stop
@@ -145,7 +284,8 @@
145
284
  </div>
146
285
 
147
286
  <div class="onlylogs-toolbar-row onlylogs-toolbar-row--status">
148
- <span data-log-streamer-target="results" class="results-text">Results: 0</span>
287
+ <span data-log-streamer-target="results" class="results-text" data-onlylogs-shortcut="j"
288
+ data-action="click->log-cursor#focus">Results: 0</span>
149
289
  <span data-log-streamer-target="message" class="status-message"></span>
150
290
  <div class="onlylogs-toolbar-row__end">
151
291
  <% unless Onlylogs.ripgrep_enabled? %>
@@ -157,12 +297,21 @@
157
297
  <button type="button"
158
298
  data-log-streamer-target="clearButton"
159
299
  class="clear-logs-button"
300
+ data-onlylogs-shortcut="c"
160
301
  data-action="click->log-streamer#clearLogs"
161
302
  aria-label="Clear all log lines"
162
303
  title="Clear all log lines">
163
304
  🗑
164
305
  </button>
306
+ <button type="button"
307
+ class="shortcuts-button"
308
+ data-action="keyboard-shortcuts#openHelp"
309
+ aria-label="Keyboard shortcuts"
310
+ title="Keyboard shortcuts (?) · hold <%= shortcut_modifier_label %> to see the letters">
311
+ ⌨
312
+ </button>
165
313
  <span data-log-streamer-target="websocketStatus" class="websocket-status websocket-status--disconnected" title="WebSocket Disconnected">🔴</span>
314
+ <%= render "onlylogs/shared/shortcuts_dialog", modifier_label: shortcut_modifier_label %>
166
315
  </div>
167
316
  </div>
168
317
  </div>