Something went wrong. Try again.
The IoT platform Cloudflare didn't build. Telemetry, realtime dashboards, and automations on Workers + D1 + R2 + Durable Objects — in your… nodrix.live
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211import { defineStore } from 'pinia';import { ref } from 'vue';import { api, ApiError } from '../api';import { authClient } from '../lib/auth-client';import { toast } from '../lib/toast';import type { AuditLogEntry, InviteCreated, InstanceUser, Project, User,} from '../types';
export type ActiveSession = { id: string; user_agent: string | null; ip_address: string | null; created_at: number; last_seen_at: number; expires_at: number; current: boolean; // Whose session this is (the owner-only list spans all users). user_id: string; user_email: string; first_name: string | null; last_name: string | null;};
export const useSessionStore = defineStore('session', () => { const user = ref<User | null>(null); const projects = ref<Project[]>([]); const loading = ref(false); const error = ref<{ status: number; reason?: string } | null>(null); const auditLog = ref<AuditLogEntry[]>([]); const auditLogPage = ref(1); const auditLogPageSize = ref(15); const auditLogPageCount = ref(1); const auditLogTotal = ref(0); const activeSessions = ref<ActiveSession[]>([]); const oauthProviders = ref<('google' | 'github')[]>([]); const instanceUsers = ref<InstanceUser[]>([]);
async function load(): Promise<void> { loading.value = true; error.value = null; try { const data = await api.get<{ user: User; projects: Project[] }>('/v1/admin/me'); user.value = data.user; projects.value = data.projects; } catch (e) { if (e instanceof ApiError) { const body = e.body as { reason?: string } | null; error.value = { status: e.status, ...(body?.reason ? { reason: body.reason } : {}) }; } else { error.value = { status: 0 }; } user.value = null; projects.value = []; } finally { loading.value = false; } }
async function createProject(name: string): Promise<Project> { const p = await api.post<Project>('/v1/admin/projects', { name }); projects.value = [...projects.value, p]; toast.success(`Project “${p.name}” created`); return p; }
async function deleteProject(id: string): Promise<void> { const name = projects.value.find((p) => p.id === id)?.name; await api.del<void>(`/v1/admin/projects/${id}`); projects.value = projects.value.filter((p) => p.id !== id); toast.success(name ? `Project “${name}” deleted` : 'Project deleted'); }
async function updateProject( id: string, patch: { name?: string; description?: string | null } ): Promise<void> { const updated = await api.patch<Project>(`/v1/admin/projects/${id}`, patch); projects.value = projects.value.map((p) => (p.id === id ? { ...p, ...updated } : p)); toast.success('Project updated'); }
async function updateMe(patch: { first_name?: string | null; last_name?: string | null; }): Promise<void> { const updated = await api.patch<User>('/v1/admin/me', patch); user.value = updated; toast.success('Profile updated'); }
async function signOut(): Promise<void> { try { await authClient.signOut(); } catch { /* ignore */ } user.value = null; projects.value = []; activeSessions.value = []; }
async function loadSessions(): Promise<void> { const data = await api.get<{ sessions: ActiveSession[] }>('/v1/admin/sessions'); activeSessions.value = data.sessions; }
async function revokeSession(id: string): Promise<void> { await api.del<void>(`/v1/admin/sessions/${id}`); activeSessions.value = activeSessions.value.filter((s) => s.id !== id); toast.success('Device signed out'); }
async function loadProviders(): Promise<void> { const data = await api.get<{ providers: ('google' | 'github')[] }>( '/v1/public/auth-providers' ); oauthProviders.value = data.providers; }
async function loadAuditLog( page = 1, filters: { action?: string; project?: string; user?: string; from?: number; to?: number } = {} ): Promise<void> { const params = new URLSearchParams({ page: String(page), limit: String(auditLogPageSize.value) }); if (filters.action) params.set('action', filters.action); if (filters.project) params.set('project', filters.project); if (filters.user) params.set('user', filters.user); if (filters.from) params.set('from', String(filters.from)); if (filters.to) params.set('to', String(filters.to)); const data = await api.get<{ entries: AuditLogEntry[]; total: number; page: number; page_size: number; page_count: number; }>(`/v1/admin/audit-log?${params.toString()}`); auditLog.value = data.entries; auditLogPage.value = data.page; auditLogPageSize.value = data.page_size; auditLogPageCount.value = data.page_count; auditLogTotal.value = data.total; }
// ─── Instance users (owner/admin) ────────────────────────────────────────────
async function loadUsers(): Promise<void> { const data = await api.get<{ users: InstanceUser[] }>('/v1/admin/users'); instanceUsers.value = data.users; }
async function setUserRole(id: string, role: 'admin' | 'member'): Promise<void> { await api.patch<{ id: string; role: string }>(`/v1/admin/users/${id}`, { role }); // Promoting to admin clears explicit project rows server-side (admins reach // all projects); mirror that locally. instanceUsers.value = instanceUsers.value.map((u) => u.id === id ? { ...u, role, projects: role === 'admin' ? [] : u.projects } : u ); toast.success(`Role changed to ${role}`); }
// Replace a member's project assignments. Returns the stored set. async function setUserProjects(id: string, projectIds: string[]): Promise<void> { const res = await api.put<{ id: string; project_ids: string[] }>( `/v1/admin/users/${id}/projects`, { project_ids: projectIds } ); const stored = new Set(res.project_ids); instanceUsers.value = instanceUsers.value.map((u) => u.id === id ? { ...u, projects: projects.value.filter((p) => stored.has(p.id)) } : u ); toast.success('Project access updated'); }
async function removeUser(id: string): Promise<void> { const email = instanceUsers.value.find((u) => u.id === id)?.email; await api.del<void>(`/v1/admin/users/${id}`); instanceUsers.value = instanceUsers.value.filter((u) => u.id !== id); toast.success(email ? `${email} removed` : 'User removed'); }
async function transferOwnership(id: string): Promise<void> { await api.post<{ id: string; role: string }>(`/v1/admin/users/${id}/transfer-ownership`); await loadUsers(); await load(); toast.success('Ownership transferred'); }
// ─── Invites (owner/admin) ──────────────────────────────────────────────────── // Create-only: the invite returns a one-time link/temp-password shown in a // modal. There's no pending-invites list (invites are throwaway).
async function createInvite(input: { email: string; instance_role?: 'admin' | 'member'; project_ids?: string[]; }): Promise<InviteCreated> { return api.post<InviteCreated>('/v1/admin/invites', input); }
return { user, projects, loading, error, auditLog, auditLogPage, auditLogPageSize, auditLogPageCount, auditLogTotal, activeSessions, oauthProviders, instanceUsers, load, createProject, deleteProject, updateProject, loadAuditLog, updateMe, signOut, loadSessions, revokeSession, loadProviders, loadUsers, setUserRole, setUserProjects, removeUser, transferOwnership, createInvite, };});