hieraviz 0.0.1 → 0.0.2

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 6c1283ce82b3cdaba2917c8bd1f2d6606f6272b5
4
- data.tar.gz: 789132864aef111d8cbc4c1d5dc4b8478ac1aa60
3
+ metadata.gz: 7943ca0a1cce06662064ff153e6574e667a9aebe
4
+ data.tar.gz: de8240ae22c0afff43b068cf4af11fc84a3247a9
5
5
  SHA512:
6
- metadata.gz: 856f47c9edd3e9333bc8e795258e916935be940d6ba216c72aba7e70ffa280f1a459dda96601c0ea3c15e0fdd46aa6bf3913fcd0ea9955c39204264b0759c968
7
- data.tar.gz: 559f5c324a0db557223502df3f302ed9399e547a797790737da0946e90ecbd2c898c9718eb1d61b79a26ca6a01dc19e32ff6be9c2153a6809bf2c4851cd36784
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?branch=master)
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
- Usage
23
- ----------
24
- `hv-ctl` is a control script to manipulate Hieraviz server. It is not written yet, unfortunately, but will be ready in the very next versions.
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
- configfile = ENV['HIERAVIZ_CONFIG_FILE'] || File.join("config", "hieraviz.yml")
12
- configfile = File.join(root, configfile) unless configfile[0] == '/'
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
@@ -1,3 +1,6 @@
1
+ require 'hieracles'
2
+ require 'hieraviz'
3
+
1
4
  require File.expand_path '../web.rb', __FILE__
2
5
  require File.expand_path '../apiv1.rb', __FILE__
3
6
 
@@ -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
- /* ----- Sidebar ---- */
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
+ }
@@ -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.innerText).
45
+ fetch('/v1/farm/' + el.dataset.item, auth_header()).
46
46
  then(res => res.json()).
47
47
  then(j => {
48
- build_list(meat, el.innerText, j);
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
+ })();
@@ -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.innerText.match(el.value))
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
+ });