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 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
+ });