onlylogs 0.1.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.
Files changed (54) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +311 -0
  3. data/Rakefile +8 -0
  4. data/app/assets/config/onlylogs_manifest.js +2 -0
  5. data/app/assets/images/onlylogs/favicon/apple-touch-icon.png +0 -0
  6. data/app/assets/images/onlylogs/favicon/favicon-96x96.png +0 -0
  7. data/app/assets/images/onlylogs/favicon/favicon.ico +0 -0
  8. data/app/assets/images/onlylogs/favicon/favicon.svg +3 -0
  9. data/app/assets/images/onlylogs/favicon/site.webmanifest.erb +21 -0
  10. data/app/assets/images/onlylogs/favicon/web-app-manifest-192x192.png +0 -0
  11. data/app/assets/images/onlylogs/favicon/web-app-manifest-512x512.png +0 -0
  12. data/app/assets/images/onlylogs/logo.png +0 -0
  13. data/app/channels/onlylogs/application_cable/channel.rb +11 -0
  14. data/app/channels/onlylogs/logs_channel.rb +181 -0
  15. data/app/controllers/onlylogs/application_controller.rb +22 -0
  16. data/app/controllers/onlylogs/logs_controller.rb +23 -0
  17. data/app/helpers/onlylogs/application_helper.rb +4 -0
  18. data/app/javascript/onlylogs/application.js +1 -0
  19. data/app/javascript/onlylogs/controllers/application.js +9 -0
  20. data/app/javascript/onlylogs/controllers/index.js +11 -0
  21. data/app/javascript/onlylogs/controllers/keyboard_shortcuts_controller.js +46 -0
  22. data/app/javascript/onlylogs/controllers/log_streamer_controller.js +432 -0
  23. data/app/javascript/onlylogs/controllers/text_selection_controller.js +90 -0
  24. data/app/jobs/onlylogs/application_job.rb +4 -0
  25. data/app/models/onlylogs/ansi_color_parser.rb +78 -0
  26. data/app/models/onlylogs/application_record.rb +5 -0
  27. data/app/models/onlylogs/batch_sender.rb +61 -0
  28. data/app/models/onlylogs/file.rb +151 -0
  29. data/app/models/onlylogs/file_path_parser.rb +118 -0
  30. data/app/models/onlylogs/grep.rb +54 -0
  31. data/app/models/onlylogs/log_line.rb +24 -0
  32. data/app/models/onlylogs/secure_file_path.rb +31 -0
  33. data/app/views/home/show.html.erb +10 -0
  34. data/app/views/layouts/onlylogs/application.html.erb +27 -0
  35. data/app/views/onlylogs/logs/index.html.erb +49 -0
  36. data/app/views/onlylogs/shared/_log_container.html.erb +106 -0
  37. data/app/views/onlylogs/shared/_log_container_styles.html.erb +228 -0
  38. data/config/importmap.rb +6 -0
  39. data/config/puma_plugins/vector.rb +94 -0
  40. data/config/routes.rb +4 -0
  41. data/config/udp_logger.rb +40 -0
  42. data/config/vector.toml +32 -0
  43. data/db/migrate/20250902112548_create_books.rb +9 -0
  44. data/lib/onlylogs/configuration.rb +133 -0
  45. data/lib/onlylogs/engine.rb +39 -0
  46. data/lib/onlylogs/formatter.rb +14 -0
  47. data/lib/onlylogs/log_silencer_middleware.rb +26 -0
  48. data/lib/onlylogs/logger.rb +10 -0
  49. data/lib/onlylogs/socket_logger.rb +71 -0
  50. data/lib/onlylogs/version.rb +3 -0
  51. data/lib/onlylogs.rb +17 -0
  52. data/lib/puma/plugin/onlylogs_sidecar.rb +113 -0
  53. data/lib/tasks/onlylogs_tasks.rake +4 -0
  54. metadata +110 -0
