Update Settings Page components (#2986)

Signed-off-by: Benjamin Perez <benjamin@bexsoft.net>
This commit is contained in:
Alex
2023-08-09 18:30:42 -06:00
committed by GitHub
parent 93bd0d65e2
commit 72bb9d0ca1
10 changed files with 189 additions and 404 deletions

View File

@@ -1,181 +0,0 @@
import React, { useEffect, useState } from "react";
import { Box, Tab, TabProps } from "@mui/material";
import { TabContext, TabList, TabPanel } from "@mui/lab";
import withStyles from "@mui/styles/withStyles";
import { Theme, useTheme } from "@mui/material/styles";
import createStyles from "@mui/styles/createStyles";
import useMediaQuery from "@mui/material/useMediaQuery";
import { useLocation } from "react-router-dom";
export type TabItemProps = {
tabConfig: TabProps | any;
content?: JSX.Element | JSX.Element[];
};
type VerticalTabsProps = {
classes: any;
children: TabItemProps[];
selectedTab?: string;
routes?: any;
isRouteTabs?: boolean;
};
const styles = (theme: Theme) =>
createStyles({
tabsContainer: {
display: "flex",
height: "100%",
width: "100%",
},
tabsHeaderContainer: {
width: "300px",
background: "#F8F8F8",
borderRight: "1px solid #EAEAEA",
"& .MuiTabs-root": {
"& .MuiTabs-indicator": {
display: "none",
},
"& .MuiTab-root": {
display: "flex",
flexFlow: "row",
alignItems: "center",
justifyContent: "flex-start",
borderBottom: "1px solid #EAEAEA",
"& .MuiSvgIcon-root": {
marginRight: 8,
marginBottom: 0,
},
"&.Mui-selected": {
background: "#E5E5E5",
fontWeight: 600,
},
},
"&. MuiTabs-scroller": {
display: "none",
},
},
},
tabContentContainer: {
width: "100%",
"& .MuiTabPanel-root": {
height: "100%",
},
},
tabPanel: {
height: "100%",
},
/*Below md breakpoint make it horizontal and style it for scrolling tabs*/
"@media (max-width: 900px)": {
tabsContainer: {
flexFlow: "column",
flexDirection: "column",
},
tabsHeaderContainer: {
width: "100%",
borderBottom: " 1px solid #EAEAEA",
"& .MuiTabs-root .MuiTabs-scroller .MuiButtonBase-root": {
borderBottom: " 0px",
},
},
},
});
const tabStripStyle = {
minHeight: 60,
};
const VerticalTabs = ({
children,
classes,
selectedTab = "0",
routes,
isRouteTabs,
}: VerticalTabsProps) => {
const theme = useTheme();
const { pathname = "" } = useLocation();
const isSmallScreen = useMediaQuery(theme.breakpoints.down("md"));
const [value, setValue] = useState(selectedTab);
const headerList: TabProps[] = [];
const contentList: React.ReactNode[] = [];
useEffect(() => {
if (isRouteTabs) {
const tabConfigElement = children.find(
(item) => item.tabConfig.to === pathname,
);
if (tabConfigElement) {
setValue(tabConfigElement.tabConfig.value);
}
}
}, [isRouteTabs, children, pathname]);
if (!children) return null;
children.forEach((child) => {
headerList.push(child.tabConfig);
contentList.push(child.content);
});
const handleChange = (event: React.SyntheticEvent, newValue: string) => {
setValue(newValue);
};
return (
<TabContext value={`${value}`}>
<Box className={classes.tabsContainer}>
<Box className={classes.tabsHeaderContainer}>
<TabList
onChange={handleChange}
orientation={isSmallScreen ? "horizontal" : "vertical"}
variant={isSmallScreen ? "scrollable" : "standard"}
scrollButtons="auto"
className={classes.tabList}
>
{headerList.map((item, index) => {
if (item) {
return (
<Tab
className={classes.tabHeader}
key={`v-tab-${index}`}
value={`${index}`}
style={tabStripStyle}
{...item}
disableRipple
disableTouchRipple
focusRipple={true}
/>
);
}
return null;
})}
</TabList>
</Box>
<Box className={classes.tabContentContainer}>
{!isRouteTabs
? contentList.map((item, index) => {
return (
<TabPanel
classes={{ ...classes.tabPanel }}
key={`v-tab-p-${index}`}
value={`${index}`}
>
{item ? item : null}
</TabPanel>
);
})
: null}
{isRouteTabs ? (
<div className={classes.tabPanel}>{routes}</div>
) : null}
</Box>
</Box>
</TabContext>
);
};
export default withStyles(styles)(VerticalTabs);

View File

@@ -15,27 +15,31 @@
// along with this program. If not, see <http://www.gnu.org/licenses/>.
import React, { Fragment, useCallback, useEffect, useState } from "react";
import { Theme } from "@mui/material/styles";
import createStyles from "@mui/styles/createStyles";
import withStyles from "@mui/styles/withStyles";
import Grid from "@mui/material/Grid";
import {
Box,
Grid,
HelpBox,
PageLayout,
ScreenTitle,
SettingsIcon,
Tabs,
} from "mds";
import { configurationElements } from "../utils";
import {
actionsTray,
containerForHeader,
searchField,
} from "../../Common/FormComponents/common/styleLibrary";
import { HelpBox, PageLayout, SettingsIcon } from "mds";
import { Link, Navigate, Route, Routes, useLocation } from "react-router-dom";
import VerticalTabs from "../../Common/VerticalTabs/VerticalTabs";
import ScreenTitle from "../../Common/ScreenTitle/ScreenTitle";
Navigate,
Route,
Routes,
useLocation,
useNavigate,
} from "react-router-dom";
import ConfigurationForm from "./ConfigurationForm";
import { IAM_PAGES } from "../../../../common/SecureComponent/permissions";
import PageHeaderWrapper from "../../Common/PageHeaderWrapper/PageHeaderWrapper";
import ExportConfigButton from "./ExportConfigButton";
import ImportConfigButton from "./ImportConfigButton";
import { Box } from "@mui/material";
import HelpMenu from "../../HelpMenu";
import { setErrorSnackMessage, setHelpName } from "../../../../systemSlice";
import { useAppDispatch } from "../../../../store";
@@ -43,26 +47,6 @@ import { api } from "../../../../api";
import { IElement } from "../types";
import { errorToHandler } from "../../../../api/errors";
interface IConfigurationOptions {
classes: any;
}
const styles = (theme: Theme) =>
createStyles({
settingsOptionsContainer: {
display: "flex" as const,
flexDirection: "row" as const,
justifyContent: "flex-start" as const,
flexWrap: "wrap" as const,
border: "#E5E5E5 1px solid",
borderRadius: 2,
backgroundColor: "#fff",
},
...searchField,
...actionsTray,
...containerForHeader,
});
const getRoutePath = (path: string) => {
return `${IAM_PAGES.SETTINGS}/${path}`;
};
@@ -71,9 +55,10 @@ const getRoutePath = (path: string) => {
const NON_SUB_SYS_CONFIG_ITEMS = ["region"];
const IGNORED_CONFIG_SUB_SYS = ["cache"]; // cache config is not supported.
const ConfigurationOptions = ({ classes }: IConfigurationOptions) => {
const ConfigurationOptions = () => {
const { pathname = "" } = useLocation();
const dispatch = useAppDispatch();
const navigate = useNavigate();
const [configSubSysList, setConfigSubSysList] = useState<string[]>([]);
const fetchConfigSubSysList = useCallback(async () => {
@@ -99,8 +84,6 @@ const ConfigurationOptions = ({ classes }: IConfigurationOptions) => {
});
}, [dispatch]);
let selConfigTab = pathname.substring(pathname.lastIndexOf("/") + 1);
selConfigTab = selConfigTab === "settings" ? "region" : selConfigTab;
useEffect(() => {
fetchConfigSubSysList();
dispatch(setHelpName("settings_Region"));
@@ -121,59 +104,57 @@ const ConfigurationOptions = ({ classes }: IConfigurationOptions) => {
<Fragment>
<PageHeaderWrapper label={"Settings"} actions={<HelpMenu />} />
<PageLayout>
<Grid item xs={12}>
<div
id="settings-container"
className={classes.settingsOptionsContainer}
>
<ScreenTitle
icon={<SettingsIcon />}
title={"MinIO Configuration:"}
actions={
<Box
sx={{
display: "flex",
gap: 2,
}}
>
<ImportConfigButton />
<ExportConfigButton />
</Box>
}
/>
<VerticalTabs
selectedTab={selConfigTab}
isRouteTabs
routes={
<Routes>
{availableConfigSubSys.map((element) => (
<Route
key={`configItem-${element.configuration_label}`}
path={`${element.configuration_id}`}
element={<ConfigurationForm />}
/>
))}
<Grid item xs={12} id={"settings-container"}>
<ScreenTitle
icon={<SettingsIcon />}
title={"MinIO Configuration:"}
actions={
<Box
sx={{
display: "flex",
gap: 10,
}}
>
<ImportConfigButton />
<ExportConfigButton />
</Box>
}
sx={{ marginBottom: 15 }}
/>
<Tabs
currentTabOrPath={pathname}
onTabClick={(path) => {
navigate(path);
}}
useRouteTabs
options={availableConfigSubSys.map((element) => {
const { configuration_id, configuration_label, icon } = element;
return {
tabConfig: {
id: `settings-tab-${configuration_label}`,
label: configuration_label,
value: configuration_id,
icon: icon,
to: getRoutePath(configuration_id),
},
};
})}
routes={
<Routes>
{availableConfigSubSys.map((element) => (
<Route
path={"/"}
element={<Navigate to={`${IAM_PAGES.SETTINGS}/region`} />}
key={`configItem-${element.configuration_label}`}
path={`${element.configuration_id}`}
element={<ConfigurationForm />}
/>
</Routes>
}
>
{availableConfigSubSys.map((element) => {
const { configuration_id, configuration_label, icon } = element;
return {
tabConfig: {
label: configuration_label,
value: configuration_id,
icon: icon,
component: Link,
to: getRoutePath(configuration_id),
},
};
})}
</VerticalTabs>
</div>
))}
<Route
path={"/"}
element={<Navigate to={`${IAM_PAGES.SETTINGS}/region`} />}
/>
</Routes>
}
/>
</Grid>
<Grid item xs={12} sx={{ paddingTop: "15px" }}>
<HelpBox
@@ -203,4 +184,4 @@ const ConfigurationOptions = ({ classes }: IConfigurationOptions) => {
);
};
export default withStyles(styles)(ConfigurationOptions);
export default ConfigurationOptions;

View File

@@ -1,3 +1,19 @@
// This file is part of MinIO Console Server
// Copyright (c) 2021 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
import React from "react";
import { Button, UploadIcon } from "mds";
import useApi from "../../Common/Hooks/useApi";

View File

@@ -1,3 +1,19 @@
// This file is part of MinIO Console Server
// Copyright (c) 2021 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
import React, { Fragment, useEffect, useRef, useState } from "react";
import { Button, DownloadIcon } from "mds";
import useApi from "../../Common/Hooks/useApi";

View File

@@ -14,38 +14,27 @@
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
import React, { useEffect, useState } from "react";
import { Theme } from "@mui/material/styles";
import createStyles from "@mui/styles/createStyles";
import withStyles from "@mui/styles/withStyles";
import Grid from "@mui/material/Grid";
import { IElementValue, IOverrideEnv, KVField } from "../Configurations/types";
import React, { Fragment, useEffect, useState } from "react";
import {
formFieldStyles,
modalBasic,
} from "../Common/FormComponents/common/styleLibrary";
CommentBox,
ConsoleIcon,
FormLayout,
Grid,
InputBox,
ReadBox,
Switch,
Tooltip,
} from "mds";
import { IElementValue, IOverrideEnv, KVField } from "../Configurations/types";
import CSVMultiSelector from "../Common/FormComponents/CSVMultiSelector/CSVMultiSelector";
import CommentBoxWrapper from "../Common/FormComponents/CommentBoxWrapper/CommentBoxWrapper";
import PredefinedList from "../Common/FormComponents/PredefinedList/PredefinedList";
import { ConsoleIcon, InputBox, Switch, Tooltip } from "mds";
interface IConfGenericProps {
onChange: (newValue: IElementValue[]) => void;
fields: KVField[];
defaultVals?: IElementValue[];
overrideEnv?: IOverrideEnv;
classes: any;
}
const styles = (theme: Theme) =>
createStyles({
...formFieldStyles,
formFieldRow: {
...formFieldStyles.formFieldRow,
},
...modalBasic,
});
// Function to get defined values,
//we make this because the backed sometimes don't return all the keys when there is an initial configuration
export const valueDef = (
@@ -71,7 +60,6 @@ const ConfTargetGeneric = ({
fields,
defaultVals,
overrideEnv,
classes,
}: IConfGenericProps) => {
const [valueHolder, setValueHolder] = useState<IElementValue[]>([]);
const fieldsElements = !fields ? [] : fields;
@@ -113,9 +101,8 @@ const ConfTargetGeneric = ({
if (override) {
return (
<PredefinedList
<ReadBox
label={field.label}
content={override.value}
actionButton={
<Grid
item
@@ -133,7 +120,10 @@ const ConfTargetGeneric = ({
</Tooltip>
</Grid>
}
/>
sx={{ width: "100%" }}
>
{override.value}
</ReadBox>
);
}
}
@@ -180,15 +170,13 @@ const ConfTargetGeneric = ({
);
case "comment":
return (
<CommentBoxWrapper
<CommentBox
id={field.name}
name={field.name}
label={field.label}
tooltip={field.tooltip}
value={holderItem ? holderItem.value : ""}
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
setValueElement(field.name, e.target.value, item)
}
onChange={(e) => setValueElement(field.name, e.target.value, item)}
placeholder={field.placeholder}
/>
);
@@ -210,16 +198,12 @@ const ConfTargetGeneric = ({
};
return (
<Grid container>
<Grid xs={12} item className={classes.fieldBox}>
{fieldsElements.map((field, item) => (
<Grid item xs={12} key={field.name} className={classes.formFieldRow}>
{fieldDefinition(field, item)}
</Grid>
))}
</Grid>
</Grid>
<FormLayout withBorders={false} containerPadding={false}>
{fieldsElements.map((field, item) => (
<Fragment key={field.name}>{fieldDefinition(field, item)}</Fragment>
))}
</FormLayout>
);
};
export default withStyles(styles)(ConfTargetGeneric);
export default ConfTargetGeneric;

View File

@@ -15,20 +15,13 @@
// along with this program. If not, see <http://www.gnu.org/licenses/>.
import React, { Fragment, useCallback, useEffect, useState } from "react";
import { Button, Loader } from "mds";
import { useLocation, useNavigate } from "react-router-dom";
import get from "lodash/get";
import { Theme } from "@mui/material/styles";
import createStyles from "@mui/styles/createStyles";
import withStyles from "@mui/styles/withStyles";
import { Box } from "@mui/material";
import Grid from "@mui/material/Grid";
import ConfTargetGeneric from "../ConfTargetGeneric";
import {
fieldBasic,
settingsCommon,
} from "../../Common/FormComponents/common/styleLibrary";
import { Box, Button, Grid, Loader } from "mds";
import { useLocation, useNavigate } from "react-router-dom";
import { useSelector } from "react-redux";
import { api } from "api";
import { Configuration, ConfigurationKV } from "api/consoleApi";
import { errorToHandler } from "api/errors";
import {
fieldsConfigurations,
overrideFields,
@@ -40,7 +33,6 @@ import {
IOverrideEnv,
KVField,
} from "../../Configurations/types";
import ResetConfigurationModal from "./ResetConfigurationModal";
import {
configurationIsLoading,
setErrorSnackMessage,
@@ -50,31 +42,16 @@ import {
} from "../../../../systemSlice";
import { AppState, useAppDispatch } from "../../../../store";
import WebhookSettings from "../WebhookSettings/WebhookSettings";
import { useSelector } from "react-redux";
import { api } from "api";
import { Configuration, ConfigurationKV } from "api/consoleApi";
import { errorToHandler } from "api/errors";
const styles = (theme: Theme) =>
createStyles({
...fieldBasic,
...settingsCommon,
settingsFormContainer: {
display: "grid",
gridTemplateColumns: "1fr",
gridGap: "10px",
},
});
import ConfTargetGeneric from "../ConfTargetGeneric";
import ResetConfigurationModal from "./ResetConfigurationModal";
interface IAddNotificationEndpointProps {
selectedConfiguration: IConfigurationElement;
classes: any;
className?: string;
}
const EditConfiguration = ({
selectedConfiguration,
classes,
className = "",
}: IAddNotificationEndpointProps) => {
const dispatch = useAppDispatch();
@@ -238,7 +215,15 @@ const EditConfiguration = ({
flexFlow: "column",
}}
>
<Grid item xs={12} className={classes.settingsFormContainer}>
<Grid
item
xs={12}
sx={{
display: "grid",
gridTemplateColumns: "1fr",
gap: "10px",
}}
>
<ConfTargetGeneric
fields={
fieldsConfigurations[
@@ -263,6 +248,7 @@ const EditConfiguration = ({
}}
>
<Button
type={"button"}
id={"restore-defaults"}
variant="secondary"
onClick={resetConfigurationMOpen}
@@ -287,4 +273,4 @@ const EditConfiguration = ({
);
};
export default withStyles(styles)(EditConfiguration);
export default EditConfiguration;

View File

@@ -15,11 +15,11 @@
// along with this program. If not, see <http://www.gnu.org/licenses/>.
import React, { Fragment, useState } from "react";
import { Button, Grid } from "mds";
import InputBoxWrapper from "../../Common/FormComponents/InputBoxWrapper/InputBoxWrapper";
import { Button, FormLayout, Grid, InputBox } from "mds";
import { api } from "api";
import { errorToHandler } from "api/errors";
import ModalWrapper from "../../Common/ModalWrapper/ModalWrapper";
import { Webhook } from "@mui/icons-material";
import { formFieldStyles } from "../../Common/FormComponents/common/styleLibrary";
import CallToActionIcon from "@mui/icons-material/CallToAction";
import PendingActionsIcon from "@mui/icons-material/PendingActions";
import {
@@ -30,8 +30,7 @@ import {
} from "../../../../systemSlice";
import { useAppDispatch } from "../../../../store";
import { LinearProgress } from "@mui/material";
import { api } from "api";
import { errorToHandler } from "api/errors";
import { modalStyleUtils } from "../../Common/FormComponents/common/styleLibrary";
interface IEndpointModal {
open: boolean;
@@ -148,8 +147,8 @@ const AddEndpointModal = ({ open, type, onCloseEndpoint }: IEndpointModal) => {
onClose={onCloseEndpoint}
titleIcon={icon}
>
<Grid item xs={12} sx={{ ...formFieldStyles.formFieldRow }}>
<InputBoxWrapper
<FormLayout containerPadding={false} withBorders={false}>
<InputBox
id="name"
name="name"
onChange={(event: React.ChangeEvent<HTMLInputElement>) => {
@@ -167,9 +166,7 @@ const AddEndpointModal = ({ open, type, onCloseEndpoint }: IEndpointModal) => {
pattern={"^(?=.*[a-zA-Z0-9]).{1,}$"}
required
/>
</Grid>
<Grid item xs={12} sx={{ ...formFieldStyles.formFieldRow }}>
<InputBoxWrapper
<InputBox
id="endpoint"
name="endpoint"
onChange={(event: React.ChangeEvent<HTMLInputElement>) => {
@@ -190,9 +187,7 @@ const AddEndpointModal = ({ open, type, onCloseEndpoint }: IEndpointModal) => {
}
required
/>
</Grid>
<Grid item xs={12} sx={{ ...formFieldStyles.formFieldRow }}>
<InputBoxWrapper
<InputBox
id="auth-token"
name="auth-token"
onChange={(event: React.ChangeEvent<HTMLInputElement>) => {
@@ -202,7 +197,7 @@ const AddEndpointModal = ({ open, type, onCloseEndpoint }: IEndpointModal) => {
label="Auth Token"
value={authToken}
/>
</Grid>
</FormLayout>
{saving && (
<Grid
item
@@ -214,14 +209,7 @@ const AddEndpointModal = ({ open, type, onCloseEndpoint }: IEndpointModal) => {
<LinearProgress />
</Grid>
)}
<Grid
item
xs={12}
sx={{
display: "flex",
justifyContent: "flex-end",
}}
>
<Grid item xs={12} sx={modalStyleUtils.modalButtonBar}>
<Button
id={"reset"}
type="button"

View File

@@ -14,9 +14,10 @@
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
import React, { useEffect, useState } from "react";
import React, { useEffect, useState, Fragment } from "react";
import { ConfirmDeleteIcon } from "mds";
import { DialogContentText } from "@mui/material";
import { api } from "api";
import { errorToHandler } from "api/errors";
import ConfirmDialog from "../../Common/ModalWrapper/ConfirmDialog";
import {
configurationIsLoading,
@@ -24,8 +25,6 @@ import {
setServerNeedsRestart,
} from "../../../../systemSlice";
import { useAppDispatch } from "../../../../store";
import { api } from "api";
import { errorToHandler } from "api/errors";
interface IDeleteWebhookEndpoint {
modalOpen: boolean;
@@ -38,7 +37,6 @@ const DeleteWebhookEndpoint = ({
modalOpen,
onClose,
selectedARN,
type,
}: IDeleteWebhookEndpoint) => {
const [deleteLoading, setDeleteLoading] = useState<boolean>(false);
@@ -84,10 +82,10 @@ const DeleteWebhookEndpoint = ({
titleIcon={<ConfirmDeleteIcon />}
onClose={onClose}
confirmationContent={
<DialogContentText>
<Fragment>
{`${message} `}
<strong>{selectedARN}</strong>?
</DialogContentText>
</Fragment>
}
/>
);

View File

@@ -43,11 +43,10 @@ test("All vertical tab items exist", async (t) => {
const settingsHealTabExists = elements.settingsHealTab.exists;
const settingsScannerTabExists = elements.settingsScannerTab.exists;
const settingsEtcdTabExists = elements.settingsEtcdTab.exists;
const settingsOpenIdTabExists = elements.settingsOpenIdTab.exists;
const settingsLdapTabExists = elements.settingsLdapTab.exists;
const settingsLoggerWebhookTabExists =
elements.settingsLoggerWebhookTab.exists;
const settingsAuditWebhookTabExists = elements.settingsAuditWebhookTab.exists;
const settingsAuditKafkaTabExists = elements.settingsAuditKafkaTab.exists;
await t
.navigateTo("http://localhost:9090/settings/configurations")
.expect(settingsRegionTabExists)
@@ -65,5 +64,7 @@ test("All vertical tab items exist", async (t) => {
.expect(settingsLoggerWebhookTabExists)
.ok()
.expect(settingsAuditWebhookTabExists)
.ok()
.expect(settingsAuditKafkaTabExists)
.ok();
});

View File

@@ -123,45 +123,41 @@ export const settingsWindow = Selector("#settings-container");
//----------------------------------------------------
// Settings page vertical tabs
//----------------------------------------------------
export const settingsRegionTab = Selector(".MuiTab-root").withAttribute(
"href",
"/settings/configurations/region",
export const settingsRegionTab = Selector("button").withAttribute(
"id",
"settings-tab-Region",
);
export const settingsCompressionTab = Selector(".MuiTab-root").withAttribute(
"href",
"/settings/configurations/compression",
export const settingsCompressionTab = Selector("button").withAttribute(
"id",
"settings-tab-Compression",
);
export const settingsApiTab = Selector(".MuiTab-root").withAttribute(
"href",
"/settings/configurations/api",
export const settingsApiTab = Selector("button").withAttribute(
"id",
"settings-tab-API",
);
export const settingsHealTab = Selector(".MuiTab-root").withAttribute(
"href",
"/settings/configurations/heal",
export const settingsHealTab = Selector("button").withAttribute(
"id",
"settings-tab-Heal",
);
export const settingsScannerTab = Selector(".MuiTab-root").withAttribute(
"href",
"/settings/configurations/scanner",
export const settingsScannerTab = Selector("button").withAttribute(
"id",
"settings-tab-Scanner",
);
export const settingsEtcdTab = Selector(".MuiTab-root").withAttribute(
"href",
"/settings/configurations/etcd",
export const settingsEtcdTab = Selector("button").withAttribute(
"id",
"settings-tab-Etcd",
);
export const settingsOpenIdTab = Selector(".MuiTab-root").withAttribute(
"href",
"/settings/configurations/identity_openid",
export const settingsLoggerWebhookTab = Selector("button").withAttribute(
"id",
"settings-tab-Logger Webhook",
);
export const settingsLdapTab = Selector(".MuiTab-root").withAttribute(
"href",
"/settings/configurations/identity_ldap",
export const settingsAuditWebhookTab = Selector("button").withAttribute(
"id",
"settings-tab-Audit Webhook",
);
export const settingsLoggerWebhookTab = Selector(".MuiTab-root").withAttribute(
"href",
"/settings/configurations/logger_webhook",
);
export const settingsAuditWebhookTab = Selector(".MuiTab-root").withAttribute(
"href",
"/settings/configurations/audit_webhook",
export const settingsAuditKafkaTab = Selector("button").withAttribute(
"id",
"settings-tab-Audit Kafka",
);
//----------------------------------------------------