forest_admin_rpc_agent 1.0.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 +674 -0
- data/bin/console +11 -0
- data/bin/forest_admin_rpc_agent +9 -0
- data/bin/setup +8 -0
- data/forest_admin_rpc_agent.gemspec +36 -0
- data/lib/forest_admin_rpc_agent/agent.rb +19 -0
- data/lib/forest_admin_rpc_agent/engine.rb +17 -0
- data/lib/forest_admin_rpc_agent/extensions/config_loader.rb +12 -0
- data/lib/forest_admin_rpc_agent/extensions/sinatra_extension.rb +32 -0
- data/lib/forest_admin_rpc_agent/facades/container.rb +29 -0
- data/lib/forest_admin_rpc_agent/middleware/authentication.rb +67 -0
- data/lib/forest_admin_rpc_agent/routes/action_execute.rb +31 -0
- data/lib/forest_admin_rpc_agent/routes/action_form.rb +49 -0
- data/lib/forest_admin_rpc_agent/routes/aggregate.rb +34 -0
- data/lib/forest_admin_rpc_agent/routes/base_route.rb +52 -0
- data/lib/forest_admin_rpc_agent/routes/chart.rb +30 -0
- data/lib/forest_admin_rpc_agent/routes/create.rb +23 -0
- data/lib/forest_admin_rpc_agent/routes/datasource_chart.rb +23 -0
- data/lib/forest_admin_rpc_agent/routes/delete.rb +28 -0
- data/lib/forest_admin_rpc_agent/routes/health_route.rb +13 -0
- data/lib/forest_admin_rpc_agent/routes/list.rb +29 -0
- data/lib/forest_admin_rpc_agent/routes/schema.rb +24 -0
- data/lib/forest_admin_rpc_agent/routes/sse.rb +80 -0
- data/lib/forest_admin_rpc_agent/routes/update.rb +26 -0
- data/lib/forest_admin_rpc_agent/sse_streamer.rb +14 -0
- data/lib/forest_admin_rpc_agent/thor/install.rb +136 -0
- data/lib/forest_admin_rpc_agent/version.rb +3 -0
- data/lib/forest_admin_rpc_agent.rb +33 -0
- metadata +122 -0
@@ -0,0 +1,80 @@
|
|
1
|
+
require 'jsonapi-serializers'
|
2
|
+
|
3
|
+
module ForestAdminRpcAgent
|
4
|
+
module Routes
|
5
|
+
class Sse
|
6
|
+
def initialize(url = 'rpc/sse', method = 'get', name = 'rpc_sse')
|
7
|
+
@url = url
|
8
|
+
@method = method
|
9
|
+
@name = name
|
10
|
+
end
|
11
|
+
|
12
|
+
def registered(app)
|
13
|
+
if defined?(Sinatra) && (app == Sinatra::Base || app.ancestors.include?(Sinatra::Base))
|
14
|
+
register_sinatra(app)
|
15
|
+
elsif defined?(Rails) && app.is_a?(ActionDispatch::Routing::Mapper)
|
16
|
+
register_rails(app)
|
17
|
+
else
|
18
|
+
raise NotImplementedError,
|
19
|
+
"Unsupported application type: #{app.class}. #{self} works with Sinatra::Base or ActionDispatch::Routing::Mapper."
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def register_sinatra(_app)
|
24
|
+
# TODO
|
25
|
+
end
|
26
|
+
|
27
|
+
def register_rails(router)
|
28
|
+
handler = proc do |hash|
|
29
|
+
request = ActionDispatch::Request.new(hash)
|
30
|
+
auth_middleware = ForestAdminRpcAgent::Middleware::Authentication.new(->(_env) { [200, {}, ['OK']] })
|
31
|
+
status, headers, response = auth_middleware.call(request.env)
|
32
|
+
|
33
|
+
if status == 200
|
34
|
+
headers = {
|
35
|
+
'Content-Type' => 'text/event-stream',
|
36
|
+
'Cache-Control' => 'no-cache',
|
37
|
+
'Connection' => 'keep-alive'
|
38
|
+
}
|
39
|
+
|
40
|
+
should_continue = true
|
41
|
+
# Intercept CTRL+C
|
42
|
+
stop_proc = proc { should_continue = false }
|
43
|
+
original_handler = trap('INT', stop_proc)
|
44
|
+
|
45
|
+
body = Enumerator.new do |yielder|
|
46
|
+
stream = SseStreamer.new(yielder)
|
47
|
+
|
48
|
+
begin
|
49
|
+
while should_continue
|
50
|
+
# ForestAdminRpcAgent::Facades::Container.logger.log('Debug', '[SSE] heartbeat')
|
51
|
+
stream.write('', event: 'heartbeat')
|
52
|
+
sleep 1
|
53
|
+
end
|
54
|
+
rescue IOError
|
55
|
+
# Client disconnected
|
56
|
+
# ForestAdminRpcAgent::Facades::Container.logger.log('Debug', '[SSE] disconnected')
|
57
|
+
ensure
|
58
|
+
trap('INT', original_handler)
|
59
|
+
stream.write({ event: 'RpcServerStop' }, event: 'RpcServerStop')
|
60
|
+
# ForestAdminRpcAgent::Facades::Container.logger.log('Debug', '[SSE] stopped streaming')
|
61
|
+
yielder.close if yielder.respond_to?(:close)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
[status, headers, body]
|
66
|
+
else
|
67
|
+
[status, headers, response]
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
router.match @url,
|
72
|
+
defaults: { format: 'event-stream' },
|
73
|
+
to: handler,
|
74
|
+
via: @method,
|
75
|
+
as: @name,
|
76
|
+
route_alias: @name
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require 'jsonapi-serializers'
|
2
|
+
|
3
|
+
module ForestAdminRpcAgent
|
4
|
+
module Routes
|
5
|
+
class Update < BaseRoute
|
6
|
+
include ForestAdminDatasourceToolkit::Components::Query
|
7
|
+
|
8
|
+
def initialize
|
9
|
+
super('rpc/:collection_name/update', 'post', 'rpc_update')
|
10
|
+
end
|
11
|
+
|
12
|
+
def handle_request(args)
|
13
|
+
return '{}' unless args[:params]['collection_name']
|
14
|
+
|
15
|
+
caller = ForestAdminDatasourceToolkit::Components::Caller.new(
|
16
|
+
**args[:params]['caller'].to_h.transform_keys(&:to_sym)
|
17
|
+
)
|
18
|
+
datasource = ForestAdminRpcAgent::Facades::Container.datasource
|
19
|
+
collection = datasource.get_collection(args[:params]['collection_name'])
|
20
|
+
filter = FilterFactory.from_plain_object(args[:params]['filter'])
|
21
|
+
|
22
|
+
collection.update(caller, filter, args[:params]['data']).to_json
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
require 'json'
|
2
|
+
|
3
|
+
module ForestAdminRpcAgent
|
4
|
+
class SseStreamer
|
5
|
+
def initialize(yielder)
|
6
|
+
@yielder = yielder
|
7
|
+
end
|
8
|
+
|
9
|
+
def write(object, event: nil)
|
10
|
+
@yielder << "event: #{event}\n" if event
|
11
|
+
@yielder << "data: #{JSON.dump(object)}\n\n"
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,136 @@
|
|
1
|
+
require 'thor'
|
2
|
+
require 'fileutils'
|
3
|
+
|
4
|
+
module ForestAdminRpcAgent
|
5
|
+
module Thor
|
6
|
+
class Install < ::Thor
|
7
|
+
include ::Thor::Actions
|
8
|
+
# Run the command:
|
9
|
+
# for a rails app : forest_admin_rpc_agent install AUTH_SECRET_FROM_YOUR_FOREST_APP
|
10
|
+
# for a sinatra app : forest_admin_rpc_agent install AUTH_SECRET_FROM_YOUR_FOREST_APP --app_file=app.rb
|
11
|
+
|
12
|
+
desc 'install AUTH_SECRET_FROM_YOUR_FOREST_APP',
|
13
|
+
'Install ForestAdmin RPC Agent by generating necessary configuration and files'
|
14
|
+
method_option :app_file, type: :string, required: false, desc: 'Main file of the Sinatra application (ex: app.rb)'
|
15
|
+
|
16
|
+
RAILS_CONFIG_PATH = 'config/initializers/forest_admin_rpc_agent.rb'.freeze
|
17
|
+
RAILS_AGENT_PATH = 'app/lib/forest_admin_rpc_agent/create_rpc_agent.rb'.freeze
|
18
|
+
|
19
|
+
SINATRA_CONFIG_PATH = 'config/forest_admin_rpc_agent.rb'.freeze
|
20
|
+
SINATRA_AGENT_PATH = 'config/create_rpc_agent.rb'.freeze
|
21
|
+
|
22
|
+
def install(auth_secret)
|
23
|
+
if rails_app?
|
24
|
+
say_status('info', 'Rails framework detected ✅', :green)
|
25
|
+
setup_rails(auth_secret)
|
26
|
+
elsif sinatra_app?
|
27
|
+
if options[:app_file].nil?
|
28
|
+
say_status('error', 'You must specify the main file of the Sinatra application with --app_file', :red)
|
29
|
+
raise ::Thor::Error, 'You must specify the main file of the Sinatra application with --app_file'
|
30
|
+
end
|
31
|
+
say_status('info', 'Sinatra framework detected ✅', :green)
|
32
|
+
setup_sinatra(auth_secret)
|
33
|
+
else
|
34
|
+
say_status('error', 'Unsupported framework', :red)
|
35
|
+
raise ::Thor::Error, 'Unsupported framework, only Rails and Sinatra are supported with ForestAdmin RPC Agent'
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
|
41
|
+
def setup_rails(auth_secret)
|
42
|
+
require 'rails/generators'
|
43
|
+
require 'rails/generators/actions'
|
44
|
+
|
45
|
+
create_config_files(auth_secret, RAILS_CONFIG_PATH, RAILS_AGENT_PATH)
|
46
|
+
|
47
|
+
klass = Class.new(Rails::Generators::Base) do
|
48
|
+
include Rails::Generators::Actions
|
49
|
+
end
|
50
|
+
klass.new.route("mount ForestAdminRpcAgent::Engine => '/forest'")
|
51
|
+
|
52
|
+
say_status('success', 'ForestAdmin RPC Agent installed on Rails ✅', :green)
|
53
|
+
end
|
54
|
+
|
55
|
+
def setup_sinatra(auth_secret)
|
56
|
+
create_config_files(auth_secret, SINATRA_CONFIG_PATH, SINATRA_AGENT_PATH)
|
57
|
+
|
58
|
+
app_file_content = File.read(options[:app_file])
|
59
|
+
if app_file_content.include?("require 'sinatra'")
|
60
|
+
insert_into_file options[:app_file], <<~RUBY, after: "require 'sinatra'\n"
|
61
|
+
require_relative 'config/forest_admin_rpc_agent'
|
62
|
+
require 'forest_admin_rpc_agent/extensions/sinatra_extension'
|
63
|
+
RUBY
|
64
|
+
|
65
|
+
say_status('success', 'ForestAdmin RPC Agent installed on Sinatra ✅', :green)
|
66
|
+
else
|
67
|
+
say_status('error', "Could not find `require 'sinatra'` in #{options[:app_file]}", :red)
|
68
|
+
raise ::Thor::Error, "Please add `require 'sinatra'` in #{options[:app_file]} before running this command."
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def create_config_files(auth_secret, config_path, agent_path)
|
73
|
+
# Create necessary directories
|
74
|
+
FileUtils.mkdir_p(File.dirname(config_path))
|
75
|
+
FileUtils.mkdir_p(File.dirname(agent_path))
|
76
|
+
|
77
|
+
# Create configuration file
|
78
|
+
create_file config_path, <<~RUBY
|
79
|
+
ForestAdminRpcAgent.configure do |config|
|
80
|
+
config.auth_secret = '#{auth_secret}'
|
81
|
+
end
|
82
|
+
RUBY
|
83
|
+
|
84
|
+
# Create agent setup file
|
85
|
+
create_file agent_path, <<~RUBY
|
86
|
+
# This file contains code to create and configure your Forest Admin agent
|
87
|
+
# You can customize this file according to your needs
|
88
|
+
|
89
|
+
module ForestAdminRpcAgent
|
90
|
+
class CreateRpcAgent
|
91
|
+
def self.setup!
|
92
|
+
# Initialize your agent here
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
RUBY
|
97
|
+
end
|
98
|
+
|
99
|
+
def rails_app?
|
100
|
+
return true if Object.const_defined?(:Rails) && Rails.respond_to?(:root)
|
101
|
+
|
102
|
+
File.exist?('config/application.rb') && Dir.exist?('app/controllers')
|
103
|
+
end
|
104
|
+
|
105
|
+
def sinatra_app?
|
106
|
+
# 1. Check if Sinatra is already loaded in memory
|
107
|
+
return true if defined?(Sinatra::Base)
|
108
|
+
|
109
|
+
# 2. Check Gemfile.lock
|
110
|
+
return true if File.exist?('Gemfile.lock') && File.read('Gemfile.lock').include?('sinatra')
|
111
|
+
|
112
|
+
# 3. Check config.ru
|
113
|
+
return true if File.exist?('config.ru') && File.read('config.ru') =~ %r{require ['"]sinatra(/base)?['"]}
|
114
|
+
|
115
|
+
# 4. Look for a class that inherits from Sinatra in Ruby files
|
116
|
+
Dir.glob('*.rb').any? do |file|
|
117
|
+
content = File.read(file)
|
118
|
+
content.match?(%r{require ['"](sinatra|sinatra/base)['"]}) &&
|
119
|
+
(content.include?('< Sinatra::') || content.include?('Sinatra::Application'))
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
class << self
|
124
|
+
private
|
125
|
+
|
126
|
+
def exit_on_failure?
|
127
|
+
true
|
128
|
+
end
|
129
|
+
|
130
|
+
def source_root
|
131
|
+
File.dirname(__FILE__)
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'dry-configurable'
|
2
|
+
require 'forest_admin_rpc_agent/version'
|
3
|
+
require 'forest_admin_rpc_agent/engine' if defined?(Rails)
|
4
|
+
require 'forest_admin_agent'
|
5
|
+
require 'forest_admin_datasource_customizer'
|
6
|
+
require 'forest_admin_datasource_toolkit'
|
7
|
+
require 'zeitwerk'
|
8
|
+
|
9
|
+
loader = Zeitwerk::Loader.for_gem
|
10
|
+
loader.setup
|
11
|
+
|
12
|
+
module ForestAdminRpcAgent
|
13
|
+
extend Dry::Configurable
|
14
|
+
|
15
|
+
setting :debug, default: true
|
16
|
+
setting :auth_secret
|
17
|
+
setting :env_secret
|
18
|
+
setting :forest_server_url, default: 'https://api.forestadmin.com'
|
19
|
+
setting :is_production, default: false
|
20
|
+
setting :prefix, default: nil
|
21
|
+
setting :cache_dir, default: :'tmp/cache/forest_admin'
|
22
|
+
setting :project_dir, default: Dir.pwd
|
23
|
+
setting :loggerLevel, default: 'info'
|
24
|
+
setting :logger, default: nil
|
25
|
+
setting :customize_error_message, default: nil
|
26
|
+
|
27
|
+
begin
|
28
|
+
require 'thor'
|
29
|
+
require 'forest_admin_rpc_agent/thor/install'
|
30
|
+
rescue LoadError
|
31
|
+
# Thor is not available, skip loading commands
|
32
|
+
end
|
33
|
+
end
|
metadata
ADDED
@@ -0,0 +1,122 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: forest_admin_rpc_agent
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Matthieu
|
8
|
+
- Nicolas
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2025-09-23 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: dry-configurable
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
requirements:
|
18
|
+
- - "~>"
|
19
|
+
- !ruby/object:Gem::Version
|
20
|
+
version: '1.1'
|
21
|
+
type: :runtime
|
22
|
+
prerelease: false
|
23
|
+
version_requirements: !ruby/object:Gem::Requirement
|
24
|
+
requirements:
|
25
|
+
- - "~>"
|
26
|
+
- !ruby/object:Gem::Version
|
27
|
+
version: '1.1'
|
28
|
+
- !ruby/object:Gem::Dependency
|
29
|
+
name: zeitwerk
|
30
|
+
requirement: !ruby/object:Gem::Requirement
|
31
|
+
requirements:
|
32
|
+
- - "~>"
|
33
|
+
- !ruby/object:Gem::Version
|
34
|
+
version: '2.3'
|
35
|
+
type: :runtime
|
36
|
+
prerelease: false
|
37
|
+
version_requirements: !ruby/object:Gem::Requirement
|
38
|
+
requirements:
|
39
|
+
- - "~>"
|
40
|
+
- !ruby/object:Gem::Version
|
41
|
+
version: '2.3'
|
42
|
+
- !ruby/object:Gem::Dependency
|
43
|
+
name: thor
|
44
|
+
requirement: !ruby/object:Gem::Requirement
|
45
|
+
requirements:
|
46
|
+
- - "~>"
|
47
|
+
- !ruby/object:Gem::Version
|
48
|
+
version: '1.3'
|
49
|
+
type: :runtime
|
50
|
+
prerelease: false
|
51
|
+
version_requirements: !ruby/object:Gem::Requirement
|
52
|
+
requirements:
|
53
|
+
- - "~>"
|
54
|
+
- !ruby/object:Gem::Version
|
55
|
+
version: '1.3'
|
56
|
+
description: |-
|
57
|
+
Forest is a modern admin interface that works on all major web frameworks. This gem makes Forest
|
58
|
+
admin work on any Ruby application.
|
59
|
+
email:
|
60
|
+
- matthv@gmail.com
|
61
|
+
- nicolasalexandre9@gmail.com
|
62
|
+
executables:
|
63
|
+
- forest_admin_rpc_agent
|
64
|
+
extensions: []
|
65
|
+
extra_rdoc_files: []
|
66
|
+
files:
|
67
|
+
- LICENSE
|
68
|
+
- bin/console
|
69
|
+
- bin/forest_admin_rpc_agent
|
70
|
+
- bin/setup
|
71
|
+
- forest_admin_rpc_agent.gemspec
|
72
|
+
- lib/forest_admin_rpc_agent.rb
|
73
|
+
- lib/forest_admin_rpc_agent/agent.rb
|
74
|
+
- lib/forest_admin_rpc_agent/engine.rb
|
75
|
+
- lib/forest_admin_rpc_agent/extensions/config_loader.rb
|
76
|
+
- lib/forest_admin_rpc_agent/extensions/sinatra_extension.rb
|
77
|
+
- lib/forest_admin_rpc_agent/facades/container.rb
|
78
|
+
- lib/forest_admin_rpc_agent/middleware/authentication.rb
|
79
|
+
- lib/forest_admin_rpc_agent/routes/action_execute.rb
|
80
|
+
- lib/forest_admin_rpc_agent/routes/action_form.rb
|
81
|
+
- lib/forest_admin_rpc_agent/routes/aggregate.rb
|
82
|
+
- lib/forest_admin_rpc_agent/routes/base_route.rb
|
83
|
+
- lib/forest_admin_rpc_agent/routes/chart.rb
|
84
|
+
- lib/forest_admin_rpc_agent/routes/create.rb
|
85
|
+
- lib/forest_admin_rpc_agent/routes/datasource_chart.rb
|
86
|
+
- lib/forest_admin_rpc_agent/routes/delete.rb
|
87
|
+
- lib/forest_admin_rpc_agent/routes/health_route.rb
|
88
|
+
- lib/forest_admin_rpc_agent/routes/list.rb
|
89
|
+
- lib/forest_admin_rpc_agent/routes/schema.rb
|
90
|
+
- lib/forest_admin_rpc_agent/routes/sse.rb
|
91
|
+
- lib/forest_admin_rpc_agent/routes/update.rb
|
92
|
+
- lib/forest_admin_rpc_agent/sse_streamer.rb
|
93
|
+
- lib/forest_admin_rpc_agent/thor/install.rb
|
94
|
+
- lib/forest_admin_rpc_agent/version.rb
|
95
|
+
homepage: https://www.forestadmin.com
|
96
|
+
licenses:
|
97
|
+
- GPL-3.0
|
98
|
+
metadata:
|
99
|
+
homepage_uri: https://www.forestadmin.com
|
100
|
+
source_code_uri: https://github.com/ForestAdmin/agent-ruby
|
101
|
+
changelog_uri: https://github.com/ForestAdmin/agent-ruby/blob/main/CHANGELOG.md
|
102
|
+
rubygems_mfa_required: 'false'
|
103
|
+
post_install_message:
|
104
|
+
rdoc_options: []
|
105
|
+
require_paths:
|
106
|
+
- lib
|
107
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
108
|
+
requirements:
|
109
|
+
- - ">="
|
110
|
+
- !ruby/object:Gem::Version
|
111
|
+
version: 3.0.0
|
112
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
113
|
+
requirements:
|
114
|
+
- - ">="
|
115
|
+
- !ruby/object:Gem::Version
|
116
|
+
version: '0'
|
117
|
+
requirements: []
|
118
|
+
rubygems_version: 3.4.20
|
119
|
+
signing_key:
|
120
|
+
specification_version: 4
|
121
|
+
summary: Ruby agent for Forest Admin.
|
122
|
+
test_files: []
|