2 |
Reset your DB SQL password
3 |
4 | You receive this mail because there was request to reset your DB SQL password. Please ignore this mail when you didn't request a new password.
5 |
6 |
7 | To reset your password, click here:
8 |
18 | Best regards,
19 |
20 | Your DB-SQL Team
21 |
22 |
23 |
24 | if the link does not work, navigate to the following url:
25 |
26 |
27 | <%= "#{root_url}users/#{@user.id}/reset_password?reset_token=#{@user.reset_password_token}" %>
28 |
29 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/colors.scss:
--------------------------------------------------------------------------------
1 | :root {
2 |
3 | --semantic-red: #db2828;
4 | --semantic-orange: #f2711c;
5 | --semantic-yellow: #fbbd08;
6 | --semantic-olive: #b5cc18;
7 | --semantic-green: #21ba45;
8 | --semantic-teal: #00b5ad;
9 | --semantic-blue: #2185d0;
10 | --semantic-violet: #6435c9;
11 | --semantic-purple: #a333c8;
12 | --semantic-pink: #e03997;
13 | --semantic-brown: #a5673f;
14 | --semantic-lightgrey: #e8e8e8;
15 | --semantic-grey: #767676;
16 | --semantic-black: #1b1c1d;
17 |
18 | --highlight-grey-10: #fafafa;
19 | --highlight-grey-20: #f3f2f2;
20 | --highlight-grey-50: #e7e6e6;
21 | --highlight-grey-100: #929292;
22 | --highlight-green-50: #b4f7ba;
23 | --highlight-green-100: #c9facd;
24 | --highlight-blue-50: #b1caff;
25 | --highlight-blue-100: #c3d2f3;
26 | --highlight-teal-50: #00b5ac25;
27 | }
--------------------------------------------------------------------------------
/app/policies/sql_query_policy.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | class SqlQueryPolicy < ApplicationPolicy
4 | def show?
5 | return true if record.user_id == user.id
6 | return false unless record.public?
7 | return false unless record.db_server.owner_type == :group
8 |
9 | record.db_server.owner.member? user
10 | end
11 |
12 | def create?
13 | true
14 | end
15 |
16 | def update?
17 | return true if record.user_id == user.id
18 | return false unless record.db_server.owner_type == :group
19 | record.db_server.owner.admin? user
20 | end
21 |
22 | def destroy?
23 | user.admin?
24 | end
25 |
26 | def index?
27 | true
28 | end
29 |
30 | class Scope < Scope
31 |
32 | # @return [ActiveRecord::Relation
]
33 | def resolve
34 | scope.where(user: user)
35 | end
36 | end
37 | end
38 |
--------------------------------------------------------------------------------
/app/models/login_token.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | # == Schema Information
4 | #
5 | # Table name: login_tokens
6 | #
7 | # id :uuid not null, primary key
8 | # token :string
9 | # created_at :datetime not null
10 | # updated_at :datetime not null
11 | # user_id :uuid not null
12 | #
13 | # Indexes
14 | #
15 | # index_login_tokens_on_user_id (user_id)
16 | #
17 | # Foreign Keys
18 | #
19 | # fk_rails_... (user_id => users.id)
20 | #
21 |
22 | class LoginToken < ApplicationRecord
23 | belongs_to :user
24 |
25 | before_create :generate_token
26 |
27 | TOKEN_VALID_DURATION = 14.days
28 |
29 | def expired?
30 | DateTime.now >= created_at + TOKEN_VALID_DURATION
31 | end
32 |
33 | private
34 |
35 | def generate_token
36 | self.token = SecureRandom.hex(32)
37 | end
38 | end
39 |
--------------------------------------------------------------------------------
/app/policies/user_policy.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | class UserPolicy < ApplicationPolicy
4 | def show?
5 | user.admin? || user.id == record.id
6 | end
7 |
8 | def create?
9 | true
10 | end
11 |
12 | def update?
13 | user.admin? || user.id == record.id
14 | end
15 |
16 | def set_password?
17 | record.id == user.id
18 | end
19 |
20 | def destroy?
21 | if user.admin?
22 | user.id != record.id
23 | else
24 | user.id == record.id
25 | end
26 | end
27 |
28 | def index?
29 | user.admin?
30 | end
31 |
32 | def group_index?
33 | true
34 | end
35 |
36 | class Scope < Scope
37 |
38 | # @return [ActiveRecord::Relation]
39 | def resolve
40 | return scope.all if user.admin?
41 |
42 | scope.where('id = :user', user: user.id)
43 | end
44 | end
45 | end
46 |
--------------------------------------------------------------------------------
/app/api/resources/status.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Resources
4 | class Status < Grape::API
5 | route_setting :auth, disabled: true
6 | get :commit do
7 |
8 | commit, short_sha = if Rails.env.production? || Rails.env.staging?
9 | sha = ENV.fetch('GIT_REV') { File.read(Rails.root.join('REVISION')) }
10 | short = sha[0...7]
11 | [sha, short]
12 | else
13 | sha = `git rev-parse HEAD`.strip
14 | short = `git rev-parse --short HEAD`.strip
15 | [sha, short]
16 | end
17 |
18 | @link = "https://github.com/lebalz/db-sql/commit/#{commit}"
19 | @commit = short_sha
20 | { commit: @commit, link: @link }
21 | end
22 | end
23 | end
24 |
--------------------------------------------------------------------------------
/swagger-ui-dist/index.js:
--------------------------------------------------------------------------------
1 | try {
2 | module.exports.SwaggerUIBundle = require("./swagger-ui-bundle.js")
3 | module.exports.SwaggerUIStandalonePreset = require("./swagger-ui-standalone-preset.js")
4 | } catch(e) {
5 | // swallow the error if there's a problem loading the assets.
6 | // allows this module to support providing the assets for browserish contexts,
7 | // without exploding in a Node context.
8 | //
9 | // see https://github.com/swagger-api/swagger-ui/issues/3291#issuecomment-311195388
10 | // for more information.
11 | }
12 |
13 | // `absolutePath` and `getAbsoluteFSPath` are both here because at one point,
14 | // we documented having one and actually implemented the other.
15 | // They were both retained so we don't break anyone's code.
16 | module.exports.absolutePath = require("./absolute-path.js")
17 | module.exports.getAbsoluteFSPath = require("./absolute-path.js")
18 |
--------------------------------------------------------------------------------
/app/javascript/shared/helpers.ts:
--------------------------------------------------------------------------------
1 | import { SemanticCOLORS, SemanticICONS } from 'semantic-ui-react';
2 | import { ResultState } from '../api/db_server';
3 | import { CopyState, TableData } from '../models/Result';
4 |
5 | export const copyIconColor = (state: CopyState): SemanticCOLORS | undefined => {
6 | switch (state) {
7 | case CopyState.Error:
8 | return 'red';
9 | case CopyState.Success:
10 | return 'green';
11 | case CopyState.Ready:
12 | return;
13 | case CopyState.Copying:
14 | return 'blue';
15 | }
16 | };
17 |
18 | export const copyIcon = (state: CopyState): SemanticICONS => {
19 | switch (state) {
20 | case CopyState.Error:
21 | return 'close';
22 | case CopyState.Success:
23 | return 'check';
24 | case CopyState.Ready:
25 | return 'copy';
26 | case CopyState.Copying:
27 | return 'copy';
28 | }
29 | };
30 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | sudo: required
2 |
3 | services:
4 | - docker
5 |
6 | env:
7 | DOCKER_COMPOSE_VERSION: 1.25.5
8 |
9 | before_install:
10 | - sudo rm /usr/local/bin/docker-compose
11 | - curl -L https://github.com/docker/compose/releases/download/${DOCKER_COMPOSE_VERSION}/docker-compose-`uname -s`-`uname -m` > docker-compose
12 | - chmod +x docker-compose
13 | - sudo mv docker-compose /usr/local/bin
14 | addons:
15 | postgresql: "10"
16 | apt:
17 | packages:
18 | - postgresql-10
19 | - postgresql-client-10
20 | before_script:
21 | - cp config/database.yml.travis config/database.yml
22 | - psql -c 'DROP DATABASE IF EXISTS travis_ci_test;' -U postgres
23 | - psql -c 'CREATE DATABASE travis_ci_test;' -U postgres
24 | dist: xenial
25 | language: ruby
26 | rvm:
27 | - 3.0.1
28 | script:
29 | - bundle exec rails db:setup RAILS_ENV=test
30 | - bundle exec rspec --format doc
--------------------------------------------------------------------------------
/bin/pry:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | # frozen_string_literal: true
3 |
4 | #
5 | # This file was generated by Bundler.
6 | #
7 | # The application 'pry' is installed as part of a gem, and
8 | # this file is here to facilitate running it.
9 | #
10 |
11 | require "pathname"
12 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
13 | Pathname.new(__FILE__).realpath)
14 |
15 | bundle_binstub = File.expand_path("../bundle", __FILE__)
16 |
17 | if File.file?(bundle_binstub)
18 | if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/
19 | load(bundle_binstub)
20 | else
21 | abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
22 | Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
23 | end
24 | end
25 |
26 | require "rubygems"
27 | require "bundler/setup"
28 |
29 | load Gem.bin_path("pry", "pry")
30 |
--------------------------------------------------------------------------------
/bin/yri:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | # frozen_string_literal: true
3 |
4 | #
5 | # This file was generated by Bundler.
6 | #
7 | # The application 'yri' is installed as part of a gem, and
8 | # this file is here to facilitate running it.
9 | #
10 |
11 | require "pathname"
12 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
13 | Pathname.new(__FILE__).realpath)
14 |
15 | bundle_binstub = File.expand_path("../bundle", __FILE__)
16 |
17 | if File.file?(bundle_binstub)
18 | if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/
19 | load(bundle_binstub)
20 | else
21 | abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
22 | Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
23 | end
24 | end
25 |
26 | require "rubygems"
27 | require "bundler/setup"
28 |
29 | load Gem.bin_path("yard", "yri")
30 |
--------------------------------------------------------------------------------
/config/credentials.yml.enc:
--------------------------------------------------------------------------------
1 | OOqreVdZCgxbk3wovGQYpGmfKDBZp1BvElBVojp2bsLrncCDcBXbOFBnoWKsDW4Yzfw6To9wVXegbHMT8V9yQ5Ar90HjsQXGsWm+umko7dZSMryHiR9eQfVlMgyXwZ2iZiPO//9FB2A8EhyfPG5uCiLG3XowjTPziea1Kdq5Gko8gmWGXWqkBi25qtaVRTMZ5NDYA2pYfe5V9IzrFDLM7enrQHwroJ2R57QcsWWv+afKw5/VM24B3mvKmW9LH0/5MUo8PSEb07Yiyb0yBQsI9gD9ZkpZHKw0YtwHr6FkkFS1ZuynonrgPOaUH8JnY6B49c731NrP3k73WUb5rJHXrDCTfUKpgL6rCKLDnmXaDOPq2kM0SA1fE0j1KNvxxu6nvLRL4aqIJhvz2TwpHIFRsj0uavvwbKCHOzjim6unEedZetlwVLTCSyqZzYFNGd2gAgPMGohw4MGixge9OZ10fU2hnSh7YY2oYMTt5BQKKxz0RMux3Ph2gSc5GhZB6RpUkytuHYxWtX3xyQi/ozGpHIDDwuQ+TFnASuXoTDH3cHIIlJSqg9X6vkL4w182tSnmPI/O1Pl5gEczQyoCycZpo/A/YayIXV/JaTDrC8Ip8wDNb5jNHOe8nsGDYOjQnbb12m677rUpVrmQ/Y2cdF4vtQ/rsDTZB4rVxWXdG4N5nyW1K/BoO4io3AVjLCMRHJsEfT17dE/1KiX3c7uVnb1oC+KYX3aDVFSxJYOQE4bDQdsy6uonn3rQxtMCELo4pHNvbabLj5Z4/gdP6c2snpIumE7ddCxdcS3+jAXXLOgl8IFYoDyRxKc4r9IE2UD9WN7BVWcTwjhMWqTP+q+89zJKYoJmxpw75bk2dM8=--kkld2oj00OS2rO24--g0kzzjrGMd/+jl8bxbf6Og==
--------------------------------------------------------------------------------
/docs/security_concepts.md:
--------------------------------------------------------------------------------
1 | # Securityconcepts of db-sql
2 |
3 | ## How does db-sql store db credentials on the server?
4 |
5 | The backend is responsible to establish the database connections. This leads to the necissity of storing sensitive credentials on the server. To ensure no data breaches happens in case of an unauthorized server access, passwords are encrypted with a key only known by the client. Concrete a HMAC-SHA256 key with 20'000 iterations and a unique salt is generated from the password of the users db-sql login ([user.rb#crypto_key](/app/models/user.rb)). It is stored client-side in the local storage of the browser.
6 |
7 | When creating a new database server, the password is encrypted with AES-256-CBC where the crypto_key fungates as the cipher key.
8 |
9 | Foreach query to this database server, the caller must provide the crypto_key to decrypt the database server password for the connection.
10 |
--------------------------------------------------------------------------------
/app/javascript/stores/status_store.ts:
--------------------------------------------------------------------------------
1 | import { observable, action, computed } from 'mobx';
2 | import { RootStore, Store } from './root_store';
3 | import _ from 'lodash';
4 | import { getCommit, Commit } from '../api/status';
5 |
6 | class State {
7 | @observable commit: Commit = { commit: '', link: '' }
8 | }
9 |
10 | class StatusStore implements Store {
11 | private readonly root: RootStore;
12 | @observable.ref
13 | private state = new State();
14 |
15 | constructor(root: RootStore) {
16 | this.root = root;
17 | this.loadCommit();
18 | }
19 |
20 | @action
21 | loadCommit() {
22 | getCommit().then(({ data }) => {
23 | this.state.commit = data;
24 | });
25 | }
26 |
27 | @computed
28 | get commit() {
29 | return this.state.commit;
30 | }
31 |
32 | @action cleanup() {
33 | this.state = new State();
34 | }
35 | }
36 |
37 | export default StatusStore;
38 |
--------------------------------------------------------------------------------
/bin/haml:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | # frozen_string_literal: true
3 |
4 | #
5 | # This file was generated by Bundler.
6 | #
7 | # The application 'haml' is installed as part of a gem, and
8 | # this file is here to facilitate running it.
9 | #
10 |
11 | require "pathname"
12 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
13 | Pathname.new(__FILE__).realpath)
14 |
15 | bundle_binstub = File.expand_path("../bundle", __FILE__)
16 |
17 | if File.file?(bundle_binstub)
18 | if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/
19 | load(bundle_binstub)
20 | else
21 | abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
22 | Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
23 | end
24 | end
25 |
26 | require "rubygems"
27 | require "bundler/setup"
28 |
29 | load Gem.bin_path("haml", "haml")
30 |
--------------------------------------------------------------------------------
/bin/puma:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | # frozen_string_literal: true
3 |
4 | #
5 | # This file was generated by Bundler.
6 | #
7 | # The application 'puma' is installed as part of a gem, and
8 | # this file is here to facilitate running it.
9 | #
10 |
11 | require "pathname"
12 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
13 | Pathname.new(__FILE__).realpath)
14 |
15 | bundle_binstub = File.expand_path("../bundle", __FILE__)
16 |
17 | if File.file?(bundle_binstub)
18 | if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/
19 | load(bundle_binstub)
20 | else
21 | abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
22 | Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
23 | end
24 | end
25 |
26 | require "rubygems"
27 | require "bundler/setup"
28 |
29 | load Gem.bin_path("puma", "puma")
30 |
--------------------------------------------------------------------------------
/bin/sass:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | # frozen_string_literal: true
3 |
4 | #
5 | # This file was generated by Bundler.
6 | #
7 | # The application 'sass' is installed as part of a gem, and
8 | # this file is here to facilitate running it.
9 | #
10 |
11 | require "pathname"
12 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
13 | Pathname.new(__FILE__).realpath)
14 |
15 | bundle_binstub = File.expand_path("../bundle", __FILE__)
16 |
17 | if File.file?(bundle_binstub)
18 | if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/
19 | load(bundle_binstub)
20 | else
21 | abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
22 | Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
23 | end
24 | end
25 |
26 | require "rubygems"
27 | require "bundler/setup"
28 |
29 | load Gem.bin_path("sass", "sass")
30 |
--------------------------------------------------------------------------------
/bin/scss:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | # frozen_string_literal: true
3 |
4 | #
5 | # This file was generated by Bundler.
6 | #
7 | # The application 'scss' is installed as part of a gem, and
8 | # this file is here to facilitate running it.
9 | #
10 |
11 | require "pathname"
12 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
13 | Pathname.new(__FILE__).realpath)
14 |
15 | bundle_binstub = File.expand_path("../bundle", __FILE__)
16 |
17 | if File.file?(bundle_binstub)
18 | if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/
19 | load(bundle_binstub)
20 | else
21 | abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
22 | Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
23 | end
24 | end
25 |
26 | require "rubygems"
27 | require "bundler/setup"
28 |
29 | load Gem.bin_path("sass", "scss")
30 |
--------------------------------------------------------------------------------
/bin/thin:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | # frozen_string_literal: true
3 |
4 | #
5 | # This file was generated by Bundler.
6 | #
7 | # The application 'thin' is installed as part of a gem, and
8 | # this file is here to facilitate running it.
9 | #
10 |
11 | require "pathname"
12 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
13 | Pathname.new(__FILE__).realpath)
14 |
15 | bundle_binstub = File.expand_path("../bundle", __FILE__)
16 |
17 | if File.file?(bundle_binstub)
18 | if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/
19 | load(bundle_binstub)
20 | else
21 | abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
22 | Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
23 | end
24 | end
25 |
26 | require "rubygems"
27 | require "bundler/setup"
28 |
29 | load Gem.bin_path("thin", "thin")
30 |
--------------------------------------------------------------------------------
/bin/thor:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | # frozen_string_literal: true
3 |
4 | #
5 | # This file was generated by Bundler.
6 | #
7 | # The application 'thor' is installed as part of a gem, and
8 | # this file is here to facilitate running it.
9 | #
10 |
11 | require "pathname"
12 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
13 | Pathname.new(__FILE__).realpath)
14 |
15 | bundle_binstub = File.expand_path("../bundle", __FILE__)
16 |
17 | if File.file?(bundle_binstub)
18 | if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/
19 | load(bundle_binstub)
20 | else
21 | abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
22 | Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
23 | end
24 | end
25 |
26 | require "rubygems"
27 | require "bundler/setup"
28 |
29 | load Gem.bin_path("thor", "thor")
30 |
--------------------------------------------------------------------------------
/bin/tilt:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | # frozen_string_literal: true
3 |
4 | #
5 | # This file was generated by Bundler.
6 | #
7 | # The application 'tilt' is installed as part of a gem, and
8 | # this file is here to facilitate running it.
9 | #
10 |
11 | require "pathname"
12 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
13 | Pathname.new(__FILE__).realpath)
14 |
15 | bundle_binstub = File.expand_path("../bundle", __FILE__)
16 |
17 | if File.file?(bundle_binstub)
18 | if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/
19 | load(bundle_binstub)
20 | else
21 | abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
22 | Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
23 | end
24 | end
25 |
26 | require "rubygems"
27 | require "bundler/setup"
28 |
29 | load Gem.bin_path("tilt", "tilt")
30 |
--------------------------------------------------------------------------------
/bin/yard:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | # frozen_string_literal: true
3 |
4 | #
5 | # This file was generated by Bundler.
6 | #
7 | # The application 'yard' is installed as part of a gem, and
8 | # this file is here to facilitate running it.
9 | #
10 |
11 | require "pathname"
12 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
13 | Pathname.new(__FILE__).realpath)
14 |
15 | bundle_binstub = File.expand_path("../bundle", __FILE__)
16 |
17 | if File.file?(bundle_binstub)
18 | if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/
19 | load(bundle_binstub)
20 | else
21 | abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
22 | Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
23 | end
24 | end
25 |
26 | require "rubygems"
27 | require "bundler/setup"
28 |
29 | load Gem.bin_path("yard", "yard")
30 |
--------------------------------------------------------------------------------
/bin/ldiff:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | # frozen_string_literal: true
3 |
4 | #
5 | # This file was generated by Bundler.
6 | #
7 | # The application 'ldiff' is installed as part of a gem, and
8 | # this file is here to facilitate running it.
9 | #
10 |
11 | require "pathname"
12 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
13 | Pathname.new(__FILE__).realpath)
14 |
15 | bundle_binstub = File.expand_path("../bundle", __FILE__)
16 |
17 | if File.file?(bundle_binstub)
18 | if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/
19 | load(bundle_binstub)
20 | else
21 | abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
22 | Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
23 | end
24 | end
25 |
26 | require "rubygems"
27 | require "bundler/setup"
28 |
29 | load Gem.bin_path("diff-lcs", "ldiff")
30 |
--------------------------------------------------------------------------------
/bin/rackup:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | # frozen_string_literal: true
3 |
4 | #
5 | # This file was generated by Bundler.
6 | #
7 | # The application 'rackup' is installed as part of a gem, and
8 | # this file is here to facilitate running it.
9 | #
10 |
11 | require "pathname"
12 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
13 | Pathname.new(__FILE__).realpath)
14 |
15 | bundle_binstub = File.expand_path("../bundle", __FILE__)
16 |
17 | if File.file?(bundle_binstub)
18 | if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/
19 | load(bundle_binstub)
20 | else
21 | abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
22 | Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
23 | end
24 | end
25 |
26 | require "rubygems"
27 | require "bundler/setup"
28 |
29 | load Gem.bin_path("rack", "rackup")
30 |
--------------------------------------------------------------------------------
/bin/yardoc:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | # frozen_string_literal: true
3 |
4 | #
5 | # This file was generated by Bundler.
6 | #
7 | # The application 'yardoc' is installed as part of a gem, and
8 | # this file is here to facilitate running it.
9 | #
10 |
11 | require "pathname"
12 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
13 | Pathname.new(__FILE__).realpath)
14 |
15 | bundle_binstub = File.expand_path("../bundle", __FILE__)
16 |
17 | if File.file?(bundle_binstub)
18 | if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/
19 | load(bundle_binstub)
20 | else
21 | abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
22 | Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
23 | end
24 | end
25 |
26 | require "rubygems"
27 | require "bundler/setup"
28 |
29 | load Gem.bin_path("yard", "yardoc")
30 |
--------------------------------------------------------------------------------
/bin/byebug:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | # frozen_string_literal: true
3 |
4 | #
5 | # This file was generated by Bundler.
6 | #
7 | # The application 'byebug' is installed as part of a gem, and
8 | # this file is here to facilitate running it.
9 | #
10 |
11 | require "pathname"
12 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
13 | Pathname.new(__FILE__).realpath)
14 |
15 | bundle_binstub = File.expand_path("../bundle", __FILE__)
16 |
17 | if File.file?(bundle_binstub)
18 | if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/
19 | load(bundle_binstub)
20 | else
21 | abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
22 | Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
23 | end
24 | end
25 |
26 | require "rubygems"
27 | require "bundler/setup"
28 |
29 | load Gem.bin_path("byebug", "byebug")
30 |
--------------------------------------------------------------------------------
/bin/coderay:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | # frozen_string_literal: true
3 |
4 | #
5 | # This file was generated by Bundler.
6 | #
7 | # The application 'coderay' is installed as part of a gem, and
8 | # this file is here to facilitate running it.
9 | #
10 |
11 | require "pathname"
12 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
13 | Pathname.new(__FILE__).realpath)
14 |
15 | bundle_binstub = File.expand_path("../bundle", __FILE__)
16 |
17 | if File.file?(bundle_binstub)
18 | if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/
19 | load(bundle_binstub)
20 | else
21 | abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
22 | Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
23 | end
24 | end
25 |
26 | require "rubygems"
27 | require "bundler/setup"
28 |
29 | load Gem.bin_path("coderay", "coderay")
30 |
--------------------------------------------------------------------------------
/bin/dotenv:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | # frozen_string_literal: true
3 |
4 | #
5 | # This file was generated by Bundler.
6 | #
7 | # The application 'dotenv' is installed as part of a gem, and
8 | # this file is here to facilitate running it.
9 | #
10 |
11 | require "pathname"
12 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
13 | Pathname.new(__FILE__).realpath)
14 |
15 | bundle_binstub = File.expand_path("../bundle", __FILE__)
16 |
17 | if File.file?(bundle_binstub)
18 | if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/
19 | load(bundle_binstub)
20 | else
21 | abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
22 | Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
23 | end
24 | end
25 |
26 | require "rubygems"
27 | require "bundler/setup"
28 |
29 | load Gem.bin_path("dotenv", "dotenv")
30 |
--------------------------------------------------------------------------------
/bin/gitlab:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | # frozen_string_literal: true
3 |
4 | #
5 | # This file was generated by Bundler.
6 | #
7 | # The application 'gitlab' is installed as part of a gem, and
8 | # this file is here to facilitate running it.
9 | #
10 |
11 | require "pathname"
12 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
13 | Pathname.new(__FILE__).realpath)
14 |
15 | bundle_binstub = File.expand_path("../bundle", __FILE__)
16 |
17 | if File.file?(bundle_binstub)
18 | if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/
19 | load(bundle_binstub)
20 | else
21 | abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
22 | Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
23 | end
24 | end
25 |
26 | require "rubygems"
27 | require "bundler/setup"
28 |
29 | load Gem.bin_path("gitlab", "gitlab")
30 |
--------------------------------------------------------------------------------
/bin/launchy:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | # frozen_string_literal: true
3 |
4 | #
5 | # This file was generated by Bundler.
6 | #
7 | # The application 'launchy' is installed as part of a gem, and
8 | # this file is here to facilitate running it.
9 | #
10 |
11 | require "pathname"
12 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
13 | Pathname.new(__FILE__).realpath)
14 |
15 | bundle_binstub = File.expand_path("../bundle", __FILE__)
16 |
17 | if File.file?(bundle_binstub)
18 | if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/
19 | load(bundle_binstub)
20 | else
21 | abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
22 | Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
23 | end
24 | end
25 |
26 | require "rubygems"
27 | require "bundler/setup"
28 |
29 | load Gem.bin_path("launchy", "launchy")
30 |
--------------------------------------------------------------------------------
/bin/listen:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | # frozen_string_literal: true
3 |
4 | #
5 | # This file was generated by Bundler.
6 | #
7 | # The application 'listen' is installed as part of a gem, and
8 | # this file is here to facilitate running it.
9 | #
10 |
11 | require "pathname"
12 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
13 | Pathname.new(__FILE__).realpath)
14 |
15 | bundle_binstub = File.expand_path("../bundle", __FILE__)
16 |
17 | if File.file?(bundle_binstub)
18 | if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/
19 | load(bundle_binstub)
20 | else
21 | abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
22 | Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
23 | end
24 | end
25 |
26 | require "rubygems"
27 | require "bundler/setup"
28 |
29 | load Gem.bin_path("listen", "listen")
30 |
--------------------------------------------------------------------------------
/bin/maruku:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | # frozen_string_literal: true
3 |
4 | #
5 | # This file was generated by Bundler.
6 | #
7 | # The application 'maruku' is installed as part of a gem, and
8 | # this file is here to facilitate running it.
9 | #
10 |
11 | require "pathname"
12 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
13 | Pathname.new(__FILE__).realpath)
14 |
15 | bundle_binstub = File.expand_path("../bundle", __FILE__)
16 |
17 | if File.file?(bundle_binstub)
18 | if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/
19 | load(bundle_binstub)
20 | else
21 | abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
22 | Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
23 | end
24 | end
25 |
26 | require "rubygems"
27 | require "bundler/setup"
28 |
29 | load Gem.bin_path("maruku", "maruku")
30 |
--------------------------------------------------------------------------------
/bin/marutex:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | # frozen_string_literal: true
3 |
4 | #
5 | # This file was generated by Bundler.
6 | #
7 | # The application 'marutex' is installed as part of a gem, and
8 | # this file is here to facilitate running it.
9 | #
10 |
11 | require "pathname"
12 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
13 | Pathname.new(__FILE__).realpath)
14 |
15 | bundle_binstub = File.expand_path("../bundle", __FILE__)
16 |
17 | if File.file?(bundle_binstub)
18 | if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/
19 | load(bundle_binstub)
20 | else
21 | abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
22 | Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
23 | end
24 | end
25 |
26 | require "rubygems"
27 | require "bundler/setup"
28 |
29 | load Gem.bin_path("maruku", "marutex")
30 |
--------------------------------------------------------------------------------
/bin/pronto:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | # frozen_string_literal: true
3 |
4 | #
5 | # This file was generated by Bundler.
6 | #
7 | # The application 'pronto' is installed as part of a gem, and
8 | # this file is here to facilitate running it.
9 | #
10 |
11 | require "pathname"
12 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
13 | Pathname.new(__FILE__).realpath)
14 |
15 | bundle_binstub = File.expand_path("../bundle", __FILE__)
16 |
17 | if File.file?(bundle_binstub)
18 | if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/
19 | load(bundle_binstub)
20 | else
21 | abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
22 | Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
23 | end
24 | end
25 |
26 | require "rubygems"
27 | require "bundler/setup"
28 |
29 | load Gem.bin_path("pronto", "pronto")
30 |
--------------------------------------------------------------------------------
/bin/pumactl:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | # frozen_string_literal: true
3 |
4 | #
5 | # This file was generated by Bundler.
6 | #
7 | # The application 'pumactl' is installed as part of a gem, and
8 | # this file is here to facilitate running it.
9 | #
10 |
11 | require "pathname"
12 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
13 | Pathname.new(__FILE__).realpath)
14 |
15 | bundle_binstub = File.expand_path("../bundle", __FILE__)
16 |
17 | if File.file?(bundle_binstub)
18 | if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/
19 | load(bundle_binstub)
20 | else
21 | abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
22 | Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
23 | end
24 | end
25 |
26 | require "rubygems"
27 | require "bundler/setup"
28 |
29 | load Gem.bin_path("puma", "pumactl")
30 |
--------------------------------------------------------------------------------
/bin/rspec:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | # frozen_string_literal: true
3 |
4 | #
5 | # This file was generated by Bundler.
6 | #
7 | # The application 'rspec' is installed as part of a gem, and
8 | # this file is here to facilitate running it.
9 | #
10 |
11 | require "pathname"
12 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
13 | Pathname.new(__FILE__).realpath)
14 |
15 | bundle_binstub = File.expand_path("../bundle", __FILE__)
16 |
17 | if File.file?(bundle_binstub)
18 | if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/
19 | load(bundle_binstub)
20 | else
21 | abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
22 | Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
23 | end
24 | end
25 |
26 | require "rubygems"
27 | require "bundler/setup"
28 |
29 | load Gem.bin_path("rspec-core", "rspec")
30 |
--------------------------------------------------------------------------------
/bin/rubocop:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | # frozen_string_literal: true
3 |
4 | #
5 | # This file was generated by Bundler.
6 | #
7 | # The application 'rubocop' is installed as part of a gem, and
8 | # this file is here to facilitate running it.
9 | #
10 |
11 | require "pathname"
12 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
13 | Pathname.new(__FILE__).realpath)
14 |
15 | bundle_binstub = File.expand_path("../bundle", __FILE__)
16 |
17 | if File.file?(bundle_binstub)
18 | if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/
19 | load(bundle_binstub)
20 | else
21 | abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
22 | Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
23 | end
24 | end
25 |
26 | require "rubygems"
27 | require "bundler/setup"
28 |
29 | load Gem.bin_path("rubocop", "rubocop")
30 |
--------------------------------------------------------------------------------
/bin/sidekiq:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | # frozen_string_literal: true
3 |
4 | #
5 | # This file was generated by Bundler.
6 | #
7 | # The application 'sidekiq' is installed as part of a gem, and
8 | # this file is here to facilitate running it.
9 | #
10 |
11 | require "pathname"
12 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
13 | Pathname.new(__FILE__).realpath)
14 |
15 | bundle_binstub = File.expand_path("../bundle", __FILE__)
16 |
17 | if File.file?(bundle_binstub)
18 | if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/
19 | load(bundle_binstub)
20 | else
21 | abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
22 | Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
23 | end
24 | end
25 |
26 | require "rubygems"
27 | require "bundler/setup"
28 |
29 | load Gem.bin_path("sidekiq", "sidekiq")
30 |
--------------------------------------------------------------------------------
/bin/spring:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | # frozen_string_literal: true
3 |
4 | #
5 | # This file was generated by Bundler.
6 | #
7 | # The application 'spring' is installed as part of a gem, and
8 | # this file is here to facilitate running it.
9 | #
10 |
11 | require "pathname"
12 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
13 | Pathname.new(__FILE__).realpath)
14 |
15 | bundle_binstub = File.expand_path("../bundle", __FILE__)
16 |
17 | if File.file?(bundle_binstub)
18 | if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/
19 | load(bundle_binstub)
20 | else
21 | abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
22 | Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
23 | end
24 | end
25 |
26 | require "rubygems"
27 | require "bundler/setup"
28 |
29 | load Gem.bin_path("spring", "spring")
30 |
--------------------------------------------------------------------------------
/bin/travis:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | # frozen_string_literal: true
3 |
4 | #
5 | # This file was generated by Bundler.
6 | #
7 | # The application 'travis' is installed as part of a gem, and
8 | # this file is here to facilitate running it.
9 | #
10 |
11 | require "pathname"
12 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
13 | Pathname.new(__FILE__).realpath)
14 |
15 | bundle_binstub = File.expand_path("../bundle", __FILE__)
16 |
17 | if File.file?(bundle_binstub)
18 | if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/
19 | load(bundle_binstub)
20 | else
21 | abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
22 | Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
23 | end
24 | end
25 |
26 | require "rubygems"
27 | require "bundler/setup"
28 |
29 | load Gem.bin_path("travis", "travis")
30 |
--------------------------------------------------------------------------------
/bin/annotate:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | # frozen_string_literal: true
3 |
4 | #
5 | # This file was generated by Bundler.
6 | #
7 | # The application 'annotate' is installed as part of a gem, and
8 | # this file is here to facilitate running it.
9 | #
10 |
11 | require "pathname"
12 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
13 | Pathname.new(__FILE__).realpath)
14 |
15 | bundle_binstub = File.expand_path("../bundle", __FILE__)
16 |
17 | if File.file?(bundle_binstub)
18 | if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/
19 | load(bundle_binstub)
20 | else
21 | abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
22 | Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
23 | end
24 | end
25 |
26 | require "rubygems"
27 | require "bundler/setup"
28 |
29 | load Gem.bin_path("annotate", "annotate")
30 |
--------------------------------------------------------------------------------
/bin/htmldiff:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | # frozen_string_literal: true
3 |
4 | #
5 | # This file was generated by Bundler.
6 | #
7 | # The application 'htmldiff' is installed as part of a gem, and
8 | # this file is here to facilitate running it.
9 | #
10 |
11 | require "pathname"
12 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
13 | Pathname.new(__FILE__).realpath)
14 |
15 | bundle_binstub = File.expand_path("../bundle", __FILE__)
16 |
17 | if File.file?(bundle_binstub)
18 | if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/
19 | load(bundle_binstub)
20 | else
21 | abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
22 | Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
23 | end
24 | end
25 |
26 | require "rubygems"
27 | require "bundler/setup"
28 |
29 | load Gem.bin_path("diff-lcs", "htmldiff")
30 |
--------------------------------------------------------------------------------
/bin/httparty:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | # frozen_string_literal: true
3 |
4 | #
5 | # This file was generated by Bundler.
6 | #
7 | # The application 'httparty' is installed as part of a gem, and
8 | # this file is here to facilitate running it.
9 | #
10 |
11 | require "pathname"
12 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
13 | Pathname.new(__FILE__).realpath)
14 |
15 | bundle_binstub = File.expand_path("../bundle", __FILE__)
16 |
17 | if File.file?(bundle_binstub)
18 | if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/
19 | load(bundle_binstub)
20 | else
21 | abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
22 | Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
23 | end
24 | end
25 |
26 | require "rubygems"
27 | require "bundler/setup"
28 |
29 | load Gem.bin_path("httparty", "httparty")
30 |
--------------------------------------------------------------------------------
/bin/nokogiri:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | # frozen_string_literal: true
3 |
4 | #
5 | # This file was generated by Bundler.
6 | #
7 | # The application 'nokogiri' is installed as part of a gem, and
8 | # this file is here to facilitate running it.
9 | #
10 |
11 | require "pathname"
12 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
13 | Pathname.new(__FILE__).realpath)
14 |
15 | bundle_binstub = File.expand_path("../bundle", __FILE__)
16 |
17 | if File.file?(bundle_binstub)
18 | if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/
19 | load(bundle_binstub)
20 | else
21 | abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
22 | Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
23 | end
24 | end
25 |
26 | require "rubygems"
27 | require "bundler/setup"
28 |
29 | load Gem.bin_path("nokogiri", "nokogiri")
30 |
--------------------------------------------------------------------------------
/app/javascript/api/admin.ts:
--------------------------------------------------------------------------------
1 | import api from './base';
2 | import { AxiosPromise, CancelTokenSource } from 'axios';
3 | import { User } from './user';
4 |
5 | export function users(cancelToken: CancelTokenSource): AxiosPromise {
6 | return api.get('admin/users', { cancelToken: cancelToken.token });
7 | }
8 |
9 | export function deleteUser(id: string, cancelToken: CancelTokenSource) {
10 | return api.delete(`admin/users/${id}`, { cancelToken: cancelToken.token });
11 | }
12 |
13 | export function user(id: string, cancelToken: CancelTokenSource): AxiosPromise {
14 | return api.get(`admin/users/${id}`, { cancelToken: cancelToken.token });
15 | }
16 |
17 | export function updateUser(id: string, userParams: Partial, cancelToken: CancelTokenSource) {
18 | return api.put(
19 | `admin/users/${id}`,
20 | {
21 | data: userParams
22 | },
23 | { cancelToken: cancelToken.token }
24 | );
25 | }
26 |
--------------------------------------------------------------------------------
/bin/ruby-parse:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | # frozen_string_literal: true
3 |
4 | #
5 | # This file was generated by Bundler.
6 | #
7 | # The application 'ruby-parse' is installed as part of a gem, and
8 | # this file is here to facilitate running it.
9 | #
10 |
11 | require "pathname"
12 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
13 | Pathname.new(__FILE__).realpath)
14 |
15 | bundle_binstub = File.expand_path("../bundle", __FILE__)
16 |
17 | if File.file?(bundle_binstub)
18 | if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/
19 | load(bundle_binstub)
20 | else
21 | abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
22 | Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
23 | end
24 | end
25 |
26 | require "rubygems"
27 | require "bundler/setup"
28 |
29 | load Gem.bin_path("parser", "ruby-parse")
30 |
--------------------------------------------------------------------------------
/bin/sidekiqmon:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | # frozen_string_literal: true
3 |
4 | #
5 | # This file was generated by Bundler.
6 | #
7 | # The application 'sidekiqmon' is installed as part of a gem, and
8 | # this file is here to facilitate running it.
9 | #
10 |
11 | require "pathname"
12 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
13 | Pathname.new(__FILE__).realpath)
14 |
15 | bundle_binstub = File.expand_path("../bundle", __FILE__)
16 |
17 | if File.file?(bundle_binstub)
18 | if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/
19 | load(bundle_binstub)
20 | else
21 | abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
22 | Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
23 | end
24 | end
25 |
26 | require "rubygems"
27 | require "bundler/setup"
28 |
29 | load Gem.bin_path("sidekiq", "sidekiqmon")
30 |
--------------------------------------------------------------------------------
/bin/sprockets:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | # frozen_string_literal: true
3 |
4 | #
5 | # This file was generated by Bundler.
6 | #
7 | # The application 'sprockets' is installed as part of a gem, and
8 | # this file is here to facilitate running it.
9 | #
10 |
11 | require "pathname"
12 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
13 | Pathname.new(__FILE__).realpath)
14 |
15 | bundle_binstub = File.expand_path("../bundle", __FILE__)
16 |
17 | if File.file?(bundle_binstub)
18 | if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/
19 | load(bundle_binstub)
20 | else
21 | abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
22 | Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
23 | end
24 | end
25 |
26 | require "rubygems"
27 | require "bundler/setup"
28 |
29 | load Gem.bin_path("sprockets", "sprockets")
30 |
--------------------------------------------------------------------------------
/bin/ruby-rewrite:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | # frozen_string_literal: true
3 |
4 | #
5 | # This file was generated by Bundler.
6 | #
7 | # The application 'ruby-rewrite' is installed as part of a gem, and
8 | # this file is here to facilitate running it.
9 | #
10 |
11 | require "pathname"
12 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
13 | Pathname.new(__FILE__).realpath)
14 |
15 | bundle_binstub = File.expand_path("../bundle", __FILE__)
16 |
17 | if File.file?(bundle_binstub)
18 | if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/
19 | load(bundle_binstub)
20 | else
21 | abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
22 | Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
23 | end
24 | end
25 |
26 | require "rubygems"
27 | require "bundler/setup"
28 |
29 | load Gem.bin_path("parser", "ruby-rewrite")
30 |
--------------------------------------------------------------------------------
/bin/sass-convert:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | # frozen_string_literal: true
3 |
4 | #
5 | # This file was generated by Bundler.
6 | #
7 | # The application 'sass-convert' is installed as part of a gem, and
8 | # this file is here to facilitate running it.
9 | #
10 |
11 | require "pathname"
12 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
13 | Pathname.new(__FILE__).realpath)
14 |
15 | bundle_binstub = File.expand_path("../bundle", __FILE__)
16 |
17 | if File.file?(bundle_binstub)
18 | if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/
19 | load(bundle_binstub)
20 | else
21 | abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
22 | Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
23 | end
24 | end
25 |
26 | require "rubygems"
27 | require "bundler/setup"
28 |
29 | load Gem.bin_path("sass", "sass-convert")
30 |
--------------------------------------------------------------------------------
/bin/solargraph:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | # frozen_string_literal: true
3 |
4 | #
5 | # This file was generated by Bundler.
6 | #
7 | # The application 'solargraph' is installed as part of a gem, and
8 | # this file is here to facilitate running it.
9 | #
10 |
11 | require "pathname"
12 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
13 | Pathname.new(__FILE__).realpath)
14 |
15 | bundle_binstub = File.expand_path("../bundle", __FILE__)
16 |
17 | if File.file?(bundle_binstub)
18 | if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/
19 | load(bundle_binstub)
20 | else
21 | abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
22 | Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
23 | end
24 | end
25 |
26 | require "rubygems"
27 | require "bundler/setup"
28 |
29 | load Gem.bin_path("solargraph", "solargraph")
30 |
--------------------------------------------------------------------------------
/bin/gdb_wrapper:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | # frozen_string_literal: true
3 |
4 | #
5 | # This file was generated by Bundler.
6 | #
7 | # The application 'gdb_wrapper' is installed as part of a gem, and
8 | # this file is here to facilitate running it.
9 | #
10 |
11 | require "pathname"
12 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
13 | Pathname.new(__FILE__).realpath)
14 |
15 | bundle_binstub = File.expand_path("../bundle", __FILE__)
16 |
17 | if File.file?(bundle_binstub)
18 | if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/
19 | load(bundle_binstub)
20 | else
21 | abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
22 | Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
23 | end
24 | end
25 |
26 | require "rubygems"
27 | require "bundler/setup"
28 |
29 | load Gem.bin_path("ruby-debug-ide", "gdb_wrapper")
30 |
--------------------------------------------------------------------------------
/bin/mailcatcher:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | # frozen_string_literal: true
3 |
4 | #
5 | # This file was generated by Bundler.
6 | #
7 | # The application 'mailcatcher' is installed as part of a gem, and
8 | # this file is here to facilitate running it.
9 | #
10 |
11 | require "pathname"
12 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
13 | Pathname.new(__FILE__).realpath)
14 |
15 | bundle_binstub = File.expand_path("../bundle", __FILE__)
16 |
17 | if File.file?(bundle_binstub)
18 | if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/
19 | load(bundle_binstub)
20 | else
21 | abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
22 | Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
23 | end
24 | end
25 |
26 | require "rubygems"
27 | require "bundler/setup"
28 |
29 | load Gem.bin_path("mailcatcher", "mailcatcher")
30 |
--------------------------------------------------------------------------------
/bin/rdebug-ide:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | # frozen_string_literal: true
3 |
4 | #
5 | # This file was generated by Bundler.
6 | #
7 | # The application 'rdebug-ide' is installed as part of a gem, and
8 | # this file is here to facilitate running it.
9 | #
10 |
11 | require "pathname"
12 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
13 | Pathname.new(__FILE__).realpath)
14 |
15 | bundle_binstub = File.expand_path("../bundle", __FILE__)
16 |
17 | if File.file?(bundle_binstub)
18 | if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/
19 | load(bundle_binstub)
20 | else
21 | abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
22 | Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
23 | end
24 | end
25 |
26 | require "rubygems"
27 | require "bundler/setup"
28 |
29 | load Gem.bin_path("ruby-debug-ide", "rdebug-ide")
30 |
--------------------------------------------------------------------------------
/bin/sqlite3_ruby:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | # frozen_string_literal: true
3 |
4 | #
5 | # This file was generated by Bundler.
6 | #
7 | # The application 'sqlite3_ruby' is installed as part of a gem, and
8 | # this file is here to facilitate running it.
9 | #
10 |
11 | require "pathname"
12 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
13 | Pathname.new(__FILE__).realpath)
14 |
15 | bundle_binstub = File.expand_path("../bundle", __FILE__)
16 |
17 | if File.file?(bundle_binstub)
18 | if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/
19 | load(bundle_binstub)
20 | else
21 | abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
22 | Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
23 | end
24 | end
25 |
26 | require "rubygems"
27 | require "bundler/setup"
28 |
29 | load Gem.bin_path("sqlite3-ruby", "sqlite3_ruby")
30 |
--------------------------------------------------------------------------------
/bin/update:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | require 'fileutils'
3 | include FileUtils
4 |
5 | # path to your application root.
6 | APP_ROOT = File.expand_path('..', __dir__)
7 |
8 | def system!(*args)
9 | system(*args) || abort("\n== Command #{args} failed ==")
10 | end
11 |
12 | chdir APP_ROOT do
13 | # This script is a way to update your development environment automatically.
14 | # Add necessary update steps to this file.
15 |
16 | puts '== Installing dependencies =='
17 | system! 'gem install bundler --conservative'
18 | system('bundle check') || system!('bundle install')
19 |
20 | # Install JavaScript dependencies if using Yarn
21 | # system('bin/yarn')
22 |
23 | puts "\n== Updating database =="
24 | system! 'bin/rails db:migrate'
25 |
26 | puts "\n== Removing old logs and tempfiles =="
27 | system! 'bin/rails log:clear tmp:clear'
28 |
29 | puts "\n== Restarting application server =="
30 | system! 'bin/rails restart'
31 | end
32 |
--------------------------------------------------------------------------------
/bin/reverse_markdown:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | # frozen_string_literal: true
3 |
4 | #
5 | # This file was generated by Bundler.
6 | #
7 | # The application 'reverse_markdown' is installed as part of a gem, and
8 | # this file is here to facilitate running it.
9 | #
10 |
11 | require "pathname"
12 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
13 | Pathname.new(__FILE__).realpath)
14 |
15 | bundle_binstub = File.expand_path("../bundle", __FILE__)
16 |
17 | if File.file?(bundle_binstub)
18 | if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/
19 | load(bundle_binstub)
20 | else
21 | abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
22 | Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
23 | end
24 | end
25 |
26 | require "rubygems"
27 | require "bundler/setup"
28 |
29 | load Gem.bin_path("reverse_markdown", "reverse_markdown")
30 |
--------------------------------------------------------------------------------
/swagger-ui-dist/README.md:
--------------------------------------------------------------------------------
1 | # Swagger UI Dist
2 | [](http://badge.fury.io/js/swagger-ui-dist)
3 |
4 | # API
5 |
6 | This module, `swagger-ui-dist`, exposes Swagger-UI's entire dist folder as a dependency-free npm module.
7 | Use `swagger-ui` instead, if you'd like to have npm install dependencies for you.
8 |
9 | `SwaggerUIBundle` and `SwaggerUIStandalonePreset` can be imported:
10 | ```javascript
11 | import { SwaggerUIBundle, SwaggerUIStandalonePreset } from "swagger-ui-dist"
12 | ```
13 |
14 | To get an absolute path to this directory for static file serving, use the exported `getAbsoluteFSPath` method:
15 |
16 | ```javascript
17 | const swaggerUiAssetPath = require("swagger-ui-dist").getAbsoluteFSPath()
18 |
19 | // then instantiate server that serves files from the swaggerUiAssetPath
20 | ```
21 |
22 | For anything else, check the [Swagger-UI](https://github.com/swagger-api/swagger-ui) repository.
23 |
--------------------------------------------------------------------------------
/config/locales/en.yml:
--------------------------------------------------------------------------------
1 | # Files in the config/locales directory are used for internationalization
2 | # and are automatically loaded by Rails. If you want to use locales other
3 | # than English, add the necessary files in this directory.
4 | #
5 | # To use the locales, use `I18n.t`:
6 | #
7 | # I18n.t 'hello'
8 | #
9 | # In views, this is aliased to just `t`:
10 | #
11 | # <%= t('hello') %>
12 | #
13 | # To use a different locale, set it with `I18n.locale`:
14 | #
15 | # I18n.locale = :es
16 | #
17 | # This would use the information in config/locales/es.yml.
18 | #
19 | # The following keys must be escaped otherwise they will not be retrieved by
20 | # the default I18n backend:
21 | #
22 | # true, false, on, off, yes, no
23 | #
24 | # Instead, surround them with single quotes.
25 | #
26 | # en:
27 | # 'true': 'foo'
28 | #
29 | # To learn more, please read the Rails Internationalization guide
30 | # available at https://guides.rubyonrails.org/i18n.html.
31 |
32 | en:
33 | hello: "Hello world"
34 |
--------------------------------------------------------------------------------
/app/api/resources/login.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Resources
4 | class Login < Grape::API
5 | route_setting :auth, disabled: true
6 | params do
7 | requires :email, type: String
8 | requires :password, type: String
9 | end
10 | post :login do
11 | @user = User.find_by(email: params[:email].downcase)
12 | error!('Invalid email or password', 401) unless @user
13 | error!('Activate your account', 403) if @user.activation_expired?
14 |
15 | token = @user.login(params[:password])
16 | error!('Invalid email or password', 401) unless token
17 |
18 | crypto_key = @user.crypto_key(params[:password])
19 | present(
20 | @user,
21 | with: Entities::User,
22 | token: token,
23 | crypto_key: crypto_key
24 | )
25 | end
26 |
27 | post :logout do
28 | token = request.headers['Authorization']
29 | current_user.logout(token)
30 |
31 | status :no_content
32 | end
33 | end
34 | end
35 |
--------------------------------------------------------------------------------
/lib/queries/query.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | # @param db_type [Symbol, String] one of DbServer.db_types
4 | # @return [String, nil] currently only :mysql and :psql are supported.
5 | def query_path(db_type:)
6 | case db_type.to_sym
7 | when :mysql
8 | File.join('lib', 'queries', 'mysql')
9 | when :mariadb
10 | File.join('lib', 'queries', 'mysql')
11 | when :psql
12 | File.join('lib', 'queries', 'psql')
13 | end
14 | end
15 |
16 | # @param name [String] name of the query action. When no file
17 | # with the provided name exists under lib/queries/:db_type/:name.sql
18 | # an empty string is returned.
19 | # e.g. 'databases'.
20 | # @param db_type [Symbol, String] one of DbServer.db_types
21 | # @return [String] currently only :mysql and :psql are supported.
22 | def query_for(name:, db_type:)
23 | file = File.join(
24 | query_path(db_type: db_type),
25 | "#{name}.sql"
26 | )
27 | return '' unless File.exist?(file)
28 |
29 | File.read(file)
30 | end
31 |
--------------------------------------------------------------------------------
/app/api/entities/user.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Entities
4 | class User < Grape::Entity
5 | # format timestamps in iso8601 format,
6 | # e.g. 2019-05-28T19:49:02Z means 28.05.2019 at 19:49:02 in timezone 0 (=Z)
7 | format_with(:iso_timestamp, &:iso8601)
8 |
9 | expose :id
10 | expose :email
11 | expose :token do |_, options|
12 | options[:token]
13 | end
14 | expose :login_count
15 | expose :crypto_key do |_, options|
16 | options[:crypto_key]
17 | end
18 | with_options(format_with: :iso_timestamp) do
19 | expose :updated_at
20 | expose :created_at
21 | end
22 | expose :role
23 | expose :activated do |user|
24 | user.activated?
25 | end
26 | expose :query_count do |user|
27 | user.query_count
28 | end
29 | expose :error_query_count do |user|
30 | user.error_query_count
31 | end
32 | expose :password_reset_requested do |user|
33 | user.password_reset_requested?
34 | end
35 | end
36 | end
37 |
--------------------------------------------------------------------------------
/app/api/concerns/error_handling.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module ErrorHandling
4 | def self.included(base)
5 | return if Rails.env.development?
6 |
7 | # Generate a properly formatted 404 error for all unmatched routes,
8 | # except '/'
9 | base.route :any, '*path' do
10 | error!(
11 | {
12 | error: 'Not Found',
13 | detail: "No such route '#{request.path}'",
14 | status: '404'
15 | },
16 | 404
17 | )
18 | end
19 |
20 | base.rescue_from ActiveRecord::RecordNotFound do |error|
21 | Rack::Response.new(
22 | {
23 | 'status' => 404,
24 | 'message' => error.message
25 | }.to_json, 404
26 | )
27 | end
28 |
29 | base.rescue_from ActiveRecord::RecordInvalid do |e|
30 | error_response(message: e.message, status: 422)
31 | end
32 |
33 | base.rescue_from Pundit::NotAuthorizedError do |e|
34 | error_response(message: e.message, status: 403)
35 | end
36 | end
37 | end
38 |
--------------------------------------------------------------------------------
/app/javascript/components/Workbench/DatabaseSchemaTree/SchemaItem.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Icon, List } from 'semantic-ui-react';
3 | import { observer } from 'mobx-react';
4 | import _ from 'lodash';
5 | import { Mark } from '../../../models/DbColumn';
6 | import DbSchema from '../../../models/DbSchema';
7 |
8 | interface DatabaseItemProps {
9 | schema: DbSchema;
10 | }
11 |
12 | @observer
13 | export default class SchemaItem extends React.Component {
14 | render() {
15 | const { schema } = this.props;
16 | const highlighted = schema.mark !== Mark.None;
17 |
18 | return (
19 | schema.toggleShow()}>
20 |
21 |
22 |
23 | {schema.name}
24 |
25 |
26 |
27 | );
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/spec/fixtures/database/mysql/ninja_turtles_create.sql:
--------------------------------------------------------------------------------
1 | -- clean up
2 | DROP DATABASE IF EXISTS ninja_turtles_db;
3 |
4 | -- create database and user
5 | CREATE DATABASE ninja_turtles_db;
6 |
7 | USE ninja_turtles_db;
8 |
9 | -- create tables
10 | CREATE TABLE ninja_turtles (
11 | id INT PRIMARY KEY auto_increment,
12 | name TEXT
13 | );
14 | CREATE TABLE fights (
15 | id INT PRIMARY KEY auto_increment,
16 | date DATETIME,
17 | badass_turtle_id INT,
18 | kickass_turtle_id INT,
19 | FOREIGN KEY kickass_idx (kickass_turtle_id) REFERENCES ninja_turtles(id),
20 | FOREIGN KEY badass_idx (badass_turtle_id) REFERENCES ninja_turtles(id)
21 | );
22 |
23 | -- insert some entries
24 | INSERT INTO ninja_turtles (id, name)
25 | VALUES
26 | (1, 'Ninja Reto'),
27 | (2, 'Warrior Maria'),
28 | (3, 'Mutant Holzkopf');
29 |
30 | INSERT INTO fights (date, badass_turtle_id, kickass_turtle_id)
31 | VALUES
32 | ('1969-01-18 10:15:00', 1, 2),
33 | ('1969-01-18 10:32:00', 1, 3),
34 | ('1969-01-18 10:48:00', 3, 2);
--------------------------------------------------------------------------------
/app/api/resources/og_meta.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 | # require 'nokogiri'
3 | require 'http'
4 |
5 | module Resources
6 | class OGMeta < Grape::API
7 | resource :og_meta do
8 | desc 'Get og:image from url'
9 | post do
10 | url = params[:url]
11 | _meta = Rails.cache.fetch("#{url}", expires_in: 2.hours) do
12 | p "Fetching #{url}"
13 | response = HTTP.get(url)
14 | if response.status != 200
15 | return error!('Url not found', 404)
16 | end
17 | doc = Nokogiri::HTML(response.body.to_s)
18 | meta = {}
19 | ['og:image', 'og:site_name', 'og:title', 'og:description'].each do |m|
20 | prop = doc.css("meta[property='#{m}']")
21 | if !prop.empty?
22 | meta[m[3..].to_sym] = prop.first.attributes["content"].content
23 | end
24 | end
25 | meta
26 | end
27 |
28 | present(
29 | _meta,
30 | with: Entities::OGMeta
31 | )
32 | end
33 | end
34 | end
35 | end
36 |
--------------------------------------------------------------------------------
/app/policies/db_server_policy.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | class DbServerPolicy < ApplicationPolicy
4 | def show?
5 | case record.owner_type
6 | when :user
7 | record.user_id == user.id
8 | when :group
9 | record.group.member?(user)
10 | end
11 | end
12 |
13 | def create?
14 | true
15 | end
16 |
17 | def update?
18 | case record.owner_type
19 | when :user
20 | record.user_id == user.id
21 | when :group
22 | record.group.admin?(user)
23 | end
24 | end
25 |
26 | def destroy?
27 | case record.owner_type
28 | when :user
29 | record.user_id == user.id
30 | when :group
31 | record.group.admin?(user)
32 | end
33 | end
34 |
35 | def index?
36 | true
37 | end
38 |
39 | class Scope < Scope
40 |
41 | # @return [ActiveRecord::Relation]
42 | def resolve
43 | scope.left_joins(:group)
44 | .joins('LEFT JOIN group_members ON groups.id = group_members.group_id')
45 | .where('db_servers.user_id = :user OR group_members.user_id = :user', user: user.id)
46 | end
47 | end
48 | end
49 |
--------------------------------------------------------------------------------
/app/views/layouts/mailer.html.erb:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
44 |
45 |
46 |
47 | <%= yield %>
48 |
49 |
50 |
--------------------------------------------------------------------------------
/spec/fixtures/database/psql/ninja_turtles_create.sql:
--------------------------------------------------------------------------------
1 | -- clean up
2 | DROP INDEX IF EXISTS badass_idx;
3 | DROP DATABASE IF EXISTS ninja_turtles_db;
4 |
5 | -- create database and user
6 | CREATE DATABASE ninja_turtles_db;
7 |
8 | -- connect to new db
9 | \c ninja_turtles_db;
10 |
11 |
12 | -- create tables
13 | CREATE TABLE ninja_turtles (
14 | id SERIAL PRIMARY KEY,
15 | name TEXT
16 | );
17 | CREATE TABLE fights (
18 | id SERIAL PRIMARY KEY,
19 | date TIMESTAMP,
20 | badass_turtle_id INTEGER REFERENCES ninja_turtles(id),
21 | kickass_turtle_id INTEGER REFERENCES ninja_turtles(id)
22 | );
23 | CREATE INDEX kickass_idx ON fights (kickass_turtle_id);
24 | CREATE INDEX badass_idx ON fights (badass_turtle_id);
25 |
26 | -- insert some entries
27 | INSERT INTO ninja_turtles (id, name)
28 | VALUES
29 | (1, 'Ninja Reto'),
30 | (2, 'Warrior Maria'),
31 | (3, 'Mutant Holzkopf');
32 |
33 | INSERT INTO fights (date, badass_turtle_id, kickass_turtle_id)
34 | VALUES
35 | ('1969-01-18 10:15:00', 1, 2),
36 | ('1969-01-18 10:32:00', 1, 3),
37 | ('1969-01-18 10:48:00', 3, 2);
38 |
39 |
--------------------------------------------------------------------------------
/db/migrate/20200613154940_create_groups.rb:
--------------------------------------------------------------------------------
1 | class CreateGroups < ActiveRecord::Migration[6.0]
2 | def change
3 | create_table :groups, id: :uuid do |t|
4 | t.string :name, null: false
5 | t.string :description, default: ''
6 | t.string :public_crypto_key
7 | t.boolean :is_private, default: true, null: false
8 | t.timestamps
9 | end
10 |
11 | # join table
12 | create_table :group_members, primary_key: %i[user_id group_id] do |t|
13 | t.references :user, type: :uuid, null: false, index: true
14 | t.references :group, type: :uuid, null: false, index: true
15 |
16 | # crypto key for the db password, encrypted with the users public key
17 | t.string :crypto_key_encrypted
18 | t.boolean :is_admin, default: false, null: false
19 | t.boolean :is_outdated, default: false, null: false
20 | t.timestamps
21 | end
22 | add_foreign_key :group_members, :users, column: :user_id
23 | add_foreign_key :group_members, :groups, column: :group_id
24 |
25 | add_reference :db_servers, :group, type: :uuid, index: true
26 | add_foreign_key :db_servers, :groups
27 | end
28 | end
29 |
--------------------------------------------------------------------------------
/app/javascript/components/Workbench/DatabaseSchemaTree/TableItem.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Icon, List } from 'semantic-ui-react';
3 | import { observer } from 'mobx-react';
4 | import _ from 'lodash';
5 | import DbTable from '../../../models/DbTable';
6 | import { Mark } from '../../../models/DbColumn';
7 |
8 | interface DatabaseItemProps {
9 | table: DbTable;
10 | indentLevel: 1 | 2;
11 | }
12 |
13 | @observer
14 | export default class TableItem extends React.Component {
15 | render() {
16 | const { table, indentLevel } = this.props;
17 | const highlighted = table.mark !== Mark.None;
18 |
19 | return (
20 | table.toggleShow()}
25 | >
26 |
27 |
28 |
29 | {table.name}
30 |
31 |
32 |
33 | );
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/app/javascript/components/SqlQueries/SqlQueryErrors.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { inject, observer } from 'mobx-react';
3 | import { computed } from 'mobx';
4 | import SqlQuery from '../../models/SqlQuery';
5 | import ViewStateStore from '../../stores/view_state_store';
6 | import { Message } from 'semantic-ui-react';
7 |
8 | interface Props {
9 | sqlQuery: SqlQuery;
10 | }
11 |
12 | interface InjectedProps extends Props {
13 | viewStateStore: ViewStateStore;
14 | }
15 |
16 | @inject('viewStateStore')
17 | @observer
18 | export default class SqlQueryErrors extends React.Component {
19 | get injected() {
20 | return this.props as InjectedProps;
21 | }
22 |
23 | @computed
24 | get sqlQuery() {
25 | return this.props.sqlQuery;
26 | }
27 |
28 | render() {
29 | return (
30 |
31 | {this.sqlQuery.errors.map((err) => {
32 | return (
33 |
34 | {`Error in the ${err.query_idx + 1}. query`}
35 | {err.error}
36 |
37 | );
38 | })}
39 |
40 | );
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/app/javascript/antlr/QuerySeparationGrammarVisitor.ts:
--------------------------------------------------------------------------------
1 | // Generated from app/javascript/antlr/QuerySeparationGrammar.g4 by ANTLR 4.7.3-SNAPSHOT
2 |
3 |
4 | import { ParseTreeVisitor } from "antlr4ts/tree/ParseTreeVisitor";
5 |
6 | import { QueriesTextContext } from "./QuerySeparationGrammarParser";
7 | import { StatementContext } from "./QuerySeparationGrammarParser";
8 |
9 |
10 | /**
11 | * This interface defines a complete generic visitor for a parse tree produced
12 | * by `QuerySeparationGrammarParser`.
13 | *
14 | * @param The return type of the visit operation. Use `void` for
15 | * operations with no return type.
16 | */
17 | export interface QuerySeparationGrammarVisitor extends ParseTreeVisitor {
18 | /**
19 | * Visit a parse tree produced by `QuerySeparationGrammarParser.queriesText`.
20 | * @param ctx the parse tree
21 | * @return the visitor result
22 | */
23 | visitQueriesText?: (ctx: QueriesTextContext) => Result;
24 |
25 | /**
26 | * Visit a parse tree produced by `QuerySeparationGrammarParser.statement`.
27 | * @param ctx the parse tree
28 | * @return the visitor result
29 | */
30 | visitStatement?: (ctx: StatementContext) => Result;
31 | }
32 |
33 |
--------------------------------------------------------------------------------
/bin/setup:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | require 'fileutils'
3 |
4 | # path to your application root.
5 | APP_ROOT = File.expand_path('..', __dir__)
6 |
7 | def system!(*args)
8 | system(*args) || abort("\n== Command #{args} failed ==")
9 | end
10 |
11 | FileUtils.chdir APP_ROOT do
12 | # This script is a way to setup or update your development environment automatically.
13 | # This script is idempotent, so that you can run it at anytime and get an expectable outcome.
14 | # Add necessary setup steps to this file.
15 |
16 | puts '== Installing dependencies =='
17 | system! 'gem install bundler --conservative'
18 | system('bundle check') || system!('bundle install')
19 |
20 | # Install JavaScript dependencies
21 | # system('bin/yarn')
22 |
23 | # puts "\n== Copying sample files =="
24 | # unless File.exist?('config/database.yml')
25 | # FileUtils.cp 'config/database.yml.sample', 'config/database.yml'
26 | # end
27 |
28 | puts "\n== Preparing database =="
29 | system! 'bin/rails db:prepare'
30 |
31 | puts "\n== Removing old logs and tempfiles =="
32 | system! 'bin/rails log:clear tmp:clear'
33 |
34 | puts "\n== Restarting application server =="
35 | system! 'bin/rails restart'
36 | end
37 |
--------------------------------------------------------------------------------
/app/api/entities/full_database.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Entities
4 | class DbConstraints < Grape::Entity
5 | expose :name
6 | expose :database
7 | expose :schema
8 | expose :table
9 | expose :column
10 | end
11 |
12 | class SqlMetadata < Grape::Entity
13 | expose :type
14 | expose :limit
15 | expose :precision
16 | expose :scale
17 | expose :sql_type
18 | end
19 |
20 | class DbColumn < Grape::Entity
21 | expose :name
22 | expose :position
23 | expose :null
24 | expose :is_primary
25 | expose :is_foreign
26 | expose :default
27 | expose :sql_type_metadata, using: Entities::SqlMetadata
28 | expose :constraints, using: Entities::DbConstraints
29 | end
30 |
31 |
32 | class DbTable < Grape::Entity
33 | expose :name
34 | expose :columns, using: Entities::DbColumn
35 | end
36 |
37 | class DbSchema < Grape::Entity
38 | expose :name
39 | expose :tables, using: Entities::DbTable
40 | end
41 |
42 | class FullDatabase < Grape::Entity
43 | with_options(expose_nil: false) do
44 | expose :name
45 | expose :db_server_id
46 | expose :schemas, using: Entities::DbSchema
47 | end
48 | end
49 | end
50 |
--------------------------------------------------------------------------------
/config/storage.yml:
--------------------------------------------------------------------------------
1 | test:
2 | service: Disk
3 | root: <%= Rails.root.join("tmp/storage") %>
4 |
5 | local:
6 | service: Disk
7 | root: <%= Rails.root.join("storage") %>
8 |
9 | # Use rails credentials:edit to set the AWS secrets (as aws:access_key_id|secret_access_key)
10 | # amazon:
11 | # service: S3
12 | # access_key_id: <%= Rails.application.credentials.dig(:aws, :access_key_id) %>
13 | # secret_access_key: <%= Rails.application.credentials.dig(:aws, :secret_access_key) %>
14 | # region: us-east-1
15 | # bucket: your_own_bucket
16 |
17 | # Remember not to checkin your GCS keyfile to a repository
18 | # google:
19 | # service: GCS
20 | # project: your_project
21 | # credentials: <%= Rails.root.join("path/to/gcs.keyfile") %>
22 | # bucket: your_own_bucket
23 |
24 | # Use rails credentials:edit to set the Azure Storage secret (as azure_storage:storage_access_key)
25 | # microsoft:
26 | # service: AzureStorage
27 | # storage_account_name: your_account_name
28 | # storage_access_key: <%= Rails.application.credentials.dig(:azure_storage, :storage_access_key) %>
29 | # container: your_container_name
30 |
31 | # mirror:
32 | # service: Mirror
33 | # primary: local
34 | # mirrors: [ amazon, google, microsoft ]
35 |
--------------------------------------------------------------------------------
/db/migrate/20200713091435_create_active_storage_tables.active_storage.rb:
--------------------------------------------------------------------------------
1 | # This migration comes from active_storage (originally 20170806125915)
2 | class CreateActiveStorageTables < ActiveRecord::Migration[5.2]
3 | def change
4 | create_table :active_storage_blobs do |t|
5 | t.string :key, null: false
6 | t.string :filename, null: false
7 | t.string :content_type
8 | t.text :metadata
9 | t.bigint :byte_size, null: false
10 | t.string :checksum, null: false
11 | t.datetime :created_at, null: false
12 |
13 | t.index [ :key ], unique: true
14 | end
15 |
16 | create_table :active_storage_attachments do |t|
17 | t.string :name, null: false
18 | t.references :record, null: false, polymorphic: true, index: false
19 | t.references :blob, null: false
20 |
21 | t.datetime :created_at, null: false
22 |
23 | t.index [ :record_type, :record_id, :name, :blob_id ], name: "index_active_storage_attachments_uniqueness", unique: true
24 | t.foreign_key :active_storage_blobs, column: :blob_id
25 | end
26 |
27 | change_column :active_storage_attachments, :record_id, 'UUID USING (uuid_generate_v4())'
28 |
29 | end
30 | end
31 |
--------------------------------------------------------------------------------
/tslint.json:
--------------------------------------------------------------------------------
1 | {
2 | "defaultSeverity": "warn",
3 | "extends": [
4 | "tslint-config-airbnb",
5 | "tslint-react"
6 | ],
7 | "jsRules": {},
8 | "rules": {
9 | "ter-indent": false,
10 | "indent": false,
11 | "semicolon": [
12 | true,
13 | "always",
14 | "ignore-bound-class-methods"
15 | ],
16 | "trailing-comma": false,
17 | "import-name": false,
18 | "eofline": false,
19 | "max-line-length": [
20 | true,
21 | 110
22 | ],
23 | "object-literal-key-quotes": false,
24 | "object-literal-shorthand": false,
25 | "variable-name": [
26 | true,
27 | "allow-pascal-case",
28 | "allow-snake-case",
29 | "allow-leading-underscore"
30 | ],
31 | "jsx-no-multiline-js": false,
32 | "jsx-boolean-value": [
33 | true,
34 | "never"
35 | ],
36 | "jsx-wrap-multiline": false,
37 | "function-name": [
38 | true,
39 | {
40 | "function-regex": "^[\\w\\d]+$",
41 | "static-method-regex": "^[a-zA-Z_\\d]+$"
42 | }
43 | ],
44 | "ter-arrow-parens": [true, "always"],
45 | "jsx-no-lambda": false,
46 | "no-this-assignment": false,
47 | "align": false
48 | },
49 | "rulesDirectory": []
50 | }
--------------------------------------------------------------------------------
/app/assets/stylesheets/sql_results.scss:
--------------------------------------------------------------------------------
1 | @import "colors";
2 |
3 | .results-header {
4 | position: sticky;
5 | top: 0px;
6 | z-index: 999;
7 | background: rgba(255, 255, 255, 0.8) !important;
8 | }
9 |
10 | .sql-results.accordion {
11 | & > div.title {
12 | position: sticky;
13 | top: 46px;
14 | z-index: 999;
15 | background: rgba(255, 255, 255, 0.8) !important;
16 | display: flex;
17 | flex-direction: row;
18 | align-items: center;
19 | }
20 | }
21 |
22 | .sql-result-table {
23 | position: relative;
24 | max-height: 72vh;
25 | overflow-y: auto;
26 | table {
27 | thead th {
28 | position: sticky;
29 | top: 0;
30 | }
31 | }
32 | .selectable-column {
33 | cursor: pointer !important;
34 | &:hover {
35 | background: #d6d6d6;
36 | }
37 | &.selected {
38 | background: var(--highlight-grey-100);
39 | &.green {
40 | background: var(--highlight-green-100);
41 | }
42 | &.blue {
43 | background: var(--highlight-blue-100);
44 | }
45 | }
46 | }
47 | td.selected {
48 | background: var(--highlight-grey-50);
49 | &.green {
50 | background: var(--highlight-green-50);
51 | }
52 | &.blue {
53 | background: var(--highlight-blue-50);
54 | }
55 | }
56 | }
--------------------------------------------------------------------------------
/app/javascript/shared/Tooltip.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Popup, { StrictPopupProps } from 'semantic-ui-react/dist/commonjs/modules/Popup/Popup';
3 | import { SemanticShorthandItem } from 'semantic-ui-react/dist/commonjs/generic';
4 | import { PopupContentProps } from 'semantic-ui-react';
5 |
6 | export type PopupPosition =
7 | | 'top left'
8 | | 'top right'
9 | | 'bottom right'
10 | | 'bottom left'
11 | | 'right center'
12 | | 'left center'
13 | | 'top center'
14 | | 'bottom center'
15 | | undefined;
16 |
17 | interface Props {
18 | content: SemanticShorthandItem;
19 | position?: PopupPosition;
20 | disabled?: boolean;
21 | delayed?: boolean;
22 | popupProps?: StrictPopupProps;
23 | }
24 | const DISPLAY_DELAY = 400;
25 |
26 | class Tooltip extends React.Component {
27 | render() {
28 | if (this.props.disabled) {
29 | return this.props.children;
30 | }
31 |
32 | return (
33 |
41 | );
42 | }
43 | }
44 |
45 | export default Tooltip;
46 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/graph_word_cloud.scss:
--------------------------------------------------------------------------------
1 | @import 'colors';
2 | .wordcloud-config {
3 | display: flex;
4 | flex-direction: row;
5 | justify-content: start;
6 | align-items: flex-start;
7 | flex-wrap: wrap;
8 |
9 | .input {
10 | margin-right: 1em;
11 | .label {
12 | cursor: pointer;
13 | }
14 | }
15 | .word-column {
16 | input {
17 | background: var(--highlight-green-100) !important;
18 | color: black !important;
19 | }
20 | }
21 | .count-column {
22 | input {
23 | background: var(--highlight-blue-100) !important;
24 | color: black !important;
25 | }
26 | }
27 | .numeric {
28 | width: 6em;
29 | margin-right: 4.5em;
30 | }
31 | .range-slider {
32 | flex-shrink: 1;
33 | flex-grow: 1;
34 | max-width: 16em;
35 | flex-basis: 8em;
36 | margin-left: 1em;
37 | margin-right: 2em;
38 | display: grid;
39 | grid-template-areas:
40 | 'min spacer max'
41 | 'slider slider slider';
42 | grid-template-columns: min-content 1fr min-content;
43 | grid-template-rows: repeat(2, min-content);
44 | #min-font-size {
45 | grid-area: min;
46 | }
47 | #max-font-size {
48 | grid-area: max;
49 | }
50 | .font-size-slider {
51 | grid-area: slider;
52 | }
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/app/javascript/antlr/QuerySeparationGrammarListener.ts:
--------------------------------------------------------------------------------
1 | // Generated from app/javascript/antlr/QuerySeparationGrammar.g4 by ANTLR 4.7.3-SNAPSHOT
2 |
3 |
4 | import { ParseTreeListener } from "antlr4ts/tree/ParseTreeListener";
5 |
6 | import { QueriesTextContext } from "./QuerySeparationGrammarParser";
7 | import { StatementContext } from "./QuerySeparationGrammarParser";
8 |
9 |
10 | /**
11 | * This interface defines a complete listener for a parse tree produced by
12 | * `QuerySeparationGrammarParser`.
13 | */
14 | export interface QuerySeparationGrammarListener extends ParseTreeListener {
15 | /**
16 | * Enter a parse tree produced by `QuerySeparationGrammarParser.queriesText`.
17 | * @param ctx the parse tree
18 | */
19 | enterQueriesText?: (ctx: QueriesTextContext) => void;
20 | /**
21 | * Exit a parse tree produced by `QuerySeparationGrammarParser.queriesText`.
22 | * @param ctx the parse tree
23 | */
24 | exitQueriesText?: (ctx: QueriesTextContext) => void;
25 |
26 | /**
27 | * Enter a parse tree produced by `QuerySeparationGrammarParser.statement`.
28 | * @param ctx the parse tree
29 | */
30 | enterStatement?: (ctx: StatementContext) => void;
31 | /**
32 | * Exit a parse tree produced by `QuerySeparationGrammarParser.statement`.
33 | * @param ctx the parse tree
34 | */
35 | exitStatement?: (ctx: StatementContext) => void;
36 | }
37 |
38 |
--------------------------------------------------------------------------------
/app/javascript/components/Workbench/SqlResult/PrismCode.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Prism from 'prismjs';
3 | import 'prismjs/themes/prism.css';
4 | import 'prismjs/components/prism-sql.min.js';
5 | import 'prismjs/components/prism-json.min.js';
6 | import 'prismjs/components/prism-markdown.min.js';
7 |
8 | import 'prismjs/plugins/line-numbers/prism-line-numbers.min.js';
9 | import 'prismjs/plugins/line-numbers/prism-line-numbers.css';
10 |
11 | interface Props {
12 | code: string;
13 | plugins?: string[];
14 | language: 'sql' | 'json' | 'markdown' | 'any';
15 | style?: React.CSSProperties;
16 | trim?: boolean;
17 | }
18 |
19 | export class PrismCode extends React.Component {
20 | ref = React.createRef();
21 |
22 | componentDidMount() {
23 | this.highlight();
24 | }
25 | componentDidUpdate() {
26 | this.highlight();
27 | }
28 |
29 | highlight = () => {
30 | if (this.ref && this.ref.current) {
31 | Prism.highlightElement(this.ref.current);
32 | }
33 | };
34 | render() {
35 | const { code, plugins, language } = this.props;
36 | return (
37 |
38 |
39 | {this.props.trim ? code.trim() : code}
40 |
41 |
42 | );
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/config/database.yml:
--------------------------------------------------------------------------------
1 | # SQLite version 3.x
2 | # gem install sqlite3
3 | #
4 | # Ensure the SQLite 3 gem is defined in your Gemfile
5 | # gem 'sqlite3'
6 | #
7 | default: &default
8 | adapter: postgresql
9 | encoding: unicode
10 | pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
11 | username: <%= ENV['DB_SQL_DATABASE_USER'] || (Rails.application.credentials[Rails.env.to_sym] || {})[:DB_SQL_DATABASE_USER] || 'db_sql' %>
12 | password: <%= ENV['DB_SQL_DATABASE_PASSWORD'] || (Rails.application.credentials[Rails.env.to_sym] || {})[:DB_SQL_DATABASE_PASSWORD] || 'db_sql' %>
13 | host: <%= ENV.fetch("DATABASE_URL") { 'localhost' } %>
14 |
15 | development:
16 | <<: *default
17 | database: db_sql_development
18 |
19 | # Warning: The database defined as "test" will be erased and
20 | # re-generated from your development database when you run "rake".
21 | # Do not set this db to the same as development or production.
22 | test:
23 | <<: *default
24 | database: db_sql_test
25 |
26 | production:
27 | adapter: postgresql
28 | encoding: unicode
29 | pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
30 | host: <%= ENV.fetch("DATABASE_URL") { 'localhost' } %>
31 | username: <%= (Rails.application.credentials[:production] || {})[:DB_SQL_DATABASE_USER] || 'db_sql_production' %>
32 | password: <%= (Rails.application.credentials[:production] || {})[:DB_SQL_DATABASE_PASSWORD] || 'db_sql_production' %>
33 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files for more about ignoring files.
2 | #
3 | # If you find yourself ignoring temporary files generated by your text editor
4 | # or operating system, you probably want to add a global ignore instead:
5 | # git config --global core.excludesfile '~/.gitignore_global'
6 |
7 | # Ignore bundler config.
8 | /.bundle
9 |
10 | # Ignore the default SQLite database.
11 | /db/*.sqlite3
12 | /db/*.sqlite3-journal
13 |
14 | # Ignore all logfiles and tempfiles.
15 | /log/*
16 | /tmp/*
17 | !/log/.keep
18 | !/tmp/.keep
19 | !/uplads/.keep
20 | *.log
21 |
22 | # Ignore uploaded files in development
23 | /storage/*
24 | !/storage/.keep
25 |
26 | /node_modules
27 | /yarn-error.log
28 |
29 | /public/assets
30 | .byebug_history
31 | .env
32 | *.operatingnode_modules/
33 | package-lock.json
34 |
35 | .ruby-gemset
36 | *.dump
37 | .yardoc
38 | doc/
39 | .rakeTasks
40 | coverage
41 | /public/packs
42 | /public/assets
43 | /public/packs-test
44 | yarn-debug.log*
45 | .yarn-integrity
46 |
47 | # Ignore master key for decrypting credentials and more.
48 | /config/master.key
49 |
50 | # Ignore vs code settings
51 | .vscode
52 | .solargraph.yml
53 |
54 | /public/packs
55 | /public/packs-test
56 | /node_modules
57 | /yarn-error.log
58 | yarn-debug.log*
59 | .yarn-integrity
60 | .browserslistrc
61 |
62 | db_servers.yaml
63 | db_connections.yaml
64 |
65 | .antlr
66 | *.interp
67 | *.tokens
--------------------------------------------------------------------------------
/app/javascript/api/sql_query.ts:
--------------------------------------------------------------------------------
1 | import api from './base';
2 | import { AxiosPromise, CancelTokenSource } from 'axios';
3 | import { DbServer } from './db_server';
4 |
5 | export enum Changeable {
6 | IsPrivate = 'is_private',
7 | IsFavorite = 'is_favorite'
8 | }
9 |
10 | export interface ChangeableProps {
11 | [Changeable.IsPrivate]: boolean;
12 | [Changeable.IsFavorite]: boolean;
13 | }
14 |
15 | export interface SqlError {
16 | query_idx: number;
17 | error: string;
18 | }
19 | export interface SqlQuery extends ChangeableProps {
20 | id: string;
21 | user_id: string;
22 | db_server_id: string;
23 | db_name: string;
24 | query: string;
25 | is_valid: boolean;
26 | exec_time?: number;
27 | error: SqlError[];
28 | created_at: string;
29 | updated_at: string;
30 | }
31 |
32 | export function getSqlQueries(cancelToken: CancelTokenSource): AxiosPromise {
33 | return api.get('sql_queries', { cancelToken: cancelToken.token });
34 | }
35 |
36 | export function getSqlQuery(id: string): AxiosPromise {
37 | return api.get(`sql_queries/${id}`);
38 | }
39 |
40 | export function getShared(groupId: string): AxiosPromise {
41 | return api.get(`sql_queries/shared?group_id=${groupId}`);
42 | }
43 |
44 | export function update(id: string, data: ChangeableProps): AxiosPromise {
45 | return api.put(`sql_queries/${id}`, {
46 | data: data
47 | });
48 | }
49 |
--------------------------------------------------------------------------------
/lib/queries/mysql/database_schema.sql:
--------------------------------------------------------------------------------
1 | SELECT
2 | cols.ORDINAL_POSITION as 'position',
3 | cols.TABLE_SCHEMA AS 'schema',
4 | cols.TABLE_NAME as 'table',
5 | cols.COLUMN_NAME as 'column',
6 | cols.COLUMN_TYPE AS 'sql_type',
7 | cols.DATA_TYPE AS 'type',
8 | cols.CHARACTER_MAXIMUM_LENGTH AS 'limit',
9 | cols.NUMERIC_PRECISION AS 'precision',
10 | cols.NUMERIC_SCALE AS 'scale',
11 | NULLIF(COALESCE(cols.COLUMN_DEFAULT, cols.extra), '') AS 'default',
12 | cols.IS_NULLABLE AS 'is_nullable',
13 | (
14 | CASE WHEN COLUMN_KEY='PRI'
15 | THEN 'YES'
16 | ELSE 'NO'
17 | END
18 | ) AS 'is_primary',
19 | (
20 | CASE WHEN COLUMN_KEY='MUL'
21 | THEN 'YES'
22 | ELSE 'NO'
23 | END
24 | ) AS 'is_foreign',
25 | constraints.CONSTRAINT_NAME AS 'constraint',
26 | constraints.REFERENCED_TABLE_SCHEMA AS 'referenced_database',
27 | constraints.REFERENCED_TABLE_SCHEMA AS 'referenced_schema',
28 | constraints.REFERENCED_TABLE_NAME AS 'referenced_table',
29 | constraints.REFERENCED_COLUMN_NAME AS 'referenced_column'
30 | FROM INFORMATION_SCHEMA.COLUMNS cols
31 | LEFT JOIN INFORMATION_SCHEMA.KEY_COLUMN_USAGE constraints
32 | ON cols.TABLE_SCHEMA = constraints.TABLE_SCHEMA
33 | AND cols.TABLE_NAME = constraints.TABLE_NAME
34 | AND cols.COLUMN_NAME = constraints.COLUMN_NAME
35 | WHERE
36 | cols.TABLE_SCHEMA=DATABASE()
37 | ORDER BY cols.TABLE_SCHEMA, cols.TABLE_NAME, cols.COLUMN_NAME;
--------------------------------------------------------------------------------
/docker-images/DB-SQL-Final/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM lebalz/ubuntu-ruby:latest
2 | LABEL maintainer="lebalz@outlook.com"
3 |
4 | # build with
5 | # cd docker-images/DB-SQL-Final
6 | # docker build . -t lebalz/rails-full-final:latest
7 | # docker push lebalz/rails-full-final:latest
8 |
9 | # For tzdata
10 | ENV DEBIAN_FRONTEND=noninteractive
11 | ENV TZ=Europe/Zurich
12 |
13 | RUN echo "# Essentials" && \
14 | apt-get update && \
15 | apt-get install -y curl \
16 | postgresql-client \
17 | mysql-client \
18 | openssl \
19 | libffi-dev \
20 | libyaml-dev \
21 | tzdata \
22 | bash \
23 | build-essential \
24 | cmake \
25 | file
26 |
27 | # Configure Rails
28 | ENV RAILS_LOG_TO_STDOUT=true
29 | ENV RAILS_SERVE_STATIC_FILES=true
30 | ENV BUNDLE_PATH=/usr/local/bundle/gems/
31 |
32 | WORKDIR /app
33 |
34 | # Expose Puma port
35 | EXPOSE 3000
36 |
37 | # Write GIT commit SHA and TIME to env vars
38 | ONBUILD ARG COMMIT_SHA
39 | ONBUILD ARG COMMIT_TIME
40 |
41 | ONBUILD ENV COMMIT_SHA=${COMMIT_SHA}
42 | ONBUILD ENV COMMIT_TIME=${COMMIT_TIME}
43 |
44 | # Add user
45 | # ONBUILD RUN addgroup --system --gid 1000 app && \
46 | # adduser --system --uid 1000 app --ingroup app
47 |
48 | # Copy app with gems from former build stage
49 | ONBUILD COPY --from=Builder /usr/local/bundle/ /usr/local/bundle/
50 | ONBUILD COPY --from=Builder --chown=app:app /app /app
51 |
--------------------------------------------------------------------------------
/app/javascript/components/Profile/SchemaQueries/LoadMoreCard.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Card, Icon } from 'semantic-ui-react';
3 | import { inject, observer } from 'mobx-react';
4 | import SchemaQueryStore from '../../../stores/schema_query_store';
5 | import { computed } from 'mobx';
6 | import { REST } from '../../../declarations/REST';
7 |
8 | interface Props {}
9 |
10 | interface InjectedProps extends Props {
11 | schemaQueryStore: SchemaQueryStore;
12 | }
13 |
14 | @inject('schemaQueryStore')
15 | @observer
16 | export default class LoadMoreCard extends React.Component {
17 | get injected() {
18 | return this.props as InjectedProps;
19 | }
20 |
21 | @computed
22 | get dbType() {
23 | return this.injected.schemaQueryStore.selectedDbType;
24 | }
25 |
26 | render() {
27 | const schemaQueryState = this.injected.schemaQueryStore.fetchRequestState[this.dbType];
28 | const isLoading = schemaQueryState.state === REST.Requested;
29 | return (
30 | this.injected.schemaQueryStore.loadNextBatch(this.dbType)}
32 | className="schema-query-card"
33 | >
34 |
35 |
36 |
37 |
38 | Load more Queries
39 |
40 |
41 | );
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/app/javascript/api/temp_db_server.ts:
--------------------------------------------------------------------------------
1 | import api from './base';
2 | import { AxiosPromise, CancelTokenSource } from 'axios';
3 | import { DbType } from '../models/DbServer';
4 | import { OwnerType } from './db_server';
5 |
6 | export interface DbServer {
7 | name: string;
8 | db_type: DbType;
9 | owner_type: OwnerType,
10 | owner_id: string,
11 | host: string;
12 | port: number;
13 | username: string;
14 | initial_db?: string;
15 | initial_table?: string;
16 | password: string;
17 | database_schema_query_id?: string;
18 | }
19 |
20 | export interface DatabaseName {
21 | name: string;
22 | }
23 |
24 | export interface DbTableName {
25 | name: string;
26 | }
27 |
28 | export interface TestResult {
29 | success: boolean;
30 | message?: string;
31 | }
32 |
33 | export function databases(dbServer: DbServer, cancelToken: CancelTokenSource): AxiosPromise {
34 | return api.post('/temp_db_server/databases', dbServer, {
35 | cancelToken: cancelToken.token
36 | });
37 | }
38 |
39 | export function tables(
40 | dbServer: DbServer,
41 | databaseName: string,
42 | cancelToken: CancelTokenSource
43 | ): AxiosPromise {
44 | return api.post(`/temp_db_server/${databaseName}/tables`, dbServer, {
45 | cancelToken: cancelToken.token
46 | });
47 | }
48 |
49 | export function test(dbServer: DbServer, cancelToken: CancelTokenSource): AxiosPromise {
50 | return api.post('/temp_db_server/test', dbServer, {
51 | cancelToken: cancelToken.token
52 | });
53 | }
54 |
--------------------------------------------------------------------------------
/app/javascript/models/DbSchema.ts:
--------------------------------------------------------------------------------
1 | import { observable, computed, action } from 'mobx';
2 | import { DbTable as DbTableProps, Index, Schema } from '../api/db_server';
3 | import _ from 'lodash';
4 | import Database from './Database';
5 | import DbColumn, { Mark } from './DbColumn';
6 | import { REST } from '../declarations/REST';
7 | import DbTable from './DbTable';
8 |
9 | interface RequestState {
10 | columns: REST;
11 | foreignKeys: REST;
12 | indexes: REST;
13 | }
14 | export default class DbSchema {
15 | readonly database: Database;
16 | readonly name: string;
17 | readonly tables: DbTable[];
18 | @observable show: boolean = false;
19 |
20 | constructor(database: Database, schema: Schema) {
21 | this.database = database;
22 | this.name = schema.name;
23 | this.tables = schema.tables.map((table) => new DbTable(this, table));
24 | }
25 |
26 | @action
27 | toggleShow() {
28 | this.show = !this.show;
29 | }
30 |
31 | @computed get mark(): Mark {
32 | const table = this.tables.find((t) => t.mark === Mark.To || t.mark === Mark.From);
33 | return table?.mark ?? Mark.None;
34 | }
35 |
36 |
37 | find(tableName: string, columnName: string): DbColumn
38 | find(tableName: string, columnName?: string): DbTable
39 | find(tableName: string, columnName?: string): DbTable | DbColumn | undefined {
40 | const dbTable = this.tables.find((t) => t.name === tableName);
41 | if (!columnName) {
42 | return dbTable;
43 | }
44 |
45 | return dbTable?.find(columnName);
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/app/policies/group_policy.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | class GroupPolicy < ApplicationPolicy
4 | def show?
5 | return true if record.public?
6 |
7 | record.member? user
8 | end
9 |
10 | def create?
11 | true
12 | end
13 |
14 | def join?
15 | record.public?
16 | end
17 |
18 | def leave?
19 | record.public?
20 | end
21 |
22 | def update?
23 | record.admin?(user)
24 | end
25 |
26 | def destroy?
27 | record.admin?(user)
28 | end
29 |
30 | def add_member?
31 | return true if record.public?
32 |
33 | record.admin?(user)
34 | end
35 |
36 | def add_db_server?
37 | record.admin?(user)
38 | end
39 |
40 | def leave?
41 | return true if record.public?
42 | return false unless record.admin? user
43 |
44 | return true if record.admins.size > 1
45 | return true if record.members.size == 1
46 | false
47 | end
48 |
49 | def remove_member?
50 | record.admin?(user)
51 | end
52 |
53 | def recrypt?
54 | record.admin?(user)
55 | end
56 |
57 | def change_member_permission?
58 | record.admin?(user)
59 | end
60 |
61 | def index?
62 | true
63 | end
64 |
65 | class Scope < Scope
66 |
67 | # @return [ActiveRecord::Relation]
68 | def resolve
69 | scope.all if user.admin?
70 |
71 | my_groups = GroupMember.select(:group_id).where(user: user).to_sql
72 |
73 | scope.where(
74 | "not groups.is_private or groups.id in (#{my_groups})"
75 | )
76 | end
77 | end
78 | end
79 |
--------------------------------------------------------------------------------
/config/initializers/content_security_policy.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # Define an application-wide content security policy
4 | # For further information see the following documentation
5 | # https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy
6 |
7 | # Rails.application.config.content_security_policy do |policy|
8 | # policy.default_src :self, :https
9 | # policy.font_src :self, :https, :data
10 | # policy.img_src :self, :https, :data
11 | # policy.object_src :none
12 | # policy.script_src :self, :https
13 | # policy.style_src :self, :https
14 | # # If you are using webpack-dev-server then specify webpack-dev-server host
15 | # policy.connect_src :self, :https, "http://localhost:3035", "ws://localhost:3035" if Rails.env.development?
16 |
17 | # # Specify URI for violation reports
18 | # # policy.report_uri "/csp-violation-report-endpoint"
19 | # end
20 |
21 | # If you are using UJS then enable automatic nonce generation
22 | # Rails.application.config.content_security_policy_nonce_generator = -> request { SecureRandom.base64(16) }
23 |
24 | # Set the nonce only to specific directives
25 | # Rails.application.config.content_security_policy_nonce_directives = %w(script-src)
26 |
27 | # Report CSP violations to a specified URI
28 | # For further information see the following documentation:
29 | # https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy-Report-Only
30 | # Rails.application.config.content_security_policy_report_only = true
31 |
--------------------------------------------------------------------------------
/docker-images/Ubuntu-Ruby/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM ubuntu:24.04 AS rubybuild
2 |
3 | # build with
4 | # docker build . -t lebalz/ubuntu-ruby:latest
5 | # docker push lebalz/ubuntu-ruby:tagname
6 |
7 | ENV RUBY_PATH=/usr/local
8 | ENV RUBY_VERSION=3.0.2
9 |
10 | # for tz data
11 | ENV DEBIAN_FRONTEND=noninteractive
12 | ENV TZ=Europe/Zurich
13 |
14 | RUN apt-get update && \
15 | apt-get install -y \
16 | autoconf\
17 | bison \
18 | build-essential \
19 | libssl-dev \
20 | libyaml-dev \
21 | libreadline6-dev \
22 | zlib1g-dev \
23 | libncurses5-dev \
24 | libffi-dev \
25 | libgdbm6 \
26 | libgdbm-dev \
27 | libdb-dev \
28 | apt-utils \
29 | locales \
30 | git \
31 | curl \
32 | gcc \
33 | make \
34 | libssl-dev \
35 | zlib1g-dev \
36 | libreadline-dev \
37 | libmysqlclient-dev \
38 | libffi-dev \
39 | redis-server \
40 | build-essential \
41 | mysql-client \
42 | libxslt1-dev \
43 | libsqlite3-dev
44 |
45 | RUN git clone https://github.com/rbenv/ruby-build.git $RUBY_PATH/plugins/ruby-build && \
46 | $RUBY_PATH/plugins/ruby-build/install.sh
47 | RUN ruby-build $RUBY_VERSION $RUBY_PATH/
48 |
49 | # ditch unused dev dependencies
50 | FROM ubuntu:24.04
51 | ENV RUBY_PATH=/usr/local
52 |
53 | ENV PATH=$RUBY_PATH/bin:$PATH
54 | RUN apt-get update && \
55 | apt-get install -y \
56 | git \
57 | curl \
58 | gcc \
59 | make \
60 | libssl-dev \
61 | libyaml-dev \
62 | zlib1g-dev \
63 | libmysqlclient-dev
64 |
65 | COPY --from=rubybuild $RUBY_PATH $RUBY_PATH
66 | CMD [ "irb" ]
--------------------------------------------------------------------------------
/app/assets/stylesheets/database_schema_tree.scss:
--------------------------------------------------------------------------------
1 | .database-index {
2 | -webkit-user-select: none;
3 | -moz-user-select: none;
4 | -ms-user-select: none;
5 | -o-user-select: none;
6 | user-select: none;
7 |
8 | .item {
9 | height: 22px;
10 |
11 | &.schema-item {
12 | margin-left: 1em;
13 | }
14 | &.table-item {
15 | margin-left: 2em;
16 | }
17 |
18 | }
19 | // must be outside of the item class
20 | // because it clashes otherwise with semantic
21 | .column-item {
22 | height: 22px;
23 | margin-left: 3em;
24 | display: flex;
25 | flex-direction: row;
26 | justify-content: space-between;
27 | align-items: center;
28 | cursor: pointer;
29 | color: #4183C4;
30 |
31 | &:hover {
32 | color: #1e70bf;
33 | }
34 |
35 | .primary-key {
36 | text-decoration: underline;
37 | font-weight: 800;
38 | }
39 | }
40 |
41 | }
42 |
43 | .database-index-header {
44 | margin: 0 8px 4px 8px;
45 | display: flex;
46 | flex-direction: row;
47 | justify-content: space-between;
48 | align-items: center;
49 |
50 | h3 {
51 | cursor: pointer;
52 | margin: 0 !important;
53 |
54 | -webkit-user-select: none;
55 | -moz-user-select: none;
56 | -ms-user-select: none;
57 | -o-user-select: none;
58 | user-select: none;
59 | }
60 | }
61 |
62 | .filter-databases {
63 | margin: 0 8px 4px 8px;
64 | input {
65 | line-height: 1rem;
66 | }
67 | }
68 | .column-metadata {
69 | td {
70 | white-space: nowrap;
71 | &:first-child {
72 | font-weight: bold;
73 | }
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/app/javascript/models/Graphs/LineGraph.ts:
--------------------------------------------------------------------------------
1 | import { action, observable, computed } from 'mobx';
2 | import { rejectUndefined } from '../../utils/listFilters';
3 | import { IGraph, GraphType } from './WordcloudGraph';
4 | import { SEMANTIC_HEX_COLORS } from '../../utils/colors';
5 |
6 | export default class LineGraph implements IGraph {
7 | readonly type = GraphType.LineGraph;
8 | readonly canFocus = true;
9 |
10 | @observable focused: 'xColumn' | 'yColumns' = 'yColumns';
11 | @observable focuseIndex: number = 0;
12 |
13 | @observable xColumn?: number;
14 | yColumns = observable([]);
15 | colors = observable(new Map());
16 |
17 | @action
18 | onColumnSelection(columnIndex: number) {
19 | if (this.focused === 'xColumn') {
20 | if (this.xColumn === columnIndex) {
21 | this.xColumn = undefined;
22 | } else {
23 | this.xColumn = columnIndex;
24 | }
25 | return;
26 | }
27 | if (this.yColumns.length > this.focuseIndex) {
28 | this.yColumns[this.focuseIndex] = columnIndex;
29 | } else {
30 | this.yColumns.push(columnIndex);
31 |
32 | this.colors.set(columnIndex, SEMANTIC_HEX_COLORS[this.colors.size % SEMANTIC_HEX_COLORS.length]);
33 | }
34 | }
35 |
36 | highlightColor(idx: number) {
37 | if (idx === this.xColumn) {
38 | return 'blue';
39 | }
40 | if (this.yColumns.includes(idx)) {
41 | return 'green';
42 | }
43 | }
44 |
45 | @computed
46 | get selectedColumns() {
47 | return rejectUndefined([this.xColumn, ...this.yColumns.slice().filter((idx) => idx >= 0)]);
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/app/javascript/components/Dashboard/AddDbServer.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { observer, inject } from 'mobx-react';
3 | import DbServerStore from '../../stores/db_server_store';
4 | import { TempDbServer, TempDbServerRole } from '../../models/TempDbServer';
5 | import { RouterStore } from 'mobx-react-router';
6 | import SchemaQueryStore from '../../stores/schema_query_store';
7 | import { OwnerType } from '../../api/db_server';
8 | import { DEFAULT_DB_SERVER } from '../../models/DbServer';
9 | import AddEntityButton from '../../shared/AddEntityButton';
10 |
11 | interface Props {
12 | ownerType: OwnerType;
13 | ownerId: string;
14 | }
15 |
16 | interface InjectedProps extends Props {
17 | dbServerStore: DbServerStore;
18 | routerStore: RouterStore;
19 | schemaQueryStore: SchemaQueryStore;
20 | }
21 |
22 | @inject('dbServerStore', 'routerStore', 'schemaQueryStore')
23 | @observer
24 | export default class AddDbServer extends React.Component {
25 | get injected() {
26 | return this.props as InjectedProps;
27 | }
28 | render() {
29 | return (
30 | {
32 | const temp = new TempDbServer(
33 | { ...DEFAULT_DB_SERVER, owner_type: this.props.ownerType, owner_id: this.props.ownerId },
34 | this.injected.dbServerStore,
35 | this.injected.schemaQueryStore,
36 | TempDbServerRole.Create,
37 | this.injected.dbServerStore.cancelToken
38 | );
39 | this.injected.dbServerStore.setTempDbServer(temp);
40 | }}
41 | title="Add new db server"
42 | />
43 | );
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/app/javascript/components/Workbench/ResultPanel/ResultPanelBody.tsx:
--------------------------------------------------------------------------------
1 | import { action, computed } from 'mobx';
2 | import { inject, observer } from 'mobx-react';
3 | import React, { Fragment } from 'react';
4 | import { ResultState } from '../../../api/db_server';
5 | import { QueryResult } from '../../../models/QueryEditor';
6 | import ViewStateStore from '../../../stores/view_state_store';
7 | import Graph from '../SqlResult/Graph';
8 | import { SqlResult } from '../SqlResult/SqlResult';
9 | import { SuccessTableData } from '../../../models/Result';
10 |
11 | interface Props {
12 | index: number;
13 | queryName: string;
14 | result: QueryResult;
15 | }
16 |
17 | interface InjectedProps extends Props {
18 | viewStateStore: ViewStateStore;
19 | }
20 |
21 | @inject('viewStateStore')
22 | @observer
23 | export default class ResultPanelBody extends React.Component {
24 | @computed
25 | get injected() {
26 | return this.props as InjectedProps;
27 | }
28 |
29 | @computed
30 | get viewState() {
31 | return this.injected.viewStateStore.resultTableState(`${this.props.queryName}#${this.props.index}`);
32 | }
33 |
34 | render() {
35 | const result = this.props.result;
36 | const idx = this.props.index;
37 | const resultId = `${this.props.queryName}#${idx}`;
38 |
39 | return (
40 |
41 | {result.data.state === ResultState.Success && this.viewState.showGraph && (
42 |
43 | )}
44 |
45 |
46 | );
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/app/javascript/components/Workbench/SqlResult/PreviewImage.tsx:
--------------------------------------------------------------------------------
1 | import axios from 'axios';
2 | import React from 'react';
3 | import { OGMeta, getOGMeta } from '../../../api/og_meta';
4 | import { Card, Icon, Image } from 'semantic-ui-react';
5 |
6 |
7 | interface Props {
8 | url: string;
9 | }
10 | const IMAGE_EXTENSIONS = ['jpg', 'jpeg', 'png', 'gif', 'svg', 'bmp', 'webp'];
11 | const PreviewImage = (props: Props) => {
12 | const [meta, setMeta] = React.useState(undefined); // [title, description, image
13 | const { url } = props;
14 |
15 | React.useEffect(() => {
16 | if (!url) {
17 | return;
18 | }
19 | if (IMAGE_EXTENSIONS.some((ext) => url.toLowerCase().endsWith(`.${ext}`))) {
20 | setMeta({
21 | title: url.split('/').pop() || '',
22 | description: '',
23 | image: url,
24 | site_name: '',
25 | });
26 | return;
27 | }
28 | getOGMeta(url).then((res) => {
29 | setMeta(res.data);
30 | }).catch((err) => {
31 | setMeta({});
32 | })
33 | }, [url]);
34 |
35 | if (!meta) {
36 | return ;
37 | }else if (Object.keys(meta).length === 0) {
38 | return ;
39 | }
40 | return (
41 |
48 | )
49 | }
50 |
51 | export default PreviewImage;
--------------------------------------------------------------------------------
/app/models/group_member.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | # == Schema Information
4 | #
5 | # Table name: group_members
6 | #
7 | # id :bigint not null, primary key
8 | # crypto_key_encrypted :string
9 | # is_admin :boolean default(FALSE), not null
10 | # is_outdated :boolean default(FALSE), not null
11 | # created_at :datetime not null
12 | # updated_at :datetime not null
13 | # group_id :uuid not null
14 | # user_id :uuid not null
15 | #
16 | # Indexes
17 | #
18 | # index_group_members_on_group_id (group_id)
19 | # index_group_members_on_user_id (user_id)
20 | # index_group_members_on_user_id_and_group_id (user_id,group_id) UNIQUE
21 | #
22 | # Foreign Keys
23 | #
24 | # fk_rails_... (group_id => groups.id)
25 | # fk_rails_... (user_id => users.id)
26 | #
27 | class GroupMember < ApplicationRecord
28 | self.table_name = 'group_members'
29 |
30 | belongs_to :user
31 | belongs_to :group
32 |
33 | def admin?
34 | is_admin
35 | end
36 |
37 | def outdated?
38 | is_outdated
39 | end
40 |
41 | # def destroy
42 | # require 'pry'; binding.pry
43 | # self.class.where(user: user, group: group).first.destroy
44 | # end
45 |
46 | # def destroy!
47 | # self.class.where(user: user, group: group).first.destroy!
48 | # end
49 |
50 | # @param private_key [OpenSSL::PKey::RSA]
51 | # @return [string] decrypted, Base64 encoded, crypto key
52 | def crypto_key(private_key)
53 | private_key.private_decrypt(Base64.strict_decode64(crypto_key_encrypted))
54 | rescue StandardError
55 | nil
56 | end
57 | end
58 |
--------------------------------------------------------------------------------
/config/puma.rb:
--------------------------------------------------------------------------------
1 | # Puma can serve each request in a thread from an internal thread pool.
2 | # The `threads` method setting takes two numbers: a minimum and maximum.
3 | # Any libraries that use thread pools should be configured to match
4 | # the maximum value specified for Puma. Default is set to 5 threads for minimum
5 | # and maximum; this matches the default thread size of Active Record.
6 | #
7 | max_threads_count = ENV.fetch("RAILS_MAX_THREADS") { 5 }
8 | min_threads_count = ENV.fetch("RAILS_MIN_THREADS") { max_threads_count }
9 | threads min_threads_count, max_threads_count
10 |
11 | # Specifies the `port` that Puma will listen on to receive requests; default is 3000.
12 | #
13 | port ENV.fetch("PORT") { 3000 }
14 |
15 | # Specifies the `environment` that Puma will run in.
16 | #
17 | environment ENV.fetch("RAILS_ENV") { "development" }
18 |
19 | # Specifies the `pidfile` that Puma will use.
20 | pidfile ENV.fetch("PIDFILE") { "tmp/pids/server.pid" }
21 |
22 | # Specifies the number of `workers` to boot in clustered mode.
23 | # Workers are forked web server processes. If using threads and workers together
24 | # the concurrency of the application would be max `threads` * `workers`.
25 | # Workers do not work on JRuby or Windows (both of which do not support
26 | # processes).
27 | #
28 | # workers ENV.fetch("WEB_CONCURRENCY") { 2 }
29 |
30 | # Use the `preload_app!` method when specifying a `workers` number.
31 | # This directive tells Puma to first boot the application and load code
32 | # before forking the application. This takes advantage of Copy On Write
33 | # process behavior so workers use less memory.
34 | #
35 | # preload_app!
36 |
37 | # Allow puma to be restarted by `rails restart` command.
38 | plugin :tmp_restart
39 |
--------------------------------------------------------------------------------
/app/javascript/models/DbTable.ts:
--------------------------------------------------------------------------------
1 | import { observable, computed, action } from 'mobx';
2 | import { DbTable as DbTableProps, Index, Schema, ReferenceConstraint } from '../api/db_server';
3 | import _ from 'lodash';
4 | import Database from './Database';
5 | import DbColumn, { Mark } from './DbColumn';
6 | import { REST } from '../declarations/REST';
7 | import DbSchema from './DbSchema';
8 |
9 | interface RequestState {
10 | columns: REST;
11 | foreignKeys: REST;
12 | indexes: REST;
13 | }
14 | export default class DbTable {
15 | readonly schema: DbSchema;
16 | readonly name: string;
17 | readonly columns: DbColumn[];
18 | @observable show: boolean = false;
19 |
20 | constructor(schema: DbSchema, table: DbTableProps) {
21 | this.schema = schema;
22 | this.name = table.name;
23 | const columns = table.columns.map((column) => new DbColumn(this, column));
24 | this.columns = columns.sort((col_a, col_b) => col_a.position - col_b.position);
25 | }
26 |
27 | get foreignConstraints(): ReferenceConstraint[] {
28 | return this.columns
29 | .map((col) => col.foreignConstraints)
30 | .reduce((prev, fks) => {
31 | prev.push(...fks);
32 | return prev;
33 | }, []);
34 | }
35 |
36 | find(columnName: string): DbColumn | undefined {
37 | return this.columns.find((c) => c.name === columnName);
38 | }
39 |
40 | @action
41 | toggleShow() {
42 | this.show = !this.show;
43 | }
44 |
45 | @computed get mark(): Mark {
46 | const col = this.columns.find((c) => c.mark === Mark.To || c.mark === Mark.From);
47 | return col?.mark ?? Mark.None;
48 | }
49 |
50 | column(name: string): DbColumn | undefined {
51 | return this.columns.find((c) => c.name === name);
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/app/javascript/models/User.tsx:
--------------------------------------------------------------------------------
1 | import { observable, computed } from 'mobx';
2 | import { User as UserProps } from '../api/user';
3 | import _ from 'lodash';
4 |
5 | export enum Role {
6 | Admin = 'admin',
7 | User = 'user'
8 | }
9 | export default class User {
10 | readonly id: string;
11 | readonly email: string;
12 | readonly createdAt: Date;
13 | readonly role: Role;
14 | readonly activated: boolean;
15 | readonly passwordResetRequested: boolean;
16 | @observable queryCount: number;
17 | @observable errorQueryCount: number;
18 | @observable loginCount: number;
19 | @observable updatedAt: Date;
20 |
21 | constructor(props: UserProps) {
22 | this.id = props.id;
23 | this.email = props.email;
24 | this.loginCount = props.login_count;
25 | this.createdAt = new Date(props.created_at);
26 | this.updatedAt = new Date(props.updated_at);
27 | this.activated = props.activated;
28 | this.role = props.role;
29 | this.queryCount = props.query_count;
30 | this.errorQueryCount = props.error_query_count;
31 | this.passwordResetRequested = props.password_reset_requested;
32 | }
33 |
34 | @computed get isAdmin() {
35 | return this.role === Role.Admin;
36 | }
37 |
38 | static formatDate(date: Date) {
39 | return date.toLocaleString();
40 | }
41 |
42 | @computed
43 | get props(): UserProps {
44 | return {
45 | id: this.id,
46 | email: this.email,
47 | login_count: this.loginCount,
48 | created_at: this.createdAt.toISOString(),
49 | updated_at: this.updatedAt.toISOString(),
50 | activated: this.activated,
51 | role: this.role,
52 | query_count: this.queryCount,
53 | error_query_count: this.errorQueryCount,
54 | password_reset_requested: this.passwordResetRequested
55 | };
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/app/api/resources/admin.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Resources
4 | class Admin < Grape::API
5 | resource :admin do
6 | before do
7 | error!('No admin rights', 403) unless current_user.admin?
8 | end
9 |
10 | resource :users do
11 | desc 'Get all users'
12 | get do
13 | authorize User, :index?
14 |
15 | present User.all, with: Entities::User
16 | end
17 |
18 | route_param :id, type: String, desc: 'User id' do
19 |
20 | desc 'User'
21 | get do
22 | user = policy_scope(User).find(params[:id])
23 | authorize user, :show?
24 |
25 | present user, with: Entities::User
26 | end
27 |
28 | desc 'Delete user'
29 | delete do
30 | to_delete = policy_scope(User).find(params[:id])
31 |
32 | authorize to_delete, :destroy?
33 | error!(to_delete.errors.messages, 400) unless to_delete.destroy
34 |
35 | status :no_content
36 | end
37 |
38 | desc 'Update user'
39 | params do
40 | requires :data, type: Hash do
41 | optional(
42 | :role,
43 | type: Symbol,
44 | default: :user,
45 | values: %i[user admin],
46 | desc: 'user roles'
47 | )
48 | end
49 | end
50 | put do
51 | user = policy_scope(User).find(params[:id])
52 | authorize user, :update?
53 |
54 | change = ActionController::Parameters.new(params[:data])
55 | success = user.update(change.permit(:role))
56 | error!(user.errors.messages, 400) unless success
57 |
58 | status :no_content
59 | end
60 | end
61 | end
62 | end
63 | end
64 | end
65 |
--------------------------------------------------------------------------------
/app/javascript/models/Graphs/WordcloudGraph.ts:
--------------------------------------------------------------------------------
1 | import { action, observable, computed } from 'mobx';
2 | import { rejectUndefined } from '../../utils/listFilters';
3 | import LineGraph from './LineGraph';
4 |
5 | export enum GraphType {
6 | WordCloud,
7 | LineGraph
8 | }
9 |
10 | export type Graph = WordcloudGraph | LineGraph;
11 | export const MAX_FONT_SIZE = 120;
12 |
13 | export interface IGraph {
14 | readonly type: GraphType;
15 | canFocus: boolean;
16 | onColumnSelection: (columnIndex: number) => any;
17 | highlightColor: (columnIndex: number) => 'green' | 'blue' | undefined;
18 | selectedColumns: number[];
19 | }
20 |
21 | export default class WordcloudGraph implements IGraph {
22 | readonly type = GraphType.WordCloud;
23 | readonly canFocus = true;
24 |
25 | @observable
26 | focused: 'wordColumn' | 'countColumn' = 'wordColumn';
27 |
28 | @observable wordColumn?: number;
29 | @observable countColumn?: number;
30 | @observable minFontSize: number = 0;
31 | @observable maxFontSize: number = 10;
32 | @observable deterministic: boolean = false;
33 |
34 | @action
35 | onColumnSelection(columnIndex: number) {
36 | if (this.focused === 'wordColumn') {
37 | if (this.wordColumn === columnIndex) {
38 | this.wordColumn = undefined;
39 | } else {
40 | this.wordColumn = columnIndex;
41 | }
42 | return;
43 | }
44 | if (this.countColumn === columnIndex) {
45 | this.countColumn = undefined;
46 | } else {
47 | this.countColumn = columnIndex;
48 | }
49 |
50 | }
51 |
52 | highlightColor(idx: number) {
53 | if (idx === this.wordColumn) {
54 | return 'green';
55 | }
56 | if (idx === this.countColumn) {
57 | return 'blue';
58 | }
59 | }
60 |
61 | @computed
62 | get selectedColumns() {
63 | return rejectUndefined([this.wordColumn, this.countColumn]);
64 | }
65 | }
--------------------------------------------------------------------------------
/public/500.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | We're sorry, but something went wrong (500)
5 |
6 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
We're sorry, but something went wrong.
62 |
63 |
If you are the application owner check the logs for more information.
64 |
65 |
66 |
67 |
--------------------------------------------------------------------------------
/app/javascript/components/Workbench/SqlResult/SqlResult.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import _ from 'lodash';
3 | import { ErrorReport } from './ErrorReport';
4 | import { ResultState } from '../../../api/db_server';
5 | import { EmptyResult } from './EmptyResult';
6 | import { SkippedResult } from './SkippedResult';
7 | import ResultTable from './ResultTable';
8 | import { TableData } from '../../../models/Result';
9 | import { observer } from 'mobx-react';
10 | import { QueryResult } from '../../../models/QueryEditor';
11 | import { PrismCode } from './PrismCode';
12 |
13 | interface Props {
14 | result: QueryResult;
15 | queryIndex: number;
16 | viewStateKey: string;
17 | }
18 |
19 | export const SqlResult = observer(({ result, queryIndex, viewStateKey }: Props) => {
20 | switch (result.tableData.state) {
21 | case ResultState.Error:
22 | return ;
23 | case ResultState.Success:
24 | if (result.tableData.result.length === 0) {
25 | return ;
26 | }
27 | console.log('dm', result)
28 | switch (result.displayMode) {
29 | case 'table':
30 | return ;
31 | case 'sql':
32 | case 'markdown':
33 | case 'json':
34 | return (
35 |
40 | );
41 | default:
42 | return ;
43 | }
44 | case ResultState.Skipped:
45 | return ;
46 | }
47 | });
48 |
--------------------------------------------------------------------------------
/public/422.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | The change you wanted was rejected (422)
5 |
6 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
The change you wanted was rejected.
62 |
Maybe you tried to change something you didn't have access to.
63 |
64 |
If you are the application owner check the logs for more information.
65 |
66 |
67 |
68 |
--------------------------------------------------------------------------------
/app/javascript/models/GroupMember.ts:
--------------------------------------------------------------------------------
1 | import { observable, computed, action } from 'mobx';
2 | import _ from 'lodash';
3 | import { GroupMember as GroupMemberProps, setAdminPermission as changeAdminPermission } from '../api/group';
4 | import GroupStore from '../stores/group_store';
5 | import Group from './Group';
6 | import { UserProfile } from '../api/user';
7 | import UserStore from '../stores/user_store';
8 |
9 | export enum Mark {
10 | From = 'from',
11 | To = 'to',
12 | None = 'none'
13 | }
14 |
15 | export default class GroupMember {
16 | private readonly groupStore: GroupStore;
17 | private readonly userStore: UserStore;
18 |
19 | @observable isAdmin: boolean;
20 | readonly isOutdated: boolean;
21 | readonly groupId: string;
22 | readonly userId: string;
23 | readonly created_at: string;
24 | readonly updated_at: string;
25 |
26 | constructor(groupStore: GroupStore, userStore: UserStore, groupId: string, props: GroupMemberProps) {
27 | this.groupStore = groupStore;
28 | this.userStore = userStore;
29 | this.isAdmin = props.is_admin;
30 | this.isOutdated = props.is_outdated;
31 | this.groupId = groupId;
32 | this.created_at = props.created_at;
33 | this.updated_at = props.updated_at;
34 | this.userId = props.user_id;
35 | }
36 |
37 | @computed
38 | get group(): Group {
39 | return this.groupStore.joinedGroups.find((group) => group.id === this.groupId)!;
40 | }
41 |
42 | @computed
43 | get user(): UserProfile | undefined {
44 | return this.userStore.groupUsers.find((user) => user.id === this.userId)!;
45 | }
46 |
47 | @computed
48 | get isCurrentUser(): boolean {
49 | return this.userId === this.userStore.loggedInUser.id;
50 | }
51 |
52 | @action
53 | setAdminPermission(isAdmin: boolean) {
54 | if (!this.user) {
55 | return;
56 | }
57 | return changeAdminPermission(this.groupId, this.user.id, isAdmin).then(({ data }) => {
58 | this.isAdmin = data.is_admin;
59 | });
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/public/404.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | The page you were looking for doesn't exist (404)
5 |
6 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
The page you were looking for doesn't exist.
62 |
You may have mistyped the address or the page may have moved.
63 |
64 |
If you are the application owner check the logs for more information.
65 |
66 |
67 |
68 |
--------------------------------------------------------------------------------
/app/javascript/components/Workbench/DatabaseSchemaTree/PlaceholderItem.tsx:
--------------------------------------------------------------------------------
1 | import React, { Fragment } from 'react';
2 | import { Icon, List, Loader } from 'semantic-ui-react';
3 | import { inject, observer } from 'mobx-react';
4 | import _ from 'lodash';
5 | import RouterStore from '../../../stores/router_store';
6 |
7 | interface PlaceholderItemProps {
8 | dbName: string;
9 | dbServerId: string;
10 | isLoading?: boolean;
11 | }
12 |
13 | interface InjectedDbItemPorps extends PlaceholderItemProps {
14 | routerStore: RouterStore;
15 | }
16 |
17 | @inject('routerStore')
18 | @observer
19 | export default class PlaceholderItem extends React.Component {
20 | state = { loading: false };
21 | get injected() {
22 | return this.props as InjectedDbItemPorps;
23 | }
24 |
25 | get link() {
26 | const { dbName, dbServerId } = this.props;
27 | return `/connections/${dbServerId}/${dbName}`;
28 | }
29 |
30 | render() {
31 | const { dbName, isLoading } = this.props;
32 | return (
33 |
34 | {
39 | this.setState({ loading: true });
40 | this.injected.routerStore.push(this.link);
41 | }}
42 | >
43 |
44 |
45 |
46 | {dbName}
47 |
48 |
49 |
50 | {(isLoading || this.state.loading) && }
51 |
52 | );
53 | }
54 | }
55 |
56 | export const DbLoadIndicator = () => {
57 | return (
58 |
59 |
60 |
61 |
62 |
63 | );
64 | };
65 |
--------------------------------------------------------------------------------
/app/javascript/components/Profile/DeleteAccount.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { InputOnChangeData, Message, Segment, Form, Header } from 'semantic-ui-react';
3 | import { inject, observer } from 'mobx-react';
4 | import SessionStore, { ApiRequestState } from '../../stores/session_store';
5 |
6 | interface InjectedProps {
7 | sessionStore: SessionStore;
8 | }
9 |
10 | @inject('sessionStore')
11 | @observer
12 | export default class DeleteAccount extends React.Component {
13 | private password: string = '';
14 |
15 | get injected() {
16 | return this.props as InjectedProps;
17 | }
18 |
19 | onChangePassword = (event: React.ChangeEvent, data: InputOnChangeData) => {
20 | this.password = event.target.value;
21 | };
22 |
23 | deleteAccount() {
24 | this.injected.sessionStore.deleteAccount(this.password);
25 | }
26 |
27 | render() {
28 | const loginState = this.injected.sessionStore.loginState.state;
29 | return (
30 |
31 |
32 |
33 |
41 |
48 |
49 |
54 |
55 |
56 | );
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/app/javascript/models/DbColumn.ts:
--------------------------------------------------------------------------------
1 | import { observable, action } from 'mobx';
2 | import _ from 'lodash';
3 | import DbTable from './DbTable';
4 | import { SqlTypeMetadata, Column as ColumnProps, Constraint, ReferenceConstraint } from '../api/db_server';
5 | import Database from './Database';
6 |
7 | export enum Mark {
8 | From = 'from',
9 | To = 'to',
10 | None = 'none'
11 | }
12 | export default class DbColumn {
13 | readonly table: DbTable;
14 | readonly position: number;
15 | readonly name: string;
16 | readonly default: string;
17 | readonly isNull: boolean;
18 | readonly isPrimaryKey: boolean;
19 | readonly sqlTypeMetadata: SqlTypeMetadata;
20 | readonly constraints: (Constraint | ReferenceConstraint)[];
21 | references?: DbColumn = undefined;
22 | referencedBy: DbColumn[] = [];
23 |
24 | @observable mark: Mark = Mark.None;
25 |
26 | constructor(table: DbTable, props: ColumnProps) {
27 | this.table = table;
28 | this.name = props.name;
29 | this.position = props.position;
30 | this.default = props.default;
31 | this.isNull = props.null;
32 | this.sqlTypeMetadata = props.sql_type_metadata;
33 | this.isPrimaryKey = props.is_primary;
34 | this.constraints = props.constraints;
35 | }
36 |
37 | @action
38 | linkForeignKeys(db: Database) {
39 | this.foreignConstraints.forEach((fc) => {
40 | const ref = db.find(fc.schema, fc.table, fc.column);
41 | if (ref) {
42 | ref.referencedBy.push(this);
43 | this.references = ref;
44 | }
45 | });
46 | }
47 |
48 | get foreignConstraints(): ReferenceConstraint[] {
49 | return this.constraints.filter((constraint) => !!constraint.schema) as ReferenceConstraint[];
50 | }
51 |
52 | get locationName(): string {
53 | return `${this.table.name}#${this.name}`;
54 | }
55 |
56 | get isForeignKey() {
57 | return !!this.references;
58 | }
59 |
60 | get foreignColumn(): DbColumn | undefined {
61 | return this.references;
62 | }
63 |
64 | get foreignTable(): DbTable | undefined {
65 | return this.references?.table;
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/app/javascript/views/ForgotPassword.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Form, Message } from 'semantic-ui-react';
3 | import { inject, observer } from 'mobx-react';
4 | import SessionStore, { ApiRequestState } from '../stores/session_store';
5 | import { requestPasswordReset } from '../api/user';
6 |
7 | interface InjectedProps {
8 | sessionStore: SessionStore;
9 | }
10 |
11 | @inject('sessionStore')
12 | @observer
13 | export default class ForgotPassword extends React.Component {
14 | state = {
15 | resetState: ApiRequestState.None
16 | };
17 | private email: string = '';
18 |
19 | get injected() {
20 | return this.props as InjectedProps;
21 | }
22 |
23 | resetPassword() {
24 | this.setState({ resetState: ApiRequestState.Waiting });
25 | requestPasswordReset(this.email)
26 | .then(() => {
27 | this.setState({ resetState: ApiRequestState.Success });
28 | })
29 | .catch(() => {
30 | this.setState({ resetState: ApiRequestState.Error });
31 | });
32 | }
33 |
34 | render() {
35 | return (
36 |
42 | (this.email = e.target.value)}
51 | />
52 |
53 |
54 |
55 |
60 |
61 | );
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/app/javascript/components/Profile/Groups/GroupProps.tsx:
--------------------------------------------------------------------------------
1 | import React, { Fragment, SyntheticEvent } from 'react';
2 | import { Input, InputOnChangeData, TextAreaProps, TextArea } from 'semantic-ui-react';
3 | import { observer } from 'mobx-react';
4 | import cx from 'classnames';
5 | import { computed } from 'mobx';
6 | import Group from '../../../models/Group';
7 |
8 | interface Props {
9 | group: Group;
10 | isReadonly?: boolean;
11 | }
12 |
13 | @observer
14 | export default class GroupProps extends React.Component {
15 |
16 | @computed
17 | get group() {
18 | return this.props.group;
19 | }
20 |
21 | onChangeName = (event: SyntheticEvent, data: InputOnChangeData) => {
22 | event.preventDefault();
23 | if (this.group) {
24 | this.group.name = data.value;
25 | }
26 | };
27 |
28 | onChangeDescription = (event: React.FormEvent, data: TextAreaProps) => {
29 | event.preventDefault();
30 | if (this.group) {
31 | this.group.description = data.value?.toString() ?? '';
32 | }
33 | };
34 |
35 |
36 | render() {
37 | return (
38 |
39 |
44 |
50 |
51 |
56 |
63 |
64 |
65 | );
66 | }
67 | }
68 |
--------------------------------------------------------------------------------