capydash 0.2.0 → 0.2.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.
- checksums.yaml +4 -4
- data/README.md +70 -171
- data/capydash.gemspec +7 -13
- data/lib/capydash/rspec.rb +567 -0
- data/lib/capydash/templates/report.html.erb +7 -24
- data/lib/capydash/version.rb +2 -2
- data/lib/capydash.rb +10 -59
- metadata +13 -84
- data/lib/capydash/auth.rb +0 -103
- data/lib/capydash/configuration.rb +0 -186
- data/lib/capydash/dashboard_server.rb +0 -167
- data/lib/capydash/engine.rb +0 -52
- data/lib/capydash/error_handler.rb +0 -101
- data/lib/capydash/event_emitter.rb +0 -29
- data/lib/capydash/forwarder.rb +0 -78
- data/lib/capydash/instrumentation.rb +0 -153
- data/lib/capydash/logger.rb +0 -99
- data/lib/capydash/persistence.rb +0 -134
- data/lib/capydash/report_generator.rb +0 -1007
- data/lib/capydash/rspec_integration.rb +0 -285
- data/lib/capydash/test_data_aggregator.rb +0 -221
- data/lib/capydash/test_data_collector.rb +0 -58
- data/lib/generators/capydash/install_generator.rb +0 -124
- data/lib/tasks/capydash.rake +0 -67
data/lib/capydash.rb
CHANGED
|
@@ -1,64 +1,15 @@
|
|
|
1
|
-
require 'ostruct'
|
|
2
1
|
require "capydash/version"
|
|
3
|
-
require "capydash/engine"
|
|
4
|
-
require "capydash/instrumentation"
|
|
5
|
-
require "capydash/event_emitter"
|
|
6
|
-
require "capydash/dashboard_server"
|
|
7
|
-
require "capydash/configuration"
|
|
8
|
-
require "capydash/logger"
|
|
9
|
-
require "capydash/error_handler"
|
|
10
|
-
require "capydash/persistence"
|
|
11
|
-
require "capydash/auth"
|
|
12
|
-
require "capydash/test_data_collector"
|
|
13
|
-
require "capydash/test_data_aggregator"
|
|
14
|
-
require "capydash/report_generator"
|
|
15
2
|
|
|
16
|
-
#
|
|
17
|
-
if defined?(RSpec)
|
|
18
|
-
require "capydash/
|
|
19
|
-
CapyDash::
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
def configure
|
|
27
|
-
self.configuration ||= OpenStruct.new
|
|
28
|
-
yield(configuration)
|
|
29
|
-
end
|
|
30
|
-
|
|
31
|
-
def config
|
|
32
|
-
@config ||= Configuration.load_from_file
|
|
33
|
-
end
|
|
34
|
-
|
|
35
|
-
def config=(new_config)
|
|
36
|
-
@config = new_config
|
|
37
|
-
end
|
|
38
|
-
|
|
39
|
-
# Convenience methods for common operations
|
|
40
|
-
def log_info(message, context = {})
|
|
41
|
-
Logger.info(message, context)
|
|
42
|
-
end
|
|
43
|
-
|
|
44
|
-
def log_error(message, context = {})
|
|
45
|
-
Logger.error(message, context)
|
|
46
|
-
end
|
|
47
|
-
|
|
48
|
-
def handle_error(error, context = {})
|
|
49
|
-
ErrorHandler.handle_error(error, context)
|
|
50
|
-
end
|
|
51
|
-
|
|
52
|
-
def save_test_run(data)
|
|
53
|
-
Persistence.save_test_run(data)
|
|
54
|
-
end
|
|
55
|
-
|
|
56
|
-
def load_test_run(run_id)
|
|
57
|
-
Persistence.load_test_run(run_id)
|
|
58
|
-
end
|
|
59
|
-
|
|
60
|
-
def list_test_runs(limit = 50)
|
|
61
|
-
Persistence.list_test_runs(limit)
|
|
3
|
+
# Auto-setup RSpec integration if RSpec is present and ready
|
|
4
|
+
if defined?(RSpec) && RSpec.respond_to?(:configure)
|
|
5
|
+
require "capydash/rspec"
|
|
6
|
+
CapyDash::RSpec.setup!
|
|
7
|
+
elsif defined?(Rails)
|
|
8
|
+
# In Rails, RSpec might load after the gem, so set up a hook
|
|
9
|
+
Rails.application.config.after_initialize do
|
|
10
|
+
if defined?(RSpec) && RSpec.respond_to?(:configure)
|
|
11
|
+
require "capydash/rspec" unless defined?(CapyDash::RSpec)
|
|
12
|
+
CapyDash::RSpec.setup!
|
|
62
13
|
end
|
|
63
14
|
end
|
|
64
15
|
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: capydash
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.2.
|
|
4
|
+
version: 0.2.2
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Damon Clark
|
|
@@ -10,21 +10,7 @@ cert_chain: []
|
|
|
10
10
|
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
11
|
dependencies:
|
|
12
12
|
- !ruby/object:Gem::Dependency
|
|
13
|
-
name:
|
|
14
|
-
requirement: !ruby/object:Gem::Requirement
|
|
15
|
-
requirements:
|
|
16
|
-
- - ">="
|
|
17
|
-
- !ruby/object:Gem::Version
|
|
18
|
-
version: '5.0'
|
|
19
|
-
type: :runtime
|
|
20
|
-
prerelease: false
|
|
21
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
-
requirements:
|
|
23
|
-
- - ">="
|
|
24
|
-
- !ruby/object:Gem::Version
|
|
25
|
-
version: '5.0'
|
|
26
|
-
- !ruby/object:Gem::Dependency
|
|
27
|
-
name: capybara
|
|
13
|
+
name: rspec
|
|
28
14
|
requirement: !ruby/object:Gem::Requirement
|
|
29
15
|
requirements:
|
|
30
16
|
- - ">="
|
|
@@ -38,77 +24,35 @@ dependencies:
|
|
|
38
24
|
- !ruby/object:Gem::Version
|
|
39
25
|
version: '3.0'
|
|
40
26
|
- !ruby/object:Gem::Dependency
|
|
41
|
-
name:
|
|
42
|
-
requirement: !ruby/object:Gem::Requirement
|
|
43
|
-
requirements:
|
|
44
|
-
- - ">="
|
|
45
|
-
- !ruby/object:Gem::Version
|
|
46
|
-
version: '0'
|
|
47
|
-
type: :runtime
|
|
48
|
-
prerelease: false
|
|
49
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
50
|
-
requirements:
|
|
51
|
-
- - ">="
|
|
52
|
-
- !ruby/object:Gem::Version
|
|
53
|
-
version: '0'
|
|
54
|
-
- !ruby/object:Gem::Dependency
|
|
55
|
-
name: eventmachine
|
|
56
|
-
requirement: !ruby/object:Gem::Requirement
|
|
57
|
-
requirements:
|
|
58
|
-
- - ">="
|
|
59
|
-
- !ruby/object:Gem::Version
|
|
60
|
-
version: '0'
|
|
61
|
-
type: :runtime
|
|
62
|
-
prerelease: false
|
|
63
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
64
|
-
requirements:
|
|
65
|
-
- - ">="
|
|
66
|
-
- !ruby/object:Gem::Version
|
|
67
|
-
version: '0'
|
|
68
|
-
- !ruby/object:Gem::Dependency
|
|
69
|
-
name: em-websocket
|
|
70
|
-
requirement: !ruby/object:Gem::Requirement
|
|
71
|
-
requirements:
|
|
72
|
-
- - ">="
|
|
73
|
-
- !ruby/object:Gem::Version
|
|
74
|
-
version: '0'
|
|
75
|
-
type: :runtime
|
|
76
|
-
prerelease: false
|
|
77
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
78
|
-
requirements:
|
|
79
|
-
- - ">="
|
|
80
|
-
- !ruby/object:Gem::Version
|
|
81
|
-
version: '0'
|
|
82
|
-
- !ruby/object:Gem::Dependency
|
|
83
|
-
name: rspec
|
|
27
|
+
name: rspec-rails
|
|
84
28
|
requirement: !ruby/object:Gem::Requirement
|
|
85
29
|
requirements:
|
|
86
30
|
- - "~>"
|
|
87
31
|
- !ruby/object:Gem::Version
|
|
88
|
-
version: '
|
|
32
|
+
version: '6.0'
|
|
89
33
|
type: :development
|
|
90
34
|
prerelease: false
|
|
91
35
|
version_requirements: !ruby/object:Gem::Requirement
|
|
92
36
|
requirements:
|
|
93
37
|
- - "~>"
|
|
94
38
|
- !ruby/object:Gem::Version
|
|
95
|
-
version: '
|
|
39
|
+
version: '6.0'
|
|
96
40
|
- !ruby/object:Gem::Dependency
|
|
97
41
|
name: rails
|
|
98
42
|
requirement: !ruby/object:Gem::Requirement
|
|
99
43
|
requirements:
|
|
100
|
-
- - "
|
|
44
|
+
- - ">="
|
|
101
45
|
- !ruby/object:Gem::Version
|
|
102
|
-
version: '
|
|
46
|
+
version: '6.0'
|
|
103
47
|
type: :development
|
|
104
48
|
prerelease: false
|
|
105
49
|
version_requirements: !ruby/object:Gem::Requirement
|
|
106
50
|
requirements:
|
|
107
|
-
- - "
|
|
51
|
+
- - ">="
|
|
108
52
|
- !ruby/object:Gem::Version
|
|
109
|
-
version: '
|
|
110
|
-
description: CapyDash
|
|
111
|
-
|
|
53
|
+
version: '6.0'
|
|
54
|
+
description: CapyDash automatically generates clean, readable HTML test reports after
|
|
55
|
+
your RSpec suite finishes. Zero configuration required.
|
|
112
56
|
email:
|
|
113
57
|
- dclark312@gmail.com
|
|
114
58
|
executables: []
|
|
@@ -118,24 +62,9 @@ files:
|
|
|
118
62
|
- README.md
|
|
119
63
|
- capydash.gemspec
|
|
120
64
|
- lib/capydash.rb
|
|
121
|
-
- lib/capydash/
|
|
122
|
-
- lib/capydash/configuration.rb
|
|
123
|
-
- lib/capydash/dashboard_server.rb
|
|
124
|
-
- lib/capydash/engine.rb
|
|
125
|
-
- lib/capydash/error_handler.rb
|
|
126
|
-
- lib/capydash/event_emitter.rb
|
|
127
|
-
- lib/capydash/forwarder.rb
|
|
128
|
-
- lib/capydash/instrumentation.rb
|
|
129
|
-
- lib/capydash/logger.rb
|
|
130
|
-
- lib/capydash/persistence.rb
|
|
131
|
-
- lib/capydash/report_generator.rb
|
|
132
|
-
- lib/capydash/rspec_integration.rb
|
|
65
|
+
- lib/capydash/rspec.rb
|
|
133
66
|
- lib/capydash/templates/report.html.erb
|
|
134
|
-
- lib/capydash/test_data_aggregator.rb
|
|
135
|
-
- lib/capydash/test_data_collector.rb
|
|
136
67
|
- lib/capydash/version.rb
|
|
137
|
-
- lib/generators/capydash/install_generator.rb
|
|
138
|
-
- lib/tasks/capydash.rake
|
|
139
68
|
homepage: https://github.com/damonclark/capydash
|
|
140
69
|
licenses:
|
|
141
70
|
- MIT
|
|
@@ -157,5 +86,5 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
157
86
|
requirements: []
|
|
158
87
|
rubygems_version: 3.6.7
|
|
159
88
|
specification_version: 4
|
|
160
|
-
summary:
|
|
89
|
+
summary: Minimal static HTML report generator for RSpec system tests
|
|
161
90
|
test_files: []
|
data/lib/capydash/auth.rb
DELETED
|
@@ -1,103 +0,0 @@
|
|
|
1
|
-
require 'securerandom'
|
|
2
|
-
require 'digest'
|
|
3
|
-
|
|
4
|
-
module CapyDash
|
|
5
|
-
class Auth
|
|
6
|
-
class << self
|
|
7
|
-
def authenticate(username, password)
|
|
8
|
-
return false unless auth_enabled?
|
|
9
|
-
|
|
10
|
-
# Simple hardcoded credentials for MVP
|
|
11
|
-
# In production, this would connect to a proper user database
|
|
12
|
-
valid_credentials = {
|
|
13
|
-
'admin' => 'capydash123',
|
|
14
|
-
'developer' => 'test123',
|
|
15
|
-
'viewer' => 'readonly123'
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
if valid_credentials[username] == password
|
|
19
|
-
token = generate_token(username)
|
|
20
|
-
Logger.info("User authenticated", {
|
|
21
|
-
username: username,
|
|
22
|
-
token: token[0..8] + "..."
|
|
23
|
-
})
|
|
24
|
-
token
|
|
25
|
-
else
|
|
26
|
-
Logger.warn("Authentication failed", {
|
|
27
|
-
username: username,
|
|
28
|
-
ip: current_ip
|
|
29
|
-
})
|
|
30
|
-
nil
|
|
31
|
-
end
|
|
32
|
-
end
|
|
33
|
-
|
|
34
|
-
def validate_token(token)
|
|
35
|
-
return false unless auth_enabled?
|
|
36
|
-
return false unless token && token.length > 10
|
|
37
|
-
|
|
38
|
-
# Simple token validation for MVP
|
|
39
|
-
# In production, this would use JWT or similar
|
|
40
|
-
begin
|
|
41
|
-
decoded = Base64.decode64(token)
|
|
42
|
-
parts = decoded.split(':')
|
|
43
|
-
return false unless parts.length == 3
|
|
44
|
-
|
|
45
|
-
username, timestamp, signature = parts
|
|
46
|
-
expected_signature = generate_signature(username, timestamp)
|
|
47
|
-
|
|
48
|
-
if signature == expected_signature
|
|
49
|
-
# Check if token is not expired (24 hours)
|
|
50
|
-
token_time = Time.at(timestamp.to_i)
|
|
51
|
-
if Time.now - token_time < 24 * 60 * 60
|
|
52
|
-
Logger.debug("Token validated", { username: username })
|
|
53
|
-
username
|
|
54
|
-
else
|
|
55
|
-
Logger.warn("Token expired", { username: username })
|
|
56
|
-
false
|
|
57
|
-
end
|
|
58
|
-
else
|
|
59
|
-
Logger.warn("Invalid token signature", { token: token[0..8] + "..." })
|
|
60
|
-
false
|
|
61
|
-
end
|
|
62
|
-
rescue => e
|
|
63
|
-
ErrorHandler.handle_error(e, {
|
|
64
|
-
error_type: 'authentication',
|
|
65
|
-
operation: 'validate_token'
|
|
66
|
-
})
|
|
67
|
-
false
|
|
68
|
-
end
|
|
69
|
-
end
|
|
70
|
-
|
|
71
|
-
def auth_enabled?
|
|
72
|
-
CapyDash.config.auth_enabled?
|
|
73
|
-
end
|
|
74
|
-
|
|
75
|
-
def require_auth!
|
|
76
|
-
return true unless auth_enabled?
|
|
77
|
-
# This would be called by middleware or controllers
|
|
78
|
-
# For now, just return true
|
|
79
|
-
true
|
|
80
|
-
end
|
|
81
|
-
|
|
82
|
-
private
|
|
83
|
-
|
|
84
|
-
def generate_token(username)
|
|
85
|
-
timestamp = Time.now.to_i.to_s
|
|
86
|
-
signature = generate_signature(username, timestamp)
|
|
87
|
-
token_data = "#{username}:#{timestamp}:#{signature}"
|
|
88
|
-
Base64.encode64(token_data).strip
|
|
89
|
-
end
|
|
90
|
-
|
|
91
|
-
def generate_signature(username, timestamp)
|
|
92
|
-
secret = CapyDash.config.secret_key
|
|
93
|
-
data = "#{username}:#{timestamp}:#{secret}"
|
|
94
|
-
Digest::SHA256.hexdigest(data)
|
|
95
|
-
end
|
|
96
|
-
|
|
97
|
-
def current_ip
|
|
98
|
-
# In a real app, this would get the actual IP
|
|
99
|
-
"127.0.0.1"
|
|
100
|
-
end
|
|
101
|
-
end
|
|
102
|
-
end
|
|
103
|
-
end
|
|
@@ -1,186 +0,0 @@
|
|
|
1
|
-
module CapyDash
|
|
2
|
-
class Configuration
|
|
3
|
-
attr_accessor :server, :dashboard, :tests, :logging, :security, :performance
|
|
4
|
-
|
|
5
|
-
def initialize
|
|
6
|
-
@server = {
|
|
7
|
-
host: "localhost",
|
|
8
|
-
port: 4000,
|
|
9
|
-
websocket_path: "/websocket",
|
|
10
|
-
max_connections: 100,
|
|
11
|
-
message_history_limit: 1000
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
@dashboard = {
|
|
15
|
-
title: "CapyDash Test Monitor",
|
|
16
|
-
refresh_interval: 1000,
|
|
17
|
-
auto_scroll: true,
|
|
18
|
-
show_timestamps: true,
|
|
19
|
-
screenshot_quality: 0.8,
|
|
20
|
-
max_screenshot_width: 1200
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
@tests = {
|
|
24
|
-
default_directory: "test",
|
|
25
|
-
system_tests_dir: "test/system",
|
|
26
|
-
feature_tests_dir: "test/features",
|
|
27
|
-
controller_tests_dir: "test/controllers",
|
|
28
|
-
model_tests_dir: "test/models",
|
|
29
|
-
screenshot_dir: "tmp/capydash_screenshots",
|
|
30
|
-
timeout: 300
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
@logging = {
|
|
34
|
-
level: "info",
|
|
35
|
-
file: "log/capydash.log",
|
|
36
|
-
max_file_size: "10MB",
|
|
37
|
-
max_files: 5
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
@security = {
|
|
41
|
-
enable_auth: false,
|
|
42
|
-
secret_key: "your-secret-key-here",
|
|
43
|
-
session_timeout: 3600
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
@performance = {
|
|
47
|
-
enable_compression: true,
|
|
48
|
-
cleanup_interval: 300,
|
|
49
|
-
max_memory_usage: "512MB"
|
|
50
|
-
}
|
|
51
|
-
end
|
|
52
|
-
|
|
53
|
-
def self.load_from_file(config_path = nil)
|
|
54
|
-
config_path ||= File.join(Dir.pwd, "config", "capydash.yml")
|
|
55
|
-
|
|
56
|
-
if File.exist?(config_path)
|
|
57
|
-
require 'yaml'
|
|
58
|
-
yaml_config = YAML.load_file(config_path)
|
|
59
|
-
|
|
60
|
-
config = new
|
|
61
|
-
config.load_from_hash(yaml_config)
|
|
62
|
-
config
|
|
63
|
-
else
|
|
64
|
-
# Return default configuration if file doesn't exist
|
|
65
|
-
new
|
|
66
|
-
end
|
|
67
|
-
rescue => e
|
|
68
|
-
puts "Warning: Could not load configuration from #{config_path}: #{e.message}"
|
|
69
|
-
puts "Using default configuration."
|
|
70
|
-
new
|
|
71
|
-
end
|
|
72
|
-
|
|
73
|
-
def load_from_hash(hash)
|
|
74
|
-
@server.merge!(hash['server']) if hash['server']
|
|
75
|
-
@dashboard.merge!(hash['dashboard']) if hash['dashboard']
|
|
76
|
-
@tests.merge!(hash['tests']) if hash['tests']
|
|
77
|
-
@logging.merge!(hash['logging']) if hash['logging']
|
|
78
|
-
@security.merge!(hash['security']) if hash['security']
|
|
79
|
-
@performance.merge!(hash['performance']) if hash['performance']
|
|
80
|
-
end
|
|
81
|
-
|
|
82
|
-
def server_host
|
|
83
|
-
@server[:host]
|
|
84
|
-
end
|
|
85
|
-
|
|
86
|
-
def server_port
|
|
87
|
-
@server[:port]
|
|
88
|
-
end
|
|
89
|
-
|
|
90
|
-
def websocket_path
|
|
91
|
-
@server[:websocket_path]
|
|
92
|
-
end
|
|
93
|
-
|
|
94
|
-
def max_connections
|
|
95
|
-
@server[:max_connections]
|
|
96
|
-
end
|
|
97
|
-
|
|
98
|
-
def message_history_limit
|
|
99
|
-
@server[:message_history_limit]
|
|
100
|
-
end
|
|
101
|
-
|
|
102
|
-
def dashboard_title
|
|
103
|
-
@dashboard[:title]
|
|
104
|
-
end
|
|
105
|
-
|
|
106
|
-
def auto_scroll?
|
|
107
|
-
@dashboard[:auto_scroll]
|
|
108
|
-
end
|
|
109
|
-
|
|
110
|
-
def show_timestamps?
|
|
111
|
-
@dashboard[:show_timestamps]
|
|
112
|
-
end
|
|
113
|
-
|
|
114
|
-
def screenshot_quality
|
|
115
|
-
@dashboard[:screenshot_quality]
|
|
116
|
-
end
|
|
117
|
-
|
|
118
|
-
def max_screenshot_width
|
|
119
|
-
@dashboard[:max_screenshot_width]
|
|
120
|
-
end
|
|
121
|
-
|
|
122
|
-
def system_tests_dir
|
|
123
|
-
@tests[:system_tests_dir]
|
|
124
|
-
end
|
|
125
|
-
|
|
126
|
-
def feature_tests_dir
|
|
127
|
-
@tests[:feature_tests_dir]
|
|
128
|
-
end
|
|
129
|
-
|
|
130
|
-
def controller_tests_dir
|
|
131
|
-
@tests[:controller_tests_dir]
|
|
132
|
-
end
|
|
133
|
-
|
|
134
|
-
def model_tests_dir
|
|
135
|
-
@tests[:model_tests_dir]
|
|
136
|
-
end
|
|
137
|
-
|
|
138
|
-
def screenshot_dir
|
|
139
|
-
@tests[:screenshot_dir]
|
|
140
|
-
end
|
|
141
|
-
|
|
142
|
-
def test_timeout
|
|
143
|
-
@tests[:timeout]
|
|
144
|
-
end
|
|
145
|
-
|
|
146
|
-
def log_level
|
|
147
|
-
@logging[:level]
|
|
148
|
-
end
|
|
149
|
-
|
|
150
|
-
def log_file
|
|
151
|
-
@logging[:file]
|
|
152
|
-
end
|
|
153
|
-
|
|
154
|
-
def max_files
|
|
155
|
-
@logging[:max_files]
|
|
156
|
-
end
|
|
157
|
-
|
|
158
|
-
def max_file_size
|
|
159
|
-
@logging[:max_file_size]
|
|
160
|
-
end
|
|
161
|
-
|
|
162
|
-
def auth_enabled?
|
|
163
|
-
@security[:enable_auth]
|
|
164
|
-
end
|
|
165
|
-
|
|
166
|
-
def secret_key
|
|
167
|
-
@security[:secret_key]
|
|
168
|
-
end
|
|
169
|
-
|
|
170
|
-
def session_timeout
|
|
171
|
-
@security[:session_timeout]
|
|
172
|
-
end
|
|
173
|
-
|
|
174
|
-
def compression_enabled?
|
|
175
|
-
@performance[:enable_compression]
|
|
176
|
-
end
|
|
177
|
-
|
|
178
|
-
def cleanup_interval
|
|
179
|
-
@performance[:cleanup_interval]
|
|
180
|
-
end
|
|
181
|
-
|
|
182
|
-
def max_memory_usage
|
|
183
|
-
@performance[:max_memory_usage]
|
|
184
|
-
end
|
|
185
|
-
end
|
|
186
|
-
end
|
|
@@ -1,167 +0,0 @@
|
|
|
1
|
-
require 'eventmachine'
|
|
2
|
-
require 'em-websocket'
|
|
3
|
-
|
|
4
|
-
module CapyDash
|
|
5
|
-
class DashboardServer
|
|
6
|
-
attr_reader :port, :clients
|
|
7
|
-
|
|
8
|
-
# Provide a single shared instance for the whole process
|
|
9
|
-
def self.instance(port: nil)
|
|
10
|
-
configured_port = port || CapyDash.config.server_port
|
|
11
|
-
@instance ||= new(port: configured_port)
|
|
12
|
-
end
|
|
13
|
-
|
|
14
|
-
def initialize(port: nil)
|
|
15
|
-
@port = port || CapyDash.config.server_port
|
|
16
|
-
@clients = []
|
|
17
|
-
@history = [] # recent events to replay on reconnect
|
|
18
|
-
@history_limit = CapyDash.config.message_history_limit
|
|
19
|
-
@max_connections = CapyDash.config.max_connections
|
|
20
|
-
end
|
|
21
|
-
|
|
22
|
-
# Start the WebSocket server in a background thread
|
|
23
|
-
def start
|
|
24
|
-
return if defined?(@started) && @started
|
|
25
|
-
@started = true
|
|
26
|
-
Thread.new do
|
|
27
|
-
EM.run do
|
|
28
|
-
EM::WebSocket.run(host: "0.0.0.0", port: @port) do |ws|
|
|
29
|
-
|
|
30
|
-
ws.onopen do
|
|
31
|
-
begin
|
|
32
|
-
# Check connection limit
|
|
33
|
-
if @clients.length >= @max_connections
|
|
34
|
-
CapyDash::Logger.warn("Connection limit reached", {
|
|
35
|
-
current_connections: @clients.length,
|
|
36
|
-
max_connections: @max_connections
|
|
37
|
-
})
|
|
38
|
-
ws.close
|
|
39
|
-
return
|
|
40
|
-
end
|
|
41
|
-
|
|
42
|
-
@clients << ws
|
|
43
|
-
CapyDash::Logger.info("Client connected", {
|
|
44
|
-
total_clients: @clients.length,
|
|
45
|
-
client_id: ws.object_id
|
|
46
|
-
})
|
|
47
|
-
puts "Client connected"
|
|
48
|
-
|
|
49
|
-
# Replay recent history so refreshed clients see last events
|
|
50
|
-
@history.each do |msg|
|
|
51
|
-
begin
|
|
52
|
-
ws.send(msg)
|
|
53
|
-
rescue => e
|
|
54
|
-
CapyDash::ErrorHandler.handle_websocket_error(e, ws)
|
|
55
|
-
end
|
|
56
|
-
end
|
|
57
|
-
rescue => e
|
|
58
|
-
CapyDash::ErrorHandler.handle_websocket_error(e, ws)
|
|
59
|
-
end
|
|
60
|
-
end
|
|
61
|
-
|
|
62
|
-
ws.onclose do
|
|
63
|
-
begin
|
|
64
|
-
@clients.delete(ws)
|
|
65
|
-
CapyDash::Logger.info("Client disconnected", {
|
|
66
|
-
remaining_clients: @clients.length,
|
|
67
|
-
client_id: ws.object_id
|
|
68
|
-
})
|
|
69
|
-
puts "Client disconnected"
|
|
70
|
-
rescue => e
|
|
71
|
-
CapyDash::ErrorHandler.handle_websocket_error(e, ws)
|
|
72
|
-
end
|
|
73
|
-
end
|
|
74
|
-
|
|
75
|
-
ws.onmessage do |msg|
|
|
76
|
-
begin
|
|
77
|
-
CapyDash::Logger.debug("Received WebSocket message", {
|
|
78
|
-
message_length: msg.length,
|
|
79
|
-
client_id: ws.object_id
|
|
80
|
-
})
|
|
81
|
-
puts "Received message: #{msg}"
|
|
82
|
-
|
|
83
|
-
data = JSON.parse(msg) rescue nil
|
|
84
|
-
if data && data["command"] == "run_tests"
|
|
85
|
-
args = data["args"] || ["bundle", "exec", "rails", "test", "test/system"]
|
|
86
|
-
CapyDash::Logger.info("Executing test command", {
|
|
87
|
-
command: args.join(' '),
|
|
88
|
-
client_id: ws.object_id
|
|
89
|
-
})
|
|
90
|
-
|
|
91
|
-
Thread.new do
|
|
92
|
-
begin
|
|
93
|
-
# Change to the dummy app directory and run tests there
|
|
94
|
-
current_dir = File.dirname(__FILE__)
|
|
95
|
-
gem_root = File.expand_path(File.join(current_dir, "..", ".."))
|
|
96
|
-
dummy_app_path = File.join(gem_root, "spec", "dummy_app")
|
|
97
|
-
|
|
98
|
-
CapyDash::Logger.info("Running tests in directory", {
|
|
99
|
-
directory: dummy_app_path,
|
|
100
|
-
exists: Dir.exist?(dummy_app_path)
|
|
101
|
-
})
|
|
102
|
-
|
|
103
|
-
Dir.chdir(dummy_app_path) do
|
|
104
|
-
# Set Rails environment and ensure CapyDash is loaded
|
|
105
|
-
ENV["RAILS_ENV"] = "test"
|
|
106
|
-
ENV["CAPYDASH_EXTERNAL_WS"] = "1" # Use external WebSocket mode
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
# Run the command and capture both stdout and stderr
|
|
110
|
-
IO.popen(args, err: [:child, :out]) do |io|
|
|
111
|
-
io.each_line do |line|
|
|
112
|
-
event = { type: "runner", line: line.strip, status: "running", ts: Time.now.to_i }
|
|
113
|
-
broadcast(event.to_json)
|
|
114
|
-
end
|
|
115
|
-
end
|
|
116
|
-
end
|
|
117
|
-
rescue => e
|
|
118
|
-
CapyDash::ErrorHandler.handle_test_execution_error(e, args.join(' '))
|
|
119
|
-
broadcast({ type: "runner", line: "Error: #{e.message}", status: "failed", ts: Time.now.to_i }.to_json)
|
|
120
|
-
end
|
|
121
|
-
broadcast({ type: "runner", line: "Finished", status: "passed", ts: Time.now.to_i }.to_json)
|
|
122
|
-
end
|
|
123
|
-
else
|
|
124
|
-
# Optionally broadcast to all clients
|
|
125
|
-
broadcast(msg)
|
|
126
|
-
end
|
|
127
|
-
rescue => e
|
|
128
|
-
CapyDash::ErrorHandler.handle_websocket_error(e, ws)
|
|
129
|
-
end
|
|
130
|
-
end
|
|
131
|
-
|
|
132
|
-
end
|
|
133
|
-
end
|
|
134
|
-
end
|
|
135
|
-
end
|
|
136
|
-
|
|
137
|
-
# Send a message to all connected clients
|
|
138
|
-
def broadcast(message)
|
|
139
|
-
begin
|
|
140
|
-
# store in history buffer
|
|
141
|
-
@history << message
|
|
142
|
-
@history.shift if @history.length > @history_limit
|
|
143
|
-
|
|
144
|
-
# fan out to all connected clients
|
|
145
|
-
@clients.each do |client|
|
|
146
|
-
begin
|
|
147
|
-
client.send(message)
|
|
148
|
-
rescue => e
|
|
149
|
-
CapyDash::ErrorHandler.handle_websocket_error(e, client)
|
|
150
|
-
# Remove dead connections
|
|
151
|
-
@clients.delete(client)
|
|
152
|
-
end
|
|
153
|
-
end
|
|
154
|
-
rescue => e
|
|
155
|
-
CapyDash::ErrorHandler.handle_error(e, {
|
|
156
|
-
error_type: 'broadcast',
|
|
157
|
-
message_length: message&.length
|
|
158
|
-
})
|
|
159
|
-
end
|
|
160
|
-
end
|
|
161
|
-
|
|
162
|
-
# Stop the WebSocket server gracefully
|
|
163
|
-
def stop
|
|
164
|
-
EM.stop_event_loop if EM.reactor_running?
|
|
165
|
-
end
|
|
166
|
-
end
|
|
167
|
-
end
|