grafana 0.8.2 → 1.0.0

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.
Files changed (44) hide show
  1. checksums.yaml +5 -5
  2. data/README.md +8 -3
  3. data/lib/grafana/admin.rb +39 -65
  4. data/lib/grafana/alerts.rb +334 -14
  5. data/lib/grafana/annotations.rb +284 -9
  6. data/lib/grafana/client.rb +38 -1
  7. data/lib/grafana/dashboard.rb +182 -39
  8. data/lib/grafana/dashboard_permissions.rb +132 -0
  9. data/lib/grafana/dashboard_versions.rb +101 -5
  10. data/lib/grafana/datasource.rb +93 -49
  11. data/lib/grafana/folder.rb +198 -0
  12. data/lib/grafana/folder_and_dashboard_search.rb +57 -0
  13. data/lib/grafana/folder_permissions.rb +155 -0
  14. data/lib/grafana/login.rb +41 -35
  15. data/lib/grafana/network.rb +128 -91
  16. data/lib/grafana/organization.rb +65 -34
  17. data/lib/grafana/organizations.rb +119 -175
  18. data/lib/grafana/playlist.rb +599 -0
  19. data/lib/grafana/preferences.rb +122 -0
  20. data/lib/grafana/tags.rb +19 -8
  21. data/lib/grafana/teams.rb +364 -0
  22. data/lib/grafana/tools.rb +44 -12
  23. data/lib/grafana/user.rb +78 -39
  24. data/lib/grafana/users.rb +104 -53
  25. data/lib/grafana/validator.rb +47 -2
  26. data/lib/grafana/version.rb +3 -3
  27. metadata +13 -38
  28. data/doc/Array.html +0 -200
  29. data/doc/Boolean.html +0 -122
  30. data/doc/FalseClass.html +0 -132
  31. data/doc/Grafana.html +0 -172
  32. data/doc/Hash.html +0 -212
  33. data/doc/Logging.html +0 -326
  34. data/doc/Object.html +0 -286
  35. data/doc/Time.html +0 -200
  36. data/doc/TrueClass.html +0 -132
  37. data/doc/_index.html +0 -380
  38. data/doc/class_list.html +0 -51
  39. data/doc/file.README.html +0 -117
  40. data/doc/file_list.html +0 -56
  41. data/doc/frames.html +0 -17
  42. data/doc/index.html +0 -117
  43. data/doc/method_list.html +0 -771
  44. data/doc/top-level-namespace.html +0 -112
