Completed
Push — master ( 3dcd25...20576f )
by Nicolas
01:26
created

GlancesBottle._api_itemvalue()   F

Complexity

Conditions 9

Size

Total Lines 29

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 9
c 1
b 0
f 0
dl 0
loc 29
rs 3
1
# -*- coding: utf-8 -*-
2
#
3
# This file is part of Glances.
4
#
5
# Copyright (C) 2015 Nicolargo <[email protected]>
6
#
7
# Glances is free software; you can redistribute it and/or modify
8
# it under the terms of the GNU Lesser General Public License as published by
9
# the Free Software Foundation, either version 3 of the License, or
10
# (at your option) any later version.
11
#
12
# Glances is distributed in the hope that it will be useful,
13
# but WITHOUT ANY WARRANTY; without even the implied warranty of
14
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15
# GNU Lesser General Public License for more details.
16
#
17
# You should have received a copy of the GNU Lesser General Public License
18
# along with this program. If not, see <http://www.gnu.org/licenses/>.
19
20
"""Web interface class."""
21
22
import json
23
import os
24
import sys
25
import tempfile
26
from io import open
27
28
from glances.logger import logger
29
30
try:
31
    from bottle import Bottle, static_file, abort, response, request, auth_basic
32
except ImportError:
33
    logger.critical('Bottle module not found. Glances cannot start in web server mode.')
34
    sys.exit(2)
35
36
37
class GlancesBottle(object):
38
39
    """This class manages the Bottle Web server."""
40
41
    def __init__(self, args=None):
42
        # Init args
43
        self.args = args
44
45
        # Init stats
46
        # Will be updated within Bottle route
47
        self.stats = None
48
49
        # Init Bottle
50
        self._app = Bottle()
51
        # Enable CORS (issue #479)
52
        self._app.install(EnableCors())
53
        # Password
54
        if args.password != '':
55
            self._app.install(auth_basic(self.check_auth))
56
        # Define routes
57
        self._route()
58
59
        # Path where the statics files are stored
60
        self.STATIC_PATH = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'static/public')
61
62
    def app(self):
63
        return self._app()
64
65
    def check_auth(self, username, password):
66
        """Check if a username/password combination is valid."""
67
        if username == self.args.username:
68
            from glances.password import GlancesPassword
69
            pwd = GlancesPassword()
70
            return pwd.check_password(self.args.password, pwd.sha256_hash(password))
71
        else:
72
            return False
73
74
    def _route(self):
75
        """Define route."""
76
        self._app.route('/', method="GET", callback=self._index)
77
        self._app.route('/<refresh_time:int>', method=["GET"], callback=self._index)
78
79
        # REST API
80
        self._app.route('/api/2/args', method="GET", callback=self._api_args)
81
        self._app.route('/api/2/args/<item>', method="GET", callback=self._api_args_item)
82
        self._app.route('/api/2/help', method="GET", callback=self._api_help)
83
        self._app.route('/api/2/pluginslist', method="GET", callback=self._api_plugins)
84
        self._app.route('/api/2/all', method="GET", callback=self._api_all)
85
        self._app.route('/api/2/all/limits', method="GET", callback=self._api_all_limits)
86
        self._app.route('/api/2/all/views', method="GET", callback=self._api_all_views)
87
        self._app.route('/api/2/<plugin>', method="GET", callback=self._api)
88
        self._app.route('/api/2/<plugin>/history', method="GET", callback=self._api_history)
89
        self._app.route('/api/2/<plugin>/history/<nb:int>', method="GET", callback=self._api_history)
90
        self._app.route('/api/2/<plugin>/limits', method="GET", callback=self._api_limits)
91
        self._app.route('/api/2/<plugin>/views', method="GET", callback=self._api_views)
92
        self._app.route('/api/2/<plugin>/<item>', method="GET", callback=self._api_item)
93
        self._app.route('/api/2/<plugin>/<item>/history', method="GET", callback=self._api_item_history)
94
        self._app.route('/api/2/<plugin>/<item>/history/<nb:int>', method="GET", callback=self._api_item_history)
