This is an automated email from the ASF dual-hosted git repository.

pierrejeambrun pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/airflow.git


The following commit(s) were added to refs/heads/main by this push:
     new 4f8cb6e328a AIP84: UI endpoint for config (#44265)
4f8cb6e328a is described below

commit 4f8cb6e328a1f99ef0550eff3b5d2ec94cb3260f
Author: vatsrahul1001 <[email protected]>
AuthorDate: Fri Nov 22 19:38:29 2024 +0530

    AIP84: UI endpoint for config (#44265)
    
    * AIP84:UI endpoint for config
    
    * remove show_trigger_form_if_no_params
    
    * flatten the dict
---
 .../ui/__init__.py => datamodels/ui/config.py}     |  32 +++-
 .../api_fastapi/core_api/openapi/v1-generated.yaml | 106 +++++++++++
 airflow/api_fastapi/core_api/routes/ui/__init__.py |   2 +
 airflow/api_fastapi/core_api/routes/ui/config.py   |  71 ++++++++
 airflow/ui/openapi-gen/queries/common.ts           |  94 +++++-----
 airflow/ui/openapi-gen/queries/prefetch.ts         | 113 ++++++------
 airflow/ui/openapi-gen/queries/queries.ts          | 151 +++++++++-------
 airflow/ui/openapi-gen/queries/suspense.ts         | 151 +++++++++-------
 airflow/ui/openapi-gen/requests/schemas.gen.ts     | 110 ++++++++++++
 airflow/ui/openapi-gen/requests/services.gen.ts    | 153 +++++++++-------
 airflow/ui/openapi-gen/requests/types.gen.ts       | 198 +++++++++++++--------
 .../api_fastapi/core_api/routes/ui/test_config.py  | 115 ++++++++++++
 12 files changed, 919 insertions(+), 377 deletions(-)

diff --git a/airflow/api_fastapi/core_api/routes/ui/__init__.py 
b/airflow/api_fastapi/core_api/datamodels/ui/config.py
similarity index 54%
copy from airflow/api_fastapi/core_api/routes/ui/__init__.py
copy to airflow/api_fastapi/core_api/datamodels/ui/config.py
index b7ebf9c5c46..48a140be7a2 100644
--- a/airflow/api_fastapi/core_api/routes/ui/__init__.py
+++ b/airflow/api_fastapi/core_api/datamodels/ui/config.py
@@ -16,13 +16,29 @@
 # under the License.
 from __future__ import annotations
 
-from airflow.api_fastapi.common.router import AirflowRouter
-from airflow.api_fastapi.core_api.routes.ui.assets import assets_router
-from airflow.api_fastapi.core_api.routes.ui.dags import dags_router
-from airflow.api_fastapi.core_api.routes.ui.dashboard import dashboard_router
+from pydantic import BaseModel
 
-ui_router = AirflowRouter(prefix="/ui")
 
-ui_router.include_router(assets_router)
-ui_router.include_router(dashboard_router)
-ui_router.include_router(dags_router)
+class ConfigResponse(BaseModel):
+    """configuration serializer."""
+
+    navbar_color: str
+    navbar_text_color: str
+    navbar_hover_color: str
+    navbar_text_hover_color: str
+    navbar_logo_text_color: str
+    page_size: int
+    auto_refresh_interval: int
+    default_ui_timezone: str
+    hide_paused_dags_by_default: bool
+    instance_name: str
+    instance_name_has_markup: bool
+    enable_swagger_ui: bool
+    require_confirmation_dag_change: bool
+    default_wrap: bool
+    warn_deployment_exposure: bool
+    audit_view_excluded_events: str
+    audit_view_included_events: str
+    is_k8s: bool
+    test_connection: str
+    state_color_mapping: dict
diff --git a/airflow/api_fastapi/core_api/openapi/v1-generated.yaml 
b/airflow/api_fastapi/core_api/openapi/v1-generated.yaml
index a3035c82208..ffb6b26e9dd 100644
--- a/airflow/api_fastapi/core_api/openapi/v1-generated.yaml
+++ b/airflow/api_fastapi/core_api/openapi/v1-generated.yaml
@@ -174,6 +174,26 @@ paths:
             application/json:
               schema:
                 $ref: '#/components/schemas/HTTPValidationError'
+  /ui/config:
+    get:
+      tags:
+      - Config
+      summary: Get Configs
+      description: Get configs for UI.
+      operationId: get_configs
+      responses:
+        '200':
+          description: Successful Response
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/ConfigResponse'
+        '404':
+          description: Not Found
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/HTTPExceptionResponse'
   /public/assets:
     get:
       tags:
@@ -5268,6 +5288,92 @@ components:
       - value
       title: ConfigOption
       description: Config option.
+    ConfigResponse:
+      properties:
+        navbar_color:
+          type: string
+          title: Navbar Color
+        navbar_text_color:
+          type: string
+          title: Navbar Text Color
+        navbar_hover_color:
+          type: string
+          title: Navbar Hover Color
+        navbar_text_hover_color:
+          type: string
+          title: Navbar Text Hover Color
+        navbar_logo_text_color:
+          type: string
+          title: Navbar Logo Text Color
+        page_size:
+          type: integer
+          title: Page Size
+        auto_refresh_interval:
+          type: integer
+          title: Auto Refresh Interval
+        default_ui_timezone:
+          type: string
+          title: Default Ui Timezone
+        hide_paused_dags_by_default:
+          type: boolean
+          title: Hide Paused Dags By Default
+        instance_name:
+          type: string
+          title: Instance Name
+        instance_name_has_markup:
+          type: boolean
+          title: Instance Name Has Markup
+        enable_swagger_ui:
+          type: boolean
+          title: Enable Swagger Ui
+        require_confirmation_dag_change:
+          type: boolean
+          title: Require Confirmation Dag Change
+        default_wrap:
+          type: boolean
+          title: Default Wrap
+        warn_deployment_exposure:
+          type: boolean
+          title: Warn Deployment Exposure
+        audit_view_excluded_events:
+          type: string
+          title: Audit View Excluded Events
+        audit_view_included_events:
+          type: string
+          title: Audit View Included Events
+        is_k8s:
+          type: boolean
+          title: Is K8S
+        test_connection:
+          type: string
+          title: Test Connection
+        state_color_mapping:
+          type: object
+          title: State Color Mapping
+      type: object
+      required:
+      - navbar_color
+      - navbar_text_color
+      - navbar_hover_color
+      - navbar_text_hover_color
+      - navbar_logo_text_color
+      - page_size
+      - auto_refresh_interval
+      - default_ui_timezone
+      - hide_paused_dags_by_default
+      - instance_name
+      - instance_name_has_markup
+      - enable_swagger_ui
+      - require_confirmation_dag_change
+      - default_wrap
+      - warn_deployment_exposure
+      - audit_view_excluded_events
+      - audit_view_included_events
+      - is_k8s
+      - test_connection
+      - state_color_mapping
+      title: ConfigResponse
+      description: configuration serializer.
     ConfigSection:
       properties:
         name:
diff --git a/airflow/api_fastapi/core_api/routes/ui/__init__.py 
b/airflow/api_fastapi/core_api/routes/ui/__init__.py
index b7ebf9c5c46..156c1219996 100644
--- a/airflow/api_fastapi/core_api/routes/ui/__init__.py
+++ b/airflow/api_fastapi/core_api/routes/ui/__init__.py
@@ -18,6 +18,7 @@ from __future__ import annotations
 
 from airflow.api_fastapi.common.router import AirflowRouter
 from airflow.api_fastapi.core_api.routes.ui.assets import assets_router
+from airflow.api_fastapi.core_api.routes.ui.config import config_router
 from airflow.api_fastapi.core_api.routes.ui.dags import dags_router
 from airflow.api_fastapi.core_api.routes.ui.dashboard import dashboard_router
 
@@ -26,3 +27,4 @@ ui_router = AirflowRouter(prefix="/ui")
 ui_router.include_router(assets_router)
 ui_router.include_router(dashboard_router)
 ui_router.include_router(dags_router)
+ui_router.include_router(config_router)
diff --git a/airflow/api_fastapi/core_api/routes/ui/config.py 
b/airflow/api_fastapi/core_api/routes/ui/config.py
new file mode 100644
index 00000000000..b395440ba08
--- /dev/null
+++ b/airflow/api_fastapi/core_api/routes/ui/config.py
@@ -0,0 +1,71 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF 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.
+from __future__ import annotations
+
+from typing import Any
+
+from fastapi import status
+
+from airflow.api_fastapi.common.router import AirflowRouter
+from airflow.api_fastapi.core_api.datamodels.ui.config import ConfigResponse
+from airflow.api_fastapi.core_api.openapi.exceptions import 
create_openapi_http_exception_doc
+from airflow.configuration import conf
+from airflow.settings import IS_K8S_OR_K8SCELERY_EXECUTOR, STATE_COLORS
+
+config_router = AirflowRouter(tags=["Config"])
+
+WEBSERVER_CONFIG_KEYS = [
+    "navbar_color",
+    "page_size",
+    "auto_refresh_interval",
+    "default_ui_timezone",
+    "hide_paused_dags_by_default",
+    "warn_deployment_exposure",
+    "default_wrap",
+    "require_confirmation_dag_change",
+    "enable_swagger_ui",
+    "instance_name_has_markup",
+    "navbar_text_color",
+    "navbar_hover_color",
+    "navbar_text_hover_color",
+    "navbar_logo_text_color",
+]
+
+
+@config_router.get(
+    "/config",
+    include_in_schema=False,
+    responses=create_openapi_http_exception_doc([status.HTTP_404_NOT_FOUND]),
+)
+def get_configs() -> ConfigResponse:
+    """Get configs for UI."""
+    conf_dict = conf.as_dict()
+
+    config = {key: conf_dict["webserver"].get(key) for key in 
WEBSERVER_CONFIG_KEYS}
+
+    additional_config: dict[str, Any] = {
+        "instance_name": conf.get("webserver", "instance_name", 
fallback="Airflow"),
+        "audit_view_included_events": conf.get("webserver", 
"audit_view_included_events", fallback=""),
+        "audit_view_excluded_events": conf.get("webserver", 
"audit_view_excluded_events", fallback=""),
+        "test_connection": conf.get("core", "test_connection", 
fallback="Disabled"),
+        "state_color_mapping": STATE_COLORS,
+        "is_k8s": IS_K8S_OR_K8SCELERY_EXECUTOR,
+    }
+
+    config.update({key: value for key, value in additional_config.items()})
+
+    return ConfigResponse.model_validate(config)
diff --git a/airflow/ui/openapi-gen/queries/common.ts 
b/airflow/ui/openapi-gen/queries/common.ts
index 98f0b68505c..a8a5ac9b978 100644
--- a/airflow/ui/openapi-gen/queries/common.ts
+++ b/airflow/ui/openapi-gen/queries/common.ts
@@ -270,6 +270,59 @@ export const UseDagsServiceRecentDagRunsKeyFn = (
     },
   ]),
 ];
