Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion .github/workflows/update-pages.yml
Original file line number Diff line number Diff line change
Expand Up @@ -49,14 +49,19 @@ jobs:
run: |
mkdir -p gh-pages
git fetch --depth=1 origin gh-pages
for cache_path in github/commitActivity github/commitActivityHashes github/prMetrics; do
for cache_path in github/commitActivity github/commitActivityHashes github/prMetrics azure; do
if git cat-file -e "origin/gh-pages:${cache_path}"; then
git archive origin/gh-pages "${cache_path}" | tar -x -C gh-pages
fi
done

- name: Collect data
env:
DASHBOARD_AZURE_SIGNING_CACHE_ONLY: ${{ github.event_name == 'pull_request' }}
AZURE_SIGNING_RESOURCE_ID: ${{ secrets.AZURE_SIGNING_RESOURCE_ID }}
AZURE_CLIENT_ID: ${{ secrets.AZURE_CLIENT_ID }}
AZURE_CLIENT_SECRET: ${{ secrets.AZURE_CLIENT_SECRET }}
AZURE_TENANT_ID: ${{ secrets.AZURE_TENANT_ID }}
DASHBOARD_AUR_REPOS: sunshine,sunshine-bin,sunshine-git
CODECOV_TOKEN: ${{ secrets.CODECOV_API_TOKEN }}
DISCORD_INVITE: ${{ secrets.DISCORD_INVITE }}
Expand Down
58 changes: 58 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,64 @@

A dashboard for viewing LizardByte repository data inside a Jekyll static site.

## Azure code signing metrics

The optional Azure Code Signing section displays Artifact Signing (formerly Trusted Signing)
completed signing requests: UTC month-to-date and last-30-day totals, plus daily history.
Counts cover the entire signing account. Azure's standard `SignCompleted` metric does not include
repository, file, certificate profile, failure-rate, or signing-duration dimensions; these counts
are not billing records. Azure reporting can be delayed and the current day is incomplete.
Missing metric samples are not presented as confirmed zero usage.

### Collection costs

The collector refreshes the current UTC day and recent unsettled days, with a three-hour cache and
no automatic retries. Each update also backfills at most seven missing historical days, starting
with the oldest dates in Azure's available 90-day window. Once a complete day has had two full
days of reporting grace and is fetched successfully, it is finalized and never fetched again.
Historical gaps are queried separately, so a backfill request never spans finalized days.
Finalized history is retained indefinitely in the existing `gh-pages` branch, without Azure storage.
Successfully queried days with no numeric samples remain unknown, rather than becoming zero;
partial month and 30-day totals are labeled while history fills in.

The scheduled job runs eight times per day: one metric query per update when caught up, or at
most two while backfilling (at most 496 queries in a 31-day month for one account). Site visitors
read the generated JSON and never query Azure.
Pull-request builds use only the Azure data restored from the published `gh-pages` cache and make
no Azure API calls, even when signing secrets are available. Their preview displays cached counts
when available; without published Azure data, the section stays hidden.
No diagnostic settings, Azure Storage, Log Analytics, Event Hubs, custom metrics, or Azure alerts
are created or required.

Microsoft currently lists unlimited standard platform metric ingestion as free and the first
1,000,000 metric query API calls per month as included. This allowance is shared with other consumers
in the Azure subscription; exceeding it can incur charges. With no other monitoring consumers,
this collector's scheduled usage fits comfortably within the included allowance. Your existing
signing plan and any signature overage charges still apply independently of this dashboard.
See [Azure Monitor pricing](https://azure.microsoft.com/en-us/pricing/details/monitor/) and
[Artifact Signing's supported metric](https://learn.microsoft.com/en-us/azure/azure-monitor/reference/supported-metrics/microsoft-codesigning-codesigningaccounts-metrics).

### Setup

1. Set the repository or organization secret `AZURE_SIGNING_RESOURCE_ID` to the full existing account ID:
`/subscriptions/<subscription-id>/resourceGroups/<resource-group>/providers/Microsoft.CodeSigning/codeSigningAccounts/<account>`.
In the Azure portal, open the signing account, select **Overview → JSON View**, and copy its **ID**.
`AZURE_SIGNING_ACCOUNT` supplies only the final account name; it is not the full resource ID.
`AZURE_SIGNING_CERT_PROFILE` is not needed for these account-wide metrics.
The resource ID does not grant access by itself, but using a secret keeps the subscription and
resource identifiers private and enables GitHub Actions log masking.
2. Make the secrets `AZURE_TENANT_ID`, `AZURE_CLIENT_ID`, and `AZURE_CLIENT_SECRET` available to this
repository. These are the same credential names used by the organization's signing workflows.
The service principal needs read access to metrics on this account; **Monitoring Reader** scoped
to the signing account supplies that permission. The signing role alone may not allow metric reads.
3. Run the Update workflow or wait for its next scheduled run.

Local collection accepts the same names in the environment or ignored `.env` file.
Collection stays disabled and the section stays hidden until the resource ID is configured.
Only counts, dates, and collection status appear in the dashboard data; credentials and raw Azure
responses are never published. A failed window retains its previous data, is eligible for retry
after three hours, and does not discard other successfully collected windows.

## Testing

### Python unit tests
Expand Down
59 changes: 58 additions & 1 deletion gh-pages-template/assets/js/dashboard.js
Original file line number Diff line number Diff line change
Expand Up @@ -620,19 +620,74 @@ function renderDocsChart(repos) {
}, false), CONFIG);
}

