console_kit 1.1.0 → 1.3.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/.DS_Store +0 -0
- data/.reek.yml.new +0 -0
- data/lib/console_kit/configuration.rb +20 -16
- data/lib/console_kit/connections/base_connection_handler.rb +36 -2
- 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 +48 -5
- data/lib/console_kit/connections/mongo_connection_handler.rb +54 -5
- data/lib/console_kit/connections/redis_connection_handler.rb +49 -11
- data/lib/console_kit/connections/sql_connection_handler.rb +49 -7
- 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 +42 -16
- data/lib/console_kit/output.rb +19 -11
- data/lib/console_kit/prompt.rb +17 -10
- data/lib/console_kit/railtie.rb +3 -3
- data/lib/console_kit/setup.rb +6 -116
- data/lib/console_kit/setup_ui.rb +37 -0
- data/lib/console_kit/tenant_configurator/context_wrapper.rb +79 -0
- data/lib/console_kit/tenant_configurator.rb +64 -56
- data/lib/console_kit/tenant_orchestrator.rb +108 -0
- data/lib/console_kit/tenant_selector.rb +8 -4
- data/lib/console_kit/version.rb +1 -1
- data/lib/console_kit.rb +6 -0
- data/lib/generators/console_kit/templates/console_kit.rb +4 -0
- metadata +41 -9
- data/CHANGELOG.md +0 -109
- data/CODE_OF_CONDUCT.md +0 -132
- data/README.md +0 -163
- data/SECURITY.md +0 -31
- 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: 348d99d0b8d74a78d96ffa4e74f7ee2baf25aab659700b5d44929d2e3cd87104
|
|
4
|
+
data.tar.gz: 9c5e7514f498818e2ffe8b0a9376511cbf364e81336a266df7fe02d143a26cf4
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 5e56f04db89485e9b0a3a23b667062a8998287a8ab9ad67dc793f3202730ba6f54dcf02f45ab65ff739e3aef23ec7c65574ba32c4aafe7cebc1a6dcff63c907f
|
|
7
|
+
data.tar.gz: 86d6d4457bb2ad62e8ac2ecdd22c3afc0fa72313f4625cf4b304bbd1f0436a67033e0a2c4190d995895b12aba4aef77c68465ffa1fe1dcf6815d93909f355394
|
data/.DS_Store
ADDED
|
Binary file
|
data/.reek.yml.new
ADDED
|
File without changes
|
|
@@ -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,18 @@ 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
|
-
|
|
40
|
-
|
|
41
|
-
|
|
41
|
+
def resolve_context_class(val)
|
|
42
|
+
klass = val.to_s.safe_constantize
|
|
43
|
+
return klass if klass
|
|
44
|
+
|
|
45
|
+
raise Error, "ConsoleKit: context_class '#{val}' could not be found. " \
|
|
42
46
|
'Ensure the class is defined before configuration is accessed.'
|
|
43
47
|
end
|
|
44
48
|
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
|
|
@@ -15,12 +20,41 @@ module ConsoleKit
|
|
|
15
20
|
def initialize(context) = @context = context
|
|
16
21
|
def connect = raise NotImplementedError, "#{self.class} must implement #connect"
|
|
17
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
|
+
timeout_diagnostics(handler_name, timeout)
|
|
33
|
+
end
|
|
34
|
+
end
|
|
18
35
|
|
|
19
36
|
private
|
|
20
37
|
|
|
21
|
-
def
|
|
22
|
-
|
|
38
|
+
def spawn_diagnostic_thread(handler_name)
|
|
39
|
+
wrapper = { value: nil }
|
|
40
|
+
thread = Thread.new { wrapper[:value] = run_diagnostics_safely(handler_name) { diagnostics } }
|
|
41
|
+
[thread, wrapper]
|
|
23
42
|
end
|
|
43
|
+
|
|
44
|
+
def run_diagnostics_safely(name)
|
|
45
|
+
yield
|
|
46
|
+
rescue StandardError => e
|
|
47
|
+
error_diagnostics(name, e)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def measure_latency
|
|
51
|
+
start = clock_time
|
|
52
|
+
yield
|
|
53
|
+
((clock_time - start) * 1000).round(1)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def context_attribute(name) = @context.try(name)
|
|
57
|
+
def unavailable_diagnostics(name) = { name: name, status: :unavailable, latency_ms: nil, details: {} }
|
|
24
58
|
end
|
|
25
59
|
end
|
|
26
60
|
end
|
|
@@ -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
|
|
@@ -6,21 +6,64 @@ module ConsoleKit
|
|
|
6
6
|
module Connections
|
|
7
7
|
# Handles Elasticsearch connections
|
|
8
8
|
class ElasticsearchConnectionHandler < BaseConnectionHandler
|
|
9
|
+
class << self
|
|
10
|
+
def elasticsearch_available?
|
|
11
|
+
return false unless defined?(Elasticsearch::Model)
|
|
12
|
+
|
|
13
|
+
Elasticsearch::Model.method(:client)
|
|
14
|
+
true
|
|
15
|
+
rescue NameError
|
|
16
|
+
false
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def apply_prefix(prefix)
|
|
20
|
+
return unless defined?(Elasticsearch::Model)
|
|
21
|
+
|
|
22
|
+
Elasticsearch::Model.try(:index_name_prefix=, prefix)
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
9
26
|
def connect
|
|
10
27
|
prefix = context_attribute(:tenant_elasticsearch_prefix).presence
|
|
11
28
|
Output.print_info(switch_message(prefix))
|
|
12
29
|
Thread.current[:console_kit_elasticsearch_prefix] = prefix
|
|
13
|
-
|
|
30
|
+
self.class.apply_prefix(prefix)
|
|
14
31
|
end
|
|
15
32
|
|
|
16
|
-
def available? =
|
|
33
|
+
def available? = self.class.elasticsearch_available?
|
|
34
|
+
|
|
35
|
+
def diagnostics
|
|
36
|
+
return unavailable_diagnostics('Elasticsearch') unless available?
|
|
37
|
+
|
|
38
|
+
perform_diagnostics
|
|
39
|
+
rescue StandardError => e
|
|
40
|
+
error_diagnostics('Elasticsearch', e)
|
|
41
|
+
end
|
|
17
42
|
|
|
18
43
|
private
|
|
19
44
|
|
|
20
|
-
def
|
|
21
|
-
|
|
45
|
+
def perform_diagnostics
|
|
46
|
+
client = Elasticsearch::Model.client
|
|
47
|
+
latency = measure_latency do
|
|
48
|
+
client.ping
|
|
49
|
+
rescue StandardError
|
|
50
|
+
nil
|
|
51
|
+
end
|
|
52
|
+
health = client.cluster.health
|
|
53
|
+
build_elasticsearch_diagnostics(health['cluster_name'], health['status'], latency)
|
|
54
|
+
end
|
|
22
55
|
|
|
23
|
-
|
|
56
|
+
def build_elasticsearch_diagnostics(cluster, status, latency)
|
|
57
|
+
{
|
|
58
|
+
name: 'Elasticsearch',
|
|
59
|
+
status: :connected,
|
|
60
|
+
latency_ms: latency,
|
|
61
|
+
details: {
|
|
62
|
+
prefix: context_attribute(:tenant_elasticsearch_prefix),
|
|
63
|
+
cluster: cluster,
|
|
64
|
+
health: status
|
|
65
|
+
}
|
|
66
|
+
}
|
|
24
67
|
end
|
|
25
68
|
|
|
26
69
|
def switch_message(prefix)
|
|
@@ -8,18 +8,67 @@ module ConsoleKit
|
|
|
8
8
|
class MongoConnectionHandler < BaseConnectionHandler
|
|
9
9
|
def connect
|
|
10
10
|
db = context_attribute(:tenant_mongo_db).presence
|
|
11
|
-
|
|
12
|
-
Mongoid.override_database(db)
|
|
11
|
+
switch_mongo(db)
|
|
13
12
|
rescue NoMethodError
|
|
14
|
-
Output.print_warning('Mongoid
|
|
13
|
+
Output.print_warning('Mongoid client override is not available in this version of Mongoid.')
|
|
15
14
|
end
|
|
16
15
|
|
|
17
16
|
def available? = defined?(Mongoid)
|
|
18
17
|
|
|
18
|
+
def diagnostics
|
|
19
|
+
return unavailable_diagnostics('MongoDB') unless available?
|
|
20
|
+
|
|
21
|
+
perform_diagnostics
|
|
22
|
+
rescue StandardError => e
|
|
23
|
+
error_diagnostics('MongoDB', e)
|
|
24
|
+
end
|
|
25
|
+
|
|
19
26
|
private
|
|
20
27
|
|
|
21
|
-
def
|
|
22
|
-
db
|
|
28
|
+
def perform_diagnostics
|
|
29
|
+
db = tenant_database
|
|
30
|
+
latency = measure_latency { db.command(ping: 1) }
|
|
31
|
+
info = db.command(buildInfo: 1).first
|
|
32
|
+
build_mongo_diagnostics(db.name, info['version'], latency)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def build_mongo_diagnostics(name, version, latency)
|
|
36
|
+
{
|
|
37
|
+
name: 'MongoDB',
|
|
38
|
+
status: :connected,
|
|
39
|
+
latency_ms: latency,
|
|
40
|
+
details: { database: name, version: version }
|
|
41
|
+
}
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def tenant_database
|
|
45
|
+
override = context_attribute(:tenant_mongo_db).presence
|
|
46
|
+
client = Mongoid.default_client
|
|
47
|
+
(override ? client.use(override) : client).database
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def switch_mongo(db)
|
|
51
|
+
if db.nil?
|
|
52
|
+
Output.print_info('Resetting MongoDB client to default')
|
|
53
|
+
reset_overrides
|
|
54
|
+
elsif named_client?(db)
|
|
55
|
+
Output.print_info("Switching to MongoDB client: #{db}")
|
|
56
|
+
Mongoid.override_client(db)
|
|
57
|
+
else
|
|
58
|
+
Output.print_info("Switching to MongoDB database: #{db}")
|
|
59
|
+
Mongoid.override_database(db)
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def reset_overrides
|
|
64
|
+
Mongoid.override_client(nil) if Mongoid.respond_to?(:override_client)
|
|
65
|
+
Mongoid.override_database(nil)
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def named_client?(name)
|
|
69
|
+
Mongoid::Config.clients.key?(name.to_s)
|
|
70
|
+
rescue StandardError
|
|
71
|
+
false
|
|
23
72
|
end
|
|
24
73
|
end
|
|
25
74
|
end
|
|
@@ -8,29 +8,67 @@ module ConsoleKit
|
|
|
8
8
|
class RedisConnectionHandler < BaseConnectionHandler
|
|
9
9
|
DEFAULT_REDIS_DB = 0
|
|
10
10
|
|
|
11
|
+
class << self
|
|
12
|
+
def redis_client = Redis.try(:current)
|
|
13
|
+
|
|
14
|
+
def warn_no_auto_select(db_index)
|
|
15
|
+
Output.print_warning("Redis DB #{db_index} configured but auto-select not supported with RedisClient. " \
|
|
16
|
+
'Ensure your Redis configuration sets the correct DB.')
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
11
20
|
def connect
|
|
12
|
-
|
|
13
|
-
Output.print_info(switch_message(
|
|
14
|
-
select_redis_db(
|
|
21
|
+
db_index = context_attribute(:tenant_redis_db) || DEFAULT_REDIS_DB
|
|
22
|
+
Output.print_info(switch_message(db_index))
|
|
23
|
+
select_redis_db(db_index)
|
|
15
24
|
end
|
|
16
25
|
|
|
17
26
|
def available? = defined?(Redis)
|
|
18
27
|
|
|
28
|
+
def diagnostics
|
|
29
|
+
redis = self.class.redis_client if available?
|
|
30
|
+
return unavailable_diagnostics('Redis') unless redis
|
|
31
|
+
|
|
32
|
+
perform_diagnostics(redis)
|
|
33
|
+
rescue StandardError => e
|
|
34
|
+
error_diagnostics('Redis', e)
|
|
35
|
+
end
|
|
36
|
+
|
|
19
37
|
private
|
|
20
38
|
|
|
21
|
-
def
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
39
|
+
def perform_diagnostics(redis)
|
|
40
|
+
latency = measure_latency { redis.ping }
|
|
41
|
+
info = redis.info
|
|
42
|
+
build_redis_diagnostics(info['redis_version'], info['used_memory_human'], latency)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def build_redis_diagnostics(version, memory, latency)
|
|
46
|
+
{
|
|
47
|
+
name: 'Redis',
|
|
48
|
+
status: :connected,
|
|
49
|
+
latency_ms: latency,
|
|
50
|
+
details: {
|
|
51
|
+
db: context_attribute(:tenant_redis_db) || DEFAULT_REDIS_DB,
|
|
52
|
+
version: version,
|
|
53
|
+
memory: memory
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def select_redis_db(db_index)
|
|
59
|
+
klass = self.class
|
|
60
|
+
redis = klass.redis_client
|
|
61
|
+
if redis
|
|
62
|
+
redis.select(db_index)
|
|
63
|
+
elsif defined?(RedisClient) && db_index != DEFAULT_REDIS_DB
|
|
64
|
+
klass.warn_no_auto_select(db_index)
|
|
27
65
|
end
|
|
28
66
|
rescue NoMethodError
|
|
29
67
|
Output.print_warning('Redis.current is not available (deprecated in Redis v5+).')
|
|
30
68
|
end
|
|
31
69
|
|
|
32
|
-
def switch_message(
|
|
33
|
-
|
|
70
|
+
def switch_message(db_index)
|
|
71
|
+
db_index ? "Switching to Redis DB: #{db_index}" : 'Resetting Redis connection to default'
|
|
34
72
|
end
|
|
35
73
|
end
|
|
36
74
|
end
|
|
@@ -6,28 +6,70 @@ module ConsoleKit
|
|
|
6
6
|
module Connections
|
|
7
7
|
# Handles SQL connections
|
|
8
8
|
class SqlConnectionHandler < BaseConnectionHandler
|
|
9
|
+
class << self
|
|
10
|
+
def sql_version(conn)
|
|
11
|
+
conn.select_value('SELECT version()')
|
|
12
|
+
rescue StandardError
|
|
13
|
+
nil
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def base_class_name = ConsoleKit.configuration.sql_base_class
|
|
17
|
+
end
|
|
18
|
+
|
|
9
19
|
def connect
|
|
10
20
|
shard = context_attribute(:tenant_shard).presence&.to_sym
|
|
11
21
|
Output.print_info("#{connection_message(shard)} via #{base_class}")
|
|
22
|
+
disconnect_existing_pool
|
|
12
23
|
shard ? base_class.establish_connection(shard) : base_class.establish_connection
|
|
13
24
|
end
|
|
14
25
|
|
|
15
|
-
def available? =
|
|
26
|
+
def available? = self.class.base_class_name.to_s.safe_constantize.present?
|
|
27
|
+
|
|
28
|
+
def diagnostics
|
|
29
|
+
return unavailable_diagnostics('SQL') unless available?
|
|
30
|
+
|
|
31
|
+
perform_diagnostics
|
|
32
|
+
rescue StandardError => e
|
|
33
|
+
error_diagnostics('SQL', e)
|
|
34
|
+
end
|
|
16
35
|
|
|
17
36
|
private
|
|
18
37
|
|
|
19
|
-
def
|
|
20
|
-
|
|
21
|
-
|
|
38
|
+
def perform_diagnostics
|
|
39
|
+
conn = base_class.connection
|
|
40
|
+
latency = measure_latency { conn.execute('SELECT 1') }
|
|
41
|
+
build_sql_diagnostics(conn, latency)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def disconnect_existing_pool
|
|
45
|
+
pool = base_class.try(:connection_pool)
|
|
46
|
+
pool&.disconnect!
|
|
47
|
+
end
|
|
22
48
|
|
|
23
|
-
|
|
49
|
+
def build_sql_diagnostics(conn, latency)
|
|
50
|
+
{
|
|
51
|
+
name: 'SQL',
|
|
52
|
+
status: :connected,
|
|
53
|
+
latency_ms: latency,
|
|
54
|
+
details: {
|
|
55
|
+
adapter: conn.adapter_name,
|
|
56
|
+
pool_size: base_class.connection_pool.size,
|
|
57
|
+
version: self.class.sql_version(conn).to_s.truncate(50)
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def base_class
|
|
63
|
+
@base_class ||= begin
|
|
64
|
+
name = self.class.base_class_name
|
|
65
|
+
klass = name.to_s.safe_constantize
|
|
66
|
+
klass || raise(Error, "ConsoleKit: sql_base_class '#{name}' could not be found.")
|
|
67
|
+
end
|
|
24
68
|
end
|
|
25
69
|
|
|
26
70
|
def connection_message(shard)
|
|
27
71
|
shard ? "Establishing SQL connection to shard: #{shard}" : 'Resetting SQL connection to default'
|
|
28
72
|
end
|
|
29
|
-
|
|
30
|
-
def sql_base_class_name = ConsoleKit.configuration.sql_base_class
|
|
31
73
|
end
|
|
32
74
|
end
|
|
33
75
|
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
|
|
@@ -3,36 +3,62 @@
|
|
|
3
3
|
module ConsoleKit
|
|
4
4
|
# Helper methods available in the Rails console
|
|
5
5
|
module ConsoleHelpers
|
|
6
|
-
def switch_tenant
|
|
6
|
+
def switch_tenant
|
|
7
|
+
ConsoleKit.reset_current_tenant
|
|
8
|
+
self
|
|
9
|
+
end
|
|
7
10
|
|
|
8
11
|
def tenant_info
|
|
9
12
|
tenant = ConsoleKit::Setup.current_tenant
|
|
10
|
-
unless tenant
|
|
11
|
-
ConsoleKit::Output.print_warning('No tenant is currently configured.')
|
|
12
|
-
return
|
|
13
|
-
end
|
|
13
|
+
return no_tenant_warning unless tenant
|
|
14
14
|
|
|
15
|
-
|
|
16
|
-
|
|
15
|
+
display_tenant_info(tenant)
|
|
16
|
+
nil
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def dashboard
|
|
20
|
+
ConsoleKit::Connections::Dashboard.display
|
|
21
|
+
self
|
|
17
22
|
end
|
|
18
23
|
|
|
19
24
|
def tenants
|
|
20
25
|
names = ConsoleKit.configuration.tenants&.keys || []
|
|
21
|
-
|
|
26
|
+
print_available_tenants(names)
|
|
22
27
|
names
|
|
23
28
|
end
|
|
24
29
|
|
|
30
|
+
DETAIL_LABELS = {
|
|
31
|
+
'Partner' => :partner_code, 'Shard' => :shard, 'Mongo DB' => :mongo_db,
|
|
32
|
+
'Redis DB' => :redis_db, 'ES Prefix' => :elasticsearch_prefix, 'Environment' => :environment
|
|
33
|
+
}.freeze
|
|
34
|
+
|
|
25
35
|
private
|
|
26
36
|
|
|
27
|
-
def
|
|
28
|
-
ConsoleKit::Output.
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
37
|
+
def no_tenant_warning
|
|
38
|
+
ConsoleKit::Output.print_warning('No tenant is currently configured.')
|
|
39
|
+
self
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def display_tenant_info(tenant)
|
|
43
|
+
constants = ConsoleKit.configuration.tenants[tenant]&.[](:constants) || {}
|
|
44
|
+
ConsoleHelpers.print_tenant_details(tenant, constants)
|
|
45
|
+
self
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def print_available_tenants(names)
|
|
49
|
+
ConsoleKit::Output.print_list(names, header: 'Available Tenants')
|
|
50
|
+
self
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
class << self
|
|
54
|
+
def print_tenant_details(tenant, constants)
|
|
55
|
+
ConsoleKit::Output.print_header("Tenant: #{tenant}")
|
|
56
|
+
DETAIL_LABELS.each do |label, key|
|
|
57
|
+
next unless constants.key?(key)
|
|
58
|
+
|
|
59
|
+
ConsoleKit::Output.print_info(" #{label.ljust(13)}#{constants[key]}")
|
|
60
|
+
end
|
|
34
61
|
end
|
|
35
|
-
nil
|
|
36
62
|
end
|
|
37
63
|
end
|
|
38
64
|
end
|