mirror of
https://github.com/fosrl/pangolin.git
synced 2026-04-01 07:26:38 +00:00
Add basic provisioning room v1 and update keys
This commit is contained in:
@@ -8,6 +8,10 @@ import SiteProvisioningKeysTable, {
|
||||
import { ListSiteProvisioningKeysResponse } from "@server/routers/siteProvisioning/types";
|
||||
import { getTranslations } from "next-intl/server";
|
||||
import { TierFeature, tierMatrix } from "@server/lib/billing/tierMatrix";
|
||||
import DismissableBanner from "@app/components/DismissableBanner";
|
||||
import Link from "next/link";
|
||||
import { Button } from "@app/components/ui/button";
|
||||
import { ArrowRight, Plug } from "lucide-react";
|
||||
|
||||
type ProvisioningKeysPageProps = {
|
||||
params: Promise<{ orgId: string }>;
|
||||
@@ -46,6 +50,29 @@ export default async function ProvisioningKeysPage(
|
||||
|
||||
return (
|
||||
<>
|
||||
<DismissableBanner
|
||||
storageKey="sites-banner-dismissed"
|
||||
version={1}
|
||||
title={t("provisioningKeysBannerTitle")}
|
||||
titleIcon={<Plug className="w-5 h-5 text-primary" />}
|
||||
description={t("provisioningKeysBannerDescription")}
|
||||
>
|
||||
<Link
|
||||
href="https://docs.pangolin.net/manage/sites/install-site"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="gap-2 hover:bg-primary/10 hover:border-primary/50 transition-colors"
|
||||
>
|
||||
{t("provisioningKeysBannerButtonText")}
|
||||
<ArrowRight className="w-4 h-4" />
|
||||
</Button>
|
||||
</Link>
|
||||
</DismissableBanner>
|
||||
|
||||
<PaidFeaturesAlert
|
||||
tiers={tierMatrix[TierFeature.SiteProvisioningKeys]}
|
||||
/>
|
||||
@@ -53,4 +80,4 @@ export default async function ProvisioningKeysPage(
|
||||
<SiteProvisioningKeysTable keys={rows} orgId={params.orgId} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,10 @@ import { AxiosResponse } from "axios";
|
||||
import { SiteRow } from "@app/components/SitesTable";
|
||||
import PendingSitesTable from "@app/components/PendingSitesTable";
|
||||
import { getTranslations } from "next-intl/server";
|
||||
import DismissableBanner from "@app/components/DismissableBanner";
|
||||
import Link from "next/link";
|
||||
import { Button } from "@app/components/ui/button";
|
||||
import { ArrowRight, Plug } from "lucide-react";
|
||||
|
||||
type PendingSitesPageProps = {
|
||||
params: Promise<{ orgId: string }>;
|
||||
@@ -69,14 +73,38 @@ export default async function PendingSitesPage(props: PendingSitesPageProps) {
|
||||
}));
|
||||
|
||||
return (
|
||||
<PendingSitesTable
|
||||
sites={siteRows}
|
||||
orgId={params.orgId}
|
||||
rowCount={pagination.total}
|
||||
pagination={{
|
||||
pageIndex: pagination.page - 1,
|
||||
pageSize: pagination.pageSize
|
||||
}}
|
||||
/>
|
||||
<>
|
||||
<DismissableBanner
|
||||
storageKey="sites-banner-dismissed"
|
||||
version={1}
|
||||
title={t("pendingSitesBannerTitle")}
|
||||
titleIcon={<Plug className="w-5 h-5 text-primary" />}
|
||||
description={t("pendingSitesBannerDescription")}
|
||||
>
|
||||
<Link
|
||||
href="https://docs.pangolin.net/manage/sites/install-site"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="gap-2 hover:bg-primary/10 hover:border-primary/50 transition-colors"
|
||||
>
|
||||
{t("pendingSitesBannerButtonText")}
|
||||
<ArrowRight className="w-4 h-4" />
|
||||
</Button>
|
||||
</Link>
|
||||
</DismissableBanner>
|
||||
<PendingSitesTable
|
||||
sites={siteRows}
|
||||
orgId={params.orgId}
|
||||
rowCount={pagination.total}
|
||||
pagination={{
|
||||
pageIndex: pagination.page - 1,
|
||||
pageSize: pagination.pageSize
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ export default async function SitesPage(props: SitesPageProps) {
|
||||
const params = await props.params;
|
||||
|
||||
const searchParams = new URLSearchParams(await props.searchParams);
|
||||
searchParams.set("status", "accepted");
|
||||
searchParams.set("status", "approved");
|
||||
|
||||
let sites: ListSitesResponse["sites"] = [];
|
||||
let pagination: ListSitesResponse["pagination"] = {
|
||||
|
||||
@@ -79,7 +79,8 @@ export default function CreateSiteProvisioningKeyCredenza({
|
||||
.max(1_000_000, {
|
||||
message: t("provisioningKeysMaxBatchSizeInvalid")
|
||||
}),
|
||||
validUntil: z.string().optional()
|
||||
validUntil: z.string().optional(),
|
||||
approveNewSites: z.boolean()
|
||||
})
|
||||
.superRefine((data, ctx) => {
|
||||
const v = data.validUntil;
|
||||
@@ -103,7 +104,8 @@ export default function CreateSiteProvisioningKeyCredenza({
|
||||
name: "",
|
||||
unlimitedBatchSize: false,
|
||||
maxBatchSize: 100,
|
||||
validUntil: ""
|
||||
validUntil: "",
|
||||
approveNewSites: true
|
||||
}
|
||||
});
|
||||
|
||||
@@ -114,7 +116,8 @@ export default function CreateSiteProvisioningKeyCredenza({
|
||||
name: "",
|
||||
unlimitedBatchSize: false,
|
||||
maxBatchSize: 100,
|
||||
validUntil: ""
|
||||
validUntil: "",
|
||||
approveNewSites: true
|
||||
});
|
||||
}
|
||||
}, [open, form]);
|
||||
@@ -123,18 +126,21 @@ export default function CreateSiteProvisioningKeyCredenza({
|
||||
setLoading(true);
|
||||
try {
|
||||
const res = await api
|
||||
.put<
|
||||
AxiosResponse<CreateSiteProvisioningKeyResponse>
|
||||
>(`/org/${orgId}/site-provisioning-key`, {
|
||||
name: data.name,
|
||||
maxBatchSize: data.unlimitedBatchSize
|
||||
? null
|
||||
: data.maxBatchSize,
|
||||
validUntil:
|
||||
data.validUntil == null || data.validUntil.trim() === ""
|
||||
? undefined
|
||||
: data.validUntil
|
||||
})
|
||||
.put<AxiosResponse<CreateSiteProvisioningKeyResponse>>(
|
||||
`/org/${orgId}/site-provisioning-key`,
|
||||
{
|
||||
name: data.name,
|
||||
maxBatchSize: data.unlimitedBatchSize
|
||||
? null
|
||||
: data.maxBatchSize,
|
||||
validUntil:
|
||||
data.validUntil == null ||
|
||||
data.validUntil.trim() === ""
|
||||
? undefined
|
||||
: data.validUntil,
|
||||
approveNewSites: data.approveNewSites
|
||||
}
|
||||
)
|
||||
.catch((e) => {
|
||||
toast({
|
||||
variant: "destructive",
|
||||
@@ -152,9 +158,7 @@ export default function CreateSiteProvisioningKeyCredenza({
|
||||
}
|
||||
}
|
||||
|
||||
const credential =
|
||||
created &&
|
||||
created.siteProvisioningKey;
|
||||
const credential = created && created.siteProvisioningKey;
|
||||
|
||||
const unlimitedBatchSize = form.watch("unlimitedBatchSize");
|
||||
|
||||
@@ -213,15 +217,12 @@ export default function CreateSiteProvisioningKeyCredenza({
|
||||
min={1}
|
||||
max={1_000_000}
|
||||
autoComplete="off"
|
||||
disabled={
|
||||
unlimitedBatchSize
|
||||
}
|
||||
disabled={unlimitedBatchSize}
|
||||
name={field.name}
|
||||
ref={field.ref}
|
||||
onBlur={field.onBlur}
|
||||
onChange={(e) => {
|
||||
const v =
|
||||
e.target.value;
|
||||
const v = e.target.value;
|
||||
field.onChange(
|
||||
v === ""
|
||||
? 100
|
||||
@@ -269,9 +270,7 @@ export default function CreateSiteProvisioningKeyCredenza({
|
||||
const dateTimeValue: DateTimeValue =
|
||||
(() => {
|
||||
if (!field.value) return {};
|
||||
const d = new Date(
|
||||
field.value
|
||||
);
|
||||
const d = new Date(field.value);
|
||||
if (isNaN(d.getTime()))
|
||||
return {};
|
||||
const hours = d
|
||||
@@ -313,11 +312,7 @@ export default function CreateSiteProvisioningKeyCredenza({
|
||||
value.date
|
||||
);
|
||||
if (value.time) {
|
||||
const [
|
||||
h,
|
||||
m,
|
||||
s
|
||||
] =
|
||||
const [h, m, s] =
|
||||
value.time.split(
|
||||
":"
|
||||
);
|
||||
@@ -352,6 +347,40 @@ export default function CreateSiteProvisioningKeyCredenza({
|
||||
);
|
||||
}}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="approveNewSites"
|
||||
render={({ field }) => (
|
||||
<FormItem className="flex flex-row items-start gap-3 space-y-0">
|
||||
<FormControl>
|
||||
<Checkbox
|
||||
id="provisioning-approve-new-sites"
|
||||
checked={field.value}
|
||||
onCheckedChange={(c) =>
|
||||
field.onChange(
|
||||
c === true
|
||||
)
|
||||
}
|
||||
/>
|
||||
</FormControl>
|
||||
<div className="flex flex-col gap-1">
|
||||
<FormLabel
|
||||
htmlFor="provisioning-approve-new-sites"
|
||||
className="cursor-pointer font-normal !mt-0"
|
||||
>
|
||||
{t(
|
||||
"provisioningKeysApproveNewSites"
|
||||
)}
|
||||
</FormLabel>
|
||||
<FormDescription>
|
||||
{t(
|
||||
"provisioningKeysApproveNewSitesDescription"
|
||||
)}
|
||||
</FormDescription>
|
||||
</div>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
</form>
|
||||
</Form>
|
||||
)}
|
||||
@@ -395,4 +424,4 @@ export default function CreateSiteProvisioningKeyCredenza({
|
||||
</CredenzaContent>
|
||||
</Credenza>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -45,6 +45,7 @@ export type EditableSiteProvisioningKey = {
|
||||
name: string;
|
||||
maxBatchSize: number | null;
|
||||
validUntil: string | null;
|
||||
approveNewSites: boolean;
|
||||
};
|
||||
|
||||
type EditSiteProvisioningKeyCredenzaProps = {
|
||||
@@ -76,7 +77,8 @@ export default function EditSiteProvisioningKeyCredenza({
|
||||
.max(1_000_000, {
|
||||
message: t("provisioningKeysMaxBatchSizeInvalid")
|
||||
}),
|
||||
validUntil: z.string().optional()
|
||||
validUntil: z.string().optional(),
|
||||
approveNewSites: z.boolean()
|
||||
})
|
||||
.superRefine((data, ctx) => {
|
||||
const v = data.validUntil;
|
||||
@@ -100,7 +102,8 @@ export default function EditSiteProvisioningKeyCredenza({
|
||||
name: "",
|
||||
unlimitedBatchSize: false,
|
||||
maxBatchSize: 100,
|
||||
validUntil: ""
|
||||
validUntil: "",
|
||||
approveNewSites: true
|
||||
}
|
||||
});
|
||||
|
||||
@@ -112,7 +115,8 @@ export default function EditSiteProvisioningKeyCredenza({
|
||||
name: provisioningKey.name,
|
||||
unlimitedBatchSize: provisioningKey.maxBatchSize == null,
|
||||
maxBatchSize: provisioningKey.maxBatchSize ?? 100,
|
||||
validUntil: provisioningKey.validUntil ?? ""
|
||||
validUntil: provisioningKey.validUntil ?? "",
|
||||
approveNewSites: provisioningKey.approveNewSites
|
||||
});
|
||||
}, [open, provisioningKey, form]);
|
||||
|
||||
@@ -135,7 +139,8 @@ export default function EditSiteProvisioningKeyCredenza({
|
||||
data.validUntil == null ||
|
||||
data.validUntil.trim() === ""
|
||||
? ""
|
||||
: data.validUntil
|
||||
: data.validUntil,
|
||||
approveNewSites: data.approveNewSites
|
||||
}
|
||||
)
|
||||
.catch((e) => {
|
||||
@@ -255,6 +260,38 @@ export default function EditSiteProvisioningKeyCredenza({
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="approveNewSites"
|
||||
render={({ field }) => (
|
||||
<FormItem className="flex flex-row items-start gap-3 space-y-0">
|
||||
<FormControl>
|
||||
<Checkbox
|
||||
id="provisioning-edit-approve-new-sites"
|
||||
checked={field.value}
|
||||
onCheckedChange={(c) =>
|
||||
field.onChange(c === true)
|
||||
}
|
||||
/>
|
||||
</FormControl>
|
||||
<div className="flex flex-col gap-1">
|
||||
<FormLabel
|
||||
htmlFor="provisioning-edit-approve-new-sites"
|
||||
className="cursor-pointer font-normal !mt-0"
|
||||
>
|
||||
{t(
|
||||
"provisioningKeysApproveNewSites"
|
||||
)}
|
||||
</FormLabel>
|
||||
<FormDescription>
|
||||
{t(
|
||||
"provisioningKeysApproveNewSitesDescription"
|
||||
)}
|
||||
</FormDescription>
|
||||
</div>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="validUntil"
|
||||
|
||||
@@ -93,7 +93,7 @@ export default function PendingSitesTable({
|
||||
async function approveSite(siteId: number) {
|
||||
setApprovingIds((prev) => new Set(prev).add(siteId));
|
||||
try {
|
||||
await api.post(`/site/${siteId}`, { status: "accepted" });
|
||||
await api.post(`/site/${siteId}`, { status: "approved" });
|
||||
toast({
|
||||
title: t("success"),
|
||||
description: t("siteApproveSuccess"),
|
||||
@@ -437,4 +437,4 @@ export default function PendingSitesTable({
|
||||
stickyRightColumn="actions"
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user