| 1 | # -*- coding: utf-8 -*- |
|---|
| 2 | |
|---|
| 3 | # ########################################################## |
|---|
| 4 | # ## make sure administrator is on localhost |
|---|
| 5 | # ########################################################### |
|---|
| 6 | |
|---|
| 7 | import os |
|---|
| 8 | import socket |
|---|
| 9 | import datetime |
|---|
| 10 | import copy |
|---|
| 11 | import gluon.contenttype |
|---|
| 12 | import gluon.fileutils |
|---|
| 13 | from gluon._compat import iteritems |
|---|
| 14 | |
|---|
| 15 | is_gae = request.env.web2py_runtime_gae or False |
|---|
| 16 | |
|---|
| 17 | # ## critical --- make a copy of the environment |
|---|
| 18 | |
|---|
| 19 | global_env = copy.copy(globals()) |
|---|
| 20 | global_env['datetime'] = datetime |
|---|
| 21 | |
|---|
| 22 | http_host = request.env.http_host.split(':')[0] |
|---|
| 23 | remote_addr = request.env.remote_addr |
|---|
| 24 | try: |
|---|
| 25 | hosts = (http_host, socket.gethostname(), |
|---|
| 26 | socket.gethostbyname(http_host), |
|---|
| 27 | '::1', '127.0.0.1', '::ffff:127.0.0.1') |
|---|
| 28 | except: |
|---|
| 29 | hosts = (http_host, ) |
|---|
| 30 | |
|---|
| 31 | if request.is_https: |
|---|
| 32 | session.secure() |
|---|
| 33 | elif (remote_addr not in hosts) and (remote_addr != "127.0.0.1") and \ |
|---|
| 34 | (request.function != 'manage'): |
|---|
| 35 | raise HTTP(200, T('appadmin is disabled because insecure channel')) |
|---|
| 36 | |
|---|
| 37 | if request.function == 'manage': |
|---|
| 38 | if not 'auth' in globals() or not request.args: |
|---|
| 39 | redirect(URL(request.controller, 'index')) |
|---|
| 40 | manager_action = auth.settings.manager_actions.get(request.args(0), None) |
|---|
| 41 | if manager_action is None and request.args(0) == 'auth': |
|---|
| 42 | manager_action = dict(role=auth.settings.auth_manager_role, |
|---|
| 43 | heading=T('Manage Access Control'), |
|---|
| 44 | tables=[auth.table_user(), |
|---|
| 45 | auth.table_group(), |
|---|
| 46 | auth.table_permission()]) |
|---|
| 47 | manager_role = manager_action.get('role', None) if manager_action else None |
|---|
| 48 | if not (gluon.fileutils.check_credentials(request) or auth.has_membership(manager_role)): |
|---|
| 49 | raise HTTP(403, "Not authorized") |
|---|
| 50 | menu = False |
|---|
| 51 | elif (request.application == 'admin' and not session.authorized) or \ |
|---|
| 52 | (request.application != 'admin' and not gluon.fileutils.check_credentials(request)): |
|---|
| 53 | redirect(URL('admin', 'default', 'index', |
|---|
| 54 | vars=dict(send=URL(args=request.args, vars=request.vars)))) |
|---|
| 55 | else: |
|---|
| 56 | response.subtitle = T('Database Administration (appadmin)') |
|---|
| 57 | menu = True |
|---|
| 58 | |
|---|
| 59 | ignore_rw = True |
|---|
| 60 | response.view = 'appadmin.html' |
|---|
| 61 | if menu: |
|---|
| 62 | response.menu = [[T('design'), False, URL('admin', 'default', 'design', |
|---|
| 63 | args=[request.application])], [T('db'), False, |
|---|
| 64 | URL('index')], [T('state'), False, |
|---|
| 65 | URL('state')], [T('cache'), False, |
|---|
| 66 | URL('ccache')]] |
|---|
| 67 | |
|---|
| 68 | # ########################################################## |
|---|
| 69 | # ## auxiliary functions |
|---|
| 70 | # ########################################################### |
|---|
| 71 | |
|---|
| 72 | if False and request.tickets_db: |
|---|
| 73 | from gluon.restricted import TicketStorage |
|---|
| 74 | ts = TicketStorage() |
|---|
| 75 | ts._get_table(request.tickets_db, ts.tablename, request.application) |
|---|
| 76 | |
|---|
| 77 | def get_databases(request): |
|---|
| 78 | dbs = {} |
|---|
| 79 | for (key, value) in global_env.items(): |
|---|
| 80 | try: |
|---|
| 81 | cond = isinstance(value, GQLDB) |
|---|
| 82 | except: |
|---|
| 83 | cond = isinstance(value, SQLDB) |
|---|
| 84 | if cond: |
|---|
| 85 | dbs[key] = value |
|---|
| 86 | return dbs |
|---|
| 87 | |
|---|
| 88 | databases = get_databases(None) |
|---|
| 89 | |
|---|
| 90 | def eval_in_global_env(text): |
|---|
| 91 | exec ('_ret=%s' % text, {}, global_env) |
|---|
| 92 | return global_env['_ret'] |
|---|
| 93 | |
|---|
| 94 | |
|---|
| 95 | def get_database(request): |
|---|
| 96 | if request.args and request.args[0] in databases: |
|---|
| 97 | return eval_in_global_env(request.args[0]) |
|---|
| 98 | else: |
|---|
| 99 | session.flash = T('invalid request') |
|---|
| 100 | redirect(URL('index')) |
|---|
| 101 | |
|---|
| 102 | def get_table(request): |
|---|
| 103 | db = get_database(request) |
|---|
| 104 | if len(request.args) > 1 and request.args[1] in db.tables: |
|---|
| 105 | return (db, request.args[1]) |
|---|
| 106 | else: |
|---|
| 107 | session.flash = T('invalid request') |
|---|
| 108 | redirect(URL('index')) |
|---|
| 109 | |
|---|
| 110 | |
|---|
| 111 | def get_query(request): |
|---|
| 112 | try: |
|---|
| 113 | return eval_in_global_env(request.vars.query) |
|---|
| 114 | except Exception: |
|---|
| 115 | return None |
|---|
| 116 | |
|---|
| 117 | |
|---|
| 118 | def query_by_table_type(tablename, db, request=request): |
|---|
| 119 | keyed = hasattr(db[tablename], '_primarykey') |
|---|
| 120 | if keyed: |
|---|
| 121 | firstkey = db[tablename][db[tablename]._primarykey[0]] |
|---|
| 122 | cond = '>0' |
|---|
| 123 | if firstkey.type in ['string', 'text']: |
|---|
| 124 | cond = '!=""' |
|---|
| 125 | qry = '%s.%s.%s%s' % ( |
|---|
| 126 | request.args[0], request.args[1], firstkey.name, cond) |
|---|
| 127 | else: |
|---|
| 128 | qry = '%s.%s.id>0' % tuple(request.args[:2]) |
|---|
| 129 | return qry |
|---|
| 130 | |
|---|
| 131 | |
|---|
| 132 | # ########################################################## |
|---|
| 133 | # ## list all databases and tables |
|---|
| 134 | # ########################################################### |
|---|
| 135 | def index(): |
|---|
| 136 | return dict(databases=databases) |
|---|
| 137 | |
|---|
| 138 | |
|---|
| 139 | # ########################################################## |
|---|
| 140 | # ## insert a new record |
|---|
| 141 | # ########################################################### |
|---|
| 142 | |
|---|
| 143 | |
|---|
| 144 | def insert(): |
|---|
| 145 | (db, table) = get_table(request) |
|---|
| 146 | form = SQLFORM(db[table], ignore_rw=ignore_rw) |
|---|
| 147 | if form.accepts(request.vars, session): |
|---|
| 148 | response.flash = T('new record inserted') |
|---|
| 149 | return dict(form=form, table=db[table]) |
|---|
| 150 | |
|---|
| 151 | |
|---|
| 152 | # ########################################################## |
|---|
| 153 | # ## list all records in table and insert new record |
|---|
| 154 | # ########################################################### |
|---|
| 155 | |
|---|
| 156 | |
|---|
| 157 | def download(): |
|---|
| 158 | import os |
|---|
| 159 | db = get_database(request) |
|---|
| 160 | return response.download(request, db) |
|---|
| 161 | |
|---|
| 162 | |
|---|
| 163 | def csv(): |
|---|
| 164 | import gluon.contenttype |
|---|
| 165 | response.headers['Content-Type'] = \ |
|---|
| 166 | gluon.contenttype.contenttype('.csv') |
|---|
| 167 | db = get_database(request) |
|---|
| 168 | query = get_query(request) |
|---|
| 169 | if not query: |
|---|
| 170 | return None |
|---|
| 171 | response.headers['Content-disposition'] = 'attachment; filename=%s_%s.csv'\ |
|---|
| 172 | % tuple(request.vars.query.split('.')[:2]) |
|---|
| 173 | return str(db(query, ignore_common_filters=True).select()) |
|---|
| 174 | |
|---|
| 175 | |
|---|
| 176 | def import_csv(table, file): |
|---|
| 177 | table.import_from_csv_file(file) |
|---|
| 178 | |
|---|
| 179 | |
|---|
| 180 | def select(): |
|---|
| 181 | import re |
|---|
| 182 | db = get_database(request) |
|---|
| 183 | dbname = request.args[0] |
|---|
| 184 | try: |
|---|
| 185 | is_imap = db._uri.startswith("imap://") |
|---|
| 186 | except (KeyError, AttributeError, TypeError): |
|---|
| 187 | is_imap = False |
|---|
| 188 | regex = re.compile(r'(?P<table>\w+)\.(?P<field>\w+)=(?P<value>\d+)') |
|---|
| 189 | if len(request.args) > 1 and hasattr(db[request.args[1]], '_primarykey'): |
|---|
| 190 | regex = re.compile(r'(?P<table>\w+)\.(?P<field>\w+)=(?P<value>.+)') |
|---|
| 191 | if request.vars.query: |
|---|
| 192 | match = regex.match(request.vars.query) |
|---|
| 193 | if match: |
|---|
| 194 | request.vars.query = '%s.%s.%s==%s' % (request.args[0], |
|---|
| 195 | match.group('table'), match.group('field'), |
|---|
| 196 | match.group('value')) |
|---|
| 197 | else: |
|---|
| 198 | request.vars.query = session.last_query |
|---|
| 199 | query = get_query(request) |
|---|
| 200 | if request.vars.start: |
|---|
| 201 | start = int(request.vars.start) |
|---|
| 202 | else: |
|---|
| 203 | start = 0 |
|---|
| 204 | nrows = 0 |
|---|
| 205 | |
|---|
| 206 | step = 100 |
|---|
| 207 | fields = [] |
|---|
| 208 | |
|---|
| 209 | if is_imap: |
|---|
| 210 | step = 3 |
|---|
| 211 | |
|---|
| 212 | stop = start + step |
|---|
| 213 | |
|---|
| 214 | table = None |
|---|
| 215 | rows = [] |
|---|
| 216 | orderby = request.vars.orderby |
|---|
| 217 | if orderby: |
|---|
| 218 | orderby = dbname + '.' + orderby |
|---|
| 219 | if orderby == session.last_orderby: |
|---|
| 220 | if orderby[0] == '~': |
|---|
| 221 | orderby = orderby[1:] |
|---|
| 222 | else: |
|---|
| 223 | orderby = '~' + orderby |
|---|
| 224 | session.last_orderby = orderby |
|---|
| 225 | session.last_query = request.vars.query |
|---|
| 226 | form = FORM(TABLE(TR(T('Query:'), '', INPUT(_style='width:400px', |
|---|
| 227 | _name='query', _value=request.vars.query or '', _class="form-control", |
|---|
| 228 | requires=IS_NOT_EMPTY( |
|---|
| 229 | error_message=T("Cannot be empty")))), TR(T('Update:'), |
|---|
| 230 | INPUT(_name='update_check', _type='checkbox', |
|---|
| 231 | value=False), INPUT(_style='width:400px', |
|---|
| 232 | _name='update_fields', _value=request.vars.update_fields |
|---|
| 233 | or '', _class="form-control")), TR(T('Delete:'), INPUT(_name='delete_check', |
|---|
| 234 | _class='delete', _type='checkbox', value=False), ''), |
|---|
| 235 | TR('', '', INPUT(_type='submit', _value=T('submit'), _class="btn btn-primary"))), |
|---|
| 236 | _action=URL(r=request, args=request.args)) |
|---|
| 237 | |
|---|
| 238 | tb = None |
|---|
| 239 | if form.accepts(request.vars, formname=None): |
|---|
| 240 | regex = re.compile(request.args[0] + r'\.(?P<table>\w+)\..+') |
|---|
| 241 | match = regex.match(form.vars.query.strip()) |
|---|
| 242 | if match: |
|---|
| 243 | table = match.group('table') |
|---|
| 244 | try: |
|---|
| 245 | nrows = db(query, ignore_common_filters=True).count() |
|---|
| 246 | if form.vars.update_check and form.vars.update_fields: |
|---|
| 247 | db(query, ignore_common_filters=True).update( |
|---|
| 248 | **eval_in_global_env('dict(%s)' % form.vars.update_fields)) |
|---|
| 249 | response.flash = T('%s %%{row} updated', nrows) |
|---|
| 250 | elif form.vars.delete_check: |
|---|
| 251 | db(query, ignore_common_filters=True).delete() |
|---|
| 252 | response.flash = T('%s %%{row} deleted', nrows) |
|---|
| 253 | nrows = db(query, ignore_common_filters=True).count() |
|---|
| 254 | |
|---|
| 255 | if is_imap: |
|---|
| 256 | fields = [db[table][name] for name in |
|---|
| 257 | ("id", "uid", "created", "to", |
|---|
| 258 | "sender", "subject")] |
|---|
| 259 | if orderby: |
|---|
| 260 | rows = db(query, ignore_common_filters=True).select( |
|---|
| 261 | *fields, limitby=(start, stop), |
|---|
| 262 | orderby=eval_in_global_env(orderby)) |
|---|
| 263 | else: |
|---|
| 264 | rows = db(query, ignore_common_filters=True).select( |
|---|
| 265 | *fields, limitby=(start, stop)) |
|---|
| 266 | except Exception as e: |
|---|
| 267 | import traceback |
|---|
| 268 | tb = traceback.format_exc() |
|---|
| 269 | (rows, nrows) = ([], 0) |
|---|
| 270 | response.flash = DIV(T('Invalid Query'), PRE(str(e))) |
|---|
| 271 | # begin handle upload csv |
|---|
| 272 | csv_table = table or request.vars.table |
|---|
| 273 | if csv_table: |
|---|
| 274 | formcsv = FORM(str(T('or import from csv file')) + " ", |
|---|
| 275 | INPUT(_type='file', _name='csvfile'), |
|---|
| 276 | INPUT(_type='hidden', _value=csv_table, _name='table'), |
|---|
| 277 | INPUT(_type='submit', _value=T('import'), _class="btn btn-primary")) |
|---|
| 278 | else: |
|---|
| 279 | formcsv = None |
|---|
| 280 | if formcsv and formcsv.process().accepted: |
|---|
| 281 | try: |
|---|
| 282 | import_csv(db[request.vars.table], |
|---|
| 283 | request.vars.csvfile.file) |
|---|
| 284 | response.flash = T('data uploaded') |
|---|
| 285 | except Exception as e: |
|---|
| 286 | response.flash = DIV(T('unable to parse csv file'), PRE(str(e))) |
|---|
| 287 | # end handle upload csv |
|---|
| 288 | |
|---|
| 289 | return dict( |
|---|
| 290 | form=form, |
|---|
| 291 | table=table, |
|---|
| 292 | start=start, |
|---|
| 293 | stop=stop, |
|---|
| 294 | step=step, |
|---|
| 295 | nrows=nrows, |
|---|
| 296 | rows=rows, |
|---|
| 297 | query=request.vars.query, |
|---|
| 298 | formcsv=formcsv, |
|---|
| 299 | tb=tb |
|---|
| 300 | ) |
|---|
| 301 | |
|---|
| 302 | |
|---|
| 303 | # ########################################################## |
|---|
| 304 | # ## edit delete one record |
|---|
| 305 | # ########################################################### |
|---|
| 306 | |
|---|
| 307 | |
|---|
| 308 | def update(): |
|---|
| 309 | (db, table) = get_table(request) |
|---|
| 310 | keyed = hasattr(db[table], '_primarykey') |
|---|
| 311 | record = None |
|---|
| 312 | db[table]._common_filter = None |
|---|
| 313 | if keyed: |
|---|
| 314 | key = [f for f in request.vars if f in db[table]._primarykey] |
|---|
| 315 | if key: |
|---|
| 316 | record = db(db[table][key[0]] == request.vars[key[ |
|---|
| 317 | 0]]).select().first() |
|---|
| 318 | else: |
|---|
| 319 | record = db(db[table].id == request.args( |
|---|
| 320 | 2)).select().first() |
|---|
| 321 | |
|---|
| 322 | if not record: |
|---|
| 323 | qry = query_by_table_type(table, db) |
|---|
| 324 | session.flash = T('record does not exist') |
|---|
| 325 | redirect(URL('select', args=request.args[:1], |
|---|
| 326 | vars=dict(query=qry))) |
|---|
| 327 | |
|---|
| 328 | if keyed: |
|---|
| 329 | for k in db[table]._primarykey: |
|---|
| 330 | db[table][k].writable = False |
|---|
| 331 | |
|---|
| 332 | form = SQLFORM( |
|---|
| 333 | db[table], record, deletable=True, delete_label=T('Check to delete'), |
|---|
| 334 | ignore_rw=ignore_rw and not keyed, |
|---|
| 335 | linkto=URL('select', |
|---|
| 336 | args=request.args[:1]), upload=URL(r=request, |
|---|
| 337 | f='download', args=request.args[:1])) |
|---|
| 338 | |
|---|
| 339 | if form.accepts(request.vars, session): |
|---|
| 340 | session.flash = T('done!') |
|---|
| 341 | qry = query_by_table_type(table, db) |
|---|
| 342 | redirect(URL('select', args=request.args[:1], |
|---|
| 343 | vars=dict(query=qry))) |
|---|
| 344 | return dict(form=form, table=db[table]) |
|---|
| 345 | |
|---|
| 346 | |
|---|
| 347 | # ########################################################## |
|---|
| 348 | # ## get global variables |
|---|
| 349 | # ########################################################### |
|---|
| 350 | |
|---|
| 351 | |
|---|
| 352 | def state(): |
|---|
| 353 | return dict() |
|---|
| 354 | |
|---|
| 355 | |
|---|
| 356 | def ccache(): |
|---|
| 357 | if is_gae: |
|---|
| 358 | form = FORM( |
|---|
| 359 | P(TAG.BUTTON(T("Clear CACHE?"), _type="submit", _name="yes", _value="yes"))) |
|---|
| 360 | else: |
|---|
| 361 | cache.ram.initialize() |
|---|
| 362 | cache.disk.initialize() |
|---|
| 363 | |
|---|
| 364 | form = FORM( |
|---|
| 365 | P(TAG.BUTTON( |
|---|
| 366 | T("Clear CACHE?"), _type="submit", _name="yes", _value="yes")), |
|---|
| 367 | P(TAG.BUTTON( |
|---|
| 368 | T("Clear RAM"), _type="submit", _name="ram", _value="ram")), |
|---|
| 369 | P(TAG.BUTTON( |
|---|
| 370 | T("Clear DISK"), _type="submit", _name="disk", _value="disk")), |
|---|
| 371 | ) |
|---|
| 372 | |
|---|
| 373 | if form.accepts(request.vars, session): |
|---|
| 374 | session.flash = "" |
|---|
| 375 | if is_gae: |
|---|
| 376 | if request.vars.yes: |
|---|
| 377 | cache.ram.clear() |
|---|
| 378 | session.flash += T("Cache Cleared") |
|---|
| 379 | else: |
|---|
| 380 | clear_ram = False |
|---|
| 381 | clear_disk = False |
|---|
| 382 | if request.vars.yes: |
|---|
| 383 | clear_ram = clear_disk = True |
|---|
| 384 | if request.vars.ram: |
|---|
| 385 | clear_ram = True |
|---|
| 386 | if request.vars.disk: |
|---|
| 387 | clear_disk = True |
|---|
| 388 | if clear_ram: |
|---|
| 389 | cache.ram.clear() |
|---|
| 390 | session.flash += T("Ram Cleared") |
|---|
| 391 | if clear_disk: |
|---|
| 392 | cache.disk.clear() |
|---|
| 393 | session.flash += T("Disk Cleared") |
|---|
| 394 | redirect(URL(r=request)) |
|---|
| 395 | |
|---|
| 396 | try: |
|---|
| 397 | from pympler.asizeof import asizeof |
|---|
| 398 | except ImportError: |
|---|
| 399 | asizeof = False |
|---|
| 400 | |
|---|
| 401 | import shelve |
|---|
| 402 | import os |
|---|
| 403 | import copy |
|---|
| 404 | import time |
|---|
| 405 | import math |
|---|
| 406 | from pydal.contrib import portalocker |
|---|
| 407 | |
|---|
| 408 | ram = { |
|---|
| 409 | 'entries': 0, |
|---|
| 410 | 'bytes': 0, |
|---|
| 411 | 'objects': 0, |
|---|
| 412 | 'hits': 0, |
|---|
| 413 | 'misses': 0, |
|---|
| 414 | 'ratio': 0, |
|---|
| 415 | 'oldest': time.time(), |
|---|
| 416 | 'keys': [] |
|---|
| 417 | } |
|---|
| 418 | |
|---|
| 419 | disk = copy.copy(ram) |
|---|
| 420 | total = copy.copy(ram) |
|---|
| 421 | disk['keys'] = [] |
|---|
| 422 | total['keys'] = [] |
|---|
| 423 | |
|---|
| 424 | def GetInHMS(seconds): |
|---|
| 425 | hours = math.floor(seconds / 3600) |
|---|
| 426 | seconds -= hours * 3600 |
|---|
| 427 | minutes = math.floor(seconds / 60) |
|---|
| 428 | seconds -= minutes * 60 |
|---|
| 429 | seconds = math.floor(seconds) |
|---|
| 430 | |
|---|
| 431 | return (hours, minutes, seconds) |
|---|
| 432 | |
|---|
| 433 | if is_gae: |
|---|
| 434 | gae_stats = cache.ram.client.get_stats() |
|---|
| 435 | try: |
|---|
| 436 | gae_stats['ratio'] = ((gae_stats['hits'] * 100) / |
|---|
| 437 | (gae_stats['hits'] + gae_stats['misses'])) |
|---|
| 438 | except ZeroDivisionError: |
|---|
| 439 | gae_stats['ratio'] = T("?") |
|---|
| 440 | gae_stats['oldest'] = GetInHMS(time.time() - gae_stats['oldest_item_age']) |
|---|
| 441 | total.update(gae_stats) |
|---|
| 442 | else: |
|---|
| 443 | # get ram stats directly from the cache object |
|---|
| 444 | ram_stats = cache.ram.stats[request.application] |
|---|
| 445 | ram['hits'] = ram_stats['hit_total'] - ram_stats['misses'] |
|---|
| 446 | ram['misses'] = ram_stats['misses'] |
|---|
| 447 | try: |
|---|
| 448 | ram['ratio'] = ram['hits'] * 100 / ram_stats['hit_total'] |
|---|
| 449 | except (KeyError, ZeroDivisionError): |
|---|
| 450 | ram['ratio'] = 0 |
|---|
| 451 | |
|---|
| 452 | for key, value in iteritems(cache.ram.storage): |
|---|
| 453 | if asizeof: |
|---|
| 454 | ram['bytes'] += asizeof(value[1]) |
|---|
| 455 | ram['objects'] += 1 |
|---|
| 456 | ram['entries'] += 1 |
|---|
| 457 | if value[0] < ram['oldest']: |
|---|
| 458 | ram['oldest'] = value[0] |
|---|
| 459 | ram['keys'].append((key, GetInHMS(time.time() - value[0]))) |
|---|
| 460 | |
|---|
| 461 | for key in cache.disk.storage: |
|---|
| 462 | value = cache.disk.storage[key] |
|---|
| 463 | if key == 'web2py_cache_statistics' and isinstance(value[1], dict): |
|---|
| 464 | disk['hits'] = value[1]['hit_total'] - value[1]['misses'] |
|---|
| 465 | disk['misses'] = value[1]['misses'] |
|---|
| 466 | try: |
|---|
| 467 | disk['ratio'] = disk['hits'] * 100 / value[1]['hit_total'] |
|---|
| 468 | except (KeyError, ZeroDivisionError): |
|---|
| 469 | disk['ratio'] = 0 |
|---|
| 470 | else: |
|---|
| 471 | if asizeof: |
|---|
| 472 | disk['bytes'] += asizeof(value[1]) |
|---|
| 473 | disk['objects'] += 1 |
|---|
| 474 | disk['entries'] += 1 |
|---|
| 475 | if value[0] < disk['oldest']: |
|---|
| 476 | disk['oldest'] = value[0] |
|---|
| 477 | disk['keys'].append((key, GetInHMS(time.time() - value[0]))) |
|---|
| 478 | |
|---|
| 479 | ram_keys = list(ram) # ['hits', 'objects', 'ratio', 'entries', 'keys', 'oldest', 'bytes', 'misses'] |
|---|
| 480 | ram_keys.remove('ratio') |
|---|
| 481 | ram_keys.remove('oldest') |
|---|
| 482 | for key in ram_keys: |
|---|
| 483 | total[key] = ram[key] + disk[key] |
|---|
| 484 | |
|---|
| 485 | try: |
|---|
| 486 | total['ratio'] = total['hits'] * 100 / (total['hits'] + |
|---|
| 487 | total['misses']) |
|---|
| 488 | except (KeyError, ZeroDivisionError): |
|---|
| 489 | total['ratio'] = 0 |
|---|
| 490 | |
|---|
| 491 | if disk['oldest'] < ram['oldest']: |
|---|
| 492 | total['oldest'] = disk['oldest'] |
|---|
| 493 | else: |
|---|
| 494 | total['oldest'] = ram['oldest'] |
|---|
| 495 | |
|---|
| 496 | ram['oldest'] = GetInHMS(time.time() - ram['oldest']) |
|---|
| 497 | disk['oldest'] = GetInHMS(time.time() - disk['oldest']) |
|---|
| 498 | total['oldest'] = GetInHMS(time.time() - total['oldest']) |
|---|
| 499 | |
|---|
| 500 | def key_table(keys): |
|---|
| 501 | return TABLE( |
|---|
| 502 | TR(TD(B(T('Key'))), TD(B(T('Time in Cache (h:m:s)')))), |
|---|
| 503 | *[TR(TD(k[0]), TD('%02d:%02d:%02d' % k[1])) for k in keys], |
|---|
| 504 | **dict(_class='cache-keys', |
|---|
| 505 | _style="border-collapse: separate; border-spacing: .5em;")) |
|---|
| 506 | |
|---|
| 507 | if not is_gae: |
|---|
| 508 | ram['keys'] = key_table(ram['keys']) |
|---|
| 509 | disk['keys'] = key_table(disk['keys']) |
|---|
| 510 | total['keys'] = key_table(total['keys']) |
|---|
| 511 | |
|---|
| 512 | return dict(form=form, total=total, |
|---|
| 513 | ram=ram, disk=disk, object_stats=asizeof != False) |
|---|
| 514 | |
|---|
| 515 | |
|---|
| 516 | def table_template(table): |
|---|
| 517 | from gluon.html import TR, TD, TABLE, TAG |
|---|
| 518 | |
|---|
| 519 | def FONT(*args, **kwargs): |
|---|
| 520 | return TAG.font(*args, **kwargs) |
|---|
| 521 | |
|---|
| 522 | def types(field): |
|---|
| 523 | f_type = field.type |
|---|
| 524 | if not isinstance(f_type,str): |
|---|
| 525 | return ' ' |
|---|
| 526 | elif f_type == 'string': |
|---|
| 527 | return field.length |
|---|
| 528 | elif f_type == 'id': |
|---|
| 529 | return B('pk') |
|---|
| 530 | elif f_type.startswith('reference') or \ |
|---|
| 531 | f_type.startswith('list:reference'): |
|---|
| 532 | return B('fk') |
|---|
| 533 | else: |
|---|
| 534 | return ' ' |
|---|
| 535 | |
|---|
| 536 | # This is horribe HTML but the only one graphiz understands |
|---|
| 537 | rows = [] |
|---|
| 538 | cellpadding = 4 |
|---|
| 539 | color = "#000000" |
|---|
| 540 | bgcolor = "#FFFFFF" |
|---|
| 541 | face = "Helvetica" |
|---|
| 542 | face_bold = "Helvetica Bold" |
|---|
| 543 | border = 0 |
|---|
| 544 | |
|---|
| 545 | rows.append(TR(TD(FONT(table, _face=face_bold, _color=bgcolor), |
|---|
| 546 | _colspan=3, _cellpadding=cellpadding, |
|---|
| 547 | _align="center", _bgcolor=color))) |
|---|
| 548 | for row in db[table]: |
|---|
| 549 | rows.append(TR(TD(FONT(row.name, _color=color, _face=face_bold), |
|---|
| 550 | _align="left", _cellpadding=cellpadding, |
|---|
| 551 | _border=border), |
|---|
| 552 | TD(FONT(row.type, _color=color, _face=face), |
|---|
| 553 | _align="left", _cellpadding=cellpadding, |
|---|
| 554 | _border=border), |
|---|
| 555 | TD(FONT(types(row), _color=color, _face=face), |
|---|
| 556 | _align="center", _cellpadding=cellpadding, |
|---|
| 557 | _border=border))) |
|---|
| 558 | return "< %s >" % TABLE(*rows, **dict(_bgcolor=bgcolor, _border=1, |
|---|
| 559 | _cellborder=0, _cellspacing=0) |
|---|
| 560 | ).xml() |
|---|
| 561 | |
|---|
| 562 | def manage(): |
|---|
| 563 | tables = manager_action['tables'] |
|---|
| 564 | if isinstance(tables[0], str): |
|---|
| 565 | db = manager_action.get('db', auth.db) |
|---|
| 566 | db = globals()[db] if isinstance(db, str) else db |
|---|
| 567 | tables = [db[table] for table in tables] |
|---|
| 568 | if request.args(0) == 'auth': |
|---|
| 569 | auth.table_user()._plural = T('Users') |
|---|
| 570 | auth.table_group()._plural = T('Roles') |
|---|
| 571 | auth.table_membership()._plural = T('Memberships') |
|---|
| 572 | auth.table_permission()._plural = T('Permissions') |
|---|
| 573 | if request.extension != 'load': |
|---|
| 574 | return dict(heading=manager_action.get('heading', |
|---|
| 575 | T('Manage %(action)s') % dict(action=request.args(0).replace('_', ' ').title())), |
|---|
| 576 | tablenames=[table._tablename for table in tables], |
|---|
| 577 | labels=[table._plural.title() for table in tables]) |
|---|
| 578 | |
|---|
| 579 | table = tables[request.args(1, cast=int)] |
|---|
| 580 | formname = '%s_grid' % table._tablename |
|---|
| 581 | linked_tables = orderby = None |
|---|
| 582 | if request.args(0) == 'auth': |
|---|
| 583 | auth.table_group()._id.readable = \ |
|---|
| 584 | auth.table_membership()._id.readable = \ |
|---|
| 585 | auth.table_permission()._id.readable = False |
|---|
| 586 | auth.table_membership().user_id.label = T('User') |
|---|
| 587 | auth.table_membership().group_id.label = T('Role') |
|---|
| 588 | auth.table_permission().group_id.label = T('Role') |
|---|
| 589 | auth.table_permission().name.label = T('Permission') |
|---|
| 590 | if table == auth.table_user(): |
|---|
| 591 | linked_tables = [auth.settings.table_membership_name] |
|---|
| 592 | elif table == auth.table_group(): |
|---|
| 593 | orderby = 'role' if not request.args(3) or '.group_id' not in request.args(3) else None |
|---|
| 594 | elif table == auth.table_permission(): |
|---|
| 595 | orderby = 'group_id' |
|---|
| 596 | kwargs = dict(user_signature=True, maxtextlength=1000, |
|---|
| 597 | orderby=orderby, linked_tables=linked_tables) |
|---|
| 598 | smartgrid_args = manager_action.get('smartgrid_args', {}) |
|---|
| 599 | kwargs.update(**smartgrid_args.get('DEFAULT', {})) |
|---|
| 600 | kwargs.update(**smartgrid_args.get(table._tablename, {})) |
|---|
| 601 | grid = SQLFORM.smartgrid(table, args=request.args[:2], formname=formname, **kwargs) |
|---|
| 602 | return grid |
|---|
| 603 | |
|---|
| 604 | def hooks(): |
|---|
| 605 | import functools |
|---|
| 606 | import inspect |
|---|
| 607 | list_op = ['_%s_%s' %(h,m) for h in ['before', 'after'] for m in ['insert','update','delete']] |
|---|
| 608 | tables = [] |
|---|
| 609 | with_build_it = False |
|---|
| 610 | for db_str in sorted(databases): |
|---|
| 611 | db = databases[db_str] |
|---|
| 612 | for t in db.tables: |
|---|
| 613 | method_hooks = [] |
|---|
| 614 | for op in list_op: |
|---|
| 615 | functions = [] |
|---|
| 616 | for f in getattr(db[t], op): |
|---|
| 617 | if hasattr(f, '__call__'): |
|---|
| 618 | try: |
|---|
| 619 | if isinstance(f, (functools.partial)): |
|---|
| 620 | f = f.func |
|---|
| 621 | filename = inspect.getsourcefile(f) |
|---|
| 622 | details = {'funcname':f.__name__, |
|---|
| 623 | 'filename':filename[len(request.folder):] if request.folder in filename else None, |
|---|
| 624 | 'lineno': inspect.getsourcelines(f)[1]} |
|---|
| 625 | if details['filename']: # Built in functions as delete_uploaded_files are not editable |
|---|
| 626 | details['url'] = URL(a='admin',c='default',f='edit', args=[request['application'], details['filename']],vars={'lineno':details['lineno']}) |
|---|
| 627 | if details['filename'] or with_build_it: |
|---|
| 628 | functions.append(details) |
|---|
| 629 | # compiled app and windows build don't support code inspection |
|---|
| 630 | except: |
|---|
| 631 | pass |
|---|
| 632 | if len(functions): |
|---|
| 633 | method_hooks.append({'name': op, 'functions':functions}) |
|---|
| 634 | if len(method_hooks): |
|---|
| 635 | tables.append({'name': "%s.%s" % (db_str, t), 'slug': IS_SLUG()("%s.%s" % (db_str,t))[0], 'method_hooks':method_hooks}) |
|---|
| 636 | # Render |
|---|
| 637 | ul_main = UL(_class='nav nav-list') |
|---|
| 638 | for t in tables: |
|---|
| 639 | ul_main.append(A(t['name'], _onclick="collapse('a_%s')" % t['slug'])) |
|---|
| 640 | ul_t = UL(_class='nav nav-list', _id="a_%s" % t['slug'], _style='display:none') |
|---|
| 641 | for op in t['method_hooks']: |
|---|
| 642 | ul_t.append(LI(op['name'])) |
|---|
| 643 | ul_t.append(UL([LI(A(f['funcname'], _class="editor_filelink", _href=f['url']if 'url' in f else None, **{'_data-lineno':f['lineno']-1})) for f in op['functions']])) |
|---|
| 644 | ul_main.append(ul_t) |
|---|
| 645 | return ul_main |
|---|
| 646 | |
|---|
| 647 | |
|---|
| 648 | # ########################################################## |
|---|
| 649 | # d3 based model visualizations |
|---|
| 650 | # ########################################################### |
|---|
| 651 | |
|---|
| 652 | def d3_graph_model(): |
|---|
| 653 | """ See https://www.facebook.com/web2py/posts/145613995589010 from Bruno Rocha |
|---|
| 654 | and also the app_admin bg_graph_model function |
|---|
| 655 | |
|---|
| 656 | Create a list of table dicts, called "nodes" |
|---|
| 657 | """ |
|---|
| 658 | |
|---|
| 659 | nodes = [] |
|---|
| 660 | links = [] |
|---|
| 661 | |
|---|
| 662 | for database in databases: |
|---|
| 663 | db = eval_in_global_env(database) |
|---|
| 664 | for tablename in db.tables: |
|---|
| 665 | fields = [] |
|---|
| 666 | for field in db[tablename]: |
|---|
| 667 | f_type = field.type |
|---|
| 668 | if not isinstance(f_type, str): |
|---|
| 669 | disp = ' ' |
|---|
| 670 | elif f_type == 'string': |
|---|
| 671 | disp = field.length |
|---|
| 672 | elif f_type == 'id': |
|---|
| 673 | disp = "PK" |
|---|
| 674 | elif f_type.startswith('reference') or \ |
|---|
| 675 | f_type.startswith('list:reference'): |
|---|
| 676 | disp = "FK" |
|---|
| 677 | else: |
|---|
| 678 | disp = ' ' |
|---|
| 679 | fields.append(dict(name=field.name, type=field.type, disp=disp)) |
|---|
| 680 | |
|---|
| 681 | if isinstance(f_type, str) and ( |
|---|
| 682 | f_type.startswith('reference') or |
|---|
| 683 | f_type.startswith('list:reference')): |
|---|
| 684 | referenced_table = f_type.split()[1].split('.')[0] |
|---|
| 685 | |
|---|
| 686 | links.append(dict(source=tablename, target = referenced_table)) |
|---|
| 687 | |
|---|
| 688 | nodes.append(dict(name=tablename, type="table", fields = fields)) |
|---|
| 689 | |
|---|
| 690 | # d3 v4 allows individual modules to be specified. The complete d3 library is included below. |
|---|
| 691 | response.files.append(URL('admin','static','js/d3.min.js')) |
|---|
| 692 | response.files.append(URL('admin','static','js/d3_graph.js')) |
|---|
| 693 | return dict(databases=databases, nodes=nodes, links=links) |
|---|