etcweb 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.bowerrc +3 -0
- data/.gitignore +9 -0
- data/.rspec +2 -0
- data/.travis.yml +19 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +21 -0
- data/README.md +36 -0
- data/Rakefile +1 -0
- data/app/bower_components/jquery/.bower.json +37 -0
- data/app/bower_components/jquery/MIT-LICENSE.txt +21 -0
- data/app/bower_components/jquery/bower.json +27 -0
- data/app/bower_components/jquery/dist/jquery.js +9205 -0
- data/app/bower_components/jquery/dist/jquery.min.js +5 -0
- data/app/bower_components/jquery/dist/jquery.min.map +1 -0
- data/app/bower_components/jquery/src/ajax.js +786 -0
- data/app/bower_components/jquery/src/ajax/jsonp.js +89 -0
- data/app/bower_components/jquery/src/ajax/load.js +75 -0
- data/app/bower_components/jquery/src/ajax/parseJSON.js +13 -0
- data/app/bower_components/jquery/src/ajax/parseXML.js +28 -0
- data/app/bower_components/jquery/src/ajax/script.js +64 -0
- data/app/bower_components/jquery/src/ajax/var/nonce.js +5 -0
- data/app/bower_components/jquery/src/ajax/var/rquery.js +3 -0
- data/app/bower_components/jquery/src/ajax/xhr.js +136 -0
- data/app/bower_components/jquery/src/attributes.js +11 -0
- data/app/bower_components/jquery/src/attributes/attr.js +141 -0
- data/app/bower_components/jquery/src/attributes/classes.js +158 -0
- data/app/bower_components/jquery/src/attributes/prop.js +94 -0
- data/app/bower_components/jquery/src/attributes/support.js +35 -0
- data/app/bower_components/jquery/src/attributes/val.js +161 -0
- data/app/bower_components/jquery/src/callbacks.js +205 -0
- data/app/bower_components/jquery/src/core.js +497 -0
- data/app/bower_components/jquery/src/core/access.js +60 -0
- data/app/bower_components/jquery/src/core/init.js +123 -0
- data/app/bower_components/jquery/src/core/parseHTML.js +39 -0
- data/app/bower_components/jquery/src/core/ready.js +97 -0
- data/app/bower_components/jquery/src/core/var/rsingleTag.js +4 -0
- data/app/bower_components/jquery/src/css.js +450 -0
- data/app/bower_components/jquery/src/css/addGetHookIf.js +22 -0
- data/app/bower_components/jquery/src/css/curCSS.js +57 -0
- data/app/bower_components/jquery/src/css/defaultDisplay.js +70 -0
- data/app/bower_components/jquery/src/css/hiddenVisibleSelectors.js +15 -0
- data/app/bower_components/jquery/src/css/support.js +96 -0
- data/app/bower_components/jquery/src/css/swap.js +28 -0
- data/app/bower_components/jquery/src/css/var/cssExpand.js +3 -0
- data/app/bower_components/jquery/src/css/var/getStyles.js +12 -0
- data/app/bower_components/jquery/src/css/var/isHidden.js +13 -0
- data/app/bower_components/jquery/src/css/var/rmargin.js +3 -0
- data/app/bower_components/jquery/src/css/var/rnumnonpx.js +5 -0
- data/app/bower_components/jquery/src/data.js +178 -0
- data/app/bower_components/jquery/src/data/Data.js +181 -0
- data/app/bower_components/jquery/src/data/accepts.js +20 -0
- data/app/bower_components/jquery/src/data/var/data_priv.js +5 -0
- data/app/bower_components/jquery/src/data/var/data_user.js +5 -0
- data/app/bower_components/jquery/src/deferred.js +149 -0
- data/app/bower_components/jquery/src/deprecated.js +13 -0
- data/app/bower_components/jquery/src/dimensions.js +50 -0
- data/app/bower_components/jquery/src/effects.js +648 -0
- data/app/bower_components/jquery/src/effects/Tween.js +114 -0
- data/app/bower_components/jquery/src/effects/animatedSelector.js +13 -0
- data/app/bower_components/jquery/src/event.js +868 -0
- data/app/bower_components/jquery/src/event/ajax.js +13 -0
- data/app/bower_components/jquery/src/event/alias.js +39 -0
- data/app/bower_components/jquery/src/event/support.js +9 -0
- data/app/bower_components/jquery/src/exports/amd.js +24 -0
- data/app/bower_components/jquery/src/exports/global.js +32 -0
- data/app/bower_components/jquery/src/intro.js +44 -0
- data/app/bower_components/jquery/src/jquery.js +37 -0
- data/app/bower_components/jquery/src/manipulation.js +580 -0
- data/app/bower_components/jquery/src/manipulation/_evalUrl.js +18 -0
- data/app/bower_components/jquery/src/manipulation/support.js +32 -0
- data/app/bower_components/jquery/src/manipulation/var/rcheckableType.js +3 -0
- data/app/bower_components/jquery/src/offset.js +207 -0
- data/app/bower_components/jquery/src/outro.js +1 -0
- data/app/bower_components/jquery/src/queue.js +142 -0
- data/app/bower_components/jquery/src/queue/delay.js +22 -0
- data/app/bower_components/jquery/src/selector-native.js +172 -0
- data/app/bower_components/jquery/src/selector-sizzle.js +14 -0
- data/app/bower_components/jquery/src/selector.js +1 -0
- data/app/bower_components/jquery/src/serialize.js +111 -0
- data/app/bower_components/jquery/src/sizzle/dist/sizzle.js +2067 -0
- data/app/bower_components/jquery/src/sizzle/dist/sizzle.min.js +3 -0
- data/app/bower_components/jquery/src/sizzle/dist/sizzle.min.map +1 -0
- data/app/bower_components/jquery/src/traversing.js +199 -0
- data/app/bower_components/jquery/src/traversing/findFilter.js +100 -0
- data/app/bower_components/jquery/src/traversing/var/rneedsContext.js +6 -0
- data/app/bower_components/jquery/src/var/arr.js +3 -0
- data/app/bower_components/jquery/src/var/class2type.js +4 -0
- data/app/bower_components/jquery/src/var/concat.js +5 -0
- data/app/bower_components/jquery/src/var/hasOwn.js +5 -0
- data/app/bower_components/jquery/src/var/indexOf.js +5 -0
- data/app/bower_components/jquery/src/var/pnum.js +3 -0
- data/app/bower_components/jquery/src/var/push.js +5 -0
- data/app/bower_components/jquery/src/var/rnotwhite.js +3 -0
- data/app/bower_components/jquery/src/var/slice.js +5 -0
- data/app/bower_components/jquery/src/var/strundefined.js +3 -0
- data/app/bower_components/jquery/src/var/support.js +4 -0
- data/app/bower_components/jquery/src/var/toString.js +5 -0
- data/app/bower_components/jquery/src/wrap.js +79 -0
- data/app/stylesheets/application.scss +31 -0
- data/app/views/dir.haml +95 -0
- data/app/views/etcd_error.haml +5 -0
- data/app/views/etcvault_keys_select.haml +6 -0
- data/app/views/index.haml +1 -0
- data/app/views/key.haml +68 -0
- data/app/views/keys.haml +30 -0
- data/app/views/layout.haml +20 -0
- data/bin/console +14 -0
- data/bin/setup +7 -0
- data/config.ru +19 -0
- data/etcweb.gemspec +37 -0
- data/lib/etcweb.rb +6 -0
- data/lib/etcweb/app.rb +230 -0
- data/lib/etcweb/version.rb +3 -0
- metadata +331 -0
@@ -0,0 +1,31 @@
|
|
1
|
+
@import "bootstrap-sprockets";
|
2
|
+
@import "bootstrap";
|
3
|
+
|
4
|
+
.keys-footer {
|
5
|
+
margin-top: 40px;
|
6
|
+
border-top: 2px solid $gray_lighter;
|
7
|
+
padding-top: 40px;
|
8
|
+
}
|
9
|
+
|
10
|
+
.header-top {
|
11
|
+
h1 {
|
12
|
+
font-size: 24px;
|
13
|
+
margin: 0;
|
14
|
+
padding: 0;
|
15
|
+
}
|
16
|
+
height: 30px;
|
17
|
+
margin-top: 15px;
|
18
|
+
margin-bottom: 8px;
|
19
|
+
|
20
|
+
button {
|
21
|
+
margin-top: 0;
|
22
|
+
margin-bottom: 0;
|
23
|
+
padding-top: 0;
|
24
|
+
padding-bottom: 0;
|
25
|
+
}
|
26
|
+
}
|
27
|
+
|
28
|
+
.align-vertical {
|
29
|
+
display: flex;
|
30
|
+
align-items: center;
|
31
|
+
}
|
data/app/views/dir.haml
ADDED
@@ -0,0 +1,95 @@
|
|
1
|
+
:javascript
|
2
|
+
jQuery(function($) {
|
3
|
+
$("#mkdir_form, #value_form").hide();
|
4
|
+
$("#mkdir_form, #value_form").removeClass("hidden");
|
5
|
+
|
6
|
+
$("#open_mkdir_form").click(function() {
|
7
|
+
$("#mkdir_form").show();
|
8
|
+
$("#value_form").hide();
|
9
|
+
$("#mkdir_form .form-control:first").focus();
|
10
|
+
});
|
11
|
+
$("#open_value_form").click(function() {
|
12
|
+
$("#mkdir_form").hide();
|
13
|
+
$("#value_form").show();
|
14
|
+
$("#value_form .form-control:first").focus();
|
15
|
+
});
|
16
|
+
});
|
17
|
+
|
18
|
+
.row
|
19
|
+
.col-md-12
|
20
|
+
%p
|
21
|
+
%button#open_mkdir_form.btn.btn-default
|
22
|
+
%span.glyphicon.glyphicon-folder-close{'aria-hidden' => true}
|
23
|
+
New directory
|
24
|
+
%button#open_value_form.btn.btn-default
|
25
|
+
%span.glyphicon.glyphicon-plus{'aria-hidden' => true}
|
26
|
+
New value
|
27
|
+
|
28
|
+
- action = "/keys#{node.key}"
|
29
|
+
- action = "/keys/" if action == "/keys"
|
30
|
+
|
31
|
+
.hidden.panel.panel-default#mkdir_form
|
32
|
+
.panel-heading New directory
|
33
|
+
.panel-body
|
34
|
+
%form.form{action: action, method: 'POST'}
|
35
|
+
%input{type: 'hidden', name: '_method', value: 'PUT'}
|
36
|
+
%input{type: 'hidden', name: 'dir', value: '1'}
|
37
|
+
.form-group
|
38
|
+
%input.form-control{type: 'text', name: 'child', placeholder: 'new key name'}
|
39
|
+
.form-group
|
40
|
+
%button.btn.btn-primary{type: 'submit'} Create
|
41
|
+
|
42
|
+
.hidden.panel.panel-default#value_form
|
43
|
+
.panel-heading New value
|
44
|
+
.panel-body
|
45
|
+
%form.form{action: action, method: 'POST'}
|
46
|
+
%input{type: 'hidden', name: '_method', value: 'PUT'}
|
47
|
+
.form-group
|
48
|
+
%input.form-control{type: 'text', name: 'child', placeholder: 'new key name'}
|
49
|
+
.form-group
|
50
|
+
%textarea.form-control{name: 'value', row: '2', placeholder: 'value'}
|
51
|
+
.form-group
|
52
|
+
.row
|
53
|
+
- if etcvault?
|
54
|
+
.col-md-4
|
55
|
+
!= haml :etcvault_keys_select
|
56
|
+
.col-md-1
|
57
|
+
%button.btn.btn-primary{type: 'submit'} Create
|
58
|
+
.col-md-4
|
59
|
+
%button.btn.btn-default{type: 'submit', name: 'continue', value: '1'} Create and Continue
|
60
|
+
|
61
|
+
.row
|
62
|
+
.col-md-12
|
63
|
+
- if node.children.empty?
|
64
|
+
%p (empty directory)
|
65
|
+
.list-group
|
66
|
+
- node.children.sort_by(&:key).each do |child|
|
67
|
+
- name = child.key.split(?/).last
|
68
|
+
%a.list-group-item{href: "/keys#{child.key}"}
|
69
|
+
.row
|
70
|
+
- if child.dir
|
71
|
+
.col-md-5
|
72
|
+
#{name}/
|
73
|
+
.col-md-7
|
74
|
+
%span.glyphicon.glyphicon-folder-open{'aria-hidden' => true}
|
75
|
+
- else
|
76
|
+
.col-md-4= name
|
77
|
+
.col-md-1.text-right
|
78
|
+
- if child.etcvault
|
79
|
+
= child.etcvault['container'] && child.etcvault['container']['KeyName']
|
80
|
+
%span.glyphicon.glyphicon-briefcase{'aria-hidden' => true}
|
81
|
+
- elsif child.etcvault_error
|
82
|
+
%span.text-danger.glyphicon.glyphicon-briefcase{'aria-hidden' => true}
|
83
|
+
.col-md-7
|
84
|
+
%code= child.value
|
85
|
+
.row
|
86
|
+
.col-md-12
|
87
|
+
%form.form-inline{action: "/keys#{node.key}", method: 'POST', onsubmit: 'javascript:return confirm("Sure?")'}
|
88
|
+
%input{type: 'hidden', name: '_method', value: 'DELETE'}
|
89
|
+
%button.btn.btn-danger{type: 'submit'} Destroy
|
90
|
+
.checkbox
|
91
|
+
%label
|
92
|
+
%input{type: 'checkbox', name: 'recursive', value: '1', checked: false}
|
93
|
+
Recursive
|
94
|
+
|
95
|
+
|
@@ -0,0 +1,6 @@
|
|
1
|
+
- name ||= 'etcvault_key'
|
2
|
+
- default_key_name ||= nil
|
3
|
+
%select.form-control{name: name}
|
4
|
+
%option{value: '', selected: default_key_name.nil?} No etcvault encryption
|
5
|
+
- etcvault_keys.each do |key_name|
|
6
|
+
%option{value: key_name, selected: default_key_name == key_name} Encrypt using etcvault: #{key_name}
|
@@ -0,0 +1 @@
|
|
1
|
+
%h1 It works
|
data/app/views/key.haml
ADDED
@@ -0,0 +1,68 @@
|
|
1
|
+
.row
|
2
|
+
.col-md-12
|
3
|
+
%h3
|
4
|
+
%span.glyphicon.glyphicon-tag{'aria-hidden' => true}
|
5
|
+
Value
|
6
|
+
%pre= node.value
|
7
|
+
.row
|
8
|
+
.col-md-12
|
9
|
+
%h3
|
10
|
+
%span.glyphicon.glyphicon-cog{'aria-hidden' => true}
|
11
|
+
Information
|
12
|
+
%p
|
13
|
+
%span
|
14
|
+
%span.label-default.label modified_index
|
15
|
+
= node.modified_index
|
16
|
+
%span
|
17
|
+
%span.label-default.label created_index
|
18
|
+
= node.created_index
|
19
|
+
- if node.ttl
|
20
|
+
%p
|
21
|
+
%span.label-default.label TTL
|
22
|
+
= node.ttl.inspect
|
23
|
+
- if node.expiration
|
24
|
+
%p
|
25
|
+
%span.label-default.label expiration
|
26
|
+
= node.expiration.inspect
|
27
|
+
|
28
|
+
- if node.etcvault || node.etcvault_error
|
29
|
+
.row
|
30
|
+
.col-md-12
|
31
|
+
%h3
|
32
|
+
%span.glyphicon.glyphicon-briefcase{'aria-hidden' => true}
|
33
|
+
Etcvault
|
34
|
+
- if node.etcvault
|
35
|
+
%p= node.etcvault.inspect
|
36
|
+
- if node.etcvault_error
|
37
|
+
%p
|
38
|
+
%span.label.label-danger Error
|
39
|
+
= node.etcvault_error.inspect
|
40
|
+
|
41
|
+
.row
|
42
|
+
.col-md-12
|
43
|
+
%h3
|
44
|
+
%span.glyphicon.glyphicon-edit{'aria-hidden' => true}
|
45
|
+
Modify
|
46
|
+
%form{action: "/keys#{node.key}", method: 'POST'}
|
47
|
+
%input{type: 'hidden', name: '_method', value: 'PUT'}
|
48
|
+
.form-group
|
49
|
+
%textarea.form-control{rows: '5', name: 'value'}= node.value
|
50
|
+
.form-group
|
51
|
+
- if etcvault?
|
52
|
+
.row
|
53
|
+
.col-md-4
|
54
|
+
- default_key_name = node.etcvault && node.etcvault['container'] && node.etcvault['container']['KeyName']
|
55
|
+
!= haml :etcvault_keys_select, locals: {default_key_name: default_key_name}
|
56
|
+
.col-md-8
|
57
|
+
%button.btn.btn-primary{type: 'submit'} Save
|
58
|
+
- else
|
59
|
+
%button.btn.btn-primary{type: 'submit'} Save
|
60
|
+
.row
|
61
|
+
.col-md-12
|
62
|
+
%h3
|
63
|
+
%span.glyphicon.glyphicon-trash{'aria-hidden' => true}
|
64
|
+
Delete
|
65
|
+
%form{action: "/keys#{node.key}", method: 'POST', onsubmit: 'javascript:return confirm("Sure?")'}
|
66
|
+
%input{type: 'hidden', name: '_method', value: 'DELETE'}
|
67
|
+
.form-group
|
68
|
+
%button.btn.btn-danger{type: 'submit'} Destroy
|
data/app/views/keys.haml
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
- node = @etcd_response.node
|
2
|
+
.row
|
3
|
+
.col-md-12
|
4
|
+
%ol.breadcrumb
|
5
|
+
- active = (key.nil? || key.empty?) ? 'active' : nil
|
6
|
+
%li{class: active}
|
7
|
+
%a{href: '/keys/'}
|
8
|
+
%span.glyphicon.glyphicon-home{'aria-hidden' => true}
|
9
|
+
- if key
|
10
|
+
- paths = key.split(?/).tap(&:shift)
|
11
|
+
- paths.each_with_index do |part, i|
|
12
|
+
- active = (i.succ == key.size) ? 'active' : nil
|
13
|
+
%li{class: active}
|
14
|
+
%a{href: "/keys/#{paths[0..i].join(?/)}"}
|
15
|
+
= part
|
16
|
+
|
17
|
+
- if node.dir
|
18
|
+
!= haml :dir, locals: {node: node}
|
19
|
+
- else
|
20
|
+
!= haml :key, locals: {node: node}
|
21
|
+
|
22
|
+
|
23
|
+
%footer.keys-footer.row
|
24
|
+
.col-md-6
|
25
|
+
%span.label-default.label etcd_index
|
26
|
+
= @etcd_response.etcd_index
|
27
|
+
%span.label-default.label raft_index
|
28
|
+
= @etcd_response.raft_index
|
29
|
+
%span.label-default.label raft_term
|
30
|
+
= @etcd_response.raft_term
|
@@ -0,0 +1,20 @@
|
|
1
|
+
!!! 5
|
2
|
+
%html
|
3
|
+
%head
|
4
|
+
%meta{charset: 'utf-8'}
|
5
|
+
%script{type: 'application/javascript', src: '/assets/jquery/dist/jquery.min.js'}
|
6
|
+
%link{rel: 'stylesheet', type: 'text/css', href: '/assets/application.css'}
|
7
|
+
%title etcweb
|
8
|
+
%body
|
9
|
+
%header.header-top.container
|
10
|
+
.row.align-vertical
|
11
|
+
.col-md-8
|
12
|
+
%h1 etcweb
|
13
|
+
.col-md-4.text-right
|
14
|
+
- if auth_enabled? && current_user
|
15
|
+
Logged in as #{current_user[:info][:nickname] || current_user[:info][:name]}
|
16
|
+
%form{action: '/logout', method: 'POST', style: 'display: inline;'}
|
17
|
+
%button.btn.btn-link{type: 'submit'}
|
18
|
+
Log out
|
19
|
+
%section.container
|
20
|
+
!= yield
|
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require "etcweb"
|
5
|
+
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
8
|
+
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
10
|
+
# require "pry"
|
11
|
+
# Pry.start
|
12
|
+
|
13
|
+
require "irb"
|
14
|
+
IRB.start
|
data/bin/setup
ADDED
data/config.ru
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'etcweb'
|
2
|
+
require 'omniauth'
|
3
|
+
|
4
|
+
use Rack::Session::Cookie, :key => 'etcweb_sess',
|
5
|
+
:secret => 'configuration'
|
6
|
+
|
7
|
+
use OmniAuth::Builder do
|
8
|
+
provider :developer
|
9
|
+
end
|
10
|
+
|
11
|
+
run Etcweb::App.rack(
|
12
|
+
etcd: {
|
13
|
+
host: '127.0.0.1', port: 4001,
|
14
|
+
},
|
15
|
+
etcvault: true,
|
16
|
+
auth: {
|
17
|
+
omniauth: 'developer',
|
18
|
+
},
|
19
|
+
)
|
data/etcweb.gemspec
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'etcweb/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "etcweb"
|
8
|
+
spec.version = Etcweb::VERSION
|
9
|
+
spec.authors = ["Shota Fukumori (sora_h)"]
|
10
|
+
spec.email = ["sorah@cookpad.com"]
|
11
|
+
|
12
|
+
spec.summary = %q{Web UI for etcd}
|
13
|
+
spec.description = nil
|
14
|
+
spec.homepage = "https://github.com/sorah/etcweb"
|
15
|
+
spec.license = "MIT"
|
16
|
+
|
17
|
+
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
18
|
+
spec.bindir = "exe"
|
19
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
20
|
+
spec.require_paths = ["lib"]
|
21
|
+
|
22
|
+
spec.add_dependency "sinatra", ">= 1.4.5"
|
23
|
+
spec.add_dependency "etcd", ">= 0.2.4"
|
24
|
+
spec.add_dependency "etcd-etcvault", ">= 1.1.0"
|
25
|
+
spec.add_dependency "sprockets", ">= 2.12.3", "< 3"
|
26
|
+
spec.add_dependency "faml", ">= 0.2.0"
|
27
|
+
spec.add_dependency "bootstrap-sass", '~> 3.3.4'
|
28
|
+
spec.add_dependency "sprockets-helpers"
|
29
|
+
spec.add_dependency "omniauth"
|
30
|
+
|
31
|
+
spec.add_development_dependency "bundler", "~> 1.9"
|
32
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
33
|
+
|
34
|
+
spec.add_development_dependency "rspec", "~> 3.2.0"
|
35
|
+
|
36
|
+
spec.add_development_dependency "rack-test"
|
37
|
+
end
|
data/lib/etcweb.rb
ADDED
data/lib/etcweb/app.rb
ADDED
@@ -0,0 +1,230 @@
|
|
1
|
+
require 'sinatra/base'
|
2
|
+
require 'json'
|
3
|
+
require 'faml'
|
4
|
+
require 'rack'
|
5
|
+
require 'sass'
|
6
|
+
require 'sprockets'
|
7
|
+
require 'sprockets/helpers'
|
8
|
+
|
9
|
+
require 'bootstrap-sass'
|
10
|
+
|
11
|
+
require 'etcd'
|
12
|
+
require 'etcd/etcvault'
|
13
|
+
|
14
|
+
require 'omniauth'
|
15
|
+
|
16
|
+
module Etcweb
|
17
|
+
class App < Sinatra::Base
|
18
|
+
set :method_override, true
|
19
|
+
set :show_exceptions, false
|
20
|
+
set :root, File.expand_path(File.join(__dir__, '..', '..', 'app'))
|
21
|
+
set :sprockets, Sprockets::Environment.new.tap { |env|
|
22
|
+
env.append_path "#{self.root}/javascripts"
|
23
|
+
env.append_path "#{self.root}/stylesheets"
|
24
|
+
env.append_path "#{self.root}/images"
|
25
|
+
env.append_path "#{self.root}/bower_components"
|
26
|
+
}
|
27
|
+
|
28
|
+
def self.initialize_etcd(config)
|
29
|
+
# etcd v0.2.4 (latest as of Mar 31, 2015) doesn't set TLS parameters in constructor
|
30
|
+
# https://github.com/ranjib/etcd-ruby/commit/bf2c7e6dee8b2c07f85cca8541d16dcbef67cc1a
|
31
|
+
Etcd.client(config).tap do |etcd|
|
32
|
+
etcd.config.ca_file = config[:ca_file]
|
33
|
+
etcd.config.ssl_cert = config[:ssl_cert]
|
34
|
+
etcd.config.ssl_key = config[:ssl_key]
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def self.initialize_context(config)
|
39
|
+
{}.tap do |ctx|
|
40
|
+
ctx[:etcd] = initialize_etcd(config[:etcd] || {})
|
41
|
+
ctx[:config] = config
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def self.rack(config={})
|
46
|
+
klass = self
|
47
|
+
|
48
|
+
context = initialize_context(config)
|
49
|
+
app = lambda { |env|
|
50
|
+
env['etcweb'] = context
|
51
|
+
klass.call(env)
|
52
|
+
}
|
53
|
+
|
54
|
+
Rack::Builder.app do
|
55
|
+
map '/assets' do
|
56
|
+
run klass.sprockets
|
57
|
+
end
|
58
|
+
|
59
|
+
map '/' do
|
60
|
+
run app
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
helpers do
|
66
|
+
include Sprockets::Helpers
|
67
|
+
|
68
|
+
def context
|
69
|
+
request.env['etcweb']
|
70
|
+
end
|
71
|
+
|
72
|
+
def etcd
|
73
|
+
context[:etcd]
|
74
|
+
end
|
75
|
+
|
76
|
+
def key
|
77
|
+
@key ||= "/#{params[:splat] && params[:splat].first}".tap do |k|
|
78
|
+
k.concat("/#{params[:child]}") if params[:child]
|
79
|
+
k.sub!(%r{^//}, '/') # for child of root
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
def etcvault?
|
84
|
+
!!context[:config][:etcvault]
|
85
|
+
end
|
86
|
+
|
87
|
+
def etcvault_keys_cache_ttl
|
88
|
+
context[:config][:etcvault_keys_cache_ttl] || 30
|
89
|
+
end
|
90
|
+
|
91
|
+
def etcvault_keys
|
92
|
+
return [] unless etcvault?
|
93
|
+
context[:etcvault_keys] ||= {}
|
94
|
+
if context[:etcvault_keys][:expiry].nil? || context[:etcvault_keys][:expiry] <= Time.now
|
95
|
+
context[:etcvault_keys] = {
|
96
|
+
expiry: Time.now + etcvault_keys_cache_ttl,
|
97
|
+
keys: etcd.etcvault_keys.sort
|
98
|
+
}
|
99
|
+
end
|
100
|
+
context[:etcvault_keys][:keys]
|
101
|
+
end
|
102
|
+
|
103
|
+
def auth_enabled?
|
104
|
+
context[:config][:auth]
|
105
|
+
end
|
106
|
+
|
107
|
+
def omniauth_strategy
|
108
|
+
context[:config][:auth][:omniauth] or raise 'no config.auth.omniauth specified'
|
109
|
+
end
|
110
|
+
|
111
|
+
def auth_allow_policy_proc
|
112
|
+
context[:config][:auth][:allow_policy_proc] || proc { true }
|
113
|
+
end
|
114
|
+
|
115
|
+
def auth_after_login_proc
|
116
|
+
context[:config][:auth][:after_login_proc] || proc { }
|
117
|
+
end
|
118
|
+
|
119
|
+
def current_user
|
120
|
+
session[:user]
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
before do
|
125
|
+
next if request.path_info.start_with?('/auth')
|
126
|
+
next unless auth_enabled?
|
127
|
+
|
128
|
+
if current_user && instance_eval(&auth_allow_policy_proc)
|
129
|
+
next
|
130
|
+
end
|
131
|
+
|
132
|
+
if request.get?
|
133
|
+
session[:back_to] = request.fullpath
|
134
|
+
redirect "/auth/#{omniauth_strategy}"
|
135
|
+
else
|
136
|
+
halt 401
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
post '/logout' do
|
141
|
+
session[:user] = nil
|
142
|
+
redirect '/'
|
143
|
+
end
|
144
|
+
|
145
|
+
post '/auth/:strategy/callback' do
|
146
|
+
auth = env['omniauth.auth']
|
147
|
+
session[:user] = {
|
148
|
+
uid: auth[:uid],
|
149
|
+
info: auth[:info],
|
150
|
+
provider: auth[:provider],
|
151
|
+
}
|
152
|
+
instance_eval(&auth_after_login_proc)
|
153
|
+
redirect session.delete(:back_to) || "/"
|
154
|
+
end
|
155
|
+
|
156
|
+
get '/' do
|
157
|
+
redirect "/keys/"
|
158
|
+
end
|
159
|
+
|
160
|
+
get '/keys' do
|
161
|
+
redirect "/keys/"
|
162
|
+
end
|
163
|
+
|
164
|
+
get '/keys/*' do
|
165
|
+
begin
|
166
|
+
@etcd_response = etcd.get(key)
|
167
|
+
rescue Etcd::NotDir, Etcd::KeyNotFound
|
168
|
+
halt 404
|
169
|
+
rescue Etcd::Error => e
|
170
|
+
status 400
|
171
|
+
return haml(:etcd_error, locals: {error: e})
|
172
|
+
end
|
173
|
+
|
174
|
+
haml :keys
|
175
|
+
end
|
176
|
+
|
177
|
+
put '/keys/*' do
|
178
|
+
options = {value: params[:value]}
|
179
|
+
|
180
|
+
if params[:etcvault_key] && !params[:etcvault_key].empty?
|
181
|
+
unless etcvault_keys.include?(params[:etcvault_key])
|
182
|
+
halt 404
|
183
|
+
end
|
184
|
+
options[:value] = "ETCVAULT::plain:#{params[:etcvault_key]}:#{options[:value]}::ETCVAULT"
|
185
|
+
end
|
186
|
+
|
187
|
+
if params[:dir]
|
188
|
+
options.replace(dir: true)
|
189
|
+
end
|
190
|
+
|
191
|
+
options[:ttl] = params[:ttl].to_i if params[:ttl]
|
192
|
+
options[:value] = options[:value].gsub("\r\n", "\n") if options[:value]
|
193
|
+
|
194
|
+
begin
|
195
|
+
@etcd_response = etcd.set(key, options)
|
196
|
+
rescue Etcd::Error => e
|
197
|
+
status 400
|
198
|
+
return haml(:etcd_error, locals: {error: e})
|
199
|
+
end
|
200
|
+
|
201
|
+
if params[:continue] != nil
|
202
|
+
parent = key.split(?/).tap(&:pop).join(?/)
|
203
|
+
redirect "/keys#{parent}"
|
204
|
+
else
|
205
|
+
redirect "/keys#{key}"
|
206
|
+
end
|
207
|
+
end
|
208
|
+
|
209
|
+
delete '/keys/*' do
|
210
|
+
begin
|
211
|
+
options = {recursive: !!params[:recursive]}
|
212
|
+
@etcd_response = etcd.delete(key, options)
|
213
|
+
rescue Etcd::NotDir, Etcd::KeyNotFound
|
214
|
+
halt 404
|
215
|
+
rescue Etcd::Error => e
|
216
|
+
status 400
|
217
|
+
return haml(:etcd_error, locals: {error: e})
|
218
|
+
end
|
219
|
+
|
220
|
+
parent = key.split(?/).tap(&:pop).join(?/)
|
221
|
+
redirect "/keys#{parent}"
|
222
|
+
end
|
223
|
+
|
224
|
+
get '/etcvault_keys' do
|
225
|
+
content_type :json
|
226
|
+
|
227
|
+
etcvault_keys.to_json
|
228
|
+
end
|
229
|
+
end
|
230
|
+
end
|