| Total Complexity | 48 |
| Total Lines | 382 |
| Duplicated Lines | 0 % |
Complex classes like glances.outputs.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 -*- |
||
| 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 | |||
| 437 |