mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Feat: Feature toggle admin page frontend interface (#72164)
* feature toggles admin page proto * feature toggle admin page proto * keep phase 1 code only * latest update with api * fix * fix * add correct premissions in admin.go * move behind toggle * Use InteractiveTable * guard behind feature toggle * use RTK * route in api.go * fixes
This commit is contained in:
parent
2d98e95cc9
commit
0d48ac2419
@ -111,6 +111,12 @@ func (hs *HTTPServer) registerRoutes() {
|
||||
r.Get("/admin/storage", reqSignedIn, hs.Index)
|
||||
r.Get("/admin/storage/*", reqSignedIn, hs.Index)
|
||||
}
|
||||
|
||||
// feature toggle admin page
|
||||
if hs.Features.IsEnabled(featuremgmt.FlagFeatureToggleAdminPage) {
|
||||
r.Get("/admin/featuretoggles", authorize(ac.EvalPermission(ac.ActionFeatureManagementRead)), hs.Index)
|
||||
}
|
||||
|
||||
r.Get("/styleguide", reqSignedIn, hs.Index)
|
||||
|
||||
r.Get("/live", reqGrafanaAdmin, hs.Index)
|
||||
|
@ -131,6 +131,7 @@ func (root *NavTreeRoot) ApplyAdminIA() {
|
||||
adminNodeLinks = AppendIfNotNil(adminNodeLinks, root.FindById("authentication"))
|
||||
adminNodeLinks = AppendIfNotNil(adminNodeLinks, root.FindById("server-settings"))
|
||||
adminNodeLinks = AppendIfNotNil(adminNodeLinks, root.FindById("global-orgs"))
|
||||
adminNodeLinks = AppendIfNotNil(adminNodeLinks, root.FindById("feature-toggles"))
|
||||
|
||||
adminNodeLinks = AppendIfNotNil(adminNodeLinks, root.FindById("upgrading"))
|
||||
adminNodeLinks = AppendIfNotNil(adminNodeLinks, root.FindById("licensing"))
|
||||
|
@ -100,6 +100,12 @@ func (s *ServiceImpl) getAdminNode(c *contextmodel.ReqContext) (*navtree.NavLink
|
||||
})
|
||||
}
|
||||
|
||||
if s.features.IsEnabled(featuremgmt.FlagFeatureToggleAdminPage) && hasAccess(ac.EvalPermission(ac.ActionFeatureManagementRead)) {
|
||||
configNodes = append(configNodes, &navtree.NavLink{
|
||||
Text: "Feature Toggles", SubTitle: "View feature toggles", Id: "feature-toggles", Url: s.cfg.AppSubURL + "/admin/featuretoggles", Icon: "toggle-on",
|
||||
})
|
||||
}
|
||||
|
||||
if s.features.IsEnabled(featuremgmt.FlagCorrelations) && hasAccess(correlations.ConfigurationPageAccess) {
|
||||
configNodes = append(configNodes, &navtree.NavLink{
|
||||
Text: "Correlations",
|
||||
|
@ -2,6 +2,7 @@ import { ReducersMapObject } from '@reduxjs/toolkit';
|
||||
import { AnyAction, combineReducers } from 'redux';
|
||||
|
||||
import sharedReducers from 'app/core/reducers';
|
||||
import { togglesApi } from 'app/features/admin/AdminFeatureTogglesAPI';
|
||||
import ldapReducers from 'app/features/admin/state/reducers';
|
||||
import alertingReducers from 'app/features/alerting/state/reducers';
|
||||
import apiKeysReducers from 'app/features/api-keys/state/reducers';
|
||||
@ -55,6 +56,7 @@ const rootReducers = {
|
||||
[alertingApi.reducerPath]: alertingApi.reducer,
|
||||
[publicDashboardApi.reducerPath]: publicDashboardApi.reducer,
|
||||
[browseDashboardsAPI.reducerPath]: browseDashboardsAPI.reducer,
|
||||
[togglesApi.reducerPath]: togglesApi.reducer,
|
||||
};
|
||||
|
||||
const addedReducers = {};
|
||||
|
34
public/app/features/admin/AdminFeatureTogglesAPI.ts
Normal file
34
public/app/features/admin/AdminFeatureTogglesAPI.ts
Normal file
@ -0,0 +1,34 @@
|
||||
import { createApi, BaseQueryFn } from '@reduxjs/toolkit/query/react';
|
||||
import { lastValueFrom } from 'rxjs';
|
||||
|
||||
import { getBackendSrv } from '@grafana/runtime';
|
||||
|
||||
const backendSrvBaseQuery =
|
||||
({ baseUrl }: { baseUrl: string }): BaseQueryFn<{ url: string }> =>
|
||||
async ({ url }) => {
|
||||
try {
|
||||
const { data } = await lastValueFrom(getBackendSrv().fetch({ url: baseUrl + url }));
|
||||
return { data };
|
||||
} catch (error) {
|
||||
return { error };
|
||||
}
|
||||
};
|
||||
|
||||
export const togglesApi = createApi({
|
||||
reducerPath: 'togglesApi',
|
||||
baseQuery: backendSrvBaseQuery({ baseUrl: '/api' }),
|
||||
endpoints: (builder) => ({
|
||||
getFeatureToggles: builder.query<FeatureToggle[], void>({
|
||||
query: () => ({ url: '/featuremgmt' }),
|
||||
}),
|
||||
}),
|
||||
});
|
||||
|
||||
type FeatureToggle = {
|
||||
name: string;
|
||||
enabled: boolean;
|
||||
description: string;
|
||||
};
|
||||
|
||||
export const { useGetFeatureTogglesQuery } = togglesApi;
|
||||
export type { FeatureToggle };
|
26
public/app/features/admin/AdminFeatureTogglesPage.tsx
Normal file
26
public/app/features/admin/AdminFeatureTogglesPage.tsx
Normal file
@ -0,0 +1,26 @@
|
||||
import React from 'react';
|
||||
|
||||
import { Page } from 'app/core/components/Page/Page';
|
||||
|
||||
import { useGetFeatureTogglesQuery } from './AdminFeatureTogglesAPI';
|
||||
import { AdminFeatureTogglesTable } from './AdminFeatureTogglesTable';
|
||||
|
||||
export default function AdminFeatureTogglesPage() {
|
||||
const { data: featureToggles, isLoading, isError } = useGetFeatureTogglesQuery();
|
||||
|
||||
const getErrorMessage = () => {
|
||||
return 'Error fetching feature toggles';
|
||||
};
|
||||
|
||||
return (
|
||||
<Page navId="feature-toggles">
|
||||
<Page.Contents>
|
||||
<>
|
||||
{isError && getErrorMessage()}
|
||||
{isLoading && 'Fetching feature toggles'}
|
||||
{featureToggles && <AdminFeatureTogglesTable featureToggles={featureToggles} />}
|
||||
</>
|
||||
</Page.Contents>
|
||||
</Page>
|
||||
);
|
||||
}
|
35
public/app/features/admin/AdminFeatureTogglesTable.tsx
Normal file
35
public/app/features/admin/AdminFeatureTogglesTable.tsx
Normal file
@ -0,0 +1,35 @@
|
||||
import React from 'react';
|
||||
|
||||
import { Switch, InteractiveTable, type CellProps } from '@grafana/ui';
|
||||
|
||||
import { type FeatureToggle } from './AdminFeatureTogglesAPI';
|
||||
|
||||
interface Props {
|
||||
featureToggles: FeatureToggle[];
|
||||
}
|
||||
|
||||
export function AdminFeatureTogglesTable({ featureToggles }: Props) {
|
||||
const columns = [
|
||||
{
|
||||
id: 'name',
|
||||
header: 'Name',
|
||||
cell: ({ cell: { value } }: CellProps<FeatureToggle, string>) => <div>{value}</div>,
|
||||
},
|
||||
{
|
||||
id: 'description',
|
||||
header: 'Description',
|
||||
cell: ({ cell: { value } }: CellProps<FeatureToggle, string>) => <div>{value}</div>,
|
||||
},
|
||||
{
|
||||
id: 'enabled',
|
||||
header: 'State',
|
||||
cell: ({ cell: { value } }: CellProps<FeatureToggle, boolean>) => (
|
||||
<div>
|
||||
<Switch value={value} disabled={true} />
|
||||
</div>
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
return <InteractiveTable columns={columns} data={featureToggles} getRowId={(featureToggle) => featureToggle.name} />;
|
||||
}
|
@ -357,6 +357,14 @@ export function getAppRoutes(): RouteDescriptor[] {
|
||||
() => import(/* webpackChunkName: "AdminEditOrgPage" */ 'app/features/admin/AdminEditOrgPage')
|
||||
),
|
||||
},
|
||||
{
|
||||
path: '/admin/featuretoggles',
|
||||
component: config.featureToggles.featureToggleAdminPage
|
||||
? SafeDynamicImport(
|
||||
() => import(/* webpackChunkName: "AdminFeatureTogglesPage" */ 'app/features/admin/AdminFeatureTogglesPage')
|
||||
)
|
||||
: () => <Redirect to="/admin" />,
|
||||
},
|
||||
{
|
||||
path: '/admin/storage/:path*',
|
||||
roles: () => ['Admin'],
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { configureStore as reduxConfigureStore, createListenerMiddleware } from '@reduxjs/toolkit';
|
||||
import { setupListeners } from '@reduxjs/toolkit/query';
|
||||
|
||||
import { togglesApi } from 'app/features/admin/AdminFeatureTogglesAPI';
|
||||
import { browseDashboardsAPI } from 'app/features/browse-dashboards/api/browseDashboardsAPI';
|
||||
import { publicDashboardApi } from 'app/features/dashboard/api/publicDashboardApi';
|
||||
import { StoreState } from 'app/types/store';
|
||||
@ -28,7 +29,8 @@ export function configureStore(initialState?: Partial<StoreState>) {
|
||||
listenerMiddleware.middleware,
|
||||
alertingApi.middleware,
|
||||
publicDashboardApi.middleware,
|
||||
browseDashboardsAPI.middleware
|
||||
browseDashboardsAPI.middleware,
|
||||
togglesApi.middleware
|
||||
),
|
||||
devTools: process.env.NODE_ENV !== 'production',
|
||||
preloadedState: {
|
||||
|
Loading…
Reference in New Issue
Block a user