+export type ConfigServiceGetConfigsDefaultResponse = Awaited<
+  ReturnType<typeof ConfigService.getConfigs>
+>;
+export type ConfigServiceGetConfigsQueryResult<
+  TData = ConfigServiceGetConfigsDefaultResponse,
+  TError = unknown,
+> = UseQueryResult<TData, TError>;
+export const useConfigServiceGetConfigsKey = "ConfigServiceGetConfigs";
+export const UseConfigServiceGetConfigsKeyFn = (queryKey?: Array<unknown>) => [
+  useConfigServiceGetConfigsKey,
+  ...(queryKey ?? []),
+];
+export type ConfigServiceGetConfigDefaultResponse = Awaited<
+  ReturnType<typeof ConfigService.getConfig>
+>;
+export type ConfigServiceGetConfigQueryResult<
+  TData = ConfigServiceGetConfigDefaultResponse,
+  TError = unknown,
+> = UseQueryResult<TData, TError>;
+export const useConfigServiceGetConfigKey = "ConfigServiceGetConfig";
+export const UseConfigServiceGetConfigKeyFn = (
+  {
+    accept,
+    section,
+  }: {
+    accept?: "application/json" | "text/plain" | "*/*";
+    section?: string;
+  } = {},
+  queryKey?: Array<unknown>,
+) => [useConfigServiceGetConfigKey, ...(queryKey ?? [{ accept, section }])];
+export type ConfigServiceGetConfigValueDefaultResponse = Awaited<
+  ReturnType<typeof ConfigService.getConfigValue>
+>;
+export type ConfigServiceGetConfigValueQueryResult<
+  TData = ConfigServiceGetConfigValueDefaultResponse,
+  TError = unknown,
+> = UseQueryResult<TData, TError>;
+export const useConfigServiceGetConfigValueKey = "ConfigServiceGetConfigValue";
+export const UseConfigServiceGetConfigValueKeyFn = (
+  {
+    accept,
+    option,
+    section,
+  }: {
+    accept?: "application/json" | "text/plain" | "*/*";
+    option: string;
+    section: string;
+  },
+  queryKey?: Array<unknown>,
+) => [
+  useConfigServiceGetConfigValueKey,
+  ...(queryKey ?? [{ accept, option, section }]),
+];
 export type BackfillServiceListBackfillsDefaultResponse = Awaited<
   ReturnType<typeof BackfillService.listBackfills>
 >;
