airview 0.1.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 +7 -0
- data/CHANGELOG.md +5 -0
- data/CODE_OF_CONDUCT.md +10 -0
- data/LICENSE.txt +21 -0
- data/README.md +65 -0
- data/app/assets/javascripts/airview/application.js +425 -0
- data/app/assets/stylesheets/airview/application.css +1239 -0
- data/app/controllers/airview/application_controller.rb +7 -0
- data/app/controllers/airview/resources_controller.rb +261 -0
- data/app/controllers/airview/setup_controller.rb +90 -0
- data/app/controllers/airview/views_controller.rb +76 -0
- data/app/helpers/airview/application_helper.rb +479 -0
- data/app/javascript/airview/controllers/grid_controller.js +7 -0
- data/app/models/airview/application_record.rb +7 -0
- data/app/models/airview/field_definition.rb +22 -0
- data/app/models/airview/resource_definition.rb +48 -0
- data/app/models/airview/view.rb +27 -0
- data/app/views/airview/resources/_delete_modal.html.erb +49 -0
- data/app/views/airview/resources/_record_modal.html.erb +62 -0
- data/app/views/airview/resources/empty.html.erb +5 -0
- data/app/views/airview/resources/show.html.erb +258 -0
- data/app/views/airview/setup/_form.html.erb +73 -0
- data/app/views/airview/setup/edit.html.erb +16 -0
- data/app/views/airview/setup/index.html.erb +52 -0
- data/app/views/airview/setup/new.html.erb +14 -0
- data/app/views/airview/shared/_sidebar.html.erb +76 -0
- data/app/views/layouts/airview/application.html.erb +21 -0
- data/config/routes.rb +23 -0
- data/db/migrate/20260806000000_create_airview_views.rb +57 -0
- data/lib/airview/configuration.rb +6 -0
- data/lib/airview/engine.rb +15 -0
- data/lib/airview/field.rb +81 -0
- data/lib/airview/model_discovery.rb +38 -0
- data/lib/airview/query.rb +194 -0
- data/lib/airview/resource.rb +51 -0
- data/lib/airview/resource_builder.rb +62 -0
- data/lib/airview/schema_inference.rb +196 -0
- data/lib/airview/version.rb +5 -0
- data/lib/airview.rb +93 -0
- data/lib/generators/airview/install/install_generator.rb +38 -0
- data/lib/generators/airview/install/templates/add_folders_to_airview_views.rb +8 -0
- data/lib/generators/airview/install/templates/airview.rb +12 -0
- data/lib/generators/airview/install/templates/create_airview_views.rb +57 -0
- data/sig/airview.rbs +54 -0
- metadata +110 -0
data/lib/airview.rb
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "airview/version"
|
|
4
|
+
require "active_support/core_ext/object/blank"
|
|
5
|
+
require "active_support/core_ext/object/inclusion"
|
|
6
|
+
require "active_support/core_ext/hash/keys"
|
|
7
|
+
require "active_support/core_ext/string/inflections"
|
|
8
|
+
require "bigdecimal"
|
|
9
|
+
require "json"
|
|
10
|
+
require_relative "airview/configuration"
|
|
11
|
+
require_relative "airview/field"
|
|
12
|
+
require_relative "airview/resource"
|
|
13
|
+
require_relative "airview/query"
|
|
14
|
+
require_relative "airview/model_discovery"
|
|
15
|
+
require_relative "airview/resource_builder"
|
|
16
|
+
require_relative "airview/schema_inference"
|
|
17
|
+
require_relative "airview/engine" if defined?(Rails)
|
|
18
|
+
|
|
19
|
+
module Airview
|
|
20
|
+
class Error < StandardError; end
|
|
21
|
+
|
|
22
|
+
class ConfigurationError < Error; end
|
|
23
|
+
class UnknownResourceError < Error; end
|
|
24
|
+
RECORD_LABEL_METHODS = %i[
|
|
25
|
+
name
|
|
26
|
+
title
|
|
27
|
+
email
|
|
28
|
+
username
|
|
29
|
+
display_name
|
|
30
|
+
company_name
|
|
31
|
+
label
|
|
32
|
+
].freeze
|
|
33
|
+
|
|
34
|
+
class << self
|
|
35
|
+
def configuration
|
|
36
|
+
@configuration ||= Configuration.new
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def configure
|
|
40
|
+
yield(configuration)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def resources
|
|
44
|
+
if defined?(Airview::ResourceDefinition) && database_resources_available?
|
|
45
|
+
ResourceBuilder.enabled_resources
|
|
46
|
+
else
|
|
47
|
+
{}
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def resource!(key)
|
|
52
|
+
resources.fetch(key.to_sym) do
|
|
53
|
+
raise UnknownResourceError, "Airview resource #{key.inspect} is not registered"
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def record_label(record, preferred_method = nil)
|
|
58
|
+
return "" unless record
|
|
59
|
+
|
|
60
|
+
label = label_from_method(record, preferred_method)
|
|
61
|
+
label ||= label_from_method(record, :full_name)
|
|
62
|
+
label ||= label_from_full_name_parts(record)
|
|
63
|
+
label ||= RECORD_LABEL_METHODS.lazy.filter_map { |method_name| label_from_method(record, method_name) }.first
|
|
64
|
+
label.presence || "#{record.class.model_name.human} ##{record.id}"
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
private
|
|
68
|
+
|
|
69
|
+
def label_from_method(record, method_name)
|
|
70
|
+
return nil if method_name.blank? || method_name.to_sym == :to_s
|
|
71
|
+
return nil unless record.respond_to?(method_name)
|
|
72
|
+
|
|
73
|
+
value = record.public_send(method_name).to_s.strip
|
|
74
|
+
return nil if value.blank? || value.start_with?("#<")
|
|
75
|
+
|
|
76
|
+
value
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def label_from_full_name_parts(record)
|
|
80
|
+
return nil unless record.respond_to?(:first_name) && record.respond_to?(:last_name)
|
|
81
|
+
|
|
82
|
+
[record.public_send(:first_name), record.public_send(:last_name)].compact.join(" ").strip.presence
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def database_resources_available?
|
|
86
|
+
return false unless ActiveRecord::Base.connected?
|
|
87
|
+
|
|
88
|
+
ActiveRecord::Base.connection.data_source_exists?("airview_resources")
|
|
89
|
+
rescue ActiveRecord::NoDatabaseError, ActiveRecord::StatementInvalid
|
|
90
|
+
false
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
end
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "rails/generators"
|
|
4
|
+
require "rails/generators/active_record/migration"
|
|
5
|
+
|
|
6
|
+
module Airview
|
|
7
|
+
module Generators
|
|
8
|
+
class InstallGenerator < Rails::Generators::Base
|
|
9
|
+
include Rails::Generators::Migration
|
|
10
|
+
|
|
11
|
+
source_root File.expand_path("templates", __dir__)
|
|
12
|
+
|
|
13
|
+
def copy_initializer
|
|
14
|
+
template "airview.rb", "config/initializers/airview.rb"
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def copy_migration
|
|
18
|
+
if migration_exists?("create_airview_views")
|
|
19
|
+
unless migration_exists?("add_folders_to_airview_views")
|
|
20
|
+
migration_template "add_folders_to_airview_views.rb", "db/migrate/add_folders_to_airview_views.rb"
|
|
21
|
+
end
|
|
22
|
+
else
|
|
23
|
+
migration_template "create_airview_views.rb", "db/migrate/create_airview_views.rb"
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def self.next_migration_number(_dirname)
|
|
28
|
+
Time.now.utc.strftime("%Y%m%d%H%M%S")
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
private
|
|
32
|
+
|
|
33
|
+
def migration_exists?(name)
|
|
34
|
+
Dir.glob(File.join(destination_root, "db/migrate/*_#{name}.rb")).any?
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class AddFoldersToAirviewViews < ActiveRecord::Migration[7.1]
|
|
4
|
+
def change
|
|
5
|
+
add_column :airview_views, :folder, :string unless column_exists?(:airview_views, :folder)
|
|
6
|
+
add_index :airview_views, %i[resource_key folder] unless index_exists?(:airview_views, %i[resource_key folder])
|
|
7
|
+
end
|
|
8
|
+
end
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Airview is intended for trusted internal admin surfaces.
|
|
4
|
+
# Protect the mount point in config/routes.rb with your app's authentication.
|
|
5
|
+
#
|
|
6
|
+
# authenticate :user, ->(user) { user.admin? } do
|
|
7
|
+
# mount Airview::Engine => "/airview"
|
|
8
|
+
# end
|
|
9
|
+
|
|
10
|
+
Airview.configure do |config|
|
|
11
|
+
# Resource configuration is managed through the Airview UI at /airview/setup.
|
|
12
|
+
end
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class CreateAirviewViews < ActiveRecord::Migration[7.1]
|
|
4
|
+
def change
|
|
5
|
+
create_table :airview_resources do |t|
|
|
6
|
+
t.string :key, null: false
|
|
7
|
+
t.string :record_class_name, null: false
|
|
8
|
+
t.string :label, null: false
|
|
9
|
+
t.string :label_method
|
|
10
|
+
t.boolean :enabled, null: false, default: false
|
|
11
|
+
t.integer :position, null: false, default: 0
|
|
12
|
+
t.json :metadata, null: false, default: {}
|
|
13
|
+
|
|
14
|
+
t.timestamps
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
add_index :airview_resources, :key, unique: true
|
|
18
|
+
add_index :airview_resources, :record_class_name, unique: true
|
|
19
|
+
add_index :airview_resources, :enabled
|
|
20
|
+
|
|
21
|
+
create_table :airview_fields do |t|
|
|
22
|
+
t.references :airview_resource, null: false, foreign_key: { to_table: :airview_resources }
|
|
23
|
+
t.string :name, null: false
|
|
24
|
+
t.string :label, null: false
|
|
25
|
+
t.string :field_type, null: false
|
|
26
|
+
t.boolean :visible, null: false, default: true
|
|
27
|
+
t.boolean :read_only, null: false, default: false
|
|
28
|
+
t.integer :position, null: false, default: 0
|
|
29
|
+
t.string :association_name
|
|
30
|
+
t.string :target_model_name
|
|
31
|
+
t.string :target_label_method
|
|
32
|
+
t.json :metadata, null: false, default: {}
|
|
33
|
+
|
|
34
|
+
t.timestamps
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
add_index :airview_fields, %i[airview_resource_id name], unique: true
|
|
38
|
+
add_index :airview_fields, :visible
|
|
39
|
+
|
|
40
|
+
create_table :airview_views do |t|
|
|
41
|
+
t.string :name, null: false
|
|
42
|
+
t.string :resource_key, null: false
|
|
43
|
+
t.string :owner_key
|
|
44
|
+
t.string :folder
|
|
45
|
+
t.json :columns, null: false, default: []
|
|
46
|
+
t.json :filters, null: false, default: {}
|
|
47
|
+
t.json :sorts, null: false, default: {}
|
|
48
|
+
t.json :preferences, null: false, default: {}
|
|
49
|
+
|
|
50
|
+
t.timestamps
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
add_index :airview_views, :resource_key
|
|
54
|
+
add_index :airview_views, %i[resource_key folder]
|
|
55
|
+
add_index :airview_views, %i[resource_key name]
|
|
56
|
+
end
|
|
57
|
+
end
|
data/sig/airview.rbs
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
module Airview
|
|
2
|
+
VERSION: String
|
|
3
|
+
|
|
4
|
+
def self.configuration: () -> Configuration
|
|
5
|
+
def self.configure: () { (Configuration) -> void } -> void
|
|
6
|
+
def self.resources: () -> Hash[Symbol, Resource]
|
|
7
|
+
def self.resource!: (Symbol | String key) -> Resource
|
|
8
|
+
|
|
9
|
+
class Error < StandardError
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
class ConfigurationError < Error
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
class UnknownResourceError < Error
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
class Configuration
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
class Field
|
|
22
|
+
attr_reader name: Symbol
|
|
23
|
+
attr_reader label: String
|
|
24
|
+
attr_reader type: Symbol
|
|
25
|
+
attr_reader options: untyped
|
|
26
|
+
attr_reader model: untyped
|
|
27
|
+
attr_reader label_method: Symbol
|
|
28
|
+
|
|
29
|
+
def initialize: (Symbol | String name, type: Symbol | String, ?label: String?, ?readonly: bool, ?options: untyped, ?model: untyped, ?label_method: Symbol?) -> void
|
|
30
|
+
def readonly?: () -> bool
|
|
31
|
+
def editable?: () -> bool
|
|
32
|
+
def association?: () -> bool
|
|
33
|
+
def attribute_name: () -> Symbol
|
|
34
|
+
def validate!: () -> void
|
|
35
|
+
def model_class: () -> untyped
|
|
36
|
+
def option_values: () -> Array[untyped]
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
class Resource
|
|
40
|
+
attr_accessor label: String
|
|
41
|
+
attr_accessor label_method: Symbol
|
|
42
|
+
attr_reader key: Symbol
|
|
43
|
+
attr_reader model: String
|
|
44
|
+
attr_reader fields: Hash[Symbol, Field]
|
|
45
|
+
|
|
46
|
+
def initialize: (Symbol | String key, model: String) -> void
|
|
47
|
+
def field: (Symbol | String name, type: Symbol | String, ?label: String?, **untyped options) -> Field
|
|
48
|
+
def model_class: () -> untyped
|
|
49
|
+
def field!: (Symbol | String name) -> Field
|
|
50
|
+
def editable_fields: () -> Array[Field]
|
|
51
|
+
def validate!: () -> void
|
|
52
|
+
def record_label: (untyped record) -> String
|
|
53
|
+
end
|
|
54
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: airview
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- jacksonriso
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: exe
|
|
10
|
+
cert_chain: []
|
|
11
|
+
date: 2026-08-06 00:00:00.000000000 Z
|
|
12
|
+
dependencies:
|
|
13
|
+
- !ruby/object:Gem::Dependency
|
|
14
|
+
name: rails
|
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
|
16
|
+
requirements:
|
|
17
|
+
- - ">="
|
|
18
|
+
- !ruby/object:Gem::Version
|
|
19
|
+
version: '7.1'
|
|
20
|
+
- - "<"
|
|
21
|
+
- !ruby/object:Gem::Version
|
|
22
|
+
version: '9.0'
|
|
23
|
+
type: :runtime
|
|
24
|
+
prerelease: false
|
|
25
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
26
|
+
requirements:
|
|
27
|
+
- - ">="
|
|
28
|
+
- !ruby/object:Gem::Version
|
|
29
|
+
version: '7.1'
|
|
30
|
+
- - "<"
|
|
31
|
+
- !ruby/object:Gem::Version
|
|
32
|
+
version: '9.0'
|
|
33
|
+
description: Airview mounts an internal admin UI for browsing and editing explicitly
|
|
34
|
+
registered ActiveRecord models.
|
|
35
|
+
email:
|
|
36
|
+
- risojackson@gmail.com
|
|
37
|
+
executables: []
|
|
38
|
+
extensions: []
|
|
39
|
+
extra_rdoc_files: []
|
|
40
|
+
files:
|
|
41
|
+
- CHANGELOG.md
|
|
42
|
+
- CODE_OF_CONDUCT.md
|
|
43
|
+
- LICENSE.txt
|
|
44
|
+
- README.md
|
|
45
|
+
- app/assets/javascripts/airview/application.js
|
|
46
|
+
- app/assets/stylesheets/airview/application.css
|
|
47
|
+
- app/controllers/airview/application_controller.rb
|
|
48
|
+
- app/controllers/airview/resources_controller.rb
|
|
49
|
+
- app/controllers/airview/setup_controller.rb
|
|
50
|
+
- app/controllers/airview/views_controller.rb
|
|
51
|
+
- app/helpers/airview/application_helper.rb
|
|
52
|
+
- app/javascript/airview/controllers/grid_controller.js
|
|
53
|
+
- app/models/airview/application_record.rb
|
|
54
|
+
- app/models/airview/field_definition.rb
|
|
55
|
+
- app/models/airview/resource_definition.rb
|
|
56
|
+
- app/models/airview/view.rb
|
|
57
|
+
- app/views/airview/resources/_delete_modal.html.erb
|
|
58
|
+
- app/views/airview/resources/_record_modal.html.erb
|
|
59
|
+
- app/views/airview/resources/empty.html.erb
|
|
60
|
+
- app/views/airview/resources/show.html.erb
|
|
61
|
+
- app/views/airview/setup/_form.html.erb
|
|
62
|
+
- app/views/airview/setup/edit.html.erb
|
|
63
|
+
- app/views/airview/setup/index.html.erb
|
|
64
|
+
- app/views/airview/setup/new.html.erb
|
|
65
|
+
- app/views/airview/shared/_sidebar.html.erb
|
|
66
|
+
- app/views/layouts/airview/application.html.erb
|
|
67
|
+
- config/routes.rb
|
|
68
|
+
- db/migrate/20260806000000_create_airview_views.rb
|
|
69
|
+
- lib/airview.rb
|
|
70
|
+
- lib/airview/configuration.rb
|
|
71
|
+
- lib/airview/engine.rb
|
|
72
|
+
- lib/airview/field.rb
|
|
73
|
+
- lib/airview/model_discovery.rb
|
|
74
|
+
- lib/airview/query.rb
|
|
75
|
+
- lib/airview/resource.rb
|
|
76
|
+
- lib/airview/resource_builder.rb
|
|
77
|
+
- lib/airview/schema_inference.rb
|
|
78
|
+
- lib/airview/version.rb
|
|
79
|
+
- lib/generators/airview/install/install_generator.rb
|
|
80
|
+
- lib/generators/airview/install/templates/add_folders_to_airview_views.rb
|
|
81
|
+
- lib/generators/airview/install/templates/airview.rb
|
|
82
|
+
- lib/generators/airview/install/templates/create_airview_views.rb
|
|
83
|
+
- sig/airview.rbs
|
|
84
|
+
homepage: https://github.com/jacksonriso/airview
|
|
85
|
+
licenses:
|
|
86
|
+
- MIT
|
|
87
|
+
metadata:
|
|
88
|
+
homepage_uri: https://github.com/jacksonriso/airview
|
|
89
|
+
source_code_uri: https://github.com/jacksonriso/airview
|
|
90
|
+
changelog_uri: https://github.com/jacksonriso/airview/blob/main/CHANGELOG.md
|
|
91
|
+
post_install_message:
|
|
92
|
+
rdoc_options: []
|
|
93
|
+
require_paths:
|
|
94
|
+
- lib
|
|
95
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
96
|
+
requirements:
|
|
97
|
+
- - ">="
|
|
98
|
+
- !ruby/object:Gem::Version
|
|
99
|
+
version: 3.2.0
|
|
100
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
101
|
+
requirements:
|
|
102
|
+
- - ">="
|
|
103
|
+
- !ruby/object:Gem::Version
|
|
104
|
+
version: '0'
|
|
105
|
+
requirements: []
|
|
106
|
+
rubygems_version: 3.5.22
|
|
107
|
+
signing_key:
|
|
108
|
+
specification_version: 4
|
|
109
|
+
summary: A Rails engine for Airtable-like database views inside Rails apps.
|
|
110
|
+
test_files: []
|