// Azure Artifact Signing account totals
function renderAzureSigning(data) {
const section = document.getElementById('azure-signing');
if (!section) return;
const enabled = Boolean(data && data.status !== 'disabled');
section.hidden = !enabled;
const nav = document.getElementById('azure-signing-nav');
if (nav) nav.hidden = !enabled;
if (!enabled) return;

const summary = document.getElementById('azure-signing-summary');
summary.replaceChildren();
const status = document.getElementById('azure-signing-status');
if (!data.daily.some(point => point.completed !== null)) {
status.textContent = data.status === 'error'
? 'Signing metrics are temporarily unavailable.'
: 'Azure has not reported signing counts for this period.';
document.getElementById('chart-azure-signing').hidden = true;
return;
}
const updated = new Date(data.collected_at).toUTCString();
status.textContent = data.status === 'error'
? `Some metrics could not be refreshed. Available data last updated: ${updated}.`
: `Metrics collected: ${updated}.`;
for (const [value, label] of [
[data.month_to_date, `Month to Date (UTC)${data.month_to_date_complete ? '' : ' — partial history'}`],
[data.last_30_days, `Last 30 Days (UTC)${data.last_30_days_complete ? '' : ' — partial history'}`],
]) {
const card = document.createElement('div');
card.className = 'col-6 mb-3 text-center';
const number = document.createElement('h3');
number.className = 'fw-bold';
number.textContent = value === null ? 'Unavailable' : value.toLocaleString();
const caption = document.createElement('small');
caption.className = 'text-muted';
caption.textContent = label;
card.append(number, caption);
summary.append(card);
}
document.getElementById('chart-azure-signing').hidden = false;
Plotly.newPlot('chart-azure-signing', [{
x: data.daily.map(point => point.date),
y: data.daily.map(point => point.completed),
type: 'bar',
marker: { color: '#28a9e6' },
hovertemplate: '%{x}: %{y} completed requests<extra></extra>',
}], themeLayout({
xaxis: { title: { text: 'Date (UTC)' }, type: 'date' },
yaxis: { title: { text: 'Completed Requests' }, rangemode: 'tozero' },
margin: { t: 30, r: 20, b: 60, l: 60 },
}), CONFIG);
}

// Main
async function loadDashboard() {
const loadingEl = document.getElementById('loading-msg');
const contentEl = document.getElementById('dashboard-content');
try {
const [repos, prs, metadata, coverageHistory, commitActivity, starHistory, codeScanningHistory] = await Promise.all([
const [repos, prs, metadata, coverageHistory, commitActivity, starHistory,
codeScanningHistory, azureSigning] = await Promise.all([
fetchJSON('repos.json'),
fetchJSON('prs.json'),
fetchJSON('metadata.json'),
fetchJSON('coverage_history.json').catch(() => []),
fetchJSON('commit_activity.json').catch(() => []),
fetchJSON('star_history.json').catch(() => []),
fetchJSON('code_scanning_history.json').catch(() => []),
fetchJSON('azure_signing.json').catch(() => null),
]);

const active = activeRepos(repos);
Expand Down Expand Up @@ -671,6 +726,7 @@ async function loadDashboard() {
renderLanguageCharts(active);
renderCommitActivityChart(commitActivity, active);
renderDocsChart(active);
renderAzureSigning(azureSigning);

} catch (err) {
if (loadingEl) loadingEl.innerHTML =
Expand Down Expand Up @@ -705,6 +761,7 @@ if (typeof module !== 'undefined' && module.exports) {
renderLanguageCharts,
renderCommitActivityChart,
renderDocsChart,
renderAzureSigning,
loadDashboard,
};
}
12 changes: 12 additions & 0 deletions gh-pages-template/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -64,9 +64,21 @@
<li class="nav-item"><a class="nav-link py-0" href="#commit-activity">Commit Activity</a></li>
<li class="nav-item"><a class="nav-link py-0" href="#languages">Languages</a></li>
<li class="nav-item"><a class="nav-link py-0" href="#docs">Docs</a></li>
<li class="nav-item" id="azure-signing-nav" hidden><a class="nav-link py-0" href="#azure-signing">Code Signing</a></li>
</ul>
</nav>

<!-- Azure Code Signing -->
<section id="azure-signing" class="mb-5" hidden>
<h2>Azure Code Signing</h2>
<p class="text-muted">Completed signing requests across the Artifact Signing account. Dates are UTC;
today's data is partial and Azure reporting may be delayed. These counts are not an invoice.</p>
<p id="azure-signing-status" role="status"></p>
<div class="row" id="azure-signing-summary"></div>
<h3>Daily Completed Requests</h3>
<div id="chart-azure-signing" style="height:400px"></div>
</section>

<!-- Stars -->
<section id="stars" class="mb-5">
<h2>Star Gazers</h2>
Expand Down
Loading
Loading