initial commit
This commit is contained in:
@@ -0,0 +1,124 @@
|
||||
import { redirect } from 'next/navigation';
|
||||
import { createSupabaseServerClient } from '@/lib/supabase/server';
|
||||
import { getSupabaseAdmin } from '@/lib/supabase/admin';
|
||||
import { ClaimForm } from './claim-form';
|
||||
import { TokenReveal } from './token-reveal';
|
||||
|
||||
export const dynamic = 'force-dynamic';
|
||||
|
||||
type Tunnel = {
|
||||
subdomain: string;
|
||||
token: string;
|
||||
is_active: boolean;
|
||||
bytes_used: number;
|
||||
quota_bytes: number;
|
||||
last_seen_at: string | null;
|
||||
created_at: string;
|
||||
};
|
||||
|
||||
function formatBytes(n: number): string {
|
||||
if (n < 1024) return `${n} B`;
|
||||
const units = ['KiB', 'MiB', 'GiB', 'TiB'];
|
||||
let v = n / 1024;
|
||||
let i = 0;
|
||||
while (v >= 1024 && i < units.length - 1) {
|
||||
v /= 1024;
|
||||
i++;
|
||||
}
|
||||
return `${v.toFixed(2)} ${units[i]}`;
|
||||
}
|
||||
|
||||
export default async function DashboardPage() {
|
||||
const supabase = createSupabaseServerClient();
|
||||
const {
|
||||
data: { user },
|
||||
} = await supabase.auth.getUser();
|
||||
if (!user) redirect('/login');
|
||||
|
||||
// Use service role so RLS doesn't surprise us here either.
|
||||
const admin = getSupabaseAdmin();
|
||||
const { data: tunnel } = await admin
|
||||
.from('tunnels')
|
||||
.select(
|
||||
'subdomain, token, is_active, bytes_used, quota_bytes, last_seen_at, created_at',
|
||||
)
|
||||
.eq('user_id', user.id)
|
||||
.maybeSingle<Tunnel>();
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h1>Dashboard</h1>
|
||||
<p className="muted">Signed in as {user.email}</p>
|
||||
|
||||
{tunnel ? (
|
||||
<div className="card">
|
||||
<h2>Your tunnel</h2>
|
||||
<div className="kv">
|
||||
<div className="k">Subdomain</div>
|
||||
<div>
|
||||
<a
|
||||
href={`https://${tunnel.subdomain}.linumiq.net`}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
{tunnel.subdomain}.linumiq.net
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div className="k">Token</div>
|
||||
<TokenReveal token={tunnel.token} />
|
||||
|
||||
<div className="k">Status</div>
|
||||
<div>{tunnel.is_active ? 'Active' : 'Inactive'}</div>
|
||||
|
||||
<div className="k">Usage</div>
|
||||
<div>
|
||||
<div>
|
||||
{formatBytes(tunnel.bytes_used)} /{' '}
|
||||
{formatBytes(tunnel.quota_bytes)}
|
||||
</div>
|
||||
<div className="progress" style={{ marginTop: '0.25rem' }}>
|
||||
<div
|
||||
style={{
|
||||
width: `${Math.min(
|
||||
100,
|
||||
(tunnel.bytes_used / Math.max(1, tunnel.quota_bytes)) *
|
||||
100,
|
||||
).toFixed(2)}%`,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="k">Last seen</div>
|
||||
<div>
|
||||
{tunnel.last_seen_at
|
||||
? new Date(tunnel.last_seen_at).toLocaleString()
|
||||
: 'never'}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p style={{ marginTop: '1.5rem' }}>
|
||||
<a
|
||||
href="https://github.com/linumiq/ha-apps/tree/main/frp-tunnel"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
Setup Home Assistant add-on →
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
) : (
|
||||
<div className="card">
|
||||
<h2>Claim a subdomain</h2>
|
||||
<p className="muted">
|
||||
Pick a subdomain like <code>smith</code> to get{' '}
|
||||
<code>smith.linumiq.net</code>. 3–32 chars, lowercase letters,
|
||||
digits, hyphens. One per account.
|
||||
</p>
|
||||
<ClaimForm />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user