95
        self._app.route('/api/2/<plugin>/<item>/<value>', method="GET", callback=self._api_value)
96
97
        self._app.route('/<filepath:path>', method="GET", callback=self._resource)
98
99
    def start(self, stats):
100
        """Start the bottle."""
101
        # Init stats
102
        self.stats = stats
103
104
        # Init plugin list
105
        self.plugins_list = self.stats.getAllPlugins()
106
107
        # Bind the Bottle TCP address/port
108
        bindmsg = 'Glances web server started on http://{}:{}/'.format(self.args.bind_address, self.args.port)
109
        logger.info(bindmsg)
110
        print(bindmsg)
111
        self._app.run(host=self.args.bind_address, port=self.args.port, quiet=not self.args.debug)
112
113
    def end(self):
114
        """End the bottle."""
115
        pass
116
117
    def _index(self, refresh_time=None):
118
        """Bottle callback for index.html (/) file."""
119
        # Update the stat
120
        self.stats.update()
121
122
        # Display
123
        return static_file("index.html", root=self.STATIC_PATH)
124
125
    def _resource(self, filepath):
126
        """Bottle callback for resources files."""
127
        # Return the static file
128
        return static_file(filepath, root=self.STATIC_PATH)
129
130
    def _api_help(self):
131
        """Glances API RESTFul implementation.
132
133
        Return the help data or 404 error.
134
        """
135
        response.content_type = 'application/json'
136
137
        # Update the stat
138
        view_data = self.stats.get_plugin("help").get_view_data()
139
        try:
140
            plist = json.dumps(view_data, sort_keys=True)
141
        except Exception as e:
142
            abort(404, "Cannot get help view data (%s)" % str(e))
143
        return plist
144
145
    def _api_plugins(self):
146
        """
147
        @api {get} /api/2/pluginslist Get plugins list
148
        @apiVersion 2.0
149
        @apiName pluginslist
150
        @apiGroup plugin
151
152
        @apiSuccess {String[]} Plugins list.
153
154
        @apiSuccessExample Success-Response:
155
            HTTP/1.1 200 OK
156
            [
157
               "load",
158
               "help",
159
               "ip",
160
               "memswap",
161
               "processlist",
162
               ...
163
            ]
164
165
         @apiError Cannot get plugin list.
166
167
         @apiErrorExample Error-Response:
168
            HTTP/1.1 404 Not Found
169
        """
170
        response.content_type = 'application/json'
171
172
        # Update the stat
173
        self.stats.update()
174
175
        try:
176
            plist = json.dumps(self.plugins_list)
177
        except Exception as e:
178
            abort(404, "Cannot get plugin list (%s)" % str(e))
179
        return plist
180
181
    def _api_all(self):
182
        """Glances API RESTFul implementation.
183
184
        Return the JSON representation of all the plugins
185
        HTTP/200 if OK
186
        HTTP/400 if plugin is not found
187
        HTTP/404 if others error
188
        """
189
        response.content_type = 'application/json'
190
191
        if self.args.debug:
192
            fname = os.path.join(tempfile.gettempdir(), 'glances-debug.json')
193
            try:
194
                with open(fname) as f:
195
                    return f.read()
196
            except IOError:
197
                logger.debug("Debug file (%s) not found" % fname)
198
199
        # Update the stat
200
        self.stats.update()
201
202
        try:
203
            # Get the JSON value of the stat ID
204
            statval = json.dumps(self.stats.getAllAsDict())
205
        except Exception as e:
206
            abort(404, "Cannot get stats (%s)" % str(e))
207
        return statval
208
209
    def _api_all_limits(self):
210
        """Glances API RESTFul implementation.
211
212
        Return the JSON representation of all the plugins limits
213
        HTTP/200 if OK
214
        HTTP/400 if plugin is not found
215
        HTTP/404 if others error
216
        """
217
        response.content_type = 'application/json'
218
219
        try:
220
            # Get the JSON value of the stat limits
221
            limits = json.dumps(self.stats.getAllLimitsAsDict())
222
        except Exception as e:
223
            abort(404, "Cannot get limits (%s)" % (str(e)))
