mirror of
https://github.com/fosrl/pangolin.git
synced 2026-01-28 22:00:51 +00:00
🚧 add device approval in the roles page
This commit is contained in:
@@ -1548,6 +1548,8 @@
|
|||||||
"IntervalSeconds": "Healthy Interval",
|
"IntervalSeconds": "Healthy Interval",
|
||||||
"timeoutSeconds": "Timeout (sec)",
|
"timeoutSeconds": "Timeout (sec)",
|
||||||
"timeIsInSeconds": "Time is in seconds",
|
"timeIsInSeconds": "Time is in seconds",
|
||||||
|
"requireDeviceApproval": "Require Device Approvals",
|
||||||
|
"requireDeviceApprovalDescription": "Users with this role need their devices approved by an admin before they can access resources",
|
||||||
"retryAttempts": "Retry Attempts",
|
"retryAttempts": "Retry Attempts",
|
||||||
"expectedResponseCodes": "Expected Response Codes",
|
"expectedResponseCodes": "Expected Response Codes",
|
||||||
"expectedResponseCodesDescription": "HTTP status code that indicates healthy status. If left blank, 200-300 is considered healthy.",
|
"expectedResponseCodesDescription": "HTTP status code that indicates healthy status. If left blank, 200-300 is considered healthy.",
|
||||||
|
|||||||
@@ -255,7 +255,9 @@ export const siteResources = sqliteTable("siteResources", {
|
|||||||
aliasAddress: text("aliasAddress"),
|
aliasAddress: text("aliasAddress"),
|
||||||
tcpPortRangeString: text("tcpPortRangeString").notNull().default("*"),
|
tcpPortRangeString: text("tcpPortRangeString").notNull().default("*"),
|
||||||
udpPortRangeString: text("udpPortRangeString").notNull().default("*"),
|
udpPortRangeString: text("udpPortRangeString").notNull().default("*"),
|
||||||
disableIcmp: integer("disableIcmp", { mode: "boolean" }).notNull().default(false)
|
disableIcmp: integer("disableIcmp", { mode: "boolean" })
|
||||||
|
.notNull()
|
||||||
|
.default(false)
|
||||||
});
|
});
|
||||||
|
|
||||||
export const clientSiteResources = sqliteTable("clientSiteResources", {
|
export const clientSiteResources = sqliteTable("clientSiteResources", {
|
||||||
@@ -796,7 +798,10 @@ export const idpOidcConfig = sqliteTable("idpOidcConfig", {
|
|||||||
identifierPath: text("identifierPath").notNull(),
|
identifierPath: text("identifierPath").notNull(),
|
||||||
emailPath: text("emailPath"),
|
emailPath: text("emailPath"),
|
||||||
namePath: text("namePath"),
|
namePath: text("namePath"),
|
||||||
scopes: text("scopes").notNull()
|
scopes: text("scopes").notNull(),
|
||||||
|
approvalState: text("approvalState")
|
||||||
|
.$type<"pending" | "approved" | "denied">()
|
||||||
|
.default("approved")
|
||||||
});
|
});
|
||||||
|
|
||||||
export const licenseKey = sqliteTable("licenseKey", {
|
export const licenseKey = sqliteTable("licenseKey", {
|
||||||
|
|||||||
@@ -36,7 +36,8 @@ async function queryRoles(orgId: string, limit: number, offset: number) {
|
|||||||
isAdmin: roles.isAdmin,
|
isAdmin: roles.isAdmin,
|
||||||
name: roles.name,
|
name: roles.name,
|
||||||
description: roles.description,
|
description: roles.description,
|
||||||
orgName: orgs.name
|
orgName: orgs.name,
|
||||||
|
requireDeviceApproval: roles.requireDeviceApproval
|
||||||
})
|
})
|
||||||
.from(roles)
|
.from(roles)
|
||||||
.leftJoin(orgs, eq(roles.orgId, orgs.orgId))
|
.leftJoin(orgs, eq(roles.orgId, orgs.orgId))
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { Button } from "@app/components/ui/button";
|
|||||||
import {
|
import {
|
||||||
Form,
|
Form,
|
||||||
FormControl,
|
FormControl,
|
||||||
|
FormDescription,
|
||||||
FormField,
|
FormField,
|
||||||
FormItem,
|
FormItem,
|
||||||
FormLabel,
|
FormLabel,
|
||||||
@@ -27,11 +28,13 @@ import {
|
|||||||
CredenzaTitle
|
CredenzaTitle
|
||||||
} from "@app/components/Credenza";
|
} from "@app/components/Credenza";
|
||||||
import { useOrgContext } from "@app/hooks/useOrgContext";
|
import { useOrgContext } from "@app/hooks/useOrgContext";
|
||||||
import { CreateRoleBody, CreateRoleResponse } from "@server/routers/role";
|
import type { CreateRoleBody, CreateRoleResponse } from "@server/routers/role";
|
||||||
import { formatAxiosError } from "@app/lib/api";
|
import { formatAxiosError } from "@app/lib/api";
|
||||||
import { createApiClient } from "@app/lib/api";
|
import { createApiClient } from "@app/lib/api";
|
||||||
import { useEnvContext } from "@app/hooks/useEnvContext";
|
import { useEnvContext } from "@app/hooks/useEnvContext";
|
||||||
import { useTranslations } from "next-intl";
|
import { useTranslations } from "next-intl";
|
||||||
|
import { CheckboxWithLabel } from "./ui/checkbox";
|
||||||
|
import { usePaidStatus } from "@app/hooks/usePaidStatus";
|
||||||
|
|
||||||
type CreateRoleFormProps = {
|
type CreateRoleFormProps = {
|
||||||
open: boolean;
|
open: boolean;
|
||||||
@@ -46,10 +49,12 @@ export default function CreateRoleForm({
|
|||||||
}: CreateRoleFormProps) {
|
}: CreateRoleFormProps) {
|
||||||
const { org } = useOrgContext();
|
const { org } = useOrgContext();
|
||||||
const t = useTranslations();
|
const t = useTranslations();
|
||||||
|
const { isPaidUser } = usePaidStatus();
|
||||||
|
|
||||||
const formSchema = z.object({
|
const formSchema = z.object({
|
||||||
name: z.string({ message: t("nameRequired") }).max(32),
|
name: z.string({ message: t("nameRequired") }).max(32),
|
||||||
description: z.string().max(255).optional()
|
description: z.string().max(255).optional(),
|
||||||
|
requireDeviceApproval: z.boolean().optional()
|
||||||
});
|
});
|
||||||
|
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
@@ -60,7 +65,8 @@ export default function CreateRoleForm({
|
|||||||
resolver: zodResolver(formSchema),
|
resolver: zodResolver(formSchema),
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
name: "",
|
name: "",
|
||||||
description: ""
|
description: "",
|
||||||
|
requireDeviceApproval: false
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -159,6 +165,36 @@ export default function CreateRoleForm({
|
|||||||
</FormItem>
|
</FormItem>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
{isPaidUser && (
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="requireDeviceApproval"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem className="pt-3">
|
||||||
|
<FormControl>
|
||||||
|
<CheckboxWithLabel
|
||||||
|
{...field}
|
||||||
|
value={undefined}
|
||||||
|
defaultChecked={
|
||||||
|
field.value
|
||||||
|
}
|
||||||
|
label={t(
|
||||||
|
"requireDeviceApproval"
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
|
||||||
|
<FormDescription>
|
||||||
|
{t(
|
||||||
|
"requireDeviceApprovalDescription"
|
||||||
|
)}
|
||||||
|
</FormDescription>
|
||||||
|
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</form>
|
</form>
|
||||||
</Form>
|
</Form>
|
||||||
</CredenzaBody>
|
</CredenzaBody>
|
||||||
|
|||||||
@@ -1,27 +1,21 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { ColumnDef } from "@tanstack/react-table";
|
|
||||||
import { ExtendedColumnDef } from "@app/components/ui/data-table";
|
|
||||||
import {
|
|
||||||
DropdownMenu,
|
|
||||||
DropdownMenuContent,
|
|
||||||
DropdownMenuItem,
|
|
||||||
DropdownMenuTrigger
|
|
||||||
} from "@app/components/ui/dropdown-menu";
|
|
||||||
import { Button } from "@app/components/ui/button";
|
|
||||||
import { ArrowUpDown, Crown, MoreHorizontal } from "lucide-react";
|
|
||||||
import { useState } from "react";
|
|
||||||
import ConfirmDeleteDialog from "@app/components/ConfirmDeleteDialog";
|
|
||||||
import { useOrgContext } from "@app/hooks/useOrgContext";
|
|
||||||
import { toast } from "@app/hooks/useToast";
|
|
||||||
import { RolesDataTable } from "@app/components/RolesDataTable";
|
|
||||||
import { Role } from "@server/db";
|
|
||||||
import CreateRoleForm from "@app/components/CreateRoleForm";
|
import CreateRoleForm from "@app/components/CreateRoleForm";
|
||||||
import DeleteRoleForm from "@app/components/DeleteRoleForm";
|
import DeleteRoleForm from "@app/components/DeleteRoleForm";
|
||||||
import { createApiClient } from "@app/lib/api";
|
import { RolesDataTable } from "@app/components/RolesDataTable";
|
||||||
|
import { Button } from "@app/components/ui/button";
|
||||||
|
import { ExtendedColumnDef } from "@app/components/ui/data-table";
|
||||||
import { useEnvContext } from "@app/hooks/useEnvContext";
|
import { useEnvContext } from "@app/hooks/useEnvContext";
|
||||||
|
import { useOrgContext } from "@app/hooks/useOrgContext";
|
||||||
|
import { toast } from "@app/hooks/useToast";
|
||||||
|
import { createApiClient } from "@app/lib/api";
|
||||||
|
import { Role } from "@server/db";
|
||||||
|
import { ArrowUpDown } from "lucide-react";
|
||||||
import { useTranslations } from "next-intl";
|
import { useTranslations } from "next-intl";
|
||||||
import { useRouter } from "next/navigation";
|
import { useRouter } from "next/navigation";
|
||||||
|
import { useState } from "react";
|
||||||
|
import { Switch } from "./ui/switch";
|
||||||
|
import { usePaidStatus } from "@app/hooks/usePaidStatus";
|
||||||
|
|
||||||
export type RoleRow = Role;
|
export type RoleRow = Role;
|
||||||
|
|
||||||
@@ -41,6 +35,7 @@ export default function UsersTable({ roles: r }: RolesTableProps) {
|
|||||||
const api = createApiClient(useEnvContext());
|
const api = createApiClient(useEnvContext());
|
||||||
|
|
||||||
const { org } = useOrgContext();
|
const { org } = useOrgContext();
|
||||||
|
const { isPaidUser } = usePaidStatus();
|
||||||
|
|
||||||
const t = useTranslations();
|
const t = useTranslations();
|
||||||
const [isRefreshing, setIsRefreshing] = useState(false);
|
const [isRefreshing, setIsRefreshing] = useState(false);
|
||||||
@@ -86,6 +81,32 @@ export default function UsersTable({ roles: r }: RolesTableProps) {
|
|||||||
friendlyName: t("description"),
|
friendlyName: t("description"),
|
||||||
header: () => <span className="p-3">{t("description")}</span>
|
header: () => <span className="p-3">{t("description")}</span>
|
||||||
},
|
},
|
||||||
|
|
||||||
|
...(isPaidUser
|
||||||
|
? ([
|
||||||
|
{
|
||||||
|
accessorKey: "requireDeviceApproval",
|
||||||
|
friendlyName: t("requireDeviceApproval"),
|
||||||
|
header: () => (
|
||||||
|
<span className="p-3">
|
||||||
|
{t("requireDeviceApproval")}
|
||||||
|
</span>
|
||||||
|
),
|
||||||
|
cell: ({ row }) => (
|
||||||
|
<Switch
|
||||||
|
defaultChecked={
|
||||||
|
!!row.original.requireDeviceApproval
|
||||||
|
}
|
||||||
|
disabled={!!row.original.isAdmin}
|
||||||
|
onCheckedChange={(val) => {
|
||||||
|
// ...
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
] as ExtendedColumnDef<RoleRow>[])
|
||||||
|
: []),
|
||||||
|
|
||||||
{
|
{
|
||||||
id: "actions",
|
id: "actions",
|
||||||
enableHiding: false,
|
enableHiding: false,
|
||||||
|
|||||||
Reference in New Issue
Block a user