diff --git a/portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/Endpoints/AIEndpointAuth.jsx b/portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/Endpoints/AIEndpointAuth.jsx
deleted file mode 100644
index 6b8fdf7d8f8..00000000000
--- a/portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/Endpoints/AIEndpointAuth.jsx
+++ /dev/null
@@ -1,181 +0,0 @@
-/*
- * Copyright (c) 2024, WSO2 LLC. (http://www.wso2.org) All Rights Reserved.
- *
- * WSO2 LLC. licenses this file to you under the Apache License,
- * Version 2.0 (the "License"); you may not use this file except
- * in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import React, { useEffect, useState } from 'react';
-import PropTypes from 'prop-types';
-import { isRestricted } from 'AppData/AuthManager';
-import { FormattedMessage, useIntl } from 'react-intl';
-import { Icon, TextField, InputAdornment, IconButton, Grid } from '@mui/material';
-import CONSTS from 'AppData/Constants';
-
-/**
- * AI Endpoint Auth component
- * @param {*} props properties
- * @returns {JSX} AI Endpoint Auth component
- */
-export default function AIEndpointAuth(props) {
- const { api, endpoint, apiKeyParamConfig, isProduction, saveEndpointSecurityConfig } = props;
- const intl = useIntl();
-
- const [apiKeyIdentifier] = useState(apiKeyParamConfig.authHeader || apiKeyParamConfig.authQueryParam);
- const [apiKeyIdentifierType] = useState(apiKeyParamConfig.authHeader ? 'HEADER' : 'QUERY_PARAMETER');
-
- const [apiKeyValue, setApiKeyValue] = useState(null);
- const [isHeaderParameter] = useState(!!apiKeyParamConfig.authHeader);
- const [showApiKey, setShowApiKey] = useState(false);
-
- const subtypeConfig = api.subtypeConfiguration && JSON.parse(api.subtypeConfiguration.configuration);
- const llmProviderName = subtypeConfig ? subtypeConfig.llmProviderName : null;
-
- useEffect(() => {
- setApiKeyValue(
- endpoint.endpointConfig?.endpoint_security?.[isProduction ? 'production' : 'sandbox']?.apiKeyValue === ''
- ? '********'
- : null
- );
- }, []);
-
- // useEffect(() => {
- // let newApiKeyValue = endpoint.endpointConfig?.endpoint_security?.[isProduction ?
- // 'production' : 'sandbox']?.apiKeyValue === '' ? '' : null;
-
- // if ((llmProviderName === 'MistralAI' || llmProviderName === 'OpenAI') &&
- // newApiKeyValue != null && newApiKeyValue !== '') {
- // newApiKeyValue = `Bearer ${newApiKeyValue}`;
- // }
-
- // saveEndpointSecurityConfig({
- // ...CONSTS.DEFAULT_ENDPOINT_SECURITY,
- // type: 'apikey',
- // apiKeyIdentifier,
- // apiKeyIdentifierType,
- // apiKeyValue: newApiKeyValue,
- // enabled: true,
- // }, isProduction ? 'production' : 'sandbox');
- // }, []);
-
- const handleApiKeyChange = (event) => {
- let apiKeyVal = event.target.value;
- if (apiKeyVal === '********') {
- apiKeyVal = '';
- } else if (apiKeyVal === '') {
- apiKeyVal = null;
- } else if (apiKeyVal.includes('********')) {
- apiKeyVal = apiKeyVal.replace('********', '');
- }
- setApiKeyValue(apiKeyVal);
- };
-
- const handleApiKeyBlur = () => {
- let updatedApiKeyValue = apiKeyValue;
- if ((llmProviderName === 'MistralAI' || llmProviderName === 'OpenAI') &&
- apiKeyValue !== null && apiKeyValue !== '') {
- updatedApiKeyValue = `Bearer ${updatedApiKeyValue}`;
- }
-
- saveEndpointSecurityConfig({
- ...CONSTS.DEFAULT_ENDPOINT_SECURITY,
- type: 'apikey',
- apiKeyIdentifier,
- apiKeyIdentifierType,
- apiKeyValue: updatedApiKeyValue,
- enabled: true,
- }, isProduction ? 'production' : 'sandbox');
- };
-
- const handleToggleApiKeyVisibility = () => {
- if (apiKeyValue !== '********') {
- setShowApiKey((prev) => !prev);
- }
- };
-
- return (
- <>
-
-
- ) : (
-
- )}
- id={'api-key-id-' + endpoint.id}
- value={apiKeyIdentifier}
- placeholder={apiKeyIdentifier}
- helperText=' '
- sx={{ width: '100%' }}
- variant='outlined'
- margin='normal'
- required
- />
-
-
- }
- id={'api-key-value' + endpoint.id}
- value={apiKeyValue}
- placeholder={intl.formatMessage({
- id: 'Apis.Details.Endpoints.Security.api.key.value.placeholder',
- defaultMessage: 'Enter API Key',
- })}
- sx={{ width: '100%' }}
- onChange={handleApiKeyChange}
- onBlur={handleApiKeyBlur}
- error={!apiKeyValue}
- helperText={!apiKeyValue ? (
-
- ) : ' '}
- variant='outlined'
- margin='normal'
- required
- type={showApiKey ? 'text' : 'password'}
- InputProps={{
- endAdornment: (
-
-
-
- {showApiKey ? 'visibility' : 'visibility_off'}
-
-
-
- ),
- }}
- />
-
- >
- );
-}
-
-AIEndpointAuth.propTypes = {
- api: PropTypes.shape({}).isRequired,
- saveEndpointSecurityConfig: PropTypes.func.isRequired,
- isProduction: PropTypes.bool.isRequired,
-};
diff --git a/portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/Endpoints/AIEndpoints/AIEndpoints.jsx b/portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/Endpoints/AIEndpoints/AIEndpoints.jsx
index bca8cd56875..b72aac5dbcd 100644
--- a/portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/Endpoints/AIEndpoints/AIEndpoints.jsx
+++ b/portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/Endpoints/AIEndpoints/AIEndpoints.jsx
@@ -238,18 +238,27 @@ const AIEndpoints = ({
defaultMessage='Production Endpoints'
/>
- {productionEndpoints.map((endpoint) => (
-
- ))}
+ {productionEndpoints.length > 0 ? (
+ productionEndpoints.map((endpoint) => (
+
+ ))
+ ) : (
+
+
+
+ )}
@@ -260,18 +269,27 @@ const AIEndpoints = ({
defaultMessage='Sandbox Endpoints'
/>
- {sandboxEndpoints.map((endpoint) => (
-
- ))}
+ {sandboxEndpoints.length > 0 ? (
+ sandboxEndpoints.map((endpoint) => (
+
+ ))
+ ) : (
+
+
+
+ )}
diff --git a/portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/Endpoints/index.jsx b/portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/Endpoints/index.jsx
index cf46a2cd93a..855ee511836 100644
--- a/portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/Endpoints/index.jsx
+++ b/portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/Endpoints/index.jsx
@@ -35,19 +35,19 @@ const Endpoint = () => {
path={'/' + urlPrefix + '/:api_uuid/endpoints/'}
component={() => }
/>
+ {!isRestricted(['apim:api_manage']) && (
+ }
+ />
+ )}
{!isRestricted(['apim:api_view', 'apim:api_manage']) && (
- <>
- }
- />
- }
- />
- >
+ }
+ />
)}
diff --git a/portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/Policies/CustomPolicies/ModelCard.tsx b/portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/Policies/CustomPolicies/ModelCard.tsx
index 102b4a7d887..e937c59a3fa 100644
--- a/portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/Policies/CustomPolicies/ModelCard.tsx
+++ b/portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/Policies/CustomPolicies/ModelCard.tsx
@@ -25,8 +25,8 @@ import InputLabel from '@mui/material/InputLabel';
import MenuItem from '@mui/material/MenuItem';
import FormControl from '@mui/material/FormControl';
import Select from '@mui/material/Select';
-import Button from '@mui/material/Button';
-import { Paper } from '@mui/material';
+import { Paper, IconButton } from '@mui/material';
+import DeleteIcon from '@mui/icons-material/Delete';
import { Endpoint, ModelData } from './Types';
interface ModelCardProps {
@@ -57,9 +57,9 @@ const ModelCard: FC = ({
return (
<>
-
+
-
+
= ({
-
+
= ({
{isWeightApplicable && (
- handleChange(e)}
- fullWidth
- />
+
+ handleChange(e)}
+ fullWidth
+ />
+
)}
-
- {onDelete && (
-
- )}
-
+
+
+
+ )}
>
);
diff --git a/portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/Policies/CustomPolicies/ModelFailover.tsx b/portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/Policies/CustomPolicies/ModelFailover.tsx
index 65e3a81157e..20dc41888c0 100644
--- a/portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/Policies/CustomPolicies/ModelFailover.tsx
+++ b/portals/publisher/src/main/webapp/source/src/app/components/Apis/Details/Policies/CustomPolicies/ModelFailover.tsx
@@ -24,7 +24,6 @@ import Grid from '@mui/material/Grid';
import Accordion from '@mui/material/Accordion';
import AccordionSummary from '@mui/material/AccordionSummary';
import AccordionDetails from '@mui/material/AccordionDetails';
-import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
import Button from '@mui/material/Button';
import AddCircle from '@mui/icons-material/AddCircle';
import API from 'AppData/api';
@@ -32,6 +31,11 @@ import { Progress } from 'AppComponents/Shared';
import { useAPI } from 'AppComponents/Apis/Details/components/ApiContext';
import { Endpoint, ModelData } from './Types';
import ModelCard from './ModelCard';
+import { styled } from '@mui/material/styles';
+import Alert from '@mui/material/Alert';
+import { Link } from 'react-router-dom';
+import Switch from '@mui/material/Switch';
+import FormControlLabel from '@mui/material/FormControlLabel';
interface ModelConfig {
targetModel: ModelData;
@@ -50,6 +54,24 @@ interface ModelFailoverProps {
manualPolicyConfig: string;
}
+const StyledAccordionSummary = styled(AccordionSummary)(({ theme }) => ({
+ minHeight: 48,
+ maxHeight: 48,
+ '&.Mui-expanded': {
+ minHeight: 48,
+ maxHeight: 48,
+ },
+ '& .MuiAccordionSummary-content': {
+ margin: 0,
+ display: 'flex',
+ justifyContent: 'space-between',
+ alignItems: 'center',
+ '&.Mui-expanded': {
+ margin: 0,
+ }
+ }
+}));
+
const ModelFailover: FC = ({
setManualPolicyConfig,
manualPolicyConfig,
@@ -77,6 +99,8 @@ const ModelFailover: FC = ({
const [productionEndpoints, setProductionEndpoints] = useState([]);
const [sandboxEndpoints, setSandboxEndpoints] = useState([]);
const [loading, setLoading] = useState(false);
+ const [productionEnabled, setProductionEnabled] = useState(false);
+ const [sandboxEnabled, setSandboxEnabled] = useState(false);
const fetchEndpoints = () => {
setLoading(true);
@@ -84,10 +108,37 @@ const ModelFailover: FC = ({
endpointsPromise
.then((response) => {
const endpoints = response.body.list;
+ const defaultEndpoints = [];
+
+ if (apiFromContext.endpointConfig?.production_endpoints) {
+ defaultEndpoints.push({
+ id: `${apiFromContext.id}--PRODUCTION`,
+ name: 'Default Production Endpoint',
+ deploymentStage: 'PRODUCTION',
+ endpointConfig: {
+ production_endpoints: apiFromContext.endpointConfig.production_endpoints,
+ endpoint_security: apiFromContext.endpointConfig.endpoint_security
+ }
+ });
+ }
+
+ if (apiFromContext.endpointConfig?.sandbox_endpoints) {
+ defaultEndpoints.push({
+ id: `${apiFromContext.id}--SANDBOX`,
+ name: 'Default Sandbox Endpoint',
+ deploymentStage: 'SANDBOX',
+ endpointConfig: {
+ sandbox_endpoints: apiFromContext.endpointConfig.sandbox_endpoints,
+ endpoint_security: apiFromContext.endpointConfig.endpoint_security
+ }
+ });
+ }
+
+ const allEndpoints = [...defaultEndpoints, ...endpoints];
// Filter endpoints based on endpoint type
- const prodEndpointList = endpoints.filter((endpoint: Endpoint) => endpoint.deploymentStage === 'PRODUCTION');
- const sandEndpointList = endpoints.filter((endpoint: Endpoint) => endpoint.deploymentStage === 'SANDBOX');
+ const prodEndpointList = allEndpoints.filter((endpoint: Endpoint) => endpoint.deploymentStage === 'PRODUCTION');
+ const sandEndpointList = allEndpoints.filter((endpoint: Endpoint) => endpoint.deploymentStage === 'SANDBOX');
setProductionEndpoints(prodEndpointList);
setSandboxEndpoints(sandEndpointList);
@@ -115,7 +166,17 @@ const ModelFailover: FC = ({
useEffect(() => {
if (manualPolicyConfig !== '') {
- setConfig(JSON.parse(manualPolicyConfig.replace(/'/g, '"')));
+ const parsedConfig = JSON.parse(manualPolicyConfig.replace(/'/g, '"'));
+ setConfig(parsedConfig);
+
+ // Set toggle states based on whether there's any configuration
+ const hasProductionConfig = parsedConfig.production.targetModel.model !== ''
+ || parsedConfig.production.fallbackModels.length > 0;
+ const hasSandboxConfig = parsedConfig.sandbox.targetModel.model !== ''
+ || parsedConfig.sandbox.fallbackModels.length > 0;
+
+ setProductionEnabled(hasProductionConfig);
+ setSandboxEnabled(hasSandboxConfig);
}
}, [manualPolicyConfig]);
@@ -168,6 +229,57 @@ const ModelFailover: FC = ({
}));
}
+ const isAddModelDisabled = (env: 'production' | 'sandbox') => {
+ if (modelList.length === 0) {
+ return true;
+ }
+ return env === 'production' ? productionEndpoints.length === 0 : sandboxEndpoints.length === 0;
+ };
+
+ const getEndpointsUrl = () => {
+ return `/apis/${apiFromContext.id}/endpoints`;
+ };
+
+ const handleProductionToggle = (event: React.ChangeEvent) => {
+ setProductionEnabled(event.target.checked);
+ if (!event.target.checked) {
+ setConfig(prev => ({
+ ...prev,
+ production: {
+ targetModel: {
+ model: '',
+ endpointId: '',
+ },
+ fallbackModels: [],
+ },
+ }));
+ }
+ };
+
+ const handleSandboxToggle = (event: React.ChangeEvent) => {
+ setSandboxEnabled(event.target.checked);
+ if (!event.target.checked) {
+ setConfig(prev => ({
+ ...prev,
+ sandbox: {
+ targetModel: {
+ model: '',
+ endpointId: '',
+ },
+ fallbackModels: [],
+ },
+ }));
+ }
+ };
+
+ const handleAccordionChange = (env: 'production' | 'sandbox') => (event: React.SyntheticEvent, expanded: boolean) => {
+ if (env === 'production') {
+ handleProductionToggle({ target: { checked: expanded } } as React.ChangeEvent);
+ } else {
+ handleSandboxToggle({ target: { checked: expanded } } as React.ChangeEvent);
+ }
+ };
+
if (loading) {
return ;
}
@@ -175,20 +287,61 @@ const ModelFailover: FC = ({
return (
<>
-
- }
+
+
+ {modelList.length === 0 && (
+
+
+
+ )}
+ {productionEndpoints.length === 0 && (
+
+
+ configure endpoints
+
+ ),
+ }}
+ />
+
+ )}
+
+
+
= ({
isWeightApplicable={false}
onUpdate={(updatedTargetModel) => handleTargetModelUpdate('production', 0, updatedTargetModel)}
/>
+
+
+
-
- }
+
+
@@ -234,21 +396,67 @@ const ModelFailover: FC = ({
defaultMessage='Sandbox'
/>
-
+
+ }
+ label=""
+ />
+
+ {modelList.length === 0 && (
+
+
+
+ )}
+ {sandboxEndpoints.length === 0 && (
+
+
+ configure endpoints
+
+ ),
+ }}
+ />
+
+ )}
+
+
+
handleTargetModelUpdate('sandbox', 0, updatedTargetModel)}
/>
+
+
+