Compare group of apps

This commit is contained in:
relikd
2020-10-13 19:46:13 +02:00
parent 215ff82d65
commit 76b22281f5
16 changed files with 429 additions and 156 deletions

View File

@@ -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); }

View File

@@ -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/)

View File

@@ -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):
</div>
<div id="stats">
{ 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'])) }
</div>
<h3>Connections</h3>
<div>

View File

@@ -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 = '<h2>{}</h2><div id="categories">'.format(title)
src = HTML.h2_path_n_rank(title, [], 'compare/', 'Compare')
src += '<div id="categories">'
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]) + '</div>'
HTML.write(base_dir, src + '</div>', 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)

136
src/html_group_compare.py Executable file
View File

@@ -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'<th{sep} colspan="{len(cols)}">{par_col}</th>'
for i, x in enumerate(cols):
sub_tr += '<th{}>{}</th>'.format(sep if i == 0 else '', x[0])
src = f'''
<div class="xscroll">
<table class="cluster alternate">
<tr><th></th>{par_tr}</tr>
<tr><th></th>{sub_tr}</tr>
'''
for group in json:
ident = group[0]
if isinstance(ident, list):
ident = f'<a href="{ident[1]}">{ident[0]}</a>'
src += f'<tr><td>{ident}</td>'
for _, cols in struct:
first = True
for _, col_i, fn in cols:
val = group[col_i]
src += '<td{}>{}</td>'.format(sep if first else '', fn(val))
first = False
src += '</tr>\n'
return src + '</table></div>\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 = '<h2>{}</h2>'.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<h3>{name}</h3>'
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'''
<h2>{HTML.a_path([('/results/', 'Results')], title_custom)}</h2>
<p class="squeeze">
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.
</p>
<div>'''
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 + '</div>', title=title_custom)
print('')
if __name__ == '__main__':
process()

View File

@@ -8,7 +8,7 @@ import index_app_names # get_sorted_app_names
def process(per_page=60):
print('generating html: app-index ...')
title = 'Apps (AZ)'
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)

View File

@@ -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, '<h2>{}</h2>{}{}'.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'''
<h2>{ HTML.a_path([('All Domains', '/index/domains/all/')],
<h2>{ HTML.a_path([('/index/domains/all/', 'All Domains')],
'<span id="name"></span>') }</h2>
<p>Known Tracker: <b id="known">?</b></p>
<p>Present in: <b id="num-apps">… applications</b></p>

View File

@@ -20,7 +20,7 @@ def html_default_description():
def html_table():
return '''
<div class="xscroll yscroll">
<table id="rank-list" class="alternate"><tr><td>Loading …</td></tr></table>
<table id="rank-list" class="alternate stick-first"><tr><td>Loading …</td></tr></table>
</div>'''
@@ -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 (AZ)', '/index/apps/')])
src = html_h2_path([('/results/', 'Results'),
('/index/apps/', 'Apps (AZ)')])
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 += '''
<p class="squeeze">
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.
</p>
<div class="found-in">'''
for x in arr:
src += '<p><a href="{}/">{}</a> <span>contains {} apps</span></p>\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 + '</div>', title=title_custom)
print('')

View File

@@ -92,9 +92,12 @@ def gen_results(base_dir, c_apps, c_domains, title):
<li>List of <a href="/category/">Categories</a></li>
<li>List of <a href="/index/domains/all/">All Domains</a>,
only <a href="/index/domains/tracker/">Trackers</a>,
or <a href="/index/domains/highly-used/">Highly-used Domains</a> which appear in at least 5 apps but are not considered tracker <i>yet</i>.</li>
or <a href="/index/domains/highly-used/">Highly-used Domains</a> <br>which appear in at least 5 apps but are not considered tracker <i>yet</i>.</li>
</ul>
<ul>
<li>Compare <a href="/lists/">App Lists</a></li>
<li>Compare <a href="/compare/">Group Lists</a></li>
</ul>
<p>Or compare similar application via custom comparison <a href="/lists/">Lists</a>.</p>
'''.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

View File

@@ -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):

View File

