console_kit 1.0.0 → 1.2.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.
- checksums.yaml +4 -4
- data/lib/console_kit/configuration.rb +18 -15
- data/lib/console_kit/connections/base_connection_handler.rb +47 -1
- data/lib/console_kit/connections/connection_manager.rb +4 -0
- data/lib/console_kit/connections/dashboard.rb +28 -0
- data/lib/console_kit/connections/diagnostic_helpers.rb +22 -0
- data/lib/console_kit/connections/elasticsearch_connection_handler.rb +62 -0
- data/lib/console_kit/connections/mongo_connection_handler.rb +36 -8
- data/lib/console_kit/connections/redis_connection_handler.rb +76 -0
- data/lib/console_kit/connections/sql_connection_handler.rb +45 -10
- data/lib/console_kit/connections/table_formatter.rb +37 -0
- data/lib/console_kit/connections/table_renderer.rb +49 -0
- data/lib/console_kit/console_helpers.rb +47 -0
- data/lib/console_kit/output.rb +11 -13
- data/lib/console_kit/prompt.rb +52 -0
- data/lib/console_kit/railtie.rb +9 -1
- data/lib/console_kit/setup.rb +31 -24
- data/lib/console_kit/setup_ui.rb +37 -0
- data/lib/console_kit/tenant_configurator.rb +33 -15
- data/lib/console_kit/tenant_selector.rb +22 -17
- data/lib/console_kit/version.rb +1 -1
- data/lib/console_kit.rb +9 -1
- data/lib/generators/console_kit/templates/console_kit.rb +12 -2
- metadata +30 -12
- data/CHANGELOG.md +0 -92
- data/CODE_OF_CONDUCT.md +0 -132
- data/README.md +0 -116
- data/SECURITY.md +0 -30
- data/sig/console_kit.rbs +0 -4
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: ee9784d78350655db23efa1080d49d5e2527e1b6530ef7a1c2772974e57a89ec
|
|
4
|
+
data.tar.gz: 2edbd66f104e2a2e549d11991fbbdbd1f77118227dfad56122bb58baefd4ee72
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 92d30809eba5bb236130e2e472cb2ae8fc34f9ea0bd69b3a5c67334199cc34a9894666797ebe66c52489f8a2c22a403d1f65e097bfafdba89e3d3d9d5ec8d5b9
|
|
7
|
+
data.tar.gz: db949d61fdcff82557532b163dd1df0487e6e3fb6c3f0901eb172fa83b6adc87f70b191b59dca6405b76c90fad67c3b2c58aaff21161e441ae295e3dbdea116b
|
|
@@ -3,20 +3,23 @@
|
|
|
3
3
|
module ConsoleKit
|
|
4
4
|
# Stores ConsoleKit configurations such as tenant map and context behavior
|
|
5
5
|
class Configuration
|
|
6
|
-
|
|
7
|
-
|
|
6
|
+
# Value object for storing configuration settings
|
|
7
|
+
Settings = Struct.new(:pretty_output, :tenants, :context_class, :sql_base_class, :show_dashboard)
|
|
8
8
|
|
|
9
9
|
def initialize
|
|
10
|
-
@
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
10
|
+
@settings = Settings.new(true, nil, nil, 'ApplicationRecord', false)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
%i[pretty_output tenants context_class sql_base_class show_dashboard].each do |attr|
|
|
14
|
+
define_method(attr) { @settings.send(attr) }
|
|
15
|
+
define_method("#{attr}=") { |val| @settings.send("#{attr}=", val) }
|
|
14
16
|
end
|
|
15
17
|
|
|
16
18
|
def context_class
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
19
|
+
val = @settings.context_class
|
|
20
|
+
case val
|
|
21
|
+
when String, Symbol then resolve_context_class(val)
|
|
22
|
+
else val
|
|
20
23
|
end
|
|
21
24
|
end
|
|
22
25
|
|
|
@@ -28,17 +31,17 @@ module ConsoleKit
|
|
|
28
31
|
end
|
|
29
32
|
|
|
30
33
|
def validate!
|
|
31
|
-
raise Error, 'ConsoleKit: `tenants` is not configured.' if
|
|
32
|
-
raise Error, 'ConsoleKit: `tenants` must be a Hash.' unless
|
|
33
|
-
raise Error, 'ConsoleKit: `context_class` is not configured.' if @context_class.blank?
|
|
34
|
+
raise Error, 'ConsoleKit: `tenants` is not configured.' if tenants.blank?
|
|
35
|
+
raise Error, 'ConsoleKit: `tenants` must be a Hash.' unless tenants.is_a?(Hash)
|
|
36
|
+
raise Error, 'ConsoleKit: `context_class` is not configured.' if @settings.context_class.blank?
|
|
34
37
|
end
|
|
35
38
|
|
|
36
39
|
private
|
|
37
40
|
|
|
38
|
-
def resolve_context_class
|
|
39
|
-
|
|
41
|
+
def resolve_context_class(val)
|
|
42
|
+
val.to_s.constantize
|
|
40
43
|
rescue NameError
|
|
41
|
-
raise Error, "ConsoleKit: context_class '#{
|
|
44
|
+
raise Error, "ConsoleKit: context_class '#{val}' could not be found. " \
|
|
42
45
|
'Ensure the class is defined before configuration is accessed.'
|
|
43
46
|
end
|
|
44
47
|
end
|
|
@@ -1,11 +1,16 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require 'active_support/core_ext/class/subclasses'
|
|
4
|
+
require 'active_support/core_ext/string/inflections'
|
|
5
|
+
require 'active_support/core_ext/string/filters'
|
|
6
|
+
require_relative 'diagnostic_helpers'
|
|
4
7
|
|
|
5
8
|
module ConsoleKit
|
|
6
9
|
module Connections
|
|
7
10
|
# Parent class for connection handlers
|
|
8
11
|
class BaseConnectionHandler
|
|
12
|
+
include DiagnosticHelpers
|
|
13
|
+
|
|
9
14
|
class << self
|
|
10
15
|
def registry = descendants
|
|
11
16
|
end
|
|
@@ -14,7 +19,48 @@ module ConsoleKit
|
|
|
14
19
|
|
|
15
20
|
def initialize(context) = @context = context
|
|
16
21
|
def connect = raise NotImplementedError, "#{self.class} must implement #connect"
|
|
17
|
-
def available? =
|
|
22
|
+
def available? = raise NotImplementedError, "#{self.class} must implement #available?"
|
|
23
|
+
def diagnostics = raise NotImplementedError, "#{self.class} must implement #diagnostics"
|
|
24
|
+
|
|
25
|
+
def safe_diagnostics(timeout: 2)
|
|
26
|
+
handler_name = self.class.name.demodulize.delete_suffix('ConnectionHandler')
|
|
27
|
+
thread, result_wrapper = spawn_diagnostic_thread(handler_name)
|
|
28
|
+
|
|
29
|
+
if thread.join(timeout)
|
|
30
|
+
result_wrapper[:value] || error_diagnostics(handler_name, StandardError.new('Unknown error'))
|
|
31
|
+
else
|
|
32
|
+
thread.kill
|
|
33
|
+
timeout_diagnostics(handler_name, timeout)
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
private
|
|
38
|
+
|
|
39
|
+
def spawn_diagnostic_thread(handler_name)
|
|
40
|
+
wrapper = { value: nil }
|
|
41
|
+
thread = Thread.new { wrapper[:value] = run_diagnostics_safely(handler_name) }
|
|
42
|
+
[thread, wrapper]
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def run_diagnostics_safely(name)
|
|
46
|
+
diagnostics
|
|
47
|
+
rescue StandardError => e
|
|
48
|
+
error_diagnostics(name, e)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def context_attribute(name)
|
|
52
|
+
@context.respond_to?(name, true) ? @context.send(name) : nil
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def measure_latency
|
|
56
|
+
start = clock_time
|
|
57
|
+
yield
|
|
58
|
+
((clock_time - start) * 1000).round(1)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def unavailable_diagnostics(name)
|
|
62
|
+
{ name: name, status: :unavailable, latency_ms: nil, details: {} }
|
|
63
|
+
end
|
|
18
64
|
end
|
|
19
65
|
end
|
|
20
66
|
end
|
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
require_relative 'sql_connection_handler'
|
|
4
4
|
require_relative 'mongo_connection_handler'
|
|
5
|
+
require_relative 'redis_connection_handler'
|
|
6
|
+
require_relative 'elasticsearch_connection_handler'
|
|
5
7
|
|
|
6
8
|
module ConsoleKit
|
|
7
9
|
module Connections
|
|
@@ -12,6 +14,8 @@ module ConsoleKit
|
|
|
12
14
|
handler_classes.filter_map do |klass|
|
|
13
15
|
handler = klass.new(context)
|
|
14
16
|
handler if handler.available?
|
|
17
|
+
rescue NotImplementedError
|
|
18
|
+
nil
|
|
15
19
|
end
|
|
16
20
|
end
|
|
17
21
|
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'table_renderer'
|
|
4
|
+
|
|
5
|
+
module ConsoleKit
|
|
6
|
+
module Connections
|
|
7
|
+
# Displays connection diagnostics as a Unicode table
|
|
8
|
+
module Dashboard
|
|
9
|
+
class << self
|
|
10
|
+
def display
|
|
11
|
+
rows = fetch_diagnostics
|
|
12
|
+
return Output.print_warning('No connections available') if rows.empty?
|
|
13
|
+
|
|
14
|
+
Output.print_header('Connection Dashboard')
|
|
15
|
+
Output.print_raw(TableRenderer.render(rows))
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
private
|
|
19
|
+
|
|
20
|
+
def fetch_diagnostics
|
|
21
|
+
ctx = ConsoleKit.configuration.context_class
|
|
22
|
+
handlers = ConnectionManager.available_handlers(ctx)
|
|
23
|
+
handlers.map(&:safe_diagnostics)
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ConsoleKit
|
|
4
|
+
module Connections
|
|
5
|
+
# Shared helper methods for connection diagnostics
|
|
6
|
+
module DiagnosticHelpers
|
|
7
|
+
module_function
|
|
8
|
+
|
|
9
|
+
def clock_time
|
|
10
|
+
Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def error_diagnostics(name, error)
|
|
14
|
+
{ name: name, status: :error, latency_ms: nil, details: { error: error.message.truncate(60) } }
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def timeout_diagnostics(name, timeout)
|
|
18
|
+
{ name: name, status: :timeout, latency_ms: nil, details: { error: "Timed out after #{timeout}s" } }
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'base_connection_handler'
|
|
4
|
+
|
|
5
|
+
module ConsoleKit
|
|
6
|
+
module Connections
|
|
7
|
+
# Handles Elasticsearch connections
|
|
8
|
+
class ElasticsearchConnectionHandler < BaseConnectionHandler
|
|
9
|
+
def connect
|
|
10
|
+
prefix = context_attribute(:tenant_elasticsearch_prefix).presence
|
|
11
|
+
Output.print_info(switch_message(prefix))
|
|
12
|
+
Thread.current[:console_kit_elasticsearch_prefix] = prefix
|
|
13
|
+
apply_model_index_prefix(prefix)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def available?
|
|
17
|
+
!!(defined?(Elasticsearch::Model) && Elasticsearch::Model.respond_to?(:client))
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def diagnostics
|
|
21
|
+
name = 'Elasticsearch'
|
|
22
|
+
return unavailable_diagnostics(name) unless available?
|
|
23
|
+
|
|
24
|
+
client = Elasticsearch::Model.client
|
|
25
|
+
latency = measure_latency { client.ping }
|
|
26
|
+
build_elasticsearch_diagnostics(client, latency)
|
|
27
|
+
rescue StandardError => e
|
|
28
|
+
error_diagnostics(name, e)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
private
|
|
32
|
+
|
|
33
|
+
def build_elasticsearch_diagnostics(client, latency)
|
|
34
|
+
{
|
|
35
|
+
name: 'Elasticsearch',
|
|
36
|
+
status: :connected,
|
|
37
|
+
latency_ms: latency,
|
|
38
|
+
details: elasticsearch_details(client.cluster.health)
|
|
39
|
+
}
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def elasticsearch_details(health)
|
|
43
|
+
{
|
|
44
|
+
prefix: context_attribute(:tenant_elasticsearch_prefix),
|
|
45
|
+
cluster: health['cluster_name'],
|
|
46
|
+
health: health['status']
|
|
47
|
+
}
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def apply_model_index_prefix(prefix)
|
|
51
|
+
return unless defined?(Elasticsearch::Model)
|
|
52
|
+
return unless Elasticsearch::Model.respond_to?(:index_name_prefix=)
|
|
53
|
+
|
|
54
|
+
Elasticsearch::Model.index_name_prefix = prefix
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def switch_message(prefix)
|
|
58
|
+
prefix ? "Setting Elasticsearch index prefix: #{prefix}" : 'Resetting Elasticsearch index prefix to default'
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
@@ -1,28 +1,56 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require 'forwardable'
|
|
4
3
|
require_relative 'base_connection_handler'
|
|
5
4
|
|
|
6
5
|
module ConsoleKit
|
|
7
6
|
module Connections
|
|
8
7
|
# Handles MongoDB connections
|
|
9
8
|
class MongoConnectionHandler < BaseConnectionHandler
|
|
10
|
-
extend Forwardable
|
|
11
|
-
|
|
12
|
-
def_delegator :@context, :tenant_mongo_db
|
|
13
|
-
|
|
14
9
|
def connect
|
|
15
|
-
db = tenant_mongo_db.presence
|
|
10
|
+
db = context_attribute(:tenant_mongo_db).presence
|
|
16
11
|
Output.print_info(switch_message(db))
|
|
17
|
-
Mongoid.
|
|
12
|
+
Mongoid.override_database(db)
|
|
18
13
|
rescue NoMethodError
|
|
19
|
-
Output.print_warning('Mongoid.
|
|
14
|
+
Output.print_warning('Mongoid.override_database is not available in this version of Mongoid.')
|
|
20
15
|
end
|
|
21
16
|
|
|
22
17
|
def available? = defined?(Mongoid)
|
|
23
18
|
|
|
19
|
+
def diagnostics
|
|
20
|
+
name = 'MongoDB'
|
|
21
|
+
return unavailable_diagnostics(name) unless available?
|
|
22
|
+
|
|
23
|
+
db = tenant_database
|
|
24
|
+
latency = measure_latency { db.command(ping: 1) }
|
|
25
|
+
build_mongo_diagnostics(db, latency)
|
|
26
|
+
rescue StandardError => e
|
|
27
|
+
error_diagnostics(name, e)
|
|
28
|
+
end
|
|
29
|
+
|
|
24
30
|
private
|
|
25
31
|
|
|
32
|
+
def tenant_database
|
|
33
|
+
override = context_attribute(:tenant_mongo_db).presence
|
|
34
|
+
client = Mongoid.default_client
|
|
35
|
+
return client.database unless override
|
|
36
|
+
|
|
37
|
+
client.use(override).database
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def build_mongo_diagnostics(database, latency)
|
|
41
|
+
build_info = database.command(buildInfo: 1).first
|
|
42
|
+
{
|
|
43
|
+
name: 'MongoDB',
|
|
44
|
+
status: :connected,
|
|
45
|
+
latency_ms: latency,
|
|
46
|
+
details: mongo_details(database.name, build_info['version'])
|
|
47
|
+
}
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def mongo_details(name, version)
|
|
51
|
+
{ database: name, version: version }
|
|
52
|
+
end
|
|
53
|
+
|
|
26
54
|
def switch_message(db)
|
|
27
55
|
db ? "Switching to MongoDB client: #{db}" : 'Resetting MongoDB client to default'
|
|
28
56
|
end
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'base_connection_handler'
|
|
4
|
+
|
|
5
|
+
module ConsoleKit
|
|
6
|
+
module Connections
|
|
7
|
+
# Handles Redis connections
|
|
8
|
+
class RedisConnectionHandler < BaseConnectionHandler
|
|
9
|
+
DEFAULT_REDIS_DB = 0
|
|
10
|
+
|
|
11
|
+
def connect
|
|
12
|
+
db = context_attribute(:tenant_redis_db) || DEFAULT_REDIS_DB
|
|
13
|
+
Output.print_info(switch_message(db))
|
|
14
|
+
select_redis_db(db)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def available? = defined?(Redis)
|
|
18
|
+
|
|
19
|
+
def diagnostics
|
|
20
|
+
name = 'Redis'
|
|
21
|
+
return unavailable_diagnostics(name) unless available?
|
|
22
|
+
|
|
23
|
+
redis = fetch_redis_client
|
|
24
|
+
return unavailable_diagnostics(name) unless redis
|
|
25
|
+
|
|
26
|
+
latency = measure_latency { redis.ping }
|
|
27
|
+
build_redis_diagnostics(redis.info, latency)
|
|
28
|
+
rescue StandardError => e
|
|
29
|
+
error_diagnostics(name, e)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
private
|
|
33
|
+
|
|
34
|
+
def fetch_redis_client
|
|
35
|
+
Redis.respond_to?(:current) && Redis.current
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def build_redis_diagnostics(info, latency)
|
|
39
|
+
{
|
|
40
|
+
name: 'Redis',
|
|
41
|
+
status: :connected,
|
|
42
|
+
latency_ms: latency,
|
|
43
|
+
details: redis_details(info)
|
|
44
|
+
}
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def redis_details(info)
|
|
48
|
+
{
|
|
49
|
+
db: context_attribute(:tenant_redis_db) || DEFAULT_REDIS_DB,
|
|
50
|
+
version: info['redis_version'],
|
|
51
|
+
memory: info['used_memory_human']
|
|
52
|
+
}
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def select_redis_db(db)
|
|
56
|
+
redis = fetch_redis_client
|
|
57
|
+
if redis
|
|
58
|
+
redis.select(db)
|
|
59
|
+
elsif defined?(RedisClient) && db != DEFAULT_REDIS_DB
|
|
60
|
+
warn_about_redis_client(db)
|
|
61
|
+
end
|
|
62
|
+
rescue NoMethodError
|
|
63
|
+
Output.print_warning('Redis.current is not available (deprecated in Redis v5+).')
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def warn_about_redis_client(db)
|
|
67
|
+
Output.print_warning("Redis DB #{db} configured but auto-select not supported with RedisClient. " \
|
|
68
|
+
'Ensure your Redis configuration sets the correct DB.')
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def switch_message(db)
|
|
72
|
+
db ? "Switching to Redis DB: #{db}" : 'Resetting Redis connection to default'
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
@@ -1,28 +1,59 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require 'forwardable'
|
|
4
3
|
require_relative 'base_connection_handler'
|
|
5
4
|
|
|
6
5
|
module ConsoleKit
|
|
7
6
|
module Connections
|
|
8
7
|
# Handles SQL connections
|
|
9
8
|
class SqlConnectionHandler < BaseConnectionHandler
|
|
10
|
-
extend Forwardable
|
|
11
|
-
|
|
12
|
-
def_delegator :@context, :tenant_shard
|
|
13
|
-
|
|
14
9
|
def connect
|
|
15
|
-
shard = tenant_shard.presence&.to_sym
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
base_class.establish_connection(shard)
|
|
10
|
+
shard = context_attribute(:tenant_shard).presence&.to_sym
|
|
11
|
+
Output.print_info("#{connection_message(shard)} via #{base_class}")
|
|
12
|
+
disconnect_existing_pool
|
|
13
|
+
shard ? base_class.establish_connection(shard) : base_class.establish_connection
|
|
20
14
|
end
|
|
21
15
|
|
|
22
16
|
def available? = sql_base_class_name.to_s.safe_constantize.present?
|
|
23
17
|
|
|
18
|
+
def diagnostics
|
|
19
|
+
name = 'SQL'
|
|
20
|
+
return unavailable_diagnostics(name) unless available?
|
|
21
|
+
|
|
22
|
+
conn = base_class.connection
|
|
23
|
+
latency = measure_latency { conn.execute('SELECT 1') }
|
|
24
|
+
build_sql_diagnostics(conn, latency)
|
|
25
|
+
rescue StandardError => e
|
|
26
|
+
error_diagnostics(name, e)
|
|
27
|
+
end
|
|
28
|
+
|
|
24
29
|
private
|
|
25
30
|
|
|
31
|
+
def disconnect_existing_pool
|
|
32
|
+
return unless base_class.respond_to?(:connection_pool)
|
|
33
|
+
|
|
34
|
+
pool = base_class.connection_pool
|
|
35
|
+
pool&.disconnect!
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def build_sql_diagnostics(conn, latency)
|
|
39
|
+
{
|
|
40
|
+
name: 'SQL',
|
|
41
|
+
status: :connected,
|
|
42
|
+
latency_ms: latency,
|
|
43
|
+
details: {
|
|
44
|
+
adapter: conn.adapter_name,
|
|
45
|
+
pool_size: base_class.connection_pool.size,
|
|
46
|
+
version: fetch_sql_version(conn).to_s.truncate(50)
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def fetch_sql_version(conn)
|
|
52
|
+
conn.select_value('SELECT version()')
|
|
53
|
+
rescue StandardError
|
|
54
|
+
nil
|
|
55
|
+
end
|
|
56
|
+
|
|
26
57
|
def base_class
|
|
27
58
|
klass = sql_base_class_name.to_s.safe_constantize
|
|
28
59
|
return klass if klass
|
|
@@ -30,6 +61,10 @@ module ConsoleKit
|
|
|
30
61
|
raise Error, "ConsoleKit: sql_base_class '#{sql_base_class_name}' could not be found."
|
|
31
62
|
end
|
|
32
63
|
|
|
64
|
+
def connection_message(shard)
|
|
65
|
+
shard ? "Establishing SQL connection to shard: #{shard}" : 'Resetting SQL connection to default'
|
|
66
|
+
end
|
|
67
|
+
|
|
33
68
|
def sql_base_class_name = ConsoleKit.configuration.sql_base_class
|
|
34
69
|
end
|
|
35
70
|
end
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ConsoleKit
|
|
4
|
+
module Connections
|
|
5
|
+
# Formats raw diagnostic data for table display
|
|
6
|
+
module TableFormatter
|
|
7
|
+
class << self
|
|
8
|
+
def format_row(diag)
|
|
9
|
+
[
|
|
10
|
+
diag[:name],
|
|
11
|
+
format_status(diag[:status]),
|
|
12
|
+
format_latency(diag[:latency_ms]),
|
|
13
|
+
format_details(diag[:details])
|
|
14
|
+
]
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def format_status(status)
|
|
18
|
+
return "\u2713 Connected" if status == :connected
|
|
19
|
+
return "\u2717 Error" if %i[error timeout].include?(status)
|
|
20
|
+
return "\u2014 N/A" if status == :unavailable
|
|
21
|
+
|
|
22
|
+
'? Unknown'
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def format_latency(latency_ms)
|
|
26
|
+
latency_ms ? "#{latency_ms}ms" : "\u2014"
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def format_details(details)
|
|
30
|
+
return '' unless details&.any?
|
|
31
|
+
|
|
32
|
+
details.compact.map { |key, value| "#{key}: #{value}" }.join(', ')
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'table_formatter'
|
|
4
|
+
|
|
5
|
+
module ConsoleKit
|
|
6
|
+
module Connections
|
|
7
|
+
# Renders diagnostic data into a Unicode box-drawing table
|
|
8
|
+
module TableRenderer
|
|
9
|
+
class << self
|
|
10
|
+
def render(rows)
|
|
11
|
+
headers = %w[Service Status Latency Details]
|
|
12
|
+
table_rows = rows.map { |row| TableFormatter.format_row(row) }
|
|
13
|
+
widths = calculate_widths(headers, table_rows)
|
|
14
|
+
|
|
15
|
+
build_table(headers, table_rows, widths)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
private
|
|
19
|
+
|
|
20
|
+
def calculate_widths(headers, rows)
|
|
21
|
+
all_rows = [headers] + rows
|
|
22
|
+
headers.each_index.map do |index|
|
|
23
|
+
column_max_width(all_rows, index)
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def column_max_width(rows, index)
|
|
28
|
+
rows.map { |row| row[index].length }.max
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def build_table(headers, rows, widths)
|
|
32
|
+
lines = [table_top(widths), table_line(headers, widths), table_mid(widths)]
|
|
33
|
+
rows.each { |row| lines << table_line(row, widths) }
|
|
34
|
+
lines << table_bottom(widths)
|
|
35
|
+
lines.join("\n")
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def table_top(widths) = "\u250C#{widths.map { |width| "\u2500" * (width + 2) }.join("\u252C")}\u2510"
|
|
39
|
+
def table_mid(widths) = "\u251C#{widths.map { |width| "\u2500" * (width + 2) }.join("\u253C")}\u2524"
|
|
40
|
+
def table_bottom(widths) = "\u2514#{widths.map { |width| "\u2500" * (width + 2) }.join("\u2534")}\u2518"
|
|
41
|
+
|
|
42
|
+
def table_line(cells, widths)
|
|
43
|
+
content = cells.each_with_index.map { |cell, index| " #{cell.ljust(widths[index])} " }.join("\u2502")
|
|
44
|
+
"\u2502#{content}\u2502"
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ConsoleKit
|
|
4
|
+
# Helper methods available in the Rails console
|
|
5
|
+
module ConsoleHelpers
|
|
6
|
+
def switch_tenant = ConsoleKit.reset_current_tenant
|
|
7
|
+
|
|
8
|
+
def tenant_info
|
|
9
|
+
tenant = ConsoleKit::Setup.current_tenant
|
|
10
|
+
unless tenant
|
|
11
|
+
ConsoleKit::Output.print_warning('No tenant is currently configured.')
|
|
12
|
+
return
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
constants = ConsoleKit.configuration.tenants[tenant]&.[](:constants) || {}
|
|
16
|
+
print_tenant_details(tenant, constants)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def dashboard = ConsoleKit::Connections::Dashboard.display
|
|
20
|
+
|
|
21
|
+
def tenants
|
|
22
|
+
names = ConsoleKit.configuration.tenants&.keys || []
|
|
23
|
+
ConsoleKit::Output.print_list(names, header: 'Available Tenants')
|
|
24
|
+
names
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
DETAIL_LABELS = {
|
|
28
|
+
'Partner' => :partner_code, 'Shard' => :shard, 'Mongo DB' => :mongo_db,
|
|
29
|
+
'Redis DB' => :redis_db, 'ES Prefix' => :elasticsearch_prefix, 'Environment' => :environment
|
|
30
|
+
}.freeze
|
|
31
|
+
|
|
32
|
+
private
|
|
33
|
+
|
|
34
|
+
def print_tenant_details(tenant, constants)
|
|
35
|
+
ConsoleKit::Output.print_header("Tenant: #{tenant}")
|
|
36
|
+
DETAIL_LABELS.each do |label, key|
|
|
37
|
+
constants.fetch(key, :missing).then do |val|
|
|
38
|
+
case val
|
|
39
|
+
when :missing then next
|
|
40
|
+
else ConsoleKit::Output.print_info(" #{label.ljust(13)}#{val}")
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
nil
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
data/lib/console_kit/output.rb
CHANGED
|
@@ -34,7 +34,7 @@ module ConsoleKit
|
|
|
34
34
|
return if silent
|
|
35
35
|
|
|
36
36
|
formatted = (type == :header ? "\n--- #{text} ---" : text)
|
|
37
|
-
print_with(type, formatted, timestamp, newline: newline)
|
|
37
|
+
print_with(type, formatted, timestamp: timestamp, newline: newline)
|
|
38
38
|
end
|
|
39
39
|
end
|
|
40
40
|
|
|
@@ -51,19 +51,24 @@ module ConsoleKit
|
|
|
51
51
|
puts text
|
|
52
52
|
end
|
|
53
53
|
|
|
54
|
-
# Backtrace prints always with timestamp, no param
|
|
55
54
|
def print_backtrace(exception)
|
|
56
55
|
return if silent
|
|
57
56
|
|
|
58
|
-
exception&.backtrace&.each
|
|
57
|
+
exception&.backtrace&.each do |line|
|
|
58
|
+
print_with(:trace, " #{line}", timestamp: true)
|
|
59
|
+
end
|
|
59
60
|
end
|
|
60
61
|
|
|
61
62
|
private
|
|
62
63
|
|
|
63
|
-
def print_with(type, text,
|
|
64
|
+
def print_with(type, text, options = {})
|
|
65
|
+
opts = options.is_a?(Hash) ? options : { timestamp: options }
|
|
64
66
|
meta = TYPES.fetch(type)
|
|
65
|
-
message = build_message(text, meta[:symbol], timestamp)
|
|
66
|
-
|
|
67
|
+
message = build_message(text, meta[:symbol], opts[:timestamp])
|
|
68
|
+
color = meta[:color]
|
|
69
|
+
formatted = ConsoleKit.configuration.pretty_output && color ? "\e[#{color}m#{message}\e[0m" : message
|
|
70
|
+
|
|
71
|
+
opts.fetch(:newline, true) ? puts(formatted) : print(formatted)
|
|
67
72
|
end
|
|
68
73
|
|
|
69
74
|
def build_message(text, symbol, timestamp)
|
|
@@ -73,13 +78,6 @@ module ConsoleKit
|
|
|
73
78
|
def prefix_for(value) = value ? yield(value) : ''
|
|
74
79
|
def timestamp_prefix(timestamp) = prefix_for(timestamp) { Time.current.strftime('[%Y-%m-%d %H:%M:%S] ') }
|
|
75
80
|
def symbol_prefix(symbol) = prefix_for(symbol) { |sym| "#{sym} " }
|
|
76
|
-
|
|
77
|
-
def output(message, color, newline: true)
|
|
78
|
-
method = newline ? :puts : :print
|
|
79
|
-
return send(method, message) unless ConsoleKit.configuration.pretty_output && color
|
|
80
|
-
|
|
81
|
-
send(method, "\e[#{color}m#{message}\e[0m")
|
|
82
|
-
end
|
|
83
81
|
end
|
|
84
82
|
end
|
|
85
83
|
end
|