'
if show_flags:
for flag in link.get('flags', []):
out += f'
โ {esc(flag)}
'
out += '
'
return out
def render_accordion(collapsed_html, details_html):
return f'''
โถ
{collapsed_html}
{details_html}
'''
def generate_report(results, orphan_pages, domain="edstellar.com"):
now = datetime.now().strftime("%b %d, %Y %H:%M")
total_pages = len(results)
total_int = sum(r['int_count'] for r in results)
total_ext = sum(r['ext_count'] for r in results)
total_broken = sum(r['broken_int_count'] + r['broken_ext_count'] for r in results)
total_redirects = sum(r['redirect_int_count'] + r['redirect_ext_count'] for r in results)
total_flags = sum(r['follow_flag_count'] for r in results)
total_dups = sum(r['duplicate_count'] for r in results)
total_sug = sum(len(r['suggestions']) for r in results)
total_orphan = len(orphan_pages)
rows_html = ""
for idx, r in enumerate(results, 1):
if r['error']:
rows_html += f'''
#{idx}
{esc(short_url(r["url"], domain))}
โ Error: {esc(r["error"])}
'''
continue
is_orphan = r['url'].rstrip('/').split('?')[0] in orphan_pages
# Internal Links
int_badges = ""
if r['int_count'] == 0:
int_badges = badge('0 Links โ ', 'issue')
else:
ok_count = r['int_count'] - r['broken_int_count'] - r['redirect_int_count']
if ok_count > 0: int_badges += badge(f'{ok_count} Active', 'ok')
if r['broken_int_count'] > 0: int_badges += badge(f'{r["broken_int_count"]} Broken', 'issue')
if r['redirect_int_count'] > 0: int_badges += badge(f'{r["redirect_int_count"]} Redirect', 'issue')
int_details = "".join(render_link_entry(l, domain) for l in r['internal_links'])
if not int_details:
int_details = '
No internal links found in body content.
'
# External Links
ext_badges = ""
if r['ext_count'] == 0:
ext_badges = badge('0 Links', 'neutral')
else:
if r['broken_ext_count'] > 0: ext_badges += badge(f'{r["broken_ext_count"]} Broken', 'issue')
if r['redirect_ext_count'] > 0: ext_badges += badge(f'{r["redirect_ext_count"]} Redirect', 'issue')
ext_df_count = sum(1 for l in r['external_links'] if l['follow'] == 'Dofollow')
if ext_df_count > 0: ext_badges += badge(f'{ext_df_count} Dofollow โ ', 'issue')
ok_ext = r['ext_count'] - r['broken_ext_count'] - r['redirect_ext_count']
if ok_ext > 0 and not r['broken_ext_count'] and not r['redirect_ext_count'] and not ext_df_count:
ext_badges += badge(f'{ok_ext} Active', 'ok')
ext_details = "".join(render_link_entry(l, domain) for l in r['external_links'])
if not ext_details:
ext_details = '
No external links in body content.
'
# Follow Flags
int_nf_flags = [l for l in r['internal_links'] if l['follow'] == 'Nofollow']
ext_df_flags_list = [l for l in r['external_links'] if l['follow'] == 'Dofollow']
flag_badges = ""
if int_nf_flags: flag_badges += badge(f'{len(int_nf_flags)} Int. Nofollow โ ', 'issue')
if ext_df_flags_list: flag_badges += badge(f'{len(ext_df_flags_list)} Ext. Dofollow โ ', 'issue')
if not flag_badges: flag_badges = badge('โ No Flags', 'ok')
flag_details = "".join(render_link_entry(l, domain, show_flags=True) for l in int_nf_flags + ext_df_flags_list)
if not flag_details:
flag_details = '
All internal=Dofollow โ and external=Nofollow โ
'
# Broken / Redirect
bi_badges = badge(f'{r["broken_int_count"]} Broken', 'issue') if r['broken_int_count'] > 0 else badge('โ None', 'ok')
bi_details = "".join(render_link_entry(l, domain) for l in r['broken_internal']) or '
No broken internal links.
'
be_badges = badge(f'{r["broken_ext_count"]} Broken', 'issue') if r['broken_ext_count'] > 0 else badge('โ None', 'ok')
be_details = "".join(render_link_entry(l, domain) for l in r['broken_external']) or '
No broken external links.
'
ri_badges = badge(f'{r["redirect_int_count"]} Redirects', 'issue') if r['redirect_int_count'] > 0 else badge('โ None', 'ok')
ri_details = "".join(render_link_entry(l, domain) for l in r['redirect_internal']) or '
No internal redirects.
'
re_badges = badge(f'{r["redirect_ext_count"]} Redirects', 'issue') if r['redirect_ext_count'] > 0 else badge('โ None', 'ok')
re_details = "".join(render_link_entry(l, domain) for l in r['redirect_external']) or '
No external redirects.
'
# Duplicates
dup_badges = badge(f'{r["duplicate_count"]} Duplicates', 'issue') if r['duplicate_count'] > 0 else badge('โ None', 'ok')
dup_details = ""
for d in r['duplicates']:
locs = ", ".join(esc(l) for l in d['locations'])
dup_details += f'
{esc(short_url(d["url"], domain))}
'
dup_details += f'
โ Appears {d["count"]}x in body content
'
dup_details += f'
๐ Locations: {locs}
'
if not dup_details:
dup_details = '
No duplicate links in body content.
'
# Suggestions
sug_list = r['suggestions']
high_count = sum(1 for s in sug_list if s['priority'] == 'High')
sug_badges = ""
if sug_list:
sug_badges = badge(f'{len(sug_list)} Suggestions', 'sug')
if high_count: sug_badges += badge(f'{high_count} High', 'issue')
else:
sug_badges = badge('0', 'neutral')
sug_details = ""
for s in sug_list:
pri_cls = 'high' if s['priority'] == 'High' else 'med'
sug_details += f'''
{esc(s["section"])}{s["priority"]}
{esc(s["target"])}
โ "{esc(s["anchor"])}"
'''
if not sug_details:
sug_details = '
No keyword matches for suggestions.
'
# Notes
issues = []
if r['int_count'] < 3:
issues.append(f"Only {r['int_count']} internal links โ very low for article length")
if r['broken_int_count'] + r['broken_ext_count'] > 0:
issues.append(f"{r['broken_int_count']+r['broken_ext_count']} broken link(s) need fixing")
if ext_df_flags_list:
issues.append(f"{len(ext_df_flags_list)} external links are Dofollow โ add nofollow")
if int_nf_flags:
issues.append(f"{len(int_nf_flags)} internal links are Nofollow โ should be Dofollow")
if r['redirect_int_count'] + r['redirect_ext_count'] > 0:
issues.append(f"{r['redirect_int_count']+r['redirect_ext_count']} redirect(s) โ update href")
if r['duplicate_count'] > 0:
issues.append(f"{r['duplicate_count']} duplicate link(s) in body")
if is_orphan:
issues.append("ORPHAN PAGE โ no incoming internal links from other pages")
note_badges = badge(f'{len(issues)} Issues', 'issue') if issues else badge('โ Clean', 'ok')
note_details = "".join(f'
โ {esc(issue)}
' for issue in issues)
if not note_details:
note_details = '