mirror of
https://github.com/fosrl/pangolin.git
synced 2026-08-14 00:09:55 +02:00
various ui improvements
This commit is contained in:
+8
-8
@@ -864,9 +864,9 @@
|
||||
"policyAuthMethodOff": "Off",
|
||||
"policyAuthSsoTitle": "Platform SSO",
|
||||
"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",
|
||||
"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.",
|
||||
"policyAuthInferenceSsoDescription": "Selected users and roles can authenticate to the gateway using their identity API key.",
|
||||
"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",
|
||||
"policyAuthSsoDefaultIdp": "Default provider",
|
||||
"policyAuthAddDefaultIdentityProvider": "Add Default Identity Provider",
|
||||
@@ -1821,7 +1821,7 @@
|
||||
"aiProviderBudgetUnitUsd": "USD",
|
||||
"aiProviderBudgetUnitTokens": "Tokens",
|
||||
"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",
|
||||
"aiProviderErrorUpdate": "Failed to update AI provider",
|
||||
"aiProviderErrorDelete": "Failed to delete AI provider",
|
||||
@@ -1868,11 +1868,11 @@
|
||||
"aiProviderModelsAllow": "Allow List",
|
||||
"aiProviderModelsAllowDescription": "Models that may be used through this provider. Empty means deny all.",
|
||||
"aiProviderModelsAllowPlaceholder": "Enter model key",
|
||||
"aiProviderModelsAllowEmpty": "No models allowed. All requests will be denied.",
|
||||
"aiProviderModelsAllowEmpty": "No models added yet.",
|
||||
"aiProviderModelsBlock": "Block List",
|
||||
"aiProviderModelsBlockDescription": "Models to deny even if they match an allow entry.",
|
||||
"aiProviderModelsBlockPlaceholder": "Enter model key",
|
||||
"aiProviderModelsBlockEmpty": "No blocked models.",
|
||||
"aiProviderModelsBlockEmpty": "No models added yet.",
|
||||
"aiProviderModelsAdd": "Add Models",
|
||||
"aiProviderModelsClearAll": "Clear All",
|
||||
"aiProviderModelsAddCustom": "Add \"{key}\"",
|
||||
@@ -1938,7 +1938,7 @@
|
||||
"aiResourceProviderAllowModelsEmpty": "No models found",
|
||||
"aiResourceProviderAllowModelsHelp": "Only models from this provider's allow list can be selected.",
|
||||
"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.",
|
||||
"aiUsageAnalyticsTitle": "AI Usage Analytics",
|
||||
"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.",
|
||||
"internalResourceFormMultiSiteRoutingHelpLearnMore": "Learn more",
|
||||
"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.",
|
||||
"editInternalResourceDialogHttpConfiguration": "HTTP configuration",
|
||||
"editInternalResourceDialogHttpConfiguration": "Domain Configuration",
|
||||
"editInternalResourceDialogHttpConfigurationDescription": "Choose the domain clients will use to reach this resource over HTTP or HTTPS.",
|
||||
"editInternalResourceDialogTcp": "TCP",
|
||||
"editInternalResourceDialogUdp": "UDP",
|
||||
|
||||
@@ -271,14 +271,14 @@ export default function CreateAiProviderPage() {
|
||||
value
|
||||
)
|
||||
);
|
||||
const currentName =
|
||||
form.getValues(
|
||||
"name"
|
||||
);
|
||||
if (
|
||||
value !==
|
||||
"custom"
|
||||
) {
|
||||
const currentName =
|
||||
form.getValues(
|
||||
"name"
|
||||
);
|
||||
const previousLabel =
|
||||
t(
|
||||
aiProviderTypeLabelMap[
|
||||
@@ -305,6 +305,30 @@ export default function CreateAiProviderPage() {
|
||||
);
|
||||
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>;
|
||||
};
|
||||
|
||||
const DEFAULT_ALLOWED_METHODS: ("http" | "https" | "h2c")[] = [
|
||||
"http",
|
||||
"https",
|
||||
"h2c"
|
||||
];
|
||||
const EMPTY_TARGETS: LocalTarget[] = [];
|
||||
|
||||
type ProxyResourceTargetsFormProps = {
|
||||
orgId: string;
|
||||
isHttp: boolean;
|
||||
@@ -115,12 +122,12 @@ export const ProxyResourceTargetsForm = forwardRef<
|
||||
{
|
||||
orgId,
|
||||
isHttp,
|
||||
initialTargets = [],
|
||||
initialTargets = EMPTY_TARGETS,
|
||||
resource,
|
||||
providerId,
|
||||
updateResource,
|
||||
onChange,
|
||||
allowedMethods = ["http", "https", "h2c"],
|
||||
allowedMethods = DEFAULT_ALLOWED_METHODS,
|
||||
emptyMessage,
|
||||
embedded = false,
|
||||
hideSaveButton = false,
|
||||
@@ -266,7 +273,7 @@ export const ProxyResourceTargetsForm = forwardRef<
|
||||
);
|
||||
});
|
||||
},
|
||||
[sites]
|
||||
[]
|
||||
);
|
||||
|
||||
const openHealthCheckDialog = useCallback((target: LocalTarget) => {
|
||||
@@ -616,7 +623,6 @@ export const ProxyResourceTargetsForm = forwardRef<
|
||||
}, [
|
||||
isAdvancedMode,
|
||||
isHttp,
|
||||
sites,
|
||||
updateTarget,
|
||||
getDockerStateForSite,
|
||||
refreshContainersForSite,
|
||||
|
||||
@@ -350,28 +350,24 @@ export function AiProviderModelListEditor({
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-3">
|
||||
<div>
|
||||
<div className="relative">
|
||||
<div
|
||||
ref={gridRef}
|
||||
className={cn(
|
||||
"grid grid-cols-1 gap-2 sm:grid-cols-2 xl:grid-cols-3",
|
||||
isCollapsed && "overflow-hidden"
|
||||
)}
|
||||
style={
|
||||
isCollapsed && clipHeight != null
|
||||
? { maxHeight: clipHeight }
|
||||
: undefined
|
||||
}
|
||||
>
|
||||
{items.length === 0 ? (
|
||||
<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}
|
||||
</span>
|
||||
</div>
|
||||
) : (
|
||||
items.map((item) => (
|
||||
{items.length === 0 ? (
|
||||
<p className="text-sm text-muted-foreground">{emptyMessage}</p>
|
||||
) : (
|
||||
<div>
|
||||
<div className="relative">
|
||||
<div
|
||||
ref={gridRef}
|
||||
className={cn(
|
||||
"grid grid-cols-1 gap-2 sm:grid-cols-2 xl:grid-cols-3",
|
||||
isCollapsed && "overflow-hidden"
|
||||
)}
|
||||
style={
|
||||
isCollapsed && clipHeight != null
|
||||
? { maxHeight: clipHeight }
|
||||
: undefined
|
||||
}
|
||||
>
|
||||
{items.map((item) => (
|
||||
<ModelCard
|
||||
key={item.clientId}
|
||||
item={item}
|
||||
@@ -385,42 +381,42 @@ export function AiProviderModelListEditor({
|
||||
}
|
||||
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>
|
||||
{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}
|
||||
</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">
|
||||
<Popover
|
||||
@@ -828,9 +824,7 @@ function EditModelCredenza({
|
||||
defaultValues: { modelKey: item.modelKey }
|
||||
});
|
||||
|
||||
const [pendingBudgetRows, setPendingBudgetRows] = useState<BudgetRow[]>(
|
||||
[]
|
||||
);
|
||||
const [pendingBudgetRows, setPendingBudgetRows] = useState<BudgetRow[]>([]);
|
||||
const [attemptedBudgetsSave, setAttemptedBudgetsSave] = useState(false);
|
||||
const [savingBudgets, setSavingBudgets] = useState(false);
|
||||
|
||||
@@ -929,9 +923,7 @@ function EditModelCredenza({
|
||||
items={[
|
||||
{ title: t("general"), href: "#" },
|
||||
{
|
||||
title: t(
|
||||
"aiProviderModelsBudgetTab"
|
||||
),
|
||||
title: t("aiProviderModelsBudgetTab"),
|
||||
href: "#"
|
||||
}
|
||||
]}
|
||||
@@ -970,9 +962,7 @@ function EditModelCredenza({
|
||||
</p>
|
||||
<BudgetRowsFields
|
||||
rows={pendingBudgetRows}
|
||||
onChange={
|
||||
setPendingBudgetRows
|
||||
}
|
||||
onChange={setPendingBudgetRows}
|
||||
disabled={
|
||||
budgetsQuery.isLoading ||
|
||||
savingBudgets
|
||||
@@ -984,9 +974,7 @@ function EditModelCredenza({
|
||||
</>
|
||||
) : (
|
||||
<p className="text-sm text-muted-foreground">
|
||||
{t(
|
||||
"aiProviderModelsBudgetUnsaved"
|
||||
)}
|
||||
{t("aiProviderModelsBudgetUnsaved")}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -573,7 +573,6 @@ export function PolicyAuthStackSectionEdit({
|
||||
className="text-primary hover:underline"
|
||||
>
|
||||
{inferenceResourceUrl}
|
||||
<ExternalLink className="ml-1 inline size-3.5 shrink-0 align-text-bottom" />
|
||||
</a>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -142,6 +142,8 @@ export function ResourceTargetAddressItem({
|
||||
isHttp,
|
||||
allowedMethods = ["http", "https", "h2c"]
|
||||
}: ResourceTargetAddressItemProps) {
|
||||
"use no memo";
|
||||
|
||||
return (
|
||||
<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">
|
||||
|
||||
Reference in New Issue
Block a user