@@ -0,0 +1,106 @@
1
+ <%# locals: (log_file_path:, tail: 100, filter: "", autoscroll: true) %>
2
+ <script src="https://cdn.jsdelivr.net/npm/clusterize.js@0.18.1/clusterize.min.js"></script>
3
+ <%= render "onlylogs/shared/log_container_styles" %>
4
+
5
+ <%
6
+ mode = filter.blank? ? "live" : "search"
7
+ cursor_position = mode == "search" ? 0 : [File.size(log_file_path) - (tail * 100), 0].max
8
+
9
+ raise SecurityError, "File path not allowed" unless Onlylogs.allowed_file_path?(log_file_path)
10
+
11
+ encrypted_log_file_path = Onlylogs::SecureFilePath.encrypt(log_file_path)
12
+ %>
13
+
14
+ <div data-controller="log-streamer text-selection keyboard-shortcuts"
15
+ data-log-streamer-file-path-value="<%= encrypted_log_file_path %>"
16
+ data-log-streamer-cursor-position-value="<%= cursor_position %>"
17
+ data-log-streamer-filter-value="<%= filter %>"
18
+ data-log-streamer-auto-scroll-value="<%= autoscroll %>"
19
+ data-log-streamer-mode-value="<%= mode %>"
20
+ class="onlylogs-log-container" >
21
+ <div data-log-streamer-target="logLines" data-text-selection-target="logLines" id="scrollArea" class="onlylogs-log-lines clusterize-scroll">
22
+ <div id="contentArea" class="clusterize-content">
23
+ </div>
24
+ </div>
25
+
26
+ <button type="button"
27
+ data-text-selection-target="button"
28
+ class="onlylogs-context-menu"
29
+ data-action="click->text-selection#searchSelectedText"
30
+ title="Search selected text"
31
+ style="display: none;">
32
+ 🔍 Search
33
+ </button>
34
+
35
+ <div class="onlylogs-log-toolbar">
36
+ <div>
37
+ <label style="margin-bottom: 0;">
38
+ <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">
39
+ <u>L</u>ive Mode
40
+ </label>
41
+ </div>
42
+ <div>
43
+ <label style="margin-bottom: 0;">
44
+ <input id="autoscroll" type="checkbox" <%= autoscroll ? "checked" : "" %> name="autoscroll" data-keyboard-shortcuts-target="autoscroll" data-action="change->log-streamer#toggleAutoScroll">
45
+ <u>A</u>utoscroll
46
+ </label>
47
+ </div>
48
+ <div>
49
+ <label style="margin-bottom: 0;">
50
+ <input id="regexpMode" type="checkbox" name="regexpMode" data-log-streamer-target="regexpMode" data-text-selection-target="regexpMode" data-action="change->log-streamer#toggleRegexpMode">
51
+ <u>R</u>egexp Mode
52
+ </label>
53
+ </div>
54
+ <div>
55
+ <label style="margin-bottom: 0;">
56
+ Filter:
57
+ <div style="display: inline-flex; align-items: center; position: relative;">
58
+ <input type="text"
59
+ name="filter"
60
+ value="<%= filter %>"
61
+ placeholder="Enter filter text..."
62
+ data-log-streamer-target="filterInput"
63
+ data-text-selection-target="filterInput"
64
+ data-action="input->log-streamer#applyFilter"
65
+ style="padding-right: 1.5rem;">
66
+ <button type="button"
67
+ data-action="click->log-streamer#clearFilter"
68
+ class="clear-filter-button"
69
+ title="Clear filter">
70
+ ×
71
+ </button>
72
+ </div>
73
+ </label>
74
+ </div>
75
+ <div>
76
+ <button type="button"
77
+ data-log-streamer-target="stopButton"
78
+ data-action="click->log-streamer#stopSearch"
79
+ class="stop-search-button"
80
+ title="Stop current search"
81
+ style="display: none;">
82
+ ⏹️ Stop
83
+ </button>
84
+ </div>
85
+ <div>
86
+ <span data-log-streamer-target="lineRange" style="color: #666;">No lines</span>
87
+ </div>
88
+ <div data-log-streamer-target="message"></div>
89
+ <% unless Onlylogs.ripgrep_enabled? %>
90
+ <span class="grep-warning" title="Search is slow. Install ripgrep for better performance">⚠️</span>
91
+ <% end %>
92
+ <div class="file-size" title="File size: <%= number_to_human_size(File.size(log_file_path)) %>">
93
+ <%= number_to_human_size(File.size(log_file_path)) %>
94
+ </div>
95
+ <div style="width:67px">
96
+ <button type="button"
97
+ data-log-streamer-target="clearButton"
98
+ data-action="click->log-streamer#clearLogs"
99
+ class="clear-logs-button"
100
+ title="Clear all log lines">
101
+ 🗑️ Clear
102
+ </button>
103
+ </div>
104
+ <div data-log-streamer-target="websocketStatus" class="websocket-status websocket-status--disconnected" title="WebSocket Disconnected">🔴</div>
105
+ </div>
106
+ </div>
@@ -0,0 +1,228 @@
1
+ <style>
2
+ .onlylogs-log-container {
3
+ background-color: var(--onlylogs-bg-color, none);
4
+ width: var(--onlylogs-width, 100%);
5
+ height: var(--onlylogs-height, 100%); /* Take full height of parent container */
6
+ min-height: var(--onlylogs-min-height, 400px);
7
+ display: grid;
8
+ grid-template-rows: auto var(--onlylogs-toolbar-height, 42px);
9
+
10
+ font-family: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, Courier, monospace;
11
+ font-size: var(--onlylogs-font-size, 0.8rem);
12
+ margin: 0;
13
+ border: none;
14
+ border-radius: 0;
15
+ box-sizing: border-box;
16
+ position: relative;
17
+
18
+ .onlylogs-log-lines {
19
+ overflow-y: auto;
20
+ overflow-x: auto;
21
+
22
+ pre {
23
+ overflow: visible;
24
+ }
25
+ }
26
+
27
+ .clusterize-content {
28
+ outline: 0;
29
+ }
30
+
31
+ .line-number {
32
+ color: #aaa;
33
+ user-select: none;
34
+ margin-right: 0.5em;
35
+ }
36
+
37
+ .color-success {
38
+ color: green;
39
+ }
40
+
41
+ .color-error {
42
+ color: red;
43
+ }
44
+
45
+ .fw-bold {
46
+ font-weight: 600;
47
+ }
48
+
49
+ .log-black {
50
+ color: lch(18% 0 0);
51
+ }
52
+
53
+ .log-red {
54
+ color: lch(55% 60 29);
55
+ }
56
+
57
+ .log-green {
58
+ color: lch(70% 45 135);
59
+ }
60
+
61
+ .log-yellow {
62
+ color: lch(85% 30 100);
63
+ }
64
+
65
+ .log-blue {
66
+ color: lch(65% 45 260);
67
+ }
68
+
69
+ .log-magenta {
70
+ color: lch(65% 40 320);
71
+ }
72
+
73
+ .log-cyan {
74
+ color: lch(75% 30 200);
75
+ }
76
+
77
+ .log-white {
78
+ color: lch(95% 2 0);
79
+ }
80
+
81
+ pre {
82
+ margin: 0 !important;
83
+ padding: 0.2rem;
84
+ word-break: break-word; /* allow breaking long tokens like UUIDs/SQL */
85
+ }
86
+
87
+ a {
88
+ color: inherit;
89
+ text-decoration: underline;
90
+ }
91
+
92
+ .onlylogs-context-menu {
93
+ position: absolute;
94
+ z-index: 1000;
95
+ background: #e3f2fd;
96
+ border: 1px solid #90caf9;
97
+ border-radius: 6px;
98
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
99
+ padding: 6px 12px;
100
+ font-size: 0.9em;
101
+ cursor: pointer;
102
+ transition: background-color 0.2s;
103
+ color: #1976d2;
104
+ font-weight: 500;
105
+ display: flex;
106
+ align-items: center;
107
+ gap: 4px;
108
+
109
+ &:active {
110
+ background-color: #90caf9;
111
+ }
112
+
113
+ &:hover {
114
+ background-color: #bbdefb;
115
+ }
116
+ }
117
+
118
+ @keyframes spin {
119
+ from { transform: rotate(0deg); }
120
+ to { transform: rotate(360deg); }
121
+ }
122
+
123
+ .onlylogs-log-toolbar {
124
+ flex-shrink: 0;
125
+ padding: 1rem 0.5rem;
126
+ display: flex;
127
+ align-items: center;
128
+ gap: 1.5rem;
129
+
130
+ .live-mode-sticky {
131
+ opacity: 0.7;
132
+ cursor: not-allowed;
133
+
134
+ input[type="checkbox"] {
135
+ cursor: not-allowed;
136
+ }
137
+ }
138
+
139
+ .clear-filter-button {
140
+ position: absolute;
141
+ right: 0.25rem;
142
+ background: none;
143
+ border: none;
144
+ color: #666;
145
+ cursor: pointer;
146
+ font-size: 0.8rem;
147
+ padding: 0;
148
+ width: 1rem;
149
+ height: 1rem;
150
+ display: flex;
151
+ align-items: center;
152
+ justify-content: center;
153
+ padding: 0.25rem;
154
+ }
155
+
156
+ .onlylogs-spin-animation {
157
+ display: inline-block;
158
+ animation: spin 1s linear infinite;
159
+ margin-right: 0.5em;
160
+ }
161
+
162
+ .grep-warning {
163
+ color: #f59e0b;
164
+ font-size: 1.2em;
165
+ cursor: help;
166
+ margin-left: auto;
167
+ }
168
+
169
+ .file-size {
170
+ color: #666;
171
+ cursor: help;
172
+ margin-left: auto;
173
+ }
174
+
175
+ .websocket-status {
176
+ cursor: help;
177
+ border-radius: 50%;
178
+ background-color: rgba(255, 255, 255, 0.1);
179
+
180
+ &--connected {
181
+ background-color: rgba(34, 197, 94, 0.2);
182
+ animation: pulse 2s infinite;
183
+ }
184
+
185
+ &--disconnected {
186
+ background-color: rgba(239, 68, 68, 0.2);
187
+ animation: pulse 2s infinite;
188
+ }
189
+
190
+ &--rejected {
191
+ background-color: rgba(245, 158, 11, 0.2);
192
+ animation: pulse 2s infinite;
193
+ }
194
+ }
195
+
196
+ @keyframes pulse {
197
+ 0%, 100% { opacity: 1; }
198
+ 50% { opacity: 0.6; }
199
+ }
200
+
201
+ .stop-search-button {
202
+ background-color: #dc2626;
203
+ color: white;
204
+ border: none;
205
+ padding: 0.25rem 0.5rem;
206
+ border-radius: 0.25rem;
207
+ transition: background-color 0.2s;
208
+
209
+ &:hover {
210
+ background-color: #b91c1c;
211
+ }
212
+
213
+ &:active {
214
+ background-color: #991b1b;
215
+ }
216
+ }
217
+
218
+ .error-message {
219
+ color: #dc2626;
220
+ font-weight: 600;
221
+ padding: 0.5rem;
222
+ background-color: rgba(220, 38, 38, 0.1);
223
+ border-radius: 0.25rem;
224
+ display: inline-block;
225
+ }
226
+ }
227
+ }
228
+ </style>
@@ -0,0 +1,6 @@
1
+ pin "application", to: "onlylogs/application.js", preload: true
2
+ pin "@rails/actioncable", to: "actioncable.esm.js"
3
+ pin "@hotwired/stimulus", to: "stimulus.min.js", preload: true
4
+ pin "@hotwired/stimulus-loading", to: "stimulus-loading.js", preload: true
5
+ pin_all_from Onlylogs::Engine.root.join("app/javascript/onlylogs/controllers"), under: "controllers", to: "onlylogs/controllers"
6
+
@@ -0,0 +1,94 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "puma/plugin"
4
+ require "rbconfig"
5
+ require "timeout"
6
+ require "shellwords"
7
+
8
+ Puma::Plugin.create do
9
+ def start(launcher)
10
+ @launcher = launcher
11
+ @events = launcher.events
12
+ @options = launcher.config.options
13
+ @vector_pid = nil
14
+
15
+ setup_paths
16
+ start_vector
17
+ register_hooks
18
+ end
19
+
20
+ private
21
+
22
+ def setup_paths
23
+ @app_root = @options[:directory] || Dir.pwd
24
+ @vector_bin = env_or_option("ONLYLOGS_VECTOR_BIN", :onlylogs_vector_bin, "vector")
25
+ @vector_config = env_or_option(
26
+ "ONLYLOGS_VECTOR_CONFIG",
27
+ :onlylogs_vector_config,
28
+ File.expand_path("../vector.toml", __dir__)
29
+ )
30
+ @vector_args = env_or_option("ONLYLOGS_VECTOR_ARGS", :onlylogs_vector_args, "")
31
+ @dsn = env_or_option("ONLYLOGS_DSN", :onlylogs_dsn, "https://onlylogs.io/drain/testmac")
32
+ end
33
+
34
+ def register_hooks
35
+ events = @launcher.events
36
+ events.register(:on_restart) { restart_vector }
37
+ at_exit { stop_vector }
38
+ end
39
+
40
+ def env_or_option(env_key, option_key, default)
41
+ ENV.fetch(env_key, @options.fetch(option_key, default))
42
+ end
43
+
44
+ def start_vector
45
+ stop_vector if @vector_pid
46
+
47
+ unless File.exist?(@vector_config)
48
+ warn "Vector config not found at #{@vector_config}; skipping start"
49
+ return
50
+ end
51
+
52
+ args = [ @vector_bin, "--config", @vector_config ]
53
+ args += Shellwords.split(@vector_args.to_s) unless @vector_args.to_s.empty?
54
+
55
+ info "Starting Vector sidecar (config: #{@vector_config}, dsn: #{@dsn})"
56
+ env = { "ONLYLOGS_DSN" => @dsn }
57
+ @vector_pid = Process.spawn(env, *args, chdir: @app_root, pgroup: true)
58
+ rescue Errno::ENOENT => e
59
+ error "Unable to start Vector sidecar: #{e.message}"
60
+ end
61
+
62
+ def restart_vector
63
+ info "Restarting Vector sidecar"
64
+ start_vector
65
+ end
66
+
67
+ def stop_vector
68
+ return unless @vector_pid
69
+
70
+ info "Stopping Vector sidecar"
71
+ pgid = Process.getpgid(@vector_pid)
72
+ Process.kill("TERM", -pgid)
73
+ Timeout.timeout(5) { Process.wait(@vector_pid) }
74
+ rescue Errno::ESRCH, Errno::ECHILD
75
+ # already stopped
76
+ rescue Timeout::Error
77
+ warn "Vector sidecar did not stop in time, killing"
78
+ Process.kill("KILL", -pgid) rescue nil
79
+ ensure
80
+ @vector_pid = nil
81
+ end
82
+
83
+ def info(message)
84
+ @events.log("[VectorSidecar] #{message}")
85
+ end
86
+
87
+ def warn(message)
88
+ @events.log("[VectorSidecar][WARN] #{message}")
89
+ end
90
+
91
+ def error(message)
92
+ @events.error("[VectorSidecar][ERROR] #{message}")
93
+ end
94
+ end
data/config/routes.rb ADDED
@@ -0,0 +1,4 @@
1
+ Onlylogs::Engine.routes.draw do
2
+ root "logs#index"
3
+ resources :logs, only: [ :index ]
4
+ end
@@ -0,0 +1,40 @@
1
+ # udp_logger.rb
2
+ require "logger"
3
+ require "socket"
4
+
5
+ class UdpLogger < Logger
6
+ def initialize(host: "127.0.0.1", port: 6000, local_fallback: $stdout)
7
+ # Use a normal Logger underneath so we still see logs locally
8
+ super(local_fallback)
9
+
10
+ @udp_host = host
11
+ @udp_port = port
12
+ @socket = UDPSocket.new
13
+ end
14
+
15
+ # Override Logger#add, which all the level methods delegate to
16
+ def add(severity, message = nil, progname = nil, &block)
17
+ # Same semantics as Logger:
18
+ if message.nil?
19
+ if block_given?
20
+ message = block.call
21
+ else
22
+ message = progname
23
+ progname = nil
24
+ end
25
+ end
26
+
27
+ # Send plain text over UDP to Vector
28
+ begin
29
+ payload = message.to_s
30
+ @socket.send(payload, 0, @udp_host, @udp_port)
31
+ rescue => e
32
+ # Swallow UDP errors so logging never crashes the app
33
+ warn "UDP logger error: #{e.class}: #{e.message}"
34
+ end
35
+
36
+ # Also log locally (stdout / file) via normal Logger behavior
37
+ super(severity, message, progname, &block)
38
+ end
39
+ end
40
+
@@ -0,0 +1,32 @@
1
+ # Where Vector keeps internal state (buffers, etc.)
2
+ data_dir = "/usr/local/var/lib/vector"
3
+
4
+ # --- 1) SOURCE: UDP socket listening on localhost:6000 ---
5
+
6
+ [sources.udp_logs]
7
+ type = "socket"
8
+ mode = "udp" # UDP mode
9
+ address = "127.0.0.1:6000"
10
+
11
+ # --- 2) SINK: console (for debugging, optional but very handy) ---
12
+
13
+ [sinks.console]
14
+ type = "console"
15
+ inputs = ["udp_logs"]
16
+ encoding.codec = "json"
17
+ target = "stdout"
18
+
19
+ # --- 3) SINK: Onlylogs HTTP drain ---
20
+
21
+ [sinks.onlylogs]
22
+ type = "http"
23
+ inputs = ["udp_logs"] # consume events from udp_logs
24
+ method = "post"
25
+ uri = "${ONLYLOGS_DSN}"
26
+
27
+ encoding.codec = "text"
28
+
29
+ #[sinks.onlylogs.request]
30
+ # Adjust headers if Onlylogs expects something specific
31
+ #headers.Content-Type = "application/json"
32
+
@@ -0,0 +1,9 @@
1
+ class CreateBooks < ActiveRecord::Migration[8.0]
2
+ def change
3
+ create_table :books do |t|
4
+ t.string :title
5
+ t.string :author
6
+ t.timestamps
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,133 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Onlylogs
4
+ class Configuration
5
+ attr_accessor :allowed_files, :default_log_file_path, :basic_auth_user, :basic_auth_password,
6
+ :parent_controller, :disable_basic_authentication, :ripgrep_enabled, :editor,
7
+ :max_line_matches
8
+
9
+ def initialize
10
+ @allowed_files = default_allowed_files
11
+ @default_log_file_path = default_log_file_path_value
12
+ @basic_auth_user = default_basic_auth_user
13
+ @basic_auth_password = default_basic_auth_password
14
+ @parent_controller = nil
15
+ @disable_basic_authentication = false
16
+ @ripgrep_enabled = default_ripgrep_enabled
17
+ @editor = default_editor
18
+ @max_line_matches = 100000
19
+ end
20
+
21
+ def configure
22
+ yield self
23
+ end
24
+
25
+ def default_editor
26
+ if (credentials_editor = Rails.application.credentials.dig(:onlylogs, :editor))
27
+ return credentials_editor
28
+ end
29
+
30
+ # 2. Check environment variables (ONLYLOGS_EDITOR > RAILS_EDITOR > EDITOR)
31
+ if ENV["ONLYLOGS_EDITOR"]
32
+ return ENV["ONLYLOGS_EDITOR"].to_sym
33
+ end
34
+
35
+ if ENV["RAILS_EDITOR"]
36
+ return ENV["RAILS_EDITOR"].to_sym
37
+ end
38
+
39
+ if ENV["EDITOR"]
40
+ return ENV["EDITOR"].to_sym
41
+ end
42
+
43
+ # 3. Default fallback
44
+ :vscode
45
+ end
46
+
47
+ def default_allowed_files
48
+ # Default to environment-specific log files (without rotation suffixes)
49
+ [
50
+ Rails.root.join("log/#{Rails.env}.log")
51
+ ]
52
+ end
53
+
54
+ def default_log_file_path_value
55
+ Rails.root.join("log/#{Rails.env}.log").to_s
56
+ end
57
+
58
+ def default_basic_auth_user
59
+ ENV["ONLYLOGS_BASIC_AUTH_USER"] || Rails.application.credentials.dig(:onlylogs, :basic_auth_user)
60
+ end
61
+
62
+ def default_basic_auth_password
63
+ ENV["ONLYLOGS_BASIC_AUTH_PASSWORD"] || Rails.application.credentials.dig(:onlylogs, :basic_auth_password)
64
+ end
65
+
66
+ def default_ripgrep_enabled
67
+ system("which rg > /dev/null 2>&1")
68
+ end
69
+ end
70
+
71
+ def self.configuration
72
+ @configuration ||= Configuration.new
73
+ end
74
+
75
+ def self.configure
76
+ yield configuration
77
+ end
78
+
79
+ def self.allowed_file_path?(file_path)
80
+ path = ::File.expand_path(file_path.to_s)
81
+
82
+ configuration.allowed_files.any? do |pattern|
83
+ pat = ::File.expand_path(pattern.to_s)
84
+ ::File.fnmatch?(pat, path, ::File::FNM_PATHNAME | ::File::FNM_DOTMATCH)
85
+ end
86
+ end
87
+
88
+ def self.allowed_files
89
+ configuration.allowed_files
90
+ end
91
+
92
+ def self.default_log_file_path
93
+ configuration.default_log_file_path
94
+ end
95
+
96
+ def self.basic_auth_user
97
+ configuration.basic_auth_user
98
+ end
99
+
100
+ def self.basic_auth_password
101
+ configuration.basic_auth_password
102
+ end
103
+
104
+ def self.parent_controller
105
+ configuration.parent_controller
106
+ end
107
+
108
+ def self.disable_basic_authentication?
109
+ configuration.disable_basic_authentication
110
+ end
111
+
112
+ def self.basic_auth_configured?
113
+ basic_auth_user.present? && basic_auth_password.present?
114
+ end
115
+
116
+ def self.ripgrep_enabled?
117
+ configuration.ripgrep_enabled
118
+ end
119
+
120
+ def self.editor
121
+ configuration.default_editor
122
+ end
123
+
124
+ def self.editor=(editor_symbol)
125
+ configuration.editor = editor_symbol
126
+ # Clear the cached editor instance when editor changes
127
+ Onlylogs::FilePathParser.clear_editor_cache
128
+ end
129
+
130
+ def self.max_line_matches
131
+ configuration.max_line_matches
132
+ end
133
+ end
@@ -0,0 +1,39 @@
1
+ require "importmap-rails"
2
+
3
+ module Onlylogs
4
+ class Engine < ::Rails::Engine
5
+ isolate_namespace Onlylogs
6
+
7
+ initializer "onlylogs.assets" do |app|
8
+ %w[images stylesheets builds fonts].each do |subdir|
9
+ path = root.join("app/assets", subdir)
10
+ app.config.assets.paths << path if path.exist?
11
+ end
12
+
13
+ app.config.assets.paths << root.join("app/javascript")
14
+ app.config.assets.precompile += %w[ onlylogs_manifest ]
15
+ end
16
+
17
+
18
+ initializer "onlylogs.importmap", after: "importmap" do |app|
19
+ Onlylogs.importmap.draw(root.join("config/importmap.rb"))
20
+ if app.config.importmap.sweep_cache && app.config.reloading_enabled?
21
+ Onlylogs.importmap.cache_sweeper(watches: root.join("app/javascript"))
22
+
23
+ ActiveSupport.on_load(:action_controller_base) do
24
+ before_action { Onlylogs.importmap.cache_sweeper.execute_if_updated }
25
+ end
26
+ end
27
+ end
28
+
29
+ # initializer "onlylogs.add_log_silencer" do |app|
30
+ # silenced_routes = ['/onlylogs']
31
+ #
32
+ # app.middleware.insert_before(
33
+ # Rails::Rack::Logger,
34
+ # Onlylogs::LogSilencerMiddleware,
35
+ # paths_to_silence: silenced_routes
36
+ # )
37
+ # end
38
+ end
39
+ end