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,
+ },
+ },
+ },
+ });
+ });
});