@@ -0,0 +1,198 @@
1
+ module Grafana
2
+
3
+ # http://docs.grafana.org/http_api/folder/#folder-api
4
+ #
5
+ module Folder
6
+
7
+ # Get all folders
8
+ # GET /api/folders
9
+ # Returns all folders that the authenticated user has permission to view.
10
+ #
11
+ # @example
12
+ # folders
13
+ #
14
+ # @return [Hash]
15
+ #
16
+ def folders
17
+
18
+ v, mv = version.values
19
+ return { 'status' => 404, 'message' => format( 'folder has been supported in Grafana since version 5. you use version %s', v) } if(mv < 5)
20
+
21
+ endpoint = '/api/folders'
22
+ @logger.debug("Getting all folders (GET #{endpoint})") if @debug
23
+ get(endpoint)
24
+ end
25
+
26
+ # Get folder by uid
27
+ # GET /api/folders/:uid
28
+ #
29
+ # Will return the folder given the folder uid.
30
+ #
31
+ # Get folder by id
32
+ # GET /api/folders/:id
33
+ #
34
+ # Will return the folder identified by id.
35
+ def folder( folder_uid )
36
+
37
+ raise ArgumentError.new(format('wrong type. user \'folder_uid\' must be an String (for an Folder Uid) or an Integer (for an Folder Id), given \'%s\'', folder_uid.class.to_s)) \
38
+ if( folder_uid.is_a?(String) && folder_uid.is_a?(Integer) )
39
+ raise ArgumentError.new('missing \'folder_uid\'') if( folder_uid.size.zero? )
40
+
41
+ v, mv = version.values
42
+ return { 'status' => 404, 'message' => format( 'folder has been supported in Grafana since version 5. you use version %s', v) } if(mv < 5)
43
+
44
+ if(folder_uid.is_a?(Integer))
45
+
46
+ f = folders
47
+ f = JSON.parse(f) if(f.is_a?(String))
48
+
49
+ status = f.dig('status')
50
+ return f if( status != 200 )
51
+
52
+ f = f.dig('message').detect {|x| x['id'] == folder_uid }
53
+
54
+ return { 'status' => 404, 'message' => format( 'No Folder \'%s\' found', folder_uid) } if( folder_uid.nil? )
55
+
56
+ folder_uid = f.dig('uid') unless(f.nil?)
57
+
58
+ return { 'status' => 404, 'message' => format( 'No Folder \'%s\' found', folder_uid) } if( folder_uid.is_a?(Integer) )
59
+ end
60
+
61
+ return { 'status' => 404, 'message' => format( 'The uid can have a maximum length of 40 characters, but it is %s characters long', folder_uid.length) } \
62
+ if( folder_uid.is_a?(String) && folder_uid.length > 40 )
63
+ return { 'status' => 404, 'message' => format( 'No Folder \'%s\' found', folder_uid) } if( folder_uid.nil? )
64
+
65
+ endpoint = format( '/api/folders/%s', folder_uid )
66
+
67
+ @logger.debug("Getting folder by Id #{folder_uid} (GET #{endpoint})") if @debug
68
+ get(endpoint)
69
+ end
70
+
71
+ # Create folder
72
+ # POST /api/folders
73
+ #
74
+ # Creates a new folder.
75
+ # JSON Body schema:
76
+ #
77
+ # uid - Optional unique identifier.
78
+ # title - The title of the folder.
79
+ def create_folder( params )
80
+
81
+ raise ArgumentError.new(format('wrong type. \'params\' must be an Hash, given \'%s\'', params.class.to_s)) unless( params.is_a?(Hash) )
82
+ raise ArgumentError.new('missing \'params\'') if( params.size.zero? )
83
+
84
+ v, mv = version.values
85
+ return { 'status' => 404, 'message' => format( 'folder has been supported in Grafana since version 5. you use version %s', v) } if(mv < 5)
86
+
87
+ title = validate( params, required: false, var: 'title', type: String )
88
+ uid = validate( params, required: true , var: 'uid' , type: String )
89
+
90
+ return { 'status' => 404, 'message' => format( 'The uid can have a maximum length of 40 characters. \'%s\' given', uid.length) } if( uid.length > 40 )
91
+
92
+ data = {
93
+ uid: uid,
94
+ title: title
95
+ }
96
+ data.reject!{ |_, y| y.nil? }
97
+
98
+ payload = data.deep_string_keys
99
+
100
+ endpoint = '/api/folders'
101
+
102
+ @logger.debug("create folder#{title} (GET #{endpoint})") if @debug
103
+ logger.debug(payload.to_json) if(@debug)
104
+
105
+ post( endpoint, payload.to_json )
106
+ end
107
+
108
+ # Update folder
109
+ # PUT /api/folders/:uid
110
+ #
111
+ # schould be fail, when the version are not incremented
112
+ # overwrite helps
113
+ #
114
+ # Updates an existing folder identified by uid.
115
+ # JSON Body schema:
116
+ #
117
+ # - uid - Provide another unique identifier than stored to change the unique identifier.
118
+ # - title - The title of the folder.
119
+ # - version - Provide the current version to be able to update the folder. Not needed if overwrite=true.
120
+ # - overwrite - Set to true if you want to overwrite existing folder with newer version.
121
+ def update_folder( params )
122
+
123
+ raise ArgumentError.new(format('wrong type. \'params\' must be an Hash, given \'%s\'', params.class.to_s)) unless( params.is_a?(Hash) )
124
+ raise ArgumentError.new('missing \'params\'') if( params.size.zero? )
125
+
126
+ v, mv = version.values
127
+ return { 'status' => 404, 'message' => format( 'folder has been supported in Grafana since version 5. you use version %s', v) } if(mv < 5)
128
+
129
+ uid = validate( params, required: true , var: 'uid' , type: String )
130
+ title = validate( params, required: true , var: 'title' , type: String )
131
+ new_uid = validate( params, required: false, var: 'new_uid' , type: String )
132
+ version = validate( params, required: false, var: 'version' , type: Integer )
133
+ overwrite = validate( params, required: false, var: 'overwrite', type: Boolean ) || false
134
+
135
+ existing_folder = folder( uid )
136
+ return { 'status' => 404, 'message' => format( 'No Folder \'%s\' found', uid) } if( existing_folder.dig('status') != 200 )
137
+
138
+ unless( new_uid.nil? )
139
+ existing_folder = folder( new_uid )
140
+ return { 'status' => 404, 'message' => format( 'Folder \'%s\' found', uid) } if( existing_folder.dig('status') == 200 )
141
+ end
142
+
143
+ payload = {
144
+ title: title,
145
+ uid: new_uid,
146
+ version: version,
147
+ overwrite: overwrite
148
+ }
149
+ payload.reject!{ |_, y| y.nil? }
150
+
151
+ @logger.debug("Updating folder with Uid #{uid}") if @debug
152
+
153
+ endpoint = format( '/api/folders/%s', uid )
154
+
155
+ put( endpoint, payload.to_json )
156
+ end
157
+
158
+
159
+ # Delete folder
160
+ # DELETE /api/folders/:uid
161
+ #
162
+ # Deletes an existing folder identified by uid together with all dashboards stored in the folder, if any.
163
+ # This operation cannot be reverted.
164
+ def delete_folder( folder_uid )
165
+
166
+ raise ArgumentError.new(format('wrong type. user \'folder_uid\' must be an String (for an Folder Uid) or an Integer (for an Folder Id), given \'%s\'', folder_uid.class.to_s)) \
167
+ if( folder_uid.is_a?(String) && folder_uid.is_a?(Integer) )
168
+ raise ArgumentError.new('missing \'folder_uid\'') if( folder_uid.size.zero? )
169
+
170
+ v, mv = version.values
171
+ return { 'status' => 404, 'message' => format( 'folder has been supported in Grafana since version 5. you use version %s', v) } if(mv < 5)
172
+
173
+ if(folder_uid.is_a?(Integer))
174
+
175
+ fldrs = folders
176
+
177
+ fldrs = JSON.parse(fldrs) if(fldrs.is_a?(String))
178
+ status = fldrs.dig('status')
179
+
180
+ return fldrs if( status != 200 )
181
+
182
+ fldrs.dig('message').each do |d|
183
+ folder_uid = d.dig('uid').to_s
184
+ end
185
+ end
186
+
187
+ return { 'status' => 404, 'message' => format( 'No User \'%s\' found', folder_uid) } if( folder_uid.nil? )
188
+
189
+ endpoint = format( '/api/folders/%s', folder_uid )
190
+
191
+ @logger.debug("deleting folder by uid #{folder_uid} (GET #{endpoint})") if @debug
192
+ delete(endpoint)
193
+ end
194
+
195
+
196
+ end
197
+ end
198
+
@@ -0,0 +1,57 @@
1
+ module Grafana
2
+
3
+ # http://docs.grafana.org/http_api/folder_dashboard_search/#folder-dashboard-search-api
4
+ #
5
+ module FolderSearch
6
+
7
+ # Search folders and dashboards
8
+ # GET /api/search/
9
+ #
10
+ # Query parameters:
11
+ #
12
+ # - query - Search Query
13
+ # - tag - List of tags to search for
14
+ # - type - Type to search for, dash-folder or dash-db
15
+ # - dashboardIds - List of dashboard id's to search for
16
+ # - folderIds - List of folder id's to search in for dashboards
17
+ # - starred - Flag indicating if only starred Dashboards should be returned
18
+ # - limit - Limit the number of returned results
19
+ def folder_and_dashboard_search(params)
20
+
21
+ raise ArgumentError.new(format('wrong type. \'params\' must be an Hash, given \'%s\'', params.class.to_s)) unless( params.is_a?(Hash) )
22
+
23
+ v, mv = version.values
24
+ return { 'status' => 404, 'message' => format( 'team has been supported in Grafana since version 5. you use version %s', v) } if(mv < 5)
25
+
26
+ query = validate( params, required: false, var: 'query' , type: String )
27
+ tag = validate( params, required: false, var: 'tag ' , type: String )
28
+ type = validate( params, required: false, var: 'type' , type: String )
29
+ dashboard_id = validate( params, required: false, var: 'dashboardIds', type: Integer )
30
+ folder_id = validate( params, required: false, var: 'folderIds' , type: Integer )
31
+ starred = validate( params, required: false, var: 'starred' , type: Boolean )
32
+ limit = validate( params, required: false, var: 'limit' , type: Integer )
33
+
34
+ unless(type.nil?)
35
+ valid_types = ['dash-folder', 'dash-db']
36
+ downcased = Set.new valid_types.map(&:downcase)
37
+ return { 'status' => 404, 'message' => format( 'wrong type. Must be one of %s, given \'%s\'', valid_types.join(', '), type ) } unless( downcased.include?( type.downcase ) )
38
+ end
39
+
40
+ api = []
41
+ api << format( 'query=%s', CGI.escape( query ) ) unless( query.nil? )
42
+ api << format( 'tags=%s', tag ) unless( tag.nil? )
43
+ api << format( 'type=%s', type ) unless( type.nil? )
44
+ api << format( 'dashboardId=%s', dashboard_id ) unless( dashboard_id.nil? )
45
+ api << format( 'folderId=%s', folder_id ) unless( folder_id.nil? )
46
+ api << format( 'starred=%s', starred ) unless( starred.nil? )
47
+ api << format( 'limit=%s', limit ) unless( limit.nil? )
48
+
49
+ api = api.join( '&' )
50
+
51
+ endpoint = format('/api/search?%s', api)
52
+ get(endpoint)
53
+ end
54
+
55
+ end
56
+ end
57
+
@@ -0,0 +1,155 @@
1
+ module Grafana
2
+
3
+ # http://docs.grafana.org/http_api/folder_permissions/#folder-permissions-api
4
+ #
5
+ # This API can be used to update/get the permissions for a folder.
6
+ #
7
+ # Permissions with folderId=-1 are the default permissions for users with the Viewer and Editor roles.
8
+ # Permissions can be set for a user, a team or a role (Viewer or Editor).
9
+ # Permissions cannot be set for Admins - they always have access to everything.
10
+ #
11
+ # The permission levels for the permission field:
12
+ #
13
+ # 1 = View
14
+ # 2 = Edit
15
+ # 4 = Admin
16
+ #
17
+ module FolderPermissions
18
+
19
+ # Get permissions for a folder
20
+ # GET /api/folders/:uid/permissions
21
+ #
22
+ # Gets all existing permissions for the folder with the given uid.
23
+ def folder_permissions( folder_id )
24
+
25
+ v, mv = version.values
26
+ return { 'status' => 404, 'message' => format( 'folder has been supported in Grafana since version 5. you use version %s', v) } if(mv < 5)
27
+
28
+ f = folder( folder_id )
29
+
30
+ status = f.dig('status')
31
+ return f if( status != 200 )
32
+
33
+ endpoint = format('/api/folders/%s/permissions', f.dig('uid') )
34
+
35
+ @logger.debug("Getting all folders (GET #{endpoint})") if @debug
36
+ get(endpoint)
37
+ end
38
+
39
+
40
+ # Update permissions for a folder
41
+ # POST /api/folders/:uid/permissions
42
+ #
43
+ # Updates permissions for a folder.
44
+ # This operation will remove existing permissions if they're not included in the request.
45
+ #
46
+ # JSON body schema:
47
+ #
48
+ # items - The permission items to add/update. Items that are omitted from the list will be removed.
49
+ #
50
+ def update_folder_permissions( params )
51
+
52
+ raise ArgumentError.new(format('wrong type. \'params\' must be an Hash, given \'%s\'', params.class.to_s)) unless( params.is_a?(Hash) )
53
+ raise ArgumentError.new('missing \'params\'') if( params.size.zero? )
54
+
55
+ v, mv = version.values
56
+ return { 'status' => 404, 'message' => format( 'folder has been supported in Grafana since version 5. you use version %s', v) } if(mv < 5)
57
+
58
+ folder = validate( params, required: true, var: 'folder' , type: String )
59
+ permissions = validate( params, required: true, var: 'permissions', type: Hash )
60
+
61
+ return { 'status' => 404, 'message' => 'no permissions given' } if( permissions.size.zero? )
62
+
63
+ f_folder = folder(folder)
64
+ return { 'status' => 404, 'message' => format( 'No Folder \'%s\' found', folder) } if( f_folder.dig('status') != 200 )
65
+
66
+ folder_uid = f_folder.dig('uid')
67
+
68
+ valid_roles = %w[View Edit Admin]
69
+ # valid_keys = %w[role permission teamId userId]
70
+
71
+ c_team = permissions.dig('team')
72
+ c_user = permissions.dig('user')
73
+ team = []
74
+ user = []
75
+
76
+ unless(c_team.nil?)
77
+ check_keys = []
78
+
79
+ c_team.uniq.each do |x|
80
+ k = x.keys.first
81
+ v = x.values.first
82
+ r = validate_hash( v, valid_roles )
83
+
84
+ f_team = team(k)
85
+ team_id = f_team.dig('id')
86
+
87
+ next unless(( f_team.dig('status') == 200) && !check_keys.include?(team_id) && r == true )
88
+
89
+ check_keys << team_id
90
+
91
+ role_id = valid_roles.index(v)
92
+ role_id += 1
93
+ role_id += 1 if(v == 'Admin')
94
+
95
+ team << {
96
+ teamId: team_id,
97
+ permission: role_id
98
+ }
99
+ end
100
+ end
101
+
102
+ unless(c_user.nil?)
103
+ check_keys = []
104
+
105
+ c_user.uniq.each do |x|
106
+ k = x.keys.first
107
+ v = x.values.first
108
+ r = validate_hash( v, valid_roles )
109
+
110
+ f_user = user(k)
111
+ user_id = f_user.dig('id')
112
+
113
+ next unless(( f_user.dig('status') == 200) && !check_keys.include?(user_id) && r == true )
114
+
115
+ check_keys << user_id
116
+
117
+ role_id = valid_roles.index(v)
118
+ role_id += 1
119
+ role_id += 1 if(v == 'Admin')
120
+
121
+ user << {
122
+ userId: user_id,
123
+ permission: role_id
124
+ }
125
+ end
126
+ end
127
+
128
+ payload = {
129
+ items: team + user
130
+ }
131
+ payload.reject!{ |_, y| y.nil? }
132
+
133
+ endpoint = format( '/api/folders/%s/permissions', folder_uid )
134
+
135
+ post(endpoint, payload.to_json)
136
+ end
137
+
138
+
139
+ # private
140
+ # def validate_hash( value, valid_params )
141
+ #
142
+ # downcased = Set.new valid_params.map(&:downcase)
143
+ #
144
+ # unless( downcased.include?( value.downcase ) )
145
+ # return {
146
+ # 'status' => 404,
147
+ # 'message' => format( 'wrong permissions. Must be one of %s, given \'%s\'', valid_params.join(', '), value )
148
+ # }
149
+ # end
150
+ # true
151
+ # end
152
+
153
+ end
154
+ end
155
+
data/lib/grafana/login.rb CHANGED
@@ -67,58 +67,59 @@ module Grafana
67
67
  accept: 'application/json',
