mirror of
https://github.com/fosrl/pangolin.git
synced 2026-02-03 16:43:45 +00:00
Merge remote-tracking branch 'upstream/dev' into feature-i18n
This commit is contained in:
@@ -14,7 +14,7 @@ import ConfirmDeleteDialog from "@app/components/ConfirmDeleteDialog";
|
||||
import { useOrgContext } from "@app/hooks/useOrgContext";
|
||||
import { toast } from "@app/hooks/useToast";
|
||||
import { RolesDataTable } from "./RolesDataTable";
|
||||
import { Role } from "@server/db/schemas";
|
||||
import { Role } from "@server/db";
|
||||
import CreateRoleForm from "./CreateRoleForm";
|
||||
import DeleteRoleForm from "./DeleteRoleForm";
|
||||
import { createApiClient } from "@app/lib/api";
|
||||
|
||||
@@ -44,7 +44,7 @@ import {
|
||||
CreateOrgApiKeyBody,
|
||||
CreateOrgApiKeyResponse
|
||||
} from "@server/routers/apiKeys";
|
||||
import { ApiKey } from "@server/db/schemas";
|
||||
import { ApiKey } from "@server/db";
|
||||
import {
|
||||
InfoSection,
|
||||
InfoSectionContent,
|
||||
|
||||
@@ -21,7 +21,7 @@ export default function ResourceInfoBox({}: ResourceInfoBoxType) {
|
||||
const { resource, authInfo, site } = useResourceContext();
|
||||
const api = createApiClient(useEnvContext());
|
||||
|
||||
const { isEnabled, isAvailable } = useDockerSocket(resource.siteId);
|
||||
const { isEnabled, isAvailable } = useDockerSocket(site!);
|
||||
const t = useTranslations();
|
||||
|
||||
let fullUrl = `${resource.ssl ? "https" : "http"}://${resource.fullDomain}`;
|
||||
@@ -72,7 +72,7 @@ export default function ResourceInfoBox({}: ResourceInfoBoxType) {
|
||||
{resource.siteName}
|
||||
</InfoSectionContent>
|
||||
</InfoSection>
|
||||
{isEnabled && (
|
||||
{/* {isEnabled && (
|
||||
<InfoSection>
|
||||
<InfoSectionTitle>Socket</InfoSectionTitle>
|
||||
<InfoSectionContent>
|
||||
@@ -89,7 +89,7 @@ export default function ResourceInfoBox({}: ResourceInfoBoxType) {
|
||||
)}
|
||||
</InfoSectionContent>
|
||||
</InfoSection>
|
||||
)}
|
||||
)} */}
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
|
||||
@@ -28,7 +28,7 @@ import {
|
||||
} from "@app/components/Credenza";
|
||||
import { formatAxiosError } from "@app/lib/api";
|
||||
import { AxiosResponse } from "axios";
|
||||
import { Resource } from "@server/db/schemas";
|
||||
import { Resource } from "@server/db";
|
||||
import { createApiClient } from "@app/lib/api";
|
||||
import { useEnvContext } from "@app/hooks/useEnvContext";
|
||||
import { useTranslations } from "next-intl";
|
||||
|
||||
@@ -28,7 +28,7 @@ import {
|
||||
} from "@app/components/Credenza";
|
||||
import { formatAxiosError } from "@app/lib/api";
|
||||
import { AxiosResponse } from "axios";
|
||||
import { Resource } from "@server/db/schemas";
|
||||
import { Resource } from "@server/db";
|
||||
import {
|
||||
InputOTP,
|
||||
InputOTPGroup,
|
||||
|
||||
@@ -771,7 +771,7 @@ export default function ReverseProxyTargets(props: {
|
||||
<Input id="ip" {...field} />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
{site && (
|
||||
{site && site.type == 'newt' && (
|
||||
<ContainersSelector
|
||||
site={site}
|
||||
onContainerSelect={(
|
||||
|
||||
@@ -32,7 +32,7 @@ import { createApiClient } from "@app/lib/api";
|
||||
import { useEnvContext } from "@app/hooks/useEnvContext";
|
||||
import { toast } from "@app/hooks/useToast";
|
||||
import { AxiosResponse } from "axios";
|
||||
import { Resource } from "@server/db/schemas";
|
||||
import { Resource } from "@server/db";
|
||||
import { StrategySelect } from "@app/components/StrategySelect";
|
||||
import {
|
||||
Select,
|
||||
|
||||
@@ -130,31 +130,35 @@ export default function GeneralPage() {
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="dockerSocketEnabled"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormControl>
|
||||
<SwitchInput
|
||||
id="docker-socket-enabled"
|
||||
label="Enable Docker Socket"
|
||||
defaultChecked={field.value}
|
||||
onCheckedChange={
|
||||
field.onChange
|
||||
}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
<FormDescription>
|
||||
Enable Docker Socket discovery
|
||||
for populating container
|
||||
information, useful in resource
|
||||
targets.
|
||||
</FormDescription>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
{site && site.type === "newt" && (
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="dockerSocketEnabled"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormControl>
|
||||
<SwitchInput
|
||||
id="docker-socket-enabled"
|
||||
label="Enable Docker Socket"
|
||||
defaultChecked={
|
||||
field.value
|
||||
}
|
||||
onCheckedChange={
|
||||
field.onChange
|
||||
}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
<FormDescription>
|
||||
Enable Docker Socket
|
||||
discovery for populating
|
||||
container information,
|
||||
useful in resource targets.
|
||||
</FormDescription>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
</form>
|
||||
</Form>
|
||||
</SettingsSectionForm>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { cookies } from "next/headers";
|
||||
import ValidateOidcToken from "./ValidateOidcToken";
|
||||
import { idp } from "@server/db/schemas";
|
||||
import db from "@server/db";
|
||||
import { idp } from "@server/db";
|
||||
import { db } from "@server/db";
|
||||
import { eq } from "drizzle-orm";
|
||||
import { getTranslations } from "next-intl/server";
|
||||
|
||||
|
||||
@@ -6,8 +6,8 @@ import DashboardLoginForm from "./DashboardLoginForm";
|
||||
import { Mail } from "lucide-react";
|
||||
import { pullEnv } from "@app/lib/pullEnv";
|
||||
import { cleanRedirect } from "@app/lib/cleanRedirect";
|
||||
import db from "@server/db";
|
||||
import { idp } from "@server/db/schemas";
|
||||
import { db } from "@server/db";
|
||||
import { idp } from "@server/db";
|
||||
import { LoginFormIDP } from "@app/components/LoginForm";
|
||||
import { getTranslations } from "next-intl/server";
|
||||
|
||||
|
||||
@@ -14,8 +14,8 @@ import ResourceAccessDenied from "./ResourceAccessDenied";
|
||||
import AccessToken from "./AccessToken";
|
||||
import { pullEnv } from "@app/lib/pullEnv";
|
||||
import { LoginFormIDP } from "@app/components/LoginForm";
|
||||
import db from "@server/db";
|
||||
import { idp } from "@server/db/schemas";
|
||||
import { db } from "@server/db";
|
||||
import { idp } from "@server/db";
|
||||
|
||||
export default async function ResourceAuthPage(props: {
|
||||
params: Promise<{ resourceId: number }>;
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
@@ -68,8 +68,9 @@ export const ContainersSelector: FC<ContainerSelectorProps> = ({
|
||||
}) => {
|
||||
const [open, setOpen] = useState(false);
|
||||
const isDesktop = useMediaQuery("(min-width: 768px)");
|
||||
|
||||
const { isAvailable, containers, fetchContainers } = useDockerSocket(
|
||||
site.siteId
|
||||
site
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
@@ -172,6 +172,8 @@ export function Layout({
|
||||
alt="Pangolin Logo"
|
||||
width={110}
|
||||
height={25}
|
||||
priority={true}
|
||||
quality={25}
|
||||
/>
|
||||
)}
|
||||
</Link>
|
||||
|
||||
@@ -4,21 +4,18 @@ import { useEnvContext } from "./useEnvContext";
|
||||
import {
|
||||
Container,
|
||||
GetDockerStatusResponse,
|
||||
GetSiteResponse,
|
||||
ListContainersResponse,
|
||||
TriggerFetchResponse
|
||||
} from "@server/routers/site";
|
||||
import { AxiosResponse } from "axios";
|
||||
import { toast } from "./useToast";
|
||||
import { Site } from "@server/db";
|
||||
|
||||
const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));
|
||||
|
||||
export function useDockerSocket(siteId: number) {
|
||||
if (!siteId) {
|
||||
throw new Error("Site ID is required to use Docker Socket");
|
||||
}
|
||||
export function useDockerSocket(site: Site) {
|
||||
console.log(`useDockerSocket initialized for site ID: ${site.siteId}`);
|
||||
|
||||
const [site, setSite] = useState<GetSiteResponse>();
|
||||
const [dockerSocket, setDockerSocket] = useState<GetDockerStatusResponse>();
|
||||
const [containers, setContainers] = useState<Container[]>([]);
|
||||
|
||||
@@ -27,40 +24,18 @@ export function useDockerSocket(siteId: number) {
|
||||
const { dockerSocketEnabled: isEnabled = true } = site || {};
|
||||
const { isAvailable = false, socketPath } = dockerSocket || {};
|
||||
|
||||
const fetchSite = useCallback(async () => {
|
||||
try {
|
||||
const res = await api.get<AxiosResponse<GetSiteResponse>>(
|
||||
`/site/${siteId}`
|
||||
);
|
||||
|
||||
if (res.status === 200) {
|
||||
setSite(res.data.data);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
toast({
|
||||
variant: "destructive",
|
||||
title: "Failed to fetch resource",
|
||||
description: formatAxiosError(
|
||||
err,
|
||||
"An error occurred while fetching resource"
|
||||
)
|
||||
});
|
||||
}
|
||||
}, [api, siteId]);
|
||||
|
||||
const checkDockerSocket = useCallback(async () => {
|
||||
if (!isEnabled) {
|
||||
console.warn("Docker socket is not enabled for this site.");
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const res = await api.post(`/site/${siteId}/docker/check`);
|
||||
const res = await api.post(`/site/${site.siteId}/docker/check`);
|
||||
console.log("Docker socket check response:", res);
|
||||
} catch (error) {
|
||||
console.error("Failed to check Docker socket:", error);
|
||||
}
|
||||
}, [api, siteId, isEnabled]);
|
||||
}, [api, site.siteId, isEnabled]);
|
||||
|
||||
const getDockerSocketStatus = useCallback(async () => {
|
||||
if (!isEnabled) {
|
||||
@@ -70,7 +45,7 @@ export function useDockerSocket(siteId: number) {
|
||||
|
||||
try {
|
||||
const res = await api.get<AxiosResponse<GetDockerStatusResponse>>(
|
||||
`/site/${siteId}/docker/status`
|
||||
`/site/${site.siteId}/docker/status`
|
||||
);
|
||||
|
||||
if (res.status === 200) {
|
||||
@@ -92,7 +67,7 @@ export function useDockerSocket(siteId: number) {
|
||||
description: "An error occurred while fetching Docker status."
|
||||
});
|
||||
}
|
||||
}, [api, siteId, isEnabled]);
|
||||
}, [api, site.siteId, isEnabled]);
|
||||
|
||||
const getContainers = useCallback(
|
||||
async (maxRetries: number = 3) => {
|
||||
@@ -111,18 +86,15 @@ export function useDockerSocket(siteId: number) {
|
||||
try {
|
||||
const res = await api.get<
|
||||
AxiosResponse<ListContainersResponse>
|
||||
>(`/site/${siteId}/docker/containers`);
|
||||
>(`/site/${site.siteId}/docker/containers`);
|
||||
setContainers(res.data.data);
|
||||
return;
|
||||
return res.data.data;
|
||||
} catch (error: any) {
|
||||
attempt++;
|
||||
|
||||
// Check if the error is a 425 (Too Early) status
|
||||
if (error?.response?.status === 425) {
|
||||
if (attempt < maxRetries) {
|
||||
// Ask the newt server to check containers
|
||||
await fetchContainerList();
|
||||
|
||||
console.log(
|
||||
`Containers not ready yet (attempt ${attempt}/${maxRetries}). Retrying in 250ms...`
|
||||
);
|
||||
@@ -160,7 +132,7 @@ export function useDockerSocket(siteId: number) {
|
||||
|
||||
try {
|
||||
const res = await api.post<AxiosResponse<TriggerFetchResponse>>(
|
||||
`/site/${siteId}/docker/trigger`
|
||||
`/site/${site.siteId}/docker/trigger`
|
||||
);
|
||||
// TODO: identify a way to poll the server for latest container list periodically?
|
||||
await fetchContainerList();
|
||||
@@ -169,13 +141,9 @@ export function useDockerSocket(siteId: number) {
|
||||
console.error("Failed to trigger Docker containers:", error);
|
||||
}
|
||||
},
|
||||
[api, siteId, isEnabled, isAvailable]
|
||||
[api, site.siteId, isEnabled, isAvailable]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
fetchSite();
|
||||
}, [fetchSite]);
|
||||
|
||||
// 2. Docker socket status monitoring
|
||||
useEffect(() => {
|
||||
if (!isEnabled || isAvailable) {
|
||||
|
||||
Reference in New Issue
Block a user