Tools Page (#1180)

* Tools Page

Signed-off-by: Daniel Valdivia <18384552+dvaldivia@users.noreply.github.com>

* Help Boxes

Signed-off-by: Daniel Valdivia <18384552+dvaldivia@users.noreply.github.com>

* Remove Un-Used code

Signed-off-by: Daniel Valdivia <18384552+dvaldivia@users.noreply.github.com>
This commit is contained in:
Daniel Valdivia
2021-11-02 16:41:52 -07:00
committed by GitHub
parent f4c90bbe7e
commit dc35bb2191
42 changed files with 1188 additions and 1789 deletions

View File

@@ -79,4 +79,4 @@ clean:
@rm -vf console
docker:
@docker build -t $(TAG) --build-arg build_version=$(BUILD_VERSION) --build-arg build_time='$(BUILD_TIME)' .
@docker buildx build --output=type=docker --platform linux/amd64 -t $(TAG) --build-arg build_version=$(BUILD_VERSION) --build-arg build_time='$(BUILD_TIME)' .

View File

@@ -1,5 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="227.615" height="256" viewBox="0 0 227.615 256">
<g id="lambda-icn" transform="translate(-89.849 -94.42)">
<path id="Trazado_442" data-name="Trazado 442" d="M71.316,41.17a15.363,15.363,0,0,0,0,30.727c23.352,0,32.017,14.872,40.928,34.353l1.475,3.134c1.229,2.643,3.872,8.542,7.436,16.408L37.271,274.264A15.363,15.363,0,0,0,64,289.382l72.823-128.623c19.481,44,44,99.494,44.431,100.415A58.2,58.2,0,0,0,252.3,294.544a15.375,15.375,0,0,0-9.1-29.375,27.531,27.531,0,0,1-33.861-16.593c-2.458-5.531-59.856-135.751-67.6-152.282l-1.352-2.95C132.462,76.506,116.3,41.17,71.316,41.17Z" transform="translate(54.335 53.25)" fill="#020202"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 692 B

View File

@@ -1,7 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="236.312" height="256" viewBox="0 0 236.312 256">
<g id="tiers-icn" transform="translate(-34.003 -3)">
<g id="tiers" transform="translate(34.003 3)">
<path id="Trazado_441" data-name="Trazado 441" d="M122.387,3a9.847,9.847,0,0,0-5.154,1.308L8.925,66.851a9.847,9.847,0,0,0,0,17.039l33.4,19.289-33.4,19.289a9.847,9.847,0,0,0,0,17.058l33.4,19.27-33.4,19.289a9.847,9.847,0,0,0,0,17.058l108.308,62.54a9.847,9.847,0,0,0,9.846,0l108.308-62.54a9.847,9.847,0,0,0,0-17.058l-33.4-19.289,33.4-19.27a9.847,9.847,0,0,0,0-17.058l-33.4-19.289,33.4-19.289a9.847,9.847,0,0,0,0-17.039L127.079,4.311A9.847,9.847,0,0,0,122.387,3ZM62.041,114.563l55.192,31.866a9.847,9.847,0,0,0,9.846,0l55.192-31.866,28.5,16.443L122.156,182.16,33.54,131.006Zm-.02,55.617,55.212,31.866a9.847,9.847,0,0,0,9.846,0l55.212-31.866,28.481,16.443-88.615,51.155L33.54,186.622Z" transform="translate(-4 -3)"/>
</g>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 942 B

9
iconos/Tools.svg Normal file
View File

@@ -0,0 +1,9 @@
<svg xmlns="http://www.w3.org/2000/svg" width="17" height="16.986" viewBox="0 0 17 16.986">
<g id="Tools" transform="translate(-56.747 -82.596)">
<g id="Grupo_1868" data-name="Grupo 1868">
<path id="Trazado_6843" data-name="Trazado 6843" d="M71.847,82.6,68.6,84.36l.016.961-2.387,2.387,2.411,2.411,2.42-2.42.871,0,1.82-3.206Z" fill="#bccee6"/>
<path id="Trazado_6844" data-name="Trazado 6844" d="M61.539,92.4l-4.525,4.525a.852.852,0,0,0,0,1.205l1.205,1.206a.853.853,0,0,0,1.206,0l4.524-4.525Z" fill="#bccee6"/>
</g>
<path id="Trazado_6845" data-name="Trazado 6845" d="M73.162,96.921l-9.736-9.735a3.381,3.381,0,0,0-4.152-4.153L61.343,85.1l-2.411,2.411-2.069-2.069A3.381,3.381,0,0,0,61.016,89.6l9.735,9.736a.853.853,0,0,0,1.206,0l1.205-1.206A.852.852,0,0,0,73.162,96.921Z" fill="#bccee6"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 828 B

View File

@@ -67,11 +67,13 @@ var (
remoteBuckets = "/remote-buckets"
replication = "/replication"
license = "/license"
watch = "/watch"
heal = "/heal"
trace = "/trace"
logs = "/logs"
healthInfo = "/health-info"
watch = "/tools/watch"
heal = "/tools/heal"
trace = "/tools/trace"
tools = "/tools"
logs = "/tools/logs"
auditLogs = "/tools/audit-logs"
healthInfo = "/tools/diagnostics"
)
type ConfigurationActionSet struct {
@@ -261,6 +263,16 @@ var logsActionSet = ConfigurationActionSet{
),
}
// toolsActionSet contains the list of admin actions required for this endpoint to work
var toolsActionSet = ConfigurationActionSet{
actionTypes: iampolicy.NewActionSet(
iampolicy.AllAdminActions,
),
actions: iampolicy.NewActionSet(
iampolicy.ConsoleLogAdminAction,
),
}
// traceActionSet contains the list of admin actions required for this endpoint to work
var traceActionSet = ConfigurationActionSet{
actionTypes: iampolicy.NewActionSet(
@@ -327,6 +339,8 @@ var endpointRules = map[string]ConfigurationActionSet{
heal: healActionSet,
trace: traceActionSet,
logs: logsActionSet,
auditLogs: logsActionSet,
tools: toolsActionSet,
healthInfo: healthInfoActionSet,
}

View File

@@ -70,7 +70,7 @@ func TestGetAuthorizedEndpoints(t *testing.T) {
"admin:*",
},
},
want: 30,
want: 32,
},
{
name: "all s3 endpoints",
@@ -89,7 +89,7 @@ func TestGetAuthorizedEndpoints(t *testing.T) {
"s3:*",
},
},
want: 32,
want: 34,
},
{
name: "Console User - default endpoints",

View File

@@ -27,6 +27,10 @@ const styles = (theme: Theme) =>
link: {
textDecoration: "none",
color: theme.palette.primary.main,
fontSize: 18,
fontWeight: 600,
marginBottom: 10,
marginTop: 10,
},
});

View File

@@ -1,15 +0,0 @@
import React from "react";
import PropTypes from "prop-types";
import Typography from "@mui/material/Typography";
export default function Title(props: React.Props<any>) {
return (
<Typography component="h2" variant="h6" color="primary" gutterBottom>
{props.children}
</Typography>
);
}
Title.propTypes = {
children: PropTypes.node,
};

View File

