diff --git a/src/containers/Tenant/Diagnostics/Configs/Configs.scss b/src/containers/Tenant/Diagnostics/Configs/Configs.scss new file mode 100644 index 0000000000..760224d401 --- /dev/null +++ b/src/containers/Tenant/Diagnostics/Configs/Configs.scss @@ -0,0 +1,8 @@ +.ydb-diagnostics-configs { + &__icon-touched { + line-height: 1; + cursor: default !important; + + color: var(--g-color-text-secondary); + } +} diff --git a/src/containers/Tenant/Diagnostics/Configs/Configs.tsx b/src/containers/Tenant/Diagnostics/Configs/Configs.tsx new file mode 100644 index 0000000000..bf6d05da90 --- /dev/null +++ b/src/containers/Tenant/Diagnostics/Configs/Configs.tsx @@ -0,0 +1,125 @@ +import {PersonPencil} from '@gravity-ui/icons'; +import type {Column} from '@gravity-ui/react-data-table'; +import {Icon, Popover, Switch} from '@gravity-ui/uikit'; +import {StringParam, useQueryParam} from 'use-query-params'; + +import {ResponseError} from '../../../../components/Errors/ResponseError'; +import {ResizeableDataTable} from '../../../../components/ResizeableDataTable/ResizeableDataTable'; +import {Search} from '../../../../components/Search'; +import {TableWithControlsLayout} from '../../../../components/TableWithControlsLayout/TableWithControlsLayout'; +import {tenantApi} from '../../../../store/reducers/tenant/tenant'; +import type {FeatureFlagConfig} from '../../../../types/api/featureFlags'; +import {cn} from '../../../../utils/cn'; +import {DEFAULT_TABLE_SETTINGS} from '../../../../utils/constants'; + +import i18n from './i18n'; + +import './Configs.scss'; + +const FEATURE_FLAGS_COLUMNS_WIDTH_LS_KEY = 'featureFlagsColumnsWidth'; + +const b = cn('ydb-diagnostics-configs'); + +const columns: Column[] = [ + { + name: 'Touched', + header: '', + render: ({row}) => + row.Current ? ( + + + + ) : null, + width: 36, + sortable: false, + resizeable: false, + }, + { + name: 'Name', + get header() { + return i18n('td-feature-flag'); + }, + render: ({row}) => (row.Current ? {row.Name} : row.Name), + width: 400, + sortable: true, + sortAccessor: ({Current, Name}) => { + return Number(!Current) + Name.toLowerCase(); + }, + }, + { + name: 'Default', + get header() { + return i18n('td-default'); + }, + render: ({row}) => { + switch (row.Default) { + case true: + return i18n('enabled'); + case false: + return i18n('disabled'); + default: + return '-'; + } + }, + width: 100, + sortable: false, + resizeable: false, + }, + { + name: 'Current', + get header() { + return i18n('td-current'); + }, + render: ({row}) => , + width: 100, + sortable: false, + resizeable: false, + }, +]; + +interface ConfigsProps { + database: string; +} + +export const Configs = ({database}: ConfigsProps) => { + const [search, setSearch] = useQueryParam('search', StringParam); + const {currentData = [], isFetching, error} = tenantApi.useGetClusterConfigQuery({database}); + + const onChange = (value: string) => { + setSearch(value || undefined, 'replaceIn'); + }; + + const featureFlagsFilter = search?.toLocaleLowerCase(); + const featureFlags = featureFlagsFilter + ? currentData.filter((item) => item.Name.toLocaleLowerCase().includes(featureFlagsFilter)) + : currentData; + + return ( + + + + + + {error ? ( + + ) : ( + + )} + + + ); +}; diff --git a/src/containers/Tenant/Diagnostics/Configs/i18n/en.json b/src/containers/Tenant/Diagnostics/Configs/i18n/en.json new file mode 100644 index 0000000000..6c8b079f1c --- /dev/null +++ b/src/containers/Tenant/Diagnostics/Configs/i18n/en.json @@ -0,0 +1,13 @@ +{ + "td-feature-flag": "Feature flag", + "td-default": "Default", + "td-current": "Current", + + "enabled": "Enabled", + "disabled": "Disabled", + "flag-touched": "Flag is changed", + + "search-placeholder": "Search by feature flag", + "search-empty": "Empty search result", + "no-data": "No data" +} diff --git a/src/containers/Tenant/Diagnostics/Configs/i18n/index.ts b/src/containers/Tenant/Diagnostics/Configs/i18n/index.ts new file mode 100644 index 0000000000..b730046175 --- /dev/null +++ b/src/containers/Tenant/Diagnostics/Configs/i18n/index.ts @@ -0,0 +1,7 @@ +import {registerKeysets} from '../../../../../utils/i18n'; + +import en from './en.json'; + +const COMPONENT = 'ydb-diagnostics-configs'; + +export default registerKeysets(COMPONENT, {en}); diff --git a/src/containers/Tenant/Diagnostics/Diagnostics.tsx b/src/containers/Tenant/Diagnostics/Diagnostics.tsx index 253998f12b..645e88f7e7 100644 --- a/src/containers/Tenant/Diagnostics/Diagnostics.tsx +++ b/src/containers/Tenant/Diagnostics/Diagnostics.tsx @@ -21,6 +21,7 @@ import {SchemaViewer} from '../Schema/SchemaViewer/SchemaViewer'; import {TenantTabsGroups} from '../TenantPages'; import {isDatabaseEntityType} from '../utils/schema'; +import {Configs} from './Configs/Configs'; import {Consumers} from './Consumers'; import Describe from './Describe/Describe'; import DetailedOverview from './DetailedOverview/DetailedOverview'; @@ -131,6 +132,9 @@ function Diagnostics(props: DiagnosticsProps) { case TENANT_DIAGNOSTICS_TABS_IDS.partitions: { return ; } + case TENANT_DIAGNOSTICS_TABS_IDS.configs: { + return ; + } default: { return
No data...
; } diff --git a/src/containers/Tenant/Diagnostics/DiagnosticsPages.ts b/src/containers/Tenant/Diagnostics/DiagnosticsPages.ts index da2bae7077..b60b048390 100644 --- a/src/containers/Tenant/Diagnostics/DiagnosticsPages.ts +++ b/src/containers/Tenant/Diagnostics/DiagnosticsPages.ts @@ -70,6 +70,11 @@ const partitions = { title: 'Partitions', }; +const configs = { + id: TENANT_DIAGNOSTICS_TABS_IDS.configs, + title: 'Configs', +}; + export const ASYNC_REPLICATION_PAGES = [overview, tablets, describe]; export const DATABASE_PAGES = [ @@ -81,6 +86,7 @@ export const DATABASE_PAGES = [ storage, network, describe, + configs, ]; export const TABLE_PAGES = [overview, schema, topShards, nodes, graph, tablets, hotKeys, describe]; diff --git a/src/services/api.ts b/src/services/api.ts index fb5e5e7603..c9e52c93f7 100644 --- a/src/services/api.ts +++ b/src/services/api.ts @@ -12,6 +12,7 @@ import type {CapabilitiesResponse} from '../types/api/capabilities'; import type {TClusterInfo} from '../types/api/cluster'; import type {TComputeInfo} from '../types/api/compute'; import type {DescribeConsumerResult} from '../types/api/consumer'; +import type {FeatureFlagConfigs} from '../types/api/featureFlags'; import type {HealthCheckAPIResponse} from '../types/api/healthcheck'; import type {JsonHotKeysResponse} from '../types/api/hotkeys'; import type { @@ -147,6 +148,15 @@ export class YdbEmbeddedAPI extends AxiosWrapper { {concurrentId: concurrentId || `getClusterInfo`, requestConfig: {signal}}, ); } + getClusterConfig(database?: string, {concurrentId, signal}: AxiosOptions = {}) { + return this.get( + this.getPath('/viewer/feature_flags'), + { + database, + }, + {concurrentId, requestConfig: {signal}}, + ); + } getNodeInfo(id?: string | number, {concurrentId, signal}: AxiosOptions = {}) { return this.get( this.getPath('/viewer/json/sysinfo?enums=true'), diff --git a/src/store/reducers/tenant/constants.ts b/src/store/reducers/tenant/constants.ts index d7818309a2..7faf1d0e0d 100644 --- a/src/store/reducers/tenant/constants.ts +++ b/src/store/reducers/tenant/constants.ts @@ -25,6 +25,7 @@ export const TENANT_DIAGNOSTICS_TABS_IDS = { graph: 'graph', consumers: 'consumers', partitions: 'partitions', + configs: 'configs', } as const; export const TENANT_SUMMARY_TABS_IDS = { diff --git a/src/store/reducers/tenant/tenant.ts b/src/store/reducers/tenant/tenant.ts index 0bba8d1022..1cad80f455 100644 --- a/src/store/reducers/tenant/tenant.ts +++ b/src/store/reducers/tenant/tenant.ts @@ -62,6 +62,18 @@ export const tenantApi = api.injectEndpoints({ }, providesTags: ['All'], }), + getClusterConfig: builder.query({ + queryFn: async ({database}: {database: string}, {signal}) => { + try { + const res = await window.api.getClusterConfig(database, {signal}); + const db = res.Databases[0]; + + return {data: db.FeatureFlags}; + } catch (error) { + return {error}; + } + }, + }), }), overrideExisting: 'throw', }); diff --git a/src/types/api/featureFlags.ts b/src/types/api/featureFlags.ts new file mode 100644 index 0000000000..54676b4fe3 --- /dev/null +++ b/src/types/api/featureFlags.ts @@ -0,0 +1,14 @@ +export interface FeatureFlagConfig { + Name: string; + Current?: boolean; + Default?: boolean; +} + +interface ConfigDb { + Name: string; + FeatureFlags: FeatureFlagConfig[]; +} + +export interface FeatureFlagConfigs { + Databases: ConfigDb[]; +}