its-showtime 0.1.1
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 +7 -0
- data/LICENSE +21 -0
- data/README.md +179 -0
- data/bin/showtime +47 -0
- data/lib/showtime/app.rb +399 -0
- data/lib/showtime/charts.rb +229 -0
- data/lib/showtime/component_registry.rb +38 -0
- data/lib/showtime/components/Components.md +309 -0
- data/lib/showtime/components/alerts.rb +83 -0
- data/lib/showtime/components/base.rb +63 -0
- data/lib/showtime/components/charts.rb +119 -0
- data/lib/showtime/components/data.rb +328 -0
- data/lib/showtime/components/inputs.rb +390 -0
- data/lib/showtime/components/layout.rb +135 -0
- data/lib/showtime/components/media.rb +73 -0
- data/lib/showtime/components/sidebar.rb +130 -0
- data/lib/showtime/components/text.rb +156 -0
- data/lib/showtime/components.rb +18 -0
- data/lib/showtime/compute_tracker.rb +21 -0
- data/lib/showtime/helpers.rb +53 -0
- data/lib/showtime/logger.rb +143 -0
- data/lib/showtime/public/.vite/manifest.json +34 -0
- data/lib/showtime/public/assets/antd-3aDVoXqG.js +447 -0
- data/lib/showtime/public/assets/charts-iowb_sWQ.js +3858 -0
- data/lib/showtime/public/assets/index-B2b3lWS5.js +43 -0
- data/lib/showtime/public/assets/index-M6NVamDM.css +1 -0
- data/lib/showtime/public/assets/react-BE6xecJX.js +32 -0
- data/lib/showtime/public/index.html +19 -0
- data/lib/showtime/public/letter.png +0 -0
- data/lib/showtime/public/logo.png +0 -0
- data/lib/showtime/release.rb +108 -0
- data/lib/showtime/session.rb +131 -0
- data/lib/showtime/version.rb +3 -0
- data/lib/showtime/views/index.erb +32 -0
- data/lib/showtime.rb +157 -0
- metadata +300 -0
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
module Showtime
|
|
2
|
+
module Components
|
|
3
|
+
class Sidebar < BaseComponent
|
|
4
|
+
def initialize
|
|
5
|
+
super
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def to_h
|
|
9
|
+
super.merge({})
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def title(text)
|
|
13
|
+
title = Title.new(text, false, nil, false)
|
|
14
|
+
Showtime::session.with_sidebar do
|
|
15
|
+
Showtime::session.add_element(title)
|
|
16
|
+
end
|
|
17
|
+
title
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def header(text)
|
|
21
|
+
header = Header.new(text, false, nil, false)
|
|
22
|
+
Showtime::session.with_sidebar do
|
|
23
|
+
Showtime::session.add_element(header)
|
|
24
|
+
end
|
|
25
|
+
header
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def subheader(text)
|
|
29
|
+
subheader = Subheader.new(text, false, nil, false)
|
|
30
|
+
Showtime::session.with_sidebar do
|
|
31
|
+
Showtime::session.add_element(subheader)
|
|
32
|
+
end
|
|
33
|
+
subheader
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def write(text)
|
|
37
|
+
text = Text.new(text)
|
|
38
|
+
Showtime::session.with_sidebar do
|
|
39
|
+
Showtime::session.add_element(text)
|
|
40
|
+
end
|
|
41
|
+
text
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def text(text)
|
|
45
|
+
text = Text.new(text)
|
|
46
|
+
Showtime::session.with_sidebar do
|
|
47
|
+
Showtime::session.add_element(text)
|
|
48
|
+
end
|
|
49
|
+
text
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def checkbox(label, value: false, key: nil, help: nil)
|
|
53
|
+
checkbox = Checkbox.new(label, value: value, key: key, help: help)
|
|
54
|
+
Showtime::session.with_sidebar do
|
|
55
|
+
Showtime::session.add_element(checkbox)
|
|
56
|
+
end
|
|
57
|
+
checkbox
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def button(label, key: nil, help: nil)
|
|
61
|
+
button = Button.new(label, key: key, help: help)
|
|
62
|
+
Showtime::session.with_sidebar do
|
|
63
|
+
Showtime::session.add_element(button)
|
|
64
|
+
end
|
|
65
|
+
button
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def text_input(label, value: "", key: nil, help: nil)
|
|
69
|
+
text_input = TextInput.new(label, value: value, key: key, help: help)
|
|
70
|
+
Showtime::session.with_sidebar do
|
|
71
|
+
Showtime::session.add_element(text_input)
|
|
72
|
+
end
|
|
73
|
+
text_input
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def number_input(label, min_value: nil, max_value: nil, value: nil, step: 1, key: nil, help: nil)
|
|
77
|
+
number_input = NumberInput.new(label, min_value: min_value, max_value: max_value, value: value, step: step, key: key, help: help)
|
|
78
|
+
Showtime::session.with_sidebar do
|
|
79
|
+
Showtime::session.add_element(number_input)
|
|
80
|
+
end
|
|
81
|
+
number_input
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def date_input(label, value: nil, min_value: nil, max_value: nil, key: nil, help: nil)
|
|
85
|
+
date_input = DateInput.new(label, value: value, min_value: min_value, max_value: max_value, key: key, help: help)
|
|
86
|
+
Showtime::session.with_sidebar do
|
|
87
|
+
Showtime::session.add_element(date_input)
|
|
88
|
+
end
|
|
89
|
+
date_input
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def selectbox(label, options, index: 0, key: nil, help: nil)
|
|
93
|
+
selectbox = Selectbox.new(label, options, index: index, key: key, help: help)
|
|
94
|
+
Showtime::session.with_sidebar do
|
|
95
|
+
Showtime::session.add_element(selectbox)
|
|
96
|
+
end
|
|
97
|
+
selectbox
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def file_uploader(label, type: nil, accept_multiple_files: false, max_size: Showtime::Components::FileUploader::DEFAULT_MAX_SIZE, key: nil, help: nil, wide: false)
|
|
101
|
+
file_uploader = FileUploader.new(label, type: type, accept_multiple_files: accept_multiple_files, max_size: max_size, key: key, help: help, wide: wide)
|
|
102
|
+
Showtime::session.with_sidebar do
|
|
103
|
+
Showtime::session.add_element(file_uploader)
|
|
104
|
+
end
|
|
105
|
+
file_uploader
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def image(src, caption: nil, width: nil, use_column_width: false, key: nil, help: nil)
|
|
109
|
+
image = Image.new(src, caption: caption, width: width, use_column_width: use_column_width, key: key, help: help)
|
|
110
|
+
Showtime::session.with_sidebar do
|
|
111
|
+
Showtime::session.add_element(image)
|
|
112
|
+
end
|
|
113
|
+
image
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
def markdown(content)
|
|
117
|
+
markdown = Markdown.new(content)
|
|
118
|
+
Showtime::session.with_sidebar do
|
|
119
|
+
Showtime::session.add_element(markdown)
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
module St
|
|
126
|
+
def self.sidebar
|
|
127
|
+
Showtime::Components::Sidebar.new
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
end
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
require_relative 'base'
|
|
2
|
+
|
|
3
|
+
module Showtime
|
|
4
|
+
module Components
|
|
5
|
+
class Title < BaseComponent
|
|
6
|
+
attr_reader :text, :divider, :help, :anchor
|
|
7
|
+
|
|
8
|
+
def initialize(text, divider = false, help = nil, anchor = true)
|
|
9
|
+
super()
|
|
10
|
+
@text = text
|
|
11
|
+
@divider = divider
|
|
12
|
+
@help = help
|
|
13
|
+
@anchor = anchor
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def to_h
|
|
17
|
+
super.merge({
|
|
18
|
+
text: @text,
|
|
19
|
+
divider: @divider,
|
|
20
|
+
help: @help,
|
|
21
|
+
anchor: @anchor
|
|
22
|
+
})
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
class Header < BaseComponent
|
|
27
|
+
attr_reader :text, :divider, :help, :anchor
|
|
28
|
+
|
|
29
|
+
def initialize(text, divider = false, help = nil, anchor = true)
|
|
30
|
+
super()
|
|
31
|
+
@text = text
|
|
32
|
+
@divider = divider
|
|
33
|
+
@help = help
|
|
34
|
+
@anchor = anchor
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def to_h
|
|
38
|
+
super.merge({
|
|
39
|
+
text: @text,
|
|
40
|
+
divider: @divider,
|
|
41
|
+
help: @help,
|
|
42
|
+
anchor: @anchor
|
|
43
|
+
})
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
class Subheader < BaseComponent
|
|
48
|
+
attr_reader :text, :divider, :help, :anchor
|
|
49
|
+
|
|
50
|
+
def initialize(text, divider = false, help = nil, anchor = true)
|
|
51
|
+
super()
|
|
52
|
+
@text = text
|
|
53
|
+
@divider = divider
|
|
54
|
+
@help = help
|
|
55
|
+
@anchor = anchor
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def to_h
|
|
59
|
+
super.merge({
|
|
60
|
+
text: @text,
|
|
61
|
+
divider: @divider,
|
|
62
|
+
help: @help,
|
|
63
|
+
anchor: @anchor
|
|
64
|
+
})
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
class Text < BaseComponent
|
|
69
|
+
attr_reader :text, :divider, :help
|
|
70
|
+
|
|
71
|
+
def initialize(text)
|
|
72
|
+
super()
|
|
73
|
+
@text = text
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def to_h
|
|
77
|
+
super.merge({
|
|
78
|
+
text: @text,
|
|
79
|
+
})
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
class Markdown < BaseComponent
|
|
84
|
+
attr_reader :content
|
|
85
|
+
|
|
86
|
+
def initialize(content)
|
|
87
|
+
super()
|
|
88
|
+
@content = content
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def to_h
|
|
92
|
+
super.merge({
|
|
93
|
+
content: @content
|
|
94
|
+
})
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
class Metric < BaseComponent
|
|
99
|
+
attr_reader :label, :value, :delta, :prefix, :suffix
|
|
100
|
+
|
|
101
|
+
def initialize(label, value:, delta: nil, prefix: nil, suffix: nil, key: nil, help: nil)
|
|
102
|
+
super(key: key, help: help)
|
|
103
|
+
@label = label
|
|
104
|
+
@value = value
|
|
105
|
+
@delta = delta
|
|
106
|
+
@prefix = prefix
|
|
107
|
+
@suffix = suffix
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def to_h
|
|
111
|
+
super.merge({
|
|
112
|
+
label: @label,
|
|
113
|
+
value: @value,
|
|
114
|
+
delta: @delta,
|
|
115
|
+
prefix: @prefix,
|
|
116
|
+
suffix: @suffix
|
|
117
|
+
})
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
module St
|
|
123
|
+
def self.title(text, divider: false, help: nil, anchor: true)
|
|
124
|
+
anchor_enabled = anchor && !Showtime::session.in_sidebar?
|
|
125
|
+
Showtime::session.add_element(Showtime::Components::Title.new(text, divider, help, anchor_enabled))
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def self.header(text, divider: false, help: nil, anchor: true)
|
|
129
|
+
anchor_enabled = anchor && !Showtime::session.in_sidebar?
|
|
130
|
+
Showtime::session.add_element(Showtime::Components::Header.new(text, divider, help, anchor_enabled))
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
def self.subheader(text, divider: false, help: nil, anchor: true)
|
|
134
|
+
anchor_enabled = anchor && !Showtime::session.in_sidebar?
|
|
135
|
+
Showtime::session.add_element(Showtime::Components::Subheader.new(text, divider, help, anchor_enabled))
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
def self.write(text)
|
|
139
|
+
self.text(text)
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
def self.text(text)
|
|
143
|
+
Showtime::session.add_element(Showtime::Components::Text.new(text))
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
def self.markdown(content)
|
|
147
|
+
Showtime::session.add_element(Showtime::Components::Markdown.new(content))
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
def self.metric(label, value:, delta: nil, prefix: nil, suffix: nil, key: nil, help: nil)
|
|
151
|
+
Showtime::session.add_element(
|
|
152
|
+
Showtime::Components::Metric.new(label, value: value, delta: delta, prefix: prefix, suffix: suffix, key: key, help: help)
|
|
153
|
+
)
|
|
154
|
+
end
|
|
155
|
+
end
|
|
156
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
require 'json'
|
|
2
|
+
require 'base64'
|
|
3
|
+
require 'securerandom'
|
|
4
|
+
|
|
5
|
+
require_relative 'components/base'
|
|
6
|
+
require_relative 'components/text'
|
|
7
|
+
require_relative 'components/alerts'
|
|
8
|
+
require_relative 'components/charts'
|
|
9
|
+
require_relative 'components/layout'
|
|
10
|
+
require_relative 'components/data'
|
|
11
|
+
require_relative 'components/media'
|
|
12
|
+
require_relative 'components/inputs'
|
|
13
|
+
require_relative 'components/sidebar'
|
|
14
|
+
|
|
15
|
+
module Showtime
|
|
16
|
+
module Components
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
module Showtime
|
|
2
|
+
# Tracks value access during computation
|
|
3
|
+
class ComputeTracker
|
|
4
|
+
def initialize(&block)
|
|
5
|
+
@accessed_keys = Set.new
|
|
6
|
+
@result = track_access(&block)
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
attr_reader :accessed_keys, :result
|
|
10
|
+
|
|
11
|
+
private
|
|
12
|
+
|
|
13
|
+
def track_access(&block)
|
|
14
|
+
old_tracker = Thread.current[:compute_tracker]
|
|
15
|
+
Thread.current[:compute_tracker] = self
|
|
16
|
+
yield
|
|
17
|
+
ensure
|
|
18
|
+
Thread.current[:compute_tracker] = old_tracker
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
require 'json'
|
|
2
|
+
|
|
3
|
+
module Showtime
|
|
4
|
+
module Helpers
|
|
5
|
+
def self.update_component_counter(component)
|
|
6
|
+
# Get the next counter directly from the session
|
|
7
|
+
Showtime.current_session.next_counter_for(component)
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def self.absolute_path(relative_path)
|
|
11
|
+
# Prioritize using the main_script_path from the current session
|
|
12
|
+
session = Showtime.current_session
|
|
13
|
+
if session&.main_script_path
|
|
14
|
+
base_dir = File.dirname(session.main_script_path)
|
|
15
|
+
resolved_path = File.expand_path(relative_path, base_dir)
|
|
16
|
+
return resolved_path
|
|
17
|
+
else
|
|
18
|
+
# Fallback: resolve relative to CWD if no suitable caller found or path is relative
|
|
19
|
+
# and not handled above.
|
|
20
|
+
return File.expand_path(relative_path, Dir.pwd)
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
module Vite
|
|
27
|
+
module Helpers
|
|
28
|
+
def vite_asset_path(chunk_name)
|
|
29
|
+
entry_point = vite_manifest_entry(chunk_name)
|
|
30
|
+
# entry_point['file'] is something like 'assets/index-123.js'
|
|
31
|
+
# We want it to be like '/assets/index-123.js' before prefixing
|
|
32
|
+
base_vite_path = "/#{entry_point['file']}"
|
|
33
|
+
|
|
34
|
+
script_name_prefix = ""
|
|
35
|
+
if defined?(request) && request.respond_to?(:script_name) && request.script_name
|
|
36
|
+
script_name_prefix = request.script_name.to_s
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# Combine and clean up slashes
|
|
40
|
+
# If script_name_prefix = "/myapp", base_vite_path = "/vite/foo.js" -> "/myapp/vite/foo.js" (after squeeze)
|
|
41
|
+
# If script_name_prefix = "", base_vite_path = "/vite/foo.js" -> "/vite/foo.js"
|
|
42
|
+
(script_name_prefix + base_vite_path).squeeze('/')
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def vite_manifest_entry(chunk_name)
|
|
46
|
+
manifest_path = File.join(settings.public_folder, '.vite/manifest.json')
|
|
47
|
+
manifest = JSON.parse(File.read(manifest_path))
|
|
48
|
+
entry_point = manifest.values.find { |entry| entry['name'] == chunk_name }
|
|
49
|
+
raise "#{chunk_name} entry point not found in Vite manifest" unless entry_point
|
|
50
|
+
entry_point
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
require 'logger'
|
|
2
|
+
require 'json'
|
|
3
|
+
require 'fileutils'
|
|
4
|
+
require 'pastel'
|
|
5
|
+
|
|
6
|
+
module Showtime
|
|
7
|
+
class Logger
|
|
8
|
+
class << self
|
|
9
|
+
attr_accessor :logger, :config, :pastel
|
|
10
|
+
|
|
11
|
+
def configure
|
|
12
|
+
@config ||= default_config
|
|
13
|
+
yield(@config) if block_given?
|
|
14
|
+
@pastel = Pastel.new
|
|
15
|
+
setup_logger
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def default_config
|
|
19
|
+
{
|
|
20
|
+
level: ENV.fetch('SHOWTIME_LOG_LEVEL', 'INFO').upcase,
|
|
21
|
+
format: ENV.fetch('SHOWTIME_LOG_FORMAT', 'text'),
|
|
22
|
+
output: ENV.fetch('SHOWTIME_LOG_OUTPUT', 'both'), # 'file', 'stdout', or 'both'
|
|
23
|
+
directory: ENV.fetch('SHOWTIME_LOG_DIR', nil),
|
|
24
|
+
file_name: ENV.fetch('SHOWTIME_LOG_FILE', 'showtime.log'),
|
|
25
|
+
rotation: ENV.fetch('SHOWTIME_LOG_ROTATION', 'daily'),
|
|
26
|
+
structured: ENV.fetch('SHOWTIME_LOG_STRUCTURED', 'false') == 'true'
|
|
27
|
+
}
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def setup_logger
|
|
31
|
+
@logger = if @config[:custom_logger]
|
|
32
|
+
@config[:custom_logger]
|
|
33
|
+
else
|
|
34
|
+
create_logger
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# Set log level
|
|
38
|
+
@logger.level = ::Logger.const_get(@config[:level])
|
|
39
|
+
|
|
40
|
+
# Set formatter
|
|
41
|
+
@logger.formatter = @config[:structured] ? method(:structured_formatter) : method(:text_formatter)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def create_logger
|
|
45
|
+
outputs = []
|
|
46
|
+
|
|
47
|
+
# Add file output if configured
|
|
48
|
+
if ['file', 'both'].include?(@config[:output])
|
|
49
|
+
log_dir = @config[:directory] || default_log_directory
|
|
50
|
+
FileUtils.mkdir_p(log_dir)
|
|
51
|
+
log_path = File.join(log_dir, @config[:file_name])
|
|
52
|
+
# Set up log rotation based on configuration
|
|
53
|
+
shift_age = @config[:rotation] || 'daily'
|
|
54
|
+
shift_size = 1024 * 1024 # 1MB
|
|
55
|
+
|
|
56
|
+
outputs << ::Logger.new(log_path, shift_age, shift_size)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# Add stdout output if configured
|
|
60
|
+
if ['stdout', 'both'].include?(@config[:output])
|
|
61
|
+
outputs << ::Logger.new($stdout)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# Return multi-logger if multiple outputs, otherwise single logger
|
|
65
|
+
outputs.size > 1 ? MultiLogger.new(outputs) : outputs.first
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def default_log_directory
|
|
69
|
+
if ENV['SHOWTIME_APP_DIR']
|
|
70
|
+
File.join(ENV['SHOWTIME_APP_DIR'], 'log')
|
|
71
|
+
else
|
|
72
|
+
File.join(Dir.pwd, 'log')
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def structured_formatter(severity, time, progname, msg)
|
|
77
|
+
log_entry = {
|
|
78
|
+
timestamp: time.iso8601,
|
|
79
|
+
level: severity,
|
|
80
|
+
message: msg
|
|
81
|
+
}
|
|
82
|
+
log_entry[:progname] = progname if progname
|
|
83
|
+
"#{log_entry.to_json}\n"
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def text_formatter(severity, time, progname, msg)
|
|
87
|
+
timestamp = time.strftime('%Y-%m-%d %H:%M:%S.%L')
|
|
88
|
+
progname_str = progname ? " [#{progname}]" : ""
|
|
89
|
+
|
|
90
|
+
# Color schemes for different log levels
|
|
91
|
+
colors = {
|
|
92
|
+
'DEBUG' => ->(s) { @pastel.bright_blue(s) },
|
|
93
|
+
'INFO' => ->(s) { @pastel.bright_green(s) },
|
|
94
|
+
'WARN' => ->(s) { @pastel.bright_yellow(s) },
|
|
95
|
+
'ERROR' => ->(s) { @pastel.bright_red(s) },
|
|
96
|
+
'FATAL' => ->(s) { @pastel.bold.bright_red(s) }
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
# Format timestamp in dim color
|
|
100
|
+
colored_timestamp = @pastel.dim("[#{timestamp}]")
|
|
101
|
+
|
|
102
|
+
# Format severity with appropriate color
|
|
103
|
+
colored_severity = colors[severity]&.call(severity) || severity
|
|
104
|
+
|
|
105
|
+
# Only use colors when outputting to terminal
|
|
106
|
+
if @config[:output] == 'stdout' || (@config[:output] == 'both' && @logger.is_a?(MultiLogger))
|
|
107
|
+
"#{colored_timestamp} #{colored_severity}#{progname_str}: #{msg}\n"
|
|
108
|
+
else
|
|
109
|
+
"[#{timestamp}] #{severity}#{progname_str}: #{msg}\n"
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
# Delegate logging methods to the logger instance
|
|
114
|
+
%i[debug info warn error fatal].each do |level|
|
|
115
|
+
define_method(level) do |*args, &block|
|
|
116
|
+
configure if logger.nil? # Ensures logger is initialized
|
|
117
|
+
logger.send(level, *args, &block)
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
# Handle multiple log outputs
|
|
124
|
+
class MultiLogger
|
|
125
|
+
def initialize(loggers)
|
|
126
|
+
@loggers = loggers
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def level=(level)
|
|
130
|
+
@loggers.each { |logger| logger.level = level }
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
def formatter=(formatter)
|
|
134
|
+
@loggers.each { |logger| logger.formatter = formatter }
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
%i[debug info warn error fatal].each do |level|
|
|
138
|
+
define_method(level) do |*args, &block|
|
|
139
|
+
@loggers.each { |logger| logger.send(level, *args, &block) }
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
end
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
{
|
|
2
|
+
"_antd-3aDVoXqG.js": {
|
|
3
|
+
"file": "assets/antd-3aDVoXqG.js",
|
|
4
|
+
"name": "antd",
|
|
5
|
+
"imports": [
|
|
6
|
+
"_react-BE6xecJX.js"
|
|
7
|
+
]
|
|
8
|
+
},
|
|
9
|
+
"_charts-iowb_sWQ.js": {
|
|
10
|
+
"file": "assets/charts-iowb_sWQ.js",
|
|
11
|
+
"name": "charts",
|
|
12
|
+
"imports": [
|
|
13
|
+
"_react-BE6xecJX.js"
|
|
14
|
+
]
|
|
15
|
+
},
|
|
16
|
+
"_react-BE6xecJX.js": {
|
|
17
|
+
"file": "assets/react-BE6xecJX.js",
|
|
18
|
+
"name": "react"
|
|
19
|
+
},
|
|
20
|
+
"index.html": {
|
|
21
|
+
"file": "assets/index-B2b3lWS5.js",
|
|
22
|
+
"name": "index",
|
|
23
|
+
"src": "index.html",
|
|
24
|
+
"isEntry": true,
|
|
25
|
+
"imports": [
|
|
26
|
+
"_react-BE6xecJX.js",
|
|
27
|
+
"_antd-3aDVoXqG.js",
|
|
28
|
+
"_charts-iowb_sWQ.js"
|
|
29
|
+
],
|
|
30
|
+
"css": [
|
|
31
|
+
"assets/index-M6NVamDM.css"
|
|
32
|
+
]
|
|
33
|
+
}
|
|
34
|
+
}
|