feat: Updated instance record and license blob box
Server Deploy / deploy (push) Successful in 1m13s
Server Deploy / deploy (push) Successful in 1m13s
This commit is contained in:
@@ -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 →
|
||||
</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>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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">
|
||||
|
||||
Reference in New Issue
Block a user