hieraviz 0.0.1 → 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +5 -0
- data/README.md +4 -4
- data/app/apiv1.rb +34 -0
- data/app/common.rb +2 -8
- data/app/main.rb +3 -0
- data/app/public/css/main.css +46 -2
- data/app/public/js/farms.js +3 -2
- data/app/public/js/fetch.js +381 -0
- data/app/public/js/main.js +24 -1
- data/app/public/js/nodes.js +25 -13
- data/app/views/_foot.erb +3 -1
- data/app/views/_head.erb +9 -0
- data/app/views/_layout.erb +5 -0
- data/app/views/data.erb +4 -0
- data/app/views/farms.erb +1 -1
- data/app/views/home.erb +6 -1
- data/app/views/modules.erb +4 -3
- data/app/views/nodes.erb +1 -1
- data/app/views/resources.erb +4 -4
- data/app/views/store.erb +6 -0
- data/app/web.rb +91 -18
- data/lib/hieraviz/auth_gitlab.rb +59 -0
- data/lib/hieraviz/config.rb +20 -0
- data/lib/hieraviz/store.rb +43 -6
- data/lib/hieraviz.rb +2 -0
- data/spec/app/apiv1_spec.rb +7 -4
- data/spec/app/web_spec.rb +1 -0
- data/spec/files/config.yml +10 -0
- data/spec/lib/auth_gitlab_spec.rb +26 -0
- data/spec/lib/config_spec.rb +20 -0
- data/spec/lib/store_spec.rb +127 -0
- data/spec/sinatra_helper.rb +10 -0
- data/spec/spec_helper.rb +20 -8
- metadata +57 -4
- data/app/config/hieraviz.default.yml +0 -13
- data/app/config/hieraviz.yml +0 -13
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 7943ca0a1cce06662064ff153e6574e667a9aebe
|
4
|
+
data.tar.gz: de8240ae22c0afff43b068cf4af11fc84a3247a9
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 7e5839350259c94f8ed16c922570e507647bc89d0cc6dd478a37763ed5bdb2601317613a13244295e8f876fa7123e0a07e6f8c55e7164e2139cacea54dda6fc7
|
7
|
+
data.tar.gz: fc6cd1e5c0a66630be9b33268234e5ee234778e35c6b15aba708e6d6061b5b0e8fab71203a6a0f93fa0b5e6483b8b81e3acce86de3f2fcd40242bfefe8ea4027
|
data/CHANGELOG.md
CHANGED
@@ -1,6 +1,11 @@
|
|
1
1
|
Hieraviz Changelog
|
2
2
|
========================
|
3
3
|
|
4
|
+
### v0.0.2 - 2016-01-18
|
5
|
+
- add oauth2 auth method against gitlab server
|
6
|
+
- add session persistence on disk
|
7
|
+
- protect api calls with usage of the web session
|
8
|
+
|
4
9
|
### v0.0.1 - 2015-12-30
|
5
10
|
- still pretty alpha at this stage, more to come soon
|
6
11
|
- made basic auth work (with logout too)
|
data/README.md
CHANGED
@@ -4,7 +4,7 @@ Hieraviz
|
|
4
4
|
[![Gem Version](https://img.shields.io/gem/v/hieraviz.svg)](http://rubygems.org/gems/hieraviz)
|
5
5
|
[![Downloads](http://img.shields.io/gem/dt/hieraviz.svg)](https://rubygems.org/gems/hieraviz)
|
6
6
|
[![Build Status](https://img.shields.io/travis/Gandi/hieraviz.svg)](https://travis-ci.org/Gandi/hieraviz)
|
7
|
-
[![Coverage Status](https://img.shields.io/coveralls/Gandi/hieraviz.svg)](https://coveralls.io/github/Gandi/hieraviz
|
7
|
+
[![Coverage Status](https://img.shields.io/coveralls/Gandi/hieraviz.svg)](https://coveralls.io/github/Gandi/hieraviz)
|
8
8
|
[![Dependency Status](https://gemnasium.com/Gandi/hieraviz.svg)](https://gemnasium.com/Gandi/hieraviz)
|
9
9
|
[![Code Climate](https://img.shields.io/codeclimate/github/Gandi/hieraviz.svg)](https://codeclimate.com/github/Gandi/hieraviz)
|
10
10
|
|
@@ -19,9 +19,9 @@ Install it from Rubygens:
|
|
19
19
|
|
20
20
|
gem install hieraviz
|
21
21
|
|
22
|
-
|
23
|
-
|
24
|
-
|
22
|
+
Development
|
23
|
+
--------------
|
24
|
+
Launch it with `shotgun` or `rackup`.
|
25
25
|
|
26
26
|
Contributing
|
27
27
|
----------------
|
data/app/apiv1.rb
CHANGED
@@ -12,44 +12,78 @@ require File.expand_path '../common.rb', __FILE__
|
|
12
12
|
module HieravizApp
|
13
13
|
class ApiV1 < Common
|
14
14
|
|
15
|
+
configure do
|
16
|
+
set :session_secret, settings.configdata['session_seed']
|
17
|
+
enable :sessions
|
18
|
+
end
|
19
|
+
|
20
|
+
helpers do
|
21
|
+
def check_authorization
|
22
|
+
if !session['access_token'] && !request.env['HTTP_X_AUTH']
|
23
|
+
redirect '/v1/not_logged'
|
24
|
+
else
|
25
|
+
token = session['access_token'] || request.env['HTTP_X_AUTH']
|
26
|
+
session_info = Hieraviz::Store.get(token, settings.configdata['session_renew'])
|
27
|
+
if !session_info
|
28
|
+
redirect '/v1/unauthorized'
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
15
34
|
get '/test' do
|
16
35
|
json data: Time.new
|
17
36
|
end
|
18
37
|
|
19
38
|
get '/nodes' do
|
39
|
+
check_authorization
|
20
40
|
json Hieracles::Registry.nodes(settings.config)
|
21
41
|
end
|
22
42
|
|
23
43
|
get '/node/:n/info' do |node|
|
44
|
+
check_authorization
|
24
45
|
node = Hieracles::Node.new(node, settings.config)
|
25
46
|
json node.info
|
26
47
|
end
|
27
48
|
|
28
49
|
get '/node/:n/params' do |node|
|
50
|
+
check_authorization
|
29
51
|
node = Hieracles::Node.new(node, settings.config)
|
30
52
|
json node.params
|
31
53
|
end
|
32
54
|
|
33
55
|
get '/node/:n/allparams' do |node|
|
56
|
+
check_authorization
|
34
57
|
node = Hieracles::Node.new(node, settings.config)
|
35
58
|
json node.params(false)
|
36
59
|
end
|
37
60
|
|
38
61
|
get '/node/:n' do |node|
|
62
|
+
check_authorization
|
39
63
|
node = Hieracles::Node.new(node, settings.config)
|
40
64
|
json node.params
|
41
65
|
end
|
42
66
|
|
43
67
|
get '/farms' do
|
68
|
+
check_authorization
|
44
69
|
json Hieracles::Registry.farms(settings.config)
|
45
70
|
end
|
46
71
|
|
47
72
|
get '/farm/:n' do |farm|
|
73
|
+
check_authorization
|
48
74
|
req = Hieracles::Puppetdb::Request.new(settings.configdata['puppetdb'])
|
49
75
|
farm_nodes = req.facts('farm', farm)
|
50
76
|
json farm_nodes.data
|
51
77
|
end
|
52
78
|
|
79
|
+
get '/not_logged' do
|
80
|
+
json({ error: "Not connected." })
|
81
|
+
end
|
82
|
+
|
83
|
+
get '/unauthorized' do
|
84
|
+
json({ error: "Unauthorized" })
|
85
|
+
end
|
86
|
+
|
53
87
|
not_found do
|
54
88
|
json({ error: "data not found" })
|
55
89
|
end
|
data/app/common.rb
CHANGED
@@ -1,21 +1,15 @@
|
|
1
1
|
require 'sinatra/base'
|
2
2
|
require 'dotenv'
|
3
|
-
require 'hieracles'
|
4
|
-
require 'hieraviz'
|
5
3
|
|
6
4
|
module HieravizApp
|
7
5
|
class Common < Sinatra::Base
|
8
6
|
|
9
7
|
configure do
|
10
8
|
set :app_name, 'HieraViz'
|
11
|
-
|
12
|
-
|
13
|
-
set :configfile, configfile
|
14
|
-
set :configdata, YAML.load_file(configfile)
|
15
|
-
set :config, Hieracles::Config.new({ config: configfile })
|
9
|
+
set :configdata, Hieraviz::Config.load
|
10
|
+
set :config, Hieracles::Config.new({ config: Hieraviz::Config.configfile })
|
16
11
|
enable :session
|
17
12
|
enable :logging
|
18
|
-
set :store, Hieraviz::Store.new
|
19
13
|
end
|
20
14
|
|
21
15
|
end
|
data/app/main.rb
CHANGED
data/app/public/css/main.css
CHANGED
@@ -80,6 +80,14 @@ input {
|
|
80
80
|
background-color: #fff;
|
81
81
|
color: #000;
|
82
82
|
}
|
83
|
+
.head .auth a#login {
|
84
|
+
background-color: #fff;
|
85
|
+
color: #000;
|
86
|
+
}
|
87
|
+
.head .auth .username {
|
88
|
+
display: inline-block;
|
89
|
+
margin-right: 1em;
|
90
|
+
}
|
83
91
|
/* ----- Content ---- */
|
84
92
|
.content {
|
85
93
|
background-color: #eee;
|
@@ -87,14 +95,20 @@ input {
|
|
87
95
|
top: 3em;
|
88
96
|
bottom: 8em;
|
89
97
|
width: 100%;
|
98
|
+
height: 90%;
|
90
99
|
}
|
91
|
-
|
92
|
-
|
100
|
+
.error {
|
101
|
+
padding: 2em;
|
102
|
+
}
|
103
|
+
/* ----- Sidebar ---- */
|
93
104
|
.side {
|
94
105
|
background-color: #eee;
|
95
106
|
display: inline-block;
|
96
107
|
float: left;
|
97
108
|
width: 330px;
|
109
|
+
height: 100%;
|
110
|
+
overflow-y: auto;
|
111
|
+
overflow-x: hidden;
|
98
112
|
}
|
99
113
|
.side .filter {
|
100
114
|
margin: 0;
|
@@ -113,6 +127,7 @@ input {
|
|
113
127
|
border-right: 1px solid #ccc;
|
114
128
|
}
|
115
129
|
.side ul li {
|
130
|
+
font-size: .9em;
|
116
131
|
padding: .1em 1em;
|
117
132
|
cursor: pointer;
|
118
133
|
border-bottom: 1px solid #ccc;
|
@@ -130,6 +145,8 @@ input {
|
|
130
145
|
background-color: #fff;
|
131
146
|
margin-left: 330px;
|
132
147
|
position: relative;
|
148
|
+
height: 100%;
|
149
|
+
overflow-y: auto;
|
133
150
|
}
|
134
151
|
.meat.text {
|
135
152
|
padding: 1em 2em;
|
@@ -239,3 +256,30 @@ input {
|
|
239
256
|
line-height: 1em;
|
240
257
|
width: 100%;
|
241
258
|
}
|
259
|
+
.foot .debug {
|
260
|
+
font-family: monospace;
|
261
|
+
font-size: .9em;
|
262
|
+
background-color: #333;
|
263
|
+
padding: .2em 1em;
|
264
|
+
float: right;
|
265
|
+
}
|
266
|
+
|
267
|
+
/* ------ flash messages ----- */
|
268
|
+
.flash {
|
269
|
+
position: relative;
|
270
|
+
padding: 1em;
|
271
|
+
cursor: pointer;
|
272
|
+
}
|
273
|
+
.flash.info {
|
274
|
+
background-color: #4d4;
|
275
|
+
color: #000;
|
276
|
+
}
|
277
|
+
.flash.warning {
|
278
|
+
background-color: #d63;
|
279
|
+
color: #fff;
|
280
|
+
}
|
281
|
+
.flash.fatal {
|
282
|
+
background-color: #a20;
|
283
|
+
color: #fff;
|
284
|
+
font-weight: bold;
|
285
|
+
}
|
data/app/public/js/farms.js
CHANGED
@@ -42,14 +42,15 @@ ready( () => {
|
|
42
42
|
item.addEventListener('click', (ev) => {
|
43
43
|
addClass(meat, 'wait');
|
44
44
|
el = ev.target;
|
45
|
-
fetch('/v1/farm/' + el.
|
45
|
+
fetch('/v1/farm/' + el.dataset.item, auth_header()).
|
46
46
|
then(res => res.json()).
|
47
47
|
then(j => {
|
48
|
-
build_list(meat, el.
|
48
|
+
build_list(meat, el.dataset.item, j);
|
49
49
|
Array.prototype.forEach.call(farms, (item, i) => {
|
50
50
|
removeClass(item, 'focus')
|
51
51
|
});
|
52
52
|
addClass(el, 'focus');
|
53
|
+
update_footer('/v1/farm/' + el.dataset.item);
|
53
54
|
removeClass(meat, 'wait');
|
54
55
|
});
|
55
56
|
});
|
@@ -0,0 +1,381 @@
|
|
1
|
+
(function() {
|
2
|
+
'use strict';
|
3
|
+
|
4
|
+
if (self.fetch) {
|
5
|
+
return
|
6
|
+
}
|
7
|
+
|
8
|
+
function normalizeName(name) {
|
9
|
+
if (typeof name !== 'string') {
|
10
|
+
name = String(name)
|
11
|
+
}
|
12
|
+
if (/[^a-z0-9\-#$%&'*+.\^_`|~]/i.test(name)) {
|
13
|
+
throw new TypeError('Invalid character in header field name')
|
14
|
+
}
|
15
|
+
return name.toLowerCase()
|
16
|
+
}
|
17
|
+
|
18
|
+
function normalizeValue(value) {
|
19
|
+
if (typeof value !== 'string') {
|
20
|
+
value = String(value)
|
21
|
+
}
|
22
|
+
return value
|
23
|
+
}
|
24
|
+
|
25
|
+
function Headers(headers) {
|
26
|
+
this.map = {}
|
27
|
+
|
28
|
+
if (headers instanceof Headers) {
|
29
|
+
headers.forEach(function(value, name) {
|
30
|
+
this.append(name, value)
|
31
|
+
}, this)
|
32
|
+
|
33
|
+
} else if (headers) {
|
34
|
+
Object.getOwnPropertyNames(headers).forEach(function(name) {
|
35
|
+
this.append(name, headers[name])
|
36
|
+
}, this)
|
37
|
+
}
|
38
|
+
}
|
39
|
+
|
40
|
+
Headers.prototype.append = function(name, value) {
|
41
|
+
name = normalizeName(name)
|
42
|
+
value = normalizeValue(value)
|
43
|
+
var list = this.map[name]
|
44
|
+
if (!list) {
|
45
|
+
list = []
|
46
|
+
this.map[name] = list
|
47
|
+
}
|
48
|
+
list.push(value)
|
49
|
+
}
|
50
|
+
|
51
|
+
Headers.prototype['delete'] = function(name) {
|
52
|
+
delete this.map[normalizeName(name)]
|
53
|
+
}
|
54
|
+
|
55
|
+
Headers.prototype.get = function(name) {
|
56
|
+
var values = this.map[normalizeName(name)]
|
57
|
+
return values ? values[0] : null
|
58
|
+
}
|
59
|
+
|
60
|
+
Headers.prototype.getAll = function(name) {
|
61
|
+
return this.map[normalizeName(name)] || []
|
62
|
+
}
|
63
|
+
|
64
|
+
Headers.prototype.has = function(name) {
|
65
|
+
return this.map.hasOwnProperty(normalizeName(name))
|
66
|
+
}
|
67
|
+
|
68
|
+
Headers.prototype.set = function(name, value) {
|
69
|
+
this.map[normalizeName(name)] = [normalizeValue(value)]
|
70
|
+
}
|
71
|
+
|
72
|
+
Headers.prototype.forEach = function(callback, thisArg) {
|
73
|
+
Object.getOwnPropertyNames(this.map).forEach(function(name) {
|
74
|
+
this.map[name].forEach(function(value) {
|
75
|
+
callback.call(thisArg, value, name, this)
|
76
|
+
}, this)
|
77
|
+
}, this)
|
78
|
+
}
|
79
|
+
|
80
|
+
function consumed(body) {
|
81
|
+
if (body.bodyUsed) {
|
82
|
+
return Promise.reject(new TypeError('Already read'))
|
83
|
+
}
|
84
|
+
body.bodyUsed = true
|
85
|
+
}
|
86
|
+
|
87
|
+
function fileReaderReady(reader) {
|
88
|
+
return new Promise(function(resolve, reject) {
|
89
|
+
reader.onload = function() {
|
90
|
+
resolve(reader.result)
|
91
|
+
}
|
92
|
+
reader.onerror = function() {
|
93
|
+
reject(reader.error)
|
94
|
+
}
|
95
|
+
})
|
96
|
+
}
|
97
|
+
|
98
|
+
function readBlobAsArrayBuffer(blob) {
|
99
|
+
var reader = new FileReader()
|
100
|
+
reader.readAsArrayBuffer(blob)
|
101
|
+
return fileReaderReady(reader)
|
102
|
+
}
|
103
|
+
|
104
|
+
function readBlobAsText(blob) {
|
105
|
+
var reader = new FileReader()
|
106
|
+
reader.readAsText(blob)
|
107
|
+
return fileReaderReady(reader)
|
108
|
+
}
|
109
|
+
|
110
|
+
var support = {
|
111
|
+
blob: 'FileReader' in self && 'Blob' in self && (function() {
|
112
|
+
try {
|
113
|
+
new Blob();
|
114
|
+
return true
|
115
|
+
} catch(e) {
|
116
|
+
return false
|
117
|
+
}
|
118
|
+
})(),
|
119
|
+
formData: 'FormData' in self,
|
120
|
+
arrayBuffer: 'ArrayBuffer' in self
|
121
|
+
}
|
122
|
+
|
123
|
+
function Body() {
|
124
|
+
this.bodyUsed = false
|
125
|
+
|
126
|
+
|
127
|
+
this._initBody = function(body) {
|
128
|
+
this._bodyInit = body
|
129
|
+
if (typeof body === 'string') {
|
130
|
+
this._bodyText = body
|
131
|
+
} else if (support.blob && Blob.prototype.isPrototypeOf(body)) {
|
132
|
+
this._bodyBlob = body
|
133
|
+
} else if (support.formData && FormData.prototype.isPrototypeOf(body)) {
|
134
|
+
this._bodyFormData = body
|
135
|
+
} else if (!body) {
|
136
|
+
this._bodyText = ''
|
137
|
+
} else if (support.arrayBuffer && ArrayBuffer.prototype.isPrototypeOf(body)) {
|
138
|
+
// Only support ArrayBuffers for POST method.
|
139
|
+
// Receiving ArrayBuffers happens via Blobs, instead.
|
140
|
+
} else {
|
141
|
+
throw new Error('unsupported BodyInit type')
|
142
|
+
}
|
143
|
+
}
|
144
|
+
|
145
|
+
if (support.blob) {
|
146
|
+
this.blob = function() {
|
147
|
+
var rejected = consumed(this)
|
148
|
+
if (rejected) {
|
149
|
+
return rejected
|
150
|
+
}
|
151
|
+
|
152
|
+
if (this._bodyBlob) {
|
153
|
+
return Promise.resolve(this._bodyBlob)
|
154
|
+
} else if (this._bodyFormData) {
|
155
|
+
throw new Error('could not read FormData body as blob')
|
156
|
+
} else {
|
157
|
+
return Promise.resolve(new Blob([this._bodyText]))
|
158
|
+
}
|
159
|
+
}
|
160
|
+
|
161
|
+
this.arrayBuffer = function() {
|
162
|
+
return this.blob().then(readBlobAsArrayBuffer)
|
163
|
+
}
|
164
|
+
|
165
|
+
this.text = function() {
|
166
|
+
var rejected = consumed(this)
|
167
|
+
if (rejected) {
|
168
|
+
return rejected
|
169
|
+
}
|
170
|
+
|
171
|
+
if (this._bodyBlob) {
|
172
|
+
return readBlobAsText(this._bodyBlob)
|
173
|
+
} else if (this._bodyFormData) {
|
174
|
+
throw new Error('could not read FormData body as text')
|
175
|
+
} else {
|
176
|
+
return Promise.resolve(this._bodyText)
|
177
|
+
}
|
178
|
+
}
|
179
|
+
} else {
|
180
|
+
this.text = function() {
|
181
|
+
var rejected = consumed(this)
|
182
|
+
return rejected ? rejected : Promise.resolve(this._bodyText)
|
183
|
+
}
|
184
|
+
}
|
185
|
+
|
186
|
+
if (support.formData) {
|
187
|
+
this.formData = function() {
|
188
|
+
return this.text().then(decode)
|
189
|
+
}
|
190
|
+
}
|
191
|
+
|
192
|
+
this.json = function() {
|
193
|
+
return this.text().then(JSON.parse)
|
194
|
+
}
|
195
|
+
|
196
|
+
return this
|
197
|
+
}
|
198
|
+
|
199
|
+
// HTTP methods whose capitalization should be normalized
|
200
|
+
var methods = ['DELETE', 'GET', 'HEAD', 'OPTIONS', 'POST', 'PUT']
|
201
|
+
|
202
|
+
function normalizeMethod(method) {
|
203
|
+
var upcased = method.toUpperCase()
|
204
|
+
return (methods.indexOf(upcased) > -1) ? upcased : method
|
205
|
+
}
|
206
|
+
|
207
|
+
function Request(input, options) {
|
208
|
+
options = options || {}
|
209
|
+
var body = options.body
|
210
|
+
if (Request.prototype.isPrototypeOf(input)) {
|
211
|
+
if (input.bodyUsed) {
|
212
|
+
throw new TypeError('Already read')
|
213
|
+
}
|
214
|
+
this.url = input.url
|
215
|
+
this.credentials = input.credentials
|
216
|
+
if (!options.headers) {
|
217
|
+
this.headers = new Headers(input.headers)
|
218
|
+
}
|
219
|
+
this.method = input.method
|
220
|
+
this.mode = input.mode
|
221
|
+
if (!body) {
|
222
|
+
body = input._bodyInit
|
223
|
+
input.bodyUsed = true
|
224
|
+
}
|
225
|
+
} else {
|
226
|
+
this.url = input
|
227
|
+
}
|
228
|
+
|
229
|
+
this.credentials = options.credentials || this.credentials || 'omit'
|
230
|
+
if (options.headers || !this.headers) {
|
231
|
+
this.headers = new Headers(options.headers)
|
232
|
+
}
|
233
|
+
this.method = normalizeMethod(options.method || this.method || 'GET')
|
234
|
+
this.mode = options.mode || this.mode || null
|
235
|
+
this.referrer = null
|
236
|
+
|
237
|
+
if ((this.method === 'GET' || this.method === 'HEAD') && body) {
|
238
|
+
throw new TypeError('Body not allowed for GET or HEAD requests')
|
239
|
+
}
|
240
|
+
this._initBody(body)
|
241
|
+
}
|
242
|
+
|
243
|
+
Request.prototype.clone = function() {
|
244
|
+
return new Request(this)
|
245
|
+
}
|
246
|
+
|
247
|
+
function decode(body) {
|
248
|
+
var form = new FormData()
|
249
|
+
body.trim().split('&').forEach(function(bytes) {
|
250
|
+
if (bytes) {
|
251
|
+
var split = bytes.split('=')
|
252
|
+
var name = split.shift().replace(/\+/g, ' ')
|
253
|
+
var value = split.join('=').replace(/\+/g, ' ')
|
254
|
+
form.append(decodeURIComponent(name), decodeURIComponent(value))
|
255
|
+
}
|
256
|
+
})
|
257
|
+
return form
|
258
|
+
}
|
259
|
+
|
260
|
+
function headers(xhr) {
|
261
|
+
var head = new Headers()
|
262
|
+
var pairs = xhr.getAllResponseHeaders().trim().split('\n')
|
263
|
+
pairs.forEach(function(header) {
|
264
|
+
var split = header.trim().split(':')
|
265
|
+
var key = split.shift().trim()
|
266
|
+
var value = split.join(':').trim()
|
267
|
+
head.append(key, value)
|
268
|
+
})
|
269
|
+
return head
|
270
|
+
}
|
271
|
+
|
272
|
+
Body.call(Request.prototype)
|
273
|
+
|
274
|
+
function Response(bodyInit, options) {
|
275
|
+
if (!options) {
|
276
|
+
options = {}
|
277
|
+
}
|
278
|
+
|
279
|
+
this._initBody(bodyInit)
|
280
|
+
this.type = 'default'
|
281
|
+
this.status = options.status
|
282
|
+
this.ok = this.status >= 200 && this.status < 300
|
283
|
+
this.statusText = options.statusText
|
284
|
+
this.headers = options.headers instanceof Headers ? options.headers : new Headers(options.headers)
|
285
|
+
this.url = options.url || ''
|
286
|
+
}
|
287
|
+
|
288
|
+
Body.call(Response.prototype)
|
289
|
+
|
290
|
+
Response.prototype.clone = function() {
|
291
|
+
return new Response(this._bodyInit, {
|
292
|
+
status: this.status,
|
293
|
+
statusText: this.statusText,
|
294
|
+
headers: new Headers(this.headers),
|
295
|
+
url: this.url
|
296
|
+
})
|
297
|
+
}
|
298
|
+
|
299
|
+
Response.error = function() {
|
300
|
+
var response = new Response(null, {status: 0, statusText: ''})
|
301
|
+
response.type = 'error'
|
302
|
+
return response
|
303
|
+
}
|
304
|
+
|
305
|
+
var redirectStatuses = [301, 302, 303, 307, 308]
|
306
|
+
|
307
|
+
Response.redirect = function(url, status) {
|
308
|
+
if (redirectStatuses.indexOf(status) === -1) {
|
309
|
+
throw new RangeError('Invalid status code')
|
310
|
+
}
|
311
|
+
|
312
|
+
return new Response(null, {status: status, headers: {location: url}})
|
313
|
+
}
|
314
|
+
|
315
|
+
self.Headers = Headers;
|
316
|
+
self.Request = Request;
|
317
|
+
self.Response = Response;
|
318
|
+
|
319
|
+
self.fetch = function(input, init) {
|
320
|
+
return new Promise(function(resolve, reject) {
|
321
|
+
var request
|
322
|
+
if (Request.prototype.isPrototypeOf(input) && !init) {
|
323
|
+
request = input
|
324
|
+
} else {
|
325
|
+
request = new Request(input, init)
|
326
|
+
}
|
327
|
+
|
328
|
+
var xhr = new XMLHttpRequest()
|
329
|
+
|
330
|
+
function responseURL() {
|
331
|
+
if ('responseURL' in xhr) {
|
332
|
+
return xhr.responseURL
|
333
|
+
}
|
334
|
+
|
335
|
+
// Avoid security warnings on getResponseHeader when not allowed by CORS
|
336
|
+
if (/^X-Request-URL:/m.test(xhr.getAllResponseHeaders())) {
|
337
|
+
return xhr.getResponseHeader('X-Request-URL')
|
338
|
+
}
|
339
|
+
|
340
|
+
return;
|
341
|
+
}
|
342
|
+
|
343
|
+
xhr.onload = function() {
|
344
|
+
var status = (xhr.status === 1223) ? 204 : xhr.status
|
345
|
+
if (status < 100 || status > 599) {
|
346
|
+
reject(new TypeError('Network request failed'))
|
347
|
+
return
|
348
|
+
}
|
349
|
+
var options = {
|
350
|
+
status: status,
|
351
|
+
statusText: xhr.statusText,
|
352
|
+
headers: headers(xhr),
|
353
|
+
url: responseURL()
|
354
|
+
}
|
355
|
+
var body = 'response' in xhr ? xhr.response : xhr.responseText;
|
356
|
+
resolve(new Response(body, options))
|
357
|
+
}
|
358
|
+
|
359
|
+
xhr.onerror = function() {
|
360
|
+
reject(new TypeError('Network request failed'))
|
361
|
+
}
|
362
|
+
|
363
|
+
xhr.open(request.method, request.url, true)
|
364
|
+
|
365
|
+
if (request.credentials === 'include') {
|
366
|
+
xhr.withCredentials = true
|
367
|
+
}
|
368
|
+
|
369
|
+
if ('responseType' in xhr && support.blob) {
|
370
|
+
xhr.responseType = 'blob'
|
371
|
+
}
|
372
|
+
|
373
|
+
request.headers.forEach(function(value, name) {
|
374
|
+
xhr.setRequestHeader(name, value)
|
375
|
+
})
|
376
|
+
|
377
|
+
xhr.send(typeof request._bodyInit === 'undefined' ? null : request._bodyInit)
|
378
|
+
})
|
379
|
+
}
|
380
|
+
self.fetch.polyfill = true
|
381
|
+
})();
|
data/app/public/js/main.js
CHANGED
@@ -68,7 +68,7 @@ function filterBox(input, els) {
|
|
68
68
|
});
|
69
69
|
else
|
70
70
|
Array.prototype.forEach.call(els, (item, i) => {
|
71
|
-
if (item.
|
71
|
+
if (item.textContent.match(el.value))
|
72
72
|
item.style.display = 'block';
|
73
73
|
else
|
74
74
|
item.style.display = 'none';
|
@@ -96,3 +96,26 @@ function restore_url(list) {
|
|
96
96
|
});
|
97
97
|
}
|
98
98
|
}
|
99
|
+
|
100
|
+
function update_footer(path) {
|
101
|
+
var debug = document.querySelector('.foot .debug');
|
102
|
+
debug.innerHTML = "curl -s http://" + window.location.host + path + " | jq '.'";
|
103
|
+
}
|
104
|
+
|
105
|
+
|
106
|
+
function auth_header() {
|
107
|
+
var h = new Headers({"x-auth": session_key});
|
108
|
+
return { headers: h }
|
109
|
+
}
|
110
|
+
|
111
|
+
|
112
|
+
ready( () => {
|
113
|
+
|
114
|
+
var flash = document.querySelectorAll('div.flash');
|
115
|
+
Array.prototype.forEach.call(flash, (item, i) => {
|
116
|
+
item.addEventListener('click', (ev) => {
|
117
|
+
ev.target.style.display = 'none';
|
118
|
+
});
|
119
|
+
});
|
120
|
+
|
121
|
+
});
|