{% if render_mode != 'fragment' %} {% if domain %}{% endif %} License Report - Finite State Report {# ECharts library — replaces Chart.js v4.4.1 and the chartjs-plugin-datalabels dependency from the pre-migration template. ECharts has native series.label.formatter for the count+percent labels the old plugin emitted. #} {% include "_design_system.html" %} {% include "_console_shell.html" %} {% include "_echarts_ready.html" %} {% endif %} {# Recipe-local styles — render in BOTH standalone and fragment modes so fragment_extractor (fragment_extractor.py:_STYLE_RE) collects them, scopes under .fs-section-license-report, and re-emits at the top of the fragment. This is the round-1 multi-review fix pattern established by PR #80. #} {% if render_mode != 'fragment' %} {% endif %} {% import "_console_macros.html" as fs with context %} {# Defensive defaults — keeps the template renderable even when the upstream transform short-circuited and never populated these (e.g. empty raw_data path before the data_transformer fix landed). Mirrors component_remediation_package.html. Belt-and-suspenders for CST-747. #} {% set category_summary = category_summary | default({}) %} {% if not category_summary is mapping %}{% set category_summary = {} %}{% endif %} {% set risk_pie = risk_pie | default({'labels': [], 'data': [], 'backgroundColor': []}) %} {% set license_table = license_table | default([]) %} {% set detail_table = detail_table | default([]) %} {% set total_components = total_components | default(0) %} {% set total_licenses = total_licenses | default(0) %} {% set no_license_count = no_license_count | default(0) %} {% if render_mode != 'fragment' %} {{ fs.topbar( crumbs=["Finite State", "License Report"], meta=[ {"label": "Domain", "value": domain or "—"}, {"label": "Scope", "value": scope_label | default("All Projects")}, {"label": "Generated", "value": generated_at or "—"}, ], controls=[], ) }} {% endif %}

License Risk Report

{{ scope_label | default('All Projects') }} — Software License Analysis

{{ fs.kpi_cell("Total Components", total_components | default(0)) }} {{ fs.kpi_cell("Unique Licenses", total_licenses | default(0)) }} {{ fs.kpi_cell("Strong Copyleft", category_summary.get('Strong Copyleft', 0), dot=("critical" if category_summary.get('Strong Copyleft', 0) > 0 else None)) }} {{ fs.kpi_cell("Weak Copyleft", category_summary.get('Weak Copyleft', 0), dot=("high" if category_summary.get('Weak Copyleft', 0) > 0 else None)) }} {{ fs.kpi_cell("Permissive", category_summary.get('Permissive', 0), dot=("low" if category_summary.get('Permissive', 0) > 0 else None)) }} {{ fs.kpi_cell("No License", no_license_count | default(0), dot=("high" if no_license_count | default(0) > 0 else None)) }}
{% if risk_pie and risk_pie.labels | default([]) | length > 0 %}
{{ fs.panel_head("License risk distribution", meta="doughnut") }}

Components grouped by license risk category.

{# Dual-render: fragment mode uses the server-side SVG produced by HTMLRenderer (html_renderer.py:937-967 coerces `risk_pie` dict to a 3-col DataFrame via pd.DataFrame and dispatches to ChartRenderer.render_doughnut_svg since recipe YAML now declares chart: doughnut). Standalone mode uses the live ECharts container the {# `_action_buttons.html` provides the connection-badge JS that fills the #fsConnectionBadge slot reserved by `_console_macros.html:73`'s topbar macro. Used by `fs-report serve` mode to show a live-connection indicator. Mirrors `triage_prioritization.html:1276`. #} {% include '_action_buttons.html' %} {% endif %}