diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 94dcd78d7..d16a13088 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -58,7 +58,7 @@ jobs: with: node-version: 14.x - name: Add public IP to AWS security group - uses: uxxman/aws-security-group-add-ip-action@patch-1 + uses: sohelamin/aws-security-group-add-ip-action@master with: aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} diff --git a/.github/workflows/dokku-clean-review-apps.yml b/.github/workflows/dokku-clean-review-apps.yml index 73c87a8d4..26217e843 100644 --- a/.github/workflows/dokku-clean-review-apps.yml +++ b/.github/workflows/dokku-clean-review-apps.yml @@ -7,7 +7,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Add public IP to AWS security group - uses: uxxman/aws-security-group-add-ip-action@patch-1 + uses: sohelamin/aws-security-group-add-ip-action@master with: aws-access-key-id: ${{ secrets.DOKKU_AWS_ACCESS_KEY_ID }} aws-secret-access-key: ${{ secrets.DOKKU_AWS_SECRET_ACCESS_KEY }} diff --git a/.github/workflows/dokku-deploy.yml b/.github/workflows/dokku-deploy.yml index d28778f8f..5bb13c093 100644 --- a/.github/workflows/dokku-deploy.yml +++ b/.github/workflows/dokku-deploy.yml @@ -20,7 +20,7 @@ jobs: fetch-depth: 0 - name: Add public IP to AWS security group - uses: uxxman/aws-security-group-add-ip-action@patch-1 + uses: sohelamin/aws-security-group-add-ip-action@master with: aws-access-key-id: ${{ secrets.DOKKU_AWS_ACCESS_KEY_ID }} aws-secret-access-key: ${{ secrets.DOKKU_AWS_SECRET_ACCESS_KEY }} @@ -49,7 +49,7 @@ jobs: fetch-depth: 0 - name: Add public IP to AWS security group - uses: uxxman/aws-security-group-add-ip-action@patch-1 + uses: sohelamin/aws-security-group-add-ip-action@master with: aws-access-key-id: ${{ secrets.DOKKU_AWS_ACCESS_KEY_ID }} aws-secret-access-key: ${{ secrets.DOKKU_AWS_SECRET_ACCESS_KEY }} @@ -114,7 +114,7 @@ jobs: if: github.event_name == 'pull_request' && ((github.event.action == 'closed' && contains(github.event.pull_request.labels.*.name, 'deploy-review-app')) || (github.event.pull_request.state == 'open' && github.event.action == 'unlabeled' && needs.checklabel.outputs.deploy_review_app_removed)) steps: - name: Add public IP to AWS security group - uses: uxxman/aws-security-group-add-ip-action@patch-1 + uses: sohelamin/aws-security-group-add-ip-action@master with: aws-access-key-id: ${{ secrets.DOKKU_AWS_ACCESS_KEY_ID }} aws-secret-access-key: ${{ secrets.DOKKU_AWS_SECRET_ACCESS_KEY }} diff --git a/Gemfile b/Gemfile index c2cbb5f11..0ee27ee9b 100644 --- a/Gemfile +++ b/Gemfile @@ -128,3 +128,5 @@ gem "strong_migrations" gem "simple_calendar", "~> 3.0" gem "icalendar", "~> 2.9" + +gem "blazer", "~> 2.6" diff --git a/Gemfile.lock b/Gemfile.lock index 0b0451feb..b0b1ed98e 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -136,6 +136,11 @@ GEM bcrypt (3.1.16) bcrypt_pbkdf (1.1.0) bindex (0.8.1) + blazer (2.6.5) + activerecord (>= 5) + chartkick (>= 3.2) + railties (>= 5) + safely_block (>= 0.1.1) bootsnap (1.7.3) msgpack (~> 1.0) builder (3.2.4) @@ -515,6 +520,7 @@ DEPENDENCIES apartment-activejob aws-sdk-s3 bcrypt_pbkdf (>= 1.0, < 2.0) + blazer (~> 2.6) bootsnap (>= 1.7.3) byebug capistrano diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 2f5483780..f93b96d0c 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -29,6 +29,10 @@ def after_accept_path_for(resource) return root_url(subdomain: Apartment::Tenant.current) end end + + def require_global_admin + redirect_to root_path unless current_user && current_user.global_admin? + end private diff --git a/app/views/comfy/admin/cms/partials/_navigation_inner.haml b/app/views/comfy/admin/cms/partials/_navigation_inner.haml index 4096c24d0..fe9f99140 100644 --- a/app/views/comfy/admin/cms/partials/_navigation_inner.haml +++ b/app/views/comfy/admin/cms/partials/_navigation_inner.haml @@ -18,4 +18,6 @@ = link_to "Blog", '/blog', class: 'dropdown-item', target: '_blank' - if Subdomain.current.forum_enabled = link_to "Forum", '/forum', class: 'dropdown-item', target: '_blank' + - if current_user.global_admin? + = link_to "Blazer BI", '/admin/blazer', class: 'dropdown-item', target: '_blank' = link_to "Logout", destroy_user_session_path, class: 'nav-link text-danger', method: :delete \ No newline at end of file diff --git a/config/blazer.yml b/config/blazer.yml new file mode 100644 index 000000000..095ccb027 --- /dev/null +++ b/config/blazer.yml @@ -0,0 +1,78 @@ +# see https://github.com/ankane/blazer for more info + +data_sources: + main: + url: <%= ENV['DATABASE_HOST'] ? "postgres://#{ENV['DATABASE_USERNAME']}:#{ENV['DATABASE_PASSWORD']}@#{ENV['DATABASE_HOST']}:#{ENV['DATABASE_PORT']}/#{ENV['DATABASE_NAME']}" : ENV['DATABASE_URL'] %> + # statement timeout, in seconds + # none by default + # timeout: 15 + + # caching settings + # can greatly improve speed + # off by default + # cache: + # mode: slow # or all + # expires_in: 60 # min + # slow_threshold: 15 # sec, only used in slow mode + + # wrap queries in a transaction for safety + # not necessary if you use a read-only user + # true by default + # use_transaction: false + + smart_variables: + # zone_id: "SELECT id, name FROM zones ORDER BY name ASC" + # period: ["day", "week", "month"] + # status: {0: "Active", 1: "Archived"} + + linked_columns: + # user_id: "/admin/users/{value}" + + smart_columns: + # user_id: "SELECT id, name FROM users WHERE id IN {value}" + +# create audits +audit: true + +# change the time zone +# time_zone: "Pacific Time (US & Canada)" + +# class name of the user model +# user_class: User + +# method name for the current user +# user_method: current_user + +# method name for the display name +# user_name: name + +# custom before_action to use for auth +before_action_method: require_global_admin + +# email to send checks from +# from_email: blazer@example.org + +# webhook for Slack +# slack_webhook_url: <%= ENV["BLAZER_SLACK_WEBHOOK_URL"] %> + +check_schedules: + - "1 day" + - "1 hour" + - "5 minutes" + +# enable anomaly detection +# note: with trend, time series are sent to https://trendapi.org +# anomaly_checks: prophet / trend / anomaly_detection + +# enable forecasting +# note: with trend, time series are sent to https://trendapi.org +# forecasting: prophet / trend + +# enable map +# mapbox_access_token: <%= ENV["MAPBOX_ACCESS_TOKEN"] %> + +# enable uploads +# uploads: +# url: <%= ENV["BLAZER_UPLOADS_URL"] %> +# schema: uploads +# data_source: main diff --git a/config/routes.rb b/config/routes.rb index 77916151d..0dba871df 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -128,6 +128,7 @@ def self.matches?(request) mount Sidekiq::Web => '/sidekiq' mount GraphiQL::Rails::Engine, at: "/graphiql", graphql_path: "/graphql" get 'web_console', to: 'web_console#index' + mount Blazer::Engine, at: "blazer" end resources :subdomain_requests, except: [:new, :create] do member do diff --git a/db/migrate/20241130190941_install_blazer.rb b/db/migrate/20241130190941_install_blazer.rb new file mode 100644 index 000000000..b55a18a99 --- /dev/null +++ b/db/migrate/20241130190941_install_blazer.rb @@ -0,0 +1,47 @@ +class InstallBlazer < ActiveRecord::Migration[6.1] + def change + create_table :blazer_queries do |t| + t.references :creator + t.string :name + t.text :description + t.text :statement + t.string :data_source + t.string :status + t.timestamps null: false + end + + create_table :blazer_audits do |t| + t.references :user + t.references :query + t.text :statement + t.string :data_source + t.datetime :created_at + end + + create_table :blazer_dashboards do |t| + t.references :creator + t.string :name + t.timestamps null: false + end + + create_table :blazer_dashboard_queries do |t| + t.references :dashboard + t.references :query + t.integer :position + t.timestamps null: false + end + + create_table :blazer_checks do |t| + t.references :creator + t.references :query + t.string :state + t.string :schedule + t.text :emails + t.text :slack_channels + t.string :check_type + t.text :message + t.datetime :last_run_at + t.timestamps null: false + end + end +end diff --git a/db/schema.rb b/db/schema.rb index 1a8f9a4a5..e2815753b 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 2024_10_06_155850) do +ActiveRecord::Schema.define(version: 2024_11_30_190941) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -208,6 +208,62 @@ t.index ["user_id"], name: "index_api_resources_on_user_id" end + create_table "blazer_audits", force: :cascade do |t| + t.bigint "user_id" + t.bigint "query_id" + t.text "statement" + t.string "data_source" + t.datetime "created_at" + t.index ["query_id"], name: "index_blazer_audits_on_query_id" + t.index ["user_id"], name: "index_blazer_audits_on_user_id" + end + + create_table "blazer_checks", force: :cascade do |t| + t.bigint "creator_id" + t.bigint "query_id" + t.string "state" + t.string "schedule" + t.text "emails" + t.text "slack_channels" + t.string "check_type" + t.text "message" + t.datetime "last_run_at" + t.datetime "created_at", precision: 6, null: false + t.datetime "updated_at", precision: 6, null: false + t.index ["creator_id"], name: "index_blazer_checks_on_creator_id" + t.index ["query_id"], name: "index_blazer_checks_on_query_id" + end + + create_table "blazer_dashboard_queries", force: :cascade do |t| + t.bigint "dashboard_id" + t.bigint "query_id" + t.integer "position" + t.datetime "created_at", precision: 6, null: false + t.datetime "updated_at", precision: 6, null: false + t.index ["dashboard_id"], name: "index_blazer_dashboard_queries_on_dashboard_id" + t.index ["query_id"], name: "index_blazer_dashboard_queries_on_query_id" + end + + create_table "blazer_dashboards", force: :cascade do |t| + t.bigint "creator_id" + t.string "name" + t.datetime "created_at", precision: 6, null: false + t.datetime "updated_at", precision: 6, null: false + t.index ["creator_id"], name: "index_blazer_dashboards_on_creator_id" + end + + create_table "blazer_queries", force: :cascade do |t| + t.bigint "creator_id" + t.string "name" + t.text "description" + t.text "statement" + t.string "data_source" + t.string "status" + t.datetime "created_at", precision: 6, null: false + t.datetime "updated_at", precision: 6, null: false + t.index ["creator_id"], name: "index_blazer_queries_on_creator_id" + end + create_table "comfy_blog_posts", force: :cascade do |t| t.integer "site_id", null: false t.string "title", null: false