224
        return limits
225
226
    def _api_all_views(self):
227
        """Glances API RESTFul implementation.
228
229
        Return the JSON representation of all the plugins views
230
        HTTP/200 if OK
231
        HTTP/400 if plugin is not found
232
        HTTP/404 if others error
233
        """
234
        response.content_type = 'application/json'
235
236
        try:
237
            # Get the JSON value of the stat view
238
            limits = json.dumps(self.stats.getAllViewsAsDict())
239
        except Exception as e:
240
            abort(404, "Cannot get views (%s)" % (str(e)))
241
        return limits
242
243
    def _api(self, plugin):
244
        """Glances API RESTFul implementation.
245
246
        Return the JSON representation of a given plugin
247
        HTTP/200 if OK
248
        HTTP/400 if plugin is not found
249
        HTTP/404 if others error
250
        """
251
        response.content_type = 'application/json'
252
253
        if plugin not in self.plugins_list:
254
            abort(400, "Unknown plugin %s (available plugins: %s)" % (plugin, self.plugins_list))
255
256
        # Update the stat
257
        self.stats.update()
258
259
        try:
260
            # Get the JSON value of the stat ID
261
            statval = self.stats.get_plugin(plugin).get_stats()
262
        except Exception as e:
263
            abort(404, "Cannot get plugin %s (%s)" % (plugin, str(e)))
264
        return statval
265
266
    def _api_history(self, plugin, nb=0):
267
        """Glances API RESTFul implementation.
268
269
        Return the JSON representation of a given plugin history
270
        Limit to the last nb items (all if nb=0)
271
        HTTP/200 if OK
272
        HTTP/400 if plugin is not found
273
        HTTP/404 if others error
274
        """
275
        response.content_type = 'application/json'
276
277
        if plugin not in self.plugins_list:
278
            abort(400, "Unknown plugin %s (available plugins: %s)" % (plugin, self.plugins_list))
279
280
        # Update the stat
281
        self.stats.update()
282
283
        try:
284
            # Get the JSON value of the stat ID
285
            statval = self.stats.get_plugin(plugin).get_stats_history(nb=int(nb))
286
        except Exception as e:
287
            abort(404, "Cannot get plugin history %s (%s)" % (plugin, str(e)))
288
        return statval
289
290
    def _api_limits(self, plugin):
291
        """Glances API RESTFul implementation.
292
293
        Return the JSON limits of a given plugin
294
        HTTP/200 if OK
295
        HTTP/400 if plugin is not found
296
        HTTP/404 if others error
297
        """
298
        response.content_type = 'application/json'
299
300
        if plugin not in self.plugins_list:
301
            abort(400, "Unknown plugin %s (available plugins: %s)" % (plugin, self.plugins_list))
302
303
        # Update the stat
304
        # self.stats.update()
305
306
        try:
307
            # Get the JSON value of the stat limits
308
            ret = self.stats.get_plugin(plugin).limits
309
        except Exception as e:
310
            abort(404, "Cannot get limits for plugin %s (%s)" % (plugin, str(e)))
311
        return ret
312
313
    def _api_views(self, plugin):
314
        """Glances API RESTFul implementation.
315
316
        Return the JSON views of a given plugin
317
        HTTP/200 if OK
318
        HTTP/400 if plugin is not found
319
        HTTP/404 if others error
320
        """
321
        response.content_type = 'application/json'
322
323
        if plugin not in self.plugins_list:
324
            abort(400, "Unknown plugin %s (available plugins: %s)" % (plugin, self.plugins_list))
325
326
        # Update the stat
327
        # self.stats.update()
328
329
        try:
330
            # Get the JSON value of the stat views
331
            ret = self.stats.get_plugin(plugin).get_views()
332
        except Exception as e:
333
            abort(404, "Cannot get views for plugin %s (%s)" % (plugin, str(e)))
334
        return ret
335
336
    def _api_itemvalue(self, plugin, item, value=None, history=False, nb=0):
337
        """Father method for _api_item and _api_value"""
338
        response.content_type = 'application/json'
339
340
        if plugin not in self.plugins_list:
