diff --git a/bun.lockb b/bun.lockb index 42f632d..67d87b7 100755 Binary files a/bun.lockb and b/bun.lockb differ diff --git a/lefthook.yml b/lefthook.yml index c2616a3..026c11e 100644 --- a/lefthook.yml +++ b/lefthook.yml @@ -13,3 +13,5 @@ pre-commit: glob: "**/wrangler.toml" run: cd view && bun generate && cd ../worker && bun generate stage_fixed: true + check: + run: bun check diff --git a/package.json b/package.json index ae78c92..bb02a0a 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,7 @@ "prettier-plugin-tailwindcss": "^0.6.9" }, "scripts": { - "prepare": "bunx lefthook install && cd worker; bun i --frozen-lockfile && cd ../view && bun i --frozen-lockfile", + "prepare": "bunx lefthook install", "generate": "cd view && bun generate && cd ../worker && bun generate", "check": "cd view && bun check && cd ../worker && bun check" }, diff --git a/view/bun.lockb b/view/bun.lockb index 3b62128..61fbfe3 100755 Binary files a/view/bun.lockb and b/view/bun.lockb differ diff --git a/view/package.json b/view/package.json index b25b553..e00632e 100644 --- a/view/package.json +++ b/view/package.json @@ -6,7 +6,6 @@ "dev": "vite dev", "build": "vite build", "generate": "bun run cf-typegen && svelte-kit sync", - "prepare": "bun generate", "preview": "bun run build && wrangler pages dev", "check": "bun generate && svelte-check --tsconfig ./tsconfig.json", "db:push": "drizzle-kit push", @@ -39,7 +38,6 @@ "apexcharts": "^4.0.0", "daisyui": "^4.12.14", "drizzle-orm": "^0.33.0", - "svelte-apexcharts": "^1.0.2", "valibot": "^1.0.0-beta.7" } } diff --git a/view/src/components/TotalVisits.svelte b/view/src/components/TotalVisits.svelte index 2bffff2..5081d0e 100644 --- a/view/src/components/TotalVisits.svelte +++ b/view/src/components/TotalVisits.svelte @@ -1,5 +1,8 @@
@@ -8,7 +11,7 @@ {total}
- {perDay.toFixed(1)} / day + {perHour.toFixed(1)} / Hour, {perDay.toFixed(1)} / Day
diff --git a/view/src/components/charts/Line.svelte b/view/src/components/charts/Line.svelte index 9c2f60e..0169a87 100644 --- a/view/src/components/charts/Line.svelte +++ b/view/src/components/charts/Line.svelte @@ -3,7 +3,10 @@ type Props = { dataset: { name: string; data: number[] }[]; titles: Date[] }; const { dataset, titles }: Props = $props(); - $inspect(titles); + onMount(() => { + console.log(titles); + console.log(dataset); + }); const options = { series: dataset, chart: { @@ -18,15 +21,17 @@ }, xaxis: { type: 'datetime', - categories: titles.map((title) => title.toLocaleDateString()) + // it's written in UTC time for some reason. converting to JST. + categories: titles.map((title) => + new Date(title.getTime() + 9 * 60 * 60 * 1000).toISOString() + ) }, tooltip: { x: { - format: 'dd/MM/yy HH:mm' + format: 'MM/dd HH:mm' } } }; - const id = Math.random().toFixed(6).toString(); onMount(async () => { const { default: ApexCharts } = await import('apexcharts'); const chart = new ApexCharts(document.querySelector('#chart-line'), options); diff --git a/view/src/lib/consts.ts b/view/src/lib/consts.ts new file mode 100644 index 0000000..306b9f8 --- /dev/null +++ b/view/src/lib/consts.ts @@ -0,0 +1,2 @@ +export const DAY = 24 * 60 * 60 * 1000; +export const HOUR = 60 * 60 * 1000; diff --git a/view/src/lib/utils.test.ts b/view/src/lib/utils.test.ts new file mode 100644 index 0000000..f334910 --- /dev/null +++ b/view/src/lib/utils.test.ts @@ -0,0 +1,47 @@ +import { test, expect } from 'bun:test'; +import { groupByTime } from './utils'; + +const now = new Date(); +const then = new Date(now.getTime() - 3 * 24 * 60 * 60 * 1000); + +test('group by time: 1', () => { + const got = groupByTime(now, then, [new Date(now.getTime() - 1 * 24 * 60 * 60 * 1000 + 50)], 3); + expect(got).toEqual([0, 0, 1]); +}); +test('group by time: 2', () => { + const got = groupByTime(now, then, [new Date(now.getTime() - 1 * 24 * 60 * 60 * 1000 - 50)], 3); + expect(got).toEqual([0, 1, 0]); +}); +test('group by time: 3', () => { + const got = groupByTime(now, then, [new Date(now.getTime() - 3 * 24 * 60 * 60 * 1000 + 50)], 3); + expect(got).toEqual([1, 0, 0]); +}); +test('group by time: 4', () => { + const got = groupByTime( + now, + then, + [ + new Date(now.getTime() - 1 * 24 * 60 * 60 * 1000 + 50), + new Date(now.getTime() - 1 * 24 * 60 * 60 * 1000 + 50), + new Date(now.getTime() - 1 * 24 * 60 * 60 * 1000 + 50), + new Date(now.getTime() - 1 * 24 * 60 * 60 * 1000 + 50), + new Date(now.getTime() - 1 * 24 * 60 * 60 * 1000 - 50) + ], + 3 + ); + expect(got).toEqual([0, 1, 4]); +}); +test('group by time: 5', () => { + const got = groupByTime( + now, + then, + [ + new Date(now.getTime() - 3 * 24 * 60 * 60 * 1000 + 50), + new Date(now.getTime() - 3 * 24 * 60 * 60 * 1000 + 50), + new Date(now.getTime() - 3 * 24 * 60 * 60 * 1000 + 50), + new Date(now.getTime() - 3 * 24 * 60 * 60 * 1000 + 50) + ], + 3 + ); + expect(got).toEqual([4, 0, 0]); +}); diff --git a/view/src/lib/utils.ts b/view/src/lib/utils.ts index 55450b0..e77d2fa 100644 --- a/view/src/lib/utils.ts +++ b/view/src/lib/utils.ts @@ -8,6 +8,13 @@ export function unwrap(val: T | null | undefined): T { export function panic(message: string): never { throw new Error(message); } +export function stairs(len: number, top: number): number[] { + const ret: number[] = []; + for (let i = 0; i < len; i++) { + ret.push((top / len) * i); + } + return ret; +} export function groupBy(list: T[], groupFn: (t: T) => U): { key: U; val: T[] }[] { const map = new Map(); @@ -25,34 +32,19 @@ export function groupBy(list: T[], groupFn: (t: T) => U): { key: U; val: T return ret; } -export function groupSteps( - current: Date, - list: Visit[], - start: Date, - steps: number -): [number, Date][] { +export function groupByTime(current: Date, start: Date, list: Date[], steps: number): number[] { const currentTime = current.getTime(); const totalDuration = currentTime - start.getTime(); const stepWidth = totalDuration / steps; - const values = list.map((i) => currentTime - i.at.getTime()); // all should be pos - const result: [number, Date][] = groupInSteps(values, stepWidth).map( - (count, index) => [count, new Date(currentTime - index * stepWidth)] as const - ); - return result; + const values = list.map((i) => currentTime - i.getTime()); // all should be pos + const result: number[] = groupInSteps(values, totalDuration, steps); + return result.reverse(); } -export function stairs(len: number, top: number): number[] { - const ret: number[] = []; - for (let i = 0; i < len; i++) { - ret.push((top / len) * i); - } - return ret; -} -function groupInSteps(list: number[], stepWidth: number): number[] { - const maxVal = list.reduce((a, b) => Math.max(a, b)); - const steps = Math.ceil(maxVal / stepWidth); - const arr: number[] = new Array(steps + 1).fill(0); - for (const datapoint of list) { +export function groupInSteps(input: number[], maxVal: number, length: number): number[] { + const stepWidth = Math.ceil(maxVal / length); + const arr: number[] = new Array(length).fill(0); + for (const datapoint of input) { const idx = Math.floor(datapoint / stepWidth); const val = arr[idx]; if (val === undefined) diff --git a/view/src/pages/Dashboard.svelte b/view/src/pages/Dashboard.svelte index fef21a8..f005480 100644 --- a/view/src/pages/Dashboard.svelte +++ b/view/src/pages/Dashboard.svelte @@ -3,7 +3,8 @@ import Line from '~/components/charts/Line.svelte'; import PieChart from '~/components/charts/PieChart.svelte'; import type { Visit } from '~/db/schema'; - import { groupBy, groupSteps, stairs } from '~/lib/utils'; + import { HOUR } from '~/lib/consts'; + import { groupBy, groupByTime, stairs } from '~/lib/utils'; import type { Kind } from '~/share/schema'; const URL_LABELS = new Map([ @@ -15,17 +16,19 @@ ['security.utcode.net', 'セキュリティ'] ]); - const SAMPLING_COUNT = 20; - type Props = { data: Visit[]; duration: number; lastFetch: Date; kind: Kind | 'all'; }; - const MILLISECS_PER_DAY = 24 * 60 * 60 * 1000; const { data, duration, lastFetch }: Props = $props(); - const start = $derived(new Date(lastFetch.getTime() - duration * MILLISECS_PER_DAY)); + function clamp(target: number, min: number, max: number) { + return Math.min(Math.max(target, min), max); + } + + const SAMPLING_COUNT = $derived(clamp(duration / HOUR, 6, 20)); // limit sampling count to 20 if it's too big + const start = $derived(new Date(lastFetch.getTime() - duration)); const sanitizedData = $derived( data.map((item) => { const sanitized = item.url.split('://')[1]?.split('/')[0]; @@ -55,16 +58,22 @@ (v) => new Date(v + start.getTime()) ) ); + $inspect(titles); const linedata = $derived( grouped.map((e) => ({ name: URL_LABELS.get(e.key) ?? e.key, - data: groupSteps(lastFetch, e.val, start, SAMPLING_COUNT).map((row) => row[0]) + data: groupByTime( + lastFetch, + start, + e.val.map((i) => i.at), + SAMPLING_COUNT + ) })) );
- + diff --git a/view/src/routes/+page.svelte b/view/src/routes/+page.svelte index 73c64db..d922edc 100644 --- a/view/src/routes/+page.svelte +++ b/view/src/routes/+page.svelte @@ -1,4 +1,5 @@