From 2fbafed46ea9bfb23955377baf70e03b4739d7cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexis=20M=C3=A9taireau?= Date: Mon, 11 Nov 2024 00:06:35 +0100 Subject: [PATCH] First commit --- generate_stats.py | 76 ++++++++ output/app.js | 8 + output/components/App.js | 28 +++ output/components/Chart.js | 113 ++++++++++++ output/components/Stats.js | 30 +++ output/stats.json | 1 + output/styles.css | 367 +++++++++++++++++++++++++++++++++++++ templates/index.html | 20 ++ 8 files changed, 643 insertions(+) create mode 100644 generate_stats.py create mode 100644 output/app.js create mode 100644 output/components/App.js create mode 100644 output/components/Chart.js create mode 100644 output/components/Stats.js create mode 100644 output/stats.json create mode 100644 output/styles.css create mode 100644 templates/index.html diff --git a/generate_stats.py b/generate_stats.py new file mode 100644 index 0000000..ed7ee32 --- /dev/null +++ b/generate_stats.py @@ -0,0 +1,76 @@ +#!/usr/bin/env -S uv run +# Needs "uv" to be run. chmod +x and then ./generate_stats.py +# /// script +# requires-python = ">=3.12" +# dependencies = [ +# "jinja2", "httpx" +# ] +# /// +import os +import json +from pathlib import Path +from datetime import datetime +from jinja2 import Environment, FileSystemLoader +import httpx +from typing import Dict, List, TypedDict + +class ReleaseStats(TypedDict): + releases: List[Dict] + +async def fetch_github_stats(repo: str) -> ReleaseStats: + """Fetch GitHub release statistics""" + async with httpx.AsyncClient() as client: + # Fetch releases with download counts + releases_resp = await client.get( + f"https://api.github.com/repos/{repo}/releases" + ) + releases = releases_resp.json() + + # Process releases to include download counts per asset + processed_releases = [] + for release in releases: + processed_releases.append({ + "name": release["name"] or release["tag_name"], + "published_at": release["published_at"], + "html_url": release["html_url"], + "assets": [{ + "name": asset["name"], + "download_count": asset["download_count"], + "download_url": asset["browser_download_url"] + } for asset in release["assets"]] + }) + + return { + "releases": processed_releases + } + +def generate_site(stats: ReleaseStats) -> None: + """Generate the static site using the templates""" + output_dir = Path("output") + output_dir.mkdir(exist_ok=True) + + templates_dir = Path("templates") + templates_dir.mkdir(exist_ok=True) + + env = Environment(loader=FileSystemLoader("templates")) + + template = env.get_template("index.html") + index_content = template.render( + stats=stats, + generated_at=datetime.now().isoformat() + ) + + with open(output_dir / "index.html", "w") as f: + f.write(index_content) + + with open(output_dir / "stats.json", "w") as f: + json.dump(stats, f) + +async def main(): + repo = os.getenv("GITHUB_REPO", "freedomofpress/dangerzone") + stats = await fetch_github_stats(repo) + generate_site(stats) + +if __name__ == "__main__": + import asyncio + asyncio.run(main()) \ No newline at end of file diff --git a/output/app.js b/output/app.js new file mode 100644 index 0000000..fb538ec --- /dev/null +++ b/output/app.js @@ -0,0 +1,8 @@ +import { h, render } from 'https://esm.sh/preact'; +import { useState } from 'https://esm.sh/preact/hooks'; +import { App } from './components/App.js'; + +const stats = window.INITIAL_STATS; +const generatedAt = window.GENERATED_AT; + +render(h(App, { stats, generatedAt }), document.getElementById('app')); \ No newline at end of file diff --git a/output/components/App.js b/output/components/App.js new file mode 100644 index 0000000..749ba26 --- /dev/null +++ b/output/components/App.js @@ -0,0 +1,28 @@ +import { h } from 'https://esm.sh/preact'; +import { useState } from 'https://esm.sh/preact/hooks'; +import { Stats } from './Stats.js'; +import { Chart } from './Chart.js'; + +export function App({ stats, generatedAt }) { + const [activeTab, setActiveTab] = useState('charts'); + + return h('div', { class: 'container' }, [ + h('header', { class: 'header' }, [ + h('h1', null, 'Dangerzone Release Stats'), + h('p', null, `Generated at: ${new Date(generatedAt).toLocaleString()}`) + ]), + h('nav', { class: 'tabs' }, [ + h('button', { + class: activeTab === 'charts' ? 'active' : '', + onClick: () => setActiveTab('charts') + }, 'Charts'), + h('button', { + class: activeTab === 'overview' ? 'active' : '', + onClick: () => setActiveTab('overview') + }, 'Overview') + ]), + activeTab === 'charts' + ? h(Chart, { stats }) + : h(Stats, { stats }) + ]); +} \ No newline at end of file diff --git a/output/components/Chart.js b/output/components/Chart.js new file mode 100644 index 0000000..c853936 --- /dev/null +++ b/output/components/Chart.js @@ -0,0 +1,113 @@ +import { h } from 'https://esm.sh/preact'; + +function getPlatform(assetName) { + assetName = assetName.toLowerCase(); + if (assetName.includes('msi')) return 'Windows'; + if (assetName.includes('arm64.dmg')) return 'Mac Silicon'; + if (assetName.includes('i686.dmg') || assetName.includes('.dmg')) return 'Mac Intel'; + if (assetName.includes('container')) return 'Container'; + return 'Other'; +} + +function getColorForPlatform(platform) { + switch (platform) { + case 'Windows': return '#00A4EF'; + case 'Mac Intel': return '#A2AAAD'; + case 'Mac Silicon': return '#C4C4C4'; + case 'Container': return '#FFD700'; + default: return '#FF69B4'; + } +} + +function formatDate(dateString) { + return new Date(dateString).toLocaleDateString(undefined, { + year: 'numeric', + month: 'short' + }); +} + +export function Chart({ stats }) { + console.log('Received stats:', stats); + + if (!stats || !stats.releases || !Array.isArray(stats.releases)) { + return h('div', { class: 'charts' }, 'No data available'); + } + + const platforms = ['Windows', 'Mac Intel', 'Mac Silicon', 'Container', 'Other']; + + const downloadsByRelease = stats.releases + .filter(release => release && release.name && release.published_at) + .map(release => { + console.log('Processing release:', release); + return { + name: (release.name || '').replace(/^Dangerzone /, ''), + date: release.published_at, + platforms: platforms.reduce((acc, platform) => { + acc[platform] = 0; + return acc; + }, {}), + total: 0, + assets: release.assets || [] + }; + }) + .map(release => { + release.assets.forEach(asset => { + const platform = getPlatform(asset.name); + release.platforms[platform] += asset.download_count || 0; + }); + + release.total = Object.values(release.platforms).reduce((a, b) => a + b, 0); + return release; + }) + .sort((a, b) => new Date(b.date) - new Date(a.date)); + + console.log('Processed releases:', downloadsByRelease); + + if (downloadsByRelease.length === 0) { + return h('div', { class: 'charts' }, 'No release data available'); + } + + const maxDownloads = Math.max(...downloadsByRelease.map(r => r.total)); + + return h('div', { class: 'charts' }, [ + h('div', { class: 'chart horizontal' }, [ + h('div', { class: 'chart-legend' }, + platforms.map(platform => + h('div', { class: 'legend-item' }, [ + h('span', { + class: 'legend-color', + style: `background-color: ${getColorForPlatform(platform)}` + }), + h('span', { class: 'legend-label' }, platform) + ]) + ) + ), + h('div', { class: 'chart-container horizontal' }, + downloadsByRelease.map(release => + h('div', { class: 'bar-group horizontal' }, [ + h('div', { class: 'bar-label horizontal' }, [ + h('span', { class: 'version-label' }, release.name), + h('span', { class: 'date-label' }, formatDate(release.date)) + ]), + h('div', { class: 'stacked-bars horizontal' }, + platforms.map(platform => { + const width = (release.platforms[platform] / maxDownloads) * 400; + return h('div', { + class: 'bar stacked-bar horizontal', + style: ` + width: ${width}px; + background-color: ${getColorForPlatform(platform)}; + `, + title: `${platform}: ${release.platforms[platform].toLocaleString()} downloads` + }); + }) + ), + h('div', { class: 'total-downloads' }, + `${release.total.toLocaleString()} downloads` + ) + ]) + ) + ) + ]) + ]); +} \ No newline at end of file diff --git a/output/components/Stats.js b/output/components/Stats.js new file mode 100644 index 0000000..e4e0043 --- /dev/null +++ b/output/components/Stats.js @@ -0,0 +1,30 @@ +import { h } from 'https://esm.sh/preact'; + +export function Stats({ stats }) { + return h('div', { class: 'stats-grid' }, [ + ...stats.releases.map(release => + h('div', { class: 'stat-card' }, [ + h('h2', null, [ + h('a', { href: release.html_url, target: '_blank' }, release.name) + ]), + h('p', { class: 'release-date' }, + new Date(release.published_at).toLocaleDateString() + ), + h('div', { class: 'assets-list' }, + release.assets.map(asset => + h('div', { class: 'asset-item' }, [ + h('a', { + href: asset.download_url, + class: 'asset-name', + target: '_blank' + }, asset.name), + h('span', { class: 'download-count' }, + `${asset.download_count.toLocaleString()} downloads` + ) + ]) + ) + ) + ]) + ) + ]); +} \ No newline at end of file diff --git a/output/stats.json b/output/stats.json new file mode 100644 index 0000000..dae5b8a --- /dev/null +++ b/output/stats.json @@ -0,0 +1 @@ +{"releases": [{"name": "Dangerzone 0.8.0", "published_at": "2024-11-06T11:21:30Z", "html_url": "https://github.com/freedomofpress/dangerzone/releases/tag/v0.8.0", "assets": [{"name": "checksums-0.8.0.txt", "download_count": 4, "download_url": "https://github.com/freedomofpress/dangerzone/releases/download/v0.8.0/checksums-0.8.0.txt"}, {"name": "container-0.8.0-arm64.tar.gz", "download_count": 1, "download_url": "https://github.com/freedomofpress/dangerzone/releases/download/v0.8.0/container-0.8.0-arm64.tar.gz"}, {"name": "container-0.8.0-arm64.tar.gz.asc", "download_count": 1, "download_url": "https://github.com/freedomofpress/dangerzone/releases/download/v0.8.0/container-0.8.0-arm64.tar.gz.asc"}, {"name": "container-0.8.0-i686.tar.gz", "download_count": 8, "download_url": "https://github.com/freedomofpress/dangerzone/releases/download/v0.8.0/container-0.8.0-i686.tar.gz"}, {"name": "container-0.8.0-i686.tar.gz.asc", "download_count": 2, "download_url": "https://github.com/freedomofpress/dangerzone/releases/download/v0.8.0/container-0.8.0-i686.tar.gz.asc"}, {"name": "Dangerzone-0.8.0-arm64.dmg", "download_count": 252, "download_url": "https://github.com/freedomofpress/dangerzone/releases/download/v0.8.0/Dangerzone-0.8.0-arm64.dmg"}, {"name": "Dangerzone-0.8.0-arm64.dmg.asc", "download_count": 6, "download_url": "https://github.com/freedomofpress/dangerzone/releases/download/v0.8.0/Dangerzone-0.8.0-arm64.dmg.asc"}, {"name": "Dangerzone-0.8.0-i686.dmg", "download_count": 88, "download_url": "https://github.com/freedomofpress/dangerzone/releases/download/v0.8.0/Dangerzone-0.8.0-i686.dmg"}, {"name": "Dangerzone-0.8.0-i686.dmg.asc", "download_count": 3, "download_url": "https://github.com/freedomofpress/dangerzone/releases/download/v0.8.0/Dangerzone-0.8.0-i686.dmg.asc"}, {"name": "Dangerzone-0.8.0.msi", "download_count": 275, "download_url": "https://github.com/freedomofpress/dangerzone/releases/download/v0.8.0/Dangerzone-0.8.0.msi"}, {"name": "Dangerzone-0.8.0.msi.asc", "download_count": 2, "download_url": "https://github.com/freedomofpress/dangerzone/releases/download/v0.8.0/Dangerzone-0.8.0.msi.asc"}, {"name": "dangerzone-0.8.0.tar.gz", "download_count": 4, "download_url": "https://github.com/freedomofpress/dangerzone/releases/download/v0.8.0/dangerzone-0.8.0.tar.gz"}, {"name": "dangerzone-0.8.0.tar.gz.asc", "download_count": 1, "download_url": "https://github.com/freedomofpress/dangerzone/releases/download/v0.8.0/dangerzone-0.8.0.tar.gz.asc"}]}, {"name": "Dangerzone 0.7.1", "published_at": "2024-10-01T15:03:27Z", "html_url": "https://github.com/freedomofpress/dangerzone/releases/tag/v0.7.1", "assets": [{"name": "checksums-0.7.1.txt", "download_count": 11, "download_url": "https://github.com/freedomofpress/dangerzone/releases/download/v0.7.1/checksums-0.7.1.txt"}, {"name": "container.tar.gz", "download_count": 235, "download_url": "https://github.com/freedomofpress/dangerzone/releases/download/v0.7.1/container.tar.gz"}, {"name": "container.tar.gz.asc", "download_count": 2, "download_url": "https://github.com/freedomofpress/dangerzone/releases/download/v0.7.1/container.tar.gz.asc"}, {"name": "Dangerzone-0.7.1-arm64.dmg", "download_count": 809, "download_url": "https://github.com/freedomofpress/dangerzone/releases/download/v0.7.1/Dangerzone-0.7.1-arm64.dmg"}, {"name": "Dangerzone-0.7.1-arm64.dmg.asc", "download_count": 6, "download_url": "https://github.com/freedomofpress/dangerzone/releases/download/v0.7.1/Dangerzone-0.7.1-arm64.dmg.asc"}, {"name": "Dangerzone-0.7.1-i686.dmg", "download_count": 343, "download_url": "https://github.com/freedomofpress/dangerzone/releases/download/v0.7.1/Dangerzone-0.7.1-i686.dmg"}, {"name": "Dangerzone-0.7.1-i686.dmg.asc", "download_count": 4, "download_url": "https://github.com/freedomofpress/dangerzone/releases/download/v0.7.1/Dangerzone-0.7.1-i686.dmg.asc"}, {"name": "Dangerzone-0.7.1.msi", "download_count": 1523, "download_url": "https://github.com/freedomofpress/dangerzone/releases/download/v0.7.1/Dangerzone-0.7.1.msi"}, {"name": "Dangerzone-0.7.1.msi.asc", "download_count": 9, "download_url": "https://github.com/freedomofpress/dangerzone/releases/download/v0.7.1/Dangerzone-0.7.1.msi.asc"}, {"name": "dangerzone-0.7.1.tar.gz", "download_count": 12, "download_url": "https://github.com/freedomofpress/dangerzone/releases/download/v0.7.1/dangerzone-0.7.1.tar.gz"}, {"name": "dangerzone-0.7.1.tar.gz.asc", "download_count": 1, "download_url": "https://github.com/freedomofpress/dangerzone/releases/download/v0.7.1/dangerzone-0.7.1.tar.gz.asc"}]}, {"name": "Dangerzone 0.7.0", "published_at": "2024-07-10T15:13:58Z", "html_url": "https://github.com/freedomofpress/dangerzone/releases/tag/v0.7.0", "assets": [{"name": "checksums-0.7.0.txt", "download_count": 27, "download_url": "https://github.com/freedomofpress/dangerzone/releases/download/v0.7.0/checksums-0.7.0.txt"}, {"name": "container.tar.gz", "download_count": 335, "download_url": "https://github.com/freedomofpress/dangerzone/releases/download/v0.7.0/container.tar.gz"}, {"name": "container.tar.gz.asc", "download_count": 9, "download_url": "https://github.com/freedomofpress/dangerzone/releases/download/v0.7.0/container.tar.gz.asc"}, {"name": "Dangerzone-0.7.0-arm64.dmg", "download_count": 1551, "download_url": "https://github.com/freedomofpress/dangerzone/releases/download/v0.7.0/Dangerzone-0.7.0-arm64.dmg"}, {"name": "Dangerzone-0.7.0-arm64.dmg.asc", "download_count": 10, "download_url": "https://github.com/freedomofpress/dangerzone/releases/download/v0.7.0/Dangerzone-0.7.0-arm64.dmg.asc"}, {"name": "Dangerzone-0.7.0-i686.dmg", "download_count": 813, "download_url": "https://github.com/freedomofpress/dangerzone/releases/download/v0.7.0/Dangerzone-0.7.0-i686.dmg"}, {"name": "Dangerzone-0.7.0-i686.dmg.asc", "download_count": 8, "download_url": "https://github.com/freedomofpress/dangerzone/releases/download/v0.7.0/Dangerzone-0.7.0-i686.dmg.asc"}, {"name": "Dangerzone-0.7.0.msi", "download_count": 3218, "download_url": "https://github.com/freedomofpress/dangerzone/releases/download/v0.7.0/Dangerzone-0.7.0.msi"}, {"name": "Dangerzone-0.7.0.msi.asc", "download_count": 27, "download_url": "https://github.com/freedomofpress/dangerzone/releases/download/v0.7.0/Dangerzone-0.7.0.msi.asc"}, {"name": "dangerzone-0.7.0.tar.gz", "download_count": 31, "download_url": "https://github.com/freedomofpress/dangerzone/releases/download/v0.7.0/dangerzone-0.7.0.tar.gz"}, {"name": "dangerzone-0.7.0.tar.gz.asc", "download_count": 9, "download_url": "https://github.com/freedomofpress/dangerzone/releases/download/v0.7.0/dangerzone-0.7.0.tar.gz.asc"}]}, {"name": "Dangerzone 0.6.1", "published_at": "2024-05-13T18:31:20Z", "html_url": "https://github.com/freedomofpress/dangerzone/releases/tag/v0.6.1", "assets": [{"name": "checksums-0.6.1.txt", "download_count": 13, "download_url": "https://github.com/freedomofpress/dangerzone/releases/download/v0.6.1/checksums-0.6.1.txt"}, {"name": "container.tar.gz", "download_count": 253, "download_url": "https://github.com/freedomofpress/dangerzone/releases/download/v0.6.1/container.tar.gz"}, {"name": "container.tar.gz.asc", "download_count": 4, "download_url": "https://github.com/freedomofpress/dangerzone/releases/download/v0.6.1/container.tar.gz.asc"}, {"name": "Dangerzone-0.6.1-arm64.dmg", "download_count": 933, "download_url": "https://github.com/freedomofpress/dangerzone/releases/download/v0.6.1/Dangerzone-0.6.1-arm64.dmg"}, {"name": "Dangerzone-0.6.1-arm64.dmg.asc", "download_count": 7, "download_url": "https://github.com/freedomofpress/dangerzone/releases/download/v0.6.1/Dangerzone-0.6.1-arm64.dmg.asc"}, {"name": "Dangerzone-0.6.1-i686.dmg", "download_count": 527, "download_url": "https://github.com/freedomofpress/dangerzone/releases/download/v0.6.1/Dangerzone-0.6.1-i686.dmg"}, {"name": "Dangerzone-0.6.1-i686.dmg.asc", "download_count": 5, "download_url": "https://github.com/freedomofpress/dangerzone/releases/download/v0.6.1/Dangerzone-0.6.1-i686.dmg.asc"}, {"name": "Dangerzone-0.6.1.msi", "download_count": 1550, "download_url": "https://github.com/freedomofpress/dangerzone/releases/download/v0.6.1/Dangerzone-0.6.1.msi"}, {"name": "Dangerzone-0.6.1.msi.asc", "download_count": 13, "download_url": "https://github.com/freedomofpress/dangerzone/releases/download/v0.6.1/Dangerzone-0.6.1.msi.asc"}, {"name": "dangerzone-0.6.1.tar.gz", "download_count": 30, "download_url": "https://github.com/freedomofpress/dangerzone/releases/download/v0.6.1/dangerzone-0.6.1.tar.gz"}, {"name": "dangerzone-0.6.1.tar.gz.asc", "download_count": 2, "download_url": "https://github.com/freedomofpress/dangerzone/releases/download/v0.6.1/dangerzone-0.6.1.tar.gz.asc"}]}, {"name": "Dangerzone 0.6.0", "published_at": "2024-02-26T21:02:45Z", "html_url": "https://github.com/freedomofpress/dangerzone/releases/tag/v0.6.0", "assets": [{"name": "checksums-0.6.0.txt", "download_count": 9, "download_url": "https://github.com/freedomofpress/dangerzone/releases/download/v0.6.0/checksums-0.6.0.txt"}, {"name": "container.tar.gz", "download_count": 485, "download_url": "https://github.com/freedomofpress/dangerzone/releases/download/v0.6.0/container.tar.gz"}, {"name": "container.tar.gz.asc", "download_count": 4, "download_url": "https://github.com/freedomofpress/dangerzone/releases/download/v0.6.0/container.tar.gz.asc"}, {"name": "Dangerzone-0.6.0-1.msi", "download_count": 1887, "download_url": "https://github.com/freedomofpress/dangerzone/releases/download/v0.6.0/Dangerzone-0.6.0-1.msi"}, {"name": "Dangerzone-0.6.0-1.msi.asc", "download_count": 5, "download_url": "https://github.com/freedomofpress/dangerzone/releases/download/v0.6.0/Dangerzone-0.6.0-1.msi.asc"}, {"name": "Dangerzone-0.6.0-arm64.dmg", "download_count": 1197, "download_url": "https://github.com/freedomofpress/dangerzone/releases/download/v0.6.0/Dangerzone-0.6.0-arm64.dmg"}, {"name": "Dangerzone-0.6.0-arm64.dmg.asc", "download_count": 3, "download_url": "https://github.com/freedomofpress/dangerzone/releases/download/v0.6.0/Dangerzone-0.6.0-arm64.dmg.asc"}, {"name": "Dangerzone-0.6.0-i686.dmg", "download_count": 753, "download_url": "https://github.com/freedomofpress/dangerzone/releases/download/v0.6.0/Dangerzone-0.6.0-i686.dmg"}, {"name": "Dangerzone-0.6.0-i686.dmg.asc", "download_count": 3, "download_url": "https://github.com/freedomofpress/dangerzone/releases/download/v0.6.0/Dangerzone-0.6.0-i686.dmg.asc"}]}, {"name": "Dangerzone 0.5.1", "published_at": "2023-12-08T19:40:06Z", "html_url": "https://github.com/freedomofpress/dangerzone/releases/tag/v0.5.1", "assets": [{"name": "container.tar.gz", "download_count": 374, "download_url": "https://github.com/freedomofpress/dangerzone/releases/download/v0.5.1/container.tar.gz"}, {"name": "Dangerzone-0.5.1-arm64.dmg", "download_count": 1201, "download_url": "https://github.com/freedomofpress/dangerzone/releases/download/v0.5.1/Dangerzone-0.5.1-arm64.dmg"}, {"name": "Dangerzone-0.5.1-i686.dmg", "download_count": 741, "download_url": "https://github.com/freedomofpress/dangerzone/releases/download/v0.5.1/Dangerzone-0.5.1-i686.dmg"}, {"name": "Dangerzone-0.5.1.msi", "download_count": 2442, "download_url": "https://github.com/freedomofpress/dangerzone/releases/download/v0.5.1/Dangerzone-0.5.1.msi"}]}, {"name": "Dangerzone 0.5.0", "published_at": "2023-10-19T21:10:24Z", "html_url": "https://github.com/freedomofpress/dangerzone/releases/tag/v0.5.0", "assets": [{"name": "container.tar.gz", "download_count": 15, "download_url": "https://github.com/freedomofpress/dangerzone/releases/download/v0.5.0/container.tar.gz"}, {"name": "Dangerzone-0.5.0-arm64.dmg", "download_count": 735, "download_url": "https://github.com/freedomofpress/dangerzone/releases/download/v0.5.0/Dangerzone-0.5.0-arm64.dmg"}, {"name": "Dangerzone-0.5.0-i686.dmg", "download_count": 558, "download_url": "https://github.com/freedomofpress/dangerzone/releases/download/v0.5.0/Dangerzone-0.5.0-i686.dmg"}, {"name": "Dangerzone-0.5.0.msi", "download_count": 1925, "download_url": "https://github.com/freedomofpress/dangerzone/releases/download/v0.5.0/Dangerzone-0.5.0.msi"}]}, {"name": "Dangerzone 0.4.2", "published_at": "2023-08-08T15:34:01Z", "html_url": "https://github.com/freedomofpress/dangerzone/releases/tag/v0.4.2", "assets": [{"name": "container.tar.gz", "download_count": 287, "download_url": "https://github.com/freedomofpress/dangerzone/releases/download/v0.4.2/container.tar.gz"}, {"name": "Dangerzone-0.4.2-arm64.dmg", "download_count": 826, "download_url": "https://github.com/freedomofpress/dangerzone/releases/download/v0.4.2/Dangerzone-0.4.2-arm64.dmg"}, {"name": "Dangerzone-0.4.2-i686.dmg", "download_count": 540, "download_url": "https://github.com/freedomofpress/dangerzone/releases/download/v0.4.2/Dangerzone-0.4.2-i686.dmg"}, {"name": "Dangerzone-0.4.2.msi", "download_count": 1620, "download_url": "https://github.com/freedomofpress/dangerzone/releases/download/v0.4.2/Dangerzone-0.4.2.msi"}]}, {"name": "Dangerzone 0.4.1", "published_at": "2023-04-24T16:41:14Z", "html_url": "https://github.com/freedomofpress/dangerzone/releases/tag/v0.4.1", "assets": [{"name": "container.tar.gz", "download_count": 285, "download_url": "https://github.com/freedomofpress/dangerzone/releases/download/v0.4.1/container.tar.gz"}, {"name": "Dangerzone-0.4.1-arm64.dmg", "download_count": 1104, "download_url": "https://github.com/freedomofpress/dangerzone/releases/download/v0.4.1/Dangerzone-0.4.1-arm64.dmg"}, {"name": "Dangerzone-0.4.1-i686.dmg", "download_count": 735, "download_url": "https://github.com/freedomofpress/dangerzone/releases/download/v0.4.1/Dangerzone-0.4.1-i686.dmg"}, {"name": "Dangerzone-0.4.1.msi", "download_count": 3134, "download_url": "https://github.com/freedomofpress/dangerzone/releases/download/v0.4.1/Dangerzone-0.4.1.msi"}]}, {"name": "Dangerzone 0.4.0", "published_at": "2022-12-01T22:51:28Z", "html_url": "https://github.com/freedomofpress/dangerzone/releases/tag/v0.4.0", "assets": [{"name": "Dangerzone-0.4.0.dmg", "download_count": 1853, "download_url": "https://github.com/freedomofpress/dangerzone/releases/download/v0.4.0/Dangerzone-0.4.0.dmg"}, {"name": "Dangerzone-0.4.0.msi", "download_count": 5898, "download_url": "https://github.com/freedomofpress/dangerzone/releases/download/v0.4.0/Dangerzone-0.4.0.msi"}]}, {"name": "Dangerzone 0.3.2", "published_at": "2022-09-06T20:51:12Z", "html_url": "https://github.com/freedomofpress/dangerzone/releases/tag/v0.3.2", "assets": [{"name": "Dangerzone-0.3.2.dmg", "download_count": 1028, "download_url": "https://github.com/freedomofpress/dangerzone/releases/download/v0.3.2/Dangerzone-0.3.2.dmg"}, {"name": "Dangerzone-0.3.2.msi", "download_count": 2000, "download_url": "https://github.com/freedomofpress/dangerzone/releases/download/v0.3.2/Dangerzone-0.3.2.msi"}]}, {"name": "Dangerzone 0.3.1", "published_at": "2021-12-14T21:59:13Z", "html_url": "https://github.com/freedomofpress/dangerzone/releases/tag/v0.3.1", "assets": [{"name": "Dangerzone-0.3.1.dmg", "download_count": 3501, "download_url": "https://github.com/freedomofpress/dangerzone/releases/download/v0.3.1/Dangerzone-0.3.1.dmg"}, {"name": "Dangerzone-0.3.1.msi", "download_count": 7650, "download_url": "https://github.com/freedomofpress/dangerzone/releases/download/v0.3.1/Dangerzone-0.3.1.msi"}]}, {"name": "Dangerzone 0.3", "published_at": "2021-12-01T19:26:00Z", "html_url": "https://github.com/freedomofpress/dangerzone/releases/tag/v0.3", "assets": [{"name": "Dangerzone-0.3.dmg", "download_count": 291, "download_url": "https://github.com/freedomofpress/dangerzone/releases/download/v0.3/Dangerzone-0.3.dmg"}, {"name": "Dangerzone-0.3.msi", "download_count": 168, "download_url": "https://github.com/freedomofpress/dangerzone/releases/download/v0.3/Dangerzone-0.3.msi"}]}, {"name": "Dangerzone 0.2.1", "published_at": "2021-06-22T21:22:57Z", "html_url": "https://github.com/freedomofpress/dangerzone/releases/tag/v0.2.1", "assets": [{"name": "Dangerzone-0.2.1.dmg", "download_count": 769, "download_url": "https://github.com/freedomofpress/dangerzone/releases/download/v0.2.1/Dangerzone-0.2.1.dmg"}, {"name": "Dangerzone-0.2.1.msi", "download_count": 1213, "download_url": "https://github.com/freedomofpress/dangerzone/releases/download/v0.2.1/Dangerzone-0.2.1.msi"}]}, {"name": "Dangerzone 0.2", "published_at": "2021-06-22T20:35:37Z", "html_url": "https://github.com/freedomofpress/dangerzone/releases/tag/v0.2", "assets": [{"name": "Dangerzone-0.2.dmg", "download_count": 230, "download_url": "https://github.com/freedomofpress/dangerzone/releases/download/v0.2/Dangerzone-0.2.dmg"}, {"name": "Dangerzone-0.2.msi", "download_count": 592, "download_url": "https://github.com/freedomofpress/dangerzone/releases/download/v0.2/Dangerzone-0.2.msi"}]}, {"name": "Dangerzone 0.1.5", "published_at": "2021-02-11T01:07:13Z", "html_url": "https://github.com/freedomofpress/dangerzone/releases/tag/v0.1.5", "assets": [{"name": "Dangerzone.0.1.5.dmg", "download_count": 806, "download_url": "https://github.com/freedomofpress/dangerzone/releases/download/v0.1.5/Dangerzone.0.1.5.dmg"}, {"name": "Dangerzone.0.1.5.msi", "download_count": 1263, "download_url": "https://github.com/freedomofpress/dangerzone/releases/download/v0.1.5/Dangerzone.0.1.5.msi"}]}, {"name": "dangerzone 0.1.4", "published_at": "2020-10-30T17:18:53Z", "html_url": "https://github.com/freedomofpress/dangerzone/releases/tag/v0.1.4", "assets": [{"name": "Dangerzone.0.1.4.dmg", "download_count": 751, "download_url": "https://github.com/freedomofpress/dangerzone/releases/download/v0.1.4/Dangerzone.0.1.4.dmg"}, {"name": "Dangerzone.0.1.4.msi", "download_count": 1017, "download_url": "https://github.com/freedomofpress/dangerzone/releases/download/v0.1.4/Dangerzone.0.1.4.msi"}]}, {"name": "dangerzone 0.1.3", "published_at": "2020-09-25T22:39:14Z", "html_url": "https://github.com/freedomofpress/dangerzone/releases/tag/v0.1.3", "assets": [{"name": "Dangerzone.0.1.3.dmg", "download_count": 280, "download_url": "https://github.com/freedomofpress/dangerzone/releases/download/v0.1.3/Dangerzone.0.1.3.dmg"}, {"name": "Dangerzone.0.1.3.msi", "download_count": 355, "download_url": "https://github.com/freedomofpress/dangerzone/releases/download/v0.1.3/Dangerzone.0.1.3.msi"}]}, {"name": "dangerzone 0.1.1", "published_at": "2020-03-17T19:06:56Z", "html_url": "https://github.com/freedomofpress/dangerzone/releases/tag/v0.1.1", "assets": [{"name": "Dangerzone.0.1.1.dmg", "download_count": 948, "download_url": "https://github.com/freedomofpress/dangerzone/releases/download/v0.1.1/Dangerzone.0.1.1.dmg"}, {"name": "Dangerzone.0.1.1.msi", "download_count": 1412, "download_url": "https://github.com/freedomofpress/dangerzone/releases/download/v0.1.1/Dangerzone.0.1.1.msi"}]}, {"name": "dangerzone 0.1", "published_at": "2020-02-29T02:01:32Z", "html_url": "https://github.com/freedomofpress/dangerzone/releases/tag/v0.1", "assets": [{"name": "Dangerzone.0.1.dmg", "download_count": 1007, "download_url": "https://github.com/freedomofpress/dangerzone/releases/download/v0.1/Dangerzone.0.1.dmg"}, {"name": "Dangerzone.0.1.msi", "download_count": 2110, "download_url": "https://github.com/freedomofpress/dangerzone/releases/download/v0.1/Dangerzone.0.1.msi"}]}]} \ No newline at end of file diff --git a/output/styles.css b/output/styles.css new file mode 100644 index 0000000..eebb931 --- /dev/null +++ b/output/styles.css @@ -0,0 +1,367 @@ +body { + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif; + margin: 0; + padding: 20px; + background-color: #f5f8fa; +} + +.container { + max-width: 1200px; + margin: 0 auto; +} + +.header { + text-align: center; + margin-bottom: 2rem; +} + +.header h1 { + color: #24292e; + margin-bottom: 0.5rem; +} + +.tabs { + display: flex; + justify-content: center; + margin-bottom: 2rem; +} + +.tabs button { + background: none; + border: none; + padding: 0.5rem 1rem; + margin: 0 0.5rem; + cursor: pointer; + border-bottom: 2px solid transparent; +} + +.tabs button.active { + border-bottom-color: #0366d6; + color: #0366d6; +} + +.stats-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); + gap: 1rem; +} + +.stat-card { + background: white; + border-radius: 8px; + padding: 1.5rem; + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.12); +} + +.stat-card h2 { + margin-top: 0; + color: #24292e; +} + +.stat-card ul { + list-style: none; + padding: 0; +} + +.stat-card li { + margin-bottom: 0.5rem; + display: flex; + justify-content: space-between; + align-items: center; +} + +.stat-card a { + color: #0366d6; + text-decoration: none; +} + +.stat-card a:hover { + text-decoration: underline; +} + +.state { + padding: 2px 6px; + border-radius: 12px; + font-size: 0.8rem; +} + +.state.open { + background: #28a745; + color: white; +} + +.state.closed { + background: #d73a49; + color: white; +} + +.charts { + background: white; + border-radius: 8px; + padding: 1.5rem; + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.12); + overflow: hidden; +} + +.chart { + margin-bottom: 2rem; +} + +.chart h3 { + margin-top: 0; + color: #24292e; +} + +.chart-container { + display: flex; + align-items: flex-end; + height: 250px; + padding: 1rem; + gap: 1rem; + min-width: 100%; + overflow-x: auto; +} + +.chart-container.stacked { + position: relative; + margin-top: 2rem; + gap: 0.5rem; +} + +.chart-legend { + position: sticky; + top: 0; + z-index: 2; + margin: 0; + padding: 1rem; + background: white; + border-radius: 6px; + display: flex; + justify-content: center; + flex-wrap: wrap; + gap: 1rem; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); +} + +.legend-item { + display: flex; + align-items: center; + gap: 0.5rem; + font-size: 0.9rem; +} + +.legend-color { + width: 16px; + height: 16px; + border-radius: 3px; +} + +.bar-group { + display: flex; + flex-direction: column; + align-items: center; + flex: 1; +} + +.bar-group.stacked { + display: flex; + flex-direction: column; + align-items: center; + flex: 1; + min-width: 45px; +} + +.stacked-bars { + display: flex; + flex-direction: column-reverse; + width: 35px; +} + +.bar { + width: 30px; + background: #0366d6; + transition: height 0.3s ease; +} + +.bar.stacked-bar { + width: 100%; + transition: height 0.3s ease; +} + +.bar.stacked-bar:hover { + opacity: 0.8; +} + +.bar-label { + margin-top: 1rem; + font-size: 0.75rem; + text-align: center; + transform: rotate(-45deg); + transform-origin: top center; + white-space: nowrap; + display: flex; + flex-direction: column; + gap: 2px; +} + +.pr-bar { + background: #0366d6; +} + +.issue-bar { + background: #28a745; +} + +.release-date { + color: #666; + font-size: 0.9rem; + margin: 0.5rem 0; +} + +.assets-list { + margin-top: 1rem; +} + +.asset-item { + display: flex; + justify-content: space-between; + align-items: center; + padding: 0.5rem 0; + border-bottom: 1px solid #eee; +} + +.asset-item:last-child { + border-bottom: none; +} + +.asset-name { + color: #0366d6; + text-decoration: none; + font-size: 0.9rem; +} + +.asset-name:hover { + text-decoration: underline; +} + +.download-count { + font-size: 0.8rem; + color: #666; + background: #f1f8ff; + padding: 2px 8px; + border-radius: 12px; +} + +.download-bar { + background: #28a745; +} + +.download-total { + display: block; + font-size: 0.7rem; + color: #666; + margin-top: 2px; +} + +.version-label { + font-weight: 500; +} + +.date-label { + font-size: 0.7rem; + color: #666; +} + +.chart.horizontal { + margin: 2rem 0; + min-height: 500px; +} + +.chart-container.horizontal { + flex-direction: column; + align-items: stretch; + height: auto; + min-height: 400px; + padding: calc(1rem + 70px) 2rem 1rem 12rem; + gap: 1.5rem; + overflow-y: auto; + position: relative; +} + +.bar-group.horizontal { + flex-direction: row; + align-items: center; + width: 100%; + height: 30px; + position: relative; + margin: 0.5rem 0; + display: flex; + border-bottom: 1px solid #eee; +} + +.bar-group.horizontal:last-child { + border-bottom: none; +} + +.bar-label.horizontal { + position: absolute; + left: -11rem; + width: 10rem; + transform: none; + margin: 0; + text-align: right; + padding-right: 1rem; + z-index: 1; +} + +.stacked-bars.horizontal { + display: flex; + flex-direction: row; + width: 400px; + height: 30px; + position: relative; + background: transparent; +} + +.bar.stacked-bar.horizontal { + height: 30px; + display: inline-block; + transition: width 0.3s ease; +} + +.bar.stacked-bar.horizontal+.bar.stacked-bar.horizontal { + margin-left: 0; +} + +.download-total.horizontal { + position: absolute; + left: calc(100% + 10px); + top: 50%; + transform: translateY(-50%); + white-space: nowrap; + font-size: 0.8rem; + color: #666; + padding-left: 1rem; +} + +.version-label { + display: block; + font-weight: 500; + font-size: 0.85rem; + color: #24292e; +} + +.date-label { + display: block; + font-size: 0.75rem; + color: #666; +} + +.total-downloads { + margin-left: 1rem; + font-size: 0.9rem; + color: #94a2b0; + white-space: nowrap; + min-width: 100px; + padding-left: 1rem; +} \ No newline at end of file diff --git a/templates/index.html b/templates/index.html new file mode 100644 index 0000000..789de17 --- /dev/null +++ b/templates/index.html @@ -0,0 +1,20 @@ + + + + + + + Dangerzone Release Stats + + + + + +
+ + + + \ No newline at end of file