@@ -495,47 +548,6 @@ export const UseDagStatsServiceGetDagStatsKeyFn = (
   } = {},
   queryKey?: Array<unknown>,
 ) => [useDagStatsServiceGetDagStatsKey, ...(queryKey ?? [{ dagIds }])];
-export type ConfigServiceGetConfigDefaultResponse = Awaited<
-  ReturnType<typeof ConfigService.getConfig>
->;
-export type ConfigServiceGetConfigQueryResult<
-  TData = ConfigServiceGetConfigDefaultResponse,
-  TError = unknown,
-> = UseQueryResult<TData, TError>;
-export const useConfigServiceGetConfigKey = "ConfigServiceGetConfig";
-export const UseConfigServiceGetConfigKeyFn = (
-  {
-    accept,
-    section,
-  }: {
-    accept?: "application/json" | "text/plain" | "*/*";
-    section?: string;
-  } = {},
-  queryKey?: Array<unknown>,
-) => [useConfigServiceGetConfigKey, ...(queryKey ?? [{ accept, section }])];
-export type ConfigServiceGetConfigValueDefaultResponse = Awaited<
-  ReturnType<typeof ConfigService.getConfigValue>
->;
-export type ConfigServiceGetConfigValueQueryResult<
-  TData = ConfigServiceGetConfigValueDefaultResponse,
-  TError = unknown,
-> = UseQueryResult<TData, TError>;
-export const useConfigServiceGetConfigValueKey = "ConfigServiceGetConfigValue";
-export const UseConfigServiceGetConfigValueKeyFn = (
-  {
-    accept,
-    option,
-    section,
-  }: {
-    accept?: "application/json" | "text/plain" | "*/*";
-    option: string;
-    section: string;
-  },
-  queryKey?: Array<unknown>,
-) => [
-  useConfigServiceGetConfigValueKey,
-  ...(queryKey ?? [{ accept, option, section }]),
-];
 export type DagWarningServiceListDagWarningsDefaultResponse = Awaited<
   ReturnType<typeof DagWarningService.listDagWarnings>
 >;
diff --git a/airflow/ui/openapi-gen/queries/prefetch.ts 
b/airflow/ui/openapi-gen/queries/prefetch.ts
index ede67e5b80d..cfc40a363c5 100644
--- a/airflow/ui/openapi-gen/queries/prefetch.ts
+++ b/airflow/ui/openapi-gen/queries/prefetch.ts
@@ -341,6 +341,68 @@ export const prefetchUseDagsServiceRecentDagRuns = (
         tags,
       }),
   });
