diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 4afaca3..ad10e5a 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -7,8 +7,8 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 - - uses: actions/setup-node@v3 + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 with: node-version: 18 @@ -18,7 +18,7 @@ jobs: - name: Build run: npm run build - - uses: actions/upload-artifact@v3 + - uses: actions/upload-artifact@v4 with: name: script path: | @@ -30,7 +30,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Validate composer.json and composer.lock run: composer validate --strict @@ -38,12 +38,12 @@ jobs: - name: Install dependencies run: composer install --prefer-dist --no-progress - - uses: actions/download-artifact@v3 + - uses: actions/download-artifact@v4 with: name: script path: public - - uses: actions/upload-artifact@v3 + - uses: actions/upload-artifact@v4 with: name: build path: . diff --git a/.gitignore b/.gitignore index 786a9b3..af5f607 100644 --- a/.gitignore +++ b/.gitignore @@ -3,5 +3,6 @@ vendor/ *.mmdb node_modules/ public/shika.js +public/shika.js.map public/build .env diff --git a/assets/js/graphs.js b/assets/js/graphs.js index d509bf1..665de3c 100644 --- a/assets/js/graphs.js +++ b/assets/js/graphs.js @@ -1,16 +1,19 @@ -import { Chart, BarController, BarElement, CategoryScale, LinearScale, Colors, Tooltip } from "chart.js" +import { Chart, BarController, PieController, BarElement, ArcElement, CategoryScale, LinearScale, Colors, Tooltip, Legend } from "chart.js" +import { ChoroplethController, ProjectionScale, ColorScale, GeoFeature, topojson } from 'chartjs-chart-geo'; +import countries50m from "world-atlas/countries-50m.json" with { type: "json" }; // Bundle optimization -Chart.register(BarController, BarElement, CategoryScale, LinearScale, Colors, Tooltip) +Chart.register(BarController, PieController, ChoroplethController, BarElement, ArcElement, CategoryScale, LinearScale, ProjectionScale, ColorScale, GeoFeature, Colors, Tooltip, Legend) // Current charts -let pagesChart = null -let referrersChart = null +let charts = [] async function displayData(site, time) { // Destroy the old charts - if (pagesChart !== null) pagesChart.destroy() - if (referrersChart !== null) referrersChart.destroy() + for (let chart of charts) { + chart.destroy() + } + charts = [] // Calculate the from time const from = (Date.now() / 1000) - time @@ -18,17 +21,22 @@ async function displayData(site, time) { // Fetch the numbers const pages = await fetch(`/api/sites/${site}/pages?from=${from}`).then(x => x.json()) const referrers = await fetch(`/api/sites/${site}/referrers?from=${from}`).then(x => x.json()) + const browsers = await fetch(`/api/sites/${site}/browsers?from=${from}`).then(x => x.json()) + const systems = await fetch(`/api/sites/${site}/operating-systems?from=${from}`).then(x => x.json()) + const devices = await fetch(`/api/sites/${site}/device-types?from=${from}`).then(x => x.json()) + const countries = await fetch(`/api/sites/${site}/countries?from=${from}`).then(x => x.json()) // Display the charts - pagesChart = displayLineChart("pages", pages.map(x => [x.path, x.count])) - referrersChart = displayLineChart("referrers", referrers.map(x => [x.referrer, x.count])) + const region = new Intl.DisplayNames(['en'], { type: 'region' }) + charts.push(displayLineChart("pages", pages.map(x => [x.path, x.count]))) + charts.push(displayLineChart("referrers", referrers.map(x => [x.referrer, x.count]))) + charts.push(displayPieChart("browsers", browsers.map(x => [x.browser, x.count]))) + charts.push(displayPieChart("systems", systems.map(x => [x.operating_system, x.count]))) + charts.push(displayPieChart("devices", devices.map(x => [x.device_type, x.count]))) + charts.push(displayCountryChart("countries", Object.fromEntries(countries.map(x => [ region.of(x.country), x.count ])))); } -function displayLineChart(id, values) { - const options = { - indexAxis: "y" - } - +function displayChartInternal(type, options, id, values) { // Map the data const data = { labels: values.map(x => x[0]), @@ -42,7 +50,7 @@ function displayLineChart(id, values) { // Display the chart const chart = new Chart(document.getElementById(id), { - type: "bar", + type: type, data, options }) @@ -50,6 +58,52 @@ function displayLineChart(id, values) { return chart } +function displayCountryChart(id, values) { + const countries = topojson.feature(countries50m, countries50m.objects.countries).features; + + const chart = new Chart(document.getElementById(id).getContext("2d"), { + type: 'choropleth', + data: { + labels: countries.map((d) => d.properties.name), + datasets: [{ + label: 'Countries', + data: countries.map((d) => { + const country = d.properties.name; + return ({feature: d, value: values[country] ? values[country] : 0}) + }), + }] + }, + options: { + scales: { + projection: { + axis: 'x', + projection: 'equalEarth' + } + } + } + }); + + return chart; +} + +function displayLineChart(id, values) { + const options = { + indexAxis: "y" + } + return displayChartInternal("bar", options, id, values) +} + +function displayPieChart(id, values) { + const options = { + plugins: { + legend: { + position: 'top' + } + } + } + return displayChartInternal("pie", options, id, values) +} + function startDisplayData() { // Get the site and time const site = document.getElementById("site-select").value diff --git a/package-lock.json b/package-lock.json index 47da64e..5562761 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7,7 +7,9 @@ "name": "shika", "dependencies": { "bootstrap": "^5.3.2", - "chart.js": "^4.4.1" + "chart.js": "^4.4.1", + "chartjs-chart-geo": "^4.3.4", + "world-atlas": "^2.0.2" }, "devDependencies": { "esbuild": "^0.19.10", @@ -567,6 +569,46 @@ "win32" ] }, + "node_modules/@types/d3-geo": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@types/d3-geo/-/d3-geo-3.1.0.tgz", + "integrity": "sha512-856sckF0oP/diXtS4jNsiQw/UuK5fQG8l/a9VVLeSouf1/PPbBE1i1W852zVwKwYCBkFJJB7nCFTbk6UMEXBOQ==", + "license": "MIT", + "dependencies": { + "@types/geojson": "*" + } + }, + "node_modules/@types/d3-scale-chromatic": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@types/d3-scale-chromatic/-/d3-scale-chromatic-3.1.0.tgz", + "integrity": "sha512-iWMJgwkK7yTRmWqRB5plb1kadXyQ5Sj8V/zYlFGMUBbIPKQScw+Dku9cAAMgJG+z5GYDoMjWGLVOvjghDEFnKQ==", + "license": "MIT" + }, + "node_modules/@types/geojson": { + "version": "7946.0.16", + "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.16.tgz", + "integrity": "sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg==", + "license": "MIT" + }, + "node_modules/@types/topojson-client": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/@types/topojson-client/-/topojson-client-3.1.5.tgz", + "integrity": "sha512-C79rySTyPxnQNNguTZNI1Ct4D7IXgvyAs3p9HPecnl6mNrJ5+UhvGNYcZfpROYV2lMHI48kJPxwR+F9C6c7nmw==", + "license": "MIT", + "dependencies": { + "@types/geojson": "*", + "@types/topojson-specification": "*" + } + }, + "node_modules/@types/topojson-specification": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@types/topojson-specification/-/topojson-specification-1.0.5.tgz", + "integrity": "sha512-C7KvcQh+C2nr6Y2Ub4YfgvWvWCgP2nOQMtfhlnwsRL4pYmmwzBS7HclGiS87eQfDOU/DLQpX6GEscviaz4yLIQ==", + "license": "MIT", + "dependencies": { + "@types/geojson": "*" + } + }, "node_modules/anymatch": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", @@ -630,6 +672,26 @@ "pnpm": ">=7" } }, + "node_modules/chartjs-chart-geo": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/chartjs-chart-geo/-/chartjs-chart-geo-4.3.4.tgz", + "integrity": "sha512-55aU289XAbbJZXaiOv4bJpSdqP8f/wISpL1bKb0chKEzD9/+Z+dhafHm3Js5/yafVI8GYNP4Vw4keIwPfEf10A==", + "license": "MIT", + "dependencies": { + "@types/d3-geo": "^3.1.0", + "@types/d3-scale-chromatic": "^3.0.3", + "@types/topojson-client": "^3.1.5", + "d3-array": "^3.2.4", + "d3-color": "^3.1.0", + "d3-geo": "^3.1.1", + "d3-interpolate": "^3.0.1", + "d3-scale-chromatic": "^3.1.0", + "topojson-client": "^3.1.0" + }, + "peerDependencies": { + "chart.js": "^4.1.0" + } + }, "node_modules/chokidar": { "version": "3.5.3", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", @@ -657,6 +719,70 @@ "fsevents": "~2.3.2" } }, + "node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "license": "MIT" + }, + "node_modules/d3-array": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz", + "integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==", + "license": "ISC", + "dependencies": { + "internmap": "1 - 2" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-color": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz", + "integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-geo": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/d3-geo/-/d3-geo-3.1.1.tgz", + "integrity": "sha512-637ln3gXKXOwhalDzinUgY83KzNWZRKbYubaG+fGVuc/dxO64RRljtCTnf5ecMyE1RIdtqpkVcq0IbtU2S8j2Q==", + "license": "ISC", + "dependencies": { + "d3-array": "2.5.0 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-interpolate": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz", + "integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==", + "license": "ISC", + "dependencies": { + "d3-color": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-scale-chromatic": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-scale-chromatic/-/d3-scale-chromatic-3.1.0.tgz", + "integrity": "sha512-A3s5PWiZ9YCXFye1o246KoscMWqf8BsD9eRiJ3He7C9OBaxKhAd5TFCdEx/7VbKtxxTsu//1mMJFrEt572cEyQ==", + "license": "ISC", + "dependencies": { + "d3-color": "1 - 3", + "d3-interpolate": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/esbuild": { "version": "0.19.10", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.19.10.tgz", @@ -739,6 +865,15 @@ "integrity": "sha512-fsXeu4J4i6WNWSikpI88v/PcVflZz+6kMhUfIwc5SY+poQRPnaf5V7qds6SUyUN3cVxEzuCab7QIoLOQ+DQ1wA==", "dev": true }, + "node_modules/internmap": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz", + "integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, "node_modules/is-binary-path": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", @@ -933,6 +1068,20 @@ "node": ">=8.0" } }, + "node_modules/topojson-client": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/topojson-client/-/topojson-client-3.1.0.tgz", + "integrity": "sha512-605uxS6bcYxGXw9qi62XyrV6Q3xwbndjachmNxu8HWTtVPxZfEJN9fd/SZS1Q54Sn2y0TMyMxFj/cJINqGHrKw==", + "license": "ISC", + "dependencies": { + "commander": "2" + }, + "bin": { + "topo2geo": "bin/topo2geo", + "topomerge": "bin/topomerge", + "topoquantize": "bin/topoquantize" + } + }, "node_modules/vite": { "version": "5.0.10", "resolved": "https://registry.npmjs.org/vite/-/vite-5.0.10.tgz", @@ -987,6 +1136,12 @@ "optional": true } } + }, + "node_modules/world-atlas": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/world-atlas/-/world-atlas-2.0.2.tgz", + "integrity": "sha512-IXfV0qwlKXpckz1FhwXVwKRjiIhOnWttOskm5CtxMsjgE/MXAYRHWJqgXOpM8IkcPBoXnyTU5lFHcYa5ChG0LQ==", + "license": "ISC" } } } diff --git a/package.json b/package.json index b7b30dd..7286c15 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,9 @@ }, "dependencies": { "bootstrap": "^5.3.2", - "chart.js": "^4.4.1" + "chart.js": "^4.4.1", + "chartjs-chart-geo": "^4.3.4", + "world-atlas": "^2.0.2" }, "devDependencies": { "esbuild": "^0.19.10", diff --git a/views/dashboard/index.html.twig b/views/dashboard/index.html.twig index c4ab635..9eea0d5 100644 --- a/views/dashboard/index.html.twig +++ b/views/dashboard/index.html.twig @@ -45,6 +45,50 @@ + +