Completed
Push — master ( 1806d1...053f07 )
by Nicolas
01:42
created

GlancesBottle   B

Complexity

Total Complexity 48

Size/Duplication

Total Lines 382
Duplicated Lines 0 %

Importance

Changes 3
Bugs 0 Features 0
Metric Value
c 3
b 0
f 0
dl 0
loc 382
rs 8.4864
wmc 48

25 Methods

Rating   Name   Duplication   Size   Complexity  
A start() 0 13 1
A _api_help() 0 14 2
A __init__() 0 20 2
A _favicon() 0 4 1
A _css() 0 4 1
A _html() 0 4 1
A _js_map() 0 4 1
B _api_plugins() 0 35 2
A _api_value() 0 9 1
A _api_limits() 0 22 3
A _api_all_limits() 0 16 2
A _api() 0 22 3
A _index() 0 7 1
A _js() 0 4 1
A _api_views() 0 22 3
A _api_all_views() 0 16 2
A end() 0 3 1
A check_auth() 0 8 2
A _images() 0 4 1
A _api_args() 0 17 2
A _api_item() 0 10 1
B _api_itemvalue() 0 22 5
B _route() 0 26 1
B _api_all() 0 27 5
A _api_args_item() 0 21 3

How to fix   Complexity   

Complex Class

Complex classes like GlancesBottle often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

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')
61
62
    def check_auth(self, username, password):
63
        """Check if a username/password combination is valid."""
64
        if username == self.args.username:
65
            from glances.password import GlancesPassword
66
            pwd = GlancesPassword()
67
            return pwd.check_password(self.args.password, pwd.sha256_hash(password))
68
        else:
69
            return False
70
71
    def _route(self):
72
        """Define route."""
73
        self._app.route('/', method="GET", callback=self._index)
74
        self._app.route('/<refresh_time:int>', method=["GET"], callback=self._index)
75
76
        self._app.route('/<filename:re:.*\.css>', method="GET", callback=self._css)
77
        self._app.route('/<filename:re:.*\.js>', method="GET", callback=self._js)
78
        self._app.route('/<filename:re:.*\.js.map>', method="GET", callback=self._js_map)
79
        self._app.route('/<filename:re:.*\.html>', method="GET", callback=self._html)
80
81
        self._app.route('/<filename:re:.*\.png>', method="GET", callback=self._images)
82
        self._app.route('/favicon.ico', method="GET", callback=self._favicon)
83
84
        # REST API
85
        self._app.route('/api/2/args', method="GET", callback=self._api_args)
86
        self._app.route('/api/2/args/:item', method="GET", callback=self._api_args_item)
87
        self._app.route('/api/2/help', method="GET", callback=self._api_help)
88
        self._app.route('/api/2/pluginslist', method="GET", callback=self._api_plugins)
89
        self._app.route('/api/2/all', method="GET", callback=self._api_all)
90
        self._app.route('/api/2/all/limits', method="GET", callback=self._api_all_limits)
91
        self._app.route('/api/2/all/views', method="GET", callback=self._api_all_views)
92
        self._app.route('/api/2/:plugin', method="GET", callback=self._api)
93
        self._app.route('/api/2/:plugin/limits', method="GET", callback=self._api_limits)
94
        self._app.route('/api/2/:plugin/views', method="GET", callback=self._api_views)
95
        self._app.route('/api/2/:plugin/:item', method="GET", callback=self._api_item)
96
        self._app.route('/api/2/:plugin/:item/:value', method="GET", callback=self._api_value)
97
98
    def start(self, stats):
99
        """Start the bottle."""
100
        # Init stats
101
        self.stats = stats
102
103
        # Init plugin list
104
        self.plugins_list = self.stats.getAllPlugins()
105
106
        # Bind the Bottle TCP address/port
107
        bindmsg = 'Glances web server started on http://{0}:{1}/'.format(self.args.bind_address, self.args.port)
108
        logger.info(bindmsg)
109
        print(bindmsg)
110
        self._app.run(host=self.args.bind_address, port=self.args.port, quiet=not self.args.debug)
111
112
    def end(self):
113
        """End the bottle."""
114
        pass
115
116
    def _index(self, refresh_time=None):
