Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/lib/api/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ export {
publicGetOnlineDevicesStatistics,
publicGetPublicShare,
sessionsDeleteSession,
sessionsGetSelfSession,
sessionsListSessions,
shareLinksAddShocker,
shareLinksCreatePublicShare,
Expand Down
2 changes: 1 addition & 1 deletion src/lib/components/Container.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@

<div
class={cn(
'container mx-auto flex h-full flex-col items-start justify-start gap-4 px-4 pt-4 pb-1 sm:px-12 sm:pt-12 sm:pb-2',
'container mx-auto flex h-full flex-col items-start justify-start gap-4 px-2 pt-4 pb-1 sm:px-12 sm:pt-12 sm:pb-2',
className
)}
>
Expand Down
6 changes: 3 additions & 3 deletions src/lib/components/PageHeader.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,12 @@
</script>

<div class={cn('mb-4 w-full', className)}>
<div class="flex w-full flex-wrap items-center gap-x-2 gap-y-1">
<h1 class="text-3xl font-bold">
<div class="flex w-full flex-col gap-2 sm:flex-row sm:items-center">
<h1 class="hidden text-3xl font-bold sm:block">
{title}
</h1>
{#if children}
<div class="ml-auto flex flex-wrap items-center justify-end gap-1">
<div class="flex flex-wrap items-center gap-1 sm:ml-auto sm:justify-end">
{@render children()}
</div>
{/if}
Expand Down
2 changes: 1 addition & 1 deletion src/lib/components/Table/DataTableTemplate.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@
});
</script>

