mirror of
https://github.com/fosrl/pangolin.git
synced 2025-08-18 08:18:43 +02:00
append site name to resource in list add add site to info card closes #297
This commit is contained in:
parent
767fec19cd
commit
8ec55eb70d
8 changed files with 83 additions and 50 deletions
|
@ -5,7 +5,8 @@ import {
|
||||||
resources,
|
resources,
|
||||||
userResources,
|
userResources,
|
||||||
roleResources,
|
roleResources,
|
||||||
resourceAccessToken
|
resourceAccessToken,
|
||||||
|
sites
|
||||||
} from "@server/db/schema";
|
} from "@server/db/schema";
|
||||||
import response from "@server/lib/response";
|
import response from "@server/lib/response";
|
||||||
import HttpCode from "@server/types/HttpCode";
|
import HttpCode from "@server/types/HttpCode";
|
||||||
|
@ -59,7 +60,8 @@ function queryAccessTokens(
|
||||||
title: resourceAccessToken.title,
|
title: resourceAccessToken.title,
|
||||||
description: resourceAccessToken.description,
|
description: resourceAccessToken.description,
|
||||||
createdAt: resourceAccessToken.createdAt,
|
createdAt: resourceAccessToken.createdAt,
|
||||||
resourceName: resources.name
|
resourceName: resources.name,
|
||||||
|
siteName: sites.name
|
||||||
};
|
};
|
||||||
|
|
||||||
if (orgId) {
|
if (orgId) {
|
||||||
|
@ -70,6 +72,10 @@ function queryAccessTokens(
|
||||||
resources,
|
resources,
|
||||||
eq(resourceAccessToken.resourceId, resources.resourceId)
|
eq(resourceAccessToken.resourceId, resources.resourceId)
|
||||||
)
|
)
|
||||||
|
.leftJoin(
|
||||||
|
sites,
|
||||||
|
eq(resources.resourceId, sites.siteId)
|
||||||
|
)
|
||||||
.where(
|
.where(
|
||||||
and(
|
and(
|
||||||
inArray(
|
inArray(
|
||||||
|
@ -91,6 +97,10 @@ function queryAccessTokens(
|
||||||
resources,
|
resources,
|
||||||
eq(resourceAccessToken.resourceId, resources.resourceId)
|
eq(resourceAccessToken.resourceId, resources.resourceId)
|
||||||
)
|
)
|
||||||
|
.leftJoin(
|
||||||
|
sites,
|
||||||
|
eq(resources.resourceId, sites.siteId)
|
||||||
|
)
|
||||||
.where(
|
.where(
|
||||||
and(
|
and(
|
||||||
inArray(
|
inArray(
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { Request, Response, NextFunction } from "express";
|
import { Request, Response, NextFunction } from "express";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { db } from "@server/db";
|
import { db } from "@server/db";
|
||||||
import { Resource, resources } from "@server/db/schema";
|
import { Resource, resources, sites } from "@server/db/schema";
|
||||||
import { eq } from "drizzle-orm";
|
import { eq } from "drizzle-orm";
|
||||||
import response from "@server/lib/response";
|
import response from "@server/lib/response";
|
||||||
import HttpCode from "@server/types/HttpCode";
|
import HttpCode from "@server/types/HttpCode";
|
||||||
|
@ -18,7 +18,9 @@ const getResourceSchema = z
|
||||||
})
|
})
|
||||||
.strict();
|
.strict();
|
||||||
|
|
||||||
export type GetResourceResponse = Resource;
|
export type GetResourceResponse = Resource & {
|
||||||
|
siteName: string;
|
||||||
|
};
|
||||||
|
|
||||||
export async function getResource(
|
export async function getResource(
|
||||||
req: Request,
|
req: Request,
|
||||||
|
@ -38,13 +40,17 @@ export async function getResource(
|
||||||
|
|
||||||
const { resourceId } = parsedParams.data;
|
const { resourceId } = parsedParams.data;
|
||||||
|
|
||||||
const resource = await db
|
const [resp] = await db
|
||||||
.select()
|
.select()
|
||||||
.from(resources)
|
.from(resources)
|
||||||
.where(eq(resources.resourceId, resourceId))
|
.where(eq(resources.resourceId, resourceId))
|
||||||
|
.leftJoin(sites, eq(sites.siteId, resources.siteId))
|
||||||
.limit(1);
|
.limit(1);
|
||||||
|
|
||||||
if (resource.length === 0) {
|
const resource = resp.resources;
|
||||||
|
const site = resp.sites;
|
||||||
|
|
||||||
|
if (!resource) {
|
||||||
return next(
|
return next(
|
||||||
createHttpError(
|
createHttpError(
|
||||||
HttpCode.NOT_FOUND,
|
HttpCode.NOT_FOUND,
|
||||||
|
@ -54,7 +60,10 @@ export async function getResource(
|
||||||
}
|
}
|
||||||
|
|
||||||
return response(res, {
|
return response(res, {
|
||||||
data: resource[0],
|
data: {
|
||||||
|
...resource,
|
||||||
|
siteName: site?.name
|
||||||
|
},
|
||||||
success: true,
|
success: true,
|
||||||
error: false,
|
error: false,
|
||||||
message: "Resource retrieved successfully",
|
message: "Resource retrieved successfully",
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
|
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
|
||||||
import { InfoIcon, ShieldCheck, ShieldOff } from "lucide-react";
|
import { ArrowRight, InfoIcon, ShieldCheck, ShieldOff } from "lucide-react";
|
||||||
import { useResourceContext } from "@app/hooks/useResourceContext";
|
import { useResourceContext } from "@app/hooks/useResourceContext";
|
||||||
import { Separator } from "@app/components/ui/separator";
|
import { Separator } from "@app/components/ui/separator";
|
||||||
import CopyToClipboard from "@app/components/CopyToClipboard";
|
import CopyToClipboard from "@app/components/CopyToClipboard";
|
||||||
|
@ -11,6 +11,7 @@ import {
|
||||||
InfoSections,
|
InfoSections,
|
||||||
InfoSectionTitle
|
InfoSectionTitle
|
||||||
} from "@app/components/InfoSection";
|
} from "@app/components/InfoSection";
|
||||||
|
import Link from "next/link";
|
||||||
|
|
||||||
type ResourceInfoBoxType = {};
|
type ResourceInfoBoxType = {};
|
||||||
|
|
||||||
|
@ -26,7 +27,7 @@ export default function ResourceInfoBox({}: ResourceInfoBoxType) {
|
||||||
Resource Information
|
Resource Information
|
||||||
</AlertTitle>
|
</AlertTitle>
|
||||||
<AlertDescription className="mt-4">
|
<AlertDescription className="mt-4">
|
||||||
<InfoSections>
|
<InfoSections cols={3}>
|
||||||
{resource.http ? (
|
{resource.http ? (
|
||||||
<>
|
<>
|
||||||
<InfoSection>
|
<InfoSection>
|
||||||
|
@ -40,22 +41,16 @@ export default function ResourceInfoBox({}: ResourceInfoBoxType) {
|
||||||
authInfo.whitelist ? (
|
authInfo.whitelist ? (
|
||||||
<div className="flex items-start space-x-2 text-green-500">
|
<div className="flex items-start space-x-2 text-green-500">
|
||||||
<ShieldCheck className="w-4 h-4 mt-0.5" />
|
<ShieldCheck className="w-4 h-4 mt-0.5" />
|
||||||
<span>
|
<span>Protected</span>
|
||||||
This resource is protected with
|
|
||||||
at least one authentication method.
|
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="flex items-center space-x-2 text-yellow-500">
|
<div className="flex items-center space-x-2 text-yellow-500">
|
||||||
<ShieldOff className="w-4 h-4" />
|
<ShieldOff className="w-4 h-4" />
|
||||||
<span>
|
<span>Not Protected</span>
|
||||||
Anyone can access this resource.
|
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</InfoSectionContent>
|
</InfoSectionContent>
|
||||||
</InfoSection>
|
</InfoSection>
|
||||||
<Separator orientation="vertical" />
|
|
||||||
<InfoSection>
|
<InfoSection>
|
||||||
<InfoSectionTitle>URL</InfoSectionTitle>
|
<InfoSectionTitle>URL</InfoSectionTitle>
|
||||||
<InfoSectionContent>
|
<InfoSectionContent>
|
||||||
|
@ -65,6 +60,12 @@ export default function ResourceInfoBox({}: ResourceInfoBoxType) {
|
||||||
/>
|
/>
|
||||||
</InfoSectionContent>
|
</InfoSectionContent>
|
||||||
</InfoSection>
|
</InfoSection>
|
||||||
|
<InfoSection>
|
||||||
|
<InfoSectionTitle>Site</InfoSectionTitle>
|
||||||
|
<InfoSectionContent>
|
||||||
|
{resource.siteName}
|
||||||
|
</InfoSectionContent>
|
||||||
|
</InfoSection>
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
|
@ -76,7 +77,6 @@ export default function ResourceInfoBox({}: ResourceInfoBoxType) {
|
||||||
</span>
|
</span>
|
||||||
</InfoSectionContent>
|
</InfoSectionContent>
|
||||||
</InfoSection>
|
</InfoSection>
|
||||||
<Separator orientation="vertical" />
|
|
||||||
<InfoSection>
|
<InfoSection>
|
||||||
<InfoSectionTitle>Port</InfoSectionTitle>
|
<InfoSectionTitle>Port</InfoSectionTitle>
|
||||||
<InfoSectionContent>
|
<InfoSectionContent>
|
||||||
|
|
|
@ -265,6 +265,12 @@ export default function GeneralForm() {
|
||||||
description: "The resource has been transferred successfully"
|
description: "The resource has been transferred successfully"
|
||||||
});
|
});
|
||||||
router.refresh();
|
router.refresh();
|
||||||
|
|
||||||
|
updateResource({
|
||||||
|
siteName:
|
||||||
|
sites.find((site) => site.siteId === data.siteId)?.name ||
|
||||||
|
""
|
||||||
|
});
|
||||||
}
|
}
|
||||||
setTransferLoading(false);
|
setTransferLoading(false);
|
||||||
}
|
}
|
||||||
|
@ -606,9 +612,7 @@ export default function GeneralForm() {
|
||||||
</PopoverTrigger>
|
</PopoverTrigger>
|
||||||
<PopoverContent className="w-full p-0">
|
<PopoverContent className="w-full p-0">
|
||||||
<Command>
|
<Command>
|
||||||
<CommandInput
|
<CommandInput placeholder="Search sites" />
|
||||||
placeholder="Search sites"
|
|
||||||
/>
|
|
||||||
<CommandEmpty>
|
<CommandEmpty>
|
||||||
No sites found.
|
No sites found.
|
||||||
</CommandEmpty>
|
</CommandEmpty>
|
||||||
|
|
|
@ -107,7 +107,12 @@ export default function CreateShareLinkForm({
|
||||||
const [isOpen, setIsOpen] = useState(false);
|
const [isOpen, setIsOpen] = useState(false);
|
||||||
|
|
||||||
const [resources, setResources] = useState<
|
const [resources, setResources] = useState<
|
||||||
{ resourceId: number; name: string; resourceUrl: string }[]
|
{
|
||||||
|
resourceId: number;
|
||||||
|
name: string;
|
||||||
|
resourceUrl: string;
|
||||||
|
siteName: string | null;
|
||||||
|
}[]
|
||||||
>([]);
|
>([]);
|
||||||
|
|
||||||
const timeUnits = [
|
const timeUnits = [
|
||||||
|
@ -159,7 +164,8 @@ export default function CreateShareLinkForm({
|
||||||
.map((r) => ({
|
.map((r) => ({
|
||||||
resourceId: r.resourceId,
|
resourceId: r.resourceId,
|
||||||
name: r.name,
|
name: r.name,
|
||||||
resourceUrl: `${r.ssl ? "https://" : "http://"}${r.fullDomain}/`
|
resourceUrl: `${r.ssl ? "https://" : "http://"}${r.fullDomain}/`,
|
||||||
|
siteName: r.siteName
|
||||||
}))
|
}))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -231,19 +237,28 @@ export default function CreateShareLinkForm({
|
||||||
token.accessToken
|
token.accessToken
|
||||||
);
|
);
|
||||||
setDirectLink(directLink);
|
setDirectLink(directLink);
|
||||||
|
|
||||||
|
const resource = resources.find((r) => r.resourceId === values.resourceId);
|
||||||
|
|
||||||
onCreated?.({
|
onCreated?.({
|
||||||
accessTokenId: token.accessTokenId,
|
accessTokenId: token.accessTokenId,
|
||||||
resourceId: token.resourceId,
|
resourceId: token.resourceId,
|
||||||
resourceName: values.resourceName,
|
resourceName: values.resourceName,
|
||||||
title: token.title,
|
title: token.title,
|
||||||
createdAt: token.createdAt,
|
createdAt: token.createdAt,
|
||||||
expiresAt: token.expiresAt
|
expiresAt: token.expiresAt,
|
||||||
|
siteName: resource?.siteName || null,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getSelectedResourceName(id: number) {
|
||||||
|
const resource = resources.find((r) => r.resourceId === id);
|
||||||
|
return `${resource?.name} ${resource?.siteName ? `(${resource.siteName})` : ""}`;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Credenza
|
<Credenza
|
||||||
|
@ -292,14 +307,9 @@ export default function CreateShareLinkForm({
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{field.value
|
{field.value
|
||||||
? resources.find(
|
? getSelectedResourceName(
|
||||||
(
|
|
||||||
r
|
|
||||||
) =>
|
|
||||||
r.resourceId ===
|
|
||||||
field.value
|
field.value
|
||||||
)
|
)
|
||||||
?.name
|
|
||||||
: "Select resource"}
|
: "Select resource"}
|
||||||
<CaretSortIcon className="ml-2 h-4 w-4 shrink-0 opacity-50" />
|
<CaretSortIcon className="ml-2 h-4 w-4 shrink-0 opacity-50" />
|
||||||
</Button>
|
</Button>
|
||||||
|
@ -348,9 +358,7 @@ export default function CreateShareLinkForm({
|
||||||
: "opacity-0"
|
: "opacity-0"
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
{
|
{`${r.name} ${r.siteName ? `(${r.siteName})` : ""}`}
|
||||||
r.name
|
|
||||||
}
|
|
||||||
</CommandItem>
|
</CommandItem>
|
||||||
)
|
)
|
||||||
)}
|
)}
|
||||||
|
|
|
@ -41,6 +41,7 @@ export type ShareLinkRow = {
|
||||||
title: string | null;
|
title: string | null;
|
||||||
createdAt: number;
|
createdAt: number;
|
||||||
expiresAt: number | null;
|
expiresAt: number | null;
|
||||||
|
siteName: string | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
type ShareLinksTableProps = {
|
type ShareLinksTableProps = {
|
||||||
|
@ -145,7 +146,8 @@ export default function ShareLinksTable({
|
||||||
return (
|
return (
|
||||||
<Link href={`/${orgId}/settings/resources/${r.resourceId}`}>
|
<Link href={`/${orgId}/settings/resources/${r.resourceId}`}>
|
||||||
<Button variant="outline">
|
<Button variant="outline">
|
||||||
{r.resourceName}
|
{r.resourceName}{" "}
|
||||||
|
{r.siteName ? `(${r.siteName})` : ""}
|
||||||
<ArrowUpRight className="ml-2 h-4 w-4" />
|
<ArrowUpRight className="ml-2 h-4 w-4" />
|
||||||
</Button>
|
</Button>
|
||||||
</Link>
|
</Link>
|
||||||
|
@ -280,14 +282,15 @@ export default function ShareLinksTable({
|
||||||
<div className="flex items-center justify-end space-x-2">
|
<div className="flex items-center justify-end space-x-2">
|
||||||
<Button
|
<Button
|
||||||
variant="outlinePrimary"
|
variant="outlinePrimary"
|
||||||
onClick={() => deleteSharelink(row.original.accessTokenId)}
|
onClick={() =>
|
||||||
|
deleteSharelink(row.original.accessTokenId)
|
||||||
|
}
|
||||||
>
|
>
|
||||||
Delete
|
Delete
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
];
|
];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -3,7 +3,6 @@
|
||||||
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
|
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
|
||||||
import { InfoIcon } from "lucide-react";
|
import { InfoIcon } from "lucide-react";
|
||||||
import { useSiteContext } from "@app/hooks/useSiteContext";
|
import { useSiteContext } from "@app/hooks/useSiteContext";
|
||||||
import { Separator } from "@app/components/ui/separator";
|
|
||||||
import {
|
import {
|
||||||
InfoSection,
|
InfoSection,
|
||||||
InfoSectionContent,
|
InfoSectionContent,
|
||||||
|
@ -33,7 +32,7 @@ export default function SiteInfoCard({}: SiteInfoCardProps) {
|
||||||
<InfoIcon className="h-4 w-4" />
|
<InfoIcon className="h-4 w-4" />
|
||||||
<AlertTitle className="font-semibold">Site Information</AlertTitle>
|
<AlertTitle className="font-semibold">Site Information</AlertTitle>
|
||||||
<AlertDescription className="mt-4">
|
<AlertDescription className="mt-4">
|
||||||
<InfoSections>
|
<InfoSections cols={2}>
|
||||||
{(site.type == "newt" || site.type == "wireguard") && (
|
{(site.type == "newt" || site.type == "wireguard") && (
|
||||||
<>
|
<>
|
||||||
<InfoSection>
|
<InfoSection>
|
||||||
|
@ -52,8 +51,6 @@ export default function SiteInfoCard({}: SiteInfoCardProps) {
|
||||||
)}
|
)}
|
||||||
</InfoSectionContent>
|
</InfoSectionContent>
|
||||||
</InfoSection>
|
</InfoSection>
|
||||||
|
|
||||||
<Separator orientation="vertical" />
|
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
<InfoSection>
|
<InfoSection>
|
||||||
|
|
|
@ -1,8 +1,16 @@
|
||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
export function InfoSections({ children }: { children: React.ReactNode }) {
|
export function InfoSections({
|
||||||
|
children,
|
||||||
|
cols
|
||||||
|
}: {
|
||||||
|
children: React.ReactNode;
|
||||||
|
cols?: number;
|
||||||
|
}) {
|
||||||
return (
|
return (
|
||||||
<div className="grid grid-cols-1 md:gap-4 gap-2 md:grid-cols-[1fr_auto_1fr] md:items-start">
|
<div
|
||||||
|
className={`grid md:grid-cols-${cols || 1} md:gap-4 gap-2 md:items-start grid-cols-1`}
|
||||||
|
>
|
||||||
{children}
|
{children}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -23,9 +31,3 @@ export function InfoSectionContent({
|
||||||
}) {
|
}) {
|
||||||
return <div className="break-words">{children}</div>;
|
return <div className="break-words">{children}</div>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function Divider() {
|
|
||||||
return (
|
|
||||||
<div className="hidden md:block border-l border-gray-300 h-auto mx-4"></div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue