sqlui 0.1.69 → 0.1.70
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.release-version +1 -1
- data/app/checks.rb +49 -0
- data/app/count_down_latch.rb +31 -0
- data/app/database_config.rb +4 -2
- data/app/deep.rb +3 -46
- data/app/github/cache.rb +65 -0
- data/app/github/caching_client.rb +36 -0
- data/app/github/client.rb +36 -0
- data/app/github/file.rb +19 -0
- data/app/github/paths.rb +21 -0
- data/app/github/tree.rb +40 -0
- data/app/github/tree_client.rb +93 -0
- data/app/saved_config.rb +16 -0
- data/app/server.rb +19 -8
- data/app/views/databases.erb +1 -1
- data/client/resources/sqlui.css +1 -1
- data/client/resources/sqlui.js +7 -0
- metadata +82 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 476cdcf4c9f35aa0b46a5d8dd3f41aa409a4ef5cf72bb0d02099ea44d4eb92e3
|
4
|
+
data.tar.gz: f52996d5793db9b6529cf7f1c8f5dd1ee171bd010f00419604e2399d430eb811
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f37ae3ed5654474330b6ce34a432cbe55c87911494819aeb46e279cf22246b48d3afeddeeb7a4f2f69bee8e39e4186d2feab86c1dc8405e7b3f149a9e09a8d87
|
7
|
+
data.tar.gz: a8eaad53be8c5740cc63fc0a5d2017a4ff3070fc90521381e3f8d2dce99d5ea399dce493544e4112579a89b0da925c606328739b1b89c137bbeeb1e865bb1af0
|
data/.release-version
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.1.
|
1
|
+
0.1.70
|
data/app/checks.rb
ADDED
@@ -0,0 +1,49 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Some argument check methods.
|
4
|
+
module Checks
|
5
|
+
def check_non_nil(hash)
|
6
|
+
hash.each do |key, value|
|
7
|
+
raise "invalid #{key}: #{value.nil? ? 'nil' : value}" if value.nil?
|
8
|
+
end
|
9
|
+
|
10
|
+
hash.size == 1 ? hash.values.first : hash.values
|
11
|
+
end
|
12
|
+
|
13
|
+
def check_is_a(hash)
|
14
|
+
hash.each do |key, (clazz, value)|
|
15
|
+
raise "invalid #{key} #{clazz}: #{value.nil? ? 'nil' : value} (#{value.class})" unless value.is_a?(clazz)
|
16
|
+
end
|
17
|
+
|
18
|
+
hash.size == 1 ? hash.values.first[1] : hash.values.map { |(_clazz, value)| value }
|
19
|
+
end
|
20
|
+
|
21
|
+
def check_boolean(hash)
|
22
|
+
hash.each do |key, value|
|
23
|
+
raise "invalid #{key}: #{value.nil? ? 'nil' : value}" unless value.is_a?(TrueClass) || value.is_a?(FalseClass)
|
24
|
+
end
|
25
|
+
|
26
|
+
hash.size == 1 ? hash.values.first : hash.values
|
27
|
+
end
|
28
|
+
|
29
|
+
def check_non_empty_string(hash)
|
30
|
+
hash.each do |key, value|
|
31
|
+
raise "invalid #{key}: #{value.nil? ? 'nil' : value}" unless value.is_a?(String) && !value.empty?
|
32
|
+
end
|
33
|
+
|
34
|
+
hash.size == 1 ? hash.values.first : hash.values
|
35
|
+
end
|
36
|
+
|
37
|
+
def check_positive_integer(hash)
|
38
|
+
hash.each do |key, value|
|
39
|
+
raise "invalid #{key}: #{value.nil? ? 'nil' : value}" unless value.is_a?(Integer) && value.positive?
|
40
|
+
end
|
41
|
+
|
42
|
+
hash.size == 1 ? hash.values.first : hash.values
|
43
|
+
end
|
44
|
+
|
45
|
+
def check_enumerable_of(enumerable, clazz)
|
46
|
+
check_is_a(enumerable: [Enumerable, enumerable])
|
47
|
+
enumerable.each { |value| check_is_a(value: [clazz, value]) }
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# A latch that allows you to wait for a maximum number of seconds.
|
4
|
+
class CountDownLatch
|
5
|
+
def initialize(count)
|
6
|
+
@count = count
|
7
|
+
@mutex = Mutex.new
|
8
|
+
@resource = ConditionVariable.new
|
9
|
+
end
|
10
|
+
|
11
|
+
def count_down
|
12
|
+
@mutex.synchronize do
|
13
|
+
if @count.positive?
|
14
|
+
@count -= 1
|
15
|
+
@resource.signal if @count.zero?
|
16
|
+
end
|
17
|
+
@count
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def count
|
22
|
+
@mutex.synchronize { @count }
|
23
|
+
end
|
24
|
+
|
25
|
+
def await(timeout:)
|
26
|
+
@mutex.synchronize do
|
27
|
+
@resource.wait(@mutex, timeout) if @count.positive?
|
28
|
+
raise 'timed out while waiting' unless @count.zero?
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
data/app/database_config.rb
CHANGED
@@ -5,10 +5,11 @@ require 'mysql2'
|
|
5
5
|
require 'set'
|
6
6
|
|
7
7
|
require_relative 'args'
|
8
|
+
require_relative 'saved_config'
|
8
9
|
|
9
10
|
# Config for a single database.
|
10
11
|
class DatabaseConfig
|
11
|
-
attr_reader :display_name, :description, :url_path, :joins, :
|
12
|
+
attr_reader :display_name, :description, :url_path, :joins, :saved_config, :tables, :columns, :client_params
|
12
13
|
|
13
14
|
def initialize(hash)
|
14
15
|
@display_name = Args.fetch_non_empty_string(hash, :display_name).strip
|
@@ -17,7 +18,8 @@ class DatabaseConfig
|
|
17
18
|
raise ArgumentError, 'url_path should not start with a /' if @url_path.start_with?('/')
|
18
19
|
raise ArgumentError, 'url_path should not end with a /' if @url_path.end_with?('/')
|
19
20
|
|
20
|
-
|
21
|
+
saved_config_hash = Args.fetch_optional_hash(hash, :saved_config)
|
22
|
+
@saved_config = saved_config_hash.nil? ? nil : SavedConfig.new(saved_config_hash)
|
21
23
|
|
22
24
|
# Make joins an array. It is only a map to allow for YAML extension.
|
23
25
|
@joins = (Args.fetch_optional_hash(hash, :joins) || {}).values
|
data/app/deep.rb
CHANGED
@@ -1,43 +1,11 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
each { |value| value.deep_transform_keys!(&block) if value.respond_to?(:deep_transform_keys!) }
|
7
|
-
self
|
8
|
-
end
|
9
|
-
|
10
|
-
def deep_symbolize_keys!
|
11
|
-
deep_transform_keys!(&:to_sym)
|
12
|
-
end
|
13
|
-
|
14
|
-
def deep_dup(result = {})
|
15
|
-
map do |value|
|
16
|
-
value.respond_to?(:deep_dup) ? value.deep_dup : value.clone
|
17
|
-
end
|
18
|
-
result
|
19
|
-
end
|
20
|
-
end
|
3
|
+
require 'active_support/core_ext/hash/deep_merge'
|
4
|
+
require 'active_support/core_ext/hash/keys'
|
5
|
+
require 'active_support/core_ext/object/deep_dup'
|
21
6
|
|
22
7
|
# Deep extensions for Hash.
|
23
8
|
class Hash
|
24
|
-
def deep_transform_keys!(&block)
|
25
|
-
transform_keys!(&block)
|
26
|
-
each_value { |value| value.deep_transform_keys!(&block) if value.respond_to?(:deep_transform_keys!) }
|
27
|
-
self
|
28
|
-
end
|
29
|
-
|
30
|
-
def deep_symbolize_keys!
|
31
|
-
deep_transform_keys!(&:to_sym)
|
32
|
-
end
|
33
|
-
|
34
|
-
def deep_dup(result = {})
|
35
|
-
each do |key, value|
|
36
|
-
result[key] = value.respond_to?(:deep_dup) ? value.deep_dup : value.clone
|
37
|
-
end
|
38
|
-
result
|
39
|
-
end
|
40
|
-
|
41
9
|
def deep_set(*path, value:)
|
42
10
|
raise ArgumentError, 'no path specified' if path.empty?
|
43
11
|
|
@@ -63,15 +31,4 @@ class Hash
|
|
63
31
|
self.[](path[0]).deep_delete(*path[1..])
|
64
32
|
end
|
65
33
|
end
|
66
|
-
|
67
|
-
def deep_merge!(hash)
|
68
|
-
hash.each do |key, value|
|
69
|
-
if self[key].is_a?(Hash) && value.is_a?(Hash)
|
70
|
-
self[key].deep_merge!(value)
|
71
|
-
else
|
72
|
-
self[key] = value
|
73
|
-
end
|
74
|
-
end
|
75
|
-
self
|
76
|
-
end
|
77
34
|
end
|
data/app/github/cache.rb
ADDED
@@ -0,0 +1,65 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative '../checks'
|
4
|
+
|
5
|
+
module Github
|
6
|
+
# A cache for GitHub API GET requests.
|
7
|
+
class Cache
|
8
|
+
include Checks
|
9
|
+
|
10
|
+
# A cache entry.
|
11
|
+
class Entry
|
12
|
+
include Checks
|
13
|
+
|
14
|
+
attr_accessor :value, :expires_at
|
15
|
+
|
16
|
+
def initialize(value, max_age_seconds)
|
17
|
+
@value = check_non_nil(value: value)
|
18
|
+
@expires_at = Time.now + check_positive_integer(max_age_seconds: max_age_seconds)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def initialize(hash, logger: Logger.new($stdout))
|
23
|
+
@mutex = Mutex.new
|
24
|
+
@hash = check_is_a(hash: [Hash, hash])
|
25
|
+
@logger = check_non_nil(logger: logger)
|
26
|
+
end
|
27
|
+
|
28
|
+
def [](key)
|
29
|
+
check_non_empty_string(key: key)
|
30
|
+
|
31
|
+
@mutex.synchronize do
|
32
|
+
evict
|
33
|
+
if (value = @hash[key])
|
34
|
+
@logger.info "#{self.class} entry found for #{key}"
|
35
|
+
value
|
36
|
+
else
|
37
|
+
@logger.info "#{self.class} entry not found for #{key}"
|
38
|
+
nil
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def []=(key, value)
|
44
|
+
check_non_empty_string(key: key)
|
45
|
+
check_is_a(value: [Entry, value])
|
46
|
+
|
47
|
+
@mutex.synchronize do
|
48
|
+
@logger.info "#{self.class} caching entry for #{key} until #{value.expires_at}"
|
49
|
+
@hash[key] = value
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
private
|
54
|
+
|
55
|
+
def evict
|
56
|
+
now = Time.now
|
57
|
+
@hash.delete_if do |key, value|
|
58
|
+
if value.expires_at < now
|
59
|
+
@logger.info "#{self.class} evicting #{key}"
|
60
|
+
true
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative '../checks'
|
4
|
+
require_relative '../deep'
|
5
|
+
require_relative 'cache'
|
6
|
+
require_relative 'client'
|
7
|
+
|
8
|
+
module Github
|
9
|
+
# Like a Github::Client but with a cache.
|
10
|
+
class CachingClient
|
11
|
+
include Checks
|
12
|
+
|
13
|
+
def initialize(client, cache)
|
14
|
+
@client = check_is_a(client: [Client, client])
|
15
|
+
@cache = check_is_a(cache: [Cache, cache])
|
16
|
+
end
|
17
|
+
|
18
|
+
def get_with_caching(url, cache_for:)
|
19
|
+
check_non_empty_string(url: url)
|
20
|
+
check_positive_integer(cache_for: cache_for)
|
21
|
+
|
22
|
+
if (cache_entry = @cache[url])
|
23
|
+
return cache_entry.value
|
24
|
+
end
|
25
|
+
|
26
|
+
response = @client.get(url)
|
27
|
+
@cache[url] = Cache::Entry.new(response, cache_for)
|
28
|
+
response.deep_dup
|
29
|
+
end
|
30
|
+
|
31
|
+
def get_without_caching(url)
|
32
|
+
check_non_empty_string(url: url)
|
33
|
+
@client.get(url)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'json'
|
4
|
+
require 'logger'
|
5
|
+
require 'rest-client'
|
6
|
+
|
7
|
+
require_relative '../checks'
|
8
|
+
|
9
|
+
module Github
|
10
|
+
# It's a GitHub client. I know there is an official client library.
|
11
|
+
class Client
|
12
|
+
include Checks
|
13
|
+
|
14
|
+
def initialize(access_token:, logger: Logger.new($stdout))
|
15
|
+
@access_token = check_non_empty_string(access_token: access_token)
|
16
|
+
@logger = check_non_nil(logger: logger)
|
17
|
+
end
|
18
|
+
|
19
|
+
def get(url)
|
20
|
+
check_non_empty_string(url: url)
|
21
|
+
|
22
|
+
@logger.info "#{self.class} GET #{url}"
|
23
|
+
response = RestClient.get(
|
24
|
+
url,
|
25
|
+
{
|
26
|
+
'Accept' => 'application/vnd.github+json',
|
27
|
+
'Authorization' => "Bearer #{@access_token}",
|
28
|
+
'X-GitHub-Api-Version' => '2022-11-28'
|
29
|
+
}
|
30
|
+
)
|
31
|
+
raise "get #{url} failed: #{response}" unless response.code == 200
|
32
|
+
|
33
|
+
JSON.parse(response)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
data/app/github/file.rb
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative '../checks'
|
4
|
+
|
5
|
+
module Github
|
6
|
+
# A GitHub file.
|
7
|
+
class File
|
8
|
+
include Checks
|
9
|
+
|
10
|
+
attr_reader :display_path, :content, :github_url
|
11
|
+
|
12
|
+
def initialize(owner:, repo:, branch:, path:, content:)
|
13
|
+
check_non_empty_string(owner: owner, repo: repo, branch: branch, path: path)
|
14
|
+
@content = check_is_a(content: [String, content])
|
15
|
+
@display_path = "#{owner}/#{repo}/#{branch}/#{path}"
|
16
|
+
@github_url = "https://github.com/#{owner}/#{repo}/blob/#{branch}/#{path}"
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
data/app/github/paths.rb
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Github
|
4
|
+
# Methods for dealing with GitHub paths.
|
5
|
+
class Paths
|
6
|
+
# "<owner>/<repo>/<ref>/<some_path>"
|
7
|
+
PATH_PATTERN = %r{^(?:[^/]+/){3}.*[^/]$}
|
8
|
+
private_constant :PATH_PATTERN
|
9
|
+
|
10
|
+
# Parses a path like "<owner>/<repo>/<ref>/<some_path>" into owner, repo, ref, path.
|
11
|
+
def self.parse_file_path(path)
|
12
|
+
check_non_empty_string(path: path)
|
13
|
+
raise "invalid path: #{path}" unless PATH_PATTERN.match?(path)
|
14
|
+
|
15
|
+
owner, repo, ref, *path = path.split('/')
|
16
|
+
path = path.join('/')
|
17
|
+
|
18
|
+
[owner, repo, ref, path]
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
data/app/github/tree.rb
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative '../checks'
|
4
|
+
require_relative 'file'
|
5
|
+
|
6
|
+
module Github
|
7
|
+
# A GitHub file tree.
|
8
|
+
class Tree
|
9
|
+
include Checks
|
10
|
+
include Enumerable
|
11
|
+
|
12
|
+
attr_reader :truncated
|
13
|
+
|
14
|
+
def initialize(files:, truncated: false)
|
15
|
+
@files = check_enumerable_of(files, File)
|
16
|
+
@truncated = check_boolean(truncated: truncated)
|
17
|
+
end
|
18
|
+
|
19
|
+
class << self
|
20
|
+
include Checks
|
21
|
+
|
22
|
+
def for(owner:, repo:, branch:, tree_response:)
|
23
|
+
check_non_empty_string(owner: owner, repo: repo, branch: branch)
|
24
|
+
check_is_a(tree_response: [Hash, tree_response])
|
25
|
+
|
26
|
+
truncated = check_boolean(truncated: tree_response['truncated'])
|
27
|
+
tree = check_is_a(tree: [Array, tree_response['tree']])
|
28
|
+
files = tree.map do |blob|
|
29
|
+
File.new(owner: owner, repo: repo, branch: branch, path: blob['path'], content: blob['content'])
|
30
|
+
end
|
31
|
+
|
32
|
+
Tree.new(files: files, truncated: truncated)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def each(&block)
|
37
|
+
@files.each(&block)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,93 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'base64'
|
4
|
+
require 'concurrent/executor/fixed_thread_pool'
|
5
|
+
require 'json'
|
6
|
+
require 'logger'
|
7
|
+
|
8
|
+
require_relative 'caching_client'
|
9
|
+
require_relative 'tree'
|
10
|
+
require_relative '../checks'
|
11
|
+
require_relative '../count_down_latch'
|
12
|
+
|
13
|
+
module Github
|
14
|
+
# Wraps a Github::Client to provide tree-specific features.
|
15
|
+
class TreeClient
|
16
|
+
include Checks
|
17
|
+
|
18
|
+
@thread_pool = Concurrent::FixedThreadPool.new(5)
|
19
|
+
|
20
|
+
class << self
|
21
|
+
attr_reader :thread_pool
|
22
|
+
end
|
23
|
+
|
24
|
+
DEFAULT_MAX_TREE_CACHE_AGE_SECONDS = 60 * 5 # 5 minutes
|
25
|
+
private_constant :DEFAULT_MAX_TREE_CACHE_AGE_SECONDS
|
26
|
+
|
27
|
+
DEFAULT_MAX_FILE_CACHE_AGE_SECONDS = 60 * 60 * 24 * 7 # 1 week
|
28
|
+
private_constant :DEFAULT_MAX_FILE_CACHE_AGE_SECONDS
|
29
|
+
|
30
|
+
MAX_TREE_SIZE = 50
|
31
|
+
private_constant :MAX_TREE_SIZE
|
32
|
+
|
33
|
+
def initialize(access_token:, cache:, logger: Logger.new($stdout))
|
34
|
+
check_non_empty_string(access_token: access_token)
|
35
|
+
check_is_a(cache: [Cache, cache])
|
36
|
+
|
37
|
+
@client = Github::CachingClient.new(Github::Client.new(access_token: access_token, logger: logger), cache)
|
38
|
+
@logger = check_non_nil(logger: logger)
|
39
|
+
end
|
40
|
+
|
41
|
+
def get_tree(owner:, repo:, branch:, regex:)
|
42
|
+
check_non_empty_string(owner: owner, repo: repo, branch: branch)
|
43
|
+
check_is_a(regex: [Regexp, regex])
|
44
|
+
|
45
|
+
response = @client.get_with_caching(
|
46
|
+
"https://api.github.com/repos/#{owner}/#{repo}/git/trees/#{branch}?recursive=true",
|
47
|
+
cache_for: DEFAULT_MAX_TREE_CACHE_AGE_SECONDS
|
48
|
+
)
|
49
|
+
|
50
|
+
response['tree'] = response['tree'].select { |blob| regex.match?(blob['path']) }
|
51
|
+
raise "trees with more than #{MAX_TREE_SIZE} blobs not supported" if response['tree'].size > MAX_TREE_SIZE
|
52
|
+
|
53
|
+
latch = CountDownLatch.new(response['tree'].size)
|
54
|
+
response['tree'].each do |blob|
|
55
|
+
TreeClient.thread_pool.post do
|
56
|
+
blob['content'] =
|
57
|
+
get_file_content_with_caching(owner: owner, repo: repo, path: blob['path'], ref: response['sha'])
|
58
|
+
ensure
|
59
|
+
latch.count_down
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
latch.await(timeout: 10)
|
64
|
+
raise 'failed to load saved files' unless response['tree'].all? { |blob| blob['content'] }
|
65
|
+
|
66
|
+
Tree.for(owner: owner, repo: repo, branch: branch, tree_response: response)
|
67
|
+
end
|
68
|
+
|
69
|
+
private
|
70
|
+
|
71
|
+
# Returns the contents of the file at the specified path. Uses the cache.
|
72
|
+
def get_file_content_with_caching(owner:, repo:, path:, ref:)
|
73
|
+
check_non_empty_string(owner: owner, repo: repo, sha: ref, path: path)
|
74
|
+
|
75
|
+
response = @client.get_with_caching(
|
76
|
+
"https://api.github.com/repos/#{owner}/#{repo}/contents/#{path}?ref=#{ref}",
|
77
|
+
cache_for: DEFAULT_MAX_FILE_CACHE_AGE_SECONDS
|
78
|
+
)
|
79
|
+
Base64.decode64(response['content'])
|
80
|
+
end
|
81
|
+
|
82
|
+
# Returns the contents of the file at the specified path. Uses the cache.
|
83
|
+
def get_file_content_without_caching(owner:, repo:, path:, ref:)
|
84
|
+
check_non_empty_string(owner: owner, repo: repo, sha: ref, path: path)
|
85
|
+
|
86
|
+
response = @client.get_without_caching(
|
87
|
+
"https://api.github.com/repos/#{owner}/#{repo}/contents/#{path}?ref=#{ref}"
|
88
|
+
)
|
89
|
+
|
90
|
+
Base64.decode64(response['content'])
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
data/app/saved_config.rb
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'args'
|
4
|
+
|
5
|
+
# Config for saved files.
|
6
|
+
class SavedConfig
|
7
|
+
attr_reader :token, :owner, :repo, :branch, :regex
|
8
|
+
|
9
|
+
def initialize(hash)
|
10
|
+
@token = Args.fetch_non_empty_string(hash, :token).strip
|
11
|
+
@owner = Args.fetch_non_empty_string(hash, :owner).strip
|
12
|
+
@repo = Args.fetch_non_empty_string(hash, :repo).strip
|
13
|
+
@branch = Args.fetch_non_empty_string(hash, :branch).strip
|
14
|
+
@regex = Regexp.new(Args.fetch_non_empty_string(hash, :regex))
|
15
|
+
end
|
16
|
+
end
|
data/app/server.rb
CHANGED
@@ -11,6 +11,11 @@ require 'sinatra/base'
|
|
11
11
|
require 'uri'
|
12
12
|
require 'webrick'
|
13
13
|
require_relative 'database_metadata'
|
14
|
+
require_relative 'github/cache'
|
15
|
+
require_relative 'github/caching_client'
|
16
|
+
require_relative 'github/client'
|
17
|
+
require_relative 'github/tree'
|
18
|
+
require_relative 'github/tree_client'
|
14
19
|
require_relative 'mysql_types'
|
15
20
|
require_relative 'sql_parser'
|
16
21
|
require_relative 'sqlui'
|
@@ -22,7 +27,7 @@ class Server < Sinatra::Base
|
|
22
27
|
@logger ||= WEBrick::Log.new
|
23
28
|
end
|
24
29
|
|
25
|
-
def self.init_and_run(config, resources_dir)
|
30
|
+
def self.init_and_run(config, resources_dir, github_cache = Github::Cache.new({}, logger: Server.logger))
|
26
31
|
logger.info("Starting SQLUI v#{Version::SQLUI}")
|
27
32
|
logger.info("Airbrake enabled: #{config.airbrake[:server]&.[](:enabled) || false}")
|
28
33
|
|
@@ -105,6 +110,13 @@ class Server < Sinatra::Base
|
|
105
110
|
end
|
106
111
|
|
107
112
|
post "#{config.base_url_path}/#{database.url_path}/metadata" do
|
113
|
+
tree = Github::Tree.new(files: [], truncated: false)
|
114
|
+
if (saved_config = database.saved_config)
|
115
|
+
tree_client = Github::TreeClient.new(access_token: database.saved_config.token, cache: github_cache,
|
116
|
+
logger: Server.logger)
|
117
|
+
tree = tree_client.get_tree(owner: saved_config.owner, repo: saved_config.repo, branch: saved_config.branch,
|
118
|
+
regex: saved_config.regex)
|
119
|
+
end
|
108
120
|
metadata = database.with_client do |client|
|
109
121
|
{
|
110
122
|
server: "#{config.name} - #{database.display_name}",
|
@@ -113,19 +125,18 @@ class Server < Sinatra::Base
|
|
113
125
|
tables: database.tables,
|
114
126
|
columns: database.columns,
|
115
127
|
joins: database.joins,
|
116
|
-
saved:
|
117
|
-
|
118
|
-
comment_lines = contents.split("\n").take_while do |l|
|
128
|
+
saved: tree.to_h do |file|
|
129
|
+
comment_lines = file.content.split("\n").take_while do |l|
|
119
130
|
l.start_with?('--')
|
120
131
|
end
|
121
|
-
filename = File.basename(path)
|
122
132
|
description = comment_lines.map { |l| l.sub(/^-- */, '') }.join("\n")
|
123
133
|
[
|
124
|
-
|
134
|
+
file.display_path,
|
125
135
|
{
|
126
|
-
filename:
|
136
|
+
filename: file.display_path,
|
137
|
+
github_url: file.github_url,
|
127
138
|
description: description,
|
128
|
-
contents:
|
139
|
+
contents: file.content
|
129
140
|
}
|
130
141
|
]
|
131
142
|
end
|
data/app/views/databases.erb
CHANGED
data/client/resources/sqlui.css
CHANGED
data/client/resources/sqlui.js
CHANGED
@@ -24997,6 +24997,12 @@
|
|
24997
24997
|
route(event.target, event, runUrl, true);
|
24998
24998
|
});
|
24999
24999
|
|
25000
|
+
const viewOnGithubLinkElement = document.createElement('a');
|
25001
|
+
viewOnGithubLinkElement.classList.add('link', 'view-github-link');
|
25002
|
+
viewOnGithubLinkElement.innerText = 'github';
|
25003
|
+
viewOnGithubLinkElement.href = file.github_url;
|
25004
|
+
viewOnGithubLinkElement.target = '_blank';
|
25005
|
+
|
25000
25006
|
const nameElement = document.createElement('h2');
|
25001
25007
|
nameElement.innerText = file.filename;
|
25002
25008
|
nameElement.classList.add('name');
|
@@ -25005,6 +25011,7 @@
|
|
25005
25011
|
linksElement.classList.add('links');
|
25006
25012
|
linksElement.appendChild(viewLinkElement);
|
25007
25013
|
linksElement.appendChild(runLinkElement);
|
25014
|
+
linksElement.appendChild(viewOnGithubLinkElement);
|
25008
25015
|
|
25009
25016
|
const descriptionElement = document.createElement('p');
|
25010
25017
|
descriptionElement.innerText = file.description;
|
metadata
CHANGED
@@ -1,15 +1,29 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: sqlui
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.70
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Nick Dower
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2023-04-
|
11
|
+
date: 2023-04-03 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: activesupport
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '7.0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '7.0'
|
13
27
|
- !ruby/object:Gem::Dependency
|
14
28
|
name: airbrake
|
15
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -24,6 +38,20 @@ dependencies:
|
|
24
38
|
- - "~>"
|
25
39
|
- !ruby/object:Gem::Version
|
26
40
|
version: '13.0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: concurrent-ruby
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '1.0'
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '1.0'
|
27
55
|
- !ruby/object:Gem::Dependency
|
28
56
|
name: mysql2
|
29
57
|
requirement: !ruby/object:Gem::Requirement
|
@@ -52,6 +80,20 @@ dependencies:
|
|
52
80
|
- - "~>"
|
53
81
|
- !ruby/object:Gem::Version
|
54
82
|
version: '4.0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: rest-client
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - "~>"
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '2.0'
|
90
|
+
type: :runtime
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - "~>"
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '2.0'
|
55
97
|
- !ruby/object:Gem::Dependency
|
56
98
|
name: sinatra
|
57
99
|
requirement: !ruby/object:Gem::Requirement
|
@@ -80,6 +122,20 @@ dependencies:
|
|
80
122
|
- - "~>"
|
81
123
|
- !ruby/object:Gem::Version
|
82
124
|
version: '1.0'
|
125
|
+
- !ruby/object:Gem::Dependency
|
126
|
+
name: pry
|
127
|
+
requirement: !ruby/object:Gem::Requirement
|
128
|
+
requirements:
|
129
|
+
- - "~>"
|
130
|
+
- !ruby/object:Gem::Version
|
131
|
+
version: '0.0'
|
132
|
+
type: :development
|
133
|
+
prerelease: false
|
134
|
+
version_requirements: !ruby/object:Gem::Requirement
|
135
|
+
requirements:
|
136
|
+
- - "~>"
|
137
|
+
- !ruby/object:Gem::Version
|
138
|
+
version: '0.0'
|
83
139
|
- !ruby/object:Gem::Dependency
|
84
140
|
name: rspec-core
|
85
141
|
requirement: !ruby/object:Gem::Requirement
|
@@ -150,6 +206,20 @@ dependencies:
|
|
150
206
|
- - "~>"
|
151
207
|
- !ruby/object:Gem::Version
|
152
208
|
version: '4.0'
|
209
|
+
- !ruby/object:Gem::Dependency
|
210
|
+
name: webmock
|
211
|
+
requirement: !ruby/object:Gem::Requirement
|
212
|
+
requirements:
|
213
|
+
- - "~>"
|
214
|
+
- !ruby/object:Gem::Version
|
215
|
+
version: '3.0'
|
216
|
+
type: :development
|
217
|
+
prerelease: false
|
218
|
+
version_requirements: !ruby/object:Gem::Requirement
|
219
|
+
requirements:
|
220
|
+
- - "~>"
|
221
|
+
- !ruby/object:Gem::Version
|
222
|
+
version: '3.0'
|
153
223
|
description: A SQL UI.
|
154
224
|
email: nicholasdower@gmail.com
|
155
225
|
executables:
|
@@ -159,10 +229,20 @@ extra_rdoc_files: []
|
|
159
229
|
files:
|
160
230
|
- ".release-version"
|
161
231
|
- app/args.rb
|
232
|
+
- app/checks.rb
|
233
|
+
- app/count_down_latch.rb
|
162
234
|
- app/database_config.rb
|
163
235
|
- app/database_metadata.rb
|
164
236
|
- app/deep.rb
|
237
|
+
- app/github/cache.rb
|
238
|
+
- app/github/caching_client.rb
|
239
|
+
- app/github/client.rb
|
240
|
+
- app/github/file.rb
|
241
|
+
- app/github/paths.rb
|
242
|
+
- app/github/tree.rb
|
243
|
+
- app/github/tree_client.rb
|
165
244
|
- app/mysql_types.rb
|
245
|
+
- app/saved_config.rb
|
166
246
|
- app/server.rb
|
167
247
|
- app/sql_parser.rb
|
168
248
|
- app/sqlui.rb
|