web: put the traffic screen in the admin frame
installTracking() beacons one view per page load and one click per control that carries a data-key, so every existing button is already counted. The new STATS pane reads it back: a 7/30/90-day range, the three totals, an SVG timeline and eight proportional bar lists (pages, clicked features, country, region, city, browser, system, device).
This commit is contained in:
@@ -84,6 +84,29 @@ export interface AdminUser {
|
||||
removed: boolean;
|
||||
}
|
||||
|
||||
// One row of a grouped count on the traffic screen. The API sorts them, largest
|
||||
// first, and drops the empty ones.
|
||||
export interface StatBucket {
|
||||
key: string;
|
||||
n: number;
|
||||
}
|
||||
|
||||
// What /api/admin/stats reports: the day series the chart draws, plus one
|
||||
// grouped breakdown per dimension. Admin only.
|
||||
export interface Stats {
|
||||
days: number;
|
||||
totals: { views: number; clicks: number; visitors: number };
|
||||
series: { date: string; views: number; clicks: number }[];
|
||||
pages: StatBucket[];
|
||||
targets: StatBucket[];
|
||||
countries: StatBucket[];
|
||||
regions: StatBucket[];
|
||||
cities: StatBucket[];
|
||||
browsers: StatBucket[];
|
||||
systems: StatBucket[];
|
||||
devices: StatBucket[];
|
||||
}
|
||||
|
||||
async function call<T>(path: string, init?: RequestInit): Promise<T> {
|
||||
const res = await fetch(`/api${path}`, {
|
||||
credentials: 'same-origin',
|
||||
@@ -167,6 +190,8 @@ export const api = {
|
||||
|
||||
// Admin only: every account, with how many photos it owns.
|
||||
adminListUsers: () => call<{ users: AdminUser[] }>('/admin/users'),
|
||||
// The traffic screen. `days` is clamped server-side, never rejected.
|
||||
adminStats: (days: number) => call<Stats>(`/admin/stats?days=${days}`),
|
||||
adminListPhotos: () => call<{ photos: AdminPhoto[] }>('/admin/photos'),
|
||||
adminDeletePhoto: (id: number) => call<void>(`/admin/photos/${id}`, { method: 'DELETE' }),
|
||||
adminClearPhotos: () => call<{ removed: number }>('/admin/photos', { method: 'DELETE' }),
|
||||
|
||||
Reference in New Issue
Block a user