feat: Updated instance record and license blob box
Server Deploy / deploy (push) Successful in 1m13s

This commit is contained in:
2026-07-27 16:48:37 +01:00
parent 8fd5eacec6
commit 792b7eb211
2 changed files with 27 additions and 105 deletions
+26 -104
View File
@@ -32,23 +32,12 @@ const KEY = (id: string) => `vantage-hq-record-open:${id}`;
* because the thing that needs you is the thing that should be open. A manual
* toggle is remembered per instance and beats the default from then on.
*/
export function InstanceRecord({
instance,
license,
reapAfterDays,
defaultOpen = false,
}: {
instance: Instance;
license?: License;
reapAfterDays?: number;
defaultOpen?: boolean;
}) {
export function InstanceRecord({ instance, license, reapAfterDays, defaultOpen = false }: { instance: Instance; license?: License; reapAfterDays?: number; defaultOpen?: boolean }) {
const state = licenceState(license?.expires_at, Boolean(license));
const days = license ? daysRemaining(license.expires_at) : 0;
const cloud = instance.deployment === "cloud";
const termDays = instance.tier === "free" ? 30 : 365;
const deleteInDays =
license && reapAfterDays ? daysRemaining(license.expires_at) + reapAfterDays : null;
const deleteInDays = license && reapAfterDays ? daysRemaining(license.expires_at) + reapAfterDays : null;
const [open, setOpen] = useState(defaultOpen);
useEffect(() => {
@@ -81,13 +70,7 @@ export function InstanceRecord({
const panelId = `record-${instance.instance_id}`;
return (
<article
className={clsx(
"relative grid gap-3.5 rounded border border-rule bg-panel p-4 pl-5",
"before:absolute before:inset-y-0 before:left-0 before:w-1 before:content-['']",
STRIPE[state],
)}
>
<article className={clsx("relative grid gap-3.5 rounded border border-rule bg-panel p-4 pl-5", "before:absolute before:inset-y-0 before:left-0 before:w-1 before:content-['']", STRIPE[state])}>
<div className="flex flex-wrap items-start justify-between gap-3">
<div className="min-w-0">
<h2 className="text-[1.22rem]">{instance.name || "Unnamed instance"}</h2>
@@ -97,10 +80,7 @@ export function InstanceRecord({
{` · created ${formatDate(instance.created_at)}`}
</p>
{cloud && instance.slug && (
<a
href={`https://${instance.slug}.vantage.hostxtra.co.uk`}
className="mt-1.5 inline-block font-mono text-[0.78rem] text-accent underline"
>
<a href={`https://${instance.slug}.vantage.hostxtra.co.uk`} className="mt-1.5 inline-block font-mono text-[0.78rem] text-accent underline">
{instance.slug}.vantage.hostxtra.co.uk &rarr;
</a>
)}
@@ -116,10 +96,7 @@ export function InstanceRecord({
aria-label={open ? "Hide details" : "Show details"}
className="grid h-[26px] w-[26px] place-items-center rounded-sm border border-rule bg-panel text-[0.6rem] text-ink-3 hover:border-accent hover:text-accent"
>
<span
aria-hidden
className={clsx("block transition-transform", open && "rotate-180")}
>
<span aria-hidden className={clsx("block transition-transform", open && "rotate-180")}>
</span>
</button>
@@ -145,60 +122,36 @@ export function InstanceRecord({
{state === "expired" && (
<div className="grid gap-1">
<p className="text-[0.82rem] text-ink-2">
Servers and monitors are still running, and your agents keep their keys.
Changes are disabled until you renew.
</p>
<p className="text-[0.82rem] text-ink-2">Servers and monitors are still running, and your agents keep their keys. Changes are disabled until you renew.</p>
{deleteInDays !== null && (
<p className="text-[0.82rem] font-semibold text-expired">
{deleteInDays <= 0
? "Scheduled for deletion."
: `Deleted in ${deleteInDays} ${deleteInDays === 1 ? "day" : "days"} unless renewed.`}
{deleteInDays <= 0 ? "Scheduled for deletion." : `Deleted in ${deleteInDays} ${deleteInDays === 1 ? "day" : "days"} unless renewed.`}
</p>
)}
</div>
)}
{state === "none" && (
<p className="text-[0.82rem] text-ink-2">
You have paid for this but it is not attached to an install yet, so no licence
has been issued. Linking takes a minute.
</p>
)}
{state === "none" && <p className="text-[0.82rem] text-ink-2">You have paid for this but it is not attached to an install yet, so no licence has been issued. Linking takes a minute.</p>}
<div id={panelId} hidden={!open} className="grid gap-3.5">
<div id={panelId} className={clsx("gap-3.5", open ? "grid" : "hidden")}>
{license && (
<div className="grid gap-2 border-t border-rule-soft pt-3">
<p className="font-mono text-[0.68rem] uppercase tracking-[0.12em] text-ink-3">
Included in {instance.tier?.replace("_", " ") ?? "this licence"}
</p>
<p className="font-mono text-[0.68rem] uppercase tracking-[0.12em] text-ink-3">Included in {instance.tier?.replace("_", " ") ?? "this licence"}</p>
<div className="flex flex-wrap gap-x-7 gap-y-2.5">
<Stat n={limitLabel(license.limits.max_servers)} label="Servers" />
<Stat
n={limitLabel(license.limits.max_secret_groups)}
label="Secret groups"
/>
<Stat n={limitLabel(license.limits.max_secret_groups)} label="Secret groups" />
<Stat n={limitLabel(license.limits.max_channels)} label="Channels" />
<Stat
n={license.features.length ? license.features.join(" · ") : "None"}
label="Features"
quiet={license.features.length === 0}
/>
<Stat n={license.features.length ? license.features.join(" · ") : "None"} label="Features" quiet={license.features.length === 0} />
</div>
</div>
)}
{cloud && (
<div className="grid gap-2 border-t border-rule-soft pt-3">
<p className="font-mono text-[0.68rem] uppercase tracking-[0.12em] text-ink-3">
Who can sign in
</p>
<p className="font-mono text-[0.68rem] uppercase tracking-[0.12em] text-ink-3">Who can sign in</p>
<div className="flex flex-wrap items-center gap-2">
{(members.data ?? []).map((m) => (
<span
key={m.member_id}
className="inline-flex items-center gap-1.5 rounded-full border border-rule-soft py-0.5 pl-0.5 pr-2.5 text-[0.78rem] text-ink-2"
>
<span key={m.member_id} className="inline-flex items-center gap-1.5 rounded-full border border-rule-soft py-0.5 pl-0.5 pr-2.5 text-[0.78rem] text-ink-2">
<span className="grid h-[18px] w-[18px] place-items-center rounded-full bg-accent font-mono text-[0.56rem] font-bold text-accent-ink">
{m.email.slice(0, 2).toUpperCase()}
</span>
@@ -206,13 +159,8 @@ export function InstanceRecord({
</span>
))}
{members.isLoading && <span className="text-[0.82rem] text-ink-3">Loading</span>}
{members.data?.length === 0 && (
<span className="text-[0.82rem] text-ink-3">Nobody yet.</span>
)}
<Link
href={`/instances/${instance.instance_id}`}
className="text-[0.82rem] font-semibold text-accent underline"
>
{members.data?.length === 0 && <span className="text-[0.82rem] text-ink-3">Nobody yet.</span>}
<Link href={`/instances/${instance.instance_id}`} className="text-[0.82rem] font-semibold text-accent underline">
Manage access
</Link>
</div>
@@ -225,45 +173,30 @@ export function InstanceRecord({
// UUID in place. Anything else without a licence gets one from
// the purchase page (self-hosted Free is created there).
instance.status === "awaiting_link" ? (
<LinkButton href={`/instances/link?claim=${instance.instance_id}`}>
Link an install
</LinkButton>
<LinkButton href={`/instances/link?claim=${instance.instance_id}`}>Link an install</LinkButton>
) : (
<LinkButton href="/purchase">Get a licence</LinkButton>
)
) : cloud && instance.slug ? (
<>
<LinkButton
external
href={`https://${instance.slug}.vantage.hostxtra.co.uk`}
>
Open instance
<LinkButton external href={`https://${instance.slug}.vantage.hostxtra.co.uk`}>
Open Cloud Instance
</LinkButton>
<LinkButton
variant="line"
href={`/instances/${instance.instance_id}`}
>
Instance settings
<LinkButton variant="line" href={`/instances/${instance.instance_id}`}>
View Instance Settings
</LinkButton>
</>
) : (
<LinkButton href={`/instances/${instance.instance_id}`}>
{state === "expired" ? "Renew and download" : "Licence and download"}
</LinkButton>
<LinkButton href={`/instances/${instance.instance_id}`}>View Instance Settings</LinkButton>
)}
{canRenew && (
<Button
type="button"
variant="line"
onClick={() => renew.mutate()}
disabled={renew.isPending}
>
<Button type="button" variant="line" onClick={() => renew.mutate()} disabled={renew.isPending}>
{renew.isPending ? "Renewing…" : "Renew"}
</Button>
)}
{cloud && <ManageBillingButton />}
{instance.tier && instance.tier !== "free" && <ManageBillingButton />}
</div>
</div>
</article>
@@ -273,19 +206,8 @@ export function InstanceRecord({
function Stat({ n, label, quiet }: { n: string; label: string; quiet?: boolean }) {
return (
<div className="grid gap-px">
<b
className={clsx(
"tabular-nums tracking-[-0.02em]",
quiet
? "text-[0.95rem] font-semibold text-ink-3"
: "text-[1.18rem] font-extrabold",
)}
>
{n}
</b>
<span className="font-mono text-[0.64rem] uppercase tracking-[0.1em] text-ink-3">
{label}
</span>
<b className={clsx("tabular-nums tracking-[-0.02em]", quiet ? "text-[0.95rem] font-semibold text-ink-3" : "text-[1.18rem] font-extrabold")}>{n}</b>
<span className="font-mono text-[0.64rem] uppercase tracking-[0.1em] text-ink-3">{label}</span>
</div>
);
}
+1 -1
View File
@@ -52,7 +52,7 @@ export function LicenceDelivery({
{copied ? "Copied" : "Copy to clipboard"}
</Button>
</div>
<pre className="overflow-x-auto rounded border border-dashed border-rule bg-panel-2 p-3 font-mono text-[0.72rem] text-ink-2">
<pre className="max-h-48 overflow-y-auto whitespace-pre-wrap break-all rounded border border-dashed border-rule bg-panel-2 p-3 font-mono text-[0.72rem] text-ink-2">
{blob}
</pre>
<ol className="grid gap-2">