From 1f8259f04d36f86febafb6cf97fb0a65a933c4c9 Mon Sep 17 00:00:00 2001 From: yuuki <> Date: Sat, 22 Jun 2024 19:33:50 +0900 Subject: [PATCH] =?UTF-8?q?1=E6=99=82=E9=96=93=E3=81=94=E3=81=A8=E3=81=AE?= =?UTF-8?q?=E7=B5=B1=E8=A8=88=E3=82=92=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- analytics_hourly.txt | 27 +++++++++ app.py | 32 +++++++++- requirements.txt | 2 +- static/index.html | 5 ++ static/script.js | 140 ++++++++++++++++++++++++++++++++++++++++++- 5 files changed, 203 insertions(+), 3 deletions(-) create mode 100644 analytics_hourly.txt diff --git a/analytics_hourly.txt b/analytics_hourly.txt new file mode 100644 index 0000000..890b039 --- /dev/null +++ b/analytics_hourly.txt @@ -0,0 +1,27 @@ +{ + viewer { + zones(filter: { + zoneTag: $zoneTag + }) { + httpRequests1hGroups( + orderBy: [datetime_ASC], + limit: $limit, + filter: { + datetime_gt: $from, + datetime_leq: $to + } + ) { + dimensions { + datetime + } + sum { + bytes + cachedBytes + } + uniq { + uniques + } + } + } + } +} diff --git a/app.py b/app.py index 517cd20..a578259 100644 --- a/app.py +++ b/app.py @@ -34,7 +34,7 @@ def fastapi_serve(dir: str, ref: str, indexes: List[str] = ["index.html", "index app = FastAPI() -@app.get("/cloudflare") +@app.get("/api/cloudflare") async def cloudflare(zone_id: str, x_token: Union[str, None] = Header()): query = Path("analytics_daily.txt").read_text("UTF-8") @@ -64,6 +64,36 @@ async def cloudflare(zone_id: str, x_token: Union[str, None] = Header()): res.headers["CDN-Cache-Control"] = f"max-age=60" return res +@app.get("/api/cloudflare2") +async def cloudflare2(zone_id: str, x_token: Union[str, None] = Header()): + query = Path("analytics_hourly.txt").read_text("UTF-8") + + now = datetime.now() + before = now - timedelta(**{ "hours": 72 }) + + variables = { + "zoneTag": zone_id, + "from": before.astimezone(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ"), + "to": now.astimezone(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ"), + "limit": 72 + } + + result = post( + url="https://api.cloudflare.com/client/v4/graphql", + headers={ + "Authorization": f"Bearer {x_token}" + }, + data=dumps({ + "query": query, + "variables": variables + }) + ) + + res = JSONResponse(result.json()) + res.headers["Cache-Control"] = f"public, max-age=60, s-maxage=60" + res.headers["CDN-Cache-Control"] = f"max-age=60" + return res + @app.get("/") @app.get("/{ref:path}") async def home(ref: str = None): diff --git a/requirements.txt b/requirements.txt index 1e458d4..7f26a9a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1,2 @@ -requests==2.32.2 +requests==2.32.3 fastapi==0.111.0 diff --git a/static/index.html b/static/index.html index 4fab117..c7dbb5c 100644 --- a/static/index.html +++ b/static/index.html @@ -9,6 +9,11 @@ +
読み込み中…
+ + + +
読み込み中…
diff --git a/static/script.js b/static/script.js index e0fed42..485543c 100644 --- a/static/script.js +++ b/static/script.js @@ -28,7 +28,22 @@ const websites = [ ]; async function worker(item) { - const res = await fetch(`/cloudflare?zone_id=${encodeURIComponent(item.zoneId)}`, { + const res = await fetch(`/api/cloudflare?zone_id=${encodeURIComponent(item.zoneId)}`, { + headers: { + "X-Token": token, + }, + }); + const json = await res.json(); + return { + domain: item.domain, + fg: `rgb(${item.color[0]}, ${item.color[1]}, ${item.color[2]})`, + bg: `rgba(${item.color[0]}, ${item.color[1]}, ${item.color[2]}, 0.2)`, + json, + }; +} + +async function worker2(item) { + const res = await fetch(`/api/cloudflare2?zone_id=${encodeURIComponent(item.zoneId)}`, { headers: { "X-Token": token, }, @@ -165,4 +180,127 @@ addEventListener("load", () => { }, }); }); + + Promise.all(websites.map((i) => worker2(i))) + .then((results) => { + document.querySelector("#loading-2").remove(); + + const scale = Math.max(...results.map((r) => r.json["data"]["viewer"]["zones"][0]["httpRequests1hGroups"].length)); + const endedAt = Math.max(...results.map((r) => r.json["data"]["viewer"]["zones"][0]["httpRequests1hGroups"].map((g) => new Date(g["dimensions"]["datetime"]).getTime())).flat()); + const range = Array.from({ length: scale }, (_, k) => new Date(endedAt - k * 60 * 60 * 1000)); + + const ctxUsers = document.querySelector("#users-2").getContext("2d"); + + new Chart(ctxUsers, { + data: { + labels: range.map((d) => d.toLocaleString()).reverse(), + datasets: results.map((r) => { + return { + type: "line", + label: r.domain, + data: range.map((d) => r.json["data"]["viewer"]["zones"][0]["httpRequests1hGroups"].find((g) => g["dimensions"]["datetime"] === d.toISOString().slice(0, 19) + "Z")?.["uniq"]?.["uniques"] ?? null).reverse(), + borderColor: r.fg, + borderWidth: 1, + fill: "origin", + backgroundColor: r.bg, + pointStyle: "star", + }; + }), + }, + options: { + plugins: { + title: { + display: true, + text: "過去72時間のユーザー数の推移", + }, + legend: { + labels: { + usePointStyle: true, + }, + }, + }, + scales: { + y: { + beginAtZero: true, + }, + }, + }, + }); + + const ctxBytes = document.querySelector("#bytes-2").getContext("2d"); + + new Chart(ctxBytes, { + data: { + labels: range.map((d) => d.toLocaleString()).reverse(), + datasets: results.map((r) => { + return { + type: "line", + label: r.domain, + data: range.map((d) => r.json["data"]["viewer"]["zones"][0]["httpRequests1hGroups"].find((g) => g["dimensions"]["datetime"] === d.toISOString().slice(0, 19) + "Z")?.["sum"]?.["bytes"] / 1000 ** 3 ?? null).reverse(), + borderColor: r.fg, + borderWidth: 1, + fill: "origin", + backgroundColor: r.bg, + pointStyle: "star", + }; + }), + }, + options: { + plugins: { + title: { + display: true, + text: "過去72時間の送受信データ量(GB)の推移", + }, + legend: { + labels: { + usePointStyle: true, + }, + }, + }, + scales: { + y: { + beginAtZero: true, + }, + }, + }, + }); + + const ctxBytes2 = document.querySelector("#bytes2-2").getContext("2d"); + + new Chart(ctxBytes2, { + data: { + labels: range.map((d) => d.toLocaleString()).reverse(), + datasets: results.map((r) => { + return { + type: "line", + label: r.domain, + data: range.map((d) => r.json["data"]["viewer"]["zones"][0]["httpRequests1hGroups"].find((g) => g["dimensions"]["datetime"] === d.toISOString().slice(0, 19) + "Z")?.["sum"]?.["cachedBytes"] / 1000 ** 3 ?? null).reverse(), + borderColor: r.fg, + borderWidth: 1, + fill: "origin", + backgroundColor: r.bg, + pointStyle: "star", + }; + }), + }, + options: { + plugins: { + title: { + display: true, + text: "過去72時間のキャッシュ済み送受信データ量(GB)の推移", + }, + legend: { + labels: { + usePointStyle: true, + }, + }, + }, + scales: { + y: { + beginAtZero: true, + }, + }, + }, + }); + }); });