Skip to content
Merged
Binary file modified bun.lockb
Binary file not shown.
2 changes: 2 additions & 0 deletions lefthook.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
},
Expand Down
Binary file modified view/bun.lockb
Binary file not shown.
2 changes: 0 additions & 2 deletions view/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -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"
}
}
7 changes: 5 additions & 2 deletions view/src/components/TotalVisits.svelte
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
<script lang="ts">
const { total, perDay }: { total: number; perDay: number } = $props();
const { total, duration }: { total: number; duration: number } = $props();
import { HOUR, DAY } from '$lib/consts';
const perHour = $derived(total / (duration / HOUR));
const perDay = $derived(total / (duration / DAY));
</script>

<div class="card card-compact inline-block w-96 bg-base-100 align-top shadow-xl">
Expand All @@ -8,7 +11,7 @@
<span class="text-xl">{total}</span>
<hr />
<span class="text-xl">
{perDay.toFixed(1)} / day
{perHour.toFixed(1)} / Hour, {perDay.toFixed(1)} / Day
</span>
</div>
</div>
13 changes: 9 additions & 4 deletions view/src/components/charts/Line.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -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: {
Expand All @@ -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);
Expand Down
2 changes: 2 additions & 0 deletions view/src/lib/consts.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export const DAY = 24 * 60 * 60 * 1000;
export const HOUR = 60 * 60 * 1000;
47 changes: 47 additions & 0 deletions view/src/lib/utils.test.ts
Original file line number Diff line number Diff line change
@@ -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]);
});
38 changes: 15 additions & 23 deletions view/src/lib/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,13 @@ export function unwrap<T>(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<T, U>(list: T[], groupFn: (t: T) => U): { key: U; val: T[] }[] {
const map = new Map<U, T[]>();
Expand All @@ -25,34 +32,19 @@ export function groupBy<T, U>(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)
Expand Down
23 changes: 16 additions & 7 deletions view/src/pages/Dashboard.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -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([
Expand All @@ -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];
Expand Down Expand Up @@ -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
)
}))
);
</script>

<main class="mt-4">
<TotalVisits total={data.length} perDay={data.length / duration} />
<TotalVisits total={data.length} {duration} />
<PieChart dataset={piedata} />

<Line dataset={linedata} {titles} />
Expand Down
14 changes: 8 additions & 6 deletions view/src/routes/+page.svelte
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
<script lang="ts">
import { HOUR, DAY } from '~/lib/consts';
import * as v from 'valibot';
import Dashboard from '~/pages/Dashboard.svelte';
import { type Kind, visit } from '~/share/schema';
Expand All @@ -8,7 +9,7 @@
let visits = $state(data.visits);

let kind: Kind | 'all' = $state('all');
let duration: number = $state(3);
let duration: number = $state(3 * HOUR);
let lastFetch: Date = $state(new Date());
$effect(() => {
visits = fetch(`/visits?kind=${kind}&duration=${duration}`)
Expand All @@ -30,11 +31,12 @@

<header>
<select name="duration" bind:value={duration} class="select w-full max-w-sm">
<option value={1}>1 days</option>
<option value={3}>3 days</option>
<option value={5}>5 days</option>
<option value={90}>3 Months</option>
<option value={370}>1 Year</option>
<option value={3 * HOUR}>3 hours</option>
<option value={6 * HOUR}>6 hours</option>
<option value={12 * HOUR}>12 hours</option>
<option value={1 * DAY}>1 days</option>
<option value={3 * DAY}>3 days</option>
<option value={6 * DAY}>6 days</option>
</select>
<select name="kind" bind:value={kind} class="select w-full max-w-sm">
<option value="all">All</option>
Expand Down
13 changes: 8 additions & 5 deletions view/src/routes/visits/+server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,14 @@ import { and, eq, gte } from 'drizzle-orm';
import { drizzle } from 'drizzle-orm/d1';
import { visitsTable } from '~/db/schema';

const MS_PER_DAY = 24 * 60 * 60 * 1000;
export const GET: ServerLoad = async ({ params, platform }) => {
const duration = Number.parseInt(params.duration ?? '') || 3;
const threshold = new Date(new Date().getTime() - duration * MS_PER_DAY);
const kind = params.kind ?? 'all';
export const GET: ServerLoad = async ({ url, platform }) => {
const duration = Number.parseInt(url.searchParams.get('duration') ?? '');
if (!duration)
return new Response(
`{"error": "failed to parse ${url.searchParams.get('duration')} to number"}`
);
const threshold = new Date(new Date().getTime() - duration);
const kind = url.searchParams.get('kind') ?? 'all';

if (!platform) return new Response('platform not found');
const db = drizzle(platform.env.DB);
Expand Down
Binary file modified worker/bun.lockb
Binary file not shown.
1 change: 0 additions & 1 deletion worker/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
"module": "fetch.ts",
"type": "module",
"scripts": {
"prepare": "bun generate",
"deploy": "wrangler deploy",
"dev": "wrangler dev",
"start": "wrangler dev",
Expand Down
Loading