mirror of
https://github.com/fosrl/pangolin.git
synced 2026-08-14 08:19:51 +02:00
various ui improvements
This commit is contained in:
+8
-8
@@ -864,9 +864,9 @@
|
|||||||
"policyAuthMethodOff": "Off",
|
"policyAuthMethodOff": "Off",
|
||||||
"policyAuthSsoTitle": "Platform SSO",
|
"policyAuthSsoTitle": "Platform SSO",
|
||||||
"policyAuthSsoDescription": "Require sign-in through your organization's identity provider",
|
"policyAuthSsoDescription": "Require sign-in through your organization's identity provider",
|
||||||
"policyAuthInferenceSsoDescription": "Selected users and roles can authenticate to the gateway using their identity API key",
|
"policyAuthInferenceSsoDescription": "Selected users and roles can authenticate to the gateway using their identity API key.",
|
||||||
"policyAuthInferenceIdentityKeyHelp": "Every user already has an identity API key, so you only need to create virtual API keys for non-user clients or shared access. Users can retrieve their key by signing in with their identity provider at <resourceLink></resourceLink>, where it will be shown after login.",
|
|
||||||
"policyAuthInferenceIdentityKeyHelpNoUrl": "Every user already has an identity API key, so you only need to create virtual API keys for non-user clients or shared access. Users can retrieve their key by signing in with their identity provider at this resource's URL, where it will be shown after login.",
|
"policyAuthInferenceIdentityKeyHelpNoUrl": "Every user already has an identity API key, so you only need to create virtual API keys for non-user clients or shared access. Users can retrieve their key by signing in with their identity provider at this resource's URL, where it will be shown after login.",
|
||||||
|
"policyAuthInferenceIdentityKeyHelp": "Every user already has an identity API key, so you only need to create virtual API keys for non-user clients or shared access. Users can retrieve their key by signing in with their identity provider at <resourceLink></resourceLink>, where it will be shown after login.",
|
||||||
"policyAuthSsoSummary": "{idp} · {users} users, {roles} roles",
|
"policyAuthSsoSummary": "{idp} · {users} users, {roles} roles",
|
||||||
"policyAuthSsoDefaultIdp": "Default provider",
|
"policyAuthSsoDefaultIdp": "Default provider",
|
||||||
"policyAuthAddDefaultIdentityProvider": "Add Default Identity Provider",
|
"policyAuthAddDefaultIdentityProvider": "Add Default Identity Provider",
|
||||||
@@ -1821,7 +1821,7 @@
|
|||||||
"aiProviderBudgetUnitUsd": "USD",
|
"aiProviderBudgetUnitUsd": "USD",
|
||||||
"aiProviderBudgetUnitTokens": "Tokens",
|
"aiProviderBudgetUnitTokens": "Tokens",
|
||||||
"aiProviderEnabled": "Enabled",
|
"aiProviderEnabled": "Enabled",
|
||||||
"aiProviderEnabledDescription": "Disable to stop using this provider without deleting it",
|
"aiProviderEnabledDescription": "Fully disable this provider across all resources",
|
||||||
"aiProviderErrorCreate": "Failed to create AI provider",
|
"aiProviderErrorCreate": "Failed to create AI provider",
|
||||||
"aiProviderErrorUpdate": "Failed to update AI provider",
|
"aiProviderErrorUpdate": "Failed to update AI provider",
|
||||||
"aiProviderErrorDelete": "Failed to delete AI provider",
|
"aiProviderErrorDelete": "Failed to delete AI provider",
|
||||||
@@ -1868,11 +1868,11 @@
|
|||||||
"aiProviderModelsAllow": "Allow List",
|
"aiProviderModelsAllow": "Allow List",
|
||||||
"aiProviderModelsAllowDescription": "Models that may be used through this provider. Empty means deny all.",
|
"aiProviderModelsAllowDescription": "Models that may be used through this provider. Empty means deny all.",
|
||||||
"aiProviderModelsAllowPlaceholder": "Enter model key",
|
"aiProviderModelsAllowPlaceholder": "Enter model key",
|
||||||
"aiProviderModelsAllowEmpty": "No models allowed. All requests will be denied.",
|
"aiProviderModelsAllowEmpty": "No models added yet.",
|
||||||
"aiProviderModelsBlock": "Block List",
|
"aiProviderModelsBlock": "Block List",
|
||||||
"aiProviderModelsBlockDescription": "Models to deny even if they match an allow entry.",
|
"aiProviderModelsBlockDescription": "Models to deny even if they match an allow entry.",
|
||||||
"aiProviderModelsBlockPlaceholder": "Enter model key",
|
"aiProviderModelsBlockPlaceholder": "Enter model key",
|
||||||
"aiProviderModelsBlockEmpty": "No blocked models.",
|
"aiProviderModelsBlockEmpty": "No models added yet.",
|
||||||
"aiProviderModelsAdd": "Add Models",
|
"aiProviderModelsAdd": "Add Models",
|
||||||
"aiProviderModelsClearAll": "Clear All",
|
"aiProviderModelsClearAll": "Clear All",
|
||||||
"aiProviderModelsAddCustom": "Add \"{key}\"",
|
"aiProviderModelsAddCustom": "Add \"{key}\"",
|
||||||
@@ -1938,7 +1938,7 @@
|
|||||||
"aiResourceProviderAllowModelsEmpty": "No models found",
|
"aiResourceProviderAllowModelsEmpty": "No models found",
|
||||||
"aiResourceProviderAllowModelsHelp": "Only models from this provider's allow list can be selected.",
|
"aiResourceProviderAllowModelsHelp": "Only models from this provider's allow list can be selected.",
|
||||||
"aiResourceAliasRequired": "Alias is required for inference resources",
|
"aiResourceAliasRequired": "Alias is required for inference resources",
|
||||||
"aiResourceDomainConfiguration": "Domain configuration",
|
"aiResourceDomainConfiguration": "Domain Configuration",
|
||||||
"aiResourceDomainConfigurationDescription": "Choose the domain clients will use to reach this inference resource.",
|
"aiResourceDomainConfigurationDescription": "Choose the domain clients will use to reach this inference resource.",
|
||||||
"aiUsageAnalyticsTitle": "AI Usage Analytics",
|
"aiUsageAnalyticsTitle": "AI Usage Analytics",
|
||||||
"aiUsageAnalyticsDescription": "Analyze AI gateway cost, token usage, and activity across providers, resources, roles, and users",
|
"aiUsageAnalyticsDescription": "Analyze AI gateway cost, token usage, and activity across providers, resources, roles, and users",
|
||||||
@@ -3720,9 +3720,9 @@
|
|||||||
"internalResourceFormMultiSiteRoutingHelp": "Selecting multiple sites enables resilient routing and failover for high availability.",
|
"internalResourceFormMultiSiteRoutingHelp": "Selecting multiple sites enables resilient routing and failover for high availability.",
|
||||||
"internalResourceFormMultiSiteRoutingHelpLearnMore": "Learn more",
|
"internalResourceFormMultiSiteRoutingHelpLearnMore": "Learn more",
|
||||||
"editInternalResourceDialogPortRestrictionsDescription": "Restrict access to specific TCP/UDP ports or allow/block all ports.",
|
"editInternalResourceDialogPortRestrictionsDescription": "Restrict access to specific TCP/UDP ports or allow/block all ports.",
|
||||||
"createInternalResourceDialogHttpConfiguration": "HTTP configuration",
|
"createInternalResourceDialogHttpConfiguration": "Domain Configuration",
|
||||||
"createInternalResourceDialogHttpConfigurationDescription": "Choose the domain clients will use to reach this resource over HTTP or HTTPS.",
|
"createInternalResourceDialogHttpConfigurationDescription": "Choose the domain clients will use to reach this resource over HTTP or HTTPS.",
|
||||||
"editInternalResourceDialogHttpConfiguration": "HTTP configuration",
|
"editInternalResourceDialogHttpConfiguration": "Domain Configuration",
|
||||||
"editInternalResourceDialogHttpConfigurationDescription": "Choose the domain clients will use to reach this resource over HTTP or HTTPS.",
|
"editInternalResourceDialogHttpConfigurationDescription": "Choose the domain clients will use to reach this resource over HTTP or HTTPS.",
|
||||||
"editInternalResourceDialogTcp": "TCP",
|
"editInternalResourceDialogTcp": "TCP",
|
||||||
"editInternalResourceDialogUdp": "UDP",
|
"editInternalResourceDialogUdp": "UDP",
|
||||||
|
|||||||
@@ -271,14 +271,14 @@ export default function CreateAiProviderPage() {
|
|||||||
value
|
value
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
const currentName =
|
||||||
|
form.getValues(
|
||||||
|
"name"
|
||||||
|
);
|
||||||
if (
|
if (
|
||||||
value !==
|
value !==
|
||||||
"custom"
|
"custom"
|
||||||
) {
|
) {
|
||||||
const currentName =
|
|
||||||
form.getValues(
|
|
||||||
"name"
|
|
||||||
);
|
|
||||||
const previousLabel =
|
const previousLabel =
|
||||||
t(
|
t(
|
||||||
aiProviderTypeLabelMap[
|
aiProviderTypeLabelMap[
|
||||||
@@ -305,6 +305,30 @@ export default function CreateAiProviderPage() {
|
|||||||
);
|
);
|
||||||
targetsRef.current =
|
targetsRef.current =
|
||||||
[];
|
[];
|
||||||
|
} else {
|
||||||
|
const isDefaultName =
|
||||||
|
Object.entries(
|
||||||
|
aiProviderTypeLabelMap
|
||||||
|
).some(
|
||||||
|
([
|
||||||
|
type,
|
||||||
|
key
|
||||||
|
]) =>
|
||||||
|
type !==
|
||||||
|
"custom" &&
|
||||||
|
currentName ===
|
||||||
|
t(
|
||||||
|
key
|
||||||
|
)
|
||||||
|
);
|
||||||
|
if (
|
||||||
|
isDefaultName
|
||||||
|
) {
|
||||||
|
form.setValue(
|
||||||
|
"name",
|
||||||
|
""
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -86,6 +86,13 @@ export type ProxyResourceTargetsFormHandle = {
|
|||||||
save: (options?: { silent?: boolean }) => Promise<boolean>;
|
save: (options?: { silent?: boolean }) => Promise<boolean>;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const DEFAULT_ALLOWED_METHODS: ("http" | "https" | "h2c")[] = [
|
||||||
|
"http",
|
||||||
|
"https",
|
||||||
|
"h2c"
|
||||||
|
];
|
||||||
|
const EMPTY_TARGETS: LocalTarget[] = [];
|
||||||
|
|
||||||
type ProxyResourceTargetsFormProps = {
|
type ProxyResourceTargetsFormProps = {
|
||||||
orgId: string;
|
orgId: string;
|
||||||
isHttp: boolean;
|
isHttp: boolean;
|
||||||
@@ -115,12 +122,12 @@ export const ProxyResourceTargetsForm = forwardRef<
|
|||||||
{
|
{
|
||||||
orgId,
|
orgId,
|
||||||
isHttp,
|
isHttp,
|
||||||
initialTargets = [],
|
initialTargets = EMPTY_TARGETS,
|
||||||
resource,
|
resource,
|
||||||
providerId,
|
providerId,
|
||||||
updateResource,
|
updateResource,
|
||||||
onChange,
|
onChange,
|
||||||
allowedMethods = ["http", "https", "h2c"],
|
allowedMethods = DEFAULT_ALLOWED_METHODS,
|
||||||
emptyMessage,
|
emptyMessage,
|
||||||
embedded = false,
|
embedded = false,
|
||||||
hideSaveButton = false,
|
hideSaveButton = false,
|
||||||
@@ -266,7 +273,7 @@ export const ProxyResourceTargetsForm = forwardRef<
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
[sites]
|
[]
|
||||||
);
|
);
|
||||||
|
|
||||||
const openHealthCheckDialog = useCallback((target: LocalTarget) => {
|
const openHealthCheckDialog = useCallback((target: LocalTarget) => {
|
||||||
@@ -616,7 +623,6 @@ export const ProxyResourceTargetsForm = forwardRef<
|
|||||||
}, [
|
}, [
|
||||||
isAdvancedMode,
|
isAdvancedMode,
|
||||||
isHttp,
|
isHttp,
|
||||||
sites,
|
|
||||||
updateTarget,
|
updateTarget,
|
||||||
getDockerStateForSite,
|
getDockerStateForSite,
|
||||||
refreshContainersForSite,
|
refreshContainersForSite,
|
||||||
|
|||||||
@@ -350,28 +350,24 @@ export function AiProviderModelListEditor({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col gap-3">
|
<div className="flex flex-col gap-3">
|
||||||
<div>
|
{items.length === 0 ? (
|
||||||
<div className="relative">
|
<p className="text-sm text-muted-foreground">{emptyMessage}</p>
|
||||||
<div
|
) : (
|
||||||
ref={gridRef}
|
<div>
|
||||||
className={cn(
|
<div className="relative">
|
||||||
"grid grid-cols-1 gap-2 sm:grid-cols-2 xl:grid-cols-3",
|
<div
|
||||||
isCollapsed && "overflow-hidden"
|
ref={gridRef}
|
||||||
)}
|
className={cn(
|
||||||
style={
|
"grid grid-cols-1 gap-2 sm:grid-cols-2 xl:grid-cols-3",
|
||||||
isCollapsed && clipHeight != null
|
isCollapsed && "overflow-hidden"
|
||||||
? { maxHeight: clipHeight }
|
)}
|
||||||
: undefined
|
style={
|
||||||
}
|
isCollapsed && clipHeight != null
|
||||||
>
|
? { maxHeight: clipHeight }
|
||||||
{items.length === 0 ? (
|
: undefined
|
||||||
<div className="flex min-w-0 items-center justify-center rounded-md border border-dashed border-input px-2.5 py-2">
|
}
|
||||||
<span className="text-xs text-muted-foreground">
|
>
|
||||||
{emptyMessage}
|
{items.map((item) => (
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
items.map((item) => (
|
|
||||||
<ModelCard
|
<ModelCard
|
||||||
key={item.clientId}
|
key={item.clientId}
|
||||||
item={item}
|
item={item}
|
||||||
@@ -385,42 +381,42 @@ export function AiProviderModelListEditor({
|
|||||||
}
|
}
|
||||||
onRemove={() => removeModel(item.clientId)}
|
onRemove={() => removeModel(item.clientId)}
|
||||||
/>
|
/>
|
||||||
))
|
))}
|
||||||
)}
|
</div>
|
||||||
|
{isCollapsed ? (
|
||||||
|
<div className="pointer-events-none absolute inset-x-0 bottom-0 h-14 bg-gradient-to-t from-card from-25% via-card/80 to-transparent" />
|
||||||
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
{isCollapsed ? (
|
{isCollapsed ? (
|
||||||
<div className="pointer-events-none absolute inset-x-0 bottom-0 h-14 bg-gradient-to-t from-card from-25% via-card/80 to-transparent" />
|
<div className="relative z-10 flex justify-center pt-2">
|
||||||
|
<Button
|
||||||
|
type="button"
|
||||||
|
variant="text"
|
||||||
|
size="sm"
|
||||||
|
className="bg-card px-2 text-muted-foreground hover:text-foreground"
|
||||||
|
onClick={() => setListExpanded(true)}
|
||||||
|
>
|
||||||
|
{t("aiProviderModelsViewMore", {
|
||||||
|
count: items.length - collapsedLimit
|
||||||
|
})}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
{hasOverflow && listExpanded ? (
|
||||||
|
<div className="flex justify-center pt-1">
|
||||||
|
<Button
|
||||||
|
type="button"
|
||||||
|
variant="text"
|
||||||
|
size="sm"
|
||||||
|
className="text-muted-foreground hover:text-foreground"
|
||||||
|
onClick={() => setListExpanded(false)}
|
||||||
|
>
|
||||||
|
{t("aiProviderModelsViewLess")}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
{isCollapsed ? (
|
)}
|
||||||
<div className="relative z-10 flex justify-center pt-2">
|
|
||||||
<Button
|
|
||||||
type="button"
|
|
||||||
variant="text"
|
|
||||||
size="sm"
|
|
||||||
className="bg-card px-2 text-muted-foreground hover:text-foreground"
|
|
||||||
onClick={() => setListExpanded(true)}
|
|
||||||
>
|
|
||||||
{t("aiProviderModelsViewMore", {
|
|
||||||
count: items.length - collapsedLimit
|
|
||||||
})}
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
) : null}
|
|
||||||
{hasOverflow && listExpanded ? (
|
|
||||||
<div className="flex justify-center pt-1">
|
|
||||||
<Button
|
|
||||||
type="button"
|
|
||||||
variant="text"
|
|
||||||
size="sm"
|
|
||||||
className="text-muted-foreground hover:text-foreground"
|
|
||||||
onClick={() => setListExpanded(false)}
|
|
||||||
>
|
|
||||||
{t("aiProviderModelsViewLess")}
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
) : null}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="flex flex-wrap items-center gap-2">
|
<div className="flex flex-wrap items-center gap-2">
|
||||||
<Popover
|
<Popover
|
||||||
@@ -828,9 +824,7 @@ function EditModelCredenza({
|
|||||||
defaultValues: { modelKey: item.modelKey }
|
defaultValues: { modelKey: item.modelKey }
|
||||||
});
|
});
|
||||||
|
|
||||||
const [pendingBudgetRows, setPendingBudgetRows] = useState<BudgetRow[]>(
|
const [pendingBudgetRows, setPendingBudgetRows] = useState<BudgetRow[]>([]);
|
||||||
[]
|
|
||||||
);
|
|
||||||
const [attemptedBudgetsSave, setAttemptedBudgetsSave] = useState(false);
|
const [attemptedBudgetsSave, setAttemptedBudgetsSave] = useState(false);
|
||||||
const [savingBudgets, setSavingBudgets] = useState(false);
|
const [savingBudgets, setSavingBudgets] = useState(false);
|
||||||
|
|
||||||
@@ -929,9 +923,7 @@ function EditModelCredenza({
|
|||||||
items={[
|
items={[
|
||||||
{ title: t("general"), href: "#" },
|
{ title: t("general"), href: "#" },
|
||||||
{
|
{
|
||||||
title: t(
|
title: t("aiProviderModelsBudgetTab"),
|
||||||
"aiProviderModelsBudgetTab"
|
|
||||||
),
|
|
||||||
href: "#"
|
href: "#"
|
||||||
}
|
}
|
||||||
]}
|
]}
|
||||||
@@ -970,9 +962,7 @@ function EditModelCredenza({
|
|||||||
</p>
|
</p>
|
||||||
<BudgetRowsFields
|
<BudgetRowsFields
|
||||||
rows={pendingBudgetRows}
|
rows={pendingBudgetRows}
|
||||||
onChange={
|
onChange={setPendingBudgetRows}
|
||||||
setPendingBudgetRows
|
|
||||||
}
|
|
||||||
disabled={
|
disabled={
|
||||||
budgetsQuery.isLoading ||
|
budgetsQuery.isLoading ||
|
||||||
savingBudgets
|
savingBudgets
|
||||||
@@ -984,9 +974,7 @@ function EditModelCredenza({
|
|||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
<p className="text-sm text-muted-foreground">
|
<p className="text-sm text-muted-foreground">
|
||||||
{t(
|
{t("aiProviderModelsBudgetUnsaved")}
|
||||||
"aiProviderModelsBudgetUnsaved"
|
|
||||||
)}
|
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -573,7 +573,6 @@ export function PolicyAuthStackSectionEdit({
|
|||||||
className="text-primary hover:underline"
|
className="text-primary hover:underline"
|
||||||
>
|
>
|
||||||
{inferenceResourceUrl}
|
{inferenceResourceUrl}
|
||||||
<ExternalLink className="ml-1 inline size-3.5 shrink-0 align-text-bottom" />
|
|
||||||
</a>
|
</a>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -142,6 +142,8 @@ export function ResourceTargetAddressItem({
|
|||||||
isHttp,
|
isHttp,
|
||||||
allowedMethods = ["http", "https", "h2c"]
|
allowedMethods = ["http", "https", "h2c"]
|
||||||
}: ResourceTargetAddressItemProps) {
|
}: ResourceTargetAddressItemProps) {
|
||||||
|
"use no memo";
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex items-center w-full" key={proxyTarget.targetId}>
|
<div className="flex items-center w-full" key={proxyTarget.targetId}>
|
||||||
<div className="flex items-center w-full justify-start py-0 space-x-2 px-0 cursor-default border border-input rounded-md">
|
<div className="flex items-center w-full justify-start py-0 space-x-2 px-0 cursor-default border border-input rounded-md">
|
||||||
|
|||||||
Reference in New Issue
Block a user