mirror of
https://github.com/fosrl/pangolin.git
synced 2026-08-22 20:20:29 +02:00
Handle warning and no routing to remote exit nodes for ai providers
This commit is contained in:
@@ -1891,6 +1891,7 @@
|
|||||||
"aiProviderRoutingModeTargetDescription": "Route through targets on your sites",
|
"aiProviderRoutingModeTargetDescription": "Route through targets on your sites",
|
||||||
"aiProviderRoutingModeTargetNote": "After creating this provider, configure site targets on the Network Settings tab.",
|
"aiProviderRoutingModeTargetNote": "After creating this provider, configure site targets on the Network Settings tab.",
|
||||||
"aiProviderTargetNoOne": "This provider doesn't have any targets. Add a target to route requests through your sites.",
|
"aiProviderTargetNoOne": "This provider doesn't have any targets. Add a target to route requests through your sites.",
|
||||||
|
"aiProviderRemoteNodeTargetsWarning": "Sites connected to remote nodes are inaccessable to be routed to on AI Gateway providers.",
|
||||||
"aiProviderSkipTlsVerification": "Skip TLS Verification",
|
"aiProviderSkipTlsVerification": "Skip TLS Verification",
|
||||||
"aiProviderSkipTlsVerificationDescription": "Disable TLS certificate verification for the upstream connection",
|
"aiProviderSkipTlsVerificationDescription": "Disable TLS certificate verification for the upstream connection",
|
||||||
"aiProviderBudget": "Budget",
|
"aiProviderBudget": "Budget",
|
||||||
|
|||||||
@@ -99,6 +99,7 @@ async function fetchProviderTargets(
|
|||||||
method: targets.method,
|
method: targets.method,
|
||||||
exitNodeSubnet: sites.exitNodeSubnet,
|
exitNodeSubnet: sites.exitNodeSubnet,
|
||||||
reachableAt: exitNodes.reachableAt,
|
reachableAt: exitNodes.reachableAt,
|
||||||
|
exitNodeType: exitNodes.type,
|
||||||
hcHealth: targetHealthCheck.hcHealth
|
hcHealth: targetHealthCheck.hcHealth
|
||||||
})
|
})
|
||||||
.from(targets)
|
.from(targets)
|
||||||
@@ -119,6 +120,12 @@ async function fetchProviderTargets(
|
|||||||
if (!row.exitNodeSubnet || !row.reachableAt) {
|
if (!row.exitNodeSubnet || !row.reachableAt) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
// Sites connected to a remote exit node aren't reachable via a
|
||||||
|
// gerbil sidecar's /router/* proxy - only "gerbil" type exit nodes
|
||||||
|
// run that endpoint.
|
||||||
|
if (row.exitNodeType !== "gerbil") {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
// A target with an active health check that's currently failing is
|
// A target with an active health check that's currently failing is
|
||||||
// taken out of rotation. No health check (null) or "unknown" (check
|
// taken out of rotation. No health check (null) or "unknown" (check
|
||||||
// hasn't run yet / hcEnabled is off) still routes normally, matching
|
// hasn't run yet / hcEnabled is off) still routes normally, matching
|
||||||
|
|||||||
@@ -345,6 +345,7 @@ export default function AiProviderNetworkPage() {
|
|||||||
ref={targetsFormRef}
|
ref={targetsFormRef}
|
||||||
orgId={orgId}
|
orgId={orgId}
|
||||||
isHttp
|
isHttp
|
||||||
|
isAiProvider
|
||||||
providerId={provider.providerId}
|
providerId={provider.providerId}
|
||||||
initialTargets={
|
initialTargets={
|
||||||
isTargetModeSaved ? remoteTargets : []
|
isTargetModeSaved ? remoteTargets : []
|
||||||
|
|||||||
@@ -682,6 +682,7 @@ export default function CreateAiProviderPage() {
|
|||||||
<ProxyResourceTargetsForm
|
<ProxyResourceTargetsForm
|
||||||
orgId={orgId}
|
orgId={orgId}
|
||||||
isHttp
|
isHttp
|
||||||
|
isAiProvider
|
||||||
onChange={(nextTargets) => {
|
onChange={(nextTargets) => {
|
||||||
targetsRef.current = nextTargets;
|
targetsRef.current = nextTargets;
|
||||||
}}
|
}}
|
||||||
|
|||||||
@@ -113,6 +113,8 @@ type ProxyResourceTargetsFormProps = {
|
|||||||
hideSaveButton?: boolean;
|
hideSaveButton?: boolean;
|
||||||
/** Hide the advanced mode toggle and always use non-advanced mode (e.g. AI providers) */
|
/** Hide the advanced mode toggle and always use non-advanced mode (e.g. AI providers) */
|
||||||
disableAdvancedMode?: boolean;
|
disableAdvancedMode?: boolean;
|
||||||
|
/** Targets picker is for an AI provider (changes which routing warnings are shown) */
|
||||||
|
isAiProvider?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const ProxyResourceTargetsForm = forwardRef<
|
export const ProxyResourceTargetsForm = forwardRef<
|
||||||
@@ -131,7 +133,8 @@ export const ProxyResourceTargetsForm = forwardRef<
|
|||||||
emptyMessage,
|
emptyMessage,
|
||||||
embedded = false,
|
embedded = false,
|
||||||
hideSaveButton = false,
|
hideSaveButton = false,
|
||||||
disableAdvancedMode = false
|
disableAdvancedMode = false,
|
||||||
|
isAiProvider = false
|
||||||
},
|
},
|
||||||
ref
|
ref
|
||||||
) {
|
) {
|
||||||
@@ -259,6 +262,14 @@ export const ProxyResourceTargetsForm = forwardRef<
|
|||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const { data: remoteExitNodes = [] } = useQuery({
|
||||||
|
...orgQueries.remoteExitNodes({ orgId }),
|
||||||
|
enabled: build === "saas" && isAiProvider
|
||||||
|
});
|
||||||
|
const hasRemoteExitNodes = remoteExitNodes.some(
|
||||||
|
(node) => node.exitNodeId !== null
|
||||||
|
);
|
||||||
|
|
||||||
const updateTarget = useCallback(
|
const updateTarget = useCallback(
|
||||||
(targetId: number, data: Partial<LocalTarget>) => {
|
(targetId: number, data: Partial<LocalTarget>) => {
|
||||||
setTargets((prevTargets) => {
|
setTargets((prevTargets) => {
|
||||||
@@ -972,6 +983,7 @@ export const ProxyResourceTargetsForm = forwardRef<
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{build === "saas" &&
|
{build === "saas" &&
|
||||||
|
!isAiProvider &&
|
||||||
targets.length > 1 &&
|
targets.length > 1 &&
|
||||||
new Set(targets.map((t) => t.siteId)).size > 1 && (
|
new Set(targets.map((t) => t.siteId)).size > 1 && (
|
||||||
<p className="text-sm text-muted-foreground mt-3">
|
<p className="text-sm text-muted-foreground mt-3">
|
||||||
@@ -988,6 +1000,11 @@ export const ProxyResourceTargetsForm = forwardRef<
|
|||||||
.
|
.
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
|
{build === "saas" && isAiProvider && hasRemoteExitNodes && (
|
||||||
|
<p className="text-sm text-muted-foreground mt-3">
|
||||||
|
{t("aiProviderRemoteNodeTargetsWarning")}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -59,6 +59,7 @@ import type {
|
|||||||
import type { GetResourceResponse } from "@server/routers/resource/getResource";
|
import type { GetResourceResponse } from "@server/routers/resource/getResource";
|
||||||
import type { GetResourceAuthInfoResponse } from "@server/routers/resource/getResourceAuthInfo";
|
import type { GetResourceAuthInfoResponse } from "@server/routers/resource/getResourceAuthInfo";
|
||||||
import type { ListResourcePoliciesResponse } from "@server/routers/resource/types";
|
import type { ListResourcePoliciesResponse } from "@server/routers/resource/types";
|
||||||
|
import type { ListRemoteExitNodesResponse } from "@server/routers/remoteExitNode/types";
|
||||||
import type { ListRolesResponse } from "@server/routers/role";
|
import type { ListRolesResponse } from "@server/routers/role";
|
||||||
import type { ListSitesResponse } from "@server/routers/site";
|
import type { ListSitesResponse } from "@server/routers/site";
|
||||||
import type {
|
import type {
|
||||||
@@ -330,6 +331,17 @@ export const orgQueries = {
|
|||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
|
|
||||||
|
remoteExitNodes: ({ orgId }: { orgId: string }) =>
|
||||||
|
queryOptions({
|
||||||
|
queryKey: ["ORG", orgId, "REMOTE_EXIT_NODES"] as const,
|
||||||
|
queryFn: async ({ signal, meta }) => {
|
||||||
|
const res = await meta!.api.get<
|
||||||
|
AxiosResponse<ListRemoteExitNodesResponse>
|
||||||
|
>(`/org/${orgId}/remote-exit-nodes`, { signal });
|
||||||
|
return res.data.data.remoteExitNodes;
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
|
||||||
labels: ({
|
labels: ({
|
||||||
orgId,
|
orgId,
|
||||||
query,
|
query,
|
||||||
|
|||||||
Reference in New Issue
Block a user