341
            abort(400, "Unknown plugin %s (available plugins: %s)" % (plugin, self.plugins_list))
342
343
        # Update the stat
344
        self.stats.update()
345
346
        if value is None:
347
            if history:
348
                ret = self.stats.get_plugin(plugin).get_stats_history(item, nb=int(nb))
349
            else:
350
                ret = self.stats.get_plugin(plugin).get_stats_item(item)
351
352
            if ret is None:
353
                abort(404, "Cannot get item %s%s in plugin %s" % (item, 'history ' if history else '', plugin))
354
        else:
355
            if history:
356
                # Not available
357
                ret = None
358
            else:
359
                ret = self.stats.get_plugin(plugin).get_stats_value(item, value)
360
361
            if ret is None:
362
                abort(404, "Cannot get item %s(%s=%s) in plugin %s" % ('history ' if history else '', item, value, plugin))
363
364
        return ret
365
366
    def _api_item(self, plugin, item):
367
        """Glances API RESTFul implementation.
368
369
        Return the JSON representation of the couple plugin/item
370
        HTTP/200 if OK
371
        HTTP/400 if plugin is not found
372
        HTTP/404 if others error
373
374
        """
375
        return self._api_itemvalue(plugin, item)
376
377
    def _api_item_history(self, plugin, item, nb=0):
378
        """Glances API RESTFul implementation.
379
380
        Return the JSON representation of the couple plugin/history of item
381
        HTTP/200 if OK
382
        HTTP/400 if plugin is not found
383
        HTTP/404 if others error
384
385
        """
386
        return self._api_itemvalue(plugin, item, history=True, nb=int(nb))
387
388
    def _api_value(self, plugin, item, value):
389
        """Glances API RESTFul implementation.
390
391
        Return the process stats (dict) for the given item=value
392
        HTTP/200 if OK
393
        HTTP/400 if plugin is not found
394
        HTTP/404 if others error
395
        """
396
        return self._api_itemvalue(plugin, item, value)
397
398
    def _api_args(self):
399
        """Glances API RESTFul implementation.
400
401
        Return the JSON representation of the Glances command line arguments
402
        HTTP/200 if OK
403
        HTTP/404 if others error
404
        """
405
        response.content_type = 'application/json'
406
407
        try:
408
            # Get the JSON value of the args' dict
409
            # Use vars to convert namespace to dict
410
            # Source: https://docs.python.org/2/library/functions.html#vars
411
            args_json = json.dumps(vars(self.args))
412
        except Exception as e:
413
            abort(404, "Cannot get args (%s)" % str(e))
414
        return args_json
415
416
    def _api_args_item(self, item):
417
        """Glances API RESTFul implementation.
418
419
        Return the JSON representation of the Glances command line arguments item
420
        HTTP/200 if OK
421
        HTTP/400 if item is not found
422
        HTTP/404 if others error
423
        """
424
        response.content_type = 'application/json'
425
426
        if item not in self.args:
427
            abort(400, "Unknown item %s" % item)
428
429
        try:
430
            # Get the JSON value of the args' dict
431
            # Use vars to convert namespace to dict
432
            # Source: https://docs.python.org/2/library/functions.html#vars
433
            args_json = json.dumps(vars(self.args)[item])
434
        except Exception as e:
435
            abort(404, "Cannot get args item (%s)" % str(e))
436
        return args_json
437
438
439
class EnableCors(object):
440
    name = 'enable_cors'
441
    api = 2
442
443
    def apply(self, fn, context):
444
        def _enable_cors(*args, **kwargs):
445
            # set CORS headers
446
            response.headers['Access-Control-Allow-Origin'] = '*'
447
            response.headers['Access-Control-Allow-Methods'] = 'GET, POST, PUT, OPTIONS'
448
            response.headers['Access-Control-Allow-Headers'] = 'Origin, Accept, Content-Type, X-Requested-With, X-CSRF-Token'
449
450
            if request.method != 'OPTIONS':
451
                # actual request; reply with the actual response
452
                return fn(*args, **kwargs)
453
454
        return _enable_cors
455