redis_dashboard 0.1.4 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (39) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +18 -0
  3. data/Gemfile +1 -1
  4. data/Gemfile.lock +13 -19
  5. data/README.md +17 -15
  6. data/lib/redis_dashboard/application.rb +65 -21
  7. data/lib/redis_dashboard/client.rb +33 -8
  8. data/lib/redis_dashboard/public/ariato.css +739 -0
  9. data/lib/redis_dashboard/public/style.css +185 -0
  10. data/lib/redis_dashboard/views/application.scss +2 -10
  11. data/lib/redis_dashboard/views/clients.erb +18 -11
  12. data/lib/redis_dashboard/views/config.erb +6 -7
  13. data/lib/redis_dashboard/views/index.erb +10 -10
  14. data/lib/redis_dashboard/views/info.erb +6 -7
  15. data/lib/redis_dashboard/views/key/hash.erb +12 -0
  16. data/lib/redis_dashboard/views/key/list.erb +11 -0
  17. data/lib/redis_dashboard/views/key/metadata.erb +47 -0
  18. data/lib/redis_dashboard/views/key/set.erb +11 -0
  19. data/lib/redis_dashboard/views/key/string.erb +3 -0
  20. data/lib/redis_dashboard/views/key/unsupported.erb +2 -0
  21. data/lib/redis_dashboard/views/key/zset.erb +16 -0
  22. data/lib/redis_dashboard/views/key.erb +5 -0
  23. data/lib/redis_dashboard/views/keys.erb +26 -0
  24. data/lib/redis_dashboard/views/keyspace.erb +23 -0
  25. data/lib/redis_dashboard/views/layout.erb +35 -29
  26. data/lib/redis_dashboard/views/memory.erb +25 -0
  27. data/lib/redis_dashboard/views/slowlog.erb +25 -18
  28. data/lib/redis_dashboard/views/stats.erb +19 -15
  29. data/lib/redis_dashboard.rb +1 -1
  30. data/redis_dashboard.gemspec +2 -2
  31. data/screenshot.jpg +0 -0
  32. metadata +19 -13
  33. data/lib/redis_dashboard/views/stylesheets/base.scss +0 -17
  34. data/lib/redis_dashboard/views/stylesheets/colors.scss +0 -6
  35. data/lib/redis_dashboard/views/stylesheets/link.scss +0 -20
  36. data/lib/redis_dashboard/views/stylesheets/page.scss +0 -69
  37. data/lib/redis_dashboard/views/stylesheets/server-card.scss +0 -37
  38. data/lib/redis_dashboard/views/stylesheets/table.scss +0 -49
  39. data/lib/redis_dashboard/views/stylesheets/typography.scss +0 -21
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: b7368d883f06fd1e645b36e38eda3265344e4e79
4
- data.tar.gz: fd50890c3609ce804ff203eea14a0ebc85455572
2
+ SHA256:
3
+ metadata.gz: 9642ce8486ef1fc0d995d2c06380537aa7918335b1f90e8cbade6983e3a84940
4
+ data.tar.gz: 5221f10a927a48865bd0f598cddac86f9601a942b800d85543e336754c2682e8
5
5
  SHA512:
6
- metadata.gz: 35aaf7042c058433465f2aa74615bec63c44d08e66906da4fef874522dd106bddea978ec68f24c779f15b88b6825667777b0d33ff058f81008e42f8d187a7415
7
- data.tar.gz: f6a728c21d83ca3f6cb31fde65b49ae0da1371a2a0db0c1416e0db8522f77d66be6feecb769aaedcf022b364c300ec002eff992fd95a0b4b9be7c588bc94f1cf
6
+ metadata.gz: 1865b8f9ef769bf860987722807e5c341d32c0303e5c115e9c09a9f8c97b91bf9a2de46bd8c2c17dc5b676f379ac1a96142c747f2a1d134611d51a101bff8389
7
+ data.tar.gz: be609cbe14b267d8421006103528db4afafbd99de6f7995bffc5880daf0f067b83d27a7230e8ed97e0fc014c4e76af53c6c2ef317587d688e309a8c82f0a9503
data/CHANGELOG.md ADDED
@@ -0,0 +1,18 @@
1
+ ## 0.3.0
2
+
3
+ * Add keyspace explorer
4
+ * Use friendly URLs with server host name
5
+ * Mute Redis error when server is too old to support memory command
6
+ * Use Ariato CSS framework https://ariato.org
7
+ * Remove sassc dependency to switch to plain CSS
8
+
9
+ ## 0.2.0
10
+
11
+ * Add memory stats
12
+ * Replace deprecated sass by sassc
13
+
14
+ ## 0.1.6
15
+
16
+ * Switch to erubi
17
+ * Escape HTML by default
18
+ * Fix cache hit ratio
data/Gemfile CHANGED
@@ -1,5 +1,5 @@
1
1
  source "https://rubygems.org"
