clean orgId and fix primary badge

This commit is contained in:
miloschwartz
2026-02-17 20:35:25 -08:00
parent 19fcc1f93b
commit d4bff9d5cb
4 changed files with 48 additions and 10 deletions

View File

@@ -1031,6 +1031,7 @@
"pangolinSetup": "Setup - Pangolin",
"orgNameRequired": "Organization name is required",
"orgIdRequired": "Organization ID is required",
"orgIdMaxLength": "Organization ID must be at most 32 characters",
"orgErrorCreate": "An error occurred while creating org",
"pageNotFound": "Page Not Found",
"pageNotFoundDescription": "Oops! The page you're looking for doesn't exist.",

View File

@@ -31,8 +31,17 @@ import { doCidrsOverlap } from "@server/lib/ip";
import { generateCA } from "@server/private/lib/sshCA";
import { encrypt } from "@server/lib/crypto";
const validOrgIdRegex = /^[a-z0-9_]+(-[a-z0-9_]+)*$/;
const createOrgSchema = z.strictObject({
orgId: z.string(),
orgId: z
.string()
.min(1, "Organization ID is required")
.max(32, "Organization ID must be at most 32 characters")
.refine((val) => validOrgIdRegex.test(val), {
message:
"Organization ID must contain only lowercase letters, numbers, underscores, and single hyphens (no leading, trailing, or consecutive hyphens)"
}),
name: z.string().min(1).max(255),
subnet: z
// .union([z.cidrv4(), z.cidrv6()])

View File

@@ -50,7 +50,10 @@ export default function StepperForm() {
const orgSchema = z.object({
orgName: z.string().min(1, { message: t("orgNameRequired") }),
orgId: z.string().min(1, { message: t("orgIdRequired") }),
orgId: z
.string()
.min(1, { message: t("orgIdRequired") })
.max(32, { message: t("orgIdMaxLength") }),
subnet: z.string().min(1, { message: t("subnetRequired") }),
utilitySubnet: z.string().min(1, { message: t("subnetRequired") })
});
@@ -140,6 +143,16 @@ export default function StepperForm() {
.replace(/^-+|-+$/g, "");
};
const sanitizeOrgId = (value: string) => {
return value
.toLowerCase()
.replace(/\s+/g, "-")
.replace(/[^a-z0-9_-]/g, "")
.replace(/-+/g, "-")
.replace(/^-+|-+$/g, "")
.slice(0, 32);
};
async function orgSubmit(values: z.infer<typeof orgSchema>) {
if (orgIdTaken) {
return;
@@ -303,7 +316,22 @@ export default function StepperForm() {
<FormItem>
<FormLabel>{t("orgId")}</FormLabel>
<FormControl>
<Input type="text" {...field} />
<Input
type="text"
{...field}
onChange={(e) => {
const value = sanitizeOrgId(
e.target.value
);
field.onChange(value);
setOrgIdTaken(false);
if (value) {
debouncedCheckOrgIdAvailability(
value
);
}
}}
/>
</FormControl>
<FormMessage />
<FormDescription>

View File

@@ -154,22 +154,22 @@ export function OrgSelector({
<Users className="h-4 w-4 text-muted-foreground" />
</div>
<div className="flex flex-col flex-1 min-w-0">
<div className="flex items-center gap-2 flex-wrap">
<span className="font-medium truncate">
{org.name}
<span className="font-medium truncate">
{org.name}
</span>
<div className="flex items-center gap-2 min-w-0">
<span className="text-xs text-muted-foreground font-mono truncate">
{org.orgId}
</span>
{org.isPrimaryOrg && (
<Badge
variant="outline"
className="shrink-0 text-[10px] px-1.5 py-0 font-medium"
className="shrink-0 text-[10px] px-1.5 py-0 font-medium ml-auto"
>
{t("primary")}
</Badge>
)}
</div>
<span className="text-xs text-muted-foreground font-mono">
{org.orgId}
</span>
</div>
<Check
className={cn(