diff --git a/gateway-ha/src/main/java/io/trino/gateway/ha/resource/GatewayWebAppResource.java b/gateway-ha/src/main/java/io/trino/gateway/ha/resource/GatewayWebAppResource.java index 7bca14cf5..84723eea3 100644 --- a/gateway-ha/src/main/java/io/trino/gateway/ha/resource/GatewayWebAppResource.java +++ b/gateway-ha/src/main/java/io/trino/gateway/ha/resource/GatewayWebAppResource.java @@ -18,6 +18,8 @@ import io.trino.gateway.ha.clustermonitor.ClusterStats; import io.trino.gateway.ha.config.HaGatewayConfiguration; import io.trino.gateway.ha.config.ProxyBackendConfiguration; +import io.trino.gateway.ha.config.RoutingRulesConfiguration; +import io.trino.gateway.ha.config.RulesType; import io.trino.gateway.ha.config.UIConfiguration; import io.trino.gateway.ha.domain.Result; import io.trino.gateway.ha.domain.RoutingRule; @@ -73,6 +75,8 @@ public class GatewayWebAppResource private final QueryHistoryManager queryHistoryManager; private final BackendStateManager backendStateManager; private final ResourceGroupsManager resourceGroupsManager; + private final boolean isRulesEngineEnabled; + private final RulesType ruleType; // TODO Avoid putting mutable objects in fields private final UIConfiguration uiConfiguration; private final RoutingRulesManager routingRulesManager; @@ -92,6 +96,9 @@ public GatewayWebAppResource( this.resourceGroupsManager = requireNonNull(resourceGroupsManager, "resourceGroupsManager is null"); this.uiConfiguration = configuration.getUiConfiguration(); this.routingRulesManager = requireNonNull(routingRulesManager, "routingRulesManager is null"); + RoutingRulesConfiguration routingRules = configuration.getRoutingRules(); + isRulesEngineEnabled = routingRules.isRulesEngineEnabled(); + ruleType = routingRules.getRulesType(); } @POST @@ -446,6 +453,10 @@ public Response readExactMatchSourceSelector() public Response getRoutingRules() throws IOException { + if (isRulesEngineEnabled && ruleType == RulesType.EXTERNAL) { + return Response.status(Response.Status.NO_CONTENT) + .entity(Result.fail("Routing rules are managed by an external service")).build(); + } List routingRulesList = routingRulesManager.getRoutingRules(); return Response.ok(Result.ok(routingRulesList)).build(); } diff --git a/webapp/src/api/base.ts b/webapp/src/api/base.ts index 47654b782..684e95383 100644 --- a/webapp/src/api/base.ts +++ b/webapp/src/api/base.ts @@ -17,6 +17,14 @@ export class ClientApi { if (res.status === 401 || res.status === 403) { this.authErrorHandler() } + else if (res.status === 204) { + // handle the case of Response.Status.NO_CONTENT when External Routing Service is used + return { isExternalRouting: true }; + } + else if (res.status >= 500) { + const resJson = await res.json(); + this.serverErrorHandler(resJson.msg); + } else if (res.status !== 200) { Toast.error({ content: Locale.Error.Network, @@ -119,6 +127,14 @@ export class ClientApi { accessStore.updateToken(""); throw new Error(Locale.Auth.Expiration); } + serverErrorHandler(msg: string): void { + Toast.error({ + content: msg, + duration: 5, + theme: "light" + }); + throw new Error(msg); + } } export const api = new ClientApi(); diff --git a/webapp/src/components/routing-rules.tsx b/webapp/src/components/routing-rules.tsx index 858e12a1b..7e62ac62b 100644 --- a/webapp/src/components/routing-rules.tsx +++ b/webapp/src/components/routing-rules.tsx @@ -1,7 +1,7 @@ import { useEffect, useState } from "react"; import { routingRulesApi, updateRoutingRulesApi } from "../api/webapp/routing-rules.ts"; import { RoutingRulesData } from "../types/routing-rules"; -import { Button, Card, Form, Toast } from "@douyinfe/semi-ui"; +import { Button, Card, Form, Toast, Spin } from "@douyinfe/semi-ui"; import { FormApi } from "@douyinfe/semi-ui/lib/es/form"; import { Role, useAccessStore } from "../store"; import Locale from "../locales"; @@ -10,6 +10,8 @@ export function RoutingRules() { const [rules, setRules] = useState([]); const [editingStates, setEditingStates] = useState([]); const [formApis, setFormApis] = useState<(FormApi | null)[]>([]); + const [isExternalRouting, setIsExternalRouting] = useState(false); + const [isLoading, setIsLoading] = useState(true); const access = useAccessStore(); useEffect(() => { @@ -17,13 +19,21 @@ export function RoutingRules() { }, []); const fetchRoutingRules = () => { + setIsLoading(true); routingRulesApi() .then(data => { - setRules(data); - setEditingStates(new Array(data.length).fill(false)); - setFormApis(new Array(data.length).fill(null)); + if (data.isExternalRouting) { + setIsExternalRouting(true); + } else { + setRules(data); + setEditingStates(new Array(data.length).fill(false)); + setFormApis(new Array(data.length).fill(null)); + setIsExternalRouting(false); + } }).catch(() => { Toast.error(Locale.RoutingRules.ErrorFetch); + }).finally(() => { + setIsLoading(false); }); }; @@ -80,8 +90,32 @@ export function RoutingRules() { }; return ( -
- {rules.map((rule, index) => ( + <> + {isLoading ? ( +
+ +
+ ) : rules.length === 0 ? ( +
+ {isExternalRouting ? + "No routing rules available. Routing rules are managed by an external service." : + "No routing rules configured. Add rules to manage query routing." + } +
+ ) : ( + rules.map((rule, index) => (
- ))} -
+ )))} + ); }