blazer 2.6.5 → 3.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +12 -0
- data/LICENSE.txt +1 -1
- data/README.md +13 -28
- data/app/assets/javascripts/blazer/ace/ace.js +7235 -8906
- data/app/assets/javascripts/blazer/ace/ext-language_tools.js +762 -774
- data/app/assets/javascripts/blazer/ace/mode-sql.js +177 -72
- data/app/assets/javascripts/blazer/ace/snippets/sql.js +5 -29
- data/app/assets/javascripts/blazer/ace/snippets/text.js +1 -6
- data/app/assets/javascripts/blazer/ace/theme-twilight.js +8 -106
- data/app/assets/javascripts/blazer/application.js +9 -6
- data/app/assets/javascripts/blazer/chart.umd.js +13 -0
- data/app/assets/javascripts/blazer/chartjs-adapter-date-fns.bundle.js +6322 -0
- data/app/assets/javascripts/blazer/chartkick.js +1020 -914
- data/app/assets/javascripts/blazer/highlight.min.js +466 -3
- data/app/assets/javascripts/blazer/mapkick.bundle.js +1029 -0
- data/app/assets/javascripts/blazer/moment-timezone-with-data.js +39 -38
- data/app/assets/javascripts/blazer/moment.js +105 -88
- data/app/assets/javascripts/blazer/queries.js +10 -1
- data/app/assets/javascripts/blazer/rails-ujs.js +746 -0
- data/app/assets/javascripts/blazer/vue.global.prod.js +1 -0
- data/app/assets/stylesheets/blazer/bootstrap.css +1 -1
- data/app/assets/stylesheets/blazer/selectize.css +1 -1
- data/app/controllers/blazer/base_controller.rb +85 -84
- data/app/controllers/blazer/checks_controller.rb +6 -6
- data/app/controllers/blazer/dashboards_controller.rb +24 -24
- data/app/controllers/blazer/queries_controller.rb +208 -186
- data/app/controllers/blazer/uploads_controller.rb +49 -49
- data/app/helpers/blazer/base_helper.rb +0 -4
- data/app/models/blazer/query.rb +1 -12
- data/app/views/blazer/checks/index.html.erb +1 -1
- data/app/views/blazer/dashboards/_form.html.erb +11 -5
- data/app/views/blazer/queries/_form.html.erb +19 -14
- data/app/views/blazer/queries/docs.html.erb +11 -1
- data/app/views/blazer/queries/home.html.erb +9 -6
- data/app/views/blazer/queries/run.html.erb +17 -32
- data/app/views/blazer/queries/show.html.erb +12 -20
- data/app/views/layouts/blazer/application.html.erb +1 -5
- data/lib/blazer/adapters/sql_adapter.rb +1 -1
- data/lib/blazer/adapters.rb +17 -0
- data/lib/blazer/anomaly_detectors.rb +22 -0
- data/lib/blazer/data_source.rb +29 -40
- data/lib/blazer/engine.rb +11 -9
- data/lib/blazer/forecasters.rb +7 -0
- data/lib/blazer/result.rb +13 -71
- data/lib/blazer/result_cache.rb +71 -0
- data/lib/blazer/run_statement.rb +1 -1
- data/lib/blazer/run_statement_job.rb +2 -2
- data/lib/blazer/statement.rb +3 -1
- data/lib/blazer/version.rb +1 -1
- data/lib/blazer.rb +51 -53
- data/licenses/LICENSE-chart.js.txt +1 -1
- data/licenses/LICENSE-chartjs-adapter-date-fns.txt +9 -0
- data/licenses/LICENSE-chartkick.js.txt +1 -1
- data/licenses/LICENSE-date-fns.txt +21 -0
- data/licenses/LICENSE-kurkle-color.txt +9 -0
- data/licenses/LICENSE-mapkick-bundle.txt +1029 -0
- data/licenses/{LICENSE-jquery-ujs.txt → LICENSE-rails-ujs.txt} +1 -1
- data/licenses/LICENSE-vue.txt +1 -1
- metadata +26 -18
- data/app/assets/javascripts/blazer/Chart.js +0 -16172
- data/app/assets/javascripts/blazer/jquery-ujs.js +0 -555
- data/app/assets/javascripts/blazer/vue.js +0 -12014
- data/lib/blazer/adapters/mongodb_adapter.rb +0 -43
- data/lib/blazer/detect_anomalies.R +0 -19
data/lib/blazer/result.rb
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
module Blazer
|
2
2
|
class Result
|
3
|
-
attr_reader :data_source, :columns, :rows, :error, :
|
3
|
+
attr_reader :data_source, :columns, :rows, :error, :forecast_error
|
4
|
+
attr_accessor :cached_at, :just_cached
|
4
5
|
|
5
6
|
def initialize(data_source, columns, rows, error, cached_at, just_cached)
|
6
7
|
@data_source = data_source
|
@@ -19,9 +20,9 @@ module Blazer
|
|
19
20
|
cached_at.present?
|
20
21
|
end
|
21
22
|
|
22
|
-
def
|
23
|
-
@
|
24
|
-
|
23
|
+
def smart_values
|
24
|
+
@smart_values ||= begin
|
25
|
+
smart_values = {}
|
25
26
|
columns.each_with_index do |key, i|
|
26
27
|
smart_columns_data_source =
|
27
28
|
([data_source] + Array(data_source.settings["inherit_smart_settings"]).map { |ds| Blazer.data_sources[ds] }).find { |ds| ds.smart_columns[key] }
|
@@ -37,10 +38,10 @@ module Blazer
|
|
37
38
|
result.rows
|
38
39
|
end
|
39
40
|
|
40
|
-
|
41
|
+
smart_values[key] = res.to_h { |k, v| [k.nil? ? k : k.to_s, v] }
|
41
42
|
end
|
42
43
|
end
|
43
|
-
|
44
|
+
smart_values
|
44
45
|
end
|
45
46
|
end
|
46
47
|
|
@@ -48,7 +49,7 @@ module Blazer
|
|
48
49
|
@column_types ||= begin
|
49
50
|
columns.each_with_index.map do |k, i|
|
50
51
|
v = (rows.find { |r| r[i] } || {})[i]
|
51
|
-
if
|
52
|
+
if smart_values[k]
|
52
53
|
"string"
|
53
54
|
elsif v.is_a?(Numeric)
|
54
55
|
"numeric"
|
@@ -93,14 +94,8 @@ module Blazer
|
|
93
94
|
def forecast
|
94
95
|
count = (@rows.size * 0.25).round.clamp(30, 365)
|
95
96
|
|
96
|
-
|
97
|
-
|
98
|
-
require "prophet"
|
99
|
-
forecast = Prophet.forecast(@rows.to_h, count: count)
|
100
|
-
else
|
101
|
-
require "trend"
|
102
|
-
forecast = Trend.forecast(@rows.to_h, count: count)
|
103
|
-
end
|
97
|
+
forecaster = Blazer.forecasters.fetch(Blazer.forecasting)
|
98
|
+
forecast = forecaster.call(@rows.to_h, count: count)
|
104
99
|
|
105
100
|
# round integers
|
106
101
|
if @rows[0][1].is_a?(Integer)
|
@@ -139,7 +134,7 @@ module Blazer
|
|
139
134
|
series << {name: k, data: rows.map{ |r| [r[0], r[i + 1]] }}
|
140
135
|
end
|
141
136
|
else
|
142
|
-
rows.group_by { |r| v = r[1]; (
|
137
|
+
rows.group_by { |r| v = r[1]; (smart_values[columns[1]] || {})[v.to_s] || v }.each_with_index.map do |(name, v), i|
|
143
138
|
series << {name: name, data: v.map { |v2| [v2[0], v2[2]] }}
|
144
139
|
end
|
145
140
|
end
|
@@ -176,61 +171,8 @@ module Blazer
|
|
176
171
|
def anomaly?(series)
|
177
172
|
series = series.reject { |v| v[0].nil? }.sort_by { |v| v[0] }
|
178
173
|
|
179
|
-
|
180
|
-
|
181
|
-
df = Rover::DataFrame.new(series[0..-2].map { |v| {"ds" => v[0], "y" => v[1]} })
|
182
|
-
m = Prophet.new(interval_width: 0.99)
|
183
|
-
m.logger.level = ::Logger::FATAL # no logging
|
184
|
-
m.fit(df)
|
185
|
-
future = Rover::DataFrame.new(series[-1..-1].map { |v| {"ds" => v[0]} })
|
186
|
-
forecast = m.predict(future).to_a[0]
|
187
|
-
lower = forecast["yhat_lower"]
|
188
|
-
upper = forecast["yhat_upper"]
|
189
|
-
value = series.last[1]
|
190
|
-
value < lower || value > upper
|
191
|
-
when "trend"
|
192
|
-
anomalies = Trend.anomalies(Hash[series])
|
193
|
-
anomalies.include?(series.last[0])
|
194
|
-
when "anomaly_detection"
|
195
|
-
period = 7 # TODO determine period
|
196
|
-
anomalies = AnomalyDetection.detect(Hash[series], period: period)
|
197
|
-
anomalies.include?(series.last[0])
|
198
|
-
else
|
199
|
-
csv_str =
|
200
|
-
CSV.generate do |csv|
|
201
|
-
csv << ["timestamp", "count"]
|
202
|
-
series.each do |row|
|
203
|
-
csv << row
|
204
|
-
end
|
205
|
-
end
|
206
|
-
|
207
|
-
r_script = %x[which Rscript].chomp
|
208
|
-
type = series.any? && series.last.first.to_time - series.first.first.to_time >= 2.weeks ? "ts" : "vec"
|
209
|
-
args = [type, csv_str]
|
210
|
-
raise "R not found" if r_script.empty?
|
211
|
-
command = "#{r_script} --vanilla #{File.expand_path("../detect_anomalies.R", __FILE__)} #{args.map { |a| Shellwords.escape(a) }.join(" ")}"
|
212
|
-
output = %x[#{command}]
|
213
|
-
if output.empty?
|
214
|
-
raise "Unknown R error"
|
215
|
-
end
|
216
|
-
|
217
|
-
rows = CSV.parse(output, headers: true)
|
218
|
-
error = rows.first && rows.first["x"]
|
219
|
-
raise error if error
|
220
|
-
|
221
|
-
timestamps = []
|
222
|
-
if type == "ts"
|
223
|
-
rows.each do |row|
|
224
|
-
timestamps << Time.parse(row["timestamp"])
|
225
|
-
end
|
226
|
-
timestamps.include?(series.last[0].to_time)
|
227
|
-
else
|
228
|
-
rows.each do |row|
|
229
|
-
timestamps << row["index"].to_i
|
230
|
-
end
|
231
|
-
timestamps.include?(series.length)
|
232
|
-
end
|
233
|
-
end
|
174
|
+
anomaly_detector = Blazer.anomaly_detectors.fetch(Blazer.anomaly_checks)
|
175
|
+
anomaly_detector.call(series)
|
234
176
|
end
|
235
177
|
end
|
236
178
|
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
module Blazer
|
2
|
+
class ResultCache
|
3
|
+
def initialize(data_source)
|
4
|
+
@data_source = data_source
|
5
|
+
end
|
6
|
+
|
7
|
+
def write_run(run_id, result)
|
8
|
+
write(run_cache_key(run_id), result, expires_in: 30.seconds)
|
9
|
+
end
|
10
|
+
|
11
|
+
def read_run(run_id)
|
12
|
+
read(run_cache_key(run_id))
|
13
|
+
end
|
14
|
+
|
15
|
+
def delete_run(run_id)
|
16
|
+
delete(run_cache_key(run_id))
|
17
|
+
end
|
18
|
+
|
19
|
+
def write_statement(statement, result, expires_in:)
|
20
|
+
write(statement_cache_key(statement), result, expires_in: expires_in) if caching?
|
21
|
+
end
|
22
|
+
|
23
|
+
def read_statement(statement)
|
24
|
+
read(statement_cache_key(statement)) if caching?
|
25
|
+
end
|
26
|
+
|
27
|
+
def delete_statement(statement)
|
28
|
+
delete(statement_cache_key(statement)) if caching?
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
def write(key, result, expires_in:)
|
34
|
+
raise ArgumentError, "expected Blazer::Result" unless result.is_a?(Blazer::Result)
|
35
|
+
value = [result.columns, result.rows, result.error, result.cached_at, result.just_cached]
|
36
|
+
cache.write(key, value, expires_in: expires_in)
|
37
|
+
end
|
38
|
+
|
39
|
+
def read(key)
|
40
|
+
value = cache.read(key)
|
41
|
+
if value
|
42
|
+
columns, rows, error, cached_at, just_cached = value
|
43
|
+
Blazer::Result.new(@data_source, columns, rows, error, cached_at, just_cached)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def delete(key)
|
48
|
+
cache.delete(key)
|
49
|
+
end
|
50
|
+
|
51
|
+
def caching?
|
52
|
+
@data_source.cache_mode != "off"
|
53
|
+
end
|
54
|
+
|
55
|
+
def cache_key(key)
|
56
|
+
(["blazer", "v5", @data_source.id] + key).join("/")
|
57
|
+
end
|
58
|
+
|
59
|
+
def statement_cache_key(statement)
|
60
|
+
cache_key(["statement", Digest::SHA256.hexdigest(statement.bind_statement.to_s.gsub("\r\n", "\n") + statement.bind_values.to_json)])
|
61
|
+
end
|
62
|
+
|
63
|
+
def run_cache_key(run_id)
|
64
|
+
cache_key(["run", run_id])
|
65
|
+
end
|
66
|
+
|
67
|
+
def cache
|
68
|
+
Blazer.cache
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
data/lib/blazer/run_statement.rb
CHANGED
@@ -32,7 +32,7 @@ module Blazer
|
|
32
32
|
audit.save! if audit.changed?
|
33
33
|
end
|
34
34
|
|
35
|
-
if query && !result.timed_out? && !query.variables.any?
|
35
|
+
if query && !result.timed_out? && !result.cached? && !query.variables.any?
|
36
36
|
query.checks.each do |check|
|
37
37
|
check.update_state(result)
|
38
38
|
end
|
@@ -11,8 +11,8 @@ module Blazer
|
|
11
11
|
Blazer::RunStatement.new.perform(statement, options)
|
12
12
|
end
|
13
13
|
rescue Exception => e
|
14
|
-
Blazer::Result.new(data_source, [], [], "Unknown error", nil, false)
|
15
|
-
|
14
|
+
result = Blazer::Result.new(data_source, [], [], "Unknown error", nil, false)
|
15
|
+
data_source.result_cache.write_run(options[:run_id], result)
|
16
16
|
raise e
|
17
17
|
end
|
18
18
|
end
|
data/lib/blazer/statement.rb
CHANGED
@@ -10,7 +10,9 @@ module Blazer
|
|
10
10
|
end
|
11
11
|
|
12
12
|
def variables
|
13
|
-
|
13
|
+
# strip commented out lines
|
14
|
+
# and regex {1} or {1,2}
|
15
|
+
@variables ||= statement.to_s.gsub(/\-\-.+/, "").gsub(/\/\*.+\*\//m, "").scan(/\{\w*?\}/i).map { |v| v[1...-1] }.reject { |v| /\A\d+(\,\d+)?\z/.match(v) || v.empty? }.uniq
|
14
16
|
end
|
15
17
|
|
16
18
|
def add_values(var_params)
|
data/lib/blazer/version.rb
CHANGED
data/lib/blazer.rb
CHANGED
@@ -4,39 +4,40 @@ require "safely/core"
|
|
4
4
|
|
5
5
|
# stdlib
|
6
6
|
require "csv"
|
7
|
+
require "digest/sha2"
|
7
8
|
require "json"
|
8
9
|
require "yaml"
|
9
10
|
|
10
11
|
# modules
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
12
|
+
require_relative "blazer/version"
|
13
|
+
require_relative "blazer/data_source"
|
14
|
+
require_relative "blazer/result"
|
15
|
+
require_relative "blazer/result_cache"
|
16
|
+
require_relative "blazer/run_statement"
|
17
|
+
require_relative "blazer/statement"
|
16
18
|
|
17
19
|
# adapters
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
require "blazer/adapters/snowflake_adapter"
|
20
|
+
require_relative "blazer/adapters/base_adapter"
|
21
|
+
require_relative "blazer/adapters/athena_adapter"
|
22
|
+
require_relative "blazer/adapters/bigquery_adapter"
|
23
|
+
require_relative "blazer/adapters/cassandra_adapter"
|
24
|
+
require_relative "blazer/adapters/drill_adapter"
|
25
|
+
require_relative "blazer/adapters/druid_adapter"
|
26
|
+
require_relative "blazer/adapters/elasticsearch_adapter"
|
27
|
+
require_relative "blazer/adapters/hive_adapter"
|
28
|
+
require_relative "blazer/adapters/ignite_adapter"
|
29
|
+
require_relative "blazer/adapters/influxdb_adapter"
|
30
|
+
require_relative "blazer/adapters/neo4j_adapter"
|
31
|
+
require_relative "blazer/adapters/opensearch_adapter"
|
32
|
+
require_relative "blazer/adapters/presto_adapter"
|
33
|
+
require_relative "blazer/adapters/salesforce_adapter"
|
34
|
+
require_relative "blazer/adapters/soda_adapter"
|
35
|
+
require_relative "blazer/adapters/spark_adapter"
|
36
|
+
require_relative "blazer/adapters/sql_adapter"
|
37
|
+
require_relative "blazer/adapters/snowflake_adapter"
|
37
38
|
|
38
39
|
# engine
|
39
|
-
|
40
|
+
require_relative "blazer/engine"
|
40
41
|
|
41
42
|
module Blazer
|
42
43
|
class Error < StandardError; end
|
@@ -66,8 +67,6 @@ module Blazer
|
|
66
67
|
attr_accessor :forecasting
|
67
68
|
attr_accessor :async
|
68
69
|
attr_accessor :images
|
69
|
-
attr_accessor :query_viewable
|
70
|
-
attr_accessor :query_editable
|
71
70
|
attr_accessor :override_csp
|
72
71
|
attr_accessor :slack_oauth_token
|
73
72
|
attr_accessor :slack_webhook_url
|
@@ -118,7 +117,7 @@ module Blazer
|
|
118
117
|
@settings ||= begin
|
119
118
|
path = Rails.root.join("config", "blazer.yml").to_s
|
120
119
|
if File.exist?(path)
|
121
|
-
YAML.
|
120
|
+
YAML.safe_load(ERB.new(File.read(path)).result, aliases: true)
|
122
121
|
else
|
123
122
|
{}
|
124
123
|
end
|
@@ -135,13 +134,6 @@ module Blazer
|
|
135
134
|
end
|
136
135
|
end
|
137
136
|
|
138
|
-
# TODO move to Statement and remove in 3.0.0
|
139
|
-
def self.extract_vars(statement)
|
140
|
-
# strip commented out lines
|
141
|
-
# and regex {1} or {1,2}
|
142
|
-
statement.to_s.gsub(/\-\-.+/, "").gsub(/\/\*.+\*\//m, "").scan(/\{\w*?\}/i).map { |v| v[1...-1] }.reject { |v| /\A\d+(\,\d+)?\z/.match(v) || v.empty? }.uniq
|
143
|
-
end
|
144
|
-
|
145
137
|
def self.run_checks(schedule: nil)
|
146
138
|
checks = Blazer::Check.includes(:query)
|
147
139
|
checks = checks.where(schedule: schedule) if schedule
|
@@ -225,6 +217,11 @@ module Blazer
|
|
225
217
|
slack_oauth_token.present? || slack_webhook_url.present?
|
226
218
|
end
|
227
219
|
|
220
|
+
# TODO show warning on invalid access token
|
221
|
+
def self.maps?
|
222
|
+
mapbox_access_token.present? && mapbox_access_token.start_with?("pk.")
|
223
|
+
end
|
224
|
+
|
228
225
|
def self.uploads?
|
229
226
|
settings.key?("uploads")
|
230
227
|
end
|
@@ -250,6 +247,22 @@ module Blazer
|
|
250
247
|
adapters[name] = adapter
|
251
248
|
end
|
252
249
|
|
250
|
+
def self.anomaly_detectors
|
251
|
+
@anomaly_detectors ||= {}
|
252
|
+
end
|
253
|
+
|
254
|
+
def self.register_anomaly_detector(name, &anomaly_detector)
|
255
|
+
anomaly_detectors[name] = anomaly_detector
|
256
|
+
end
|
257
|
+
|
258
|
+
def self.forecasters
|
259
|
+
@forecasters ||= {}
|
260
|
+
end
|
261
|
+
|
262
|
+
def self.register_forecaster(name, &forecaster)
|
263
|
+
forecasters[name] = forecaster
|
264
|
+
end
|
265
|
+
|
253
266
|
def self.archive_queries
|
254
267
|
raise "Audits must be enabled to archive" unless Blazer.audit
|
255
268
|
raise "Missing status column - see https://github.com/ankane/blazer#23" unless Blazer::Query.column_names.include?("status")
|
@@ -264,21 +277,6 @@ module Blazer
|
|
264
277
|
end
|
265
278
|
end
|
266
279
|
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
Blazer.register_adapter "drill", Blazer::Adapters::DrillAdapter
|
271
|
-
Blazer.register_adapter "druid", Blazer::Adapters::DruidAdapter
|
272
|
-
Blazer.register_adapter "elasticsearch", Blazer::Adapters::ElasticsearchAdapter
|
273
|
-
Blazer.register_adapter "hive", Blazer::Adapters::HiveAdapter
|
274
|
-
Blazer.register_adapter "ignite", Blazer::Adapters::IgniteAdapter
|
275
|
-
Blazer.register_adapter "influxdb", Blazer::Adapters::InfluxdbAdapter
|
276
|
-
Blazer.register_adapter "mongodb", Blazer::Adapters::MongodbAdapter
|
277
|
-
Blazer.register_adapter "neo4j", Blazer::Adapters::Neo4jAdapter
|
278
|
-
Blazer.register_adapter "opensearch", Blazer::Adapters::OpensearchAdapter
|
279
|
-
Blazer.register_adapter "presto", Blazer::Adapters::PrestoAdapter
|
280
|
-
Blazer.register_adapter "salesforce", Blazer::Adapters::SalesforceAdapter
|
281
|
-
Blazer.register_adapter "soda", Blazer::Adapters::SodaAdapter
|
282
|
-
Blazer.register_adapter "spark", Blazer::Adapters::SparkAdapter
|
283
|
-
Blazer.register_adapter "sql", Blazer::Adapters::SqlAdapter
|
284
|
-
Blazer.register_adapter "snowflake", Blazer::Adapters::SnowflakeAdapter
|
280
|
+
require_relative "blazer/adapters"
|
281
|
+
require_relative "blazer/anomaly_detectors"
|
282
|
+
require_relative "blazer/forecasters"
|
@@ -1,6 +1,6 @@
|
|
1
1
|
The MIT License (MIT)
|
2
2
|
|
3
|
-
Copyright (c)
|
3
|
+
Copyright (c) 2014-2022 Chart.js Contributors
|
4
4
|
|
5
5
|
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
6
6
|
|
@@ -0,0 +1,9 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2019 Chart.js Contributors
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
6
|
+
|
7
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
8
|
+
|
9
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
@@ -0,0 +1,21 @@
|
|
1
|
+
MIT License
|
2
|
+
|
3
|
+
Copyright (c) 2021 Sasha Koss and Lesha Koss https://kossnocorp.mit-license.org
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
13
|
+
copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
21
|
+
SOFTWARE.
|
@@ -0,0 +1,9 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2018-2021 Jukka Kurkela
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
6
|
+
|
7
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
8
|
+
|
9
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|