@@ -16,7 +16,6 @@
import React from "react";
import { SvgIcon, SvgIconProps } from "@mui/material";
import { IIcon } from "./props";
const DeleteIcon = (props: SvgIconProps) => {
return (

View File

@@ -0,0 +1,45 @@
// 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 * as React from "react";
import { SvgIcon, SvgIconProps } from "@mui/material";
const ToolsIcon = (props: SvgIconProps) => {
return (
<SvgIcon {...props}>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 17 16.986">
<g fill="#bccee6">
<g data-name="Grupo 1868">
<path
data-name="Trazado 6843"
d="M15.1.004l-3.247 1.76.016.961-2.387 2.387 2.411 2.411 2.42-2.42h.871l1.82-3.206z"
/>
<path
data-name="Trazado 6844"
d="M4.792 9.804L.267 14.329a.852.852 0 000 1.205l1.205 1.206a.853.853 0 001.206 0l4.524-4.525z"
/>
</g>
<path
data-name="Trazado 6845"
d="M16.415 14.325L6.679 4.59A3.381 3.381 0 002.527.437l2.069 2.067-2.411 2.411L.116 2.846a3.381 3.381 0 004.153 4.158l9.735 9.736a.853.853 0 001.206 0l1.205-1.206a.852.852 0 000-1.209z"
/>
</g>
</svg>
</SvgIcon>
);
};
export default ToolsIcon;

View File

@@ -102,3 +102,4 @@ export { default as UptimeIcon } from "./UptimeIcon";
export { default as LambdaIcon } from "./LambdaIcon";
export { default as TiersIcon } from "./TiersIcon";
export { default as OpenListIcon } from "./OpenListIcon";
export { default as ToolsIcon } from "./ToolsIcon";

View File

@@ -14,7 +14,7 @@
// 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, { Fragment, useEffect, useState } from "react";
import { connect } from "react-redux";
import { Theme } from "@mui/material/styles";
import createStyles from "@mui/styles/createStyles";
@@ -22,13 +22,12 @@ import withStyles from "@mui/styles/withStyles";
import Grid from "@mui/material/Grid";
import api from "../../../common/api";
import { Button, IconButton, Tooltip } from "@mui/material";
import Typography from "@mui/material/Typography";
import { NewServiceAccount } from "../Common/CredentialsPrompt/types";
import { setErrorSnackMessage } from "../../../actions";
import AddServiceAccount from "./AddServiceAccount";
import DeleteServiceAccount from "./DeleteServiceAccount";
import CredentialsPrompt from "../Common/CredentialsPrompt/CredentialsPrompt";
import { AddIcon, LockIcon } from "../../../icons";
import { AccountIcon, AddIcon, LockIcon } from "../../../icons";
import TextField from "@mui/material/TextField";
import InputAdornment from "@mui/material/InputAdornment";
import TableWrapper from "../Common/TableWrapper/TableWrapper";
@@ -42,6 +41,7 @@ import {
import { ErrorResponseHandler } from "../../../common/types";
import ChangePasswordModal from "./ChangePasswordModal";
import SearchIcon from "../../../icons/SearchIcon";
import HelpBox from "../../../common/HelpBox";
const styles = (theme: Theme) =>
createStyles({
@@ -75,6 +75,9 @@ const styles = (theme: Theme) =>
},
},
},
twHeight: {
minHeight: 600,
},
imageIcon: {
height: "100%",
},
@@ -212,7 +215,7 @@ const Account = ({
closeModal={() => setChangePasswordModalOpen(false)}
/>
<PageHeader
label="Account"
label="Service Accounts"
actions={
<React.Fragment>
{changePassword && (
@@ -231,60 +234,79 @@ const Account = ({
</React.Fragment>
}
/>
<Grid container>
<Grid item xs={12} className={classes.container}>
<Grid item xs={12}>
<Typography variant="h5" component="h5">
Service Accounts
</Typography>
</Grid>
<Grid item xs={12}>
<br />
</Grid>
<Grid item xs={12} className={classes.actionsTray}>
<TextField
placeholder="Search Service Accounts"
className={classes.searchField}
id="search-resource"
label=""
InputProps={{
disableUnderline: true,
startAdornment: (
<InputAdornment position="start">
<SearchIcon />
</InputAdornment>
),
}}
onChange={(e) => {
setFilter(e.target.value);
}}
variant="standard"
/>
<Button
variant="contained"
color="primary"
startIcon={<AddIcon />}
onClick={() => {
setAddScreenOpen(true);
setSelectedServiceAccount(null);
}}
>
Create service account
</Button>
</Grid>
<Grid item xs={12}>
<br />
</Grid>
<Grid item xs={12}>
<TableWrapper
isLoading={loading}
records={filteredRecords}
entityName={"Service Accounts"}
idField={""}
columns={[{ label: "Service Account", elementKey: "" }]}
itemActions={tableActions}
/>
</Grid>
<Grid container className={classes.container}>
<Grid item xs={12} className={classes.actionsTray}>
<TextField
placeholder="Search Service Accounts"
className={classes.searchField}
id="search-resource"
label=""
InputProps={{
disableUnderline: true,
startAdornment: (
<InputAdornment position="start">
<SearchIcon />
</InputAdornment>
),
}}
onChange={(e) => {
setFilter(e.target.value);
}}
variant="standard"
/>
<Button
variant="contained"
color="primary"
startIcon={<AddIcon />}
onClick={() => {
setAddScreenOpen(true);
setSelectedServiceAccount(null);
}}
>
Create service account
</Button>
</Grid>
<Grid item xs={12}>
<br />
</Grid>
<Grid item xs={12}>
<TableWrapper
isLoading={loading}
records={filteredRecords}
entityName={"Service Accounts"}
idField={""}
columns={[{ label: "Service Account", elementKey: "" }]}
itemActions={tableActions}
customPaperHeight={classes.twHeight}
/>
</Grid>
<Grid item xs={12}>
<HelpBox
title={"Learn more about SERVICE ACCOUNTS"}
iconComponent={<AccountIcon />}
help={
<Fragment>
MinIO service accounts are child identities of an authenticated
MinIO user, including externally managed identities. Each
service account inherits its privileges based on the policies
attached to its parent user or those groups in which the parent
user has membership. Service accounts also support an optional
inline policy which further restricts access to a subset of
actions and resources available to the parent user.
<br />
<br />
You can learn more at our{" "}
<a
href="https://docs.min.io/minio/baremetal/security/minio-identity-management/user-management.html?ref=con#service-accounts"
target="_blank"
rel="noreferrer"
>
documentation
</a>
.
</Fragment>
}
/>
</Grid>
</Grid>
</React.Fragment>

View File

@@ -25,15 +25,14 @@ import TextField from "@mui/material/TextField";
import InputAdornment from "@mui/material/InputAdornment";
import FileCopyIcon from "@mui/icons-material/FileCopy";
import { Bucket, BucketList, HasPermissionResponse } from "../types";
import {
AddIcon,
BucketsIcon,
WatchIcon,
} from "../../../../icons";
import { AddIcon, BucketsIcon, WatchIcon } from "../../../../icons";
import { AppState } from "../../../../store";
import { addBucketOpen, addBucketReset } from "../actions";
import { setErrorSnackMessage } from "../../../../actions";
import { containerForHeader, linkStyles } from "../../Common/FormComponents/common/styleLibrary";
import {
containerForHeader,
linkStyles,
} from "../../Common/FormComponents/common/styleLibrary";
import { ErrorResponseHandler } from "../../../../common/types";
import api from "../../../../common/api";
import AddBucket from "./AddBucket";

View File

@@ -24,6 +24,7 @@ import { IElement } from "../../Configurations/types";
interface ISettingsCard {
classes: any;
configuration: IElement;
prefix?: string;
}
const styles = (theme: Theme) =>
@@ -55,10 +56,14 @@ const styles = (theme: Theme) =>
},
});
const SettingsCard = ({ classes, configuration }: ISettingsCard) => {
const SettingsCard = ({
classes,
configuration,
prefix = "settings",
}: ISettingsCard) => {
return (
<Link
to={`/settings/${configuration.configuration_id}`}
to={`/${prefix}/${configuration.configuration_id}`}
className={classes.configurationLink}
>
{configuration.icon}

View File

@@ -1,84 +0,0 @@
// 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 } from "react";
import { AutoSizer } from "react-virtualized";
import createStyles from "@mui/styles/createStyles";
import withStyles from "@mui/styles/withStyles";
interface ISlideOptions {
classes: any;
slideOptions: any;
currentSlide: number;
}
const styles = () =>
createStyles({
masterContainer: {
overflowX: "hidden",
overflowY: "auto",
},
sliderContainer: {
width: "auto",
transitionDuration: "0.3s",
position: "relative",
},
slide: {
float: "left",
},
});
const SlideOptions = ({
classes,
slideOptions,
currentSlide,
}: ISlideOptions) => {
return (
<AutoSizer>
{({ width, height }: any) => {
const currentSliderPosition = currentSlide * width;
const containerSize = width * slideOptions.length;
return (
<Fragment>
<div className={classes.masterContainer} style={{ width, height }}>
<div
className={classes.sliderContainer}
style={{
left: `-${currentSliderPosition}px`,
width: `${containerSize}px`,
}}
>
{slideOptions.map((block: any, index: number) => {
return (
<div
className={classes.slide}
style={{ width }}
key={`slide-panel-${index.toString()}`}
>
{block}
</div>
);
})}
</div>
</div>
</Fragment>
);
}}
</AutoSizer>
);
};
export default withStyles(styles)(SlideOptions);

View File

@@ -1,21 +0,0 @@
import React from "react";
import { IIcon, selected, unSelected } from "./common";
const DescriptionIcon = ({ active = false }: IIcon) => {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
viewBox="0 0 10 11.429"
>
<path
fill={active ? selected : unSelected}
d="M-43.375,11.429-48.35,8.664l-5.025,2.764V0h10Z"
transform="translate(53.375)"
/>
</svg>
);
};
export default DescriptionIcon;

View File

@@ -28,6 +28,8 @@ import {
} from "../../Common/FormComponents/common/styleLibrary";
import PageHeader from "../../Common/PageHeader/PageHeader";
import SettingsCard from "../../Common/SettingsCard/SettingsCard";
import HelpBox from "../../../../common/HelpBox";
import { SettingsIcon } from "../../../../icons";
interface IConfigurationOptions {
classes: any;
@@ -91,6 +93,29 @@ const ConfigurationOptions = ({ classes }: IConfigurationOptions) => {
</div>
</Grid>
</Grid>
<Grid item xs={12}>
<HelpBox
title={"Learn more about SETTINGS"}
iconComponent={<SettingsIcon />}
help={
<Fragment>
MinIO supports a variety of configurations ranging from
encryption, compression, region, notifications, etc.
<br />
<br />
You can learn more at our{" "}
<a
href="https://docs.min.io/minio/baremetal/reference/minio-cli/minio-mc-admin/mc-admin.config.html?ref=con#id4"
target="_blank"
rel="noreferrer"
>
documentation
</a>
.
</Fragment>
}
/>
</Grid>
</Grid>
</Fragment>
);

View File

@@ -1,152 +0,0 @@
// 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, { useState, Fragment } from "react";
import get from "lodash/get";
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 history from "../../../../history";
import TableWrapper from "../../Common/TableWrapper/TableWrapper";
import { configurationElements } from "../utils";
import { IConfigurationElement } from "../types";
import EditConfiguration from "../../NotificationEndpoints/CustomForms/EditConfiguration";
import {
actionsTray,
containerForHeader,
searchField,
settingsCommon,
} from "../../Common/FormComponents/common/styleLibrary";
import SlideOptions from "../../Common/SlideOptions/SlideOptions";
import BackSettingsIcon from "../../../../icons/BackSettingsIcon";
interface IListConfiguration {
classes: any;
history: any;
}
const styles = (theme: Theme) =>
createStyles({
strongText: {
fontWeight: 700,
},
keyName: {
marginLeft: 5,
},
iconText: {
lineHeight: "24px",
},
customConfigurationPage: {
height: "calc(100vh - 324px)",
scrollbarWidth: "none" as const,
"&::-webkit-scrollbar": {
display: "none",
},
},
...searchField,
...actionsTray,
...settingsCommon,
...containerForHeader(theme.spacing(4)),
});
const initialConfiguration = {
configuration_id: "",
configuration_label: "",
};
const ConfigurationsList = ({ classes, history }: IListConfiguration) => {
const [selectedConfiguration, setSelectedConfiguration] =
useState(initialConfiguration);
const [currentConfiguration, setCurrentConfiguration] = useState<number>(0);
const tableActions = [
{
type: "edit",
onClick: (element: IConfigurationElement) => {
const url = get(element, "url", "");
if (url !== "") {
// We redirect Browser
history.push(url);
} else {
setCurrentConfiguration(1);
setSelectedConfiguration(element);
}
},
},
];
const backToInitialConfig = () => {
setCurrentConfiguration(0);
setSelectedConfiguration(initialConfiguration);
};
return (
<Fragment>
<Grid container>
<Grid item xs={12}>
<Grid item xs={12}>
<div className={classes.settingsOptionsContainer}>
<SlideOptions
slideOptions={[
<Fragment>
<TableWrapper
itemActions={tableActions}
columns={[
{
label: "Configuration",
elementKey: "configuration_id",
},
]}
isLoading={false}
records={configurationElements}
entityName="Configurations"
idField="configuration_id"
customPaperHeight={classes.customConfigurationPage}
noBackground
/>
</Fragment>,
<Fragment>
<Grid item xs={12} className={classes.backContainer}>
<button
onClick={backToInitialConfig}
className={classes.backButton}
>
<BackSettingsIcon />
Back To Configurations
</button>
</Grid>
<Grid item xs={12}>
{currentConfiguration === 1 ? (
<EditConfiguration
history={history}
selectedConfiguration={selectedConfiguration}
/>
) : null}
</Grid>
</Fragment>,
]}
currentSlide={currentConfiguration}
/>
</div>
</Grid>
</Grid>
</Grid>
</Fragment>
);
};
export default withStyles(styles)(ConfigurationsList);

View File

@@ -20,7 +20,7 @@ import { connect } from "react-redux";
import { Theme } from "@mui/material/styles";
import createStyles from "@mui/styles/createStyles";
import withStyles from "@mui/styles/withStyles";
import { IconButton, LinearProgress, TextField } from "@mui/material";
import { LinearProgress, TextField } from "@mui/material";
import Grid from "@mui/material/Grid";
import Button from "@mui/material/Button";
import InputAdornment from "@mui/material/InputAdornment";

View File

@@ -47,7 +47,6 @@ import ConfigurationMain from "./Configurations/ConfigurationMain";
import TenantDetails from "./Tenants/TenantDetails/TenantDetails";
import License from "./License/License";
import Trace from "./Trace/Trace";
import LogsMain from "./Logs/LogsMain";
import Heal from "./Heal/Heal";
import Watch from "./Watch/Watch";
import HealthInfo from "./HealthInfo/HealthInfo";
@@ -63,6 +62,9 @@ import ListTiersConfiguration from "./Configurations/TiersConfiguration/ListTier
import TierTypeSelector from "./Configurations/TiersConfiguration/TierTypeSelector";
import AddTierConfiguration from "./Configurations/TiersConfiguration/AddTierConfiguration";
import ListTenants from "./Tenants/ListTenants/ListTenants";
import Tools from "./Tools/Tools";
import ErrorLogs from "./Logs/ErrorLogs/ErrorLogs";
import LogsSearchMain from "./Logs/LogSearch/LogsSearchMain";
const drawerWidth = 245;
@@ -243,7 +245,7 @@ const Console = ({
},
{
component: Watch,
path: "/watch",
path: "/tools/watch",
},
{
component: Users,
@@ -267,19 +269,27 @@ const Console = ({
},
{
component: Heal,
path: "/heal",
path: "/tools/heal",
},
{
component: Trace,
path: "/trace",
},
{
component: LogsMain,
path: "/logs",
path: "/tools/trace",
},
{
component: HealthInfo,
path: "/health-info",
path: "/tools/diagnostics",
},
{
component: ErrorLogs,
path: "/tools/logs",
},
{
component: LogsSearchMain,
path: "/tools/audit-logs",
},
{
component: Tools,
path: "/tools",
},
{
component: ConfigurationMain,

View File

@@ -1,64 +0,0 @@
import React from "react";
import { useTheme } from "@mui/material/styles";
import {
LineChart,
Line,
XAxis,
YAxis,
Label,
ResponsiveContainer,
} from "recharts";
import Title from "../../../common/Title";
// Generate Sales Data
function createData(time: string, amount: number) {
return { time, amount };
}
const data = [
createData("00:00", 0),
createData("03:00", 300),
createData("06:00", 600),
createData("09:00", 800),
createData("12:00", 1500),
createData("15:00", 2000),
createData("18:00", 2400),
createData("21:00", 2400),
];
export default function Chart() {
const theme = useTheme();
return (
<React.Fragment>
<Title>Today</Title>
<ResponsiveContainer width="99%">
<LineChart
data={data}
margin={{
top: 16,
right: 16,
bottom: 0,
left: 24,
}}
>
<XAxis dataKey="time" stroke={theme.palette.text.secondary} />
<YAxis stroke={theme.palette.text.secondary}>
<Label
position="left"
style={{ textAnchor: "middle", fill: theme.palette.text.primary }}
>
Sales ($)
</Label>
</YAxis>
<Line
type="monotone"
dataKey="amount"
stroke={theme.palette.primary.main}
dot={false}
/>
</LineChart>
</ResponsiveContainer>
</React.Fragment>
);
}

View File

@@ -1,35 +0,0 @@
import React from "react";
import Link from "@mui/material/Link";
import makeStyles from "@mui/styles/makeStyles";
import Typography from "@mui/material/Typography";
import Title from "../../../common/Title";
function preventDefault(event: React.MouseEvent) {
event.preventDefault();
}
const useStyles = makeStyles({
depositContext: {
flex: 1,
},
});
export default function Deposits() {
const classes = useStyles();
return (
<React.Fragment>
<Title>Recent Deposits</Title>
<Typography component="p" variant="h4">
$3,024.00
</Typography>
<Typography color="textSecondary" className={classes.depositContext}>
on 15 March, 2019
</Typography>
<div>
<Link color="primary" href="#" onClick={preventDefault}>
View balance
</Link>
</div>
</React.Fragment>
);
}

View File

@@ -1,110 +0,0 @@
import * as React from "react";
import Link from "@mui/material/Link";
import makeStyles from "@mui/styles/makeStyles";
import Table from "@mui/material/Table";
import TableBody from "@mui/material/TableBody";
import TableCell from "@mui/material/TableCell";
import TableHead from "@mui/material/TableHead";
import TableRow from "@mui/material/TableRow";
import Title from "../../../common/Title";
// Generate Order Data
function createData(
id: number,
date: string,
name: string,
shipTo: string,
paymentMethod: string,
amount: number
) {
return { id, date, name, shipTo, paymentMethod, amount };
}
const rows = [
createData(
0,
"16 Mar, 2019",
"Elvis Presley",
"Tupelo, MS",
"VISA ⠀•••• 3719",
312.44
),
createData(
1,
"16 Mar, 2019",
"Paul McCartney",
"London, UK",
"VISA ⠀•••• 2574",
866.99
),
createData(
2,
"16 Mar, 2019",
"Tom Scholz",
"Boston, MA",
"MC ⠀•••• 1253",
100.81
),
createData(
3,
"16 Mar, 2019",
"Michael Jackson",
"Gary, IN",
"AMEX ⠀•••• 2000",
654.39
),
createData(
4,
"15 Mar, 2019",
"Bruce Springsteen",
"Long Branch, NJ",
"VISA ⠀•••• 5919",
212.79
),
];
function preventDefault(event: React.MouseEvent) {
event.preventDefault();
}
const useStyles = makeStyles((theme) => ({
seeMore: {
marginTop: theme.spacing(3),
},
}));
export default function Orders() {
const classes = useStyles();
return (
<React.Fragment>
<Title>Recent Orders</Title>
<Table size="small">
<TableHead>
<TableRow>
<TableCell>Date</TableCell>
<TableCell>Name</TableCell>
<TableCell>Ship To</TableCell>
<TableCell>Payment Method</TableCell>
<TableCell align="right">Sale Amount</TableCell>
</TableRow>
</TableHead>
<TableBody>
{rows.map((row) => (
<TableRow key={row.id}>
<TableCell>{row.date}</TableCell>
<TableCell>{row.name}</TableCell>
<TableCell>{row.shipTo}</TableCell>
<TableCell>{row.paymentMethod}</TableCell>
<TableCell align="right">{row.amount}</TableCell>
</TableRow>
))}
</TableBody>
</Table>
<div className={classes.seeMore}>
<Link color="primary" href="#" onClick={preventDefault}>
See more orders
</Link>
</div>
</React.Fragment>
);
}

View File

@@ -73,6 +73,9 @@ const styles = (theme: Theme) =>
whiteSpace: "normal",
wordWrap: "break-word",
},
twHeight: {
minHeight: 600,
},
minTableHeader: {
color: "#393939",
"& tr": {
@@ -241,6 +244,7 @@ const Groups = ({ classes, setErrorSnackMessage }: IGroupsProps) => {
records={filteredRecords}
entityName="Groups"
idField=""
customPaperHeight={classes.twHeight}
/>
</Grid>
<Grid item xs={12}>

View File

@@ -17,27 +17,35 @@
import React, { useEffect, useState } from "react";
import { connect } from "react-redux";
import { HorizontalBar } from "react-chartjs-2";
import { Button, Grid, TextField, InputBase } from "@mui/material";
import {
Button,
FormControl,
Grid,
InputBase,
MenuItem,
Select,
TextField,
} from "@mui/material";
import { IMessageEvent, w3cwebsocket as W3CWebSocket } from "websocket";
import { Theme } from "@mui/material/styles";
import createStyles from "@mui/styles/createStyles";
import withStyles from "@mui/styles/withStyles";
import { wsProtocol } from "../../../utils/wsUtils";
import { FormControl, MenuItem, Select } from "@mui/material";
import { BucketList, Bucket } from "../Watch/types";
import { HealStatus, colorH } from "./types";
import { Bucket, BucketList } from "../Watch/types";
import { colorH, HealStatus } from "./types";
import { niceBytes } from "../../../common/utils";
import {
actionsTray,
containerForHeader,
searchField,
inlineCheckboxes,
searchField,
} from "../Common/FormComponents/common/styleLibrary";
import { AppState } from "../../../store";
import { ErrorResponseHandler } from "../../../common/types";
import CheckboxWrapper from "../Common/FormComponents/CheckboxWrapper/CheckboxWrapper";
import PageHeader from "../Common/PageHeader/PageHeader";
import api from "../../../common/api";
import BackLink from "../../../common/BackLink";
const styles = (theme: Theme) =>
createStyles({
@@ -242,123 +250,124 @@ const Heal = ({ classes, distributedSetup }: IHeal) => {
<PageHeader label="Heal" />
<Grid container className={classes.container}>
<Grid item xs={12}>
<Grid item xs={12} className={classes.actionsTray}>
<FormControl variant="outlined">
<Select
id="bucket-name"
name="bucket-name"
value={bucketName}
onChange={(e) => {
setBucketName(e.target.value as string);
}}
className={classes.searchField}
input={<SelectStyled />}
displayEmpty
>
<MenuItem value="" key={`select-bucket-name-default`}>
Select Bucket
</MenuItem>
{bucketNames.map((option) => (
<MenuItem
value={option.value}
key={`select-bucket-name-${option.label}`}
>
{option.label}
</MenuItem>
))}
</Select>
</FormControl>
<TextField
placeholder="Prefix"
<BackLink to="/tools" label="Return to Tools" />
</Grid>
<Grid item xs={12} className={classes.actionsTray}>
<FormControl variant="outlined">
<Select
id="bucket-name"
name="bucket-name"
value={bucketName}
onChange={(e) => {
setBucketName(e.target.value as string);
}}
className={classes.searchField}
id="prefix-resource"
label=""
disabled={false}
InputProps={{
disableUnderline: true,
}}
onChange={(e) => {
setPrefix(e.target.value);
}}
variant="standard"
/>
<Button
type="submit"
variant="contained"
color="primary"
disabled={start}
onClick={() => setStart(true)}
input={<SelectStyled />}
displayEmpty
>
Start
</Button>
</Grid>
<Grid item xs={12} className={classes.inlineCheckboxes}>
<CheckboxWrapper
name="recursive"
id="recursive"
value="recursive"
checked={recursive}
onChange={(e) => {
setRecursive(e.target.checked);
}}
disabled={false}
label="Recursive"
/>
<CheckboxWrapper
name="forceStart"
id="forceStart"
value="forceStart"
checked={forceStart}
onChange={(e) => {
setForceStart(e.target.checked);
}}
disabled={false}
label="Force Start"
/>
<CheckboxWrapper
name="forceStop"
id="forceStop"
value="forceStop"
checked={forceStop}
onChange={(e) => {
setForceStop(e.target.checked);
}}
disabled={false}
label="Force Stop"
/>
</Grid>
<Grid item xs={12}>
<br />
</Grid>
<Grid item xs={12} className={classes.graphContainer}>
<HorizontalBar
data={data}
width={80}
height={30}
options={{
title: {
display: true,
text: "Item's Health Status [%]",
fontSize: 20,
},
legend: {
display: true,
position: "right",
},
}}
/>
<Grid item xs={12} className={classes.scanInfo}>
<div className={classes.scanData}>
<strong>Size scanned:</strong> {hStatus.sizeScanned}
</div>
<div className={classes.scanData}>
<strong>Objects healed:</strong> {hStatus.objectsHealed} /{" "}
{hStatus.objectsScanned}
</div>
<div className={classes.scanData}>
<strong>Healing time:</strong> {hStatus.healDuration}s
</div>
</Grid>
<MenuItem value="" key={`select-bucket-name-default`}>
Select Bucket
</MenuItem>
{bucketNames.map((option) => (
<MenuItem
value={option.value}
key={`select-bucket-name-${option.label}`}
>
{option.label}
</MenuItem>
))}
</Select>
</FormControl>
<TextField
placeholder="Prefix"
className={classes.searchField}
id="prefix-resource"
label=""
disabled={false}
InputProps={{
disableUnderline: true,
}}
onChange={(e) => {
setPrefix(e.target.value);
}}
variant="standard"
/>
<Button
type="submit"
variant="contained"
color="primary"
disabled={start}
onClick={() => setStart(true)}
>
Start
</Button>
</Grid>
<Grid item xs={12} className={classes.inlineCheckboxes}>
<CheckboxWrapper
name="recursive"
id="recursive"
value="recursive"
checked={recursive}
onChange={(e) => {
setRecursive(e.target.checked);
}}
disabled={false}
label="Recursive"
/>
<CheckboxWrapper
name="forceStart"
id="forceStart"
value="forceStart"
checked={forceStart}
onChange={(e) => {
setForceStart(e.target.checked);
}}
disabled={false}
label="Force Start"
/>
<CheckboxWrapper
name="forceStop"
id="forceStop"
value="forceStop"
checked={forceStop}
onChange={(e) => {
setForceStop(e.target.checked);
}}
disabled={false}
label="Force Stop"
/>
</Grid>
<Grid item xs={12}>
<br />
</Grid>
<Grid item xs={12} className={classes.graphContainer}>
<HorizontalBar
data={data}
width={80}
height={30}
options={{
title: {
display: true,
text: "Item's Health Status [%]",
fontSize: 20,
},
legend: {
display: true,
position: "right",
},
}}
/>
<Grid item xs={12} className={classes.scanInfo}>
<div className={classes.scanData}>
<strong>Size scanned:</strong> {hStatus.sizeScanned}
</div>
<div className={classes.scanData}>
<strong>Objects healed:</strong> {hStatus.objectsHealed} /{" "}
{hStatus.objectsScanned}
</div>
<div className={classes.scanData}>
<strong>Healing time:</strong> {hStatus.healDuration}s
</div>
</Grid>
</Grid>
</Grid>

View File

@@ -13,38 +13,39 @@
//
// 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, { useState, useEffect } from "react";
import React, { useEffect, useState } from "react";
import {
ICloseEvent,
IMessageEvent,
w3cwebsocket as W3CWebSocket,
ICloseEvent,
} from "websocket";
import { AppState } from "../../../store";
import { connect } from "react-redux";
import { healthInfoMessageReceived, healthInfoResetMessage } from "./actions";
import {
HealthInfoMessage,
DiagStatError,
DiagStatInProgress,
DiagStatSuccess,
DiagStatError,
HealthInfoMessage,
} from "./types";
import { Theme } from "@mui/material/styles";
import createStyles from "@mui/styles/createStyles";
import withStyles from "@mui/styles/withStyles";
import {
wsProtocol,
WSCloseAbnormalClosure,
WSCloseInternalServerErr,
WSClosePolicyViolation,
WSCloseAbnormalClosure,
wsProtocol,
} from "../../../utils/wsUtils";
import {
actionsTray,
containerForHeader,
} from "../Common/FormComponents/common/styleLibrary";
import { Grid, Button } from "@mui/material";
import { Button, Grid } from "@mui/material";
import PageHeader from "../Common/PageHeader/PageHeader";
import { setSnackBarMessage, setServerDiagStat } from "../../../actions";
import { setServerDiagStat, setSnackBarMessage } from "../../../actions";
import CircularProgress from "@mui/material/CircularProgress";
import BackLink from "../../../common/BackLink";
const styles = (theme: Theme) =>
createStyles({
@@ -66,6 +67,12 @@ const styles = (theme: Theme) =>
justifyContent: "flex-start",
gap: 20,
},
boxy: {
border: "#E5E5E5 1px solid",
borderRadius: 2,
padding: 40,
backgroundColor: "#fff",
},
...actionsTray,
...containerForHeader(theme.spacing(4)),
});
@@ -200,8 +207,11 @@ const HealthInfo = ({
<React.Fragment>
<PageHeader label="Diagnostic" />
<Grid container>
<Grid item xs={12} className={classes.container}>
<Grid container className={classes.container}>
<Grid item xs={12}>
<BackLink to="/tools" label="Return to Tools" />
</Grid>
<Grid item xs={12} className={classes.boxy}>
<Grid container className={classes.buttons}>
<Grid key="start-diag" item>
<Button

View File

@@ -13,7 +13,7 @@
//
// 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, useState, useEffect } from "react";
import React, { Fragment, useEffect, useState } from "react";
import { IMessageEvent, w3cwebsocket as W3CWebSocket } from "websocket";
import { Theme } from "@mui/material/styles";
import createStyles from "@mui/styles/createStyles";
@@ -30,10 +30,13 @@ import { timeFromDate } from "../../../../common/utils";
import { wsProtocol } from "../../../../utils/wsUtils";
import {
actionsTray,
containerForHeader,
logsCommon,
searchField,
} from "../../Common/FormComponents/common/styleLibrary";
import SearchIcon from "../../../../icons/SearchIcon";
import PageHeader from "../../Common/PageHeader/PageHeader";
import BackLink from "../../../../common/BackLink";
const styles = (theme: Theme) =>
createStyles({
@@ -68,6 +71,7 @@ const styles = (theme: Theme) =>
...actionsTray,
...searchField,
...logsCommon,
...containerForHeader(theme.spacing(4)),
});
interface ILogs {
@@ -325,32 +329,40 @@ const ErrorLogs = ({
return (
<Fragment>
<Grid container className={classes.logsSubContainer}>
<Grid item xs={12} className={classes.actionsTray}>
<TextField
placeholder="Highlight Line"
className={classes.searchField}
id="search-resource"
label=""
onChange={(val) => {
setHighlight(val.target.value);
}}
InputProps={{
disableUnderline: true,
startAdornment: (
<InputAdornment position="start">
<SearchIcon />
</InputAdornment>
),
}}
variant="standard"
/>
<PageHeader label="Logs" />
<Grid container className={classes.container}>
<Grid item xs={12}>
<BackLink to="/tools" label="Return to Tools" />
</Grid>
<Grid item xs={12}>
<br />
</Grid>
<Grid item xs={12}>
<div className={classes.logList}>{renderLines}</div>
<Grid container className={classes.logsSubContainer}>
<Grid item xs={12} className={classes.actionsTray}>
<TextField
placeholder="Highlight Line"
className={classes.searchField}
id="search-resource"
label=""
onChange={(val) => {
setHighlight(val.target.value);
}}
InputProps={{
disableUnderline: true,
startAdornment: (
<InputAdornment position="start">
<SearchIcon />
</InputAdornment>
),
}}
variant="standard"
/>
</Grid>
<Grid item xs={12}>
<br />
</Grid>
<Grid item xs={12}>
<div className={classes.logList}>{renderLines}</div>
</Grid>
</Grid>
</Grid>
</Grid>
</Fragment>

View File

@@ -14,7 +14,7 @@
// 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, useState, useEffect, useCallback } from "react";
import React, { Fragment, useCallback, useEffect, useState } from "react";
import { connect } from "react-redux";
import get from "lodash/get";
import { Theme } from "@mui/material/styles";
@@ -40,6 +40,8 @@ import FilterInputWrapper from "../../Common/FormComponents/FilterInputWrapper/F
import LogSearchFullModal from "./LogSearchFullModal";
import { LogSearchColumnLabels } from "./utils";
import DateRangeSelector from "../../Common/FormComponents/DateRangeSelector/DateRangeSelector";
import PageHeader from "../../Common/PageHeader/PageHeader";
import BackLink from "../../../../common/BackLink";
interface ILogSearchProps {
classes: any;
@@ -285,187 +287,199 @@ const LogsSearchMain = ({
onClose={closeViewExtraInformation}
/>
)}
<Grid container className={classes.logsSubContainer}>
<Grid
item
xs={12}
className={`${classes.actionsTray} ${classes.timeContainers}`}
>
<DateRangeSelector
setTimeEnd={setTimeEnd}
setTimeStart={setTimeStart}
timeEnd={timeEnd}
timeStart={timeStart}
/>
<PageHeader label="Audit Logs" />
<Grid container className={classes.container}>
<Grid item xs={12}>
<BackLink to="/tools" label="Return to Tools" />
</Grid>
<Grid item xs={12} className={`${classes.advancedLabelContainer}`}>
<div
className={`${classes.blockCollapsed} ${
filterOpen ? classes.filterOpen : ""
}`}
<Grid container className={classes.logsSubContainer}>
<Grid
item
xs={12}
className={`${classes.actionsTray} ${classes.timeContainers}`}
>
<div className={classes.innerContainer}>
<div className={classes.noticeLabel}>
Enable your preferred options to get filtered records.
<br />
You can use '*' to match any character, '.' to signify a single
character or '\' to scape an special character (E.g. mybucket-*)
</div>
<div className={classes.filtersContainer}>
<FilterInputWrapper
onChange={setBucket}
value={bucket}
label={"Bucket"}
id="bucket"
name="bucket"
/>
<FilterInputWrapper
onChange={setApiName}
value={apiName}
label={"API Name"}
id="api_name"
name="api_name"
/>
<FilterInputWrapper
onChange={setUserAgent}
value={userAgent}
label={"User Agent"}
id="user_agent"
name="user_agent"
/>
</div>
<div className={classes.filtersContainer}>
<FilterInputWrapper
onChange={setObject}
value={object}
label={"Object"}
id="object"
name="object"
/>
<FilterInputWrapper
onChange={setRequestID}
value={requestID}
label={"Request ID"}
id="request_id"
name="request_id"
/>
<FilterInputWrapper
onChange={setResponseStatus}
value={responseStatus}
label={"Response Status"}
id="response_status"
name="response_status"
/>
<DateRangeSelector
setTimeEnd={setTimeEnd}
setTimeStart={setTimeStart}
timeEnd={timeEnd}
timeStart={timeStart}
/>
</Grid>
<Grid item xs={12} className={`${classes.advancedLabelContainer}`}>
<div
className={`${classes.blockCollapsed} ${
filterOpen ? classes.filterOpen : ""
}`}
>
<div className={classes.innerContainer}>
<div className={classes.noticeLabel}>
Enable your preferred options to get filtered records.
<br />
You can use '*' to match any character, '.' to signify a
single character or '\' to scape an special character (E.g.
mybucket-*)
</div>
<div className={classes.filtersContainer}>
<FilterInputWrapper
onChange={setBucket}
value={bucket}
label={"Bucket"}
id="bucket"
name="bucket"
/>
<FilterInputWrapper
onChange={setApiName}
value={apiName}
label={"API Name"}
id="api_name"
name="api_name"
/>
<FilterInputWrapper
onChange={setUserAgent}
value={userAgent}
label={"User Agent"}
id="user_agent"
name="user_agent"
/>
</div>
<div className={classes.filtersContainer}>
<FilterInputWrapper
onChange={setObject}
value={object}
label={"Object"}
id="object"
name="object"
/>
<FilterInputWrapper
onChange={setRequestID}
value={requestID}
label={"Request ID"}
id="request_id"
name="request_id"
/>
<FilterInputWrapper
onChange={setResponseStatus}
value={responseStatus}
label={"Response Status"}
id="response_status"
name="response_status"
/>
</div>
</div>
</div>
</div>
</Grid>
<Grid
item
xs={12}
className={`${classes.actionsTray} ${classes.endLineAction}`}
>
<div>
<button
type="button"
className={`${classes.advancedLabel} overrideMargin`}
onClick={() => {
setFilterOpen(!filterOpen);
}}
>
Advanced Filters{" "}
{filterOpen ? <ArrowDropUp /> : <ArrowDropDownIcon />}
</button>
</div>
<Button
type="button"
variant="contained"
color="primary"
onClick={triggerLoad}
</Grid>
<Grid
item
xs={12}
className={`${classes.actionsTray} ${classes.endLineAction}`}
>
Get Information
</Button>
</Grid>
<Grid item xs={12}>
<TableWrapper
columns={[
{
label: LogSearchColumnLabels.time,
elementKey: "time",
enableSort: true,
},
{ label: LogSearchColumnLabels.api_name, elementKey: "api_name" },
{ label: LogSearchColumnLabels.bucket, elementKey: "bucket" },
{ label: LogSearchColumnLabels.object, elementKey: "object" },
{
label: LogSearchColumnLabels.remote_host,
elementKey: "remote_host",
},
{
label: LogSearchColumnLabels.request_id,
elementKey: "request_id",
},
{
label: LogSearchColumnLabels.user_agent,
elementKey: "user_agent",
},
{
label: LogSearchColumnLabels.response_status,
elementKey: "response_status",
renderFunction: (element) => (
<Fragment>
<span>
{element.response_status_code} ({element.response_status})
</span>
</Fragment>
),
renderFullObject: true,
},
{
label: LogSearchColumnLabels.request_content_length,
elementKey: "request_content_length",
renderFunction: niceBytes,
},
{
label: LogSearchColumnLabels.response_content_length,
elementKey: "response_content_length",
renderFunction: niceBytes,
},
{
label: LogSearchColumnLabels.time_to_response_ns,
elementKey: "time_to_response_ns",
renderFunction: nsToSeconds,
contentTextAlign: "right",
},
]}
isLoading={loading}
records={records}
entityName="Logs"
customEmptyMessage={"There is no information with this criteria"}
idField="request_id"
columnsSelector
columnsShown={columnsShown}
onColumnChange={selectColumn}
customPaperHeight={
filterOpen ? classes.tableFOpen : classes.tableFClosed
}
sortConfig={{
currentSort: "time",
currentDirection: sortOrder,
triggerSort: sortChange,
}}
infiniteScrollConfig={{
recordsCount: 1000000,
loadMoreRecords: loadMoreRecords,
}}
itemActions={[
{
type: "view",
onClick: openExtraInformation,
},
]}
textSelectable
/>
<div>
<button
type="button"
className={`${classes.advancedLabel} overrideMargin`}
onClick={() => {
setFilterOpen(!filterOpen);
}}
>
Advanced Filters{" "}
{filterOpen ? <ArrowDropUp /> : <ArrowDropDownIcon />}
</button>
</div>
<Button
type="button"
variant="contained"
color="primary"
onClick={triggerLoad}
>
Get Information
</Button>
</Grid>
<Grid item xs={12}>
<TableWrapper
columns={[
{
label: LogSearchColumnLabels.time,
elementKey: "time",
enableSort: true,
},
{
label: LogSearchColumnLabels.api_name,
elementKey: "api_name",
},
{ label: LogSearchColumnLabels.bucket, elementKey: "bucket" },
{ label: LogSearchColumnLabels.object, elementKey: "object" },
{
label: LogSearchColumnLabels.remote_host,
elementKey: "remote_host",
},
{
label: LogSearchColumnLabels.request_id,
elementKey: "request_id",
},
{
label: LogSearchColumnLabels.user_agent,
elementKey: "user_agent",
},
{
label: LogSearchColumnLabels.response_status,
elementKey: "response_status",
renderFunction: (element) => (
<Fragment>
<span>
{element.response_status_code} (
{element.response_status})
</span>
</Fragment>
),
renderFullObject: true,
},
{
label: LogSearchColumnLabels.request_content_length,
elementKey: "request_content_length",
renderFunction: niceBytes,
},
{
label: LogSearchColumnLabels.response_content_length,
elementKey: "response_content_length",
renderFunction: niceBytes,
},
{
label: LogSearchColumnLabels.time_to_response_ns,
elementKey: "time_to_response_ns",
renderFunction: nsToSeconds,
contentTextAlign: "right",
},
]}
isLoading={loading}
records={records}
entityName="Logs"
customEmptyMessage={"There is no information with this criteria"}
idField="request_id"
columnsSelector
columnsShown={columnsShown}
onColumnChange={selectColumn}
customPaperHeight={
filterOpen ? classes.tableFOpen : classes.tableFClosed
}
sortConfig={{
currentSort: "time",
currentDirection: sortOrder,
triggerSort: sortChange,
}}
infiniteScrollConfig={{
recordsCount: 1000000,
loadMoreRecords: loadMoreRecords,
}}
itemActions={[
{
type: "view",
onClick: openExtraInformation,
},
]}
textSelectable
/>
</Grid>
</Grid>
</Grid>
</Fragment>

View File

@@ -1,102 +0,0 @@
// 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, useState } from "react";
import { connect } from "react-redux";
import PageHeader from "../Common/PageHeader/PageHeader";
import { Grid, List, ListItem, ListItemText } from "@mui/material";
import { Theme } from "@mui/material/styles";
import createStyles from "@mui/styles/createStyles";
import withStyles from "@mui/styles/withStyles";
import { containerForHeader } from "../Common/FormComponents/common/styleLibrary";
import ErrorLogs from "./ErrorLogs/ErrorLogs";
import LogsSearchMain from "./LogSearch/LogsSearchMain";
import { AppState } from "../../../store";
interface ILogsMainProps {
classes: any;
features: string[] | null;
}
const styles = (theme: Theme) =>
createStyles({
headerLabel: {
fontSize: 22,
fontWeight: 600,
color: "#000",
marginTop: 4,
},
...containerForHeader(theme.spacing(4)),
});
const LogsMain = ({ classes, features }: ILogsMainProps) => {
const [currentTab, setCurrentTab] = useState<number>(0);
const logSearchEnabled = features && features.includes("log-search");
return (
<Fragment>
<PageHeader label="Logs" />
<Grid container className={classes.container}>
<Grid item xs={2}>
<List component="nav" dense={true}>
<ListItem
button
selected={currentTab === 0}
onClick={() => {
setCurrentTab(0);
}}
>
<ListItemText primary="Error Logs" />
</ListItem>
<ListItem
button
selected={currentTab === 1}
disabled={!logSearchEnabled}
onClick={() => {
setCurrentTab(1);
}}
>
<ListItemText primary="Audit Logs" />
</ListItem>
</List>
</Grid>
<Grid item xs={10}>
{currentTab === 0 && (
<Fragment>
<h1 className={classes.sectionTitle}>Error Logs</h1>
<ErrorLogs />
</Fragment>
)}
{currentTab === 1 && logSearchEnabled && (
<Fragment>
<h1 className={classes.sectionTitle}>Audit Logs</h1>
<LogsSearchMain />
</Fragment>
)}
</Grid>
</Grid>
</Fragment>
);
};
const mapState = (state: AppState) => ({
features: state.console.session.features,
});
const connector = connect(mapState, null);
export default withStyles(styles)(connector(LogsMain));

View File

@@ -36,7 +36,7 @@ import {
IAMPoliciesIcon,
LambdaIcon,
TiersIcon,
TraceIcon,
ToolsIcon,
UsersIcon,
VersionIcon,
} from "../../../icons";
@@ -45,15 +45,12 @@ import { clearSession } from "../../../common/utils";
import LicenseIcon from "../../../icons/LicenseIcon";
import LogoutIcon from "../../../icons/LogoutIcon";
import HealIcon from "../../../icons/HealIcon";
import WatchIcon from "../../../icons/WatchIcon";
import OperatorLogo from "../../../icons/OperatorLogo";
import ConsoleLogo from "../../../icons/ConsoleLogo";
import history from "../../../history";
import api from "../../../common/api";
import AccountIcon from "../../../icons/AccountIcon";
import DiagnosticsIcon from "../../../icons/DiagnosticsIcon";
import DocumentationIcon from "../../../icons/DocumentationIcon";
import LogsIcon from "../../../icons/LogsIcon";
import SettingsIcon from "../../../icons/SettingsIcon";
import StorageIcon from "../../../icons/StorageIcon";
import TenantsOutlinedIcon from "../../../icons/TenantsOutlineIcon";
@@ -383,28 +380,12 @@ const Menu = ({
icon: <TiersIcon />,
},
{
group: "Tools",
group: "common",
type: "item",
component: NavLink,
to: "/logs",
name: "Logs",
icon: <LogsIcon />,
},
{
group: "Tools",
type: "item",
component: NavLink,
to: "/watch",
name: "Watch",
icon: <WatchIcon />,
},
{
group: "Tools",
type: "item",
component: NavLink,
to: "/trace",
name: "Trace",
icon: <TraceIcon />,
to: "/tools",
name: "Tools",
icon: <ToolsIcon />,
},
{
group: "Tools",
@@ -415,14 +396,6 @@ const Menu = ({
icon: <HealIcon />,
fsHidden: distributedSetup,
},
{
group: "Tools",
type: "item",
component: NavLink,
to: "/health-info",
name: "Diagnostic",
icon: <DiagnosticsIcon />,
},
{
group: "Operator",

View File

@@ -19,7 +19,7 @@ import { connect } from "react-redux";
import { Theme } from "@mui/material/styles";
import createStyles from "@mui/styles/createStyles";
import withStyles from "@mui/styles/withStyles";
import { IconButton, LinearProgress, TextField } from "@mui/material";
import { LinearProgress, TextField } from "@mui/material";
import { red } from "@mui/material/colors";
import Grid from "@mui/material/Grid";
import FiberManualRecordIcon from "@mui/icons-material/FiberManualRecord";

View File

@@ -14,7 +14,7 @@
// 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, { useState, useEffect } from "react";
import React, { Fragment, useEffect, useState } from "react";
import { connect } from "react-redux";
import get from "lodash/get";
import { Theme } from "@mui/material/styles";
@@ -25,7 +25,7 @@ import Grid from "@mui/material/Grid";
import TextField from "@mui/material/TextField";
import InputAdornment from "@mui/material/InputAdornment";
import { Policy, PolicyList } from "./types";
import { AddIcon } from "../../../icons";
import { AddIcon, IAMPoliciesIcon } from "../../../icons";
import { setErrorSnackMessage } from "../../../actions";
import {
actionsTray,
@@ -40,6 +40,7 @@ import PageHeader from "../Common/PageHeader/PageHeader";
import api from "../../../common/api";
import history from "../../../history";
import SearchIcon from "../../../icons/SearchIcon";
import HelpBox from "../../../common/HelpBox";
const styles = (theme: Theme) =>
createStyles({
@@ -68,6 +69,9 @@ const styles = (theme: Theme) =>
},
},
},
twHeight: {
minHeight: 600,
},
...actionsTray,
...searchField,
...containerForHeader(theme.spacing(4)),
@@ -175,52 +179,84 @@ const ListPolicies = ({ classes, setErrorSnackMessage }: IPoliciesProps) => {
/>
)}
<PageHeader label="IAM Policies" />
<Grid container>
<Grid item xs={12} className={classes.container}>
<Grid item xs={12} className={classes.actionsTray}>
<TextField
placeholder="Search Policies"
className={classes.searchField}
id="search-resource"
label=""
onChange={(val) => {
setFilterPolicies(val.target.value);
}}
InputProps={{
disableUnderline: true,
startAdornment: (
<InputAdornment position="start">
<SearchIcon />
</InputAdornment>
),
}}
variant="standard"
/>
<Button
variant="contained"
color="primary"
startIcon={<AddIcon />}
onClick={() => {
setAddScreenOpen(true);
setPolicyEdit(null);
}}
>
Create Policy
</Button>
</Grid>
<Grid item xs={12}>
<br />
</Grid>
<Grid item xs={12}>
<TableWrapper
itemActions={tableActions}
columns={[{ label: "Name", elementKey: "name" }]}
isLoading={loading}
records={filteredRecords}
entityName="Policies"
idField="name"
/>
</Grid>
<Grid container className={classes.container}>
<Grid item xs={12} className={classes.actionsTray}>
<TextField
placeholder="Search Policies"
className={classes.searchField}
id="search-resource"
label=""
onChange={(val) => {
setFilterPolicies(val.target.value);
}}
InputProps={{
disableUnderline: true,
startAdornment: (
<InputAdornment position="start">
<SearchIcon />
</InputAdornment>
),
}}
variant="standard"
/>
<Button
variant="contained"
color="primary"
startIcon={<AddIcon />}
onClick={() => {
setAddScreenOpen(true);
setPolicyEdit(null);
}}
>
Create Policy
</Button>
</Grid>
<Grid item xs={12}>
<br />
</Grid>
<Grid item xs={12}>
<TableWrapper
itemActions={tableActions}
columns={[{ label: "Name", elementKey: "name" }]}
isLoading={loading}
records={filteredRecords}
entityName="Policies"
idField="name"
customPaperHeight={classes.twHeight}
/>
</Grid>
<Grid item xs={12}>
<HelpBox
title={"Learn more about IAM POLICIES"}
iconComponent={<IAMPoliciesIcon />}
help={
<Fragment>
MinIO uses Policy-Based Access Control (PBAC) to define the
authorized actions and resources to which an authenticated user
has access. Each policy describes one or more actions and
conditions that outline the permissions of a user or group of
users.
<br />
<br />
MinIO PBAC is built for compatibility with AWS IAM policy
syntax, structure, and behavior. The MinIO documentation makes a
best-effort to cover IAM-specific behavior and functionality.
Consider deferring to the IAM documentation for more complete
documentation on AWS IAM-specific topics.
<br />
<br />
You can learn more at our{" "}
<a
href="https://docs.min.io/minio/baremetal/security/minio-identity-management/policy-based-access-control.html?ref=con"
target="_blank"
rel="noreferrer"
>
documentation
</a>
.
</Fragment>
}
/>
</Grid>
</Grid>
</React.Fragment>

View File

@@ -1,150 +0,0 @@
// 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 } from "react";
import { connect } from "react-redux";
import { Theme } from "@mui/material/styles";
import createStyles from "@mui/styles/createStyles";
import withStyles from "@mui/styles/withStyles";
import Table from "@mui/material/Table";
import TableBody from "@mui/material/TableBody";
import TableCell from "@mui/material/TableCell";
import TableRow from "@mui/material/TableRow";
import { AppState } from "../../../../../store";
import {
modalBasic,
wizardCommon,
} from "../../../Common/FormComponents/common/styleLibrary";
import { Paper } from "@mui/material";
interface IPreviewProps {
classes: any;
tenantName: string;
customImage: boolean;
imageName: string;
namespace: string;
selectedStorageClass: string;
volumeSize: string;
sizeFactor: string;
advancedMode: boolean;
enableTLS: boolean;
}
const styles = (theme: Theme) =>
createStyles({
buttonContainer: {
textAlign: "right",
},
...modalBasic,
...wizardCommon,
});
const Preview = ({
classes,
tenantName,
customImage,
imageName,
namespace,
selectedStorageClass,
volumeSize,
sizeFactor,
advancedMode,
enableTLS,
}: IPreviewProps) => {
return (
<Paper className={classes.paperWrapper}>
<div className={classes.headerElement}>
<h3 className={classes.h3Section}>Review</h3>
<span className={classes.descriptionText}>
Review the details of the new tenant
</span>
</div>
<Table size="small">
<TableBody>
<TableRow>
<TableCell align="right" className={classes.tableTitle}>
Tenant Name
</TableCell>
<TableCell>{tenantName}</TableCell>
</TableRow>
{customImage && (
<Fragment>
<TableRow>
<TableCell align="right" className={classes.tableTitle}>
MinIO Image
</TableCell>
<TableCell>{imageName}</TableCell>
</TableRow>
</Fragment>
)}
{namespace !== "" && (
<TableRow>
<TableCell align="right" className={classes.tableTitle}>
Namespace
</TableCell>
<TableCell>{namespace}</TableCell>
</TableRow>
)}
<TableRow>
<TableCell align="right" className={classes.tableTitle}>
Storage Class
</TableCell>
<TableCell>{selectedStorageClass}</TableCell>
</TableRow>
<TableRow>
<TableCell align="right" className={classes.tableTitle}>
Total Size
</TableCell>
<TableCell>
{volumeSize} {sizeFactor}
</TableCell>
</TableRow>
{advancedMode && (
<Fragment>
<TableRow>
<TableCell align="right" className={classes.tableTitle}>
Enable TLS
</TableCell>
<TableCell>{enableTLS ? "Enabled" : "Disabled"}</TableCell>
</TableRow>
</Fragment>
)}
</TableBody>
</Table>
</Paper>
);
};
const mapState = (state: AppState) => ({
advancedMode: state.tenants.createTenant.advancedModeOn,
enableTLS: state.tenants.createTenant.fields.security.enableTLS,
tenantName: state.tenants.createTenant.fields.nameTenant.tenantName,
selectedStorageClass:
state.tenants.createTenant.fields.nameTenant.selectedStorageClass,
customImage: state.tenants.createTenant.fields.configure.customImage,
imageName: state.tenants.createTenant.fields.configure.imageName,
namespace: state.tenants.createTenant.fields.nameTenant.namespace,
volumeSize: state.tenants.createTenant.fields.tenantSize.volumeSize,
sizeFactor: state.tenants.createTenant.fields.tenantSize.sizeFactor,
});
const connector = connect(mapState, {});
export default withStyles(styles)(connector(Preview));

View File

@@ -19,7 +19,7 @@ import { connect } from "react-redux";
import Grid from "@mui/material/Grid";
import TextField from "@mui/material/TextField";
import InputAdornment from "@mui/material/InputAdornment";
import { Box, Button, IconButton, LinearProgress } from "@mui/material";
import { Box, Button, LinearProgress } from "@mui/material";
import { Theme } from "@mui/material/styles";
import createStyles from "@mui/styles/createStyles";
import withStyles from "@mui/styles/withStyles";

View File

@@ -1,219 +0,0 @@
// 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, { useState } from "react";
import { Theme } from "@mui/material/styles";
import createStyles from "@mui/styles/createStyles";
import withStyles from "@mui/styles/withStyles";
import { modalBasic } from "../../Common/FormComponents/common/styleLibrary";
import Grid from "@mui/material/Grid";
import InputBoxWrapper from "../../Common/FormComponents/InputBoxWrapper/InputBoxWrapper";
import SelectWrapper from "../../Common/FormComponents/SelectWrapper/SelectWrapper";
import { Button, LinearProgress, SelectChangeEvent } from "@mui/material";
import ModalWrapper from "../../Common/ModalWrapper/ModalWrapper";
import Tab from "@mui/material/Tab";
import Tabs from "@mui/material/Tabs";
interface IReplicationProps {
classes: any;
open: boolean;
closeModalAndRefresh: (refreshList: boolean) => void;
}
interface IDropDownElements {
label: string;
value: string;
}
const styles = (theme: Theme) =>
createStyles({
buttonContainer: {
textAlign: "right",
},
multiContainer: {
display: "flex",
alignItems: "center" as const,
justifyContent: "flex-start" as const,
},
sizeFactorContainer: {
marginLeft: 8,
},
...modalBasic,
});
const ReplicationSetup = ({
classes,
open,
closeModalAndRefresh,
}: IReplicationProps) => {
const [addSending, setAddSending] = useState<boolean>(false);
const [selectedTab, setSelectedTab] = useState<number>(0);
const [sourceBucket, setSourceBucket] = useState<string>("");
const [clusterSelected, setClusterSelected] = useState<string>("");
const [destinationBucket, setDestinationBucket] = useState<string>("");
const [address, setAddress] = useState<string>("");
const [bucket, setBucket] = useState<string>("");
const [accessKey, setAccessKey] = useState<string>("");
const [secretKey, setSecretKey] = useState<string>("");
const clustersList: IDropDownElements[] = [];
const sourceBuckets: IDropDownElements[] = [];
const destinationBuckets: IDropDownElements[] = [];
return (
<ModalWrapper
modalOpen={open}
title="Add Pool"
onClose={() => {
closeModalAndRefresh(false);
}}
>
<form
noValidate
autoComplete="off"
onSubmit={(e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
setAddSending(true);
}}
>
<Grid item xs={12}>
<SelectWrapper
label="Source Bucket"
options={sourceBuckets}
onChange={(e: SelectChangeEvent<string>) => {
setSourceBucket(e.target.value as string);
}}
value={sourceBucket}
name="source_bucket"
id="source_bucket"
/>
</Grid>
<Grid item xs={12}>
<Tabs
value={selectedTab}
indicatorColor="primary"
textColor="primary"
onChange={(_, newValue: number) => {
setSelectedTab(newValue);
}}
aria-label="cluster-tabs"
variant="scrollable"
scrollButtons="auto"
>
<Tab label="Local Cluster" />
<Tab label="Remote Cluster" />
</Tabs>
</Grid>
<Grid item xs={12}>
<br />
</Grid>
{selectedTab === 0 && (
<React.Fragment>
<Grid item xs={12}>
<SelectWrapper
label="Cluster"
options={clustersList}
onChange={(e: SelectChangeEvent<string>) => {
setClusterSelected(e.target.value as string);
}}
value={clusterSelected}
name="cluster"
id="cluster"
/>
</Grid>
<Grid item xs={12}>
<SelectWrapper
label="Destination Bucket"
options={destinationBuckets}
onChange={(e: SelectChangeEvent<string>) => {
setDestinationBucket(e.target.value as string);
}}
value={destinationBucket}
name="destination_bucket"
id="destination_bucket"
/>
</Grid>
</React.Fragment>
)}
{selectedTab === 1 && (
<React.Fragment>
<Grid item xs={12}>
<InputBoxWrapper
id="address"
name="address"
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
setAddress(e.target.value);
}}
label="Address"
value={address}
/>
</Grid>
<Grid item xs={12}>
<InputBoxWrapper
id="bucket"
name="bucket"
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
setBucket(e.target.value);
}}
label="Bucket"
value={bucket}
/>
</Grid>
<Grid item xs={12}>
<InputBoxWrapper
id="accessKey"
name="accessKey"
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
setAccessKey(e.target.value);
}}
label="Access Key"
value={accessKey}
/>
</Grid>
<Grid item xs={12}>
<InputBoxWrapper
id="secretKey"
name="secretKey"
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
setSecretKey(e.target.value);
}}
label="Secret Key"
value={secretKey}
/>
</Grid>
</React.Fragment>
)}
<Grid item xs={12} className={classes.buttonContainer}>
<Button
type="submit"
variant="contained"
color="primary"
disabled={addSending}
>
Save
</Button>
</Grid>
{addSending && (
<Grid item xs={12}>
<LinearProgress />
</Grid>
)}
</form>
</ModalWrapper>
);
};
export default withStyles(styles)(ReplicationSetup);

View File

@@ -14,7 +14,21 @@
// 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/>.
export interface ServiceAccountsList {
service_accounts: string[];
total: number;
}
import React from "react";
import { Route, Router, Switch } from "react-router-dom";
import history from "../../../history";
import NotFoundPage from "../../NotFoundPage";
import ToolsList from "./ToolsPanel/ToolsList";
const Tools = () => {
return (
<Router history={history}>
<Switch>
<Route path="/tools" exact component={ToolsList} />
<Route component={NotFoundPage} />
</Switch>
</Router>
);
};
export default Tools;

View File

@@ -0,0 +1,100 @@
// 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 } 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 { configurationElements } from "../utils";
import {
actionsTray,
containerForHeader,
searchField,
} from "../../Common/FormComponents/common/styleLibrary";
import PageHeader from "../../Common/PageHeader/PageHeader";
import SettingsCard from "../../Common/SettingsCard/SettingsCard";
interface IConfigurationOptions {
classes: any;
}
const styles = (theme: Theme) =>
createStyles({
strongText: {
fontWeight: 700,
},
keyName: {
marginLeft: 5,
},
iconText: {
lineHeight: "24px",
},
customConfigurationPage: {
height: "calc(100vh - 324px)",
scrollbarWidth: "none" as const,
"&::-webkit-scrollbar": {
display: "none",
},
},
settingsOptionsContainer: {
display: "flex" as const,
flexDirection: "row" as const,
justifyContent: "flex-start" as const,
flexWrap: "wrap" as const,
border: "#E5E5E5 1px solid",
borderRadius: 2,
padding: 5,
backgroundColor: "#fff",
},
configurationLink: {
border: "#E5E5E5 1px solid",
borderRadius: 2,
padding: 20,
width: 190,
height: 80,
margin: 15,
},
...searchField,
...actionsTray,
...containerForHeader(theme.spacing(4)),
});
const ToolsList = ({ classes }: IConfigurationOptions) => {
return (
<Fragment>
<PageHeader label={"Tools"} />
<Grid container className={classes.container}>
<Grid item xs={12}>
<Grid item xs={12}>
<div className={classes.settingsOptionsContainer}>
{configurationElements.map((element) => (
<SettingsCard
prefix={"tools"}
configuration={element}
key={`configItem-${element.configuration_label}`}
/>
))}
</div>
</Grid>
</Grid>
</Grid>
</Fragment>
);
};
export default withStyles(styles)(ToolsList);

View File

@@ -0,0 +1,61 @@
// 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 { SelectorTypes } from "../Common/FormComponents/RadioGroupSelector/RadioGroupSelector";
export type KVFieldType =
| "string"
| "number"
| "on|off"
| "enum"
| "path"
| "url"
| "address"
| "duration"
| "uri"
| "sentence"
| "csv"
| "comment"
| "switch";
export interface KVField {
name: string;
label: string;
tooltip: string;
required?: boolean;
type: KVFieldType;
options?: SelectorTypes[];
multiline?: boolean;
placeholder?: string;
withBorder?: boolean;
}
export interface IConfigurationElement {
configuration_id: string;
configuration_label: string;
url?: string;
}
export interface IElementValue {
key: string;
value: string;
}
export interface IElement {
configuration_id: string;
configuration_label: string;
icon?: any;
}

View File

@@ -0,0 +1,58 @@
// 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 { IElement } from "./types";
import {
DiagnosticsIcon,
HealIcon,
LogsIcon,
SearchIcon,
TraceIcon,
WatchIcon,
} from "../../../icons";
export const configurationElements: IElement[] = [
{
icon: <LogsIcon />,
configuration_id: "logs",
configuration_label: "Logs",
},
{
icon: <SearchIcon />,
configuration_id: "audit-logs",
configuration_label: "Audit Logs",
},
{
icon: <WatchIcon />,
configuration_id: "watch",
configuration_label: "Watch",
},
{
icon: <TraceIcon />,
configuration_id: "trace",
configuration_label: "trace",
},
{
icon: <HealIcon />,
configuration_id: "heal",
configuration_label: "heal",
},
{
icon: <DiagnosticsIcon />,
configuration_id: "diagnostics",
configuration_label: "Diagnostics",
},
];

View File

@@ -14,15 +14,15 @@
// 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, { useState, Fragment } from "react";
import { Grid, Button, TextField } from "@mui/material";
import React, { Fragment, useState } from "react";
import { Button, Grid, TextField } from "@mui/material";
import { IMessageEvent, w3cwebsocket as W3CWebSocket } from "websocket";
import { AppState } from "../../../store";
import { connect } from "react-redux";
import {
setTraceStarted,
traceMessageReceived,
traceResetMessages,
setTraceStarted,
} from "./actions";
import { TraceMessage } from "./types";
import { Theme } from "@mui/material/styles";
@@ -31,16 +31,17 @@ import withStyles from "@mui/styles/withStyles";
import { niceBytes, timeFromDate } from "../../../common/utils";
import { wsProtocol } from "../../../utils/wsUtils";
import {
containerForHeader,
searchField,
actionsTray,
containerForHeader,
hrClass,
inlineCheckboxes,
searchField,
} from "../Common/FormComponents/common/styleLibrary";
import TableWrapper from "../Common/TableWrapper/TableWrapper";
import PageHeader from "../Common/PageHeader/PageHeader";
import CheckboxWrapper from "../Common/FormComponents/CheckboxWrapper/CheckboxWrapper";
import moment from "moment/moment";
import BackLink from "../../../common/BackLink";
const styles = (theme: Theme) =>
createStyles({
@@ -192,246 +193,247 @@ const Trace = ({
return (
<Fragment>
<PageHeader label={"Trace"} />
<Grid container>
<Grid item xs={12} className={classes.container}>
<Grid item xs={12} className={classes.actionsTray}>
<TextField
placeholder="Status Code"
className={classes.searchField}
id="status-code"
label=""
InputProps={{
disableUnderline: true,
}}
value={statusCode}
onChange={(e) => {
setStatusCode(e.target.value);
}}
disabled={traceStarted}
variant="standard"
/>
<TextField
placeholder="Method"
className={classes.searchField}
id="method"
label=""
InputProps={{
disableUnderline: true,
}}
value={method}
onChange={(e) => {
setMethod(e.target.value);
}}
disabled={traceStarted}
variant="standard"
/>
<TextField
placeholder="Function Name"
className={classes.searchField}
id="func-name"
label=""
disabled={traceStarted}
InputProps={{
disableUnderline: true,
}}
value={func}
onChange={(e) => {
setFunc(e.target.value);
}}
variant="standard"
/>
<TextField
placeholder="Path"
className={classes.searchField}
id="path"
label=""
disabled={traceStarted}
InputProps={{
disableUnderline: true,
}}
value={path}
onChange={(e) => {
setPath(e.target.value);
}}
variant="standard"
/>
<TextField
type="number"
className={classes.searchField}
id="fthreshold"
label="Response Threshold"
disabled={traceStarted}
InputProps={{
disableUnderline: true,
}}
inputProps={{
min: 0,
}}
value={threshold}
onChange={(e) => {
setThreshold(parseInt(e.target.value));
}}
variant="standard"
/>
</Grid>
<Grid item xs={12} className={classes.inlineCheckboxes}>
<span className={classes.labelCheckboxes}>Calls to trace:</span>
<CheckboxWrapper
checked={all}
id={"all_calls"}
name={"all_calls"}
label={"All"}
onChange={(item) => {
setAll(item.target.checked);
}}
value={"all"}
disabled={traceStarted}
/>
<CheckboxWrapper
checked={s3 || all}
id={"s3_calls"}
name={"s3_calls"}
label={"S3"}
onChange={(item) => {
setS3(item.target.checked);
}}
value={"s3"}
disabled={all || traceStarted}
/>
<CheckboxWrapper
checked={internal || all}
id={"internal_calls"}
name={"internal_calls"}
label={"Internal"}
onChange={(item) => {
setInternal(item.target.checked);
}}
value={"internal"}
disabled={all || traceStarted}
/>
<CheckboxWrapper
checked={storage || all}
id={"storage_calls"}
name={"storage_calls"}
label={"Storage"}
onChange={(item) => {
setStorage(item.target.checked);
}}
value={"storage"}
disabled={all || traceStarted}
/>
<CheckboxWrapper
checked={os || all}
id={"os_calls"}
name={"os_calls"}
label={"OS"}
onChange={(item) => {
setOS(item.target.checked);
}}
value={"os"}
disabled={all || traceStarted}
/>
<span className={classes.labelCheckboxes}>
&nbsp; &nbsp; &nbsp; | &nbsp; &nbsp; &nbsp;
</span>
<CheckboxWrapper
checked={errors}
id={"only_errors"}
name={"only_errors"}
label={"Display only Errors"}
onChange={(item) => {
setErrors(item.target.checked);
}}
value={"only_errors"}
disabled={traceStarted}
/>
</Grid>
<Grid item xs={12} className={classes.startButton}>
{!traceStarted && (
<Button
type="submit"
variant="contained"
color="primary"
disabled={traceStarted}
onClick={startTrace}
>
Start
</Button>
)}
{traceStarted && (
<Button
type="button"
variant="contained"
color="primary"
onClick={stopTrace}
>
Stop
</Button>
)}
</Grid>
<Grid item xs={12}>
<br />
</Grid>
<TableWrapper
itemActions={[]}
columns={[
{
label: "Time",
elementKey: "ptime",
renderFunction: (time: Date) => {
const timeParse = new Date(time);
return timeFromDate(timeParse);
},
globalClass: classes.timeItem,
},
{ label: "Name", elementKey: "api" },
{
label: "Status",
elementKey: "",
renderFunction: (fullElement: TraceMessage) =>
`${fullElement.statusCode} ${fullElement.statusMsg}`,
renderFullObject: true,
},
{
label: "Location",
elementKey: "configuration_id",
renderFunction: (fullElement: TraceMessage) =>
`${fullElement.host} ${fullElement.client}`,
renderFullObject: true,
},
{
label: "Load Time",
elementKey: "callStats.duration",
globalClass: classes.timeItem,
},
{
label: "Upload",
elementKey: "callStats.rx",
renderFunction: niceBytes,
globalClass: classes.sizeItem,
},
{
label: "Download",
elementKey: "callStats.tx",
renderFunction: niceBytes,
globalClass: classes.sizeItem,
},
]}
isLoading={false}
records={messages}
entityName="Traces"
idField="api"
customEmptyMessage={
traceStarted
? "No Traced elements received yet"
: "Trace is not started yet"
}
customPaperHeight={classes.tableWrapper}
autoScrollToBottom
<Grid container className={classes.container}>
<Grid item xs={12}>
<BackLink to="/tools" label="Return to Tools" />
</Grid>
<Grid item xs={12} className={classes.actionsTray}>
<TextField
placeholder="Status Code"
className={classes.searchField}
id="status-code"
label=""
InputProps={{
disableUnderline: true,
}}
value={statusCode}
onChange={(e) => {
setStatusCode(e.target.value);
}}
disabled={traceStarted}
variant="standard"
/>
<TextField
placeholder="Method"
className={classes.searchField}
id="method"
label=""
InputProps={{
disableUnderline: true,
}}
value={method}
onChange={(e) => {
setMethod(e.target.value);
}}
disabled={traceStarted}
variant="standard"
/>
<TextField
placeholder="Function Name"
className={classes.searchField}
id="func-name"
label=""
disabled={traceStarted}
InputProps={{
disableUnderline: true,
}}
value={func}
onChange={(e) => {
setFunc(e.target.value);
}}
variant="standard"
/>
<TextField
placeholder="Path"
className={classes.searchField}
id="path"
label=""
disabled={traceStarted}
InputProps={{
disableUnderline: true,
}}
value={path}
onChange={(e) => {
setPath(e.target.value);
}}
variant="standard"
/>
<TextField
type="number"
className={classes.searchField}
id="fthreshold"
label="Response Threshold"
disabled={traceStarted}
InputProps={{
disableUnderline: true,
}}
inputProps={{
min: 0,
}}
value={threshold}
onChange={(e) => {
setThreshold(parseInt(e.target.value));
}}
variant="standard"
/>
</Grid>
<Grid item xs={12} className={classes.inlineCheckboxes}>
<span className={classes.labelCheckboxes}>Calls to trace:</span>
<CheckboxWrapper
checked={all}
id={"all_calls"}
name={"all_calls"}
label={"All"}
onChange={(item) => {
setAll(item.target.checked);
}}
value={"all"}
disabled={traceStarted}
/>
<CheckboxWrapper
checked={s3 || all}
id={"s3_calls"}
name={"s3_calls"}
label={"S3"}
onChange={(item) => {
setS3(item.target.checked);
}}
value={"s3"}
disabled={all || traceStarted}
/>
<CheckboxWrapper
checked={internal || all}
id={"internal_calls"}
name={"internal_calls"}
label={"Internal"}
onChange={(item) => {
setInternal(item.target.checked);
}}
value={"internal"}
disabled={all || traceStarted}
/>
<CheckboxWrapper
checked={storage || all}
id={"storage_calls"}
name={"storage_calls"}
label={"Storage"}
onChange={(item) => {
setStorage(item.target.checked);
}}
value={"storage"}
disabled={all || traceStarted}
/>
<CheckboxWrapper
checked={os || all}
id={"os_calls"}
name={"os_calls"}
label={"OS"}
onChange={(item) => {
setOS(item.target.checked);
}}
value={"os"}
disabled={all || traceStarted}
/>
<span className={classes.labelCheckboxes}>
&nbsp; &nbsp; &nbsp; | &nbsp; &nbsp; &nbsp;
</span>
<CheckboxWrapper
checked={errors}
id={"only_errors"}
name={"only_errors"}
label={"Display only Errors"}
onChange={(item) => {
setErrors(item.target.checked);
}}
value={"only_errors"}
disabled={traceStarted}
/>
</Grid>
<Grid item xs={12} className={classes.startButton}>
{!traceStarted && (
<Button
type="submit"
variant="contained"
color="primary"
disabled={traceStarted}
onClick={startTrace}
>
Start
</Button>
)}
{traceStarted && (
<Button
type="button"
variant="contained"
color="primary"
onClick={stopTrace}
>
Stop
</Button>
)}
</Grid>
<Grid item xs={12}>
<br />
</Grid>
<TableWrapper
itemActions={[]}
columns={[
{
label: "Time",
elementKey: "ptime",
renderFunction: (time: Date) => {
const timeParse = new Date(time);
return timeFromDate(timeParse);
},
globalClass: classes.timeItem,
},
{ label: "Name", elementKey: "api" },
{
label: "Status",
elementKey: "",
renderFunction: (fullElement: TraceMessage) =>
`${fullElement.statusCode} ${fullElement.statusMsg}`,
renderFullObject: true,
},
{
label: "Location",
elementKey: "configuration_id",
renderFunction: (fullElement: TraceMessage) =>
`${fullElement.host} ${fullElement.client}`,
renderFullObject: true,
},
{
label: "Load Time",
elementKey: "callStats.duration",
globalClass: classes.timeItem,
},
{
label: "Upload",
elementKey: "callStats.rx",
renderFunction: niceBytes,
globalClass: classes.sizeItem,
},
{
label: "Download",
elementKey: "callStats.tx",
renderFunction: niceBytes,
globalClass: classes.sizeItem,
},
]}
isLoading={false}
records={messages}
entityName="Traces"
idField="api"
customEmptyMessage={
traceStarted
? "No Traced elements received yet"
: "Trace is not started yet"
}
customPaperHeight={classes.tableWrapper}
autoScrollToBottom
/>
</Grid>
</Fragment>
);

View File

@@ -14,7 +14,15 @@
// 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 { Button, Grid, TextField, InputBase } from "@mui/material";
import {
Button,
FormControl,
Grid,
InputBase,
MenuItem,
Select,
TextField,
} from "@mui/material";
import { IMessageEvent, w3cwebsocket as W3CWebSocket } from "websocket";
import { connect } from "react-redux";
import { Theme } from "@mui/material/styles";
@@ -22,10 +30,9 @@ import createStyles from "@mui/styles/createStyles";
import withStyles from "@mui/styles/withStyles";
import { AppState } from "../../../store";
import { watchMessageReceived, watchResetMessages } from "./actions";
import { EventInfo, BucketList, Bucket } from "./types";
import { Bucket, BucketList, EventInfo } from "./types";
import { niceBytes, timeFromDate } from "../../../common/utils";
import { wsProtocol } from "../../../utils/wsUtils";
import { FormControl, MenuItem, Select } from "@mui/material";
import {
actionsTray,
containerForHeader,
@@ -35,6 +42,7 @@ import { ErrorResponseHandler } from "../../../common/types";
import TableWrapper from "../Common/TableWrapper/TableWrapper";
import PageHeader from "../Common/PageHeader/PageHeader";
import api from "../../../common/api";
import BackLink from "../../../common/BackLink";
const styles = (theme: Theme) =>
createStyles({
@@ -183,8 +191,11 @@ const Watch = ({
return (
<React.Fragment>
<PageHeader label="Watch" />
<Grid container>
<Grid item xs={12} className={classes.container}>
<Grid container className={classes.container}>
<Grid item xs={12}>
<BackLink to="/tools" label="Return to Tools" />
</Grid>
<Grid item xs={12}>
<Grid item xs={12} className={classes.actionsTray}>
<FormControl variant="outlined">
<Select

View File

@@ -1,74 +0,0 @@
import { createTheme, adaptV4Theme } from "@mui/material";
const newTheme = createTheme(
adaptV4Theme({
palette: {
primary: {
light: "#0c4453",
main: "#01262e",
dark: "#001115",
contrastText: "#fff",
},
secondary: {
light: "#ff7961",
main: "#f44336",
dark: "#01262E",
contrastText: "#000",
},
grey: {
100: "#F7F7F7",
200: "#D8DDDE",
300: "#BAC3C5",
400: "#9BA9AC",
500: "#7C8F93",
600: "#5D7479",
700: "#3F5A60",
800: "#204047",
900: "#01262E",
},
background: {
default: "#F4F4F4",
},
success: {
main: "#4ccb92",
},
warning: {
main: "#ffb300",
},
error: {
light: "#e03a48",
main: "#dc1f2e",
contrastText: "#ffffff",
},
},
typography: {
fontFamily: ["Lato", "sans-serif"].join(","),
h1: {
fontWeight: "bold",
color: "#01262E",
},
h2: {
fontWeight: "bold",
color: "#01262E",
},
h3: {
fontWeight: "bold",
color: "#01262E",
},
h4: {
fontWeight: "bold",
color: "#01262E",
},
h5: {
fontWeight: "bold",
color: "#01262E",
},
h6: {
fontWeight: "bold",
color: "#01262E",
},
},
})
);
export default newTheme;