@@ -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_list(index, affected_ids):
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 = []
for bid, values in index.items():
ret.append([bid, index_app_names.get_name(bid)] + values)
del(values[8:]) # prepare for write_rank_index
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):
# 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('')

View File

@@ -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):

View File

@@ -23,9 +23,9 @@ def a_subdomain(x, inner=None, attr_str=''):
return '<a{} href="/subdomain/#{}">{}</a>'.format(attr_str, x, inner or x)
def h2_path_n_rank(title, path_parts, rank_href):
return '<h2>{} <a class="snd floatr" href="{}">Ranking</a></h2>'.format(
a_path(path_parts, title), rank_href)
def h2_path_n_rank(title, path_parts, rank_href, rank_title='Ranking'):
return '<h2>{} <a class="snd floatr" href="{}">{}</a></h2>'.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(['<a href="{}">{}</a>'.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'<p class="squeeze">{desc}</p>'
return f'''
<div class="title-sub">
<h4><a href="{href}/">{title}</a></h4>
<span>{subtitle}</span>
{desc or ''}
</div>'''
# Higher level constructs
def pagination(current, total):

View File

@@ -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)

View File

@@ -1,156 +1,166 @@
<svg width="660pt" height="404pt" viewBox="0.00 0.00 660.00 404.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<svg width="611pt" height="404pt" viewBox="0.00 0.00 611.00 404.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g id="graph1" class="graph" transform="scale(1 1) rotate(0) translate(4 400)">
<title>Dependency</title>
<polygon fill="white" stroke="white" points="-4,5 -4,-400 657,-400 657,5 -4,5"></polygon>
<polygon fill="white" stroke="white" points="-4,5 -4,-400 608,-400 608,5 -4,5"></polygon>
<!-- . -->
<g id="node1" class="node"><title>.</title>
<ellipse fill="none" stroke="black" cx="299" cy="-378" rx="27" ry="18"></ellipse>
<text text-anchor="middle" x="299" y="-373.8" font-family="Times,serif" font-size="14.00">.</text>
</g>
<!-- download_itunes -->
<g id="node3" class="node"><title>download_itunes</title>
<ellipse fill="none" stroke="black" cx="299" cy="-306" rx="77.0235" ry="18"></ellipse>
<text text-anchor="middle" x="299" y="-301.8" font-family="Times,serif" font-size="14.00">download_itunes</text>
</g>
<!-- .&#45;&gt;download_itunes -->
<g id="edge2" class="edge"><title>.-&gt;download_itunes</title>
<path fill="none" stroke="black" d="M299,-359.697C299,-351.983 299,-342.712 299,-334.112"></path>
<polygon fill="black" stroke="black" points="302.5,-334.104 299,-324.104 295.5,-334.104 302.5,-334.104"></polygon>
</g>
<!-- bundle_combine -->
<g id="node5" class="node"><title>bundle_combine</title>
<ellipse fill="none" stroke="black" cx="129" cy="-306" rx="75.1062" ry="18"></ellipse>
<text text-anchor="middle" x="129" y="-301.8" font-family="Times,serif" font-size="14.00">bundle_combine</text>
</g>
<!-- .&#45;&gt;bundle_combine -->
<g id="edge4" class="edge"><title>.-&gt;bundle_combine</title>
<path fill="none" stroke="black" d="M276.425,-367.705C250.531,-357.042 207.41,-339.287 174.475,-325.725"></path>
<polygon fill="black" stroke="black" points="175.625,-322.413 165.045,-321.842 172.959,-328.886 175.625,-322.413"></polygon>
<ellipse fill="none" stroke="black" cx="261" cy="-378" rx="27" ry="18"></ellipse>
<text text-anchor="middle" x="261" y="-373.8" font-family="Times,serif" font-size="14.00">.</text>
</g>
<!-- download_tracker -->
<g id="node30" class="node"><title>download_tracker</title>
<ellipse fill="none" stroke="black" cx="475" cy="-306" rx="80.1284" ry="18"></ellipse>
<text text-anchor="middle" x="475" y="-301.8" font-family="Times,serif" font-size="14.00">download_tracker</text>
<g id="node3" class="node"><title>download_tracker</title>
<ellipse fill="none" stroke="black" cx="85" cy="-306" rx="80.1284" ry="18"></ellipse>
<text text-anchor="middle" x="85" y="-301.8" font-family="Times,serif" font-size="14.00">download_tracker</text>
</g>
<!-- .&#45;&gt;download_tracker -->
<g id="edge32" class="edge"><title>.-&gt;download_tracker</title>
<path fill="none" stroke="black" d="M321.649,-367.992C348.38,-357.36 393.544,-339.397 427.955,-325.711"></path>
<polygon fill="black" stroke="black" points="429.352,-328.922 437.351,-321.974 426.765,-322.418 429.352,-328.922"></polygon>
<g id="edge2" class="edge"><title>.-&gt;download_tracker</title>
<path fill="none" stroke="black" d="M238.351,-367.992C211.62,-357.36 166.456,-339.397 132.045,-325.711"></path>
<polygon fill="black" stroke="black" points="133.235,-322.418 122.649,-321.974 130.648,-328.922 133.235,-322.418"></polygon>
</g>
<!-- download_itunes -->
<g id="node5" class="node"><title>download_itunes</title>
<ellipse fill="none" stroke="black" cx="261" cy="-306" rx="77.0235" ry="18"></ellipse>
<text text-anchor="middle" x="261" y="-301.8" font-family="Times,serif" font-size="14.00">download_itunes</text>
</g>
<!-- .&#45;&gt;download_itunes -->
<g id="edge4" class="edge"><title>.-&gt;download_itunes</title>
<path fill="none" stroke="black" d="M261,-359.697C261,-351.983 261,-342.712 261,-334.112"></path>
<polygon fill="black" stroke="black" points="264.5,-334.104 261,-324.104 257.5,-334.104 264.5,-334.104"></polygon>
</g>
<!-- bundle_combine -->
<g id="node7" class="node"><title>bundle_combine</title>
<ellipse fill="none" stroke="black" cx="431" cy="-306" rx="75.1062" ry="18"></ellipse>
<text text-anchor="middle" x="431" y="-301.8" font-family="Times,serif" font-size="14.00">bundle_combine</text>
</g>
<!-- .&#45;&gt;bundle_combine -->
<g id="edge6" class="edge"><title>.-&gt;bundle_combine</title>
<path fill="none" stroke="black" d="M283.575,-367.705C309.469,-357.042 352.59,-339.287 385.525,-325.725"></path>
<polygon fill="black" stroke="black" points="387.041,-328.886 394.955,-321.842 384.375,-322.413 387.041,-328.886"></polygon>
</g>
<!-- index_app_names -->
<g id="node7" class="node"><title>index_app_names</title>
<ellipse fill="none" stroke="black" cx="299" cy="-234" rx="80.1456" ry="18"></ellipse>
<text text-anchor="middle" x="299" y="-229.8" font-family="Times,serif" font-size="14.00">index_app_names</text>
<g id="node9" class="node"><title>index_app_names</title>
<ellipse fill="none" stroke="black" cx="261" cy="-234" rx="80.1456" ry="18"></ellipse>
<text text-anchor="middle" x="261" y="-229.8" font-family="Times,serif" font-size="14.00">index_app_names</text>
</g>
<!-- download_itunes&#45;&gt;index_app_names -->
<g id="edge6" class="edge"><title>download_itunes-&gt;index_app_names</title>
<path fill="none" stroke="black" d="M299,-287.697C299,-279.983 299,-270.712 299,-262.112"></path>
<polygon fill="black" stroke="black" points="302.5,-262.104 299,-252.104 295.5,-262.104 302.5,-262.104"></polygon>
<g id="edge8" class="edge"><title>download_itunes-&gt;index_app_names</title>
<path fill="none" stroke="black" d="M261,-287.697C261,-279.983 261,-270.712 261,-262.112"></path>
<polygon fill="black" stroke="black" points="264.5,-262.104 261,-252.104 257.5,-262.104 264.5,-262.104"></polygon>
</g>
<!-- index_rank -->
<g id="node11" class="node"><title>index_rank</title>
<ellipse fill="none" stroke="black" cx="257" cy="-162" rx="54.219" ry="18"></ellipse>
<text text-anchor="middle" x="257" y="-157.8" font-family="Times,serif" font-size="14.00">index_rank</text>
<g id="node19" class="node"><title>index_rank</title>
<ellipse fill="none" stroke="black" cx="390" cy="-90" rx="54.219" ry="18"></ellipse>
<text text-anchor="middle" x="390" y="-85.8" font-family="Times,serif" font-size="14.00">index_rank</text>
</g>
<!-- bundle_combine&#45;&gt;index_rank -->
<g id="edge18" class="edge"><title>bundle_combine-&gt;index_rank</title>
<path fill="none" stroke="black" d="M144.021,-288.252C160.084,-270.35 186.314,-241.143 209,-216 217.49,-206.59 226.843,-196.256 235.019,-187.232"></path>
<polygon fill="black" stroke="black" points="237.729,-189.455 241.851,-179.695 232.542,-184.754 237.729,-189.455"></polygon>
<path fill="none" stroke="black" d="M427.344,-287.832C425.16,-277.467 422.36,-263.987 420,-252 410.771,-205.131 400.734,-150.437 394.925,-118.376"></path>
<polygon fill="black" stroke="black" points="398.348,-117.632 393.125,-108.414 391.459,-118.877 398.348,-117.632"></polygon>
</g>
<!-- index_domains -->
<g id="node20" class="node"><title>index_domains</title>
<ellipse fill="none" stroke="black" cx="92" cy="-234" rx="70.0665" ry="18"></ellipse>
<text text-anchor="middle" x="92" y="-229.8" font-family="Times,serif" font-size="14.00">index_domains</text>
<g id="node21" class="node"><title>index_domains</title>
<ellipse fill="none" stroke="black" cx="499" cy="-234" rx="70.0665" ry="18"></ellipse>
<text text-anchor="middle" x="499" y="-229.8" font-family="Times,serif" font-size="14.00">index_domains</text>
</g>
<!-- bundle_combine&#45;&gt;index_domains -->
<g id="edge20" class="edge"><title>bundle_combine-&gt;index_domains</title>
<path fill="none" stroke="black" d="M120.043,-288.055C115.789,-280.007 110.608,-270.205 105.862,-261.226"></path>
<polygon fill="black" stroke="black" points="108.899,-259.481 101.131,-252.275 102.71,-262.752 108.899,-259.481"></polygon>
<path fill="none" stroke="black" d="M447.115,-288.411C455.544,-279.734 466.024,-268.946 475.354,-259.342"></path>
<polygon fill="black" stroke="black" points="478.071,-261.568 482.528,-251.956 473.05,-256.69 478.071,-261.568"></polygon>
</g>
<!-- index_categories -->
<g id="node9" class="node"><title>index_categories</title>
<ellipse fill="none" stroke="black" cx="407" cy="-162" rx="76.1936" ry="18"></ellipse>
<text text-anchor="middle" x="407" y="-157.8" font-family="Times,serif" font-size="14.00">index_categories</text>
<g id="node11" class="node"><title>index_categories</title>
<ellipse fill="none" stroke="black" cx="250" cy="-162" rx="76.1936" ry="18"></ellipse>
<text text-anchor="middle" x="250" y="-157.8" font-family="Times,serif" font-size="14.00">index_categories</text>
</g>
<!-- index_app_names&#45;&gt;index_categories -->
<g id="edge8" class="edge"><title>index_app_names-&gt;index_categories</title>
<path fill="none" stroke="black" d="M324.049,-216.765C338.77,-207.223 357.567,-195.04 373.545,-184.684"></path>
<polygon fill="black" stroke="black" points="375.599,-187.523 382.087,-179.147 371.792,-181.649 375.599,-187.523"></polygon>
</g>
<!-- index_app_names&#45;&gt;index_rank -->
<g id="edge10" class="edge"><title>index_app_names-&gt;index_rank</title>
<path fill="none" stroke="black" d="M288.833,-216.055C283.88,-207.801 277.821,-197.701 272.323,-188.538"></path>
<polygon fill="black" stroke="black" points="275.22,-186.563 267.073,-179.789 269.217,-190.165 275.22,-186.563"></polygon>
<g id="edge10" class="edge"><title>index_app_names-&gt;index_categories</title>
<path fill="none" stroke="black" d="M258.281,-215.697C257.069,-207.983 255.612,-198.712 254.261,-190.112"></path>
<polygon fill="black" stroke="black" points="257.698,-189.44 252.688,-180.104 250.783,-190.526 257.698,-189.44"></polygon>
</g>
<!-- html_index_domains -->
<g id="node13" class="node"><title>html_index_domains</title>
<ellipse fill="none" stroke="black" cx="92" cy="-162" rx="92.3709" ry="18"></ellipse>
<text text-anchor="middle" x="92" y="-157.8" font-family="Times,serif" font-size="14.00">html_index_domains</text>
<ellipse fill="none" stroke="black" cx="511" cy="-162" rx="92.3709" ry="18"></ellipse>
<text text-anchor="middle" x="511" y="-157.8" font-family="Times,serif" font-size="14.00">html_index_domains</text>
</g>
<!-- index_app_names&#45;&gt;html_index_domains -->
<g id="edge12" class="edge"><title>index_app_names-&gt;html_index_domains</title>
<path fill="none" stroke="black" d="M256.611,-218.666C224.5,-207.807 180.162,-192.813 145.592,-181.123"></path>
<polygon fill="black" stroke="black" points="146.712,-177.807 136.118,-177.919 144.47,-184.438 146.712,-177.807"></polygon>
<path fill="none" stroke="black" d="M309.203,-219.503C349.483,-208.225 407.134,-192.082 450.51,-179.937"></path>
<polygon fill="black" stroke="black" points="451.685,-183.243 460.371,-177.176 449.798,-176.502 451.685,-183.243"></polygon>
</g>
<!-- html_categories -->
<g id="node15" class="node"><title>html_categories</title>
<ellipse fill="none" stroke="black" cx="407" cy="-90" rx="72.5712" ry="18"></ellipse>
<text text-anchor="middle" x="407" y="-85.8" font-family="Times,serif" font-size="14.00">html_categories</text>
<ellipse fill="none" stroke="black" cx="72" cy="-90" rx="72.5712" ry="18"></ellipse>
<text text-anchor="middle" x="72" y="-85.8" font-family="Times,serif" font-size="14.00">html_categories</text>
</g>
<!-- index_categories&#45;&gt;html_categories -->
<g id="edge14" class="edge"><title>index_categories-&gt;html_categories</title>
<path fill="none" stroke="black" d="M407,-143.697C407,-135.983 407,-126.712 407,-118.112"></path>
<polygon fill="black" stroke="black" points="410.5,-118.104 407,-108.104 403.5,-118.104 410.5,-118.104"></polygon>
<path fill="none" stroke="black" d="M212.251,-146.155C184.928,-135.41 147.83,-120.821 118.651,-109.346"></path>
<polygon fill="black" stroke="black" points="119.672,-105.986 109.084,-105.584 117.11,-112.501 119.672,-105.986"></polygon>
</g>
<!-- html_index_apps -->
<g id="node17" class="node"><title>html_index_apps</title>
<ellipse fill="none" stroke="black" cx="575" cy="-90" rx="77.3345" ry="18"></ellipse>
<text text-anchor="middle" x="575" y="-85.8" font-family="Times,serif" font-size="14.00">html_index_apps</text>
<ellipse fill="none" stroke="black" cx="240" cy="-90" rx="77.3345" ry="18"></ellipse>
<text text-anchor="middle" x="240" y="-85.8" font-family="Times,serif" font-size="14.00">html_index_apps</text>
</g>
<!-- index_categories&#45;&gt;html_index_apps -->
<g id="edge16" class="edge"><title>index_categories-&gt;html_index_apps</title>
<path fill="none" stroke="black" d="M443.04,-145.983C468.396,-135.418 502.52,-121.2 529.715,-109.869"></path>
<polygon fill="black" stroke="black" points="531.135,-113.069 539.019,-105.992 528.442,-106.607 531.135,-113.069"></polygon>
<path fill="none" stroke="black" d="M247.528,-143.697C246.426,-135.983 245.102,-126.712 243.873,-118.112"></path>
<polygon fill="black" stroke="black" points="247.323,-117.509 242.443,-108.104 240.393,-118.499 247.323,-117.509"></polygon>
</g>
<!-- html_bundle -->
<g id="node22" class="node"><title>html_bundle</title>
<ellipse fill="none" stroke="black" cx="256" cy="-90" rx="59.2871" ry="18"></ellipse>
<text text-anchor="middle" x="256" y="-85.8" font-family="Times,serif" font-size="14.00">html_bundle</text>
</g>
<!-- index_rank&#45;&gt;html_bundle -->
<g id="edge22" class="edge"><title>index_rank-&gt;html_bundle</title>
<path fill="none" stroke="black" d="M256.753,-143.697C256.643,-135.983 256.51,-126.712 256.387,-118.112"></path>
<polygon fill="black" stroke="black" points="259.887,-118.053 256.244,-108.104 252.888,-118.153 259.887,-118.053"></polygon>
<!-- index_categories&#45;&gt;index_rank -->
<g id="edge32" class="edge"><title>index_categories-&gt;index_rank</title>
<path fill="none" stroke="black" d="M281.071,-145.465C302.076,-134.962 329.938,-121.031 352.226,-109.887"></path>
<polygon fill="black" stroke="black" points="354.021,-112.903 361.4,-105.3 350.89,-106.642 354.021,-112.903"></polygon>
</g>
<!-- html_root -->
<g id="node28" class="node"><title>html_root</title>
<ellipse fill="none" stroke="black" cx="92" cy="-90" rx="49.1927" ry="18"></ellipse>
<text text-anchor="middle" x="92" y="-85.8" font-family="Times,serif" font-size="14.00">html_root</text>
<g id="node29" class="node"><title>html_root</title>
<ellipse fill="none" stroke="black" cx="512" cy="-90" rx="49.1927" ry="18"></ellipse>
<text text-anchor="middle" x="512" y="-85.8" font-family="Times,serif" font-size="14.00">html_root</text>
</g>
<!-- html_index_domains&#45;&gt;html_root -->
<g id="edge30" class="edge"><title>html_index_domains-&gt;html_root</title>
<path fill="none" stroke="black" d="M92,-143.697C92,-135.983 92,-126.712 92,-118.112"></path>
<polygon fill="black" stroke="black" points="95.5001,-118.104 92,-108.104 88.5001,-118.104 95.5001,-118.104"></polygon>
<path fill="none" stroke="black" d="M511.247,-143.697C511.357,-135.983 511.49,-126.712 511.613,-118.112"></path>
<polygon fill="black" stroke="black" points="515.112,-118.153 511.756,-108.104 508.113,-118.053 515.112,-118.153"></polygon>
</g>
<!-- html_ranking -->
<g id="node25" class="node"><title>html_ranking</title>
<ellipse fill="none" stroke="black" cx="491" cy="-18" rx="63.108" ry="18"></ellipse>
<text text-anchor="middle" x="491" y="-13.8" font-family="Times,serif" font-size="14.00">html_ranking</text>
<g id="node26" class="node"><title>html_ranking</title>
<ellipse fill="none" stroke="black" cx="143" cy="-18" rx="63.108" ry="18"></ellipse>
<text text-anchor="middle" x="143" y="-13.8" font-family="Times,serif" font-size="14.00">html_ranking</text>
</g>
<!-- html_categories&#45;&gt;html_ranking -->
<g id="edge28" class="edge"><title>html_categories-&gt;html_ranking</title>
<path fill="none" stroke="black" d="M426.907,-72.411C437.865,-63.2789 451.629,-51.8093 463.59,-41.8418"></path>
<polygon fill="black" stroke="black" points="466.084,-44.3191 471.526,-35.2285 461.603,-38.9416 466.084,-44.3191"></polygon>
<path fill="none" stroke="black" d="M88.826,-72.411C97.845,-63.519 109.112,-52.4107 119.032,-42.6309"></path>
<polygon fill="black" stroke="black" points="121.631,-44.9831 126.295,-35.4699 116.716,-39.9983 121.631,-44.9831"></polygon>
</g>
<!-- html_index_apps&#45;&gt;html_ranking -->
<g id="edge26" class="edge"><title>html_index_apps-&gt;html_ranking</title>
<path fill="none" stroke="black" d="M555.093,-72.411C544.135,-63.2789 530.371,-51.8093 518.41,-41.8418"></path>
<polygon fill="black" stroke="black" points="520.397,-38.9416 510.474,-35.2285 515.916,-44.3191 520.397,-38.9416"></polygon>
<path fill="none" stroke="black" d="M217.502,-72.7646C204.433,-63.3328 187.786,-51.3197 173.544,-41.0422"></path>
<polygon fill="black" stroke="black" points="175.198,-37.9197 165.041,-34.906 171.102,-43.596 175.198,-37.9197"></polygon>
</g>
<!-- html_bundle -->
<g id="node23" class="node"><title>html_bundle</title>
<ellipse fill="none" stroke="black" cx="526" cy="-18" rx="59.2871" ry="18"></ellipse>
<text text-anchor="middle" x="526" y="-13.8" font-family="Times,serif" font-size="14.00">html_bundle</text>
</g>
<!-- index_rank&#45;&gt;html_bundle -->
<g id="edge22" class="edge"><title>index_rank-&gt;html_bundle</title>
<path fill="none" stroke="black" d="M418.179,-74.496C438.33,-64.1241 465.685,-50.0446 487.798,-38.6626"></path>
<polygon fill="black" stroke="black" points="489.629,-41.6569 496.918,-33.9684 486.425,-35.4329 489.629,-41.6569"></polygon>
</g>
<!-- html_group_compare -->
<g id="node32" class="node"><title>html_group_compare</title>
<ellipse fill="none" stroke="black" cx="354" cy="-18" rx="94.2613" ry="18"></ellipse>
<text text-anchor="middle" x="354" y="-13.8" font-family="Times,serif" font-size="14.00">html_group_compare</text>
</g>
<!-- index_rank&#45;&gt;html_group_compare -->
<g id="edge34" class="edge"><title>index_rank-&gt;html_group_compare</title>
<path fill="none" stroke="black" d="M381.285,-72.055C377.146,-64.0067 372.105,-54.2046 367.488,-45.2259"></path>
<polygon fill="black" stroke="black" points="370.57,-43.5675 362.884,-36.2753 364.345,-46.7689 370.57,-43.5675"></polygon>
</g>
<!-- index_domains&#45;&gt;html_index_domains -->
<g id="edge24" class="edge"><title>index_domains-&gt;html_index_domains</title>
<path fill="none" stroke="black" d="M92,-215.697C92,-207.983 92,-198.712 92,-190.112"></path>
<polygon fill="black" stroke="black" points="95.5001,-190.104 92,-180.104 88.5001,-190.104 95.5001,-190.104"></polygon>
<path fill="none" stroke="black" d="M501.966,-215.697C503.289,-207.983 504.878,-198.712 506.352,-190.112"></path>
<polygon fill="black" stroke="black" points="509.828,-190.552 508.068,-180.104 502.928,-189.369 509.828,-190.552"></polygon>
</g>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 9.8 KiB

After

Width:  |  Height:  |  Size: 10 KiB

View File

@@ -3,9 +3,9 @@
<head>
<meta charset="utf-8"/>
<meta name="viewport" content="width=device-width, initial-scale=0.75" />
<script type="text/javascript" src="/static/script.js?4"></script>
<script type="text/javascript" src="/static/script.js?5"></script>
<title>#_TITLE_#AppCheck: Privacy Monitor</title>
<link rel="stylesheet" type="text/css" href="/static/style.css?4">
<link rel="stylesheet" type="text/css" href="/static/style.css?5">
<link rel="stylesheet" type="text/css" href="/static/fonts/font.css">
<link rel="apple-touch-icon" sizes="180x180" href="/static/favicon/apple-touch-icon.png">
<link rel="icon" type="image/png" sizes="32x32" href="/static/favicon/favicon-32x32.png">