117
        """Bottle callback for index.html (/) file."""
118
        # Update the stat
119
        self.stats.update()
120
121
        # Display
122
        return static_file("index.html", root=os.path.join(self.STATIC_PATH, 'html'))
123
124
    def _html(self, filename):
125
        """Bottle callback for *.html files."""
126
        # Return the static file
127
        return static_file(filename, root=os.path.join(self.STATIC_PATH, 'html'))
128
129
    def _css(self, filename):
130
        """Bottle callback for *.css files."""
131
        # Return the static file
132
        return static_file(filename, root=os.path.join(self.STATIC_PATH, 'css'))
133
134
    def _js(self, filename):
135
        """Bottle callback for *.js files."""
136
        # Return the static file
137
        return static_file(filename, root=os.path.join(self.STATIC_PATH, 'js'))
138
139
    def _js_map(self, filename):
140
        """Bottle callback for *.js.map files."""
141
        # Return the static file
142
        return static_file(filename, root=os.path.join(self.STATIC_PATH, 'js'))
143
144
    def _images(self, filename):
145
        """Bottle callback for *.png files."""
146
        # Return the static file
147
        return static_file(filename, root=os.path.join(self.STATIC_PATH, 'images'))
148
149
    def _favicon(self):
150
        """Bottle callback for favicon."""
151
        # Return the static file
152
        return static_file('favicon.ico', root=self.STATIC_PATH)
153
154
    def _api_help(self):
155
        """Glances API RESTFul implementation.
156
157
        Return the help data or 404 error.
158
        """
159
        response.content_type = 'application/json'
160
161
        # Update the stat
162
        view_data = self.stats.get_plugin("help").get_view_data()
163
        try:
164
            plist = json.dumps(view_data, sort_keys=True)
165
        except Exception as e:
166
            abort(404, "Cannot get help view data (%s)" % str(e))
167
        return plist
168
169
    def _api_plugins(self):
170
        """
171
        @api {get} /api/2/pluginslist Get plugins list
172
        @apiVersion 2.0
173
        @apiName pluginslist
174
        @apiGroup plugin
175
176
        @apiSuccess {String[]} Plugins list.
177
178
        @apiSuccessExample Success-Response:
179
            HTTP/1.1 200 OK
180
            [
181
               "load",
182
               "help",
183
               "ip",
184
               "memswap",
185
               "processlist",
186
               ...
187
            ]
188
189
         @apiError Cannot get plugin list.
190
191
         @apiErrorExample Error-Response:
192
            HTTP/1.1 404 Not Found
193
        """
194
        response.content_type = 'application/json'
195
196
        # Update the stat
197
        self.stats.update()
198
199
        try:
200
            plist = json.dumps(self.plugins_list)
201
        except Exception as e:
202
            abort(404, "Cannot get plugin list (%s)" % str(e))
203
        return plist
204
205
    def _api_all(self):
206
        """Glances API RESTFul implementation.
207
208
        Return the JSON representation of all the plugins
209
        HTTP/200 if OK
210
        HTTP/400 if plugin is not found
211
        HTTP/404 if others error
212
        """
213
        response.content_type = 'application/json'
214
215
        if self.args.debug:
216
            fname = os.path.join(tempfile.gettempdir(), 'glances-debug.json')
217
            try:
218
                with open(fname) as f:
219
                    return f.read()
220
            except IOError:
221
                logger.debug("Debug file (%s) not found" % fname)
222
223
        # Update the stat
224
        self.stats.update()
225
226
        try:
227
            # Get the JSON value of the stat ID
228
            statval = json.dumps(self.stats.getAllAsDict())
229
        except Exception as e:
230
            abort(404, "Cannot get stats (%s)" % str(e))
231
        return statval
232
233
    def _api_all_limits(self):
234
        """Glances API RESTFul implementation.
235
236
        Return the JSON representation of all the plugins limits
237
        HTTP/200 if OK
238
        HTTP/400 if plugin is not found
239
        HTTP/404 if others error
240
        """
241
        response.content_type = 'application/json'
242
243
        try:
244
            # Get the JSON value of the stat limits
245
            limits = json.dumps(self.stats.getAllLimitsAsDict())