+/**
+ * Get Configs
+ * Get configs for UI.
+ * @returns ConfigResponse Successful Response
+ * @throws ApiError
+ */
+export const prefetchUseConfigServiceGetConfigs = (queryClient: QueryClient) =>
+  queryClient.prefetchQuery({
+    queryKey: Common.UseConfigServiceGetConfigsKeyFn(),
+    queryFn: () => ConfigService.getConfigs(),
+  });
+/**
+ * Get Config
+ * @param data The data for the request.
+ * @param data.section
+ * @param data.accept
+ * @returns Config Successful Response
+ * @throws ApiError
+ */
+export const prefetchUseConfigServiceGetConfig = (
+  queryClient: QueryClient,
+  {
+    accept,
+    section,
+  }: {
+    accept?: "application/json" | "text/plain" | "*/*";
+    section?: string;
+  } = {},
+) =>
+  queryClient.prefetchQuery({
+    queryKey: Common.UseConfigServiceGetConfigKeyFn({ accept, section }),
+    queryFn: () => ConfigService.getConfig({ accept, section }),
+  });
+/**
+ * Get Config Value
+ * @param data The data for the request.
+ * @param data.section
+ * @param data.option
+ * @param data.accept
+ * @returns Config Successful Response
+ * @throws ApiError
+ */
+export const prefetchUseConfigServiceGetConfigValue = (
+  queryClient: QueryClient,
+  {
+    accept,
+    option,
+    section,
+  }: {
+    accept?: "application/json" | "text/plain" | "*/*";
+    option: string;
+    section: string;
+  },
+) =>
+  queryClient.prefetchQuery({
+    queryKey: Common.UseConfigServiceGetConfigValueKeyFn({
+      accept,
+      option,
+      section,
+    }),
+    queryFn: () => ConfigService.getConfigValue({ accept, option, section }),
+  });
 /**
  * List Backfills
  * @param data The data for the request.
@@ -630,57 +692,6 @@ export const prefetchUseDagStatsServiceGetDagStats = (
     queryKey: Common.UseDagStatsServiceGetDagStatsKeyFn({ dagIds }),
     queryFn: () => DagStatsService.getDagStats({ dagIds }),
   });
-/**
- * Get Config
- * @param data The data for the request.
- * @param data.section
- * @param data.accept
- * @returns Config Successful Response
- * @throws ApiError
- */
-export const prefetchUseConfigServiceGetConfig = (
-  queryClient: QueryClient,
-  {
-    accept,
-    section,
-  }: {
-    accept?: "application/json" | "text/plain" | "*/*";
-    section?: string;
-  } = {},
-) =>
-  queryClient.prefetchQuery({
-    queryKey: Common.UseConfigServiceGetConfigKeyFn({ accept, section }),
-    queryFn: () => ConfigService.getConfig({ accept, section }),
-  });
-/**
- * Get Config Value
- * @param data The data for the request.
- * @param data.section
- * @param data.option
- * @param data.accept
- * @returns Config Successful Response
- * @throws ApiError
- */
-export const prefetchUseConfigServiceGetConfigValue = (
-  queryClient: QueryClient,
-  {
-    accept,
-    option,
-    section,
-  }: {
-    accept?: "application/json" | "text/plain" | "*/*";
-    option: string;
-    section: string;
-  },
-) =>
-  queryClient.prefetchQuery({
-    queryKey: Common.UseConfigServiceGetConfigValueKeyFn({
-      accept,
-      option,
-      section,
-    }),
-    queryFn: () => ConfigService.getConfigValue({ accept, option, section }),
-  });
 /**
  * List Dag Warnings
  * Get a list of DAG warnings.
diff --git a/airflow/ui/openapi-gen/queries/queries.ts 
b/airflow/ui/openapi-gen/queries/queries.ts
index 29fa6562524..fb1d6568cf1 100644
--- a/airflow/ui/openapi-gen/queries/queries.ts
+++ b/airflow/ui/openapi-gen/queries/queries.ts
@@ -429,6 +429,91 @@ export const useDagsServiceRecentDagRuns = <
       }) as TData,
     ...options,
   });
+/**
+ * Get Configs
+ * Get configs for UI.
+ * @returns ConfigResponse Successful Response
+ * @throws ApiError
+ */
+export const useConfigServiceGetConfigs = <
+  TData = Common.ConfigServiceGetConfigsDefaultResponse,
+  TError = unknown,
+  TQueryKey extends Array<unknown> = unknown[],
+>(
+  queryKey?: TQueryKey,
+  options?: Omit<UseQueryOptions<TData, TError>, "queryKey" | "queryFn">,
+) =>
+  useQuery<TData, TError>({
+    queryKey: Common.UseConfigServiceGetConfigsKeyFn(queryKey),
+    queryFn: () => ConfigService.getConfigs() as TData,
+    ...options,
+  });
+/**
+ * Get Config
+ * @param data The data for the request.
+ * @param data.section
+ * @param data.accept
+ * @returns Config Successful Response
+ * @throws ApiError
+ */
+export const useConfigServiceGetConfig = <
+  TData = Common.ConfigServiceGetConfigDefaultResponse,
+  TError = unknown,
+  TQueryKey extends Array<unknown> = unknown[],
+>(
+  {
+    accept,
+    section,
+  }: {
+    accept?: "application/json" | "text/plain" | "*/*";
+    section?: string;
+  } = {},
+  queryKey?: TQueryKey,
+  options?: Omit<UseQueryOptions<TData, TError>, "queryKey" | "queryFn">,
+) =>
+  useQuery<TData, TError>({
+    queryKey: Common.UseConfigServiceGetConfigKeyFn(
+      { accept, section },
+      queryKey,
+    ),
+    queryFn: () => ConfigService.getConfig({ accept, section }) as TData,
+    ...options,
+  });
+/**
+ * Get Config Value
+ * @param data The data for the request.
+ * @param data.section
+ * @param data.option
+ * @param data.accept
+ * @returns Config Successful Response
+ * @throws ApiError
+ */
+export const useConfigServiceGetConfigValue = <
+  TData = Common.ConfigServiceGetConfigValueDefaultResponse,
+  TError = unknown,
+  TQueryKey extends Array<unknown> = unknown[],
+>(
+  {
+    accept,
+    option,
+    section,
+  }: {
+    accept?: "application/json" | "text/plain" | "*/*";
+    option: string;
+    section: string;
+  },
+  queryKey?: TQueryKey,
+  options?: Omit<UseQueryOptions<TData, TError>, "queryKey" | "queryFn">,
+) =>
+  useQuery<TData, TError>({
+    queryKey: Common.UseConfigServiceGetConfigValueKeyFn(
+      { accept, option, section },
+      queryKey,
+    ),
+    queryFn: () =>
+      ConfigService.getConfigValue({ accept, option, section }) as TData,
+    ...options,
+  });
 /**
  * List Backfills
  * @param data The data for the request.
@@ -782,72 +867,6 @@ export const useDagStatsServiceGetDagStats = <
     queryFn: () => DagStatsService.getDagStats({ dagIds }) as TData,
     ...options,
   });
-/**
- * Get Config
- * @param data The data for the request.
- * @param data.section
- * @param data.accept
- * @returns Config Successful Response
- * @throws ApiError
- */
-export const useConfigServiceGetConfig = <
-  TData = Common.ConfigServiceGetConfigDefaultResponse,
-  TError = unknown,
-  TQueryKey extends Array<unknown> = unknown[],
->(
-  {
-    accept,
-    section,
-  }: {
-    accept?: "application/json" | "text/plain" | "*/*";
-    section?: string;
-  } = {},
-  queryKey?: TQueryKey,
-  options?: Omit<UseQueryOptions<TData, TError>, "queryKey" | "queryFn">,
-) =>
-  useQuery<TData, TError>({
-    queryKey: Common.UseConfigServiceGetConfigKeyFn(
-      { accept, section },
-      queryKey,
-    ),
-    queryFn: () => ConfigService.getConfig({ accept, section }) as TData,
-    ...options,
-  });
-/**
- * Get Config Value
- * @param data The data for the request.
- * @param data.section
- * @param data.option
- * @param data.accept
- * @returns Config Successful Response
- * @throws ApiError
- */
-export const useConfigServiceGetConfigValue = <
-  TData = Common.ConfigServiceGetConfigValueDefaultResponse,
-  TError = unknown,
-  TQueryKey extends Array<unknown> = unknown[],
->(
-  {
-    accept,
-    option,
-    section,
-  }: {
-    accept?: "application/json" | "text/plain" | "*/*";
-    option: string;
-    section: string;
-  },
-  queryKey?: TQueryKey,
-  options?: Omit<UseQueryOptions<TData, TError>, "queryKey" | "queryFn">,
-) =>
-  useQuery<TData, TError>({
-    queryKey: Common.UseConfigServiceGetConfigValueKeyFn(
-      { accept, option, section },
-      queryKey,
-    ),
-    queryFn: () =>
-      ConfigService.getConfigValue({ accept, option, section }) as TData,
-    ...options,
-  });
 /**
  * List Dag Warnings
  * Get a list of DAG warnings.
diff --git a/airflow/ui/openapi-gen/queries/suspense.ts 
b/airflow/ui/openapi-gen/queries/suspense.ts
index ec5f2740800..af0ddd9a09a 100644
--- a/airflow/ui/openapi-gen/queries/suspense.ts
+++ b/airflow/ui/openapi-gen/queries/suspense.ts
@@ -409,6 +409,91 @@ export const useDagsServiceRecentDagRunsSuspense = <
       }) as TData,
     ...options,
   });
+/**
+ * Get Configs
+ * Get configs for UI.
+ * @returns ConfigResponse Successful Response
+ * @throws ApiError
+ */
+export const useConfigServiceGetConfigsSuspense = <
+  TData = Common.ConfigServiceGetConfigsDefaultResponse,
+  TError = unknown,
+  TQueryKey extends Array<unknown> = unknown[],
+>(
+  queryKey?: TQueryKey,
+  options?: Omit<UseQueryOptions<TData, TError>, "queryKey" | "queryFn">,
+) =>
+  useSuspenseQuery<TData, TError>({
+    queryKey: Common.UseConfigServiceGetConfigsKeyFn(queryKey),
+    queryFn: () => ConfigService.getConfigs() as TData,
+    ...options,
+  });
+/**
+ * Get Config
+ * @param data The data for the request.
+ * @param data.section
+ * @param data.accept
+ * @returns Config Successful Response
+ * @throws ApiError
+ */
+export const useConfigServiceGetConfigSuspense = <
+  TData = Common.ConfigServiceGetConfigDefaultResponse,
+  TError = unknown,
+  TQueryKey extends Array<unknown> = unknown[],
+>(
+  {
+    accept,
+    section,
+  }: {
+    accept?: "application/json" | "text/plain" | "*/*";
+    section?: string;
+  } = {},
+  queryKey?: TQueryKey,
+  options?: Omit<UseQueryOptions<TData, TError>, "queryKey" | "queryFn">,
+) =>
+  useSuspenseQuery<TData, TError>({
+    queryKey: Common.UseConfigServiceGetConfigKeyFn(
+      { accept, section },
+      queryKey,
+    ),
+    queryFn: () => ConfigService.getConfig({ accept, section }) as TData,
+    ...options,
+  });
+/**
+ * Get Config Value
+ * @param data The data for the request.
+ * @param data.section
+ * @param data.option
+ * @param data.accept
+ * @returns Config Successful Response
+ * @throws ApiError
+ */
+export const useConfigServiceGetConfigValueSuspense = <
+  TData = Common.ConfigServiceGetConfigValueDefaultResponse,
+  TError = unknown,
+  TQueryKey extends Array<unknown> = unknown[],
+>(
+  {
+    accept,
+    option,
+    section,
+  }: {
+    accept?: "application/json" | "text/plain" | "*/*";
+    option: string;
+    section: string;
+  },
+  queryKey?: TQueryKey,
+  options?: Omit<UseQueryOptions<TData, TError>, "queryKey" | "queryFn">,
+) =>
+  useSuspenseQuery<TData, TError>({
+    queryKey: Common.UseConfigServiceGetConfigValueKeyFn(
+      { accept, option, section },
+      queryKey,
+    ),
+    queryFn: () =>
+      ConfigService.getConfigValue({ accept, option, section }) as TData,
+    ...options,
+  });
 /**
  * List Backfills
  * @param data The data for the request.
@@ -762,72 +847,6 @@ export const useDagStatsServiceGetDagStatsSuspense = <
     queryFn: () => DagStatsService.getDagStats({ dagIds }) as TData,
     ...options,
   });
-/**
- * Get Config
- * @param data The data for the request.
- * @param data.section
- * @param data.accept
- * @returns Config Successful Response
- * @throws ApiError
- */
-export const useConfigServiceGetConfigSuspense = <
-  TData = Common.ConfigServiceGetConfigDefaultResponse,
-  TError = unknown,
-  TQueryKey extends Array<unknown> = unknown[],
->(
-  {
-    accept,
-    section,
-  }: {
-    accept?: "application/json" | "text/plain" | "*/*";
-    section?: string;
-  } = {},
-  queryKey?: TQueryKey,
-  options?: Omit<UseQueryOptions<TData, TError>, "queryKey" | "queryFn">,
-) =>
-  useSuspenseQuery<TData, TError>({
-    queryKey: Common.UseConfigServiceGetConfigKeyFn(
-      { accept, section },
-      queryKey,
-    ),
-    queryFn: () => ConfigService.getConfig({ accept, section }) as TData,
-    ...options,
-  });
-/**
- * Get Config Value
- * @param data The data for the request.
- * @param data.section
- * @param data.option
- * @param data.accept
- * @returns Config Successful Response
- * @throws ApiError
- */
-export const useConfigServiceGetConfigValueSuspense = <
-  TData = Common.ConfigServiceGetConfigValueDefaultResponse,
-  TError = unknown,
-  TQueryKey extends Array<unknown> = unknown[],
->(
-  {
-    accept,
-    option,
-    section,
-  }: {
-    accept?: "application/json" | "text/plain" | "*/*";
-    option: string;
-    section: string;
-  },
-  queryKey?: TQueryKey,
-  options?: Omit<UseQueryOptions<TData, TError>, "queryKey" | "queryFn">,
-) =>
-  useSuspenseQuery<TData, TError>({
-    queryKey: Common.UseConfigServiceGetConfigValueKeyFn(
-      { accept, option, section },
-      queryKey,
-    ),
-    queryFn: () =>
-      ConfigService.getConfigValue({ accept, option, section }) as TData,
-    ...options,
-  });
 /**
  * List Dag Warnings
  * Get a list of DAG warnings.
diff --git a/airflow/ui/openapi-gen/requests/schemas.gen.ts 
b/airflow/ui/openapi-gen/requests/schemas.gen.ts
index 3a4e903b60d..0589b7a7cf3 100644
--- a/airflow/ui/openapi-gen/requests/schemas.gen.ts
+++ b/airflow/ui/openapi-gen/requests/schemas.gen.ts
@@ -606,6 +606,116 @@ export const $ConfigOption = {
   description: "Config option.",
 } as const;
 
+export const $ConfigResponse = {
+  properties: {
+    navbar_color: {
+      type: "string",
+      title: "Navbar Color",
+    },
+    navbar_text_color: {
+      type: "string",
+      title: "Navbar Text Color",
+    },
+    navbar_hover_color: {
+      type: "string",
+      title: "Navbar Hover Color",
+    },
+    navbar_text_hover_color: {
+      type: "string",
+      title: "Navbar Text Hover Color",
+    },
+    navbar_logo_text_color: {
+      type: "string",
+      title: "Navbar Logo Text Color",
+    },
+    page_size: {
+      type: "integer",
+      title: "Page Size",
+    },
+    auto_refresh_interval: {
+      type: "integer",
+      title: "Auto Refresh Interval",
+    },
+    default_ui_timezone: {
+      type: "string",
+      title: "Default Ui Timezone",
+    },
+    hide_paused_dags_by_default: {
+      type: "boolean",
+      title: "Hide Paused Dags By Default",
+    },
+    instance_name: {
+      type: "string",
+      title: "Instance Name",
+    },
+    instance_name_has_markup: {
+      type: "boolean",
+      title: "Instance Name Has Markup",
+    },
+    enable_swagger_ui: {
+      type: "boolean",
+      title: "Enable Swagger Ui",
+    },
+    require_confirmation_dag_change: {
+      type: "boolean",
+      title: "Require Confirmation Dag Change",
+    },
+    default_wrap: {
+      type: "boolean",
+      title: "Default Wrap",
+    },
+    warn_deployment_exposure: {
+      type: "boolean",
+      title: "Warn Deployment Exposure",
+    },
+    audit_view_excluded_events: {
+      type: "string",
+      title: "Audit View Excluded Events",
+    },
+    audit_view_included_events: {
+      type: "string",
+      title: "Audit View Included Events",
+    },
+    is_k8s: {
+      type: "boolean",
+      title: "Is K8S",
+    },
+    test_connection: {
+      type: "string",
+      title: "Test Connection",
+    },
+    state_color_mapping: {
+      type: "object",
+      title: "State Color Mapping",
+    },
+  },
+  type: "object",
+  required: [
+    "navbar_color",
+    "navbar_text_color",
+    "navbar_hover_color",
+    "navbar_text_hover_color",
+    "navbar_logo_text_color",
+    "page_size",
+    "auto_refresh_interval",
+    "default_ui_timezone",
+    "hide_paused_dags_by_default",
+    "instance_name",
+    "instance_name_has_markup",
+    "enable_swagger_ui",
+    "require_confirmation_dag_change",
+    "default_wrap",
+    "warn_deployment_exposure",
+    "audit_view_excluded_events",
+    "audit_view_included_events",
+    "is_k8s",
+    "test_connection",
+    "state_color_mapping",
+  ],
+  title: "ConfigResponse",
+  description: "configuration serializer.",
+} as const;
+
 export const $ConfigSection = {
   properties: {
     name: {
diff --git a/airflow/ui/openapi-gen/requests/services.gen.ts 
b/airflow/ui/openapi-gen/requests/services.gen.ts
index dd305837ee8..71e9c729a7f 100644
--- a/airflow/ui/openapi-gen/requests/services.gen.ts
+++ b/airflow/ui/openapi-gen/requests/services.gen.ts
@@ -29,6 +29,11 @@ import type {
   HistoricalMetricsResponse,
   RecentDagRunsData,
   RecentDagRunsResponse,
+  GetConfigsResponse,
+  GetConfigData,
+  GetConfigResponse,
+  GetConfigValueData,
+  GetConfigValueResponse,
   ListBackfillsData,
   ListBackfillsResponse,
   CreateBackfillData,
@@ -69,10 +74,6 @@ import type {
   GetDagSourceResponse,
   GetDagStatsData,
   GetDagStatsResponse,
-  GetConfigData,
-  GetConfigResponse,
-  GetConfigValueData,
-  GetConfigValueResponse,
   ListDagWarningsData,
   ListDagWarningsResponse,
   GetDagsData,
@@ -560,6 +561,86 @@ export class DagsService {
   }
 }
 
+export class ConfigService {
+  /**
+   * Get Configs
+   * Get configs for UI.
+   * @returns ConfigResponse Successful Response
+   * @throws ApiError
+   */
+  public static getConfigs(): CancelablePromise<GetConfigsResponse> {
+    return __request(OpenAPI, {
+      method: "GET",
+      url: "/ui/config",
+      errors: {
+        404: "Not Found",
+      },
+    });
+  }
+
+  /**
+   * Get Config
+   * @param data The data for the request.
+   * @param data.section
+   * @param data.accept
+   * @returns Config Successful Response
+   * @throws ApiError
+   */
+  public static getConfig(
+    data: GetConfigData = {},
+  ): CancelablePromise<GetConfigResponse> {
+    return __request(OpenAPI, {
+      method: "GET",
+      url: "/public/config",
+      headers: {
+        accept: data.accept,
+      },
+      query: {
+        section: data.section,
+      },
+      errors: {
+        401: "Unauthorized",
+        403: "Forbidden",
+        404: "Not Found",
+        406: "Not Acceptable",
+        422: "Validation Error",
+      },
+    });
+  }
+
+  /**
+   * Get Config Value
+   * @param data The data for the request.
+   * @param data.section
+   * @param data.option
+   * @param data.accept
+   * @returns Config Successful Response
+   * @throws ApiError
+   */
+  public static getConfigValue(
+    data: GetConfigValueData,
+  ): CancelablePromise<GetConfigValueResponse> {
+    return __request(OpenAPI, {
+      method: "GET",
+      url: "/public/config/section/{section}/option/{option}",
+      path: {
+        section: data.section,
+        option: data.option,
+      },
+      headers: {
+        accept: data.accept,
+      },
+      errors: {
+        401: "Unauthorized",
+        403: "Forbidden",
+        404: "Not Found",
+        406: "Not Acceptable",
+        422: "Validation Error",
+      },
+    });
+  }
+}
+
 export class BackfillService {
   /**
    * List Backfills
@@ -1164,70 +1245,6 @@ export class DagStatsService {
   }
 }
 
-export class ConfigService {
-  /**
-   * Get Config
-   * @param data The data for the request.
-   * @param data.section
-   * @param data.accept
-   * @returns Config Successful Response
-   * @throws ApiError
-   */
-  public static getConfig(
-    data: GetConfigData = {},
-  ): CancelablePromise<GetConfigResponse> {
-    return __request(OpenAPI, {
-      method: "GET",
-      url: "/public/config",
-      headers: {
-        accept: data.accept,
-      },
-      query: {
-        section: data.section,
-      },
-      errors: {
-        401: "Unauthorized",
-        403: "Forbidden",
-        404: "Not Found",
-        406: "Not Acceptable",
-        422: "Validation Error",
-      },
-    });
-  }
-
-  /**
-   * Get Config Value
-   * @param data The data for the request.
-   * @param data.section
-   * @param data.option
-   * @param data.accept
-   * @returns Config Successful Response
-   * @throws ApiError
-   */
-  public static getConfigValue(
-    data: GetConfigValueData,
-  ): CancelablePromise<GetConfigValueResponse> {
-    return __request(OpenAPI, {
-      method: "GET",
-      url: "/public/config/section/{section}/option/{option}",
-      path: {
-        section: data.section,
-        option: data.option,
-      },
-      headers: {
-        accept: data.accept,
-      },
-      errors: {
-        401: "Unauthorized",
-        403: "Forbidden",
-        404: "Not Found",
-        406: "Not Acceptable",
-        422: "Validation Error",
-      },
-    });
-  }
-}
-
 export class DagWarningService {
   /**
    * List Dag Warnings
diff --git a/airflow/ui/openapi-gen/requests/types.gen.ts 
b/airflow/ui/openapi-gen/requests/types.gen.ts
index f84d918d581..c844b3b56f8 100644
--- a/airflow/ui/openapi-gen/requests/types.gen.ts
+++ b/airflow/ui/openapi-gen/requests/types.gen.ts
@@ -161,6 +161,34 @@ export type ConfigOption = {
   value: string | [string, string];
 };
 
+/**
+ * configuration serializer.
+ */
+export type ConfigResponse = {
+  navbar_color: string;
+  navbar_text_color: string;
+  navbar_hover_color: string;
+  navbar_text_hover_color: string;
+  navbar_logo_text_color: string;
+  page_size: number;
+  auto_refresh_interval: number;
+  default_ui_timezone: string;
+  hide_paused_dags_by_default: boolean;
+  instance_name: string;
+  instance_name_has_markup: boolean;
+  enable_swagger_ui: boolean;
+  require_confirmation_dag_change: boolean;
+  default_wrap: boolean;
+  warn_deployment_exposure: boolean;
+  audit_view_excluded_events: string;
+  audit_view_included_events: string;
+  is_k8s: boolean;
+  test_connection: string;
+  state_color_mapping: {
+    [key: string]: unknown;
+  };
+};
+
 /**
  * Config Section Schema.
  */
@@ -1239,6 +1267,23 @@ export type RecentDagRunsData = {
 
 export type RecentDagRunsResponse = DAGWithLatestDagRunsCollectionResponse;
 
+export type GetConfigsResponse = ConfigResponse;
+
+export type GetConfigData = {
+  accept?: "application/json" | "text/plain" | "*/*";
+  section?: string | null;
+};
+
+export type GetConfigResponse = Config;
+
+export type GetConfigValueData = {
+  accept?: "application/json" | "text/plain" | "*/*";
+  option: string;
+  section: string;
+};
+
+export type GetConfigValueResponse = Config;
+
 export type ListBackfillsData = {
   dagId: string;
   limit?: number;
@@ -1390,21 +1435,6 @@ export type GetDagStatsData = {
 
 export type GetDagStatsResponse = DagStatsCollectionResponse;
 
-export type GetConfigData = {
-  accept?: "application/json" | "text/plain" | "*/*";
-  section?: string | null;
-};
-
-export type GetConfigResponse = Config;
-
-export type GetConfigValueData = {
-  accept?: "application/json" | "text/plain" | "*/*";
-  option: string;
-  section: string;
-};
-
-export type GetConfigValueResponse = Config;
-
 export type ListDagWarningsData = {
   dagId?: string | null;
   limit?: number;
@@ -2095,6 +2125,82 @@ export type $OpenApiTs = {
       };
     };
   };
+  "/ui/config": {
+    get: {
+      res: {
+        /**
+         * Successful Response
+         */
+        200: ConfigResponse;
+        /**
+         * Not Found
+         */
+        404: HTTPExceptionResponse;
+      };
+    };
+  };
+  "/public/config": {
+    get: {
+      req: GetConfigData;
+      res: {
+        /**
+         * Successful Response
+         */
+        200: Config;
+        /**
+         * Unauthorized
+         */
+        401: HTTPExceptionResponse;
+        /**
+         * Forbidden
+         */
+        403: HTTPExceptionResponse;
+        /**
+         * Not Found
+         */
+        404: HTTPExceptionResponse;
+        /**
+         * Not Acceptable
+         */
+        406: HTTPExceptionResponse;
+        /**
+         * Validation Error
+         */
+        422: HTTPValidationError;
+      };
+    };
+  };
+  "/public/config/section/{section}/option/{option}": {
+    get: {
+      req: GetConfigValueData;
+      res: {
+        /**
+         * Successful Response
+         */
+        200: Config;
+        /**
+         * Unauthorized
+         */
+        401: HTTPExceptionResponse;
+        /**
+         * Forbidden
+         */
+        403: HTTPExceptionResponse;
+        /**
+         * Not Found
+         */
+        404: HTTPExceptionResponse;
+        /**
+         * Not Acceptable
+         */
+        406: HTTPExceptionResponse;
+        /**
+         * Validation Error
+         */
+        422: HTTPValidationError;
+      };
+    };
+  };
   "/public/backfills": {
     get: {
       req: ListBackfillsData;
@@ -2655,68 +2761,6 @@ export type $OpenApiTs = {
       };
     };
   };
-  "/public/config": {
-    get: {
-      req: GetConfigData;
-      res: {
-        /**
-         * Successful Response
-         */
-        200: Config;
-        /**
-         * Unauthorized
-         */
-        401: HTTPExceptionResponse;
-        /**
-         * Forbidden
-         */
-        403: HTTPExceptionResponse;
-        /**
-         * Not Found
-         */
-        404: HTTPExceptionResponse;
-        /**
-         * Not Acceptable
-         */
-        406: HTTPExceptionResponse;
-        /**
-         * Validation Error
-         */
-        422: HTTPValidationError;
-      };
-    };
-  };
-  "/public/config/section/{section}/option/{option}": {
-    get: {
-      req: GetConfigValueData;
-      res: {
-        /**
-         * Successful Response
-         */
-        200: Config;
-        /**
-         * Unauthorized
-         */
-        401: HTTPExceptionResponse;
-        /**
-         * Forbidden
-         */
-        403: HTTPExceptionResponse;
-        /**
-         * Not Found
-         */
-        404: HTTPExceptionResponse;
-        /**
-         * Not Acceptable
-         */
-        406: HTTPExceptionResponse;
-        /**
-         * Validation Error
-         */
-        422: HTTPValidationError;
-      };
-    };
-  };
   "/public/dagWarnings": {
     get: {
       req: ListDagWarningsData;
diff --git a/tests/api_fastapi/core_api/routes/ui/test_config.py 
b/tests/api_fastapi/core_api/routes/ui/test_config.py
new file mode 100644
index 00000000000..f5b3a3067e6
--- /dev/null
+++ b/tests/api_fastapi/core_api/routes/ui/test_config.py
@@ -0,0 +1,115 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF 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.
+from __future__ import annotations
+
+from unittest.mock import patch
+
+import pytest
+
+mock_config_response = {
+    "navbar_color": "#fff",
+    "navbar_text_color": "#51504f",
+    "navbar_hover_color": "#eee",
+    "navbar_text_hover_color": "#51504f",
+    "navbar_logo_text_color": "#51504f",
+    "page_size": 100,
+    "auto_refresh_interval": 3,
+    "default_ui_timezone": "UTC",
+    "hide_paused_dags_by_default": False,
+    "instance_name": "Airflow",
+    "instance_name_has_markup": False,
+    "enable_swagger_ui": True,
+    "require_confirmation_dag_change": False,
+    "default_wrap": False,
+    "warn_deployment_exposure": False,
+    "audit_view_excluded_events": "",
+    "audit_view_included_events": "",
+    "is_k8s": False,
+    "test_connection": "Disabled",
+    "state_color_mapping": {
+        "deferred": "mediumpurple",
+        "failed": "red",
+        "queued": "gray",
+        "removed": "lightgrey",
+        "restarting": "violet",
+        "running": "lime",
+        "scheduled": "tan",
+        "skipped": "hotpink",
+        "success": "green",
+        "up_for_reschedule": "turquoise",
+        "up_for_retry": "gold",
+        "upstream_failed": "orange",
+    },
+}
+
+
[email protected]
+def mock_config_data():
+    """
+    Mock configuration settings used in the endpoint.
+    """
+    with patch("airflow.configuration.conf.as_dict") as mock_conf:
+        mock_conf.return_value = {
+            "webserver": {
+                "navbar_color": "#fff",
+                "navbar_text_color": "#51504f",
+                "navbar_hover_color": "#eee",
+                "navbar_text_hover_color": "#51504f",
+                "navbar_logo_text_color": "#51504f",
+                "page_size": "100",
+                "auto_refresh_interval": "3",
+                "default_ui_timezone": "UTC",
+                "hide_paused_dags_by_default": "false",
+                "instance_name": "Airflow",
+                "instance_name_has_markup": "false",
+                "enable_swagger_ui": "true",
+                "require_confirmation_dag_change": "false",
+                "default_wrap": "false",
+                "warn_deployment_exposure": "false",
+                "audit_view_excluded_events": "",
+                "audit_view_included_events": "",
+            }
+        }
+        with patch(
+            "airflow.settings.STATE_COLORS",
+            {
+                "deferred": "mediumpurple",
+                "failed": "red",
+                "queued": "gray",
+                "removed": "lightgrey",
+                "restarting": "violet",
+                "running": "lime",
+                "scheduled": "tan",
+                "skipped": "hotpink",
+                "success": "green",
+                "up_for_reschedule": "turquoise",
+                "up_for_retry": "gold",
+                "upstream_failed": "orange",
+            },
+        ):
+            yield mock_conf
+
+
+def test_get_configs_basic(mock_config_data, test_client):
+    """
+    Test the /ui/config endpoint to verify response matches mock data.
+    """
+
+    response = test_client.get("/ui/config")
+
+    assert response.status_code == 200
+    assert response.json() == mock_config_response


Reply via email to