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