68
68
  cookies: response_cookies
69
69
  }
70
+ @username = username
71
+ @password = password
70
72
  end
71
73
 
72
74
  rescue SocketError
73
- if( retried < max_retries )
74
- retried += 1
75
- logger.debug( format( 'cannot login, socket error (retry %d / %d)', retried, max_retries ) ) if @debug
76
- sleep( sleep_between_retries )
77
- retry
78
- else
79
- raise format( 'Maximum retries (%d) against \'%s/login\' reached. Giving up ...', max_retries, @url )
80
- end
75
+ raise format( 'Maximum retries (%d) against \'%s/login\' reached. Giving up ...', max_retries, @url ) unless( retried < max_retries )
76
+
77
+ retried += 1
78
+ logger.debug( format( 'cannot login, socket error (retry %d / %d)', retried, max_retries ) ) if @debug
79
+ sleep( sleep_between_retries )
80
+ retry
81
81
 
82
82
  rescue RestClient::Unauthorized
83
83
  logger.debug( request_data.to_json ) if @debug
84
84
  raise format( 'Not authorized to connect \'%s\' - wrong username or password?', @url )
85
85
 
86
86
  rescue RestClient::BadGateway
87
- if( retried < max_retries )
88
- retried += 1
89
- logger.debug( format( 'cannot login, connection refused (retry %d / %d)', retried, max_retries ) ) if @debug
90
- sleep( sleep_between_retries )
91
- retry
92
- else
93
- raise format( 'Maximum retries (%d) against \'%s/login\' reached. Giving up ...', max_retries, @url )
94
- end
87
+ raise format( 'Maximum retries (%d) against \'%s/login\' reached. Giving up ...', max_retries, @url ) unless( retried < max_retries )
88
+
89
+ retried += 1
90
+ logger.debug( format( 'cannot login, connection refused (retry %d / %d)', retried, max_retries ) ) if @debug
91
+ sleep( sleep_between_retries )
92
+ retry
95
93
 
