diff --git a/out/static/style.css b/out/static/style.css index 74e5f91..357f551 100644 --- a/out/static/style.css +++ b/out/static/style.css @@ -128,6 +128,12 @@ footer .links { .pagination a { margin: .5em; padding: .2em; } .pagination a.active { border: 1pt solid black; border-radius: .2em; } +/* Lists */ +.title-sub h4, h2.title { margin-bottom: 0; } +.title-sub span, p.subtitle { margin-top: .2em; } +.title-sub p { margin: .2em .5em 1.5em; font-style: italic; } +.title-sub span, .found-in span, .snd { color: #586472; font-size: .85em; } + #categories h3 { margin-top: 2em; } @@ -136,7 +142,6 @@ footer .links { #dom-top10>div { margin: .4em; } #dom-top10 a, #dom-toc a { word-wrap: break-word; } #dom-toc span { display: table; } -.found-in span, .snd { color: #586472; font-size: .85em; } .fillbar { display: block; background: #DDD; @@ -159,8 +164,6 @@ footer .links { /* app bundle */ .squeeze { max-width: 700px; } -h2.title { margin-bottom: 0; } -p.subtitle { margin-top: .2em; } .mg_lr { margin: 0 .4em; } .mg_top { margin-top: 2em; } .right { text-align: right; } @@ -172,9 +175,11 @@ p.subtitle { margin-top: .2em; } .xscroll { overflow-x: scroll; max-width: 100%; } .yscroll { overflow-y: scroll; max-height: 80vh; } .floatr, #main-nav { float: right; } +table { border-collapse: collapse; } table.alternate td, table.alternate th { padding: .5em; } table.alternate tr:nth-child(even) { background: #DDD; } table.alternate tr:nth-child(odd) { background: #F9F9F9; } +table.stick-first td:nth-child(1) { position: sticky; left: 0; background: inherit; } /*#meta { margin-bottom: 2em; }*/ #meta .icons { margin-bottom: 2em; } @@ -282,14 +287,16 @@ p.trckr { font-size: .9em; margin-left: .5em; } #rank-list th:not(:first-child) { min-width: 12ex; } #rank-list img { width: 25px; height: 25px; vertical-align: bottom; } -#rank-list td { text-align: center; min-width: max-content; } -#rank-list td:nth-child(1) { - position: sticky; left: 0; - background: inherit; - text-align: right; -} +#rank-list td { text-align: center; } +#rank-list td:nth-child(1) { text-align: right; white-space: nowrap; } #rank-list td:nth-child(2) { text-align: left; } +/* rank-cluster */ +table.cluster tr { font-size: 0.8em; } +table.cluster td { padding: 0.75em; } +table.cluster .sep { border-left: 1pt solid #000; } +table.cluster td:not(:first-child) { text-align: center; } + /* responsive */ @media(max-width: 900px) { #stats { grid-template-columns: repeat(2, max-content); } diff --git a/src/README.md b/src/README.md index d7c706c..0b26357 100644 --- a/src/README.md +++ b/src/README.md @@ -40,11 +40,11 @@ Given A → B, B depends on A ``` digraph Dependency { + "." -> download_tracker "." -> download_itunes "." -> bundle_combine download_itunes -> index_app_names index_app_names -> index_categories - index_app_names -> index_rank index_app_names -> html_index_domains index_categories -> html_categories index_categories -> html_index_apps @@ -55,7 +55,8 @@ digraph Dependency { html_index_apps -> html_ranking html_categories -> html_ranking html_index_domains -> html_root - "." -> download_tracker + index_categories -> index_rank + index_rank -> html_group_compare } ``` [graphviz](http://www.webgraphviz.com/) \ No newline at end of file diff --git a/src/html_bundle.py b/src/html_bundle.py index 7386b82..13cb7bf 100755 --- a/src/html_bundle.py +++ b/src/html_bundle.py @@ -47,21 +47,6 @@ def stat(col, title, ident, value, optional=None): def gen_page(bundle_id, obj): - def round_num(num): - return format(num, '.1f') # .rstrip('0').rstrip('.') - - def as_pm(value): - return round_num(value) + '/min' - - def as_percent(value): - return round_num(value * 100) + '%' - - def seconds_to_time(seconds): - seconds = int(seconds) - minutes, seconds = divmod(seconds, 60) - hours, minutes = divmod(minutes, 60) - return '{:02d}:{:02d}:{:02d}'.format(hours, minutes, seconds) - name = index_app_names.get_name(bundle_id) gernes = index_categories.get_categories(bundle_id) obj['tracker'] = list(filter(lambda x: x[2], obj['subdom'])) @@ -83,13 +68,13 @@ def gen_page(bundle_id, obj):
{ stat(1, 'Number of recordings:', 'sum_rec', obj['sum_rec']) } - { stat(1, 'Average recording time:', 'avg_time', seconds_to_time(obj['avg_time'])) } - { stat(2, 'Cumulative recording time:', 'sum_time', seconds_to_time(obj['sum_time'])) } - { stat(1, 'Average number of requests:', 'avg_logs_pm', round_num(obj['avg_logs']), as_pm(obj['avg_logs_pm'])) } - { stat(2, 'Total number of requests:', 'sum_logs_pm', str(obj['sum_logs']), as_pm(obj['sum_logs_pm'])) } + { stat(1, 'Average recording time:', 'avg_time', HTML.seconds_to_time(obj['avg_time'])) } + { stat(2, 'Cumulative recording time:', 'sum_time', HTML.seconds_to_time(obj['sum_time'])) } + { stat(1, 'Average number of requests:', 'avg_logs_pm', HTML.fmt_round_num(obj['avg_logs']), HTML.fmt_as_pm(obj['avg_logs_pm'])) } + { stat(2, 'Total number of requests:', 'sum_logs_pm', str(obj['sum_logs']), HTML.fmt_as_pm(obj['sum_logs_pm'])) } { stat(1, 'Number of domains:', 'pardom', len(obj['pardom'])) } { stat(2, 'Number of subdomains:', 'subdom', len(obj['subdom'])) } - { stat(3, 'Tracker percentage:', 'tracker_percent', as_percent(obj['tracker_percent'])) } + { stat(3, 'Tracker percentage:', 'tracker_percent', HTML.fmt_as_percent(obj['tracker_percent'])) }

Connections

diff --git a/src/html_categories.py b/src/html_categories.py index aa55776..762bac3 100755 --- a/src/html_categories.py +++ b/src/html_categories.py @@ -2,6 +2,7 @@ import lib_common as mylib import lib_html as HTML +import html_group_compare def write_overview_page(base_dir, category_tuples, title): @@ -12,7 +13,8 @@ def write_overview_page(base_dir, category_tuples, title): cluster[i].append(x) except KeyError: cluster[i] = [x] - src = '

{}

'.format(title) + src = HTML.h2_path_n_rank(title, [], 'compare/', 'Compare') + src += '
' for i, arr in sorted(cluster.items()): mylib.sort_by_name(arr, 1) kind = 'Apps' if i == 6 else 'Games' if i == 7 else 'Other' @@ -21,6 +23,12 @@ def write_overview_page(base_dir, category_tuples, title): src += ''.join([HTML.a_category(*x) for x in arr]) + '
' HTML.write(base_dir, src + '
', title) + # make groupby compare html + base_dir = mylib.path_add(base_dir, 'compare') + html_group_compare.write_groupby_multi( + base_dir, [('/category', 'All Categories')], 'Compare', 'Categories', + [('category-6', 'Category: Apps'), ('category-7', 'Category: Games')]) + def process(affected=None, per_page=60): print('generating html: category-index ...') @@ -34,7 +42,7 @@ def process(affected=None, per_page=60): continue out_dir = mylib.path_add(base, cid) # full url since categories can have page 2, 3, etc. - A = HTML.h2_path_n_rank(cname, [(parent, '/category/')], 'ranking/') + A = HTML.h2_path_n_rank(cname, [('/category/', parent)], 'ranking/') Z = HTML.p_download_json('data.json', 'category-{}.json'.format(cid)) _, a = HTML.write_app_pages(out_dir, json['apps'], cname, per_page, pre=A, post=Z) diff --git a/src/html_group_compare.py b/src/html_group_compare.py new file mode 100755 index 0000000..05c0872 --- /dev/null +++ b/src/html_group_compare.py @@ -0,0 +1,136 @@ +#!/usr/bin/env python3 + +import lib_common as mylib +import lib_html as HTML +import index_rank # fname_rank_list + + +def make_groupby_table_html(json, struct): + sep = ' class="sep"' + par_tr = '' + sub_tr = '' + for par_col, cols in struct: + par_tr += f'{par_col}' + for i, x in enumerate(cols): + sub_tr += '{}'.format(sep if i == 0 else '', x[0]) + src = f''' +
+ + {par_tr} + {sub_tr} +''' + for group in json: + ident = group[0] + if isinstance(ident, list): + ident = f'{ident[0]}' + src += f'' + for _, cols in struct: + first = True + for _, col_i, fn in cols: + val = group[col_i] + src += '{}'.format(sep if first else '', fn(val)) + first = False + src += '\n' + return src + '
{ident}
\n' + + +def prefill_groupby_table(cluster_file): + return make_groupby_table_html(mylib.json_read(cluster_file), [ + ['Apps', [ + (' ', 1, int)]], + ['Number of Recordings', [ + ('avg', 2, HTML.fmt_round_num), + ('total', 3, int)]], + ['Number of Requests', [ + ('avg', 4, HTML.fmt_round_num), + ('total', 5, int)]], + ['Recording Time', [ + ('avg', 6, HTML.seconds_to_time), + ('total', 7, HTML.seconds_to_time)]], + ['Requests per Minute', [ + ('min', 9, HTML.fmt_as_pm), + ('avg', 8, HTML.fmt_as_pm), + ('max', 10, HTML.fmt_as_pm)]], + ['Number of Domains', [ + ('min', 12, int), + ('avg', 11, HTML.fmt_round_num), + ('max', 13, int)]], + ['Number of Subdomains', [ + ('min', 15, int), + ('avg', 14, HTML.fmt_round_num), + ('max', 16, int)]], + ['Tracker Percentage', [ + ('min', 18, HTML.fmt_as_percent), + ('avg', 17, HTML.fmt_as_percent), + ('max', 19, HTML.fmt_as_percent)]], + ]) + + +def gen_and_link_groupby(cluster_id, base, raw_fname='data'): + json_file = index_rank.fname_rank_list('groupby', cluster_id) + raw_fname += '.json' + mylib.symlink(json_file, mylib.path_add(base, raw_fname)) + named_download = 'compare-{}.json'.format(cluster_id) + return prefill_groupby_table(json_file) + HTML.p_download_json( + raw_fname, named_download) + + +def write_groupby_multi(base_dir, h2_paths, h2_title, page_title, groups_arr): + mylib.mkdir(base_dir) + src = '

{}

'.format(HTML.a_path(h2_paths, h2_title)) + for gid, name in groups_arr: + raw_name = gid if len(groups_arr) > 1 else 'data' + src += f'\n

{name}

' + src += gen_and_link_groupby(gid, base_dir, raw_name) + HTML.write(base_dir, src, title='Compare: ' + page_title) + + +def write_groupby_single(base_dir, gid, name, parent): + base = mylib.path_add(base_dir, gid) + write_groupby_multi(base, + [('/results/', 'Results'), ('/compare/', parent)], + name, name, [(gid, '')]) + + +def process(): + print('generating html: group compare ...') + print(' lists') + base_custom = mylib.path_out('compare') + title_custom = 'Compare' + arr = [] + for list_id, json in mylib.enum_custom_lists('groupby_'): + try: + if json['hidden']: + continue + except KeyError: + pass + list_name = json['name'] + print(' ' + list_name) + grp_count = len(json['groups']) + app_count = sum([len(x['apps']) for x in json['groups']]) + try: + desc = json['desc'] + except KeyError: + desc = None + arr.append((list_id, list_name, grp_count, app_count, desc)) + write_groupby_single(base_custom, list_id, list_name, title_custom) + + print(' index page') + mylib.sort_by_name(arr, 1) + src = f''' +

{HTML.a_path([('/results/', 'Results')], title_custom)}

+

+ Comparison groups do not compare a single application against another. + Instead comparison groups cluster multiple applications into a single, logical group. + For example, paid-vs-free apps could be a comparison group. +

+
''' + for x in arr: + src += HTML.h4_a_title_sub_desc( + x[0], x[1], f'has {x[2]} groups with {x[3]} apps', x[4]) + HTML.write(base_custom, src + '
', title=title_custom) + print('') + + +if __name__ == '__main__': + process() diff --git a/src/html_index_apps.py b/src/html_index_apps.py index c63dab6..b2035ff 100755 --- a/src/html_index_apps.py +++ b/src/html_index_apps.py @@ -8,7 +8,7 @@ import index_app_names # get_sorted_app_names def process(per_page=60): print('generating html: app-index ...') title = 'Apps (A–Z)' - header = HTML.h2_path_n_rank(title, [('Results', '/results/')], 'ranking/') + header = HTML.h2_path_n_rank(title, [('/results/', 'Results')], 'ranking/') p, a = HTML.write_app_pages(mylib.path_out('index', 'apps'), index_app_names.get_sorted_app_names(), title, per_page=per_page, pre=header) diff --git a/src/html_index_domains.py b/src/html_index_domains.py index df80898..82a37f3 100755 --- a/src/html_index_domains.py +++ b/src/html_index_domains.py @@ -70,7 +70,7 @@ def gen_html_trinity(idx_dir, app_count, json, title, symlink): def write_index(fname, title, button): HTML.write(idx_dir, '

{}

{}{}'.format( - HTML.a_path([('Results', '/results/')], title), + HTML.a_path([('/results/', 'Results')], title), dropdown_choose(button), duo_list(list1, list2) ), title=title, fname=fname) @@ -91,7 +91,7 @@ def gen_html_trinity(idx_dir, app_count, json, title, symlink): def gen_lookup(html_dir, doms_dict, flag, title): HTML.write(html_dir, f''' -

{ HTML.a_path([('All Domains', '/index/domains/all/')], +

{ HTML.a_path([('/index/domains/all/', 'All Domains')], '') }

Known Tracker: ?

Present in: … applications

diff --git a/src/html_ranking.py b/src/html_ranking.py index cdcffec..4156ee9 100755 --- a/src/html_ranking.py +++ b/src/html_ranking.py @@ -20,7 +20,7 @@ def html_default_description(): def html_table(): return '''
-
Loading …
+
Loading …
''' @@ -35,8 +35,8 @@ def html_script_chunk(fname, sort_col, sort_order): def write_ranking_all(title, base_dir): # full urls since app index can have page 2, 3, etc. - src = html_h2_path([('Results', '/results/'), - ('Apps (A–Z)', '/index/apps/')]) + src = html_h2_path([('/results/', 'Results'), + ('/index/apps/', 'Apps (A–Z)')]) src += html_default_description() src += html_table() src += HTML.p_download_json('data.json', 'raw-apps.json') @@ -49,8 +49,8 @@ def write_ranking_all(title, base_dir): def write_ranking_category(cid, category_name): base = mylib.path_out('category', cid, 'ranking') # full urls since categories can have page 2, 3, etc. - src = html_h2_path([('All Categories', '/category/'), - (category_name, '/category/{}/'.format(cid))]) + src = html_h2_path([('/category/', 'All Categories'), + ('/category/{}/'.format(cid), category_name)]) src += html_default_description() src += html_table() src += HTML.p_download_json('data.json', @@ -63,8 +63,8 @@ def write_ranking_category(cid, category_name): def write_ranking_custom_lists(base_dir, list_id, list_name, parent_title): base = mylib.path_add(base_dir, list_id) - src = html_h2_path([('Results', '/results/'), - (parent_title, '/lists/')], list_name) + src = html_h2_path([('/results/', 'Results'), + ('/lists/', parent_title)], list_name) src += html_table() src += HTML.p_download_json('data.json', 'raw-list-{}.json'.format(list_id)) @@ -88,22 +88,28 @@ def process(): base_custom = mylib.path_out('lists') title_custom = 'Lists' arr = [] - for list_id, json in mylib.enum_custom_lists(): + for list_id, json in mylib.enum_custom_lists('list_'): list_name = json['name'] - arr.append((list_id, list_name, len(json['apps']))) + try: + desc = json['desc'] + except KeyError: + desc = None + arr.append((list_id, list_name, len(json['apps']), desc)) write_ranking_custom_lists(base_custom, list_id, list_name, parent_title=title_custom) print(' index page') mylib.sort_by_name(arr, 1) - src = html_h2_path([('Results', '/results/')], title_custom) + src = html_h2_path([('/results/', 'Results')], title_custom) src += '''

- We present selected lists of apps that have been added to AppCheck. + App lists compare individual applications against each other. + These curated lists group together, at least in some aspect, similar applications. + This could be all instant messengers, health related apps, or apps from a common developer or country.

''' for x in arr: - src += '

{} contains {} apps

\n'.format(*x) + src += HTML.h4_a_title_sub_desc(x[0], x[1], f'contains {x[2]} apps', x[3]) HTML.write(base_custom, src + '
', title=title_custom) print('') diff --git a/src/html_root.py b/src/html_root.py index 52cbe30..81c310e 100755 --- a/src/html_root.py +++ b/src/html_root.py @@ -92,9 +92,12 @@ def gen_results(base_dir, c_apps, c_domains, title):
  • List of Categories
  • List of All Domains, only Trackers, - or Highly-used Domains which appear in at least 5 apps but are not considered tracker yet.
  • + or Highly-used Domains
    which appear in at least 5 apps but are not considered tracker yet. + + -

    Or compare similar application via custom comparison Lists.

    '''.format(title, c_apps, c_domains, c_recs, c_logs), title=title) mylib.symlink(index_rank.fname_app_rank(), mylib.path_add(base_dir, 'rank.json')) # after HTML.write diff --git a/src/index_categories.py b/src/index_categories.py index 8a9e221..b7ad461 100755 --- a/src/index_categories.py +++ b/src/index_categories.py @@ -4,6 +4,7 @@ import sys import lib_common as mylib import download_itunes # get_genres import index_app_names # get_name +import index_rank # save_groupby_list _dict_apps = None _dict_names = None @@ -96,9 +97,23 @@ def persist_individual_files(): index = sorted_reverse_index() make_dir_individuals() + cat_groups = {} for cid, cname in _dict_names.items(): + # write individual mylib.json_write(fname_cat_individual(cid), {'meta': [cid, cname], 'apps': index[cid]}) + # prep groupby compare + obj = {'name': cname, 'apps': [x for x, _ in index[cid]]} + cat_g_id = int(int(cid) / 1000) + try: + cat_groups[cat_g_id].append(obj) + except KeyError: + cat_groups[cat_g_id] = [obj] + + for key, vals in cat_groups.items(): + mylib.sort_by_name(vals, 'name') + index_rank.save_groupby_list( + f'category-{key}', f'Category {key}xxx', vals, hidden=True) def get_categories(bundle_id): diff --git a/src/index_rank.py b/src/index_rank.py index 0299ae9..de9af52 100755 --- a/src/index_rank.py +++ b/src/index_rank.py @@ -48,6 +48,39 @@ def json_to_list(json): ] +def group_multiple_apps(values_array): + c = len(values_array) + asum = [0] * 10 + amin = [float('inf')] * 10 + amax = [0] * 10 + for values in values_array: + for i in range(10): + asum[i] += values[i] + amin[i] = min(amin[i], values[i]) + amax[i] = max(amax[i], values[i]) + + def flt(val): + return int(val * 1000) / 1000 + return [ + # app-count + c, + # rec-count (avg, sum) + flt(asum[0] / c), asum[0], + # req-count (avg, sum) + flt(asum[9] / c), asum[8], + # rec-time (avg, sum) + int(asum[1] / c), asum[2], + # req-pm (avg, min, max) + flt(asum[3] / c), flt(amin[3]), flt(amax[3]), + # pardom-count (avg, min, max) + flt(asum[5] / c), amin[5], amax[5], + # subdom-count (avg, min, max) + flt(asum[6] / c), amin[6], amax[6], + # tracker-percent (avg, min, max) + flt(asum[7] / c), flt(amin[7]), flt(amax[7]), + ] + + def update_summary_index(index, bundle_ids, deleteOnly=False): did_change = False if deleteOnly: @@ -102,18 +135,45 @@ def write_ranking_category_list(index, affected_ids): def write_ranking_custom_lists(index, affected_ids): make_rank_list_dir('custom', reset=affected_ids == ['*']) - for list_id, json in mylib.enum_custom_lists(): + for list_id, json in mylib.enum_custom_lists('list_'): ret = list(filter_by_list(index, json['apps'], affected_ids)) if len(ret) == 0: continue mylib.json_write(fname_rank_list('custom', list_id), ret, pretty=False) +def write_ranking_groupby_lists(index, affected_ids): + make_rank_list_dir('groupby', reset=affected_ids == ['*']) + for listid, json in mylib.enum_custom_lists('groupby_'): + ret = [] + anyone = affected_ids == ['*'] + for group in json['groups']: + arr = [] + for x in index: + if not anyone and x[0] in affected_ids: + anyone = True + if x[0] in group['apps']: + arr.append(x[2:]) + if len(arr) > 0: + ident = group['name'] + try: + url = group['url'] + ident = [ident, url] + except KeyError: + pass + ret.append([ident] + group_multiple_apps(arr)) + if len(ret) == 0 or not anyone: + continue + mylib.json_write(fname_rank_list('groupby', listid), ret, pretty=False) + + def write_ranking_list(index, affected_ids): - ret = [] - for bid, values in index.items(): - ret.append([bid, index_app_names.get_name(bid)] + values) - del(values[8:]) # prepare for write_rank_index + # prepend bundle-id and app name + ret = [[b, index_app_names.get_name(b)] + v for b, v in index.items()] + + # TODO: return list of updated files + print(' write group-by lists') + write_ranking_groupby_lists(ret, affected_ids) print(' write custom lists') # sort by %-tracker asc, #-pardom asc, avg-req-per-min asc @@ -166,6 +226,14 @@ def get_total_counts(): return [0, 0] +def save_groupby_list(gid, title, groups, hidden=False): + base = mylib.path_data('_lists') + mylib.mkdir(base) + fname = mylib.path_add(base, f'groupby_{gid}.json') + json = {'name': title, 'hidden': hidden, 'groups': groups} + mylib.json_write(fname, json, pretty=False) + + def process(bundle_ids, deleteOnly=False): print('writing index: ranking ...') fname = fname_app_summary() @@ -177,6 +245,8 @@ def process(bundle_ids, deleteOnly=False): ids = mylib.appids_in_data(bundle_ids) if update_summary_index(index, ids, deleteOnly=deleteOnly): write_ranking_list(index, bundle_ids) + for values in index.values(): + del(values[8:]) write_rank_index(index) print('') diff --git a/src/lib_common.py b/src/lib_common.py index fa2a1b9..63af419 100755 --- a/src/lib_common.py +++ b/src/lib_common.py @@ -247,10 +247,10 @@ def enum_newly_added(): yield fname, os.path.basename(fname)[3:] # del prefix 'in_' -def enum_custom_lists(): - for fname in glob.glob(path_data('_lists', 'list_*.json')): +def enum_custom_lists(prefix): + for fname in glob.glob(path_data('_lists', prefix + '*.json')): with open(fname, 'r') as fp: - yield os.path.basename(fname)[5:-5], json.load(fp) + yield os.path.basename(fname)[len(prefix):-5], json.load(fp) def enum_jsons(bundle_id): diff --git a/src/lib_html.py b/src/lib_html.py index d035c8b..1738622 100755 --- a/src/lib_html.py +++ b/src/lib_html.py @@ -23,9 +23,9 @@ def a_subdomain(x, inner=None, attr_str=''): return '{}'.format(attr_str, x, inner or x) -def h2_path_n_rank(title, path_parts, rank_href): - return '

    {} Ranking

    '.format( - a_path(path_parts, title), rank_href) +def h2_path_n_rank(title, path_parts, rank_href, rank_title='Ranking'): + return '

    {} {}

    '.format( + a_path(path_parts, title), rank_href, rank_title) def p_download_json(href, download_name): @@ -65,7 +65,7 @@ def div(inner, attr=None): def a_path(parts, suffix): ''' expects (name, url) tuples ''' return ' / '.join(['{}'.format(url, title) - for title, url in parts] + [suffix]) + for url, title in parts] + [suffix]) # Simple constructs @@ -81,6 +81,36 @@ def date_utc(ctime): time.strftime('%Y-%m-%d, %H:%M', time.gmtime(ctime))) +def seconds_to_time(seconds): + seconds = int(seconds) + minutes, seconds = divmod(seconds, 60) + hours, minutes = divmod(minutes, 60) + return '{:02d}:{:02d}:{:02d}'.format(hours, minutes, seconds) + + +def fmt_round_num(num): + return format(num, '.1f') # .rstrip('0').rstrip('.') + + +def fmt_as_pm(value): + return fmt_round_num(value) + '/min' + + +def fmt_as_percent(value): + return fmt_round_num(value * 100) + '%' + + +def h4_a_title_sub_desc(href, title, subtitle, desc): + if desc: + desc = f'

    {desc}

    ' + return f''' +
    +

    {title}

    + {subtitle} + {desc or ''} +
    ''' + + # Higher level constructs def pagination(current, total): diff --git a/src/main.py b/src/main.py index 9b14a8c..afe3a31 100755 --- a/src/main.py +++ b/src/main.py @@ -8,6 +8,7 @@ import download_itunes import download_tracker import html_bundle import html_categories +import html_group_compare import html_index_apps import html_index_domains import html_ranking @@ -41,6 +42,7 @@ def rebuild_html(bundle_ids=None, cat_ids=None, inclIApp=True): else: print('no new bundle, not rebuilding index') html_ranking.process() # after html_categories & html_index_apps + html_group_compare.process() # after index_rank app_count, dom_count = html_index_domains.process() # after index_domains html_root.process(app_count, dom_count, inclStatic=True) diff --git a/src/z_dependency.svg b/src/z_dependency.svg index 5a58206..476aaa1 100644 --- a/src/z_dependency.svg +++ b/src/z_dependency.svg @@ -1,156 +1,166 @@ - + Dependency - + . - -. - - -download_itunes - -download_itunes - - -.->download_itunes - - - - -bundle_combine - -bundle_combine - - -.->bundle_combine - - + +. -download_tracker - -download_tracker +download_tracker + +download_tracker -.->download_tracker - - +.->download_tracker + + + + +download_itunes + +download_itunes + + +.->download_itunes + + + + +bundle_combine + +bundle_combine + + +.->bundle_combine + + -index_app_names - -index_app_names +index_app_names + +index_app_names -download_itunes->index_app_names - - +download_itunes->index_app_names + + -index_rank - -index_rank +index_rank + +index_rank bundle_combine->index_rank - - + + -index_domains - -index_domains +index_domains + +index_domains bundle_combine->index_domains - - + + -index_categories - -index_categories +index_categories + +index_categories -index_app_names->index_categories - - - - -index_app_names->index_rank - - +index_app_names->index_categories + + html_index_domains - -html_index_domains + +html_index_domains index_app_names->html_index_domains - - + + html_categories - -html_categories + +html_categories index_categories->html_categories - - + + html_index_apps - -html_index_apps + +html_index_apps index_categories->html_index_apps - - + + - -html_bundle - -html_bundle - - -index_rank->html_bundle - - + +index_categories->index_rank + + -html_root - -html_root +html_root + +html_root html_index_domains->html_root - - + + -html_ranking - -html_ranking +html_ranking + +html_ranking html_categories->html_ranking - - + + html_index_apps->html_ranking - - + + + + +html_bundle + +html_bundle + + +index_rank->html_bundle + + + + +html_group_compare + +html_group_compare + + +index_rank->html_group_compare + + index_domains->html_index_domains - - + + \ No newline at end of file diff --git a/templates/base.html b/templates/base.html index 86df014..1df8ba1 100644 --- a/templates/base.html +++ b/templates/base.html @@ -3,9 +3,9 @@ - + #_TITLE_#AppCheck: Privacy Monitor - +