mirror of
https://github.com/OpenMaxIO/openmaxio-object-browser
synced 2026-07-01 07:41:18 -07:00
Added speedtest page & updated diagnostic page (#1099)
Signed-off-by: Benjamin Perez <benjamin@bexsoft.net> Co-authored-by: Benjamin Perez <benjamin@bexsoft.net> Co-authored-by: Daniel Valdivia <18384552+dvaldivia@users.noreply.github.com>
This commit is contained in:
6
go.mod
6
go.mod
@@ -19,9 +19,9 @@ require (
|
||||
github.com/minio/cli v1.22.0
|
||||
github.com/minio/direct-csi v1.3.5-0.20210601185811-f7776f7961bf
|
||||
github.com/minio/kes v0.11.0
|
||||
github.com/minio/madmin-go v1.1.10
|
||||
github.com/minio/mc v0.0.0-20211027024940-7866f97ef502
|
||||
github.com/minio/minio-go/v7 v7.0.15-0.20211004160302-3b57c1e369ca
|
||||
github.com/minio/madmin-go v1.1.12
|
||||
github.com/minio/mc v0.0.0-20211110003602-1461b652d920
|
||||
github.com/minio/minio-go/v7 v7.0.15
|
||||
github.com/minio/operator v0.0.0-20211011212245-31460bbbc4b7
|
||||
github.com/minio/operator/logsearchapi v0.0.0-20211011212245-31460bbbc4b7
|
||||
github.com/minio/pkg v1.1.5
|
||||
|
||||
12
go.sum
12
go.sum
@@ -909,16 +909,18 @@ github.com/minio/filepath v1.0.0/go.mod h1:/nRZA2ldl5z6jT9/KQuvZcQlxZIMQoFFQPvEX
|
||||
github.com/minio/kes v0.11.0 h1:8ma6OCVSxKT50b1uYXLJro3m7PmZtCLxBaTddQexI5k=
|
||||
github.com/minio/kes v0.11.0/go.mod h1:mTF1Bv8YVEtQqF/B7Felp4tLee44Pp+dgI0rhCvgNg8=
|
||||
github.com/minio/madmin-go v1.0.12/go.mod h1:BK+z4XRx7Y1v8SFWXsuLNqQqnq5BO/axJ8IDJfgyvfs=
|
||||
github.com/minio/madmin-go v1.1.10 h1:pfMgXkzdwADnNfVdNMJbwok2fjb2sJ7Q76kDt89RGzE=
|
||||
github.com/minio/madmin-go v1.1.10/go.mod h1:Iu0OnrMWNBYx1lqJTW+BFjBMx0Hi0wjw8VmqhiOs2Jo=
|
||||
github.com/minio/mc v0.0.0-20211027024940-7866f97ef502 h1:7ip9qTspUniv+WDENgOcfUr95IccxG5aDkBM4Z96kQg=
|
||||
github.com/minio/mc v0.0.0-20211027024940-7866f97ef502/go.mod h1:vxztwXLB9Gyl/h3Yh08Mpz1CB/0FO5Es0iQRpzxvS5I=
|
||||
github.com/minio/madmin-go v1.1.11-0.20211102182201-e51fd3d6b104/go.mod h1:Iu0OnrMWNBYx1lqJTW+BFjBMx0Hi0wjw8VmqhiOs2Jo=
|
||||
github.com/minio/madmin-go v1.1.12 h1:dtSPkOlzDa1Z2dnw9VQL0+OVVl+7O23o2lfztWs0Dqc=
|
||||
github.com/minio/madmin-go v1.1.12/go.mod h1:Iu0OnrMWNBYx1lqJTW+BFjBMx0Hi0wjw8VmqhiOs2Jo=
|
||||
github.com/minio/mc v0.0.0-20211110003602-1461b652d920 h1:3QhH/Ji1X6OsoQFkebmsU0D2R86bSpqm567xzWM7WtY=
|
||||
github.com/minio/mc v0.0.0-20211110003602-1461b652d920/go.mod h1:V8NmUfU0W3G/mrifeO6nm4CWFTiXY2nx7FJyMge/aHk=
|
||||
github.com/minio/md5-simd v1.1.0/go.mod h1:XpBqgZULrMYD3R+M28PcmP0CkI7PEMzB3U77ZrKZ0Gw=
|
||||
github.com/minio/md5-simd v1.1.2 h1:Gdi1DZK69+ZVMoNHRXJyNcxrMA4dSxoYHZSQbirFg34=
|
||||
github.com/minio/md5-simd v1.1.2/go.mod h1:MzdKDxYpY2BT9XQFocsiZf/NKVtR7nkE4RoEpN+20RM=
|
||||
github.com/minio/minio-go/v7 v7.0.11-0.20210302210017-6ae69c73ce78/go.mod h1:mTh2uJuAbEqdhMVl6CMIIZLUeiMiWtJR4JB8/5g2skw=
|
||||
github.com/minio/minio-go/v7 v7.0.15-0.20211004160302-3b57c1e369ca h1:DKdUaXCMM6fFUwS9K68HGw8nlqqUZhQN106rPW1V/oI=
|
||||
github.com/minio/minio-go/v7 v7.0.15-0.20211004160302-3b57c1e369ca/go.mod h1:pUV0Pc+hPd1nccgmzQF/EXh48l/Z/yps6QPF1aaie4g=
|
||||
github.com/minio/minio-go/v7 v7.0.15 h1:r9/NhjJ+nXYrIYvbObhvc1wPj3YH1iDpJzz61uRKLyY=
|
||||
github.com/minio/minio-go/v7 v7.0.15/go.mod h1:pUV0Pc+hPd1nccgmzQF/EXh48l/Z/yps6QPF1aaie4g=
|
||||
github.com/minio/operator v0.0.0-20211011212245-31460bbbc4b7 h1:dkfuMNslMjGoJ4ArAMSoQhidYNdm3SgzLBP+f96O3/E=
|
||||
github.com/minio/operator v0.0.0-20211011212245-31460bbbc4b7/go.mod h1:lDpuz8nwsfhKlfiBaA3Z8AW019fWEAjO2gltfLbdorE=
|
||||
github.com/minio/operator/logsearchapi v0.0.0-20211011212245-31460bbbc4b7 h1:vFtQqCt67ETp0JAkOKRWTKkgwFv14Vc1jJSxmQ8wJE0=
|
||||
|
||||
@@ -74,6 +74,7 @@ var (
|
||||
tools = "/tools"
|
||||
logs = "/tools/logs"
|
||||
auditLogs = "/tools/audit-logs"
|
||||
speedtest = "/tools/speedtest"
|
||||
healthInfo = "/tools/diagnostics"
|
||||
)
|
||||
|
||||
@@ -294,6 +295,16 @@ var healthInfoActionSet = ConfigurationActionSet{
|
||||
),
|
||||
}
|
||||
|
||||
// logsActionSet contains the list of admin actions required for this endpoint to work
|
||||
var speedtestActionSet = ConfigurationActionSet{
|
||||
actionTypes: iampolicy.NewActionSet(
|
||||
iampolicy.AllAdminActions,
|
||||
),
|
||||
actions: iampolicy.NewActionSet(
|
||||
iampolicy.HealthInfoAdminAction,
|
||||
),
|
||||
}
|
||||
|
||||
var displayRules = map[string]func() bool{
|
||||
// disable users page if LDAP is enabled
|
||||
users: func() bool {
|
||||
@@ -344,6 +355,7 @@ var endpointRules = map[string]ConfigurationActionSet{
|
||||
auditLogs: logsActionSet,
|
||||
tools: toolsActionSet,
|
||||
healthInfo: healthInfoActionSet,
|
||||
speedtest: speedtestActionSet,
|
||||
}
|
||||
|
||||
// operatorRules contains the mapping between endpoints and ActionSets for operator only mode
|
||||
|
||||
@@ -70,7 +70,7 @@ func TestGetAuthorizedEndpoints(t *testing.T) {
|
||||
"admin:*",
|
||||
},
|
||||
},
|
||||
want: 33,
|
||||
want: 34,
|
||||
},
|
||||
{
|
||||
name: "all s3 endpoints",
|
||||
@@ -89,7 +89,7 @@ func TestGetAuthorizedEndpoints(t *testing.T) {
|
||||
"s3:*",
|
||||
},
|
||||
},
|
||||
want: 35,
|
||||
want: 36,
|
||||
},
|
||||
{
|
||||
name: "Console User - default endpoints",
|
||||
|
||||
@@ -1,23 +1,23 @@
|
||||
{
|
||||
"files": {
|
||||
"main.css": "./static/css/main.ed78990a.chunk.css",
|
||||
"main.js": "./static/js/main.894c8129.chunk.js",
|
||||
"main.js.map": "./static/js/main.894c8129.chunk.js.map",
|
||||
"main.js": "./static/js/main.f80bc34d.chunk.js",
|
||||
"main.js.map": "./static/js/main.f80bc34d.chunk.js.map",
|
||||
"runtime-main.js": "./static/js/runtime-main.30f8243a.js",
|
||||
"runtime-main.js.map": "./static/js/runtime-main.30f8243a.js.map",
|
||||
"static/css/2.71021f35.chunk.css": "./static/css/2.71021f35.chunk.css",
|
||||
"static/js/2.0aaf1c4d.chunk.js": "./static/js/2.0aaf1c4d.chunk.js",
|
||||
"static/js/2.0aaf1c4d.chunk.js.map": "./static/js/2.0aaf1c4d.chunk.js.map",
|
||||
"static/js/2.05f3ee05.chunk.js": "./static/js/2.05f3ee05.chunk.js",
|
||||
"static/js/2.05f3ee05.chunk.js.map": "./static/js/2.05f3ee05.chunk.js.map",
|
||||
"index.html": "./index.html",
|
||||
"static/css/2.71021f35.chunk.css.map": "./static/css/2.71021f35.chunk.css.map",
|
||||
"static/css/main.ed78990a.chunk.css.map": "./static/css/main.ed78990a.chunk.css.map",
|
||||
"static/js/2.0aaf1c4d.chunk.js.LICENSE.txt": "./static/js/2.0aaf1c4d.chunk.js.LICENSE.txt"
|
||||
"static/js/2.05f3ee05.chunk.js.LICENSE.txt": "./static/js/2.05f3ee05.chunk.js.LICENSE.txt"
|
||||
},
|
||||
"entrypoints": [
|
||||
"static/js/runtime-main.30f8243a.js",
|
||||
"static/css/2.71021f35.chunk.css",
|
||||
"static/js/2.0aaf1c4d.chunk.js",
|
||||
"static/js/2.05f3ee05.chunk.js",
|
||||
"static/css/main.ed78990a.chunk.css",
|
||||
"static/js/main.894c8129.chunk.js"
|
||||
"static/js/main.f80bc34d.chunk.js"
|
||||
]
|
||||
}
|
||||
@@ -1 +1 @@
|
||||
<!doctype html><html lang="en"><head><meta charset="utf-8"/><meta name="viewport" content="width=device-width,initial-scale=1"/><meta name="theme-color" content="#081C42" media="(prefers-color-scheme: light)"/><meta name="theme-color" content="#081C42" media="(prefers-color-scheme: dark)"/><meta name="description" content="MinIO Console"/><link href="./styles/root-styles.css" rel="stylesheet"/><link rel="apple-touch-icon" sizes="180x180" href="./apple-icon-180x180.png"/><link rel="icon" type="image/png" sizes="32x32" href="./favicon-32x32.png"/><link rel="icon" type="image/png" sizes="96x96" href="./favicon-96x96.png"/><link rel="icon" type="image/png" sizes="16x16" href="./favicon-16x16.png"/><link rel="manifest" href="./manifest.json"/><link rel="mask-icon" href="./safari-pinned-tab.svg" color="#3a4e54"/><title>MinIO Console</title><link href="./static/css/2.71021f35.chunk.css" rel="stylesheet"><link href="./static/css/main.ed78990a.chunk.css" rel="stylesheet"></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"><div id="loader-block"><svg class="loader-svg-container" viewBox="22 22 44 44"><circle class="loader-style MuiCircularProgress-circle MuiCircularProgress-circleIndeterminate" cx="44" cy="44" r="20.2" fill="none" stroke-width="3.6"></circle></svg></div></div><script>!function(e){function r(r){for(var n,l,i=r[0],a=r[1],p=r[2],c=0,s=[];c<i.length;c++)l=i[c],Object.prototype.hasOwnProperty.call(o,l)&&o[l]&&s.push(o[l][0]),o[l]=0;for(n in a)Object.prototype.hasOwnProperty.call(a,n)&&(e[n]=a[n]);for(f&&f(r);s.length;)s.shift()();return u.push.apply(u,p||[]),t()}function t(){for(var e,r=0;r<u.length;r++){for(var t=u[r],n=!0,i=1;i<t.length;i++){var a=t[i];0!==o[a]&&(n=!1)}n&&(u.splice(r--,1),e=l(l.s=t[0]))}return e}var n={},o={1:0},u=[];function l(r){if(n[r])return n[r].exports;var t=n[r]={i:r,l:!1,exports:{}};return e[r].call(t.exports,t,t.exports,l),t.l=!0,t.exports}l.m=e,l.c=n,l.d=function(e,r,t){l.o(e,r)||Object.defineProperty(e,r,{enumerable:!0,get:t})},l.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},l.t=function(e,r){if(1&r&&(e=l(e)),8&r)return e;if(4&r&&"object"==typeof e&&e&&e.__esModule)return e;var t=Object.create(null);if(l.r(t),Object.defineProperty(t,"default",{enumerable:!0,value:e}),2&r&&"string"!=typeof e)for(var n in e)l.d(t,n,function(r){return e[r]}.bind(null,n));return t},l.n=function(e){var r=e&&e.__esModule?function(){return e.default}:function(){return e};return l.d(r,"a",r),r},l.o=function(e,r){return Object.prototype.hasOwnProperty.call(e,r)},l.p="./";var i=this["webpackJsonpportal-ui"]=this["webpackJsonpportal-ui"]||[],a=i.push.bind(i);i.push=r,i=i.slice();for(var p=0;p<i.length;p++)r(i[p]);var f=a;t()}([])</script><script src="./static/js/2.0aaf1c4d.chunk.js"></script><script src="./static/js/main.894c8129.chunk.js"></script></body></html>
|
||||
<!doctype html><html lang="en"><head><meta charset="utf-8"/><meta name="viewport" content="width=device-width,initial-scale=1"/><meta name="theme-color" content="#081C42" media="(prefers-color-scheme: light)"/><meta name="theme-color" content="#081C42" media="(prefers-color-scheme: dark)"/><meta name="description" content="MinIO Console"/><link href="./styles/root-styles.css" rel="stylesheet"/><link rel="apple-touch-icon" sizes="180x180" href="./apple-icon-180x180.png"/><link rel="icon" type="image/png" sizes="32x32" href="./favicon-32x32.png"/><link rel="icon" type="image/png" sizes="96x96" href="./favicon-96x96.png"/><link rel="icon" type="image/png" sizes="16x16" href="./favicon-16x16.png"/><link rel="manifest" href="./manifest.json"/><link rel="mask-icon" href="./safari-pinned-tab.svg" color="#3a4e54"/><title>MinIO Console</title><link href="./static/css/2.71021f35.chunk.css" rel="stylesheet"><link href="./static/css/main.ed78990a.chunk.css" rel="stylesheet"></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"><div id="loader-block"><svg class="loader-svg-container" viewBox="22 22 44 44"><circle class="loader-style MuiCircularProgress-circle MuiCircularProgress-circleIndeterminate" cx="44" cy="44" r="20.2" fill="none" stroke-width="3.6"></circle></svg></div></div><script>!function(e){function r(r){for(var n,l,i=r[0],a=r[1],p=r[2],c=0,s=[];c<i.length;c++)l=i[c],Object.prototype.hasOwnProperty.call(o,l)&&o[l]&&s.push(o[l][0]),o[l]=0;for(n in a)Object.prototype.hasOwnProperty.call(a,n)&&(e[n]=a[n]);for(f&&f(r);s.length;)s.shift()();return u.push.apply(u,p||[]),t()}function t(){for(var e,r=0;r<u.length;r++){for(var t=u[r],n=!0,i=1;i<t.length;i++){var a=t[i];0!==o[a]&&(n=!1)}n&&(u.splice(r--,1),e=l(l.s=t[0]))}return e}var n={},o={1:0},u=[];function l(r){if(n[r])return n[r].exports;var t=n[r]={i:r,l:!1,exports:{}};return e[r].call(t.exports,t,t.exports,l),t.l=!0,t.exports}l.m=e,l.c=n,l.d=function(e,r,t){l.o(e,r)||Object.defineProperty(e,r,{enumerable:!0,get:t})},l.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},l.t=function(e,r){if(1&r&&(e=l(e)),8&r)return e;if(4&r&&"object"==typeof e&&e&&e.__esModule)return e;var t=Object.create(null);if(l.r(t),Object.defineProperty(t,"default",{enumerable:!0,value:e}),2&r&&"string"!=typeof e)for(var n in e)l.d(t,n,function(r){return e[r]}.bind(null,n));return t},l.n=function(e){var r=e&&e.__esModule?function(){return e.default}:function(){return e};return l.d(r,"a",r),r},l.o=function(e,r){return Object.prototype.hasOwnProperty.call(e,r)},l.p="./";var i=this["webpackJsonpportal-ui"]=this["webpackJsonpportal-ui"]||[],a=i.push.bind(i);i.push=r,i=i.slice();for(var p=0;p<i.length;p++)r(i[p]);var f=a;t()}([])</script><script src="./static/js/2.05f3ee05.chunk.js"></script><script src="./static/js/main.f80bc34d.chunk.js"></script></body></html>
|
||||
3
portal-ui/build/static/js/2.05f3ee05.chunk.js
Normal file
3
portal-ui/build/static/js/2.05f3ee05.chunk.js
Normal file
File diff suppressed because one or more lines are too long
1
portal-ui/build/static/js/2.05f3ee05.chunk.js.map
Normal file
1
portal-ui/build/static/js/2.05f3ee05.chunk.js.map
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
2
portal-ui/build/static/js/main.f80bc34d.chunk.js
Normal file
2
portal-ui/build/static/js/main.f80bc34d.chunk.js
Normal file
File diff suppressed because one or more lines are too long
1
portal-ui/build/static/js/main.f80bc34d.chunk.js.map
Normal file
1
portal-ui/build/static/js/main.f80bc34d.chunk.js.map
Normal file
File diff suppressed because one or more lines are too long
@@ -24,6 +24,7 @@ const minMemReq = 2147483648; // Minimal Memory required for MinIO in bytes
|
||||
export const units = ["B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"];
|
||||
export const k8sUnits = ["Ki", "Mi", "Gi", "Ti", "Pi", "Ei"];
|
||||
export const k8sCalcUnits = ["B", ...k8sUnits];
|
||||
export const timeUnits = ["ms", "s", "m", "h", "d", "w", "M", "Q", "y"];
|
||||
|
||||
export const niceBytes = (x: string, showK8sUnits: boolean = false) => {
|
||||
let n = parseInt(x, 10) || 0;
|
||||
|
||||
52
portal-ui/src/icons/ComputerLineIcon.tsx
Normal file
52
portal-ui/src/icons/ComputerLineIcon.tsx
Normal file
@@ -0,0 +1,52 @@
|
||||
// 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 { SvgIcon } from "@mui/material";
|
||||
|
||||
interface IClusterIcon {
|
||||
width?: number;
|
||||
}
|
||||
|
||||
const ComputerLineIcon = ({ width = 24 }: IClusterIcon) => {
|
||||
return (
|
||||
<SvgIcon style={{ width: width, height: width }}>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="100%"
|
||||
height="100%"
|
||||
viewBox="0 0 38.888 29.791"
|
||||
>
|
||||
<g id="computer-line" transform="translate(-1 -5)">
|
||||
<path
|
||||
d="M9.833,24.9V10.833H26.791L28.6,9H8V24.9Z"
|
||||
transform="translate(1.021 0.583)"
|
||||
/>
|
||||
<path
|
||||
d="M6.292,7.292h27.5V25.624h2.292V6.719A1.719,1.719,0,0,0,34.363,5H5.719A1.719,1.719,0,0,0,4,6.719V25.624H6.292Z"
|
||||
transform="translate(0.437)"
|
||||
/>
|
||||
<path
|
||||
d="M1,25v3.9a2.979,2.979,0,0,0,2.979,2.979h32.93A2.979,2.979,0,0,0,39.888,28.9V25Zm36.665,3.9a.687.687,0,0,1-.687.687H3.933a.687.687,0,0,1-.687-.687V26.753h11.4A1.879,1.879,0,0,0,16.365,27.9h8.169a1.879,1.879,0,0,0,1.719-1.146H37.665Z"
|
||||
transform="translate(0 2.916)"
|
||||
/>
|
||||
</g>
|
||||
</svg>
|
||||
</SvgIcon>
|
||||
);
|
||||
};
|
||||
|
||||
export default ComputerLineIcon;
|
||||
@@ -20,16 +20,15 @@ import { SvgIcon } from "@mui/material";
|
||||
const CopyIcon = () => {
|
||||
return (
|
||||
<SvgIcon>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
|
||||
<title>ic_h_copy-new_sl</title>
|
||||
<g id="Layer_2" data-name="Layer 2">
|
||||
<g id="Layer_1-2" data-name="Layer 1">
|
||||
<path
|
||||
className="cls-1"
|
||||
d="M0,0V16H16V0ZM11.886,9.048H9.048v2.838h-2.1V9.048H4.114v-2.1H6.952V4.114h2.1V6.952h2.838Z"
|
||||
/>
|
||||
</g>
|
||||
</g>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 13.677 13.677">
|
||||
<path
|
||||
d="M41.764,15.9H34.3a1.4,1.4,0,0,0-1.4,1.4v7.46a1.4,1.4,0,0,0,1.4,1.4h7.46a1.4,1.4,0,0,0,1.4-1.4V17.3A1.4,1.4,0,0,0,41.764,15.9Zm.2,8.864a.2.2,0,0,1-.2.2H34.3a.2.2,0,0,1-.2-.2V17.3a.2.2,0,0,1,.2-.2h7.46a.2.2,0,0,1,.2.2Z"
|
||||
transform="translate(-29.491 -15.9)"
|
||||
/>
|
||||
<path
|
||||
d="M17.3,34.1h.441a.6.6,0,1,0,0-1.2H17.3a1.4,1.4,0,0,0-1.4,1.4v7.46a1.4,1.4,0,0,0,1.4,1.4h7.46a1.4,1.4,0,0,0,1.4-1.4v-.481a.6.6,0,0,0-1.2,0v.481a.2.2,0,0,1-.2.2H17.3a.2.2,0,0,1-.2-.2V34.3A.2.2,0,0,1,17.3,34.1Z"
|
||||
transform="translate(-15.9 -29.491)"
|
||||
/>
|
||||
</svg>
|
||||
</SvgIcon>
|
||||
);
|
||||
|
||||
46
portal-ui/src/icons/DownloadStatIcon.tsx
Normal file
46
portal-ui/src/icons/DownloadStatIcon.tsx
Normal file
@@ -0,0 +1,46 @@
|
||||
// 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 DownloadStatIcon = (props: SvgIconProps) => {
|
||||
return (
|
||||
<SvgIcon {...props}>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 13 13">
|
||||
<g transform="translate(0)">
|
||||
<path
|
||||
className="a"
|
||||
fill="#4CCB92"
|
||||
d="M1.966,8.119a.69.69,0,0,0,1.38,0V2.355l.782.781A.69.69,0,0,0,5.1,2.161L3.148.206a.69.69,0,0,0-.984,0L.21,2.161a.69.69,0,0,0,.975.975l.781-.781V8.119Z"
|
||||
transform="translate(9.248 11.151) rotate(180)"
|
||||
/>
|
||||
<g
|
||||
className="b"
|
||||
stroke="#4CCB92"
|
||||
fill="none"
|
||||
transform="translate(0)"
|
||||
>
|
||||
<circle className="c" stroke="none" cx="6.5" cy="6.5" r="6.5" />
|
||||
<circle className="d" fill="none" cx="6.5" cy="6.5" r="6" />
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
</SvgIcon>
|
||||
);
|
||||
};
|
||||
|
||||
export default DownloadStatIcon;
|
||||
73
portal-ui/src/icons/JSONIcon.tsx
Normal file
73
portal-ui/src/icons/JSONIcon.tsx
Normal file
@@ -0,0 +1,73 @@
|
||||
// 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 JSONIcon = (props: SvgIconProps) => {
|
||||
return (
|
||||
<SvgIcon {...props}>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24.726 20.753">
|
||||
<g transform="translate(-1649.388 -543.732)">
|
||||
<g transform="translate(1656.071 552.846)">
|
||||
<circle
|
||||
cx="1.5"
|
||||
cy="1.5"
|
||||
r="1.5"
|
||||
transform="translate(-0.329 -0.295)"
|
||||
/>
|
||||
<circle
|
||||
cx="1.5"
|
||||
cy="1.5"
|
||||
r="1.5"
|
||||
transform="translate(8.671 -0.295)"
|
||||
/>
|
||||
<circle
|
||||
cx="1.5"
|
||||
cy="1.5"
|
||||
r="1.5"
|
||||
transform="translate(4.671 -0.295)"
|
||||
/>
|
||||
</g>
|
||||
<g transform="translate(1649.588 543.932)">
|
||||
<g transform="translate(17.829 0)">
|
||||
<path
|
||||
d="M13.681,0a.838.838,0,0,0,0,1.677,2.307,2.307,0,0,1,2.306,2.305V8.454a2.505,2.505,0,0,0,.685,1.722,2.505,2.505,0,0,0-.685,1.722v4.472a2.307,2.307,0,0,1-2.306,2.306.838.838,0,0,0,0,1.677,3.986,3.986,0,0,0,3.982-3.982V11.9a.839.839,0,0,1,.838-.838.838.838,0,0,0,.838-.838c0-.015,0-.03,0-.046s0-.03,0-.046a.838.838,0,0,0-.838-.838.839.839,0,0,1-.838-.838V3.982A3.987,3.987,0,0,0,13.681,0Z"
|
||||
transform="translate(-12.843 0)"
|
||||
/>
|
||||
<path
|
||||
d="M13.681-.2a4.187,4.187,0,0,1,4.182,4.182V8.454a.639.639,0,0,0,.638.638,1.039,1.039,0,0,1,1.038,1.038c0,.015,0,.031,0,.046s0,.03,0,.046A1.04,1.04,0,0,1,18.5,11.261a.639.639,0,0,0-.638.638v4.472a4.187,4.187,0,0,1-4.182,4.182,1.038,1.038,0,0,1,0-2.077,2.108,2.108,0,0,0,2.106-2.106V11.9a2.7,2.7,0,0,1,.619-1.722,2.7,2.7,0,0,1-.619-1.722V3.982a2.108,2.108,0,0,0-2.106-2.105,1.038,1.038,0,0,1,0-2.077Zm0,20.353a3.787,3.787,0,0,0,3.782-3.782V11.9A1.04,1.04,0,0,1,18.5,10.861a.639.639,0,0,0,.638-.638c0-.012,0-.023,0-.035v-.021c0-.012,0-.023,0-.035a.639.639,0,0,0-.638-.638,1.04,1.04,0,0,1-1.038-1.038V3.982A3.786,3.786,0,0,0,13.681.2a.638.638,0,0,0,0,1.277,2.508,2.508,0,0,1,2.506,2.505V8.454a2.3,2.3,0,0,0,.631,1.585l.129.137-.129.137a2.3,2.3,0,0,0-.631,1.585v4.472a2.508,2.508,0,0,1-2.506,2.506.638.638,0,0,0,0,1.277Z"
|
||||
transform="translate(-12.843 0)"
|
||||
/>
|
||||
</g>
|
||||
<g transform="translate(0 0)">
|
||||
<path
|
||||
d="M18.5,0a.838.838,0,0,1,0,1.677A2.307,2.307,0,0,0,16.2,3.982V8.454a2.505,2.505,0,0,1-.685,1.722A2.505,2.505,0,0,1,16.2,11.9v4.472A2.307,2.307,0,0,0,18.5,18.676a.838.838,0,0,1,0,1.677,3.986,3.986,0,0,1-3.982-3.982V11.9a.839.839,0,0,0-.838-.838.838.838,0,0,1-.838-.838c0-.015,0-.03,0-.046s0-.03,0-.046a.838.838,0,0,1,.838-.838.839.839,0,0,0,.838-.838V3.982A3.987,3.987,0,0,1,18.5,0Z"
|
||||
transform="translate(-12.843 0)"
|
||||
/>
|
||||
<path
|
||||
d="M18.5-.2a1.038,1.038,0,0,1,0,2.077A2.108,2.108,0,0,0,16.4,3.982V8.454a2.7,2.7,0,0,1-.619,1.722A2.7,2.7,0,0,1,16.4,11.9v4.472A2.108,2.108,0,0,0,18.5,18.476a1.038,1.038,0,0,1,0,2.077,4.187,4.187,0,0,1-4.182-4.182V11.9a.639.639,0,0,0-.638-.638,1.04,1.04,0,0,1-1.038-1.038c0-.015,0-.031,0-.046s0-.03,0-.046a1.04,1.04,0,0,1,1.038-1.038.639.639,0,0,0,.638-.638V3.982A4.187,4.187,0,0,1,18.5-.2Zm0,20.353a.638.638,0,0,0,0-1.277A2.508,2.508,0,0,1,16,16.371V11.9a2.3,2.3,0,0,0-.631-1.585l-.129-.137.129-.137A2.3,2.3,0,0,0,16,8.454V3.982A2.508,2.508,0,0,1,18.5,1.477.638.638,0,0,0,18.5.2a3.787,3.787,0,0,0-3.782,3.782V8.454a1.04,1.04,0,0,1-1.038,1.038.639.639,0,0,0-.638.638c0,.012,0,.024,0,.035v.021c0,.012,0,.023,0,.035a.639.639,0,0,0,.638.638A1.04,1.04,0,0,1,14.719,11.9v4.472A3.787,3.787,0,0,0,18.5,20.153Z"
|
||||
transform="translate(-12.843 0)"
|
||||
/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
</SvgIcon>
|
||||
);
|
||||
};
|
||||
|
||||
export default JSONIcon;
|
||||
33
portal-ui/src/icons/NextArrowIcon.tsx
Normal file
33
portal-ui/src/icons/NextArrowIcon.tsx
Normal file
@@ -0,0 +1,33 @@
|
||||
// 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 NextArrowIcon = (props: SvgIconProps) => {
|
||||
return (
|
||||
<SvgIcon {...props}>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 12.621 7.62">
|
||||
<path
|
||||
d="M2.821,11.646a.989.989,0,0,0,1.979,0V3.378L5.92,4.5a.99.99,0,0,0,1.4-1.4L4.515.3A.989.989,0,0,0,3.1.3L.3,3.1A.989.989,0,0,0,1.7,4.5L2.821,3.378v8.268Z"
|
||||
transform="translate(12.621) rotate(90)"
|
||||
/>
|
||||
</svg>
|
||||
</SvgIcon>
|
||||
);
|
||||
};
|
||||
|
||||
export default NextArrowIcon;
|
||||
109
portal-ui/src/icons/SpeedtestIcon.tsx
Normal file
109
portal-ui/src/icons/SpeedtestIcon.tsx
Normal file
@@ -0,0 +1,109 @@
|
||||
// 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 SpeedtestIcon = (props: SvgIconProps) => {
|
||||
return (
|
||||
<SvgIcon {...props}>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256">
|
||||
<g id="speedtest-icn" transform="translate(-909.185 -1023.223)">
|
||||
<path
|
||||
id="Trazado_426"
|
||||
data-name="Trazado 426"
|
||||
d="M1032.321,1146.118l-.1.084a5.326,5.326,0,0,0,3.505,9.344l-.011.063a5.319,5.319,0,0,0,3.516-1.371l.1-.084q.167-.135.322-.281a5.337,5.337,0,1,0-7.333-7.756Z"
|
||||
transform="translate(-59.722 -59.838)"
|
||||
/>
|
||||
<path
|
||||
id="Trazado_427"
|
||||
data-name="Trazado 427"
|
||||
d="M999.251,1197.047a4.336,4.336,0,0,0-5.884,1.729v.095a4.336,4.336,0,0,0,3.817,6.344l-.011.01a4.361,4.361,0,0,0,2.078-8.178Z"
|
||||
transform="translate(-41.238 -85.391)"
|
||||
/>
|
||||
<path
|
||||
id="Trazado_428"
|
||||
data-name="Trazado 428"
|
||||
d="M1152.562,1112.162h.293a7.816,7.816,0,1,0-.046-15.631h-.247a7.816,7.816,0,0,0,0,15.631Z"
|
||||
transform="translate(-116.086 -36.123)"
|
||||
/>
|
||||
<path
|
||||
id="Trazado_429"
|
||||
data-name="Trazado 429"
|
||||
d="M979.368,1259.216l-.028,0a2.958,2.958,0,0,0-3.324,2.541v.08a2.973,2.973,0,0,0,2.559,3.336,3.173,3.173,0,0,0,.379,0l-.021.007a2.972,2.972,0,0,0,2.959-2.558v-.056A2.966,2.966,0,0,0,979.368,1259.216Z"
|
||||
transform="translate(-32.919 -116.272)"
|
||||
/>
|
||||
<path
|
||||
id="Trazado_430"
|
||||
data-name="Trazado 430"
|
||||
d="M1085.813,1109.608l-.209.078a7.07,7.07,0,0,0,2.5,13.688l-.022.065a7.009,7.009,0,0,0,2.537-.529l.165-.066.1-.039a7.07,7.07,0,1,0-5.076-13.2Z"
|
||||
transform="translate(-84.673 -42.333)"
|
||||
/>
|
||||
<path
|
||||
id="Trazado_431"
|
||||
data-name="Trazado 431"
|
||||
d="M1271.935,1152.822a9.817,9.817,0,0,0-.929-13.852l-.268-.235a9.817,9.817,0,0,0-12.881,14.8l.246.212a9.806,9.806,0,0,0,6.452,2.426h0A9.815,9.815,0,0,0,1271.935,1152.822Z"
|
||||
transform="translate(-170.269 -55.836)"
|
||||
/>
|
||||
<path
|
||||
id="Trazado_432"
|
||||
data-name="Trazado 432"
|
||||
d="M1313.471,1236.2h0Z"
|
||||
transform="translate(-199.154 -104.944)"
|
||||
/>
|
||||
<path
|
||||
id="Trazado_433"
|
||||
data-name="Trazado 433"
|
||||
d="M1333.353,1250.845l-.067-.495a12.786,12.786,0,0,0-12.612-11.007,12.761,12.761,0,0,0-12.638,14.485v.428a12.786,12.786,0,0,0,12.612,11.047,13.068,13.068,0,0,0,1.778-.12A12.76,12.76,0,0,0,1333.353,1250.845Z"
|
||||
transform="translate(-196.477 -106.494)"
|
||||
/>
|
||||
<path
|
||||
id="Trazado_434"
|
||||
data-name="Trazado 434"
|
||||
d="M1307.5,1203.557a11.283,11.283,0,0,0,4.537-15.3l-.2-.361c-.086-.167-.176-.332-.27-.5a11.283,11.283,0,1,0-19.545,11.281l.187.336a11.278,11.278,0,0,0,15.289,4.538Z"
|
||||
transform="translate(-187.898 -78.119)"
|
||||
/>
|
||||
<path
|
||||
id="Trazado_435"
|
||||
data-name="Trazado 435"
|
||||
d="M1214.118,1106.631l-.289-.111a8.657,8.657,0,1,0-6.052,16.222l.255.1a8.643,8.643,0,0,0,3.048.556l-.01.066a8.7,8.7,0,0,0,3.048-16.833Z"
|
||||
transform="translate(-144.357 -40.775)"
|
||||
/>
|
||||
<path
|
||||
id="Trazado_436"
|
||||
data-name="Trazado 436"
|
||||
d="M1165.325,1242.488l-13.839,11.867a.333.333,0,0,1-.331,0,17.171,17.171,0,1,0,10.435,12.167.333.333,0,0,1,0-.316l13.9-11.866a7.807,7.807,0,0,0-10.165-11.851Zm-12.039,27.588a8.26,8.26,0,1,1-8.26-8.26A8.26,8.26,0,0,1,1153.286,1270.075Z"
|
||||
transform="translate(-107.706 -107.117)"
|
||||
/>
|
||||
<path
|
||||
id="Trazado_437"
|
||||
data-name="Trazado 437"
|
||||
d="M1158.132,1407.172h-20.3a3.765,3.765,0,0,0,0,7.53h20.33a3.764,3.764,0,0,0,3.764-3.765v-.03A3.765,3.765,0,0,0,1158.132,1407.172Z"
|
||||
transform="translate(-110.812 -189.193)"
|
||||
/>
|
||||
<path
|
||||
id="Trazado_438"
|
||||
data-name="Trazado 438"
|
||||
d="M1037.185,1023.223a128,128,0,1,0,128,128A128.15,128.15,0,0,0,1037.185,1023.223Zm0,233.412A105.412,105.412,0,1,1,1142.6,1151.223,105.412,105.412,0,0,1,1037.185,1256.635Z"
|
||||
transform="translate(0 0)"
|
||||
/>
|
||||
</g>
|
||||
</svg>
|
||||
</SvgIcon>
|
||||
);
|
||||
};
|
||||
|
||||
export default SpeedtestIcon;
|
||||
46
portal-ui/src/icons/UploadStatIcon.tsx
Normal file
46
portal-ui/src/icons/UploadStatIcon.tsx
Normal file
@@ -0,0 +1,46 @@
|
||||
// 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 DownloadStatIcon = (props: SvgIconProps) => {
|
||||
return (
|
||||
<SvgIcon {...props}>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 13 13">
|
||||
<g transform="translate(0)">
|
||||
<path
|
||||
className="a"
|
||||
fill="#2781B0"
|
||||
d="M1.966,8.119a.69.69,0,0,0,1.38,0V2.355l.782.781A.69.69,0,0,0,5.1,2.161L3.148.206a.69.69,0,0,0-.984,0L.21,2.161a.69.69,0,0,0,.975.975l.781-.781V8.119Z"
|
||||
transform="translate(3.851 2.12)"
|
||||
/>
|
||||
<g
|
||||
className="b"
|
||||
stroke="#2781B0"
|
||||
fill="none"
|
||||
transform="translate(0)"
|
||||
>
|
||||
<circle className="c" stroke="none" cx="6.5" cy="6.5" r="6.5" />
|
||||
<circle className="d" fill="none" cx="6.5" cy="6.5" r="6" />
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
</SvgIcon>
|
||||
);
|
||||
};
|
||||
|
||||
export default DownloadStatIcon;
|
||||
@@ -105,3 +105,8 @@ export { default as OpenListIcon } from "./OpenListIcon";
|
||||
export { default as ToolsIcon } from "./ToolsIcon";
|
||||
export { default as RecoverIcon } from "./RecoverIcon";
|
||||
export { default as PrometheusIcon } from "./PrometheusIcon";
|
||||
export { default as NextArrowIcon } from "./NextArrowIcon";
|
||||
export { default as DownloadStatIcon } from "./DownloadStatIcon";
|
||||
export { default as UploadStatIcon } from "./UploadStatIcon";
|
||||
export { default as ComputerLineIcon } from "./ComputerLineIcon";
|
||||
export { default as JSONIcon } from "./JSONIcon";
|
||||
|
||||
@@ -40,7 +40,7 @@ import {
|
||||
ADMIN_LIST_USER_POLICIES,
|
||||
ADMIN_LIST_USERS,
|
||||
} from "../../../../types";
|
||||
import PanelTitle from "../../Common/PanelTitle";
|
||||
import PanelTitle from "../../Common/PanelTitle/PanelTitle";
|
||||
|
||||
const styles = (theme: Theme) => createStyles({});
|
||||
|
||||
|
||||
@@ -40,7 +40,7 @@ import {
|
||||
import { BucketInfo } from "../types";
|
||||
import { displayComponent } from "../../../../utils/permissions";
|
||||
import { S3_GET_BUCKET_POLICY, S3_PUT_BUCKET_POLICY } from "../../../../types";
|
||||
import PanelTitle from "../../Common/PanelTitle";
|
||||
import PanelTitle from "../../Common/PanelTitle/PanelTitle";
|
||||
|
||||
const styles = (theme: Theme) =>
|
||||
createStyles({
|
||||
|
||||
@@ -53,7 +53,7 @@ import { BucketsIcon, DeleteIcon, FolderIcon } from "../../../../icons";
|
||||
import DeleteBucket from "../ListBuckets/DeleteBucket";
|
||||
import AccessRulePanel from "./AccessRulePanel";
|
||||
import RefreshIcon from "../../../../icons/RefreshIcon";
|
||||
import BoxIconButton from "../../Common/BoxIconButton";
|
||||
import BoxIconButton from "../../Common/BoxIconButton/BoxIconButton";
|
||||
import {
|
||||
ADMIN_GET_POLICY,
|
||||
ADMIN_LIST_USER_POLICIES,
|
||||
|
||||
@@ -42,7 +42,7 @@ import {
|
||||
S3_GET_BUCKET_NOTIFICATIONS,
|
||||
S3_PUT_BUCKET_NOTIFICATIONS,
|
||||
} from "../../../../types";
|
||||
import PanelTitle from "../../Common/PanelTitle";
|
||||
import PanelTitle from "../../Common/PanelTitle/PanelTitle";
|
||||
|
||||
const styles = (theme: Theme) =>
|
||||
createStyles({
|
||||
|
||||
@@ -43,7 +43,7 @@ import {
|
||||
S3_GET_LIFECYCLE_CONFIGURATION,
|
||||
S3_PUT_LIFECYCLE_CONFIGURATION,
|
||||
} from "../../../../types";
|
||||
import PanelTitle from "../../Common/PanelTitle";
|
||||
import PanelTitle from "../../Common/PanelTitle/PanelTitle";
|
||||
|
||||
const styles = (theme: Theme) =>
|
||||
createStyles({
|
||||
|
||||
@@ -45,7 +45,7 @@ import {
|
||||
S3_GET_REPLICATION_CONFIGURATION,
|
||||
S3_PUT_REPLICATION_CONFIGURATION,
|
||||
} from "../../../../types";
|
||||
import PanelTitle from "../../Common/PanelTitle";
|
||||
import PanelTitle from "../../Common/PanelTitle/PanelTitle";
|
||||
|
||||
interface IBucketReplicationProps {
|
||||
classes: any;
|
||||
|
||||
@@ -68,7 +68,7 @@ import {
|
||||
S3_PUT_BUCKET_VERSIONING,
|
||||
S3_PUT_OBJECT_RETENTION,
|
||||
} from "../../../../types";
|
||||
import PanelTitle from "../../Common/PanelTitle";
|
||||
import PanelTitle from "../../Common/PanelTitle/PanelTitle";
|
||||
|
||||
interface IBucketSummaryProps {
|
||||
classes: any;
|
||||
|
||||
@@ -43,9 +43,9 @@ import { ISessionResponse } from "../../types";
|
||||
import TextField from "@mui/material/TextField";
|
||||
import InputAdornment from "@mui/material/InputAdornment";
|
||||
import SearchIcon from "../../../../icons/SearchIcon";
|
||||
import BoxIconButton from "../../Common/BoxIconButton";
|
||||
import BoxIconButton from "../../Common/BoxIconButton/BoxIconButton";
|
||||
import RefreshIcon from "../../../../icons/RefreshIcon";
|
||||
import AButton from "../../Common/AButton";
|
||||
import AButton from "../../Common/AButton/AButton";
|
||||
|
||||
const styles = (theme: Theme) =>
|
||||
createStyles({
|
||||
|
||||
@@ -102,7 +102,7 @@ import {
|
||||
} from "../../../../../../types";
|
||||
import { setBucketDetailsLoad, setBucketInfo } from "../../../actions";
|
||||
import { AppState } from "../../../../../../store";
|
||||
import BoxIconButton from "../../../../Common/BoxIconButton";
|
||||
import BoxIconButton from "../../../../Common/BoxIconButton/BoxIconButton";
|
||||
|
||||
const commonIcon = {
|
||||
backgroundRepeat: "no-repeat",
|
||||
|
||||
@@ -93,7 +93,7 @@ import SearchIcon from "../../../../../../icons/SearchIcon";
|
||||
import ObjectBrowserIcon from "../../../../../../icons/ObjectBrowserIcon";
|
||||
import PreviewFileContent from "../Preview/PreviewFileContent";
|
||||
import RestoreFileVersion from "./RestoreFileVersion";
|
||||
import BoxIconButton from "../../../../Common/BoxIconButton";
|
||||
import BoxIconButton from "../../../../Common/BoxIconButton/BoxIconButton";
|
||||
import { RecoverIcon } from "../../../../../../icons";
|
||||
|
||||
const styles = (theme: Theme) =>
|
||||
|
||||
@@ -55,6 +55,7 @@ interface InputBoxProps {
|
||||
max?: string;
|
||||
overlayIcon?: any;
|
||||
overlayAction?: () => void;
|
||||
overlayObject?: any;
|
||||
extraInputProps?: StandardInputProps["inputProps"];
|
||||
noLabelMinWidth?: boolean;
|
||||
}
|
||||
@@ -126,6 +127,7 @@ const InputBoxWrapper = ({
|
||||
min,
|
||||
max,
|
||||
overlayIcon = null,
|
||||
overlayObject = null,
|
||||
extraInputProps = {},
|
||||
overlayAction,
|
||||
noLabelMinWidth = false,
|
||||
@@ -214,6 +216,15 @@ const InputBoxWrapper = ({
|
||||
</IconButton>
|
||||
</div>
|
||||
)}
|
||||
{overlayObject && (
|
||||
<div
|
||||
className={`${classes.overlayAction} ${
|
||||
label !== "" ? "withLabel" : ""
|
||||
}`}
|
||||
>
|
||||
{overlayObject}
|
||||
</div>
|
||||
)}
|
||||
</Grid>
|
||||
</React.Fragment>
|
||||
);
|
||||
|
||||
@@ -0,0 +1,107 @@
|
||||
// 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 { selectorTypes } from "../SelectWrapper/SelectWrapper";
|
||||
import { Menu, MenuItem } from "@mui/material";
|
||||
|
||||
interface IInputUnitBox {
|
||||
classes: any;
|
||||
id: string;
|
||||
unitSelected: string;
|
||||
unitsList: selectorTypes[];
|
||||
disabled?: boolean;
|
||||
onUnitChange: (newValue: string) => void;
|
||||
}
|
||||
|
||||
const styles = (theme: Theme) =>
|
||||
createStyles({
|
||||
buttonTrigger: {
|
||||
border: "#F0F2F2 1px solid",
|
||||
borderRadius: 3,
|
||||
color: "#838383",
|
||||
backgroundColor: "#fff",
|
||||
fontSize: 12,
|
||||
},
|
||||
});
|
||||
|
||||
const InputUnitMenu = ({
|
||||
classes,
|
||||
id,
|
||||
unitSelected,
|
||||
unitsList,
|
||||
disabled = false,
|
||||
onUnitChange,
|
||||
}: IInputUnitBox) => {
|
||||
const [anchorEl, setAnchorEl] = React.useState<null | HTMLElement>(null);
|
||||
const open = Boolean(anchorEl);
|
||||
const handleClick = (event: React.MouseEvent<HTMLElement>) => {
|
||||
setAnchorEl(event.currentTarget);
|
||||
};
|
||||
const handleClose = (newUnit: string) => {
|
||||
setAnchorEl(null);
|
||||
if (newUnit !== "") {
|
||||
onUnitChange(newUnit);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<button
|
||||
id={`${id}-button`}
|
||||
aria-controls={`${id}-menu`}
|
||||
aria-haspopup="true"
|
||||
aria-expanded={open ? "true" : undefined}
|
||||
onClick={handleClick}
|
||||
className={classes.buttonTrigger}
|
||||
disabled={disabled}
|
||||
>
|
||||
{unitSelected}
|
||||
</button>
|
||||
<Menu
|
||||
id={`${id}-menu`}
|
||||
aria-labelledby={`${id}-button`}
|
||||
anchorEl={anchorEl}
|
||||
open={open}
|
||||
onClose={() => {
|
||||
handleClose("");
|
||||
}}
|
||||
anchorOrigin={{
|
||||
vertical: "bottom",
|
||||
horizontal: "center",
|
||||
}}
|
||||
transformOrigin={{
|
||||
vertical: "top",
|
||||
horizontal: "center",
|
||||
}}
|
||||
>
|
||||
{unitsList.map((unit) => (
|
||||
<MenuItem
|
||||
onClick={() => handleClose(unit.value)}
|
||||
key={`itemUnit-${unit.value}-${unit.label}`}
|
||||
>
|
||||
{unit.label}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Menu>
|
||||
</Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
export default withStyles(styles)(InputUnitMenu);
|
||||
@@ -987,3 +987,13 @@ export const commonDashboardInfocard = {
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export const linkStyles = (color: string) => ({
|
||||
link: {
|
||||
textDecoration: "underline",
|
||||
color,
|
||||
backgroundColor: "transparent",
|
||||
border: 0,
|
||||
cursor: "pointer",
|
||||
},
|
||||
});
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
// 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 { styled } from "@mui/material/styles";
|
||||
import LinearProgress, {
|
||||
linearProgressClasses,
|
||||
} from "@mui/material/LinearProgress";
|
||||
|
||||
interface IProgressBarWrapper {
|
||||
value: number;
|
||||
ready: boolean;
|
||||
}
|
||||
|
||||
const BorderLinearProgress = styled(LinearProgress)(() => ({
|
||||
height: 10,
|
||||
borderRadius: 5,
|
||||
[`&.${linearProgressClasses.colorPrimary}`]: {
|
||||
backgroundColor: "#f1f1f1",
|
||||
},
|
||||
[`& .${linearProgressClasses.bar}`]: {
|
||||
borderRadius: 5,
|
||||
},
|
||||
}));
|
||||
|
||||
const ProgressBarWrapper = ({ value, ready }: IProgressBarWrapper) => {
|
||||
return (
|
||||
<BorderLinearProgress
|
||||
variant="determinate"
|
||||
value={value}
|
||||
color={ready ? "success" : "primary"}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default ProgressBarWrapper;
|
||||
235
portal-ui/src/screens/Console/Common/TestWrapper/TestWrapper.tsx
Normal file
235
portal-ui/src/screens/Console/Common/TestWrapper/TestWrapper.tsx
Normal file
@@ -0,0 +1,235 @@
|
||||
// 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, useEffect } from "react";
|
||||
import { Theme } from "@mui/material/styles";
|
||||
import createStyles from "@mui/styles/createStyles";
|
||||
import withStyles from "@mui/styles/withStyles";
|
||||
import { CircularProgress, Grid } from "@mui/material";
|
||||
import ArrowForwardIosIcon from "@mui/icons-material/ArrowForwardIos";
|
||||
import { DrivesIcon, VersionIcon } from "../../../../icons";
|
||||
import { ServerInfo, Usage } from "../../Dashboard/types";
|
||||
import { ErrorResponseHandler } from "../../../../common/types";
|
||||
import api from "../../../../common/api";
|
||||
|
||||
interface ITestWrapper {
|
||||
title: any;
|
||||
children: any;
|
||||
classes: any;
|
||||
advancedVisible: boolean;
|
||||
advancedContent?: any;
|
||||
}
|
||||
|
||||
const styles = (theme: Theme) =>
|
||||
createStyles({
|
||||
titleBar: {
|
||||
borderBottom: "#E5E5E5 1px solid",
|
||||
padding: "30px 25px",
|
||||
fontSize: 20,
|
||||
color: "#07193E",
|
||||
fontWeight: "bold",
|
||||
borderRadius: "10px 10px 0px 0px",
|
||||
paddingTop: 0,
|
||||
},
|
||||
divisorContainer: {
|
||||
padding: 25,
|
||||
},
|
||||
serversData: {
|
||||
color: "#07193E",
|
||||
fontSize: 18,
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
"& svg": {
|
||||
marginRight: 10,
|
||||
},
|
||||
},
|
||||
minioVersionContainer: {
|
||||
fontSize: 12,
|
||||
color: "#07193E",
|
||||
justifyContent: "center",
|
||||
alignSelf: "center",
|
||||
alignItems: "center",
|
||||
display: "flex",
|
||||
},
|
||||
versionIcon: {
|
||||
color: "#07193E",
|
||||
marginRight: 20,
|
||||
},
|
||||
loaderAlign: {
|
||||
textAlign: "center",
|
||||
},
|
||||
advancedContainer: {
|
||||
justifyContent: "flex-end",
|
||||
display: "flex",
|
||||
},
|
||||
optionsContainer: {
|
||||
padding: 0,
|
||||
marginBottom: 25,
|
||||
},
|
||||
advancedConfiguration: {
|
||||
color: "#2781B0",
|
||||
fontSize: 10,
|
||||
textDecoration: "underline",
|
||||
border: "none",
|
||||
backgroundColor: "transparent",
|
||||
cursor: "pointer",
|
||||
alignItems: "center",
|
||||
display: "flex",
|
||||
|
||||
"&:hover": {
|
||||
color: "#07193E",
|
||||
},
|
||||
|
||||
"& svg": {
|
||||
width: 10,
|
||||
alignSelf: "center",
|
||||
marginLeft: 5,
|
||||
},
|
||||
},
|
||||
advancedOpen: {
|
||||
transform: "rotateZ(-90deg) translateX(-4px) translateY(2px)",
|
||||
},
|
||||
advancedClosed: {
|
||||
transform: "rotateZ(90deg)",
|
||||
},
|
||||
advancedContent: {
|
||||
backgroundColor: "#F5F7F9",
|
||||
maxHeight: 0,
|
||||
transitionDuration: "0.3s",
|
||||
overflow: "hidden",
|
||||
padding: "0 15px",
|
||||
"&.open": {
|
||||
maxHeight: 400,
|
||||
padding: 15,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const TestWrapper = ({
|
||||
title,
|
||||
children,
|
||||
classes,
|
||||
advancedVisible,
|
||||
advancedContent,
|
||||
}: ITestWrapper) => {
|
||||
const [version, setVersion] = useState<string>("N/A");
|
||||
const [totalNodes, setTotalNodes] = useState<number>(0);
|
||||
const [totalDrives, setTotalDrives] = useState<number>(0);
|
||||
const [loading, setLoading] = useState<boolean>(true);
|
||||
const [advancedOpen, setAdvancedOpen] = useState<boolean>(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (loading) {
|
||||
api
|
||||
.invoke("GET", `/api/v1/admin/info?defaultOnly=true`)
|
||||
.then((res: Usage) => {
|
||||
const totalServers = res.servers?.length;
|
||||
setTotalNodes(totalServers);
|
||||
|
||||
if (res.servers.length > 0) {
|
||||
setVersion(res.servers[0].version);
|
||||
|
||||
const totalServers = res.servers.reduce(
|
||||
(prevTotal: number, currentElement: ServerInfo) => {
|
||||
return prevTotal + currentElement.drives.length;
|
||||
},
|
||||
0
|
||||
);
|
||||
setTotalDrives(totalServers);
|
||||
}
|
||||
|
||||
setLoading(false);
|
||||
})
|
||||
.catch((err: ErrorResponseHandler) => {
|
||||
setLoading(false);
|
||||
});
|
||||
}
|
||||
}, [loading]);
|
||||
|
||||
return (
|
||||
<Grid item xs={12}>
|
||||
<Grid item xs={12} className={classes.titleBar}>
|
||||
{title}
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<Grid item xs={12} className={classes.optionsContainer}>
|
||||
<Grid container className={classes.divisorContainer}>
|
||||
{!loading ? (
|
||||
<Fragment>
|
||||
<Grid item xs={12} md={4} className={classes.serversData}>
|
||||
<DrivesIcon /> <strong>{totalNodes}</strong>
|
||||
nodes,
|
||||
<strong>{totalDrives}</strong> drives
|
||||
</Grid>
|
||||
<Grid
|
||||
item
|
||||
xs={12}
|
||||
md={4}
|
||||
className={classes.minioVersionContainer}
|
||||
>
|
||||
<span className={classes.versionIcon}>
|
||||
<VersionIcon />
|
||||
</span>{" "}
|
||||
MinIO VERSION <strong>{version}</strong>
|
||||
</Grid>
|
||||
<Grid item xs={12} md={4} className={classes.advancedContainer}>
|
||||
{advancedVisible && (
|
||||
<button
|
||||
onClick={() => {
|
||||
setAdvancedOpen(!advancedOpen);
|
||||
}}
|
||||
className={classes.advancedConfiguration}
|
||||
>
|
||||
Advanced configurations{" "}
|
||||
<span
|
||||
className={
|
||||
advancedOpen
|
||||
? classes.advancedOpen
|
||||
: classes.advancedClosed
|
||||
}
|
||||
>
|
||||
<ArrowForwardIosIcon />
|
||||
</span>
|
||||
</button>
|
||||
)}
|
||||
</Grid>
|
||||
</Fragment>
|
||||
) : (
|
||||
<Fragment>
|
||||
<Grid item xs={12} className={classes.loaderAlign}>
|
||||
<CircularProgress size={25} />
|
||||
</Grid>
|
||||
</Fragment>
|
||||
)}
|
||||
</Grid>
|
||||
{advancedContent && (
|
||||
<Grid
|
||||
xs={12}
|
||||
className={`${classes.advancedContent} ${
|
||||
advancedOpen ? "open" : ""
|
||||
}`}
|
||||
>
|
||||
{advancedContent}
|
||||
</Grid>
|
||||
)}
|
||||
</Grid>
|
||||
{children}
|
||||
</Grid>
|
||||
</Grid>
|
||||
);
|
||||
};
|
||||
|
||||
export default withStyles(styles)(TestWrapper);
|
||||
@@ -43,8 +43,8 @@ import RefreshIcon from "../../../../icons/RefreshIcon";
|
||||
import SearchIcon from "../../../../icons/SearchIcon";
|
||||
import PageHeader from "../../Common/PageHeader/PageHeader";
|
||||
import HelpBox from "../../../../common/HelpBox";
|
||||
import BoxIconButton from "../../Common/BoxIconButton";
|
||||
import AButton from "../../Common/AButton";
|
||||
import BoxIconButton from "../../Common/BoxIconButton/BoxIconButton";
|
||||
import AButton from "../../Common/AButton/AButton";
|
||||
|
||||
interface IListTiersConfig {
|
||||
classes: any;
|
||||
|
||||
@@ -66,6 +66,7 @@ import Tools from "./Tools/Tools";
|
||||
import ErrorLogs from "./Logs/ErrorLogs/ErrorLogs";
|
||||
import LogsSearchMain from "./Logs/LogSearch/LogsSearchMain";
|
||||
import GroupsDetails from "./Groups/GroupsDetails";
|
||||
import Speedtest from "./Speedtest/Speedtest";
|
||||
|
||||
const drawerWidth = 245;
|
||||
|
||||
@@ -250,6 +251,10 @@ const Console = ({
|
||||
component: Watch,
|
||||
path: "/tools/watch",
|
||||
},
|
||||
{
|
||||
component: Speedtest,
|
||||
path: "/tools/speedtest",
|
||||
},
|
||||
{
|
||||
component: Users,
|
||||
path: "/users/:userName+",
|
||||
|
||||
@@ -43,7 +43,7 @@ import FormatDrives from "./FormatDrives";
|
||||
import FormatErrorsResult from "./FormatErrorsResult";
|
||||
import RefreshIcon from "../../../icons/RefreshIcon";
|
||||
import SearchIcon from "../../../icons/SearchIcon";
|
||||
import BoxIconButton from "../Common/BoxIconButton";
|
||||
import BoxIconButton from "../Common/BoxIconButton/BoxIconButton";
|
||||
|
||||
interface IDirectCSIMain {
|
||||
classes: any;
|
||||
|
||||
@@ -42,7 +42,7 @@ import PageHeader from "../Common/PageHeader/PageHeader";
|
||||
import SearchIcon from "../../../icons/SearchIcon";
|
||||
import HelpBox from "../../../common/HelpBox";
|
||||
import history from "../../../history";
|
||||
import AButton from "../Common/AButton";
|
||||
import AButton from "../Common/AButton/AButton";
|
||||
|
||||
interface IGroupsProps {
|
||||
classes: any;
|
||||
|
||||
@@ -28,7 +28,7 @@ import SetPolicy from "../Policies/SetPolicy";
|
||||
import AddGroupMember from "./AddGroupMember";
|
||||
import { ErrorResponseHandler } from "../../../common/types";
|
||||
import DeleteGroup from "./DeleteGroup";
|
||||
import PanelTitle from "../Common/PanelTitle";
|
||||
import PanelTitle from "../Common/PanelTitle/PanelTitle";
|
||||
|
||||
const styles = (theme: Theme) =>
|
||||
createStyles({
|
||||
|
||||
@@ -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, { useEffect, useState } from "react";
|
||||
import React, { Fragment, useState, useEffect } from "react";
|
||||
import {
|
||||
ICloseEvent,
|
||||
IMessageEvent,
|
||||
@@ -46,6 +46,7 @@ import PageHeader from "../Common/PageHeader/PageHeader";
|
||||
import { setServerDiagStat, setSnackBarMessage } from "../../../actions";
|
||||
import CircularProgress from "@mui/material/CircularProgress";
|
||||
import BackLink from "../../../common/BackLink";
|
||||
import TestWrapper from "../Common/TestWrapper/TestWrapper";
|
||||
|
||||
const styles = (theme: Theme) =>
|
||||
createStyles({
|
||||
@@ -73,6 +74,26 @@ const styles = (theme: Theme) =>
|
||||
padding: 40,
|
||||
backgroundColor: "#fff",
|
||||
},
|
||||
localMessage: {
|
||||
fontSize: 24,
|
||||
color: "#07193E",
|
||||
fontWeight: "bold",
|
||||
textAlign: "center",
|
||||
marginBottom: 10,
|
||||
},
|
||||
startDiagnostic: {
|
||||
textAlign: "center",
|
||||
marginBottom: 25,
|
||||
},
|
||||
progressResult: {
|
||||
textAlign: "center",
|
||||
marginBottom: 25,
|
||||
},
|
||||
diagNew: {
|
||||
textAlign: "right",
|
||||
margin: 25,
|
||||
marginBottom: 0,
|
||||
},
|
||||
...actionsTray,
|
||||
...containerForHeader(theme.spacing(4)),
|
||||
});
|
||||
@@ -115,7 +136,27 @@ const HealthInfo = ({
|
||||
serverDiagnosticStatus,
|
||||
}: IHealthInfo) => {
|
||||
const [startDiagnostic, setStartDiagnostic] = useState(false);
|
||||
const [diagStarted, setDiagStarted] = useState<boolean>(false);
|
||||
const [downloadDisabled, setDownloadDisabled] = useState(true);
|
||||
const [localMessage, setMessage] = useState<string>("");
|
||||
const [title, setTitle] = useState<string>("Start new Diagnostic");
|
||||
|
||||
useEffect(() => {
|
||||
if (serverDiagnosticStatus === DiagStatInProgress) {
|
||||
setTitle("Diagnostic in progress...");
|
||||
return;
|
||||
}
|
||||
|
||||
if (serverDiagnosticStatus === DiagStatSuccess && diagStarted) {
|
||||
setTitle("Diagnostic complete");
|
||||
return;
|
||||
}
|
||||
|
||||
if (serverDiagnosticStatus === DiagStatError) {
|
||||
setTitle("Error");
|
||||
return;
|
||||
}
|
||||
}, [serverDiagnosticStatus, startDiagnostic, diagStarted]);
|
||||
|
||||
useEffect(() => {
|
||||
if (
|
||||
@@ -155,7 +196,8 @@ const HealthInfo = ({
|
||||
interval = setInterval(() => {
|
||||
c.send("ok");
|
||||
}, 10 * 1000);
|
||||
setSnackBarMessage(
|
||||
setDiagStarted(true);
|
||||
setMessage(
|
||||
"Diagnostic started. Please do not refresh page during diagnosis."
|
||||
);
|
||||
setServerDiagStat(DiagStatInProgress);
|
||||
@@ -163,6 +205,7 @@ const HealthInfo = ({
|
||||
c.onmessage = (message: IMessageEvent) => {
|
||||
let m: HealthInfoMessage = JSON.parse(message.data.toString());
|
||||
m.timestamp = new Date(m.timestamp.toString());
|
||||
|
||||
healthInfoMessageReceived(m);
|
||||
};
|
||||
c.onerror = (error: Error) => {
|
||||
@@ -180,13 +223,12 @@ const HealthInfo = ({
|
||||
) {
|
||||
// handle close with error
|
||||
console.log("connection closed by server with code:", event.code);
|
||||
setSnackBarMessage(
|
||||
"An error occurred while getting Diagnostic file."
|
||||
);
|
||||
setMessage("An error occurred while getting Diagnostic file.");
|
||||
setServerDiagStat(DiagStatError);
|
||||
} else {
|
||||
console.log("connection closed by server");
|
||||
setSnackBarMessage("Diagnostic file is ready to be downloaded.");
|
||||
|
||||
setMessage("Diagnostic file is ready to be downloaded.");
|
||||
setServerDiagStat(DiagStatSuccess);
|
||||
}
|
||||
};
|
||||
@@ -204,7 +246,7 @@ const HealthInfo = ({
|
||||
]);
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<Fragment>
|
||||
<PageHeader label="Diagnostic" />
|
||||
|
||||
<Grid container className={classes.container}>
|
||||
@@ -212,44 +254,76 @@ const HealthInfo = ({
|
||||
<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
|
||||
type="submit"
|
||||
variant="contained"
|
||||
color="primary"
|
||||
disabled={startDiagnostic}
|
||||
onClick={() => setStartDiagnostic(true)}
|
||||
>
|
||||
Start Diagnostic
|
||||
</Button>
|
||||
</Grid>
|
||||
<Grid key="start-download" item>
|
||||
{serverDiagnosticStatus === DiagStatInProgress ? (
|
||||
<div className={classes.loading}>
|
||||
<CircularProgress size={25} />
|
||||
</div>
|
||||
) : (
|
||||
<Button
|
||||
type="submit"
|
||||
variant="contained"
|
||||
color="primary"
|
||||
onClick={() => {
|
||||
download(
|
||||
"diagnostic.json",
|
||||
JSON.stringify(message, null, 2)
|
||||
);
|
||||
}}
|
||||
disabled={downloadDisabled}
|
||||
<TestWrapper title={title} advancedVisible={false}>
|
||||
<Grid container className={classes.buttons}>
|
||||
{!diagStarted && (
|
||||
<Grid
|
||||
key="start-diag"
|
||||
item
|
||||
xs={12}
|
||||
className={classes.startDiagnostic}
|
||||
>
|
||||
Download
|
||||
</Button>
|
||||
<Button
|
||||
type="submit"
|
||||
variant="contained"
|
||||
color="primary"
|
||||
disabled={startDiagnostic}
|
||||
onClick={() => setStartDiagnostic(true)}
|
||||
>
|
||||
Start Diagnostic
|
||||
</Button>
|
||||
</Grid>
|
||||
)}
|
||||
{diagStarted && (
|
||||
<Grid
|
||||
key="start-download"
|
||||
item
|
||||
xs={12}
|
||||
className={classes.progressResult}
|
||||
>
|
||||
<div className={classes.localMessage}>{localMessage}</div>
|
||||
{serverDiagnosticStatus === DiagStatInProgress ? (
|
||||
<div className={classes.loading}>
|
||||
<CircularProgress size={25} />
|
||||
</div>
|
||||
) : (
|
||||
<Fragment>
|
||||
{serverDiagnosticStatus !== DiagStatError && (
|
||||
<Button
|
||||
type="submit"
|
||||
variant="contained"
|
||||
color="primary"
|
||||
onClick={() => {
|
||||
download(
|
||||
"diagnostic.json",
|
||||
JSON.stringify(message, null, 2)
|
||||
);
|
||||
}}
|
||||
disabled={downloadDisabled}
|
||||
>
|
||||
Download
|
||||
</Button>
|
||||
)}
|
||||
<Grid item xs={12} className={classes.diagNew}>
|
||||
<Button
|
||||
type="submit"
|
||||
variant="contained"
|
||||
color="primary"
|
||||
disabled={startDiagnostic}
|
||||
onClick={() => setStartDiagnostic(true)}
|
||||
>
|
||||
Start new Diagnostic
|
||||
</Button>
|
||||
</Grid>
|
||||
</Fragment>
|
||||
)}
|
||||
</Grid>
|
||||
)}
|
||||
</Grid>
|
||||
</Grid>
|
||||
</TestWrapper>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</React.Fragment>
|
||||
</Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@ import React from "react";
|
||||
import { connect } from "react-redux";
|
||||
import { NavLink } from "react-router-dom";
|
||||
import { Divider, Drawer, IconButton, Tooltip } from "@mui/material";
|
||||
import { ChevronLeft } from "@mui/icons-material";
|
||||
import withStyles from "@mui/styles/withStyles";
|
||||
import { Theme } from "@mui/material/styles";
|
||||
import createStyles from "@mui/styles/createStyles";
|
||||
@@ -25,6 +26,7 @@ import ListItem from "@mui/material/ListItem";
|
||||
import ListItemIcon from "@mui/material/ListItemIcon";
|
||||
import ListItemText from "@mui/material/ListItemText";
|
||||
import List from "@mui/material/List";
|
||||
import clsx from "clsx";
|
||||
import { AppState } from "../../../store";
|
||||
import { setMenuOpen, userLoggedIn } from "../../../actions";
|
||||
import { menuGroups } from "./utils";
|
||||
@@ -32,6 +34,7 @@ import { IMenuItem } from "./types";
|
||||
import {
|
||||
BucketsIcon,
|
||||
DashboardIcon,
|
||||
DiagnosticsIcon,
|
||||
GroupsIcon,
|
||||
IAMPoliciesIcon,
|
||||
LambdaIcon,
|
||||
@@ -56,9 +59,6 @@ import StorageIcon from "../../../icons/StorageIcon";
|
||||
import TenantsOutlinedIcon from "../../../icons/TenantsOutlineIcon";
|
||||
import MenuIcon from "@mui/icons-material/Menu";
|
||||
|
||||
import clsx from "clsx";
|
||||
import { ChevronLeft } from "@mui/icons-material";
|
||||
|
||||
const drawerWidth = 245;
|
||||
|
||||
const styles = (theme: Theme) =>
|
||||
@@ -396,7 +396,14 @@ const Menu = ({
|
||||
icon: <HealIcon />,
|
||||
fsHidden: distributedSetup,
|
||||
},
|
||||
|
||||
{
|
||||
group: "Tools",
|
||||
type: "item",
|
||||
component: NavLink,
|
||||
to: "/health-info",
|
||||
name: "Diagnostic",
|
||||
icon: <DiagnosticsIcon />,
|
||||
},
|
||||
{
|
||||
group: "Operator",
|
||||
type: "item",
|
||||
|
||||
@@ -46,8 +46,8 @@ import RefreshIcon from "../../../icons/RefreshIcon";
|
||||
import SearchIcon from "../../../icons/SearchIcon";
|
||||
import history from "../../../history";
|
||||
import HelpBox from "../../../common/HelpBox";
|
||||
import BoxIconButton from "../Common/BoxIconButton";
|
||||
import AButton from "../Common/AButton";
|
||||
import BoxIconButton from "../Common/BoxIconButton/BoxIconButton";
|
||||
import AButton from "../Common/AButton/AButton";
|
||||
|
||||
interface IListNotificationEndpoints {
|
||||
classes: any;
|
||||
|
||||
@@ -47,7 +47,7 @@ import IAMPoliciesIcon from "../../../icons/IAMPoliciesIcon";
|
||||
import RefreshIcon from "../../../icons/RefreshIcon";
|
||||
import SearchIcon from "../../../icons/SearchIcon";
|
||||
import TrashIcon from "../../../icons/TrashIcon";
|
||||
import BoxIconButton from "../Common/BoxIconButton";
|
||||
import BoxIconButton from "../Common/BoxIconButton/BoxIconButton";
|
||||
|
||||
interface IPolicyDetailsProps {
|
||||
classes: any;
|
||||
|
||||
519
portal-ui/src/screens/Console/Speedtest/STResults.tsx
Normal file
519
portal-ui/src/screens/Console/Speedtest/STResults.tsx
Normal file
@@ -0,0 +1,519 @@
|
||||
// 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 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";
|
||||
import { IndvServerMetric, SpeedTestResponse, STServer } from "./types";
|
||||
import { calculateBytes, prettyNumber } from "../../../common/utils";
|
||||
import {
|
||||
ComputerLineIcon,
|
||||
DownloadIcon,
|
||||
DownloadStatIcon,
|
||||
JSONIcon,
|
||||
StorageIcon,
|
||||
UploadStatIcon,
|
||||
VersionIcon,
|
||||
} from "../../../icons";
|
||||
import BoxIconButton from "../Common/BoxIconButton/BoxIconButton";
|
||||
import CodeMirrorWrapper from "../Common/FormComponents/CodeMirrorWrapper/CodeMirrorWrapper";
|
||||
import { Area, AreaChart, CartesianGrid, ResponsiveContainer } from "recharts";
|
||||
import { cleanMetrics } from "./utils";
|
||||
|
||||
interface ISTResults {
|
||||
classes: any;
|
||||
results: SpeedTestResponse[];
|
||||
}
|
||||
|
||||
const styles = (theme: Theme) =>
|
||||
createStyles({
|
||||
statContainer: {
|
||||
padding: "18px 0 18px 25px",
|
||||
},
|
||||
statBlock: {
|
||||
border: "#EEF1F4 1px solid",
|
||||
borderRadius: 4,
|
||||
},
|
||||
testedAmount: {
|
||||
display: "flex",
|
||||
justifyContent: "space-between",
|
||||
color: "#07193E",
|
||||
padding: "12px 18px",
|
||||
border: "#EEF1F4 1px solid",
|
||||
borderLeft: 0,
|
||||
borderRight: 0,
|
||||
},
|
||||
serverLength: {
|
||||
color: "#696969",
|
||||
},
|
||||
serverDescrContainer: {
|
||||
display: "flex",
|
||||
margin: "15px 5px 0",
|
||||
position: "relative",
|
||||
},
|
||||
serverDescrIcon: {
|
||||
marginRight: 10,
|
||||
"& svg": {
|
||||
width: 16,
|
||||
},
|
||||
},
|
||||
serverDescriptor: {
|
||||
color: "#696969",
|
||||
whiteSpace: "nowrap",
|
||||
maxWidth: "100%",
|
||||
overflow: "hidden",
|
||||
textOverflow: "ellipsis",
|
||||
},
|
||||
serversResume: {
|
||||
marginBottom: 15,
|
||||
},
|
||||
objectGeneralTitle: {
|
||||
fontWeight: "bold",
|
||||
color: "#000",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
"& svg": {
|
||||
width: 14,
|
||||
},
|
||||
},
|
||||
generalContainer: {
|
||||
padding: 20,
|
||||
},
|
||||
generalUnit: {
|
||||
color: "#000",
|
||||
marginTop: 6,
|
||||
fontSize: 12,
|
||||
fontWeight: "bold",
|
||||
},
|
||||
testUnitRes: {
|
||||
fontSize: 120,
|
||||
color: "#081C42",
|
||||
fontWeight: "bold",
|
||||
},
|
||||
shareResults: {
|
||||
padding: "18px 25px",
|
||||
color: "#07193E",
|
||||
fontSize: 14,
|
||||
},
|
||||
metricValContainer: {
|
||||
lineHeight: 1,
|
||||
},
|
||||
actionButtons: {
|
||||
textAlign: "right",
|
||||
},
|
||||
descriptorLabel: {
|
||||
fontWeight: "bold",
|
||||
fontSize: 14,
|
||||
},
|
||||
resultsContainer: {
|
||||
backgroundColor: "#FBFAFA",
|
||||
borderTop: "#F1F1F1 1px solid",
|
||||
marginTop: 30,
|
||||
padding: 25,
|
||||
},
|
||||
resultsIcon: {
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
"& svg": {
|
||||
fill: "#07193E",
|
||||
},
|
||||
},
|
||||
detailedItem: {
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "flex-start",
|
||||
},
|
||||
detailedVersion: {
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "flex-end",
|
||||
},
|
||||
serversTable: {
|
||||
width: "100%",
|
||||
marginTop: 15,
|
||||
"& thead > tr > th": {
|
||||
textAlign: "left",
|
||||
padding: 15,
|
||||
fontSize: 14,
|
||||
fontWeight: "bold",
|
||||
},
|
||||
"& tbody > tr": {
|
||||
"&:last-of-type": {
|
||||
"& > td": {
|
||||
borderBottom: "#E2E2E2 1px solid",
|
||||
},
|
||||
},
|
||||
"& > td": {
|
||||
borderTop: "#E2E2E2 1px solid",
|
||||
padding: 15,
|
||||
fontSize: 14,
|
||||
"&:first-of-type": {
|
||||
borderLeft: "#E2E2E2 1px solid",
|
||||
},
|
||||
"&:last-of-type": {
|
||||
borderRight: "#E2E2E2 1px solid",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
serverIcon: {
|
||||
width: 55,
|
||||
},
|
||||
serverUnit: {
|
||||
width: 70,
|
||||
},
|
||||
serverValue: {
|
||||
width: 70,
|
||||
},
|
||||
serverHost: {
|
||||
maxWidth: 540,
|
||||
overflow: "hidden",
|
||||
textOverflow: "ellipsis",
|
||||
whiteSpace: "nowrap",
|
||||
},
|
||||
tableOverflow: {
|
||||
overflowX: "auto",
|
||||
paddingBottom: 15,
|
||||
},
|
||||
objectGeneral: {
|
||||
marginTop: 15,
|
||||
},
|
||||
});
|
||||
|
||||
const STResults = ({ classes, results }: ISTResults) => {
|
||||
const [jsonView, setJsonView] = useState<boolean>(false);
|
||||
|
||||
const finalRes = results[results.length - 1];
|
||||
|
||||
const getServers: STServer[] = get(finalRes, "GETStats.servers", []) || [];
|
||||
const putServers: STServer[] = get(finalRes, "PUTStats.servers", []) || [];
|
||||
|
||||
const getThroughput = get(finalRes, "GETStats.throughputPerSec", 0);
|
||||
const getObjects = get(finalRes, "GETStats.objectsPerSec", 0);
|
||||
|
||||
const putThroughput = get(finalRes, "PUTStats.throughputPerSec", 0);
|
||||
const putObjects = get(finalRes, "PUTStats.objectsPerSec", 0);
|
||||
|
||||
const ObjectGeneral = ({
|
||||
title,
|
||||
throughput,
|
||||
objects,
|
||||
}: {
|
||||
title: any;
|
||||
throughput: string;
|
||||
objects: number;
|
||||
}) => {
|
||||
const avg = calculateBytes(throughput);
|
||||
|
||||
return (
|
||||
<Grid container>
|
||||
<Grid item xs={12} className={classes.objectGeneralTitle}>
|
||||
{title}
|
||||
</Grid>
|
||||
<Grid item xs={12} md={6} className={classes.metricValContainer}>
|
||||
<span className={classes.testUnitRes}>{avg.total}</span>
|
||||
<span className={classes.generalUnit}>{avg.unit}/S</span>
|
||||
</Grid>
|
||||
</Grid>
|
||||
);
|
||||
};
|
||||
|
||||
let statJoin: IndvServerMetric[] = [];
|
||||
|
||||
getServers.forEach((item) => {
|
||||
const hostName = item.endpoint;
|
||||
const putMetric = putServers.find((item) => item.endpoint === hostName);
|
||||
|
||||
let itemJoin: IndvServerMetric = {
|
||||
getUnit: "-",
|
||||
getValue: "N/A",
|
||||
host: item.endpoint,
|
||||
putUnit: "-",
|
||||
putValue: "N/A",
|
||||
};
|
||||
|
||||
if (item.err && item.err !== "") {
|
||||
itemJoin.getError = item.err;
|
||||
itemJoin.getUnit = "-";
|
||||
itemJoin.getValue = "N/A";
|
||||
} else {
|
||||
const niceGet = calculateBytes(item.throughputPerSec.toString());
|
||||
|
||||
itemJoin.getUnit = niceGet.unit;
|
||||
itemJoin.getValue = niceGet.total.toString();
|
||||
}
|
||||
|
||||
if (putMetric) {
|
||||
if (putMetric.err && putMetric.err !== "") {
|
||||
itemJoin.putError = putMetric.err;
|
||||
itemJoin.putUnit = "-";
|
||||
itemJoin.putValue = "N/A";
|
||||
} else {
|
||||
const nicePut = calculateBytes(putMetric.throughputPerSec.toString());
|
||||
|
||||
itemJoin.putUnit = nicePut.unit;
|
||||
itemJoin.putValue = nicePut.total.toString();
|
||||
}
|
||||
}
|
||||
|
||||
statJoin.push(itemJoin);
|
||||
});
|
||||
|
||||
const downloadResults = () => {
|
||||
const date = new Date();
|
||||
let element = document.createElement("a");
|
||||
element.setAttribute(
|
||||
"href",
|
||||
"data:text/plain;charset=utf-8," + JSON.stringify(finalRes)
|
||||
);
|
||||
element.setAttribute(
|
||||
"download",
|
||||
`speedtest_results-${date.toISOString()}.log`
|
||||
);
|
||||
|
||||
element.style.display = "none";
|
||||
document.body.appendChild(element);
|
||||
|
||||
element.click();
|
||||
|
||||
document.body.removeChild(element);
|
||||
};
|
||||
|
||||
const toggleJSONView = () => {
|
||||
setJsonView(!jsonView);
|
||||
};
|
||||
|
||||
const finalResJSON = finalRes ? JSON.stringify(finalRes, null, 4) : "";
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<Grid container className={classes.objectGeneral}>
|
||||
<Grid item xs={12} md={6} lg={4}>
|
||||
<ObjectGeneral
|
||||
title={
|
||||
<Fragment>
|
||||
<DownloadStatIcon />
|
||||
GET
|
||||
</Fragment>
|
||||
}
|
||||
throughput={getThroughput}
|
||||
objects={getObjects}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12} md={6} lg={4}>
|
||||
<ObjectGeneral
|
||||
title={
|
||||
<Fragment>
|
||||
<UploadStatIcon />
|
||||
PUT
|
||||
</Fragment>
|
||||
}
|
||||
throughput={putThroughput}
|
||||
objects={putObjects}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12} md={12} lg={4}>
|
||||
<ResponsiveContainer width="99%">
|
||||
<AreaChart data={cleanMetrics(results)}>
|
||||
<defs>
|
||||
<linearGradient id="colorPut" x1="0" y1="0" x2="0" y2="1">
|
||||
<stop offset="0%" stopColor="#2781B0" stopOpacity={0.9} />
|
||||
<stop offset="95%" stopColor="#fff" stopOpacity={0} />
|
||||
</linearGradient>
|
||||
<linearGradient id="colorGet" x1="0" y1="0" x2="0" y2="1">
|
||||
<stop offset="0%" stopColor="#4CCB92" stopOpacity={0.9} />
|
||||
<stop offset="95%" stopColor="#fff" stopOpacity={0} />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
|
||||
<CartesianGrid
|
||||
strokeDasharray={"0 0"}
|
||||
strokeWidth={1}
|
||||
strokeOpacity={0.5}
|
||||
stroke={"#F1F1F1"}
|
||||
vertical={false}
|
||||
/>
|
||||
|
||||
<Area
|
||||
type="monotone"
|
||||
dataKey={"get"}
|
||||
stroke={"#4CCB92"}
|
||||
fill={"url(#colorGet)"}
|
||||
fillOpacity={0.3}
|
||||
strokeWidth={2}
|
||||
dot={false}
|
||||
/>
|
||||
<Area
|
||||
type="monotone"
|
||||
dataKey={"put"}
|
||||
stroke={"#2781B0"}
|
||||
fill={"url(#colorPut)"}
|
||||
fillOpacity={0.3}
|
||||
strokeWidth={2}
|
||||
dot={false}
|
||||
/>
|
||||
</AreaChart>
|
||||
</ResponsiveContainer>
|
||||
</Grid>
|
||||
</Grid>
|
||||
<br />
|
||||
<Grid container>
|
||||
<Grid item xs={12} md={6} className={classes.descriptorLabel}>
|
||||
{jsonView ? "JSON Results:" : "Detailed Results:"}
|
||||
</Grid>
|
||||
<Grid item xs={12} md={6} className={classes.actionButtons}>
|
||||
<BoxIconButton
|
||||
aria-label="Download"
|
||||
onClick={downloadResults}
|
||||
size="large"
|
||||
>
|
||||
<DownloadIcon />
|
||||
</BoxIconButton>
|
||||
|
||||
<BoxIconButton
|
||||
aria-label="Download"
|
||||
onClick={toggleJSONView}
|
||||
size="large"
|
||||
>
|
||||
<JSONIcon />
|
||||
</BoxIconButton>
|
||||
</Grid>
|
||||
</Grid>
|
||||
<Grid container className={classes.resultsContainer}>
|
||||
{jsonView ? (
|
||||
<Fragment>
|
||||
<CodeMirrorWrapper
|
||||
value={finalResJSON}
|
||||
readOnly
|
||||
onBeforeChange={() => {}}
|
||||
/>
|
||||
</Fragment>
|
||||
) : (
|
||||
<Fragment>
|
||||
<Grid
|
||||
item
|
||||
xs={12}
|
||||
sm={12}
|
||||
md={1}
|
||||
lg={1}
|
||||
className={classes.resultsIcon}
|
||||
alignItems={"flex-end"}
|
||||
>
|
||||
<ComputerLineIcon width={45} />
|
||||
</Grid>
|
||||
<Grid
|
||||
item
|
||||
xs={12}
|
||||
sm={6}
|
||||
md={3}
|
||||
lg={2}
|
||||
className={classes.detailedItem}
|
||||
>
|
||||
Nodes: <strong>{finalRes.servers}</strong>
|
||||
</Grid>
|
||||
<Grid
|
||||
item
|
||||
xs={12}
|
||||
sm={6}
|
||||
md={3}
|
||||
lg={2}
|
||||
className={classes.detailedItem}
|
||||
>
|
||||
Drives: <strong>{finalRes.disks}</strong>
|
||||
</Grid>
|
||||
<Grid
|
||||
item
|
||||
xs={12}
|
||||
sm={6}
|
||||
md={3}
|
||||
lg={2}
|
||||
className={classes.detailedItem}
|
||||
>
|
||||
Concurrent: <strong>{finalRes.concurrent}</strong>
|
||||
</Grid>
|
||||
<Grid
|
||||
item
|
||||
xs={12}
|
||||
sm={12}
|
||||
md={12}
|
||||
lg={5}
|
||||
className={classes.detailedVersion}
|
||||
>
|
||||
<span className={classes.versionIcon}>
|
||||
<VersionIcon />
|
||||
</span>{" "}
|
||||
MinIO VERSION <strong>{finalRes.version}</strong>
|
||||
</Grid>
|
||||
<Grid item xs={12} className={classes.tableOverflow}>
|
||||
<table
|
||||
className={classes.serversTable}
|
||||
cellSpacing={0}
|
||||
cellPadding={0}
|
||||
>
|
||||
<thead>
|
||||
<tr>
|
||||
<th colSpan={2}>Servers</th>
|
||||
<th colSpan={2}>GET</th>
|
||||
<th colSpan={2}>PUT</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{statJoin.map((stats, index) => (
|
||||
<tr key={`storage-${index.toString()}`}>
|
||||
<td className={classes.serverIcon}>
|
||||
<StorageIcon />
|
||||
</td>
|
||||
<td className={classes.serverHost}>{stats.host}</td>
|
||||
{stats.getError && stats.getError !== "" ? (
|
||||
<td colSpan={2}>{stats.getError}</td>
|
||||
) : (
|
||||
<Fragment>
|
||||
<td className={classes.serverValue}>
|
||||
{prettyNumber(parseFloat(stats.getValue))}
|
||||
</td>
|
||||
<td className={classes.serverUnit}>
|
||||
{stats.getUnit}/s.
|
||||
</td>
|
||||
</Fragment>
|
||||
)}
|
||||
{stats.putError && stats.putError !== "" ? (
|
||||
<td colSpan={2}>{stats.putError}</td>
|
||||
) : (
|
||||
<Fragment>
|
||||
<td className={classes.serverValue}>
|
||||
{prettyNumber(parseFloat(stats.putValue))}
|
||||
</td>
|
||||
<td className={classes.serverUnit}>
|
||||
{stats.putUnit}/s.
|
||||
</td>
|
||||
</Fragment>
|
||||
)}
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</Grid>
|
||||
</Fragment>
|
||||
)}
|
||||
</Grid>
|
||||
</Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
export default withStyles(styles)(STResults);
|
||||
373
portal-ui/src/screens/Console/Speedtest/Speedtest.tsx
Normal file
373
portal-ui/src/screens/Console/Speedtest/Speedtest.tsx
Normal file
@@ -0,0 +1,373 @@
|
||||
// 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, useState } from "react";
|
||||
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 { Button, CircularProgress, Grid } from "@mui/material";
|
||||
import ArrowForwardIosIcon from "@mui/icons-material/ArrowForwardIos";
|
||||
import moment from "moment/moment";
|
||||
import PageHeader from "../Common/PageHeader/PageHeader";
|
||||
import {
|
||||
actionsTray,
|
||||
containerForHeader,
|
||||
searchField,
|
||||
} from "../Common/FormComponents/common/styleLibrary";
|
||||
import { wsProtocol } from "../../../utils/wsUtils";
|
||||
import { SpeedTestResponse } from "./types";
|
||||
import STResults from "./STResults";
|
||||
import InputBoxWrapper from "../Common/FormComponents/InputBoxWrapper/InputBoxWrapper";
|
||||
import BackLink from "../../../common/BackLink";
|
||||
import ProgressBarWrapper from "../Common/ProgressBarWrapper/ProgressBarWrapper";
|
||||
import InputUnitMenu from "../Common/FormComponents/InputUnitMenu/InputUnitMenu";
|
||||
|
||||
interface ISpeedtest {
|
||||
classes: any;
|
||||
}
|
||||
|
||||
const styles = (theme: Theme) =>
|
||||
createStyles({
|
||||
boxy: {
|
||||
border: "#E5E5E5 1px solid",
|
||||
borderRadius: 2,
|
||||
padding: 40,
|
||||
backgroundColor: "#fff",
|
||||
},
|
||||
advancedConfiguration: {
|
||||
color: "#2781B0",
|
||||
fontSize: 10,
|
||||
textDecoration: "underline",
|
||||
border: "none",
|
||||
backgroundColor: "transparent",
|
||||
cursor: "pointer",
|
||||
alignItems: "center",
|
||||
display: "flex",
|
||||
float: "right",
|
||||
|
||||
"&:hover": {
|
||||
color: "#07193E",
|
||||
},
|
||||
|
||||
"& svg": {
|
||||
width: 10,
|
||||
alignSelf: "center",
|
||||
marginLeft: 5,
|
||||
},
|
||||
},
|
||||
advancedOpen: {
|
||||
transform: "rotateZ(-90deg) translateX(-4px) translateY(2px)",
|
||||
},
|
||||
advancedClosed: {
|
||||
transform: "rotateZ(90deg)",
|
||||
},
|
||||
advancedContent: {
|
||||
backgroundColor: "#FBFAFA",
|
||||
maxHeight: 0,
|
||||
transitionDuration: "0.3s",
|
||||
overflow: "hidden",
|
||||
padding: "0 15px",
|
||||
marginTop: 15,
|
||||
justifyContent: "space-between",
|
||||
"&.open": {
|
||||
maxHeight: 400,
|
||||
},
|
||||
},
|
||||
advancedButton: {
|
||||
flexGrow: 1,
|
||||
alignItems: "flex-end",
|
||||
display: "flex",
|
||||
justifyContent: "flex-end",
|
||||
},
|
||||
progressContainer: {
|
||||
padding: "0 15px",
|
||||
},
|
||||
stepProgressText: {
|
||||
fontSize: 13,
|
||||
marginBottom: 8,
|
||||
},
|
||||
advancedOption: {
|
||||
marginTop: 20,
|
||||
},
|
||||
...actionsTray,
|
||||
...searchField,
|
||||
...containerForHeader(theme.spacing(4)),
|
||||
});
|
||||
|
||||
const Speedtest = ({ classes }: ISpeedtest) => {
|
||||
const [start, setStart] = useState<boolean>(false);
|
||||
|
||||
const [currStatus, setCurrStatus] = useState<SpeedTestResponse[] | null>(
|
||||
null
|
||||
);
|
||||
const [duration, setDuration] = useState<string>("10");
|
||||
const [durationUnit, setDurationUnit] = useState<string>("s");
|
||||
const [size, setSize] = useState<string>("64");
|
||||
const [sizeUnit, setSizeUnit] = useState<string>("MB");
|
||||
const [concurrent, setConcurrent] = useState<string>("");
|
||||
const [topDate, setTopDate] = useState<number>(0);
|
||||
const [currentValue, setCurrentValue] = useState<number>(0);
|
||||
const [totalSeconds, setTotalSeconds] = useState<number>(0);
|
||||
const [speedometerValue, setSpeedometerValue] = useState<number>(0);
|
||||
const [advancedOpen, setAdvancedOpen] = useState<boolean>(false);
|
||||
|
||||
useEffect(() => {
|
||||
// begin watch if bucketName in bucketList and start pressed
|
||||
if (start) {
|
||||
const url = new URL(window.location.toString());
|
||||
const isDev = process.env.NODE_ENV === "development";
|
||||
const port = isDev ? "9090" : url.port;
|
||||
|
||||
const wsProt = wsProtocol(url.protocol);
|
||||
const c = new W3CWebSocket(
|
||||
`${wsProt}://${
|
||||
url.hostname
|
||||
}:${port}/ws/speedtest?duration=${duration}${durationUnit}&size=${size}${sizeUnit}${
|
||||
concurrent.trim() !== "" ? `&concurrent=${concurrent}` : ""
|
||||
}`
|
||||
);
|
||||
|
||||
const baseDate = moment();
|
||||
|
||||
const currentTime = baseDate.unix() / 1000;
|
||||
|
||||
const incrementDate =
|
||||
baseDate
|
||||
.add(
|
||||
parseInt(duration) * 2,
|
||||
durationUnit as moment.unitOfTime.DurationConstructor
|
||||
)
|
||||
.unix() / 1000;
|
||||
|
||||
const totalSeconds = (incrementDate - currentTime) / 1000;
|
||||
|
||||
setTopDate(incrementDate);
|
||||
setCurrentValue(currentTime);
|
||||
setTotalSeconds(totalSeconds);
|
||||
|
||||
let interval: any | null = null;
|
||||
if (c !== null) {
|
||||
c.onopen = () => {
|
||||
console.log("WebSocket Client Connected");
|
||||
c.send("ok");
|
||||
interval = setInterval(() => {
|
||||
c.send("ok");
|
||||
}, 10 * 1000);
|
||||
};
|
||||
c.onmessage = (message: IMessageEvent) => {
|
||||
const data: SpeedTestResponse = JSON.parse(message.data.toString());
|
||||
|
||||
setCurrStatus((prevStatus) => {
|
||||
let prSt: SpeedTestResponse[] = [];
|
||||
if (prevStatus) {
|
||||
prSt = [...prevStatus];
|
||||
}
|
||||
return [...prSt, data];
|
||||
});
|
||||
|
||||
const currTime = moment().unix() / 1000;
|
||||
setCurrentValue(currTime);
|
||||
};
|
||||
c.onclose = () => {
|
||||
clearInterval(interval);
|
||||
console.log("connection closed by server");
|
||||
// reset start status
|
||||
setStart(false);
|
||||
};
|
||||
return () => {
|
||||
// close websocket on useEffect cleanup
|
||||
c.close(1000);
|
||||
clearInterval(interval);
|
||||
console.log("closing websockets");
|
||||
};
|
||||
}
|
||||
} else {
|
||||
// reset start status
|
||||
setStart(false);
|
||||
}
|
||||
}, [concurrent, duration, durationUnit, size, sizeUnit, start]);
|
||||
|
||||
useEffect(() => {
|
||||
const actualSeconds = (topDate - currentValue) / 1000;
|
||||
|
||||
let percToDisplay = 100 - (actualSeconds * 100) / totalSeconds;
|
||||
|
||||
if (percToDisplay > 100) {
|
||||
percToDisplay = 100;
|
||||
}
|
||||
|
||||
setSpeedometerValue(percToDisplay);
|
||||
}, [start, currentValue, topDate, totalSeconds]);
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<PageHeader label="Speedtest" />
|
||||
|
||||
<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>
|
||||
<Grid item>
|
||||
<Button
|
||||
onClick={() => {
|
||||
setCurrStatus(null);
|
||||
setStart(true);
|
||||
}}
|
||||
color="primary"
|
||||
type="button"
|
||||
variant={
|
||||
currStatus !== null && !start ? "contained" : "outlined"
|
||||
}
|
||||
className={`${classes.buttonBackground} ${classes.speedStart}`}
|
||||
disabled={duration.trim() === "" || size.trim() === "" || start}
|
||||
>
|
||||
{!start && (
|
||||
<Fragment>
|
||||
{currStatus !== null ? "Retest" : "Start"}
|
||||
</Fragment>
|
||||
)}
|
||||
{start ? "Start" : ""}
|
||||
</Button>
|
||||
</Grid>
|
||||
<Grid item md={9} sm={12} className={classes.progressContainer}>
|
||||
<div className={classes.stepProgressText}>
|
||||
{start ? (
|
||||
"Speedtest in progress..."
|
||||
) : (
|
||||
<Fragment>
|
||||
{currStatus && !start ? "Done!" : "Start a new test"}
|
||||
</Fragment>
|
||||
)}
|
||||
{start && <CircularProgress size={15} />}
|
||||
</div>
|
||||
<div>
|
||||
<ProgressBarWrapper
|
||||
value={speedometerValue}
|
||||
ready={currStatus !== null && !start}
|
||||
/>
|
||||
</div>
|
||||
</Grid>
|
||||
<Grid item className={classes.advancedButton}>
|
||||
<button
|
||||
onClick={() => {
|
||||
setAdvancedOpen(!advancedOpen);
|
||||
}}
|
||||
className={classes.advancedConfiguration}
|
||||
>
|
||||
{advancedOpen ? "Hide" : "Show"} advanced options{" "}
|
||||
<span
|
||||
className={
|
||||
advancedOpen ? classes.advancedOpen : classes.advancedClosed
|
||||
}
|
||||
>
|
||||
<ArrowForwardIosIcon />
|
||||
</span>
|
||||
</button>
|
||||
</Grid>
|
||||
</Grid>
|
||||
<Grid
|
||||
container
|
||||
className={`${classes.advancedContent} ${
|
||||
advancedOpen ? "open" : ""
|
||||
}`}
|
||||
>
|
||||
<Grid item xs={12} md={3} className={classes.advancedOption}>
|
||||
<InputBoxWrapper
|
||||
id={"duration"}
|
||||
name={"duration"}
|
||||
label={"Duration"}
|
||||
onChange={(e) => {
|
||||
setDuration(e.target.value);
|
||||
}}
|
||||
value={duration}
|
||||
disabled={start}
|
||||
overlayObject={
|
||||
<InputUnitMenu
|
||||
id={"duration-unit"}
|
||||
onUnitChange={setDurationUnit}
|
||||
unitSelected={durationUnit}
|
||||
unitsList={[
|
||||
{ label: "miliseconds", value: "ms" },
|
||||
{ label: "seconds", value: "s" },
|
||||
]}
|
||||
disabled={start}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12} md={3} className={classes.advancedOption}>
|
||||
<InputBoxWrapper
|
||||
id={"size"}
|
||||
name={"size"}
|
||||
label={"Object Size"}
|
||||
onChange={(e) => {
|
||||
setSize(e.target.value);
|
||||
}}
|
||||
value={size}
|
||||
disabled={start}
|
||||
overlayObject={
|
||||
<InputUnitMenu
|
||||
id={"size-unit"}
|
||||
onUnitChange={setSizeUnit}
|
||||
unitSelected={sizeUnit}
|
||||
unitsList={[
|
||||
{ label: "KB", value: "KB" },
|
||||
{ label: "MB", value: "MB" },
|
||||
{ label: "GB", value: "GB" },
|
||||
]}
|
||||
disabled={start}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12} md={3} className={classes.advancedOption}>
|
||||
<InputBoxWrapper
|
||||
type="number"
|
||||
min="0"
|
||||
id={"concurrent"}
|
||||
name={"concurrent"}
|
||||
label={"Concurrent Requests"}
|
||||
onChange={(e) => {
|
||||
setConcurrent(e.target.value);
|
||||
}}
|
||||
value={concurrent}
|
||||
disabled={start}
|
||||
/>
|
||||
</Grid>
|
||||
</Grid>
|
||||
<Grid container className={classes.multiModule}>
|
||||
<Grid item xs={12}>
|
||||
<Fragment>
|
||||
<Grid item xs={12}>
|
||||
{!start && currStatus !== null && (
|
||||
<Fragment>
|
||||
<STResults results={currStatus} />
|
||||
</Fragment>
|
||||
)}
|
||||
</Grid>
|
||||
</Fragment>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
export default withStyles(styles)(Speedtest);
|
||||
48
portal-ui/src/screens/Console/Speedtest/types.ts
Normal file
48
portal-ui/src/screens/Console/Speedtest/types.ts
Normal file
@@ -0,0 +1,48 @@
|
||||
// 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/>.
|
||||
|
||||
export interface SpeedTestResponse {
|
||||
version: string;
|
||||
servers: number;
|
||||
disks: number;
|
||||
size: number;
|
||||
concurrent: number;
|
||||
PUTStats?: STStats;
|
||||
GETStats?: STStats;
|
||||
}
|
||||
|
||||
export interface STStats {
|
||||
throughputPerSec: number;
|
||||
objectsPerSec: number;
|
||||
servers: STServer[] | null;
|
||||
}
|
||||
|
||||
export interface STServer {
|
||||
endpoint: string;
|
||||
throughputPerSec: number;
|
||||
objectsPerSec: number;
|
||||
err: string;
|
||||
}
|
||||
|
||||
export interface IndvServerMetric {
|
||||
host: string;
|
||||
getValue: string;
|
||||
getUnit: string;
|
||||
getError?: string;
|
||||
putValue: string;
|
||||
putUnit: string;
|
||||
putError?: string;
|
||||
}
|
||||
32
portal-ui/src/screens/Console/Speedtest/utils.ts
Normal file
32
portal-ui/src/screens/Console/Speedtest/utils.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
// 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 { SpeedTestResponse } from "./types";
|
||||
|
||||
export const cleanMetrics = (results: SpeedTestResponse[]) => {
|
||||
const cleanRes = results.filter(
|
||||
(item) => item.version !== "0" && item.disks !== 0
|
||||
);
|
||||
|
||||
const states = cleanRes.map((itemRes) => {
|
||||
return {
|
||||
get: itemRes.GETStats?.throughputPerSec || 0,
|
||||
put: itemRes.PUTStats?.throughputPerSec || 0,
|
||||
};
|
||||
});
|
||||
|
||||
return [{ get: 0, put: 0 }, ...states];
|
||||
};
|
||||
@@ -42,8 +42,8 @@ import SearchIcon from "../../../../icons/SearchIcon";
|
||||
import PageHeader from "../../Common/PageHeader/PageHeader";
|
||||
import TenantListItem from "./TenantListItem";
|
||||
import HelpBox from "../../../../common/HelpBox";
|
||||
import BoxIconButton from "../../Common/BoxIconButton";
|
||||
import AButton from "../../Common/AButton";
|
||||
import BoxIconButton from "../../Common/BoxIconButton/BoxIconButton";
|
||||
import AButton from "../../Common/AButton/AButton";
|
||||
|
||||
interface ITenantsList {
|
||||
classes: any;
|
||||
|
||||
@@ -58,7 +58,7 @@ import ScreenTitle from "../../Common/ScreenTitle/ScreenTitle";
|
||||
import EditIcon from "../../../../icons/EditIcon";
|
||||
import RefreshIcon from "../../../../icons/RefreshIcon";
|
||||
import TenantsIcon from "../../../../icons/TenantsIcon";
|
||||
import BoxIconButton from "../../Common/BoxIconButton";
|
||||
import BoxIconButton from "../../Common/BoxIconButton/BoxIconButton";
|
||||
|
||||
interface ITenantDetailsProps {
|
||||
classes: any;
|
||||
|
||||
@@ -35,7 +35,7 @@ import { AppState } from "../../../../store";
|
||||
import history from "./../../../../history";
|
||||
import { CircleIcon } from "../../../../icons";
|
||||
import { tenantIsOnline } from "../ListTenants/utils";
|
||||
import AButton from "../../Common/AButton";
|
||||
import AButton from "../../Common/AButton/AButton";
|
||||
|
||||
interface ITenantsSummary {
|
||||
classes: any;
|
||||
|
||||
@@ -23,6 +23,7 @@ import {
|
||||
TraceIcon,
|
||||
WatchIcon,
|
||||
} from "../../../icons";
|
||||
import SpeedtestIcon from "../../../icons/SpeedtestIcon";
|
||||
|
||||
export const configurationElements: IElement[] = [
|
||||
{
|
||||
@@ -55,4 +56,9 @@ export const configurationElements: IElement[] = [
|
||||
configuration_id: "diagnostics",
|
||||
configuration_label: "Diagnostics",
|
||||
},
|
||||
{
|
||||
icon: <SpeedtestIcon />,
|
||||
configuration_id: "speedtest",
|
||||
configuration_label: "Speedtest",
|
||||
},
|
||||
];
|
||||
|
||||
@@ -47,7 +47,7 @@ import PageHeader from "../Common/PageHeader/PageHeader";
|
||||
import SearchIcon from "../../../icons/SearchIcon";
|
||||
import { decodeFileName } from "../../../common/utils";
|
||||
import HelpBox from "../../../common/HelpBox";
|
||||
import AButton from "../Common/AButton";
|
||||
import AButton from "../Common/AButton/AButton";
|
||||
|
||||
const styles = (theme: Theme) =>
|
||||
createStyles({
|
||||
|
||||
@@ -54,8 +54,8 @@ import ListItemText from "@mui/material/ListItemText";
|
||||
import List from "@mui/material/List";
|
||||
import LockIcon from "@mui/icons-material/Lock";
|
||||
import ScreenTitle from "../Common/ScreenTitle/ScreenTitle";
|
||||
import BoxIconButton from "../Common/BoxIconButton";
|
||||
import PanelTitle from "../Common/PanelTitle";
|
||||
import BoxIconButton from "../Common/BoxIconButton/BoxIconButton";
|
||||
import PanelTitle from "../Common/PanelTitle/PanelTitle";
|
||||
|
||||
const styles = (theme: Theme) =>
|
||||
createStyles({
|
||||
|
||||
@@ -35,7 +35,7 @@ import DeleteServiceAccount from "../Account/DeleteServiceAccount";
|
||||
import CredentialsPrompt from "../Common/CredentialsPrompt/CredentialsPrompt";
|
||||
import { AddIcon } from "../../../icons";
|
||||
import Button from "@mui/material/Button";
|
||||
import PanelTitle from "../Common/PanelTitle";
|
||||
import PanelTitle from "../Common/PanelTitle/PanelTitle";
|
||||
|
||||
interface IUserServiceAccountsProps {
|
||||
classes: any;
|
||||
|
||||
@@ -40,7 +40,7 @@ import (
|
||||
func registerAdminInfoHandlers(api *operations.ConsoleAPI) {
|
||||
// return usage stats
|
||||
api.AdminAPIAdminInfoHandler = admin_api.AdminInfoHandlerFunc(func(params admin_api.AdminInfoParams, session *models.Principal) middleware.Responder {
|
||||
infoResp, err := getAdminInfoResponse(session)
|
||||
infoResp, err := getAdminInfoResponse(session, params)
|
||||
if err != nil {
|
||||
return admin_api.NewAdminInfoDefault(int(err.Code)).WithPayload(err)
|
||||
}
|
||||
@@ -824,8 +824,13 @@ type LabelResults struct {
|
||||
}
|
||||
|
||||
// getAdminInfoResponse returns the response containing total buckets, objects and usage.
|
||||
func getAdminInfoResponse(session *models.Principal) (*models.AdminInfoResponse, *models.Error) {
|
||||
prometheusURL := getPrometheusURL()
|
||||
func getAdminInfoResponse(session *models.Principal, params admin_api.AdminInfoParams) (*models.AdminInfoResponse, *models.Error) {
|
||||
prometheusURL := ""
|
||||
|
||||
if !*params.DefaultOnly {
|
||||
prometheusURL = getPrometheusURL()
|
||||
}
|
||||
|
||||
mAdmin, err := NewMinioAdminClient(session)
|
||||
if err != nil {
|
||||
return nil, prepareError(err)
|
||||
|
||||
119
restapi/admin_speedtest.go
Normal file
119
restapi/admin_speedtest.go
Normal file
@@ -0,0 +1,119 @@
|
||||
// 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/>.
|
||||
|
||||
package restapi
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/dustin/go-humanize"
|
||||
"github.com/gorilla/websocket"
|
||||
"github.com/minio/madmin-go"
|
||||
)
|
||||
|
||||
// getSpeedtesthOptionsFromReq gets duration, size & concurrent requests from a websocket
|
||||
// path come as : `/speedtest?duration=2h&size=12MiB&concurrent=10`
|
||||
func getSpeedtestOptionsFromReq(req *http.Request) (*madmin.SpeedtestOpts, error) {
|
||||
optionsSet := madmin.SpeedtestOpts{}
|
||||
|
||||
queryPairs := req.URL.Query()
|
||||
|
||||
paramDuration := queryPairs.Get("duration")
|
||||
|
||||
if paramDuration == "" {
|
||||
paramDuration = "10s"
|
||||
}
|
||||
|
||||
duration, err := time.ParseDuration(paramDuration)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to parse duration: %s", paramDuration)
|
||||
}
|
||||
|
||||
if duration <= 0 {
|
||||
return nil, fmt.Errorf("duration cannot be 0 or negative")
|
||||
}
|
||||
|
||||
optionsSet.Duration = duration
|
||||
|
||||
paramSize := queryPairs.Get("size")
|
||||
|
||||
if paramSize == "" {
|
||||
paramSize = "64MiB"
|
||||
}
|
||||
|
||||
size, err := humanize.ParseBytes(paramSize)
|
||||
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to parse object size")
|
||||
}
|
||||
|
||||
if size < 0 {
|
||||
return nil, fmt.Errorf("size is expected to be atleast 0 bytes")
|
||||
}
|
||||
|
||||
optionsSet.Size = int(size)
|
||||
|
||||
paramConcurrent := queryPairs.Get("concurrent")
|
||||
|
||||
if paramConcurrent == "" {
|
||||
paramConcurrent = "32"
|
||||
}
|
||||
|
||||
concurrent, err := strconv.Atoi(paramConcurrent)
|
||||
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid concurrent value: %s", paramConcurrent)
|
||||
}
|
||||
|
||||
if concurrent <= 0 {
|
||||
return nil, fmt.Errorf("concurrency cannot be '0' or negative")
|
||||
}
|
||||
|
||||
optionsSet.Concurrency = concurrent
|
||||
|
||||
return &optionsSet, nil
|
||||
}
|
||||
|
||||
func startSpeedtest(ctx context.Context, conn WSConn, client MinioAdmin, speedtestOpts *madmin.SpeedtestOpts) error {
|
||||
speedtestRes, err := client.speedtest(ctx, *speedtestOpts)
|
||||
|
||||
if err != nil {
|
||||
LogError("error initializing speedtest: %v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
for result := range speedtestRes {
|
||||
// Serializing message
|
||||
bytes, err := json.Marshal(result)
|
||||
if err != nil {
|
||||
LogError("error serializing json: %v", err)
|
||||
return err
|
||||
}
|
||||
// Send Message through websocket connection
|
||||
err = conn.writeMessage(websocket.TextMessage, bytes)
|
||||
if err != nil {
|
||||
LogError("error writing speedtest response: %v", err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -51,3 +51,7 @@ func (ac adminClientMock) addRemoteBucket(ctx context.Context, bucket string, ta
|
||||
func (ac adminClientMock) changePassword(ctx context.Context, accessKey, secretKey string) error {
|
||||
return minioChangePasswordMock(ctx, accessKey, secretKey)
|
||||
}
|
||||
|
||||
func (ac adminClientMock) speedtest(ctx context.Context, opts madmin.SpeedtestOpts) (chan madmin.SpeedTestResult, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
@@ -112,6 +112,8 @@ type MinioAdmin interface {
|
||||
addTier(ctx context.Context, tier *madmin.TierConfig) error
|
||||
// Edit Tier Credentials
|
||||
editTierCreds(ctx context.Context, tierName string, creds madmin.TierCreds) error
|
||||
// Speedtest
|
||||
speedtest(ctx context.Context, opts madmin.SpeedtestOpts) (chan madmin.SpeedTestResult, error)
|
||||
}
|
||||
|
||||
// Interface implementation
|
||||
@@ -452,3 +454,7 @@ func GetConsoleHTTPClient() *http.Client {
|
||||
}
|
||||
return httpClient
|
||||
}
|
||||
|
||||
func (ac AdminClient) speedtest(ctx context.Context, opts madmin.SpeedtestOpts) (chan madmin.SpeedTestResult, error) {
|
||||
return ac.Client.Speedtest(ctx, opts)
|
||||
}
|
||||
|
||||
@@ -142,6 +142,14 @@ func init() {
|
||||
],
|
||||
"summary": "Returns information about the deployment",
|
||||
"operationId": "AdminInfo",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
"name": "defaultOnly",
|
||||
"in": "query"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "A successful response.",
|
||||
@@ -5789,6 +5797,14 @@ func init() {
|
||||
],
|
||||
"summary": "Returns information about the deployment",
|
||||
"operationId": "AdminInfo",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
"name": "defaultOnly",
|
||||
"in": "query"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "A successful response.",
|
||||
|
||||
@@ -26,15 +26,25 @@ import (
|
||||
"net/http"
|
||||
|
||||
"github.com/go-openapi/errors"
|
||||
"github.com/go-openapi/runtime"
|
||||
"github.com/go-openapi/runtime/middleware"
|
||||
"github.com/go-openapi/strfmt"
|
||||
"github.com/go-openapi/swag"
|
||||
)
|
||||
|
||||
// NewAdminInfoParams creates a new AdminInfoParams object
|
||||
//
|
||||
// There are no default values defined in the spec.
|
||||
// with the default values initialized.
|
||||
func NewAdminInfoParams() AdminInfoParams {
|
||||
|
||||
return AdminInfoParams{}
|
||||
var (
|
||||
// initialize parameters with default values
|
||||
|
||||
defaultOnlyDefault = bool(false)
|
||||
)
|
||||
|
||||
return AdminInfoParams{
|
||||
DefaultOnly: &defaultOnlyDefault,
|
||||
}
|
||||
}
|
||||
|
||||
// AdminInfoParams contains all the bound params for the admin info operation
|
||||
@@ -45,6 +55,12 @@ type AdminInfoParams struct {
|
||||
|
||||
// HTTP Request Object
|
||||
HTTPRequest *http.Request `json:"-"`
|
||||
|
||||
/*
|
||||
In: query
|
||||
Default: false
|
||||
*/
|
||||
DefaultOnly *bool
|
||||
}
|
||||
|
||||
// BindRequest both binds and validates a request, it assumes that complex things implement a Validatable(strfmt.Registry) error interface
|
||||
@@ -56,8 +72,38 @@ func (o *AdminInfoParams) BindRequest(r *http.Request, route *middleware.Matched
|
||||
|
||||
o.HTTPRequest = r
|
||||
|
||||
qs := runtime.Values(r.URL.Query())
|
||||
|
||||
qDefaultOnly, qhkDefaultOnly, _ := qs.GetOK("defaultOnly")
|
||||
if err := o.bindDefaultOnly(qDefaultOnly, qhkDefaultOnly, route.Formats); err != nil {
|
||||
res = append(res, err)
|
||||
}
|
||||
if len(res) > 0 {
|
||||
return errors.CompositeValidationError(res...)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// bindDefaultOnly binds and validates parameter DefaultOnly from query.
|
||||
func (o *AdminInfoParams) bindDefaultOnly(rawData []string, hasKey bool, formats strfmt.Registry) error {
|
||||
var raw string
|
||||
if len(rawData) > 0 {
|
||||
raw = rawData[len(rawData)-1]
|
||||
}
|
||||
|
||||
// Required: false
|
||||
// AllowEmptyValue: false
|
||||
|
||||
if raw == "" { // empty values pass all other validations
|
||||
// Default values have been previously initialized by NewAdminInfoParams()
|
||||
return nil
|
||||
}
|
||||
|
||||
value, err := swag.ConvertBool(raw)
|
||||
if err != nil {
|
||||
return errors.InvalidType("defaultOnly", "query", "bool", raw)
|
||||
}
|
||||
o.DefaultOnly = &value
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -26,11 +26,17 @@ import (
|
||||
"errors"
|
||||
"net/url"
|
||||
golangswaggerpaths "path"
|
||||
|
||||
"github.com/go-openapi/swag"
|
||||
)
|
||||
|
||||
// AdminInfoURL generates an URL for the admin info operation
|
||||
type AdminInfoURL struct {
|
||||
DefaultOnly *bool
|
||||
|
||||
_basePath string
|
||||
// avoid unkeyed usage
|
||||
_ struct{}
|
||||
}
|
||||
|
||||
// WithBasePath sets the base path for this url builder, only required when it's different from the
|
||||
@@ -60,6 +66,18 @@ func (o *AdminInfoURL) Build() (*url.URL, error) {
|
||||
}
|
||||
_result.Path = golangswaggerpaths.Join(_basePath, _path)
|
||||
|
||||
qs := make(url.Values)
|
||||
|
||||
var defaultOnlyQ string
|
||||
if o.DefaultOnly != nil {
|
||||
defaultOnlyQ = swag.FormatBool(*o.DefaultOnly)
|
||||
}
|
||||
if defaultOnlyQ != "" {
|
||||
qs.Set("defaultOnly", defaultOnlyQ)
|
||||
}
|
||||
|
||||
_result.RawQuery = qs.Encode()
|
||||
|
||||
return &_result, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@ package restapi
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"strconv"
|
||||
@@ -28,6 +29,7 @@ import (
|
||||
"github.com/gorilla/websocket"
|
||||
"github.com/minio/console/models"
|
||||
"github.com/minio/console/pkg/auth"
|
||||
"github.com/minio/madmin-go"
|
||||
)
|
||||
|
||||
var upgrader = websocket.Upgrader{
|
||||
@@ -212,6 +214,23 @@ func serveWS(w http.ResponseWriter, req *http.Request) {
|
||||
return
|
||||
}
|
||||
go wsS3Client.watch(wOptions)
|
||||
case strings.HasPrefix(wsPath, `/speedtest`):
|
||||
fmt.Println("Speedtest triggered")
|
||||
speedtestOpts, err := getSpeedtestOptionsFromReq(req)
|
||||
|
||||
if err != nil {
|
||||
LogError("error getting speedtest options: %v", err)
|
||||
closeWsConn(conn)
|
||||
return
|
||||
}
|
||||
|
||||
wsAdminClient, err := newWebSocketAdminClient(conn, session)
|
||||
if err != nil {
|
||||
closeWsConn(conn)
|
||||
return
|
||||
}
|
||||
go wsAdminClient.speedtest(speedtestOpts)
|
||||
|
||||
default:
|
||||
// path not found
|
||||
closeWsConn(conn)
|
||||
@@ -374,6 +393,21 @@ func (wsc *wsAdminClient) healthInfo(deadline *time.Duration) {
|
||||
sendWsCloseMessage(wsc.conn, err)
|
||||
}
|
||||
|
||||
func (wsc *wsAdminClient) speedtest(opts *madmin.SpeedtestOpts) {
|
||||
defer func() {
|
||||
LogInfo("speedtest stopped")
|
||||
// close connection after return
|
||||
wsc.conn.close()
|
||||
}()
|
||||
LogInfo("speedtest started")
|
||||
|
||||
ctx := wsReadClientCtx(wsc.conn)
|
||||
|
||||
err := startSpeedtest(ctx, wsc.conn, wsc.client, opts)
|
||||
|
||||
sendWsCloseMessage(wsc.conn, err)
|
||||
}
|
||||
|
||||
// sendWsCloseMessage sends Websocket Connection Close Message indicating the Status Code
|
||||
// see https://tools.ietf.org/html/rfc6455#page-45
|
||||
func sendWsCloseMessage(conn WSConn, err error) {
|
||||
|
||||
@@ -1981,6 +1981,12 @@ paths:
|
||||
get:
|
||||
summary: Returns information about the deployment
|
||||
operationId: AdminInfo
|
||||
parameters:
|
||||
- name: defaultOnly
|
||||
in: query
|
||||
required: false
|
||||
type: boolean
|
||||
default: false
|
||||
responses:
|
||||
200:
|
||||
description: A successful response.
|
||||
|
||||
Reference in New Issue
Block a user