2
2
 
3
3
  gem "sinatra"
4
+ gem "erubi"
4
5
  gem "redis"
5
- gem "sass"
data/Gemfile.lock CHANGED
@@ -1,34 +1,28 @@
1
1
  GEM
2
2
  remote: https://rubygems.org/
3
3
  specs:
4
- ffi (1.11.1)
5
- mustermann (1.0.3)
6
- rack (2.0.7)
7
- rack-protection (2.0.5)
4
+ erubi (1.10.0)
5
+ mustermann (1.1.1)
6
+ ruby2_keywords (~> 0.0.1)
7
+ rack (2.2.3)
8
+ rack-protection (2.1.0)
8
9
  rack
9
- rb-fsevent (0.10.3)
10
- rb-inotify (0.10.0)
11
- ffi (~> 1.0)
12
- redis (4.1.2)
13
- sass (3.7.4)
14
- sass-listen (~> 4.0.0)
15
- sass-listen (4.0.0)
16
- rb-fsevent (~> 0.9, >= 0.9.4)
17
- rb-inotify (~> 0.9, >= 0.9.7)
18
- sinatra (2.0.5)
10
+ redis (4.5.1)
11
+ ruby2_keywords (0.0.5)
12
+ sinatra (2.1.0)
19
13
  mustermann (~> 1.0)
20
- rack (~> 2.0)
21
- rack-protection (= 2.0.5)
14
+ rack (~> 2.2)
15
+ rack-protection (= 2.1.0)
22
16
  tilt (~> 2.0)
23
- tilt (2.0.9)
17
+ tilt (2.0.10)
24
18
 
25
19
  PLATFORMS
26
20
  ruby
27
21
 
28
22
  DEPENDENCIES
23
+ erubi
29
24
  redis
30
- sass
31
25
  sinatra
32
26
 
33
27
  BUNDLED WITH
34
- 1.15.0
28
+ 2.1.4
data/README.md CHANGED
@@ -20,38 +20,40 @@ You can run it in standalone or inside your Rails app.
20
20
 
21
21
  ## Installation inside a Rails app
22
22
 
23
- Add this line in your Gemfile:
24
- ```ruby
25
- gem "redis_dashboard"
26
- ```
27
-
28
- In your terminal run the following command:
29
- ```shell
30
- bundle install
31
- ```
23
+ Add to your Gemfile `gem "redis_dashboard"` and run `bundle install`.
32
24
 
33
25
  Then mount the app from `config/routes.rb`:
34
26
  ```ruby
35
- mount RedisDashboard::Application, at: "redis_dashboard"
27
+ mount RedisDashboard::Application, at: "redis"
36
28
  ```
37
29
 
38
- Specify the Redis URLs in `config/redis_dashboard.rb`:
30
+ By default Redis dashboard tries to connect to `REDIS_URL` environment variable or to `localhost`. You can specify any other URL by adding an initializer in `config/initializers/redis_dashboard.rb` :
39
31
  ```ruby
40
- RedisDashboard.urls = ["redis://localhost"]
32
+ RedisDashboard.urls = [ENV["REDIS_URL"] || "redis://localhost"]
41
33
  ```
42
34
 
43
- Finally visit http://localhost/redis_dashboard/.
35
+ Finally visit http://localhost:3000/redis.
44
36
 
45
- ## Authentication
37
+ ## Authentication and permissions
46
38
 
47
- To protect your dashboard you can setup a basic HTTP authentication:
39
+ To protect your dashboard you can setup a basic HTTP authentication :
48
40
 