96
94
  rescue Errno::ECONNREFUSED
97
- if( retried < max_retries )
98
- retried += 1
99
- logger.debug( format( 'cannot login, connection refused (retry %d / %d)', retried, max_retries ) ) if @debug
100
- sleep( sleep_between_retries )
101
- retry
102
- else
103
- raise format( 'Maximum retries (%d) against \'%s/login\' reached. Giving up ...', max_retries, @url )
104
- end
95
+ raise format( 'Maximum retries (%d) against \'%s/login\' reached. Giving up ...', max_retries, @url ) unless( retried < max_retries )
96
+
97
+ retried += 1
98
+ logger.debug( format( 'cannot login, connection refused (retry %d / %d)', retried, max_retries ) ) if @debug
99
+ sleep( sleep_between_retries )
100
+ retry
105
101
 
106
102
  rescue Errno::EHOSTUNREACH
107
- if( retried < max_retries )
108
- retried += 1
109
- logger.debug( format( 'cannot login, host unreachable (retry %d / %d)', retried, max_retries ) ) if @debug
110
- sleep( sleep_between_retries )
111
- retry
112
- else
113
- raise format( 'Maximum retries (%d) against \'%s/login\' reached. Giving up ...', max_retries, @url )
114
- end
103
+ raise format( 'Maximum retries (%d) against \'%s/login\' reached. Giving up ...', max_retries, @url ) unless( retried < max_retries )
104
+
105
+ retried += 1
106
+ logger.debug( format( 'cannot login, host unreachable (retry %d / %d)', retried, max_retries ) ) if @debug
107
+ sleep( sleep_between_retries )
108
+ retry
109
+
110
+ rescue => error
111
+ raise format( 'Maximum retries (%d) against \'%s/login\' reached. Giving up ...', max_retries, @url ) unless( retried < max_retries )
112
+
113
+ retried += 1
114
+ logger.error( error )
115
+ logger.debug( format( 'cannot login (retry %d / %d)', retried, max_retries ) ) if @debug
116
+ sleep( sleep_between_retries )
117
+ retry
115
118
  end
116
119
 
117
120
  logger.debug('User session initiated') if @debug
118
-
119
121
  return true
120
122
  end
121
-
122
123
  false
123
124
  end
124
125
 
@@ -130,10 +131,15 @@ module Grafana
130
131
  # @return [Hash]
131
132
  #
132
133
  def ping_session
133
- endpoint = '/api/login/ping'
134
134
  logger.debug( "Pinging current session (GET #{endpoint})" ) if @debug
135
+ endpoint = '/api/login/ping'
135
136
  get( endpoint )
136
137
  end
138
+
139
+
140
+ def headers
141
+ @headers
142
+ end
137
143
  end
138
144
 
139
145
  end