+
diff --git a/src/containers/Configs/components/Startup/Startup.tsx b/src/containers/Configs/components/Startup/Startup.tsx
new file mode 100644
index 0000000000..1a59840e19
--- /dev/null
+++ b/src/containers/Configs/components/Startup/Startup.tsx
@@ -0,0 +1,49 @@
+import {useThemeValue} from '@gravity-ui/uikit';
+import MonacoEditor from 'react-monaco-editor';
+
+import {ResponseError} from '../../../../components/Errors/ResponseError';
+import {LoaderWrapper} from '../../../../components/LoaderWrapper/LoaderWrapper';
+import {configsApi} from '../../../../store/reducers/configs';
+import {useAutoRefreshInterval} from '../../../../utils/hooks/useAutoRefreshInterval';
+
+interface StartupProps {
+ database?: string;
+ className?: string;
+}
+
+const EDITOR_OPTIONS = {
+ automaticLayout: true,
+ selectOnLineNumbers: true,
+ readOnly: true,
+ minimap: {
+ enabled: false,
+ },
+ wrappingIndent: 'indent' as const,
+};
+
+export function Startup({database, className}: StartupProps) {
+ const [autoRefreshInterval] = useAutoRefreshInterval();
+ const theme = useThemeValue();
+ const {currentData, isLoading, error} = configsApi.useGetConfigQuery(
+ {database},
+ {pollingInterval: autoRefreshInterval},
+ );
+
+ const {startup} = currentData || {};
+
+ return (
+
+ {error ? : null}
+ {startup ? (
+
+
+
+ ) : null}
+
+ );
+}
diff --git a/src/containers/Tenant/Diagnostics/Configs/i18n/en.json b/src/containers/Configs/i18n/en.json
similarity index 53%
rename from src/containers/Tenant/Diagnostics/Configs/i18n/en.json
rename to src/containers/Configs/i18n/en.json
index 6c8b079f1c..f009c88cf5 100644
--- a/src/containers/Tenant/Diagnostics/Configs/i18n/en.json
+++ b/src/containers/Configs/i18n/en.json
@@ -7,7 +7,13 @@
"disabled": "Disabled",
"flag-touched": "Flag is changed",
- "search-placeholder": "Search by feature flag",
+ "search-placeholder": "Search...",
"search-empty": "Empty search result",
- "no-data": "No data"
+ "no-data": "No data",
+
+ "title_current": "Current",
+ "title_startup": "Startup",
+ "title_features": "Feature flags",
+
+ "action_copy-config": "Copy config"
}
diff --git a/src/containers/Tenant/Diagnostics/Configs/i18n/index.ts b/src/containers/Configs/i18n/index.ts
similarity index 67%
rename from src/containers/Tenant/Diagnostics/Configs/i18n/index.ts
rename to src/containers/Configs/i18n/index.ts
index b730046175..a146812817 100644
--- a/src/containers/Tenant/Diagnostics/Configs/i18n/index.ts
+++ b/src/containers/Configs/i18n/index.ts
@@ -1,4 +1,4 @@
-import {registerKeysets} from '../../../../../utils/i18n';
+import {registerKeysets} from '../../../utils/i18n';
import en from './en.json';
diff --git a/src/containers/Configs/types.ts b/src/containers/Configs/types.ts
new file mode 100644
index 0000000000..08d5efc3f0
--- /dev/null
+++ b/src/containers/Configs/types.ts
@@ -0,0 +1,24 @@
+import {z} from 'zod';
+
+import i18n from './i18n';
+
+export const ConfigTypes = {
+ current: 'current',
+ features: 'features',
+ startup: 'startup',
+} as const;
+
+export const configTypesSchema = z.nativeEnum(ConfigTypes).catch(ConfigTypes.current);
+export type ConfigType = z.infer;
+
+export const ConfigTypeTitles: Record = {
+ get current() {
+ return i18n('title_current');
+ },
+ get features() {
+ return i18n('title_features');
+ },
+ get startup() {
+ return i18n('title_startup');
+ },
+};
diff --git a/src/containers/Configs/useConfigsQueryParams.ts b/src/containers/Configs/useConfigsQueryParams.ts
new file mode 100644
index 0000000000..a1fd50e01b
--- /dev/null
+++ b/src/containers/Configs/useConfigsQueryParams.ts
@@ -0,0 +1,61 @@
+import React from 'react';
+
+import {StringParam, createEnumParam, useQueryParams, withDefault} from 'use-query-params';
+
+import {
+ useBaseConfigAvailable,
+ useFeatureFlagsAvailable,
+} from '../../store/reducers/capabilities/hooks';
+
+import type {ConfigType} from './types';
+import {ConfigTypes} from './types';
+
+const configTypesArray: ConfigType[] = [
+ ConfigTypes.current,
+ ConfigTypes.startup,
+ ConfigTypes.features,
+];
+
+export const ConfigTypeValueEnum = createEnumParam(configTypesArray);
+export const ConfigTypeValueParam = withDefault(
+ ConfigTypeValueEnum,
+ 'current',
+);
+
+export function useConfigQueryParams() {
+ const isFeaturesAvailable = useFeatureFlagsAvailable();
+ const isConfigsAvailable = useBaseConfigAvailable();
+ const [{configType, search}, setQueryParams] = useQueryParams({
+ configType: ConfigTypeValueParam,
+ search: StringParam,
+ });
+ const handleConfigTypeChange = React.useCallback(
+ (value?: ConfigType) => {
+ setQueryParams({configType: value || undefined}, 'replaceIn');
+ },
+ [setQueryParams],
+ );
+ const handleSearchChange = React.useCallback(
+ (value?: string) => {
+ setQueryParams({search: value || undefined}, 'replaceIn');
+ },
+ [setQueryParams],
+ );
+
+ React.useEffect(() => {
+ if (!isConfigsAvailable && !isFeaturesAvailable) {
+ handleConfigTypeChange(undefined);
+ } else if (!isFeaturesAvailable && configType === ConfigTypes.features) {
+ handleConfigTypeChange(ConfigTypes.current);
+ } else {
+ handleConfigTypeChange(ConfigTypes.features);
+ }
+ }, [isFeaturesAvailable, isConfigsAvailable]);
+
+ return {
+ configType,
+ handleConfigTypeChange,
+ search,
+ handleSearchChange,
+ };
+}
diff --git a/src/containers/Header/Header.scss b/src/containers/Header/Header.scss
index 96fe8ff7c1..10a34be38f 100644
--- a/src/containers/Header/Header.scss
+++ b/src/containers/Header/Header.scss
@@ -4,6 +4,8 @@
justify-content: space-between;
align-items: center;
+ height: var(--header-height, auto);
+ max-height: var(--header-height, auto);
padding: 0 var(--g-spacing-5);
border-bottom: 1px solid var(--g-color-line-generic);
diff --git a/src/containers/Node/Node.scss b/src/containers/Node/Node.scss
index d0db7fa48a..9a6470fbf1 100644
--- a/src/containers/Node/Node.scss
+++ b/src/containers/Node/Node.scss
@@ -1,6 +1,8 @@
@use '../../styles/mixins';
.node {
+ --ydb-configs-controls-height: 62px;
+ --ydb-configs-container-height: calc(100vh - var(--header-height));
position: relative;
overflow: auto;
@@ -31,4 +33,7 @@
&__tabs {
@include mixins.tabs-wrapper-styles();
}
+ &__treads {
+ --ydb-table-with-controls-layout-controls-height: 0px;
+ }
}
diff --git a/src/containers/Node/Node.tsx b/src/containers/Node/Node.tsx
index 6de74259f0..09421b36fd 100644
--- a/src/containers/Node/Node.tsx
+++ b/src/containers/Node/Node.tsx
@@ -15,6 +15,7 @@ import {PageMetaWithAutorefresh} from '../../components/PageMeta/PageMeta';
import routes from '../../routes';
import {
useCapabilitiesLoaded,
+ useConfigAvailable,
useDiskPagesAvailable,
} from '../../store/reducers/capabilities/hooks';
import {setHeaderBreadcrumbs} from '../../store/reducers/header/header';
@@ -22,7 +23,9 @@ import {nodeApi} from '../../store/reducers/node/node';
import type {PreparedNode} from '../../store/reducers/node/types';
import {cn} from '../../utils/cn';
import {useAutoRefreshInterval, useTypedDispatch} from '../../utils/hooks';
+import {useIsViewerUser} from '../../utils/hooks/useIsUserAllowedToMakeChanges';
import {useAppTitle} from '../App/AppTitleContext';
+import {Configs} from '../Configs/Configs';
import {PaginatedStorage} from '../Storage/PaginatedStorage';
import {Tablets} from '../Tablets/Tablets';
@@ -40,6 +43,10 @@ const STORAGE_ROLE = 'Storage';
export function Node() {
const container = React.useRef(null);
+ const isViewerUser = useIsViewerUser();
+ const hasConfigs = useConfigAvailable();
+
+ const configsAvailable = isViewerUser && hasConfigs;
const dispatch = useTypedDispatch();
@@ -71,22 +78,26 @@ export function Node() {
const threadsQuantity = node?.Threads?.length;
const {activeTab, nodeTabs} = React.useMemo(() => {
- let actualNodeTabs = isStorageNode
- ? NODE_TABS
- : NODE_TABS.filter((el) => el.id !== 'storage');
+ const skippedTabs: NodeTab[] = [];
+ if (!isStorageNode) {
+ skippedTabs.push('storage');
+ }
+ if (!configsAvailable) {
+ skippedTabs.push('configs');
+ }
if (isDiskPagesAvailable) {
- actualNodeTabs = actualNodeTabs.filter((el) => el.id !== 'structure');
+ skippedTabs.push('structure');
}
- // Filter out threads tab if there's no thread data in the API response
if (!threadsQuantity) {
- actualNodeTabs = actualNodeTabs.filter((el) => el.id !== 'threads');
+ skippedTabs.push('threads');
}
+ const actualNodeTabs = NODE_TABS.filter((el) => !skippedTabs.includes(el.id));
const actualActiveTab =
actualNodeTabs.find(({id}) => id === activeTabId) ?? actualNodeTabs[0];
return {activeTab: actualActiveTab, nodeTabs: actualNodeTabs};
- }, [isStorageNode, isDiskPagesAvailable, activeTabId, threadsQuantity]);
+ }, [isStorageNode, isDiskPagesAvailable, activeTabId, threadsQuantity, configsAvailable]);
const database = tenantNameFromQuery?.toString();
@@ -255,7 +266,17 @@ function NodePageContent({
}
case 'threads': {
- return ;
+ return (
+
+ );
+ }
+
+ case 'configs': {
+ return ;
}
default:
diff --git a/src/containers/Node/NodePages.ts b/src/containers/Node/NodePages.ts
index 3f4c23ac13..2b8a5a5638 100644
--- a/src/containers/Node/NodePages.ts
+++ b/src/containers/Node/NodePages.ts
@@ -12,6 +12,7 @@ const NODE_TABS_IDS = {
tablets: 'tablets',
structure: 'structure',
threads: 'threads',
+ configs: 'configs',
} as const;
export type NodeTab = ValueOf;
@@ -41,6 +42,12 @@ export const NODE_TABS = [
return i18n('tabs.threads');
},
},
+ {
+ id: NODE_TABS_IDS.configs,
+ get title() {
+ return i18n('tabs.configs');
+ },
+ },
];
export const nodePageTabSchema = z.nativeEnum(NODE_TABS_IDS).catch(NODE_TABS_IDS.tablets);
diff --git a/src/containers/Node/Threads/Threads.tsx b/src/containers/Node/Threads/Threads.tsx
index 2328ada020..859dad6d74 100644
--- a/src/containers/Node/Threads/Threads.tsx
+++ b/src/containers/Node/Threads/Threads.tsx
@@ -1,6 +1,6 @@
import {ResponseError} from '../../../components/Errors/ResponseError';
-import {LoaderWrapper} from '../../../components/LoaderWrapper/LoaderWrapper';
import {ResizeableDataTable} from '../../../components/ResizeableDataTable/ResizeableDataTable';
+import {TableWithControlsLayout} from '../../../components/TableWithControlsLayout/TableWithControlsLayout';
import {nodeApi} from '../../../store/reducers/node/node';
import {DEFAULT_TABLE_SETTINGS} from '../../../utils/constants';
import {useAutoRefreshInterval} from '../../../utils/hooks';
@@ -11,11 +11,12 @@ import i18n from './i18n';
interface ThreadsProps {
nodeId: string;
className?: string;
+ scrollContainerRef: React.RefObject;
}
const THREADS_COLUMNS_WIDTH_LS_KEY = 'threadsTableColumnsWidth';
-export function Threads({nodeId, className}: ThreadsProps) {
+export function Threads({nodeId, className, scrollContainerRef}: ThreadsProps) {
const [autoRefreshInterval] = useAutoRefreshInterval();
const {
@@ -27,15 +28,18 @@ export function Threads({nodeId, className}: ThreadsProps) {
const data = nodeData?.Threads || [];
return (
-
+
{error ? : null}
-
-
+
+
+
+
);
}
diff --git a/src/containers/Node/i18n/en.json b/src/containers/Node/i18n/en.json
index 0762ce7af3..38ea152935 100644
--- a/src/containers/Node/i18n/en.json
+++ b/src/containers/Node/i18n/en.json
@@ -6,6 +6,7 @@
"tabs.structure": "Structure",
"tabs.tablets": "Tablets",
"tabs.threads": "Threads",
+ "tabs.configs": "Configs",
"node": "Node",
"fqdn": "FQDN",
diff --git a/src/containers/Tenant/Diagnostics/Describe/Describe.tsx b/src/containers/Tenant/Diagnostics/Describe/Describe.tsx
index 92c3eeb262..906d0291dc 100644
--- a/src/containers/Tenant/Diagnostics/Describe/Describe.tsx
+++ b/src/containers/Tenant/Diagnostics/Describe/Describe.tsx
@@ -1,5 +1,3 @@
-import {ClipboardButton} from '@gravity-ui/uikit';
-
import {ResponseError} from '../../../../components/Errors/ResponseError';
import {JsonViewer} from '../../../../components/JsonViewer/JsonViewer';
import {useUnipikaConvert} from '../../../../components/JsonViewer/unipika/unipika';
@@ -45,12 +43,10 @@ const Describe = ({path, database, databaseFullPath}: IDescribeProps) => {
- }
+ withClipboardButton={{
+ withLabel: false,
+ copyText: JSON.stringify(currentData),
+ }}
search
collapsedInitially
/>
diff --git a/src/containers/Tenant/Diagnostics/Diagnostics.scss b/src/containers/Tenant/Diagnostics/Diagnostics.scss
index cf349c27af..94c229d652 100644
--- a/src/containers/Tenant/Diagnostics/Diagnostics.scss
+++ b/src/containers/Tenant/Diagnostics/Diagnostics.scss
@@ -26,6 +26,10 @@
}
&__page-wrapper {
+ --ydb-configs-controls-height: 46px;
+ --ydb-configs-container-height: 100cqh;
+ --ydb-table-with-controls-layout-controls-height: 44px;
+ --ydb-table-with-controls-layout-controls-padding-top: 0px;
overflow: auto;
flex-grow: 1;
@@ -37,20 +41,8 @@
margin-top: var(--diagnostics-margin-top);
padding: 0 var(--g-spacing-5);
- .ydb-table-with-controls-layout {
- &__controls {
- height: 46px;
- padding-top: 0;
- }
-
- &__controls-wrapper {
- align-items: flex-start;
- }
-
- .data-table__sticky_moving,
- .ydb-paginated-table__head {
- top: 46px !important;
- }
- }
+ // Use container query units to fix the height to this specific container
+ // This prevents inheritance issues with percentage-based calculations
+ container-type: size;
}
}
diff --git a/src/containers/Tenant/Diagnostics/Diagnostics.tsx b/src/containers/Tenant/Diagnostics/Diagnostics.tsx
index c7daa945b5..f8eb4fcb73 100644
--- a/src/containers/Tenant/Diagnostics/Diagnostics.tsx
+++ b/src/containers/Tenant/Diagnostics/Diagnostics.tsx
@@ -7,7 +7,7 @@ import {AutoRefreshControl} from '../../../components/AutoRefreshControl/AutoRef
import {DrawerContextProvider} from '../../../components/Drawer/DrawerContext';
import {InternalLink} from '../../../components/InternalLink';
import {
- useFeatureFlagsAvailable,
+ useConfigAvailable,
useTopicDataAvailable,
} from '../../../store/reducers/capabilities/hooks';
import {TENANT_DIAGNOSTICS_TABS_IDS} from '../../../store/reducers/tenant/constants';
@@ -17,6 +17,7 @@ import {uiFactory} from '../../../uiFactory/uiFactory';
import {cn} from '../../../utils/cn';
import {useScrollPosition, useTypedDispatch, useTypedSelector} from '../../../utils/hooks';
import {useIsViewerUser} from '../../../utils/hooks/useIsUserAllowedToMakeChanges';
+import {Configs} from '../../Configs/Configs';
import {Heatmap} from '../../Heatmap';
import {Nodes} from '../../Nodes/Nodes';
import {Operations} from '../../Operations';
@@ -27,7 +28,6 @@ import {useCurrentSchema} from '../TenantContext';
import {isDatabaseEntityType} from '../utils/schema';
import {AccessRights} from './AccessRights/AccessRights';
-import {Configs} from './Configs/Configs';
import {Consumers} from './Consumers';
import Describe from './Describe/Describe';
import DetailedOverview from './DetailedOverview/DetailedOverview';
@@ -63,15 +63,14 @@ function Diagnostics(props: DiagnosticsProps) {
isDatabaseEntityType(type) ? database : '',
);
- const hasFeatureFlags = useFeatureFlagsAvailable();
+ const hasConfigs = useConfigAvailable();
const hasTopicData = useTopicDataAvailable();
const isViewerUser = useIsViewerUser();
const pages = getPagesByType(type, subType, {
- hasFeatureFlags,
hasTopicData,
isTopLevel: path === database,
hasBackups: typeof uiFactory.renderBackups === 'function' && Boolean(controlPlane),
- hasConfigs: isViewerUser,
+ hasConfigs: isViewerUser && hasConfigs,
hasAccess: uiFactory.hasAccess,
databaseType,
});
diff --git a/src/containers/Tenant/Diagnostics/DiagnosticsPages.ts b/src/containers/Tenant/Diagnostics/DiagnosticsPages.ts
index 87a4de5a4a..67462186ab 100644
--- a/src/containers/Tenant/Diagnostics/DiagnosticsPages.ts
+++ b/src/containers/Tenant/Diagnostics/DiagnosticsPages.ts
@@ -16,7 +16,6 @@ type Page = {
};
interface GetPagesOptions {
- hasFeatureFlags?: boolean;
hasTopicData?: boolean;
isTopLevel?: boolean;
hasBackups?: boolean;
@@ -231,12 +230,7 @@ export const getPagesByType = (
const dbContext = isDatabaseEntityType(type) || options?.isTopLevel;
const seeded = dbContext ? getDatabasePages(options?.databaseType) : base;
- let withFlags = seeded;
- if (!options?.hasFeatureFlags) {
- withFlags = seeded.filter((p) => p.id !== TENANT_DIAGNOSTICS_TABS_IDS.configs);
- }
-
- return applyFilters(withFlags, type, options);
+ return applyFilters(seeded, type, options);
};
export const useDiagnosticsPageLinkGetter = () => {
diff --git a/src/containers/Tenant/Query/QueriesHistory/QueriesHistory.scss b/src/containers/Tenant/Query/QueriesHistory/QueriesHistory.scss
index 26a16c55dd..6fa3a97008 100644
--- a/src/containers/Tenant/Query/QueriesHistory/QueriesHistory.scss
+++ b/src/containers/Tenant/Query/QueriesHistory/QueriesHistory.scss
@@ -1,6 +1,7 @@
@use '../../../../styles/mixins.scss';
.ydb-queries-history {
+ --ydb-table-with-controls-layout-controls-height: 44px;
overflow: auto;
height: 100%;
@@ -9,14 +10,10 @@
@include mixins.flex-container();
.ydb-table-with-controls-layout__controls {
- height: 46px;
+ height: var(--ydb-table-with-controls-layout-controls-height);
padding-top: 0;
}
- &.ydb-table-with-controls-layout .data-table__sticky_moving {
- top: 46px !important;
- }
-
&__search {
@include mixins.search();
}
diff --git a/src/containers/Tenant/Query/SavedQueries/SavedQueries.scss b/src/containers/Tenant/Query/SavedQueries/SavedQueries.scss
index 165d339847..1d9962ba7a 100644
--- a/src/containers/Tenant/Query/SavedQueries/SavedQueries.scss
+++ b/src/containers/Tenant/Query/SavedQueries/SavedQueries.scss
@@ -3,6 +3,8 @@
.ydb-saved-queries {
$block: &;
+ --ydb-table-with-controls-layout-controls-height: 44px;
+
overflow: auto;
height: 100%;
@@ -11,14 +13,10 @@
@include mixins.flex-container();
.ydb-table-with-controls-layout__controls {
- height: 46px;
+ height: var(--ydb-table-with-controls-layout-controls-height);
padding-top: 0;
}
- &.ydb-table-with-controls-layout .data-table__sticky_moving {
- top: 46px !important;
- }
-
&__search {
@include mixins.search();
}
diff --git a/src/services/api/viewer.ts b/src/services/api/viewer.ts
index 1826d51ee5..6d9fba9a6f 100644
--- a/src/services/api/viewer.ts
+++ b/src/services/api/viewer.ts
@@ -522,7 +522,7 @@ export class ViewerAPI extends BaseYdbAPI {
);
}
- getClusterConfig(database?: string, {concurrentId, signal}: AxiosOptions = {}) {
+ getFeatureFlags(database?: string, {concurrentId, signal}: AxiosOptions = {}) {
return this.get(
this.getPath('/viewer/feature_flags'),
{
@@ -531,6 +531,15 @@ export class ViewerAPI extends BaseYdbAPI {
{concurrentId, requestConfig: {signal}},
);
}
+ getConfig(database?: string, {concurrentId, signal}: AxiosOptions = {}) {
+ return this.get>(
+ this.getPath('/viewer/config'),
+ {
+ database,
+ },
+ {concurrentId, requestConfig: {signal}},
+ );
+ }
getVDiskBlobIndexStat(
{
diff --git a/src/store/reducers/capabilities/hooks.ts b/src/store/reducers/capabilities/hooks.ts
index 7787636b3c..4fe87b3fae 100644
--- a/src/store/reducers/capabilities/hooks.ts
+++ b/src/store/reducers/capabilities/hooks.ts
@@ -87,6 +87,16 @@ export const useClusterDashboardAvailable = () => {
export const useStreamingAvailable = () => {
return useGetFeatureVersion('/viewer/query') >= 8;
};
+export const useBaseConfigAvailable = () => {
+ return useGetFeatureVersion('/viewer/config') >= 1;
+};
+
+export const useConfigAvailable = () => {
+ const isBaseConfigsAvailable = useBaseConfigAvailable();
+ const isFeaturesAvailable = useFeatureFlagsAvailable();
+ return isBaseConfigsAvailable || isFeaturesAvailable;
+};
+
export const useEditAccessAvailable = () => {
return useGetFeatureVersion('/viewer/acl') >= 2 && !uiFactory.hideGrantAccess;
};
diff --git a/src/store/reducers/configs.ts b/src/store/reducers/configs.ts
new file mode 100644
index 0000000000..51eccf541e
--- /dev/null
+++ b/src/store/reducers/configs.ts
@@ -0,0 +1,34 @@
+import {api} from './api';
+
+export const configsApi = api.injectEndpoints({
+ endpoints: (build) => ({
+ getFeatureFlags: build.query({
+ queryFn: async ({database}: {database?: string}, {signal}) => {
+ try {
+ const res = await window.api.viewer.getFeatureFlags(database, {signal});
+ const db = res.Databases[0];
+
+ return {data: db.FeatureFlags};
+ } catch (error) {
+ return {error};
+ }
+ },
+ providesTags: ['All'],
+ }),
+ getConfig: build.query({
+ queryFn: async ({database}: {database?: string}, {signal}) => {
+ try {
+ const res = await window.api.viewer.getConfig(database, {signal});
+
+ const {StartupConfigYaml, ...rest} = res;
+
+ return {data: {current: rest, startup: String(StartupConfigYaml)}};
+ } catch (error) {
+ return {error};
+ }
+ },
+ providesTags: ['All'],
+ }),
+ }),
+ overrideExisting: 'throw',
+});
diff --git a/src/store/reducers/tenant/tenant.ts b/src/store/reducers/tenant/tenant.ts
index 395f535d96..9cd40d182e 100644
--- a/src/store/reducers/tenant/tenant.ts
+++ b/src/store/reducers/tenant/tenant.ts
@@ -108,20 +108,6 @@ export const tenantApi = api.injectEndpoints({
return {clusterName, database};
},
}),
-
- getClusterConfig: builder.query({
- queryFn: async ({database}: {database: string}, {signal}) => {
- try {
- const res = await window.api.viewer.getClusterConfig(database, {signal});
- const db = res.Databases[0];
-
- return {data: db.FeatureFlags};
- } catch (error) {
- return {error};
- }
- },
- providesTags: ['All'],
- }),
}),
overrideExisting: 'throw',
});
diff --git a/src/styles/mixins.scss b/src/styles/mixins.scss
index a991d07f51..5ccd333d18 100644
--- a/src/styles/mixins.scss
+++ b/src/styles/mixins.scss
@@ -57,9 +57,9 @@
line-height: var(--g-text-header-2-line-height);
}
-@mixin sticky-top {
+@mixin sticky-top($top: 0) {
position: sticky;
- top: 0;
+ top: $top;
left: 0;
background-color: var(--g-color-base-background);
@@ -88,12 +88,12 @@
text-overflow: ellipsis;
}
-@mixin controls() {
+@mixin controls($padding-top: var(--g-spacing-4)) {
display: flex;
align-items: center;
gap: 12px;
- padding: 16px 0 18px;
+ padding: $padding-top 0 var(--g-spacing-4);
}
@mixin search() {
diff --git a/src/types/api/capabilities.ts b/src/types/api/capabilities.ts
index 769ca52428..728085ab5f 100644
--- a/src/types/api/capabilities.ts
+++ b/src/types/api/capabilities.ts
@@ -21,6 +21,7 @@ export type Capability =
| '/storage/groups'
| '/viewer/query'
| '/viewer/feature_flags'
+ | '/viewer/config'
| '/viewer/cluster'
| '/viewer/nodes'
| '/viewer/acl'