49
41
  ```ruby
42
+ # config/initializers/redis_dashboard.rb
50
43
  RedisDashboard::Application.use(Rack::Auth::Basic) do |user, password|
51
44
  user == "USER" && password == "PASSWORD"
52
45
  end
53
46
  ```
54
47
 
48
+ In case you handle authentication with Devise, you can perform the permission verification directly from the routes :
49
+
50
+ ```ruby
51
+ # config/routes.rb
52
+ authenticate :user, -> (u) { u.admin? } do # Supposing there is a User#admin? method
53
+ mount RedisDashboard::Application, at: "redis"
54
+ end
55
+ ```
56
+
55
57
  ## MIT License
56
58
 
57
59
  Made by [Base Secrète](https://basesecrete.com).
@@ -1,40 +1,64 @@
1
1
  require "sinatra/base"
2
+ require "erubi"
2
3
  require "redis"
3
4
  require "uri"
4
5
 
5
6
  class RedisDashboard::Application < Sinatra::Base
7
+ set :erb, escape_html: true
8
+
6
9
  after { close_clients }
7
10
 
8
11
  get "/" do
9
12
  erb(:index, locals: {clients: clients})
10
13
  end
11
14
 
12
- get "/info" do
13
- erb(:info, locals: {info: client.info})
14
- end
15
-
16
- get "/config" do
15
+ get "/:server/config" do
17
16
  erb(:config, locals: {config: client.config})
18
17
  end
19
18
 
20
- get "/clients" do
19
+ get "/:server/clients" do
21
20
  erb(:clients, locals: {clients: client.clients})
22
21
  end
23
22
 
24
- get "/stats" do
23
+ get "/:server/stats" do
25
24
  erb(:stats, locals: {stats: client.stats})
26
25
  end
27
26
 
28
- get "/slowlog" do
27
+ get "/:server/slowlog" do
29
28
  erb(:slowlog, locals: {client: client, commands: client.slow_commands})
30
29
  end
31
30
 
32
- get "/application.css" do
33
- scss(:application, style: :expanded)
31
+ get "/:server/memory" do
32
+ stats = mute_redis_command_error { client.memory_stats } || {}
33
+ erb(:memory, locals: {client: client, stats: stats })
34
+ end
35
+
36
+ get "/:server/keyspace" do
37
+ erb(:keyspace, locals: {keyspace: client.keyspace})
38
+ end
39
+
40
+ get "/:server/keyspace/:db" do
41
+ client.connection.select(params[:db].sub(/^db/, ""))
42
+ erb(:keys, locals: {client: client, keys: client.keys(params[:query])})
43
+ end
44
+
45
+ get "/:server/keyspace/:db/*" do
46
+ params[:key] = params[:splat].first
47
+ client.connection.select(params[:db].sub(/^db/, ""))
48
+ erb(:key, locals: {client: client})
49
+ end
50
+
51
+ get "/:server" do
52
+ erb(:info, locals: {info: client.info})
34
53
  end
35
54
 
36
55
  def client
37
- @client ||= RedisDashboard::Client.new(RedisDashboard.urls[redis_id.to_i])
56
+ return @client if @client
57
+ if url = RedisDashboard.urls.find { |url| URI(url).host == params[:server] }
58
+ @client ||= RedisDashboard::Client.new(url)
59
+ else
60
+ raise Sinatra::NotFound
61
+ end
38
62
  end
39
63
 
40
64
  def clients
@@ -57,12 +81,12 @@ class RedisDashboard::Application < Sinatra::Base
57
81
  Time.at(epoch).strftime("%b %d %H:%M")
58
82
  end
59
83
 
60
- def redis_id
61
- params[:id]
84
+ def active_page_css(path)
85
+ request.path_info == path && "active"
62
86
  end
63
87
 
64
- def active_page?(path='')
65
- request.path_info == '/' + path
88
+ def active_path_css(path)
89
+ request.path_info.start_with?(path) && "active"
66
90
  end
67
91
 
68
92
  def format_impact_percentage(percentage)
@@ -74,16 +98,34 @@ class RedisDashboard::Application < Sinatra::Base
74
98
  end
75
99
 
76
100
  def compute_cache_hit_ratio(info)
77
- if (total = info["keyspace_hits"].to_i + info["keyspace_misses"].to_i) > 0
78
- info["keyspace_hits"].to_f * 100
101
+ hits = info["keyspace_hits"].to_i
102
+ misses = info["keyspace_misses"].to_i
103
+ if (total = hits + misses) > 0
104
+ hits * 100.0 / total
79
105
  else
80
106
  0
81
107
  end
82
108
  end
83
109
 
110
+ def render_key_data(key)
111
+ type = client.connection.type(params[:key])
112
+ erb(:"key/#{type}", locals: {key: key})
113
+ rescue Errno::ENOENT
114
+ erb(:"key/unsupported", locals: {key: key})
115
+ end
116
+
117
+ def mute_redis_command_error(&block)
118
+ block.call
119
+ rescue Redis::CommandError
120
+ end
121
+
122
+ def escape_key(key)
123
+ key.gsub("#", "%23")
124
+ end
125
+
84
126
  def clients_column_description(col)
85
127
  # https://redis.io/commands/client-list
86
- {
128
+ @clients_column_description ||= {
87
129
  id: "an unique 64-bit client ID (introduced in Redis 2.8.12).",
88
130
  addr: "address/port of the client",
89
131
  fd: "file descriptor corresponding to the socket",
@@ -101,12 +143,13 @@ class RedisDashboard::Application < Sinatra::Base
101
143
  omem: "output buffer memory usage",
102
144
  events: "file descriptor events (see below)",
103
145
  cmd: "last command played",
104
- }[col.to_sym]
146
+ }
147
+ @clients_column_description[col.to_sym]
105
148
  end
106
149
 
107
150
  def client_event_description(event)
108
151
  # https://redis.io/commands/client-list
109
- {
152
+ @client_event_description ||= {
110
153
  O: "the client is a slave in MONITOR mode",
111
154
  S: "the client is a normal slave server",
112
155
  M: "the client is a master",
@@ -120,7 +163,8 @@ class RedisDashboard::Application < Sinatra::Base
120
163
  r: "the client is in readonly mode against a cluster node",
121
164
  A: "connection to be closed ASAP",
122
165
  N: "no specific flag set",
123
- }[event.to_sym]
166
+ }
167
+ @client_event_description[event.to_sym]
124
168
  end
125
169
  end
126
170
  end
@@ -3,6 +3,7 @@ class RedisDashboard::Client
3
3
 
4
4
  def initialize(url)
5
5
  @url = url
6
+ @connection ||= Redis.new(url: url)
6
7
  end
7
8
 
8
9
  def clients
@@ -10,12 +11,7 @@ class RedisDashboard::Client
10
11
  end
11
12
 
12
13
  def config
13
- hash = {}
14
- array = connection.config("get", "*")
15
- while (pair = array.slice!(0, 2)).any?
16
- hash[pair.first] = pair.last
17
- end
18
- hash
14
+ array_reply_to_hash(connection.config("get", "*"))
19
15
  end
20
16
 
21
17
  def info
@@ -40,13 +36,42 @@ class RedisDashboard::Client
40
36
  end.sort{ |left, right| right.microseconds <=> left.microseconds }
41
37
  end
42
38
 
39
+ def memory_stats
40
+ array_reply_to_hash(connection.memory("stats"))
41
+ end
42
+
43
+ def keys(pattern)
44
+ connection.keys(pattern)
45
+ end
46
+
43
47
  def close
44
48
  connection.close if connection
45
49
  end
46
50
 
51
+ def host
52
+ URI(url).host
53
+ end
54
+
55
+ def keyspace
56
+ connection.info("KEYSPACE").inject({}) do |hash, space|
57
+ db, str = space
58
+ hash[db] = str.split(",").inject({}) do |h, s|
59
+ k, v = s.split("=")
60
+ h[k] = v
61
+ h
62
+ end
63
+ hash
64
+ end
65
+ end
66
+
47
67
  private
48
68
 
49
- def connection
50
- @connection ||= Redis.new(url: url)
69
+ # Array reply is a Redis format which is translated into a hash for convenience.
70
+ def array_reply_to_hash(array)
71
+ hash = {}
72
+ while (pair = array.slice!(0, 2)).any?
73
+ hash[pair.first] = pair.last.is_a?(Array) ? array_reply_to_hash(pair.last) : pair.last
74
+ end
75
+ hash
51
76
  end
52
77
  end