246
        except Exception as e:
247
            abort(404, "Cannot get limits (%s)" % (str(e)))
248
        return limits
249
250
    def _api_all_views(self):
251
        """Glances API RESTFul implementation.
252
253
        Return the JSON representation of all the plugins views
254
        HTTP/200 if OK
255
        HTTP/400 if plugin is not found
256
        HTTP/404 if others error
257
        """
258
        response.content_type = 'application/json'
259
260
        try:
261
            # Get the JSON value of the stat view
262
            limits = json.dumps(self.stats.getAllViewsAsDict())
263
        except Exception as e:
264
            abort(404, "Cannot get views (%s)" % (str(e)))
265
        return limits
266
267
    def _api(self, plugin):
268
        """Glances API RESTFul implementation.
269
270
        Return the JSON representation of a given plugin
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()
286
        except Exception as e:
287
            abort(404, "Cannot get plugin %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):
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
            ret = self.stats.get_plugin(plugin).get_stats_item(item)
348
349
            if ret is None:
350
                abort(404, "Cannot get item %s in plugin %s" % (item, plugin))
351
        else:
352
            ret = self.stats.get_plugin(plugin).get_stats_value(item, value)
353
354
            if ret is None:
355
                abort(404, "Cannot get item(%s)=value(%s) in plugin %s" % (item, value, plugin))
356
357
        return ret
358
359
    def _api_item(self, plugin, item):
360
        """Glances API RESTFul implementation.
361
362
        Return the JSON represenation of the couple plugin/item
363
        HTTP/200 if OK
364
        HTTP/400 if plugin is not found
365
        HTTP/404 if others error
366
367
        """
368
        return self._api_itemvalue(plugin, item)
369
370
    def _api_value(self, plugin, item, value):
371
        """Glances API RESTFul implementation.
372
373
        Return the process stats (dict) for the given item=value
374
        HTTP/200 if OK
375
        HTTP/400 if plugin is not found
376
        HTTP/404 if others error
377
        """
378
        return self._api_itemvalue(plugin, item, value)
379
380
    def _api_args(self):
381
        """Glances API RESTFul implementation.
382
383
        Return the JSON representation of the Glances command line arguments
384
        HTTP/200 if OK
385
        HTTP/404 if others error
386
        """
387
        response.content_type = 'application/json'
388
389
        try:
390
            # Get the JSON value of the args' dict
391
            # Use vars to convert namespace to dict
392
            # Source: https://docs.python.org/2/library/functions.html#vars
393
            args_json = json.dumps(vars(self.args))
394
        except Exception as e:
395
            abort(404, "Cannot get args (%s)" % str(e))
396
        return args_json
397
398
    def _api_args_item(self, item):
399
        """Glances API RESTFul implementation.
400
401
        Return the JSON representation of the Glances command line arguments item
402
        HTTP/200 if OK
403
        HTTP/400 if item is not found
404
        HTTP/404 if others error
405
        """
406
        response.content_type = 'application/json'
407
408
        if item not in self.args:
409
            abort(400, "Unknown item %s" % item)
410
411
        try:
412
            # Get the JSON value of the args' dict
413
            # Use vars to convert namespace to dict
414
            # Source: https://docs.python.org/2/library/functions.html#vars
415
            args_json = json.dumps(vars(self.args)[item])
416
        except Exception as e:
417
            abort(404, "Cannot get args item (%s)" % str(e))
418
        return args_json
419
420
421
class EnableCors(object):
422
    name = 'enable_cors'
423
    api = 2
424
425
    def apply(self, fn, context):
426
        def _enable_cors(*args, **kwargs):
427
            # set CORS headers
428
            response.headers['Access-Control-Allow-Origin'] = '*'
429
            response.headers['Access-Control-Allow-Methods'] = 'GET, POST, PUT, OPTIONS'
430
            response.headers['Access-Control-Allow-Headers'] = 'Origin, Accept, Content-Type, X-Requested-With, X-CSRF-Token'
431
432
            if request.method != 'OPTIONS':
433
                # actual request; reply with the actual response
434
                return fn(*args, **kwargs)
435
436
        return _enable_cors
437