diff --git a/out/static/style.css b/out/static/style.css index 8272833..cbb0e5f 100644 --- a/out/static/style.css +++ b/out/static/style.css @@ -29,9 +29,6 @@ header h1 { margin-top: 0; } header h1 span { font-size: 0.7em; color: silver; } -@media(max-width: 622px) { /* 3 columns */ - header h1 span { display: none; } -} main { padding: 0.1em 2em 1.5em; background: #fff; @@ -45,9 +42,6 @@ footer .col3 div { padding: 1%; display: inline-block; } -@media(max-width: 647px) { - footer .col3 div { width: 100%; padding: 0; } -} footer .links { text-align: center; font-size: 0.9em; @@ -60,6 +54,7 @@ footer .links a { color: #ddd; } td { padding: 0.2em 1em 0.2em 0.1em; } .squeeze { max-width: 700px; } +.wrap { word-wrap: anywhere; } #get-appcheck:hover { color: #586472; border-bottom: unset; } #get-appcheck img { width: 3em; height: 3em; margin: 0.3em; } @@ -84,22 +79,22 @@ td { padding: 0.2em 1em 0.2em 0.1em; } margin: 0.5em auto 1em; display: block; } -#app-toc img, #get-appcheck img, #appicon { +#app-toc img, #get-appcheck img, #meta img { border-radius: 21.5%; border: 0.7px solid #ccc; } -#app-toc span { font-size: 0.8em; } -#app-toc span.name { font-weight: bold; } +#app-toc span.name { font-size: 0.8em; font-weight: bold; } +#app-toc span.detail { font-size: 0.7em; } #pagination { text-align: center; margin-top: 2em; } #pagination a { margin: 0.5em; padding: 0.2em } #pagination a.active { border: 1pt solid black; border-radius: 0.2em; } -@media(min-width: 647px) { - #meta #appicon { float: right; } -} #meta td:nth-child(2) { font-weight: bold } -#connections i:not(.empty) { + +/* domain tags */ +.tags { margin: 2em 0; } +.tags i { font-size: 0.9em; font-style: normal; font-weight: normal; @@ -110,6 +105,59 @@ td { padding: 0.2em 1em 0.2em 0.1em; } display: inline-block; margin: 0.12em; } -#connections i.bad { border-color: red; } -#connections td { vertical-align: top; } -#connections figure { display: inline-block; } +.tags i.trckr:before, .tags.trckr i:before, p.trckr:before { + content: '* '; + color: red; + font-weight: bold; +} +.tags i.trckr, .tags.trckr i { background: #DBB; border-color: #C88; } +p.trckr { font-size: 0.8em; } + +/* graphs */ +.pie-chart { width: 100px; height: 100px; } +.dot-graph span { outline: 3px solid transparent; outline-offset: 2px; } +.dot-graph span:hover { outline-color: black; } +.dot-graph span:hover p { display: inline; } +.dot-graph p { + display: none; + position: absolute; + vertical-align: bottom; + background: white; + border: 1px solid #ddd; + padding: 0.2em; + margin: -2em 0; +} +.dot-graph i { + display: inline-block; + vertical-align: bottom; + width: 20px; + height: 20px; + padding: 0; + margin: 1px 1px; +} +/* color-bind friendly color palette */ +.c0{color:#63ACBE} .cb0{background:#63ACBE} +.c1{color:#601A4A} .cb1{background:#601A4A} +.c2{color:#09F4EC} .cb2{background:#09F4EC} +.c3{color:#1F77B4} .cb3{background:#1F77B4} +.c4{color:#EE442F} .cb4{background:#EE442F} +.c5{color:#7F7F7F} .cb5{background:#7F7F7F} +.c6{color:#0F2080} .cb6{background:#0F2080} +.c7{color:#3b9f35} .cb7{background:#3b9f35} +.c8{color:#F5793A} .cb8{background:#F5793A} +.c9{color:#AC66FB} .cb9{background:#AC66FB} + +.cs0{stroke:#3AE48C} +.cs1{stroke:#D11} + +/* responsive */ +@media(max-width: 647px) { + header h1 span { display: none; } /* header subtitle */ + footer .col3 div { width: 100%; padding: 0; } /* 3 columns */ + #meta .icons { margin-bottom: 1em; } /* icons beside each other */ + .pie-chart { float: right; } +} +@media(min-width: 648px) { + #meta .icons { float: right; } /* icons below each other */ + .pie-chart { margin-top: 1em; } +} diff --git a/src/bundle_combine.py b/src/bundle_combine.py index b5f9193..ee45723 100755 --- a/src/bundle_combine.py +++ b/src/bundle_combine.py @@ -1,5 +1,6 @@ #!/usr/bin/env python3 +import os import sys import common_lib as mylib import tracker_download as tracker @@ -39,7 +40,9 @@ def json_combine(bundle_id): domB = dict() # total sub domains domC = dict() # unique parent domains domD = dict() # total parent domains + latest = 0 for fname, jdata in mylib.enum_jsons(bundle_id): + latest = max(latest, os.path.getmtime(fname)) # or getctime res['name'] = jdata['app-name'] res['#rec'] += 1 dict_increment(res, 'rec-total', jdata['duration']) @@ -71,6 +74,7 @@ def json_combine(bundle_id): par_tracker[x] = tracker.is_tracker(x) res['tracker_subdom'] = sub_tracker res['tracker_pardom'] = par_tracker + res['last_date'] = latest return res diff --git a/src/html_bundle.py b/src/html_bundle.py index 196c660..f8abdf6 100755 --- a/src/html_bundle.py +++ b/src/html_bundle.py @@ -1,29 +1,9 @@ #!/usr/bin/env python3 import sys +import time +import math import common_lib as mylib -# import matplotlib -# import matplotlib.pyplot as plt - -# matplotlib.use('Agg') # disable interactive mode - - -def sort_dict(count_dict): - sorted_count = sorted(count_dict.items(), key=lambda x: (-x[1], x[0])) - names = ['{} ({})'.format(*x) for x in sorted_count] - sizes = [x[1] for x in sorted_count] - return names, sizes - - -def gen_graph(count_dict, outfile, overwrite=False): - if mylib.file_exists(outfile) and not overwrite: - return - # names, sizes = sort_dict(count_dict) - # pie1, _ = plt.pie(sizes, labels=names) - # plt.setp(pie1, width=0.5, edgecolor='white') - # plt.subplots_adjust(left=0, right=1, top=0.7, bottom=0.3) - # plt.savefig(outfile, bbox_inches='tight', pad_inches=0) # transparent=True - # plt.close() def seconds_to_time(seconds): @@ -32,14 +12,74 @@ def seconds_to_time(seconds): return '{:02d}:{:02d}:{:02d}'.format(hours, minutes, seconds) -def gen_dom_tags(unsorted_dict, trackers=None): +def gen_dom_tags(unsorted_dict, trackers=None, additionalClasses=None): sorted_arr = sorted(unsorted_dict, key=lambda x: (-x[1], x[0])) - res = [] - for x, y in sorted_arr: - clss = ' class="bad"' if trackers and trackers[x] else '' - title = x # if y == 1 else '{} ({})'.format(x, y) - res.append('{}'.format(clss, title)) - return ' '.join(res) if len(res) > 0 else '– None –' + txt = '' + anyMark = False + for i, (x, y) in enumerate(sorted_arr): + mark = trackers[x] if trackers else True + title = x if y == 1 else '{} ({})'.format(x, y) + txt += '{} '.format(' class="trckr"' if mark else '', title) + anyMark |= mark + if txt: + note = '
known tracker
' + return ''.format( + additionalClasses or '', txt, note if anyMark else '') + else: + return '– None –' + + +def gen_dotgraph(count_dict): + txt = '' + sorted_count = sorted(count_dict.items(), key=lambda x: (-x[1], x[0])) + for i, (name, count) in enumerate(sorted_count): + # TODO: use average not total count + txt += '{0} ({1})
'.format(name, count) + for x in range(count): + txt += ''.format(i % 10) + txt += '' + return '| Bundle-id: | { + |
| Bundle-id: | { bundle_id } |
| Number of recordings: | { @@ -65,45 +108,42 @@ def gen_html(bundle_id, obj): |
| Average recording time: | { round(obj['rec-total'] / obj['#rec'], 1) } s |
| Last updated: |
| Known Trackers ({ len(track_dom) }): | { - gen_dom_tags(track_dom) - } |
| Domains: | { - gen_dom_tags(obj['total_pardom'].items(), obj['tracker_pardom']) - } |
| Subdomains: | { - gen_dom_tags(obj['total_subdom'].items(), obj['tracker_subdom']) - } |