<div class={cn('overflow-y-auto rounded-md border', className)}>
<div class={cn('overflow-x-auto rounded-md border', className)}>
<Table.Root>
<Table.Header>
{#each table.getHeaderGroups() as headerGroup (headerGroup.id)}
Expand Down
4 changes: 2 additions & 2 deletions src/routes/(app)/home/+page.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
</div>

<!-- Stats cards -->
<div class="grid grid-cols-1 gap-4 sm:grid-cols-3">
<div class="grid grid-cols-2 gap-4 sm:grid-cols-3">
<Card.Root>
<Card.Header class="flex flex-row items-center justify-between pb-2">
<Card.Title class="text-sm font-medium">Shockers</Card.Title>
Expand Down Expand Up @@ -83,7 +83,7 @@
<!-- Quick links -->
<div>
<h2 class="mb-3 text-lg font-semibold">Quick Links</h2>
<div class="grid grid-cols-1 gap-3 sm:grid-cols-2 lg:grid-cols-4">
<div class="grid grid-cols-2 gap-3 lg:grid-cols-4">
<Button
variant="outline"
class="h-auto justify-start gap-3 p-4"
Expand Down
62 changes: 32 additions & 30 deletions src/routes/(app)/hubs/[hubId=guid]/update/+page.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -356,38 +356,40 @@
No update history found for this hub.
</p>
{:else}
<Table.Root class="w-full">
<Table.Header>
<Table.Row>
<Table.Head>ID</Table.Head>
<Table.Head>Started</Table.Head>
<Table.Head>Status</Table.Head>
<Table.Head>Version</Table.Head>
<Table.Head>Message</Table.Head>
</Table.Row>
</Table.Header>
<Table.Body>
{#each otaLogs as otaLog (otaLog.id)}
<div class="overflow-x-auto">
<Table.Root class="w-full">
<Table.Header>
<Table.Row>
<Table.Cell class="text-muted-foreground font-mono text-xs">
{NumberToHexPadded(otaLog.id, 8)}
</Table.Cell>
<Table.Cell class="text-sm" title={otaLog.startedAt.toLocaleString()}>
{formatRelativeTime(otaLog.startedAt)}
</Table.Cell>
<Table.Cell>
<Badge variant={getStatusBadgeVariant(otaLog.status)}>
{otaLog.status}
</Badge>
</Table.Cell>
<Table.Cell class="font-mono text-sm">{otaLog.version}</Table.Cell>
<Table.Cell class="text-muted-foreground max-w-xs truncate text-sm">
{otaLog.message ?? '-'}
</Table.Cell>
<Table.Head>ID</Table.Head>
<Table.Head>Started</Table.Head>
<Table.Head>Status</Table.Head>
<Table.Head>Version</Table.Head>
<Table.Head>Message</Table.Head>
</Table.Row>
{/each}
</Table.Body>
</Table.Root>
</Table.Header>
<Table.Body>
{#each otaLogs as otaLog (otaLog.id)}
<Table.Row>
<Table.Cell class="text-muted-foreground font-mono text-xs">
{NumberToHexPadded(otaLog.id, 8)}
</Table.Cell>
<Table.Cell class="text-sm" title={otaLog.startedAt.toLocaleString()}>
{formatRelativeTime(otaLog.startedAt)}
</Table.Cell>
<Table.Cell>
<Badge variant={getStatusBadgeVariant(otaLog.status)}>
{otaLog.status}
</Badge>
</Table.Cell>
<Table.Cell class="font-mono text-sm">{otaLog.version}</Table.Cell>
<Table.Cell class="text-muted-foreground max-w-xs truncate text-sm">
{otaLog.message ?? '-'}
</Table.Cell>
</Table.Row>
{/each}
</Table.Body>
</Table.Root>
</div>
{/if}
</Card.Content>
</Card.Root>
Expand Down
2 changes: 1 addition & 1 deletion src/routes/(app)/profile/+page.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
</script>

<Container>
<h1 class="text-3xl font-bold">Profile</h1>
<h1 class="hidden text-3xl font-bold sm:block">Profile</h1>
<div
class="border-border bg-card flex w-full flex-col items-center justify-center gap-3 rounded-lg border border-dashed py-20"
>
Expand Down
35 changes: 15 additions & 20 deletions src/routes/(app)/settings/api-tokens/+page.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
} from '$lib/components/Table/ColumnUtils';
import DataTable from '$lib/components/Table/DataTableTemplate.svelte';
import Button from '$lib/components/ui/button/button.svelte';
import * as Card from '$lib/components/ui/card';
import PageHeader from '$lib/components/PageHeader.svelte';
import { handleApiError } from '$lib/errorhandling/apiErrorHandling';
import { onMount } from 'svelte';
import { toast } from 'svelte-sonner';
Expand Down Expand Up @@ -74,23 +74,18 @@
</script>

<Container>
<Card.Header class="w-full">
<Card.Title class="flex items-center justify-between space-x-2 text-3xl">
API Tokens
<div>
<Button href={resolve('/settings/api-tokens/new')}>
<Plus />
Generate Token
</Button>
<Button onclick={refresh}>
<RotateCcw />
Refresh
</Button>
</div>
</Card.Title>
<Card.Description>API Tokens are used to authenticate with the OpenShock API</Card.Description>
</Card.Header>
<Card.Content class="flex w-full flex-col space-y-4">
<DataTable data={tokens} {columns} {sorting} />
</Card.Content>
<PageHeader
title="API Tokens"
subtitle="API Tokens are used to authenticate with the OpenShock API"
>
<Button href={resolve('/settings/api-tokens/new')}>
<Plus />
Generate Token
</Button>
<Button onclick={refresh}>
<RotateCcw />
Refresh
</Button>
</PageHeader>
<DataTable data={tokens} {columns} {sorting} />
</Container>
2 changes: 1 addition & 1 deletion src/routes/(app)/settings/api-tokens/new/+page.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,7 @@
</script>

<Container class="items-center-safe justify-center-safe overflow-y-auto">
<Card.Root class="w-lg max-w-2xl shrink-0">
<Card.Root class="w-full max-w-2xl shrink-0">
{#if parseError}
<Card.Header>
<Card.Title class="text-2xl">API Token Request</Card.Title>
Expand Down
31 changes: 13 additions & 18 deletions src/routes/(app)/settings/connections/+page.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@
import type { OAuthConnectionResponse } from '$lib/api';
import { GetOAuthAuthorizeUrl } from '$lib/api/next/oauth';
import Container from '$lib/components/Container.svelte';
import PageHeader from '$lib/components/PageHeader.svelte';
import { Button } from '$lib/components/ui/button';
import * as Card from '$lib/components/ui/card';
import * as Dropdown from '$lib/components/ui/dropdown-menu';
import * as Separator from '$lib/components/ui/separator';
import { handleApiError } from '$lib/errorhandling/apiErrorHandling';
Expand Down Expand Up @@ -96,22 +96,17 @@
</script>

<Container>
<Card.Header class="w-full">
<Card.Title class="flex items-center justify-between text-3xl">
OAuth Connections
<div class="flex items-center gap-2">
<Button variant="ghost" onclick={refresh} disabled={loading} aria-busy={loading}>
<RotateCcw class="mr-2 size-4" />
{loading ? 'Refreshing…' : 'Refresh'}
</Button>
</div>
</Card.Title>
<Card.Description>
Link or unlink third-party accounts to sign in faster and keep your profile in sync.
</Card.Description>
</Card.Header>

<Card.Content class="w-full space-y-6">
<PageHeader
title="OAuth Connections"
subtitle="Link or unlink third-party accounts to sign in faster and keep your profile in sync."
>
<Button variant="outline" onclick={refresh} disabled={loading} aria-busy={loading}>
<RotateCcw class="size-4" />
{loading ? 'Refreshing…' : 'Refresh'}
</Button>
</PageHeader>

<div class="w-full space-y-6">
<!-- Quick actions -->
<div class="flex flex-wrap gap-2">
<Dropdown.Root>
Expand Down Expand Up @@ -194,7 +189,7 @@
{/each}
</div>
{/if}
</Card.Content>
</div>
</Container>

<!-- Confirm dialog lives once at root -->
Expand Down
87 changes: 30 additions & 57 deletions src/routes/(app)/settings/sessions/+page.svelte
Original file line number Diff line number Diff line change
@@ -1,93 +1,66 @@
<script lang="ts">
import { sessionsListSessions } from '$lib/api';
import { sessionsListSessions, sessionsGetSelfSession } from '$lib/api';
import type { LoginSessionResponse } from '$lib/api';
import RotateCcw from '@lucide/svelte/icons/rotate-ccw';
import type { ColumnDef, SortingState } from '@tanstack/table-core';
import Container from '$lib/components/Container.svelte';
import {
CreateActionsColumnDef,
CreateColumnDef,
CreateSortableColumnDef,
RenderCell,
TimeSinceRelativeOrNeverRenderer,
TimeSinceRelativeRenderer,
UserAgentRenderer,
} from '$lib/components/Table/ColumnUtils';
import DataTable from '$lib/components/Table/DataTableTemplate.svelte';
import Button from '$lib/components/ui/button/button.svelte';
import * as Card from '$lib/components/ui/card';
import PageHeader from '$lib/components/PageHeader.svelte';
import { handleApiError } from '$lib/errorhandling/apiErrorHandling';
import { onMount } from 'svelte';
import { toast } from 'svelte-sonner';
import { registerBreadcrumbs } from '$lib/state/breadcrumbs-state.svelte';
import DataTableActions from './data-table-actions.svelte';
import SessionCard from './session-card.svelte';

registerBreadcrumbs(() => [
{ label: 'Settings', href: '/settings/account' },
{ label: 'Sessions' },
]);

let data = $state<LoginSessionResponse[]>([]);
let sorting = $state<SortingState>([]);
let sessions = $state<LoginSessionResponse[]>([]);
let currentSessionId = $state<string | null>(null);

function onRevoked(sessionId: string) {
const idx = data.findIndex((session) => session.id === sessionId);
if (idx === -1) return;

data.splice(idx, 1);
const idx = sessions.findIndex((s) => s.id === sessionId);
if (idx !== -1) sessions.splice(idx, 1);
}

const columns: ColumnDef<LoginSessionResponse>[] = [
CreateColumnDef('ip', 'Ip', RenderCell),
CreateSortableColumnDef('userAgent', 'User Agent', UserAgentRenderer),
CreateSortableColumnDef('created', 'Created', TimeSinceRelativeRenderer),
CreateSortableColumnDef('expires', 'Expires', TimeSinceRelativeRenderer),
CreateSortableColumnDef('lastUsed', 'Last seen', TimeSinceRelativeOrNeverRenderer),
CreateActionsColumnDef(DataTableActions, (session) => ({ session, onRevoked })),
];
// Sort so current session is always first
const sortedSessions = $derived(
[...sessions].sort((a, b) => {
if (a.id === currentSessionId) return -1;
if (b.id === currentSessionId) return 1;
return 0;
})
);

async function fetchSessions() {
try {
data = await sessionsListSessions();
[sessions, { id: currentSessionId }] = await Promise.all([
sessionsListSessions(),
sessionsGetSelfSession(),
]);
} catch (error) {
await handleApiError(error);
}
}

async function onRefreshClicked() {
await fetchSessions();
toast.success('Sessions refreshed successfully');
}

onMount(() => {
fetchSessions();

// Update timestamps every 5 seconds
const interval = setInterval(() => {
if (data) {
data = Object.assign([], data);
}
if (sessions.length) sessions = [...sessions];
}, 5000);

return () => clearInterval(interval);
});
</script>

<Container>
<Card.Header class="w-full">
<Card.Title class="flex items-center justify-between space-x-2 text-3xl">
Sessions
<Button class="text-xl" onclick={onRefreshClicked}>
<RotateCcw />
<span> Refresh </span>
</Button>
</Card.Title>
<Card.Description>
This is a list of all active sessions of your account. Revoke any sessions you do not
recognize.
</Card.Description>
</Card.Header>
<Card.Content class="w-full">
<DataTable {data} {columns} {sorting} />
</Card.Content>
<PageHeader
title="Sessions"
subtitle="This is a list of all active sessions of your account. Revoke any sessions you do not recognize."
/>

<div class="flex w-full flex-col gap-3">
{#each sortedSessions as session (session.id)}
<SessionCard {session} isCurrent={session.id === currentSessionId} {onRevoked} />
{/each}
</div>
</Container>
Loading
Loading