Add get healthInfo api using websockets (#543)
Integrate also HealthInfo API with Console UI
This commit is contained in:
2
Makefile
2
Makefile
@@ -50,7 +50,7 @@ swagger-gen:
|
||||
@swagger generate server -A console --main-package=console --exclude-main -P models.Principal -f ./swagger.yml -r NOTICE
|
||||
|
||||
assets:
|
||||
@(cd portal-ui; yarn install; make build-static; cd ..)
|
||||
@(cd portal-ui; yarn install; make build-static; yarn prettier --write . --loglevel warn; cd ..)
|
||||
|
||||
test:
|
||||
@(GO111MODULE=on go test -race -v github.com/minio/console/restapi/...)
|
||||
|
||||
4
go.sum
4
go.sum
@@ -319,6 +319,7 @@ github.com/fatih/color v1.10.0 h1:s36xzo75JdqLaaWoiEHk767eHiwo0598uUxyfiPkDsg=
|
||||
github.com/fatih/color v1.10.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM=
|
||||
github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo=
|
||||
github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M=
|
||||
github.com/felixge/httpsnoop v1.0.1 h1:lvB5Jl89CsZtGIWuTcDM1E/vkVs49/Ml7JJe07l8SPQ=
|
||||
github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
|
||||
github.com/fortytw2/leaktest v1.2.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g=
|
||||
github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw=
|
||||
@@ -602,6 +603,7 @@ github.com/goreleaser/nfpm v1.2.1/go.mod h1:TtWrABZozuLOttX2uDlYyECfQX7x5XYkVxhj
|
||||
github.com/goreleaser/nfpm v1.3.0/go.mod h1:w0p7Kc9TAUgWMyrub63ex3M2Mgw88M4GZXoTq5UCb40=
|
||||
github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
|
||||
github.com/gorilla/handlers v1.4.2/go.mod h1:Qkdc/uu4tH4g6mTK6auzZ766c4CA0Ng8+o/OAirnOIQ=
|
||||
github.com/gorilla/handlers v1.5.1 h1:9lRY6j8DEeeBT10CvO9hGW0gmky0BprnvDI5vfhUHH4=
|
||||
github.com/gorilla/handlers v1.5.1/go.mod h1:t8XrUpc4KVXb7HGyJ4/cEnwQiaxrX/hz1Zv/4g96P1Q=
|
||||
github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
|
||||
github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
|
||||
@@ -922,6 +924,7 @@ github.com/maxbrunsfeld/counterfeiter/v6 v6.2.2/go.mod h1:eD9eIE7cdwcMi9rYluz88J
|
||||
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=
|
||||
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
|
||||
github.com/miekg/dns v1.1.8/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
|
||||
github.com/miekg/dns v1.1.35 h1:oTfOaDH+mZkdcgdIjH6yBajRGtIwcwcaR+rt23ZSrJs=
|
||||
github.com/miekg/dns v1.1.35/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM=
|
||||
github.com/minio/cli v1.22.0 h1:VTQm7lmXm3quxO917X3p+el1l0Ca5X3S4PM2ruUYO68=
|
||||
github.com/minio/cli v1.22.0/go.mod h1:bYxnK0uS629N3Bq+AOZZ+6lwF77Sodk4+UL9vNuXhOY=
|
||||
@@ -1346,6 +1349,7 @@ go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
|
||||
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||
go.opencensus.io v0.22.3 h1:8sGtKOrtQqkN1bp2AtX+misvLIlOmsEsNd+9NIcPEm8=
|
||||
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||
go.opencensus.io v0.22.5 h1:dntmOdLpSpHlVqbW5Eay97DelsZHe+55D+xC6i0dDS0=
|
||||
go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk=
|
||||
go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
|
||||
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
|
||||
|
||||
@@ -43,6 +43,7 @@ var (
|
||||
heal = "/heal"
|
||||
trace = "/trace"
|
||||
logs = "/logs"
|
||||
healthInfo = "/health-info"
|
||||
)
|
||||
|
||||
type ConfigurationActionSet struct {
|
||||
@@ -231,6 +232,16 @@ var traceActionSet = ConfigurationActionSet{
|
||||
),
|
||||
}
|
||||
|
||||
// healthInfoActionSet contains the list of admin actions required for this endpoint to work
|
||||
var healthInfoActionSet = ConfigurationActionSet{
|
||||
actionTypes: iampolicy.NewActionSet(
|
||||
iampolicy.AllAdminActions,
|
||||
),
|
||||
actions: iampolicy.NewActionSet(
|
||||
iampolicy.HealthInfoAdminAction,
|
||||
),
|
||||
}
|
||||
|
||||
// endpointRules contains the mapping between endpoints and ActionSets, additional rules can be added here
|
||||
var endpointRules = map[string]ConfigurationActionSet{
|
||||
configuration: configurationActionSet,
|
||||
@@ -252,6 +263,7 @@ var endpointRules = map[string]ConfigurationActionSet{
|
||||
heal: healActionSet,
|
||||
trace: traceActionSet,
|
||||
logs: logsActionSet,
|
||||
healthInfo: healthInfoActionSet,
|
||||
}
|
||||
|
||||
// operatorRules contains the mapping between endpoints and ActionSets for operator only mode
|
||||
|
||||
@@ -72,7 +72,7 @@ func TestGetAuthorizedEndpoints(t *testing.T) {
|
||||
"admin:*",
|
||||
},
|
||||
},
|
||||
want: 17,
|
||||
want: 18,
|
||||
},
|
||||
{
|
||||
name: "all s3 endpoints",
|
||||
@@ -91,7 +91,7 @@ func TestGetAuthorizedEndpoints(t *testing.T) {
|
||||
"s3:*",
|
||||
},
|
||||
},
|
||||
want: 19,
|
||||
want: 20,
|
||||
},
|
||||
{
|
||||
name: "Console User - default endpoints",
|
||||
|
||||
2
portal-ui/.prettierignore
Normal file
2
portal-ui/.prettierignore
Normal file
@@ -0,0 +1,2 @@
|
||||
build
|
||||
coverage
|
||||
1
portal-ui/.prettierrc.json
Normal file
1
portal-ui/.prettierrc.json
Normal file
@@ -0,0 +1 @@
|
||||
{}
|
||||
File diff suppressed because one or more lines are too long
@@ -1,10 +1,10 @@
|
||||
const rewireReactHotLoader = require('react-app-rewire-hot-loader');
|
||||
const rewireReactHotLoader = require("react-app-rewire-hot-loader");
|
||||
|
||||
/* config-overrides.js */
|
||||
module.exports = function override(config, env) {
|
||||
if (env === 'development') {
|
||||
config.resolve.alias['react-dom'] = '@hot-loader/react-dom';
|
||||
}
|
||||
config = rewireReactHotLoader(config, env);
|
||||
return config;
|
||||
if (env === "development") {
|
||||
config.resolve.alias["react-dom"] = "@hot-loader/react-dom";
|
||||
}
|
||||
config = rewireReactHotLoader(config, env);
|
||||
return config;
|
||||
};
|
||||
|
||||
@@ -79,7 +79,7 @@
|
||||
"proxy": "http://localhost:9090/",
|
||||
"devDependencies": {
|
||||
"jest": "^24.9.0",
|
||||
"prettier": "^1.19.1",
|
||||
"prettier": "2.2.1",
|
||||
"typescript": "^4.1.2"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,21 +4,44 @@
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<meta name="theme-color" content="#000000" />
|
||||
<meta
|
||||
name="description"
|
||||
content="MinIO Console"
|
||||
/>
|
||||
<meta name="description" content="MinIO Console" />
|
||||
<!--
|
||||
manifest.json provides metadata used when your web app is installed on a
|
||||
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
|
||||
-->
|
||||
<link href="https://fonts.googleapis.com/css2?family=Lato:wght@400;500;700;900&display=swap" rel="stylesheet">
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="%PUBLIC_URL%/apple-icon-180x180.png">
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="%PUBLIC_URL%/favicon-32x32.png">
|
||||
<link rel="icon" type="image/png" sizes="96x96" href="%PUBLIC_URL%/favicon-96x96.png">
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="%PUBLIC_URL%/favicon-16x16.png">
|
||||
<link rel="manifest" href="%PUBLIC_URL%/manifest.json">
|
||||
<link rel="mask-icon" href="%PUBLIC_URL%/safari-pinned-tab.svg" color="#3a4e54">
|
||||
<link
|
||||
href="https://fonts.googleapis.com/css2?family=Lato:wght@400;500;700;900&display=swap"
|
||||
rel="stylesheet"
|
||||
/>
|
||||
<link
|
||||
rel="apple-touch-icon"
|
||||
sizes="180x180"
|
||||
href="%PUBLIC_URL%/apple-icon-180x180.png"
|
||||
/>
|
||||
<link
|
||||
rel="icon"
|
||||
type="image/png"
|
||||
sizes="32x32"
|
||||
href="%PUBLIC_URL%/favicon-32x32.png"
|
||||
/>
|
||||
<link
|
||||
rel="icon"
|
||||
type="image/png"
|
||||
sizes="96x96"
|
||||
href="%PUBLIC_URL%/favicon-96x96.png"
|
||||
/>
|
||||
<link
|
||||
rel="icon"
|
||||
type="image/png"
|
||||
sizes="16x16"
|
||||
href="%PUBLIC_URL%/favicon-16x16.png"
|
||||
/>
|
||||
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
|
||||
<link
|
||||
rel="mask-icon"
|
||||
href="%PUBLIC_URL%/safari-pinned-tab.svg"
|
||||
color="#3a4e54"
|
||||
/>
|
||||
<!--
|
||||
Notice the use of %PUBLIC_URL% in the tags above.
|
||||
It will be replaced with the URL of the `public` folder during the build.
|
||||
|
||||
@@ -22,6 +22,7 @@ import {
|
||||
USER_LOGGED,
|
||||
SET_LOADING_PROGRESS,
|
||||
SET_SNACK_BAR_MESSAGE,
|
||||
SET_SERVER_DIAG_STAT,
|
||||
} from "./types";
|
||||
|
||||
export function userLoggedIn(loggedIn: boolean) {
|
||||
@@ -72,3 +73,10 @@ export const setSnackBarMessage = (message: string) => {
|
||||
snackBarMessage: message,
|
||||
};
|
||||
};
|
||||
|
||||
export const setServerDiagStat = (status: string) => {
|
||||
return {
|
||||
type: SET_SERVER_DIAG_STAT,
|
||||
serverDiagnosticStatus: status,
|
||||
};
|
||||
};
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { createBrowserHistory } from "history";
|
||||
|
||||
|
||||
export default createBrowserHistory();
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: 'Lato', sans-serif;
|
||||
font-family: "Lato", sans-serif;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
code {
|
||||
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
|
||||
font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New",
|
||||
monospace;
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@ import {
|
||||
USER_LOGGED,
|
||||
SET_LOADING_PROGRESS,
|
||||
SET_SNACK_BAR_MESSAGE,
|
||||
SET_SERVER_DIAG_STAT,
|
||||
} from "./types";
|
||||
|
||||
const initialState: SystemState = {
|
||||
@@ -36,6 +37,7 @@ const initialState: SystemState = {
|
||||
serverIsLoading: false,
|
||||
loadingProgress: 100,
|
||||
snackBarMessage: "",
|
||||
serverDiagnosticStatus: "",
|
||||
};
|
||||
|
||||
export function systemReducer(
|
||||
@@ -79,6 +81,11 @@ export function systemReducer(
|
||||
...state,
|
||||
snackBarMessage: action.snackBarMessage,
|
||||
};
|
||||
case SET_SERVER_DIAG_STAT:
|
||||
return {
|
||||
...state,
|
||||
serverDiagnosticStatus: action.serverDiagnosticStatus,
|
||||
};
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
|
||||
@@ -1,160 +1,258 @@
|
||||
/* BASICS */
|
||||
|
||||
.CodeMirror {
|
||||
/* Set height, width, borders, and global font properties here */
|
||||
font-family: monospace;
|
||||
height: 300px;
|
||||
color: #fff;
|
||||
background: #081C42;
|
||||
direction: ltr;
|
||||
font-size: 13px;
|
||||
/* Set height, width, borders, and global font properties here */
|
||||
font-family: monospace;
|
||||
height: 300px;
|
||||
color: #fff;
|
||||
background: #081c42;
|
||||
direction: ltr;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
/* PADDING */
|
||||
|
||||
.CodeMirror-lines {
|
||||
padding: 4px 0; /* Vertical padding around content */
|
||||
padding: 4px 0; /* Vertical padding around content */
|
||||
}
|
||||
.CodeMirror pre.CodeMirror-line,
|
||||
.CodeMirror pre.CodeMirror-line-like {
|
||||
padding: 0 4px; /* Horizontal padding of content */
|
||||
padding: 0 4px; /* Horizontal padding of content */
|
||||
}
|
||||
|
||||
.CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler {
|
||||
background-color: rgba(255,255,255,0.8); /* The little square between H and V scrollbars */
|
||||
.CodeMirror-scrollbar-filler,
|
||||
.CodeMirror-gutter-filler {
|
||||
background-color: rgba(
|
||||
255,
|
||||
255,
|
||||
255,
|
||||
0.8
|
||||
); /* The little square between H and V scrollbars */
|
||||
}
|
||||
|
||||
/* GUTTER */
|
||||
|
||||
.CodeMirror-gutters {
|
||||
border-right: 1px solid #ddd;
|
||||
background-color: #ffffff80;
|
||||
white-space: nowrap;
|
||||
border-right: 1px solid #ddd;
|
||||
background-color: #ffffff80;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.CodeMirror-linenumbers {
|
||||
}
|
||||
.CodeMirror-linenumbers {}
|
||||
.CodeMirror-linenumber {
|
||||
padding: 0 3px 0 5px;
|
||||
min-width: 20px;
|
||||
white-space: nowrap;
|
||||
color: #000;
|
||||
font-size: 10px;
|
||||
height: 18px;
|
||||
line-height: 18px;
|
||||
text-align: center;
|
||||
padding: 0 3px 0 5px;
|
||||
min-width: 20px;
|
||||
white-space: nowrap;
|
||||
color: #000;
|
||||
font-size: 10px;
|
||||
height: 18px;
|
||||
line-height: 18px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.CodeMirror-guttermarker { color: black; }
|
||||
.CodeMirror-guttermarker-subtle { color: #999; }
|
||||
.CodeMirror-guttermarker {
|
||||
color: black;
|
||||
}
|
||||
.CodeMirror-guttermarker-subtle {
|
||||
color: #999;
|
||||
}
|
||||
|
||||
/* CURSOR */
|
||||
|
||||
.CodeMirror-cursor {
|
||||
border-left: 1px solid white;
|
||||
border-right: none;
|
||||
width: 0;
|
||||
border-left: 1px solid white;
|
||||
border-right: none;
|
||||
width: 0;
|
||||
}
|
||||
/* Shown when moving in bi-directional text */
|
||||
.CodeMirror div.CodeMirror-secondarycursor {
|
||||
border-left: 1px solid silver;
|
||||
border-left: 1px solid silver;
|
||||
}
|
||||
.cm-fat-cursor .CodeMirror-cursor {
|
||||
width: auto;
|
||||
border: 0 !important;
|
||||
background: #7e7;
|
||||
width: auto;
|
||||
border: 0 !important;
|
||||
background: #7e7;
|
||||
}
|
||||
.cm-fat-cursor div.CodeMirror-cursors {
|
||||
z-index: 1;
|
||||
z-index: 1;
|
||||
}
|
||||
.cm-fat-cursor-mark {
|
||||
background-color: rgba(20, 255, 20, 0.5);
|
||||
-webkit-animation: blink 1.06s steps(1) infinite;
|
||||
-moz-animation: blink 1.06s steps(1) infinite;
|
||||
animation: blink 1.06s steps(1) infinite;
|
||||
background-color: rgba(20, 255, 20, 0.5);
|
||||
-webkit-animation: blink 1.06s steps(1) infinite;
|
||||
-moz-animation: blink 1.06s steps(1) infinite;
|
||||
animation: blink 1.06s steps(1) infinite;
|
||||
}
|
||||
.cm-animate-fat-cursor {
|
||||
width: auto;
|
||||
border: 0;
|
||||
-webkit-animation: blink 1.06s steps(1) infinite;
|
||||
-moz-animation: blink 1.06s steps(1) infinite;
|
||||
animation: blink 1.06s steps(1) infinite;
|
||||
background-color: #7e7;
|
||||
width: auto;
|
||||
border: 0;
|
||||
-webkit-animation: blink 1.06s steps(1) infinite;
|
||||
-moz-animation: blink 1.06s steps(1) infinite;
|
||||
animation: blink 1.06s steps(1) infinite;
|
||||
background-color: #7e7;
|
||||
}
|
||||
@-moz-keyframes blink {
|
||||
0% {}
|
||||
50% { background-color: transparent; }
|
||||
100% {}
|
||||
0% {
|
||||
}
|
||||
50% {
|
||||
background-color: transparent;
|
||||
}
|
||||
100% {
|
||||
}
|
||||
}
|
||||
@-webkit-keyframes blink {
|
||||
0% {}
|
||||
50% { background-color: transparent; }
|
||||
100% {}
|
||||
0% {
|
||||
}
|
||||
50% {
|
||||
background-color: transparent;
|
||||
}
|
||||
100% {
|
||||
}
|
||||
}
|
||||
@keyframes blink {
|
||||
0% {}
|
||||
50% { background-color: transparent; }
|
||||
100% {}
|
||||
0% {
|
||||
}
|
||||
50% {
|
||||
background-color: transparent;
|
||||
}
|
||||
100% {
|
||||
}
|
||||
}
|
||||
|
||||
/* Can style cursor different in overwrite (non-insert) mode */
|
||||
.CodeMirror-overwrite .CodeMirror-cursor {}
|
||||
.CodeMirror-overwrite .CodeMirror-cursor {
|
||||
}
|
||||
|
||||
.cm-tab { display: inline-block; text-decoration: inherit; }
|
||||
.cm-tab {
|
||||
display: inline-block;
|
||||
text-decoration: inherit;
|
||||
}
|
||||
|
||||
.CodeMirror-rulers {
|
||||
position: absolute;
|
||||
left: 0; right: 0; top: -50px; bottom: 0;
|
||||
overflow: hidden;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
top: -50px;
|
||||
bottom: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
.CodeMirror-ruler {
|
||||
border-left: 1px solid #ccc;
|
||||
top: 0; bottom: 0;
|
||||
position: absolute;
|
||||
border-left: 1px solid #ccc;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
/* DEFAULT THEME */
|
||||
|
||||
.cm-s-default .cm-header {color: #fff;}
|
||||
.cm-s-default .cm-quote {color: #fff;}
|
||||
.cm-negative {color: #fff;}
|
||||
.cm-positive {color: #fff;}
|
||||
.cm-header, .cm-strong {font-weight: bold;}
|
||||
.cm-em {font-style: italic;}
|
||||
.cm-link {text-decoration: underline;}
|
||||
.cm-strikethrough {text-decoration: line-through;}
|
||||
.cm-s-default .cm-header {
|
||||
color: #fff;
|
||||
}
|
||||
.cm-s-default .cm-quote {
|
||||
color: #fff;
|
||||
}
|
||||
.cm-negative {
|
||||
color: #fff;
|
||||
}
|
||||
.cm-positive {
|
||||
color: #fff;
|
||||
}
|
||||
.cm-header,
|
||||
.cm-strong {
|
||||
font-weight: bold;
|
||||
}
|
||||
.cm-em {
|
||||
font-style: italic;
|
||||
}
|
||||
.cm-link {
|
||||
text-decoration: underline;
|
||||
}
|
||||
.cm-strikethrough {
|
||||
text-decoration: line-through;
|
||||
}
|
||||
|
||||
.cm-s-default .cm-keyword {color: #fff;}
|
||||
.cm-s-default .cm-atom {color: #fff;}
|
||||
.cm-s-default .cm-number {color: #fff;}
|
||||
.cm-s-default .cm-def {color: #fff;}
|
||||
.cm-s-default .cm-keyword {
|
||||
color: #fff;
|
||||
}
|
||||
.cm-s-default .cm-atom {
|
||||
color: #fff;
|
||||
}
|
||||
.cm-s-default .cm-number {
|
||||
color: #fff;
|
||||
}
|
||||
.cm-s-default .cm-def {
|
||||
color: #fff;
|
||||
}
|
||||
.cm-s-default .cm-variable,
|
||||
.cm-s-default .cm-punctuation,
|
||||
.cm-s-default .cm-property,
|
||||
.cm-s-default .cm-operator {}
|
||||
.cm-s-default .cm-variable-2 {color: #fff;}
|
||||
.cm-s-default .cm-variable-3, .cm-s-default .cm-type {color: #fff;}
|
||||
.cm-s-default .cm-comment {color: #fff;}
|
||||
.cm-s-default .cm-string {color: #fff;}
|
||||
.cm-s-default .cm-string-2 {color: #fff;}
|
||||
.cm-s-default .cm-meta {color: #fff;}
|
||||
.cm-s-default .cm-qualifier {color: #fff;}
|
||||
.cm-s-default .cm-builtin {color: #fff;}
|
||||
.cm-s-default .cm-bracket {color: #fff;}
|
||||
.cm-s-default .cm-tag {color: #fff;}
|
||||
.cm-s-default .cm-attribute {color: #fff;}
|
||||
.cm-s-default .cm-hr {color: #fff;}
|
||||
.cm-s-default .cm-link {color: #fff;}
|
||||
.cm-s-default .cm-operator {
|
||||
}
|
||||
.cm-s-default .cm-variable-2 {
|
||||
color: #fff;
|
||||
}
|
||||
.cm-s-default .cm-variable-3,
|
||||
.cm-s-default .cm-type {
|
||||
color: #fff;
|
||||
}
|
||||
.cm-s-default .cm-comment {
|
||||
color: #fff;
|
||||
}
|
||||
.cm-s-default .cm-string {
|
||||
color: #fff;
|
||||
}
|
||||
.cm-s-default .cm-string-2 {
|
||||
color: #fff;
|
||||
}
|
||||
.cm-s-default .cm-meta {
|
||||
color: #fff;
|
||||
}
|
||||
.cm-s-default .cm-qualifier {
|
||||
color: #fff;
|
||||
}
|
||||
.cm-s-default .cm-builtin {
|
||||
color: #fff;
|
||||
}
|
||||
.cm-s-default .cm-bracket {
|
||||
color: #fff;
|
||||
}
|
||||
.cm-s-default .cm-tag {
|
||||
color: #fff;
|
||||
}
|
||||
.cm-s-default .cm-attribute {
|
||||
color: #fff;
|
||||
}
|
||||
.cm-s-default .cm-hr {
|
||||
color: #fff;
|
||||
}
|
||||
.cm-s-default .cm-link {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.cm-s-default .cm-error {color: #fff;}
|
||||
.cm-invalidchar {color: #fff;}
|
||||
.cm-s-default .cm-error {
|
||||
color: #fff;
|
||||
}
|
||||
.cm-invalidchar {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.CodeMirror-composing { border-bottom: 2px solid; }
|
||||
.CodeMirror-composing {
|
||||
border-bottom: 2px solid;
|
||||
}
|
||||
|
||||
/* Default styles for common addons */
|
||||
|
||||
div.CodeMirror span.CodeMirror-matchingbracket {color: #fff;}
|
||||
div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #fff;}
|
||||
.CodeMirror-matchingtag { background: rgba(255, 150, 0, .3); }
|
||||
.CodeMirror-activeline-background {background: #e8f2ff;}
|
||||
div.CodeMirror span.CodeMirror-matchingbracket {
|
||||
color: #fff;
|
||||
}
|
||||
div.CodeMirror span.CodeMirror-nonmatchingbracket {
|
||||
color: #fff;
|
||||
}
|
||||
.CodeMirror-matchingtag {
|
||||
background: rgba(255, 150, 0, 0.3);
|
||||
}
|
||||
.CodeMirror-activeline-background {
|
||||
background: #e8f2ff;
|
||||
}
|
||||
|
||||
/* STOP */
|
||||
|
||||
@@ -162,130 +260,153 @@ div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #fff;}
|
||||
the editor. You probably shouldn't touch them. */
|
||||
|
||||
.CodeMirror {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.CodeMirror-scroll {
|
||||
overflow: scroll !important; /* Things will break if this is overridden */
|
||||
/* 50px is the magic margin used to hide the element's real scrollbars */
|
||||
/* See overflow: hidden in .CodeMirror */
|
||||
margin-bottom: -50px; margin-right: -50px;
|
||||
padding-bottom: 50px;
|
||||
height: 100%;
|
||||
outline: none; /* Prevent dragging from highlighting the element */
|
||||
position: relative;
|
||||
overflow: scroll !important; /* Things will break if this is overridden */
|
||||
/* 50px is the magic margin used to hide the element's real scrollbars */
|
||||
/* See overflow: hidden in .CodeMirror */
|
||||
margin-bottom: -50px;
|
||||
margin-right: -50px;
|
||||
padding-bottom: 50px;
|
||||
height: 100%;
|
||||
outline: none; /* Prevent dragging from highlighting the element */
|
||||
position: relative;
|
||||
}
|
||||
.CodeMirror-sizer {
|
||||
position: relative;
|
||||
border-right: 50px solid transparent;
|
||||
position: relative;
|
||||
border-right: 50px solid transparent;
|
||||
}
|
||||
|
||||
/* The fake, visible scrollbars. Used to force redraw during scrolling
|
||||
before actual scrolling happens, thus preventing shaking and
|
||||
flickering artifacts. */
|
||||
.CodeMirror-vscrollbar, .CodeMirror-hscrollbar, .CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler {
|
||||
position: absolute;
|
||||
z-index: 6;
|
||||
display: none;
|
||||
.CodeMirror-vscrollbar,
|
||||
.CodeMirror-hscrollbar,
|
||||
.CodeMirror-scrollbar-filler,
|
||||
.CodeMirror-gutter-filler {
|
||||
position: absolute;
|
||||
z-index: 6;
|
||||
display: none;
|
||||
}
|
||||
.CodeMirror-vscrollbar {
|
||||
right: 0; top: 0;
|
||||
overflow-x: hidden;
|
||||
overflow-y: scroll;
|
||||
right: 0;
|
||||
top: 0;
|
||||
overflow-x: hidden;
|
||||
overflow-y: scroll;
|
||||
}
|
||||
.CodeMirror-hscrollbar {
|
||||
bottom: 0; left: 0;
|
||||
overflow-y: hidden;
|
||||
overflow-x: scroll;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
overflow-y: hidden;
|
||||
overflow-x: scroll;
|
||||
}
|
||||
.CodeMirror-scrollbar-filler {
|
||||
right: 0; bottom: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
}
|
||||
.CodeMirror-gutter-filler {
|
||||
left: 0; bottom: 0;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
}
|
||||
|
||||
.CodeMirror-gutters {
|
||||
position: absolute; left: 0; top: 0;
|
||||
min-height: 100%;
|
||||
z-index: 3;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
min-height: 100%;
|
||||
z-index: 3;
|
||||
}
|
||||
.CodeMirror-gutter {
|
||||
white-space: normal;
|
||||
height: 100%;
|
||||
display: inline-block;
|
||||
vertical-align: top;
|
||||
margin-bottom: -50px;
|
||||
white-space: normal;
|
||||
height: 100%;
|
||||
display: inline-block;
|
||||
vertical-align: top;
|
||||
margin-bottom: -50px;
|
||||
}
|
||||
.CodeMirror-gutter-wrapper {
|
||||
position: absolute;
|
||||
z-index: 4;
|
||||
background: none !important;
|
||||
border: none !important;
|
||||
position: absolute;
|
||||
z-index: 4;
|
||||
background: none !important;
|
||||
border: none !important;
|
||||
}
|
||||
.CodeMirror-gutter-background {
|
||||
position: absolute;
|
||||
top: 0; bottom: 0;
|
||||
z-index: 4;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
z-index: 4;
|
||||
}
|
||||
.CodeMirror-gutter-elt {
|
||||
position: absolute;
|
||||
cursor: default;
|
||||
z-index: 4;
|
||||
position: absolute;
|
||||
cursor: default;
|
||||
z-index: 4;
|
||||
}
|
||||
.CodeMirror-gutter-wrapper ::selection {
|
||||
background-color: transparent;
|
||||
}
|
||||
.CodeMirror-gutter-wrapper ::-moz-selection {
|
||||
background-color: transparent;
|
||||
}
|
||||
.CodeMirror-gutter-wrapper ::selection { background-color: transparent }
|
||||
.CodeMirror-gutter-wrapper ::-moz-selection { background-color: transparent }
|
||||
|
||||
.CodeMirror-lines {
|
||||
cursor: text;
|
||||
min-height: 1px; /* prevents collapsing before first draw */
|
||||
cursor: text;
|
||||
min-height: 1px; /* prevents collapsing before first draw */
|
||||
}
|
||||
.CodeMirror pre.CodeMirror-line,
|
||||
.CodeMirror pre.CodeMirror-line-like {
|
||||
/* Reset some styles that the rest of the page might have set */
|
||||
-moz-border-radius: 0; -webkit-border-radius: 0; border-radius: 0;
|
||||
border-width: 0;
|
||||
background: transparent;
|
||||
font-family: inherit;
|
||||
font-size: inherit;
|
||||
margin: 0;
|
||||
white-space: pre;
|
||||
word-wrap: normal;
|
||||
line-height: inherit;
|
||||
color: inherit;
|
||||
z-index: 2;
|
||||
position: relative;
|
||||
overflow: visible;
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
-webkit-font-variant-ligatures: contextual;
|
||||
font-variant-ligatures: contextual;
|
||||
/* Reset some styles that the rest of the page might have set */
|
||||
-moz-border-radius: 0;
|
||||
-webkit-border-radius: 0;
|
||||
border-radius: 0;
|
||||
border-width: 0;
|
||||
background: transparent;
|
||||
font-family: inherit;
|
||||
font-size: inherit;
|
||||
margin: 0;
|
||||
white-space: pre;
|
||||
word-wrap: normal;
|
||||
line-height: inherit;
|
||||
color: inherit;
|
||||
z-index: 2;
|
||||
position: relative;
|
||||
overflow: visible;
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
-webkit-font-variant-ligatures: contextual;
|
||||
font-variant-ligatures: contextual;
|
||||
}
|
||||
.CodeMirror-wrap pre.CodeMirror-line,
|
||||
.CodeMirror-wrap pre.CodeMirror-line-like {
|
||||
word-wrap: break-word;
|
||||
white-space: pre-wrap;
|
||||
word-break: normal;
|
||||
word-wrap: break-word;
|
||||
white-space: pre-wrap;
|
||||
word-break: normal;
|
||||
}
|
||||
|
||||
.CodeMirror-linebackground {
|
||||
position: absolute;
|
||||
left: 0; right: 0; top: 0; bottom: 0;
|
||||
z-index: 0;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
.CodeMirror-linewidget {
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
padding: 0.1px; /* Force widget margins to stay inside of the container */
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
padding: 0.1px; /* Force widget margins to stay inside of the container */
|
||||
}
|
||||
|
||||
.CodeMirror-widget {}
|
||||
.CodeMirror-widget {
|
||||
}
|
||||
|
||||
.CodeMirror-rtl pre { direction: rtl; }
|
||||
.CodeMirror-rtl pre {
|
||||
direction: rtl;
|
||||
}
|
||||
|
||||
.CodeMirror-code {
|
||||
outline: none;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
/* Force content-box sizing for the elements where we expect it */
|
||||
@@ -294,60 +415,82 @@ div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #fff;}
|
||||
.CodeMirror-gutter,
|
||||
.CodeMirror-gutters,
|
||||
.CodeMirror-linenumber {
|
||||
-moz-box-sizing: content-box;
|
||||
box-sizing: content-box;
|
||||
-moz-box-sizing: content-box;
|
||||
box-sizing: content-box;
|
||||
}
|
||||
|
||||
.CodeMirror-measure {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 0;
|
||||
overflow: hidden;
|
||||
visibility: hidden;
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 0;
|
||||
overflow: hidden;
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.CodeMirror-cursor {
|
||||
position: absolute;
|
||||
pointer-events: none;
|
||||
position: absolute;
|
||||
pointer-events: none;
|
||||
}
|
||||
.CodeMirror-measure pre {
|
||||
position: static;
|
||||
}
|
||||
.CodeMirror-measure pre { position: static; }
|
||||
|
||||
div.CodeMirror-cursors {
|
||||
visibility: hidden;
|
||||
position: relative;
|
||||
z-index: 3;
|
||||
visibility: hidden;
|
||||
position: relative;
|
||||
z-index: 3;
|
||||
}
|
||||
div.CodeMirror-dragcursors {
|
||||
visibility: visible;
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
.CodeMirror-focused div.CodeMirror-cursors {
|
||||
visibility: visible;
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
.CodeMirror-selected { background: #d9d9d9; }
|
||||
.CodeMirror-focused .CodeMirror-selected { background: #d7d4f0; }
|
||||
.CodeMirror-crosshair { cursor: crosshair; }
|
||||
.CodeMirror-line::selection, .CodeMirror-line > span::selection, .CodeMirror-line > span > span::selection { background: #d7d4f0; }
|
||||
.CodeMirror-line::-moz-selection, .CodeMirror-line > span::-moz-selection, .CodeMirror-line > span > span::-moz-selection { background: #d7d4f0; }
|
||||
.CodeMirror-selected {
|
||||
background: #d9d9d9;
|
||||
}
|
||||
.CodeMirror-focused .CodeMirror-selected {
|
||||
background: #d7d4f0;
|
||||
}
|
||||
.CodeMirror-crosshair {
|
||||
cursor: crosshair;
|
||||
}
|
||||
.CodeMirror-line::selection,
|
||||
.CodeMirror-line > span::selection,
|
||||
.CodeMirror-line > span > span::selection {
|
||||
background: #d7d4f0;
|
||||
}
|
||||
.CodeMirror-line::-moz-selection,
|
||||
.CodeMirror-line > span::-moz-selection,
|
||||
.CodeMirror-line > span > span::-moz-selection {
|
||||
background: #d7d4f0;
|
||||
}
|
||||
|
||||
.cm-searching {
|
||||
background-color: #ffa;
|
||||
background-color: rgba(255, 255, 0, .4);
|
||||
background-color: #ffa;
|
||||
background-color: rgba(255, 255, 0, 0.4);
|
||||
}
|
||||
|
||||
/* Used to force a border model for a node */
|
||||
.cm-force-border { padding-right: .1px; }
|
||||
.cm-force-border {
|
||||
padding-right: 0.1px;
|
||||
}
|
||||
|
||||
@media print {
|
||||
/* Hide the cursor when printing */
|
||||
.CodeMirror div.CodeMirror-cursors {
|
||||
visibility: hidden;
|
||||
}
|
||||
/* Hide the cursor when printing */
|
||||
.CodeMirror div.CodeMirror-cursors {
|
||||
visibility: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
/* See issue #2901 */
|
||||
.cm-tab-wrap-hack:after { content: ''; }
|
||||
.cm-tab-wrap-hack:after {
|
||||
content: "";
|
||||
}
|
||||
|
||||
/* Help users use markselection to safely style text background */
|
||||
span.CodeMirror-selectedtext { background: none; }
|
||||
span.CodeMirror-selectedtext {
|
||||
background: none;
|
||||
}
|
||||
|
||||
@@ -52,6 +52,7 @@ import Trace from "./Trace/Trace";
|
||||
import LogsMain from "./Logs/LogsMain";
|
||||
import Heal from "./Heal/Heal";
|
||||
import Watch from "./Watch/Watch";
|
||||
import HealthInfo from "./HealthInfo/HealthInfo";
|
||||
|
||||
const drawerWidth = 245;
|
||||
|
||||
@@ -288,6 +289,10 @@ const Console = ({
|
||||
component: LogsMain,
|
||||
path: "/logs",
|
||||
},
|
||||
{
|
||||
component: HealthInfo,
|
||||
path: "/health-info",
|
||||
},
|
||||
{
|
||||
component: ConfigurationMain,
|
||||
path: "/settings",
|
||||
|
||||
253
portal-ui/src/screens/Console/HealthInfo/HealthInfo.tsx
Normal file
253
portal-ui/src/screens/Console/HealthInfo/HealthInfo.tsx
Normal file
@@ -0,0 +1,253 @@
|
||||
// This file is part of MinIO Console Server
|
||||
// Copyright (c) 2021 MinIO, Inc.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
import React, { useState, useEffect } from "react";
|
||||
import {
|
||||
IMessageEvent,
|
||||
w3cwebsocket as W3CWebSocket,
|
||||
ICloseEvent,
|
||||
} from "websocket";
|
||||
import { AppState } from "../../../store";
|
||||
import { connect } from "react-redux";
|
||||
import { healthInfoMessageReceived, healthInfoResetMessage } from "./actions";
|
||||
import {
|
||||
HealthInfoMessage,
|
||||
DiagStatInProgress,
|
||||
DiagStatSuccess,
|
||||
DiagStatError,
|
||||
} from "./types";
|
||||
import { createStyles, Theme, withStyles } from "@material-ui/core/styles";
|
||||
import {
|
||||
wsProtocol,
|
||||
WSCloseInternalServerErr,
|
||||
WSClosePolicyViolation,
|
||||
} from "../../../utils/wsUtils";
|
||||
import {
|
||||
actionsTray,
|
||||
containerForHeader,
|
||||
} from "../Common/FormComponents/common/styleLibrary";
|
||||
import { Grid, Button } from "@material-ui/core";
|
||||
import PageHeader from "../Common/PageHeader/PageHeader";
|
||||
import { setSnackBarMessage, setServerDiagStat } from "../../../actions";
|
||||
|
||||
const styles = (theme: Theme) =>
|
||||
createStyles({
|
||||
logList: {
|
||||
background: "#fff",
|
||||
minHeight: 400,
|
||||
height: "calc(100vh - 270px)",
|
||||
overflow: "auto",
|
||||
fontSize: 13,
|
||||
padding: "25px 45px",
|
||||
border: "1px solid #EAEDEE",
|
||||
borderRadius: 4,
|
||||
},
|
||||
...actionsTray,
|
||||
...containerForHeader(theme.spacing(4)),
|
||||
});
|
||||
|
||||
const download = (filename: string, text: string) => {
|
||||
let element = document.createElement("a");
|
||||
element.setAttribute(
|
||||
"href",
|
||||
"data:text/plain;charset=utf-8," + encodeURIComponent(text)
|
||||
);
|
||||
element.setAttribute("download", filename);
|
||||
|
||||
element.style.display = "none";
|
||||
document.body.appendChild(element);
|
||||
|
||||
element.click();
|
||||
|
||||
document.body.removeChild(element);
|
||||
};
|
||||
|
||||
interface IHealthInfo {
|
||||
classes: any;
|
||||
healthInfoMessageReceived: typeof healthInfoMessageReceived;
|
||||
healthInfoResetMessage: typeof healthInfoResetMessage;
|
||||
message: HealthInfoMessage;
|
||||
namespace: string;
|
||||
tenant: string;
|
||||
setSnackBarMessage: typeof setSnackBarMessage;
|
||||
setServerDiagStat: typeof setServerDiagStat;
|
||||
serverDiagnosticStatus: string;
|
||||
}
|
||||
|
||||
const HealthInfo = ({
|
||||
classes,
|
||||
healthInfoMessageReceived,
|
||||
healthInfoResetMessage,
|
||||
message,
|
||||
setSnackBarMessage,
|
||||
setServerDiagStat,
|
||||
serverDiagnosticStatus,
|
||||
}: IHealthInfo) => {
|
||||
// TODO: Diagnostic button states should be global so that they are not overwritten
|
||||
const [startDiagnostic, setStartDiagnostic] = useState(false);
|
||||
const [downloadDisabled, setDownloadDisabled] = useState(true);
|
||||
|
||||
useEffect(() => {
|
||||
if (
|
||||
serverDiagnosticStatus === DiagStatSuccess &&
|
||||
message !== ({} as HealthInfoMessage)
|
||||
) {
|
||||
// Allow download of diagnostics file only when
|
||||
// it succeded fetching all the results and info is not empty.
|
||||
setDownloadDisabled(false);
|
||||
setStartDiagnostic(false);
|
||||
}
|
||||
if (serverDiagnosticStatus === DiagStatInProgress) {
|
||||
// Disable Start Diagnotic and Disable Download buttons
|
||||
// if a Diagnosis is in progress.
|
||||
setStartDiagnostic(false);
|
||||
setDownloadDisabled(true);
|
||||
}
|
||||
}, [serverDiagnosticStatus, message]);
|
||||
|
||||
useEffect(() => {
|
||||
if (startDiagnostic) {
|
||||
healthInfoResetMessage();
|
||||
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/health-info?deadline=1h`
|
||||
);
|
||||
|
||||
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);
|
||||
setSnackBarMessage(
|
||||
"Diagnostic started. Please do not refresh page during diagnosis."
|
||||
);
|
||||
setServerDiagStat(DiagStatInProgress);
|
||||
};
|
||||
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) => {
|
||||
console.log("error, closing websocket:", error.message);
|
||||
c.close(1000);
|
||||
clearInterval(interval);
|
||||
setServerDiagStat(DiagStatError);
|
||||
};
|
||||
c.onclose = (event: ICloseEvent) => {
|
||||
clearInterval(interval);
|
||||
if (
|
||||
event.code === WSCloseInternalServerErr ||
|
||||
event.code === WSClosePolicyViolation
|
||||
) {
|
||||
// handle close with error
|
||||
console.log("connection closed by server with code:", event.code);
|
||||
setSnackBarMessage(
|
||||
"An error occurred while getting Diagnostic file."
|
||||
);
|
||||
setServerDiagStat(DiagStatError);
|
||||
} else {
|
||||
console.log("connection closed by server");
|
||||
setSnackBarMessage("Diagnostic file is ready to be downloaded.");
|
||||
setServerDiagStat(DiagStatSuccess);
|
||||
}
|
||||
};
|
||||
}
|
||||
} else {
|
||||
// reset start status
|
||||
setStartDiagnostic(false);
|
||||
}
|
||||
}, [
|
||||
healthInfoMessageReceived,
|
||||
healthInfoResetMessage,
|
||||
startDiagnostic,
|
||||
setSnackBarMessage,
|
||||
setServerDiagStat,
|
||||
]);
|
||||
|
||||
const renderResponse = (data: HealthInfoMessage) => {
|
||||
// render response in json format
|
||||
let str = JSON.stringify(data, null, 2);
|
||||
if (str === "{}") {
|
||||
str = "";
|
||||
}
|
||||
return <pre>{str}</pre>;
|
||||
};
|
||||
return (
|
||||
<React.Fragment>
|
||||
<PageHeader label="Diagnostic" />
|
||||
<Grid container>
|
||||
<Grid item xs={12} className={classes.container}>
|
||||
<Grid container justify="flex-end" spacing={2}>
|
||||
<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>
|
||||
<Button
|
||||
type="submit"
|
||||
variant="contained"
|
||||
color="primary"
|
||||
onClick={() => {
|
||||
download("diagnostic.json", JSON.stringify(message, null, 2));
|
||||
}}
|
||||
disabled={downloadDisabled}
|
||||
>
|
||||
Download
|
||||
</Button>
|
||||
</Grid>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<br />
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<div className={classes.logList}>
|
||||
<pre>{renderResponse(message)}</pre>
|
||||
</div>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</React.Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
const mapState = (state: AppState) => ({
|
||||
message: state.healthInfo.message,
|
||||
serverDiagnosticStatus: state.system.serverDiagnosticStatus,
|
||||
});
|
||||
|
||||
const connector = connect(mapState, {
|
||||
healthInfoMessageReceived: healthInfoMessageReceived,
|
||||
healthInfoResetMessage: healthInfoResetMessage,
|
||||
setSnackBarMessage,
|
||||
setServerDiagStat,
|
||||
});
|
||||
|
||||
export default connector(withStyles(styles)(HealthInfo));
|
||||
46
portal-ui/src/screens/Console/HealthInfo/actions.ts
Normal file
46
portal-ui/src/screens/Console/HealthInfo/actions.ts
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 { HealthInfoMessage } from "./types";
|
||||
|
||||
export const HEALTH_INFO_MESSAGE_RECEIVED = "HEALTH_INFO_MESSAGE_RECEIVED";
|
||||
export const HEALTH_INFO_RESET_MESSAGE = "HEALTH_INFO_RESET_MESSAGE";
|
||||
|
||||
interface HealthInfoMessageReceivedAction {
|
||||
type: typeof HEALTH_INFO_MESSAGE_RECEIVED;
|
||||
message: HealthInfoMessage;
|
||||
}
|
||||
|
||||
interface HealthInfoResetMessagesAction {
|
||||
type: typeof HEALTH_INFO_RESET_MESSAGE;
|
||||
}
|
||||
|
||||
export type HealthInfoActionTypes =
|
||||
| HealthInfoMessageReceivedAction
|
||||
| HealthInfoResetMessagesAction;
|
||||
|
||||
export function healthInfoMessageReceived(message: HealthInfoMessage) {
|
||||
return {
|
||||
type: HEALTH_INFO_MESSAGE_RECEIVED,
|
||||
message: message,
|
||||
};
|
||||
}
|
||||
|
||||
export function healthInfoResetMessage() {
|
||||
return {
|
||||
type: HEALTH_INFO_RESET_MESSAGE,
|
||||
};
|
||||
}
|
||||
50
portal-ui/src/screens/Console/HealthInfo/reducers.ts
Normal file
50
portal-ui/src/screens/Console/HealthInfo/reducers.ts
Normal file
@@ -0,0 +1,50 @@
|
||||
// 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 {
|
||||
HEALTH_INFO_MESSAGE_RECEIVED,
|
||||
HEALTH_INFO_RESET_MESSAGE,
|
||||
HealthInfoActionTypes,
|
||||
} from "./actions";
|
||||
import { HealthInfoMessage } from "./types";
|
||||
|
||||
export interface HealthInfoState {
|
||||
message: HealthInfoMessage;
|
||||
}
|
||||
|
||||
const initialState: HealthInfoState = {
|
||||
message: {} as HealthInfoMessage,
|
||||
};
|
||||
|
||||
export function healthInfoReducer(
|
||||
state = initialState,
|
||||
action: HealthInfoActionTypes
|
||||
): HealthInfoState {
|
||||
switch (action.type) {
|
||||
case HEALTH_INFO_MESSAGE_RECEIVED:
|
||||
return {
|
||||
...state,
|
||||
message: action.message,
|
||||
};
|
||||
case HEALTH_INFO_RESET_MESSAGE:
|
||||
return {
|
||||
...state,
|
||||
message: {} as HealthInfoMessage,
|
||||
};
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
}
|
||||
519
portal-ui/src/screens/Console/HealthInfo/types.ts
Normal file
519
portal-ui/src/screens/Console/HealthInfo/types.ts
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/>.
|
||||
|
||||
export const DiagStatError = "error";
|
||||
export const DiagStatSuccess = "success";
|
||||
export const DiagStatInProgress = "inProgress";
|
||||
export interface HealthInfoMessage {
|
||||
timestamp: Date;
|
||||
error: string;
|
||||
perf: perfInfo;
|
||||
minio: minioHealthInfo;
|
||||
sys: sysHealthInfo;
|
||||
}
|
||||
|
||||
export interface perfInfo {
|
||||
drives: serverDrivesInfo[];
|
||||
net: serverNetHealthInfo[];
|
||||
net_parallel: serverNetHealthInfo;
|
||||
error: string;
|
||||
}
|
||||
|
||||
export interface serverDrivesInfo {
|
||||
addr: string;
|
||||
serial: drivePerfInfo[];
|
||||
parallel: drivePerfInfo[];
|
||||
error: string;
|
||||
}
|
||||
|
||||
export interface drivePerfInfo {
|
||||
endpoint: string;
|
||||
latency: diskLatency;
|
||||
throughput: diskThroughput;
|
||||
error: string;
|
||||
}
|
||||
export interface diskLatency {
|
||||
avg_secs: number;
|
||||
percentile50_secs: number;
|
||||
percentile90_secs: number;
|
||||
percentile99_secs: number;
|
||||
min_secs: number;
|
||||
max_secs: number;
|
||||
}
|
||||
|
||||
export interface diskThroughput {
|
||||
avg_bytes_per_sec: number;
|
||||
percentile50_bytes_per_sec: number;
|
||||
percentile90_bytes_per_sec: number;
|
||||
percentile99_bytes_per_sec: number;
|
||||
min_bytes_per_sec: number;
|
||||
max_bytes_per_sec: number;
|
||||
}
|
||||
|
||||
export interface serverNetHealthInfo {
|
||||
addr: string;
|
||||
net: netPerfInfo[];
|
||||
error: string;
|
||||
}
|
||||
|
||||
export interface netPerfInfo {
|
||||
remote: string;
|
||||
latency: netLatency;
|
||||
throughput: netThroughput;
|
||||
error: string;
|
||||
}
|
||||
|
||||
export interface netLatency {
|
||||
avg_secs: number;
|
||||
percentile50_secs: number;
|
||||
percentile90_secs: number;
|
||||
percentile99_secs: number;
|
||||
min_secs: number;
|
||||
max_secs: number;
|
||||
}
|
||||
export interface netThroughput {
|
||||
avg_bytes_per_sec: number;
|
||||
percentile50_bytes_per_sec: number;
|
||||
percentile90_bytes_per_sec: number;
|
||||
percentile99_bytes_per_sec: number;
|
||||
min_bytes_per_sec: number;
|
||||
max_bytes_per_sec: number;
|
||||
}
|
||||
|
||||
export interface minioHealthInfo {
|
||||
info: infoMessage;
|
||||
config: any;
|
||||
error: string;
|
||||
}
|
||||
|
||||
export interface infoMessage {
|
||||
mode: string;
|
||||
domain: string[];
|
||||
region: string;
|
||||
sqsARN: string[];
|
||||
deploymentID: string;
|
||||
buckets: buckets;
|
||||
objects: objects;
|
||||
usage: usage;
|
||||
services: services;
|
||||
backend: any;
|
||||
servers: serverProperties[];
|
||||
}
|
||||
|
||||
export interface buckets {
|
||||
count: number;
|
||||
}
|
||||
|
||||
export interface objects {
|
||||
count: number;
|
||||
}
|
||||
|
||||
export interface usage {
|
||||
size: number;
|
||||
}
|
||||
|
||||
export interface services {
|
||||
vault: vault;
|
||||
ldap: ldap;
|
||||
logger: Map<string, status[]>[];
|
||||
audit: Map<string, status[]>[];
|
||||
notifications: Map<string, Map<string, status[]>[]>;
|
||||
}
|
||||
|
||||
export interface vault {
|
||||
status: string;
|
||||
encrypt: string;
|
||||
decrypt: string;
|
||||
}
|
||||
|
||||
export interface ldap {
|
||||
status: string;
|
||||
}
|
||||
|
||||
export interface status {
|
||||
status: string;
|
||||
}
|
||||
|
||||
export interface serverProperties {
|
||||
state: string;
|
||||
endpoint: string;
|
||||
uptime: number;
|
||||
version: string;
|
||||
commitID: string;
|
||||
network: Map<string, string>;
|
||||
drives: disk[];
|
||||
}
|
||||
|
||||
export interface disk {
|
||||
endpoint: string;
|
||||
rootDisk: boolean;
|
||||
path: string;
|
||||
healing: boolean;
|
||||
state: string;
|
||||
uuid: string;
|
||||
model: string;
|
||||
totalspace: number;
|
||||
usedspace: number;
|
||||
availspace: number;
|
||||
readthroughput: number;
|
||||
writethroughput: number;
|
||||
readlatency: number;
|
||||
writelatency: number;
|
||||
utilization: number;
|
||||
}
|
||||
|
||||
export interface sysHealthInfo {
|
||||
cpus: serverCpuInfo[];
|
||||
drives: serverDiskHwInfo[];
|
||||
osinfos: serverOsInfo[];
|
||||
meminfos: serverMemInfo[];
|
||||
procinfos: serverProcInfo[];
|
||||
error: string;
|
||||
}
|
||||
|
||||
export interface serverCpuInfo {
|
||||
addr: string;
|
||||
cpu: cpuInfoStat[];
|
||||
time: cpuTimeStat[];
|
||||
error: string;
|
||||
}
|
||||
|
||||
export interface cpuInfoStat {
|
||||
cpu: number;
|
||||
vendorId: string;
|
||||
family: string;
|
||||
model: string;
|
||||
stepping: number;
|
||||
physicalId: string;
|
||||
coreId: string;
|
||||
cores: number;
|
||||
modelName: string;
|
||||
mhz: number;
|
||||
cacheSize: number;
|
||||
flags: string[];
|
||||
microcode: string;
|
||||
}
|
||||
|
||||
export interface cpuTimeStat {
|
||||
cpu: string;
|
||||
user: number;
|
||||
system: number;
|
||||
idle: number;
|
||||
nice: number;
|
||||
iowait: number;
|
||||
irq: number;
|
||||
softirq: number;
|
||||
steal: number;
|
||||
guest: number;
|
||||
guestNice: number;
|
||||
}
|
||||
|
||||
export interface serverDiskHwInfo {
|
||||
addr: string;
|
||||
usages: diskUsageStat[];
|
||||
partitions: partitionStat[];
|
||||
counters: Map<string, diskIOCountersStat>;
|
||||
error: string;
|
||||
}
|
||||
|
||||
export interface diskUsageStat {
|
||||
path: string;
|
||||
fstype: string;
|
||||
total: number;
|
||||
free: number;
|
||||
used: number;
|
||||
usedPercent: number;
|
||||
inodesTotal: number;
|
||||
inodesUsed: number;
|
||||
inodesFree: number;
|
||||
inodesUsedPercent: number;
|
||||
}
|
||||
|
||||
export interface partitionStat {
|
||||
device: string;
|
||||
mountpoint: string;
|
||||
fstype: string;
|
||||
opts: string;
|
||||
smartInfo: smartInfo;
|
||||
}
|
||||
|
||||
export interface smartInfo {
|
||||
device: string;
|
||||
scsi: scsiInfo;
|
||||
nvme: nvmeInfo;
|
||||
ata: ataInfo;
|
||||
error: string;
|
||||
}
|
||||
|
||||
export interface scsiInfo {
|
||||
scsiCapacityBytes: number;
|
||||
scsiModeSenseBuf: string;
|
||||
scsirespLen: number;
|
||||
scsiBdLen: number;
|
||||
scsiOffset: number;
|
||||
sciRpm: number;
|
||||
}
|
||||
|
||||
export interface nvmeInfo {
|
||||
serialNum: string;
|
||||
vendorId: string;
|
||||
firmwareVersion: string;
|
||||
modelNum: string;
|
||||
spareAvailable: string;
|
||||
spareThreshold: string;
|
||||
temperature: string;
|
||||
criticalWarning: string;
|
||||
maxDataTransferPages: number;
|
||||
controllerBusyTime: number;
|
||||
powerOnHours: number;
|
||||
powerCycles: number;
|
||||
unsafeShutdowns: number;
|
||||
mediaAndDataIntgerityErrors: number;
|
||||
dataUnitsReadBytes: number;
|
||||
dataUnitsWrittenBytes: number;
|
||||
hostReadCommands: number;
|
||||
hostWriteCommands: number;
|
||||
}
|
||||
|
||||
export interface ataInfo {
|
||||
scsiLuWWNDeviceID: string;
|
||||
serialNum: string;
|
||||
modelNum: string;
|
||||
firmwareRevision: string;
|
||||
RotationRate: string;
|
||||
MajorVersion: string;
|
||||
MinorVersion: string;
|
||||
smartSupportAvailable: boolean;
|
||||
smartSupportEnabled: boolean;
|
||||
smartErrorLog: string;
|
||||
transport: string;
|
||||
}
|
||||
|
||||
export interface diskIOCountersStat {
|
||||
readCount: number;
|
||||
mergedReadCount: number;
|
||||
DriteCount: number;
|
||||
mergedWriteCount: number;
|
||||
readBytes: number;
|
||||
writeBytes: number;
|
||||
readTime: number;
|
||||
writeTime: number;
|
||||
iopsInProgress: number;
|
||||
ioTime: number;
|
||||
weightedIO: number;
|
||||
name: string;
|
||||
serialNumber: string;
|
||||
label: string;
|
||||
}
|
||||
|
||||
export interface serverOsInfo {
|
||||
addr: string;
|
||||
info: infoStat;
|
||||
sensors: temperatureStat[];
|
||||
users: userStat[];
|
||||
error: string;
|
||||
}
|
||||
|
||||
export interface infoStat {
|
||||
hostname: string;
|
||||
uptime: number;
|
||||
bootTime: number;
|
||||
procs: number;
|
||||
os: string;
|
||||
platform: string;
|
||||
platformFamily: string;
|
||||
platformVersion: string;
|
||||
kernelVersion: string;
|
||||
kernelArch: string;
|
||||
virtualizationSystem: string;
|
||||
virtualizationRole: string;
|
||||
hostid: string;
|
||||
}
|
||||
|
||||
export interface temperatureStat {
|
||||
sensorKey: string;
|
||||
sensorTemperature: number;
|
||||
}
|
||||
|
||||
export interface userStat {
|
||||
user: string;
|
||||
terminal: string;
|
||||
host: string;
|
||||
started: number;
|
||||
}
|
||||
|
||||
export interface serverMemInfo {
|
||||
addr: string;
|
||||
swap: swapMemoryStat;
|
||||
virtualmem: virtualMemoryStat;
|
||||
error: string;
|
||||
}
|
||||
|
||||
export interface swapMemoryStat {
|
||||
total: number;
|
||||
used: number;
|
||||
free: number;
|
||||
usedPercent: number;
|
||||
sin: number;
|
||||
sout: number;
|
||||
pgin: number;
|
||||
pgout: number;
|
||||
pgfault: number;
|
||||
pgmajfault: number;
|
||||
}
|
||||
|
||||
export interface virtualMemoryStat {
|
||||
total: number;
|
||||
available: number;
|
||||
used: number;
|
||||
usedPercent: number;
|
||||
free: number;
|
||||
active: number;
|
||||
inactive: number;
|
||||
wired: number;
|
||||
laundry: number;
|
||||
buffers: number;
|
||||
cached: number;
|
||||
writeback: number;
|
||||
dirty: number;
|
||||
writebacktmp: number;
|
||||
shared: number;
|
||||
slab: number;
|
||||
sreclaimable: number;
|
||||
sunreclaim: number;
|
||||
pagetables: number;
|
||||
swapcached: number;
|
||||
commitlimit: number;
|
||||
committedas: number;
|
||||
hightotal: number;
|
||||
highfree: number;
|
||||
lowtotal: number;
|
||||
lowfree: number;
|
||||
swaptotal: number;
|
||||
swapfree: number;
|
||||
mapped: number;
|
||||
vmalloctotal: number;
|
||||
vmallocused: number;
|
||||
vmallocchunk: number;
|
||||
hugepagestotal: number;
|
||||
hugepagesfree: number;
|
||||
hugepagesize: number;
|
||||
}
|
||||
|
||||
export interface serverProcInfo {
|
||||
addr: string;
|
||||
processes: sysProcess[];
|
||||
error: string;
|
||||
}
|
||||
|
||||
export interface sysProcess {
|
||||
pid: number;
|
||||
background: boolean;
|
||||
cpupercent: number;
|
||||
children: number[];
|
||||
cmd: string;
|
||||
connections: nethwConnectionStat[];
|
||||
createtime: number;
|
||||
cwd: string;
|
||||
exe: string;
|
||||
gids: number[];
|
||||
iocounters: processIOCountersStat;
|
||||
isrunning: boolean;
|
||||
meminfo: memoryInfoStat;
|
||||
memmaps: any[];
|
||||
mempercent: number;
|
||||
name: string;
|
||||
netiocounters: nethwIOCounterStat[];
|
||||
nice: number;
|
||||
numctxswitches: processNmCtxSwitchesStat;
|
||||
numfds: number;
|
||||
numthreads: number;
|
||||
pagefaults: processPageFaultsStat;
|
||||
parent: number;
|
||||
ppid: number;
|
||||
rlimit: processRLimitStat[];
|
||||
status: string;
|
||||
tgid: number;
|
||||
cputimes: cpuTimeStat;
|
||||
uids: number[];
|
||||
username: string;
|
||||
}
|
||||
|
||||
export interface nethwConnectionStat {
|
||||
fd: number;
|
||||
family: number;
|
||||
type: number;
|
||||
localaddr: netAddr;
|
||||
remoteaddr: netAddr;
|
||||
status: string;
|
||||
uids: number[];
|
||||
pid: number;
|
||||
}
|
||||
|
||||
export interface netAddr {
|
||||
ip: string;
|
||||
port: number;
|
||||
}
|
||||
|
||||
export interface processIOCountersStat {
|
||||
readCount: number;
|
||||
writeCount: number;
|
||||
readBytes: number;
|
||||
writeBytes: number;
|
||||
}
|
||||
|
||||
export interface memoryInfoStat {
|
||||
rss: number;
|
||||
vms: number;
|
||||
hwm: number;
|
||||
data: number;
|
||||
stack: number;
|
||||
locked: number;
|
||||
swap: number;
|
||||
}
|
||||
|
||||
export interface nethwIOCounterStat {
|
||||
name: string;
|
||||
bytesSent: number;
|
||||
bytesRecv: number;
|
||||
packetsSent: number;
|
||||
packetsRecv: number;
|
||||
errin: number;
|
||||
errout: number;
|
||||
dropin: number;
|
||||
dropout: number;
|
||||
fifoin: number;
|
||||
fifoout: number;
|
||||
}
|
||||
|
||||
export interface processNmCtxSwitchesStat {
|
||||
voluntary: number;
|
||||
involuntary: number;
|
||||
}
|
||||
|
||||
export interface processPageFaultsStat {
|
||||
minorFaults: number;
|
||||
majorFaults: number;
|
||||
childMinorFaults: number;
|
||||
childMajorFaults: number;
|
||||
}
|
||||
|
||||
export interface processRLimitStat {
|
||||
resource: number;
|
||||
soft: number;
|
||||
hard: number;
|
||||
used: number;
|
||||
}
|
||||
@@ -52,6 +52,7 @@ import LogoutIcon from "../../../icons/LogoutIcon";
|
||||
import ConsoleIcon from "../../../icons/ConsoleIcon";
|
||||
import HealIcon from "../../../icons/HealIcon";
|
||||
import WatchIcon from "../../../icons/WatchIcon";
|
||||
import TrackChangesSharpIcon from "@material-ui/icons/TrackChangesSharp";
|
||||
|
||||
const styles = (theme: Theme) =>
|
||||
createStyles({
|
||||
@@ -275,6 +276,14 @@ const Menu = ({ userLoggedIn, classes, pages, operatorMode }: IMenuProps) => {
|
||||
name: "Heal",
|
||||
icon: <HealIcon />,
|
||||
},
|
||||
{
|
||||
group: "Tools",
|
||||
type: "item",
|
||||
component: NavLink,
|
||||
to: "/health-info",
|
||||
name: "Diagnostic",
|
||||
icon: <TrackChangesSharpIcon />,
|
||||
},
|
||||
{
|
||||
group: "Admin",
|
||||
type: "item",
|
||||
|
||||
@@ -11,9 +11,9 @@
|
||||
// opt-in, read https://bit.ly/CRA-PWA
|
||||
|
||||
const isLocalhost = Boolean(
|
||||
window.location.hostname === 'localhost' ||
|
||||
window.location.hostname === "localhost" ||
|
||||
// [::1] is the IPv6 localhost address.
|
||||
window.location.hostname === '[::1]' ||
|
||||
window.location.hostname === "[::1]" ||
|
||||
// 127.0.0.1/8 is considered localhost for IPv4.
|
||||
window.location.hostname.match(
|
||||
/^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/
|
||||
@@ -26,7 +26,7 @@ type Config = {
|
||||
};
|
||||
|
||||
export function register(config?: Config) {
|
||||
if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
|
||||
if (process.env.NODE_ENV === "production" && "serviceWorker" in navigator) {
|
||||
// The URL constructor is available in all browsers that support SW.
|
||||
const publicUrl = new URL(
|
||||
(process as { env: { [key: string]: string } }).env.PUBLIC_URL,
|
||||
@@ -39,7 +39,7 @@ export function register(config?: Config) {
|
||||
return;
|
||||
}
|
||||
|
||||
window.addEventListener('load', () => {
|
||||
window.addEventListener("load", () => {
|
||||
const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
|
||||
|
||||
if (isLocalhost) {
|
||||
@@ -50,8 +50,8 @@ export function register(config?: Config) {
|
||||
// service worker/PWA documentation.
|
||||
navigator.serviceWorker.ready.then(() => {
|
||||
console.log(
|
||||
'This web app is being served cache-first by a service ' +
|
||||
'worker. To learn more, visit https://bit.ly/CRA-PWA'
|
||||
"This web app is being served cache-first by a service " +
|
||||
"worker. To learn more, visit https://bit.ly/CRA-PWA"
|
||||
);
|
||||
});
|
||||
} else {
|
||||
@@ -65,21 +65,21 @@ export function register(config?: Config) {
|
||||
function registerValidSW(swUrl: string, config?: Config) {
|
||||
navigator.serviceWorker
|
||||
.register(swUrl)
|
||||
.then(registration => {
|
||||
.then((registration) => {
|
||||
registration.onupdatefound = () => {
|
||||
const installingWorker = registration.installing;
|
||||
if (installingWorker == null) {
|
||||
return;
|
||||
}
|
||||
installingWorker.onstatechange = () => {
|
||||
if (installingWorker.state === 'installed') {
|
||||
if (installingWorker.state === "installed") {
|
||||
if (navigator.serviceWorker.controller) {
|
||||
// At this point, the updated precached content has been fetched,
|
||||
// but the previous service worker will still serve the older
|
||||
// content until all client tabs are closed.
|
||||
console.log(
|
||||
'New content is available and will be used when all ' +
|
||||
'tabs for this page are closed. See https://bit.ly/CRA-PWA.'
|
||||
"New content is available and will be used when all " +
|
||||
"tabs for this page are closed. See https://bit.ly/CRA-PWA."
|
||||
);
|
||||
|
||||
// Execute callback
|
||||
@@ -90,7 +90,7 @@ function registerValidSW(swUrl: string, config?: Config) {
|
||||
// At this point, everything has been precached.
|
||||
// It's the perfect time to display a
|
||||
// "Content is cached for offline use." message.
|
||||
console.log('Content is cached for offline use.');
|
||||
console.log("Content is cached for offline use.");
|
||||
|
||||
// Execute callback
|
||||
if (config && config.onSuccess) {
|
||||
@@ -101,23 +101,23 @@ function registerValidSW(swUrl: string, config?: Config) {
|
||||
};
|
||||
};
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error during service worker registration:', error);
|
||||
.catch((error) => {
|
||||
console.error("Error during service worker registration:", error);
|
||||
});
|
||||
}
|
||||
|
||||
function checkValidServiceWorker(swUrl: string, config?: Config) {
|
||||
// Check if the service worker can be found. If it can't reload the page.
|
||||
fetch(swUrl)
|
||||
.then(response => {
|
||||
.then((response) => {
|
||||
// Ensure service worker exists, and that we really are getting a JS file.
|
||||
const contentType = response.headers.get('content-type');
|
||||
const contentType = response.headers.get("content-type");
|
||||
if (
|
||||
response.status === 404 ||
|
||||
(contentType != null && contentType.indexOf('javascript') === -1)
|
||||
(contentType != null && contentType.indexOf("javascript") === -1)
|
||||
) {
|
||||
// No service worker found. Probably a different app. Reload the page.
|
||||
navigator.serviceWorker.ready.then(registration => {
|
||||
navigator.serviceWorker.ready.then((registration) => {
|
||||
registration.unregister().then(() => {
|
||||
window.location.reload();
|
||||
});
|
||||
@@ -129,14 +129,14 @@ function checkValidServiceWorker(swUrl: string, config?: Config) {
|
||||
})
|
||||
.catch(() => {
|
||||
console.log(
|
||||
'No internet connection found. App is running in offline mode.'
|
||||
"No internet connection found. App is running in offline mode."
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
export function unregister() {
|
||||
if ('serviceWorker' in navigator) {
|
||||
navigator.serviceWorker.ready.then(registration => {
|
||||
if ("serviceWorker" in navigator) {
|
||||
navigator.serviceWorker.ready.then((registration) => {
|
||||
registration.unregister();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ import thunk from "redux-thunk";
|
||||
import { systemReducer } from "./reducer";
|
||||
import { traceReducer } from "./screens/Console/Trace/reducers";
|
||||
import { logReducer } from "./screens/Console/Logs/reducers";
|
||||
import { healthInfoReducer } from "./screens/Console/HealthInfo/reducers";
|
||||
import { watchReducer } from "./screens/Console/Watch/reducers";
|
||||
import { consoleReducer } from "./screens/Console/reducer";
|
||||
import { bucketsReducer } from "./screens/Console/Buckets/reducers";
|
||||
@@ -32,6 +33,7 @@ const globalReducer = combineReducers({
|
||||
console: consoleReducer,
|
||||
buckets: bucketsReducer,
|
||||
objectBrowser: objectBrowserReducer,
|
||||
healthInfo: healthInfoReducer,
|
||||
});
|
||||
|
||||
declare global {
|
||||
|
||||
@@ -24,6 +24,7 @@ export interface SystemState {
|
||||
serverIsLoading: boolean;
|
||||
loadingProgress: number;
|
||||
snackBarMessage: string;
|
||||
serverDiagnosticStatus: string;
|
||||
}
|
||||
|
||||
export const USER_LOGGED = "USER_LOGGED";
|
||||
@@ -33,6 +34,7 @@ export const SERVER_NEEDS_RESTART = "SERVER_NEEDS_RESTART";
|
||||
export const SERVER_IS_LOADING = "SERVER_IS_LOADING";
|
||||
export const SET_LOADING_PROGRESS = "SET_LOADING_PROGRESS";
|
||||
export const SET_SNACK_BAR_MESSAGE = "SET_SNACK_BAR_MESSAGE";
|
||||
export const SET_SERVER_DIAG_STAT = "SET_SERVER_DIAG_STAT";
|
||||
|
||||
interface UserLoggedAction {
|
||||
type: typeof USER_LOGGED;
|
||||
@@ -68,6 +70,11 @@ interface SetSnackBarMessage {
|
||||
snackBarMessage: string;
|
||||
}
|
||||
|
||||
interface SetServerDiagStat {
|
||||
type: typeof SET_SERVER_DIAG_STAT;
|
||||
serverDiagnosticStatus: string;
|
||||
}
|
||||
|
||||
export type SystemActionTypes =
|
||||
| UserLoggedAction
|
||||
| OperatorModeAction
|
||||
@@ -75,4 +82,5 @@ export type SystemActionTypes =
|
||||
| ServerNeedsRestartAction
|
||||
| ServerIsLoading
|
||||
| SetLoadingProgress
|
||||
| SetSnackBarMessage;
|
||||
| SetSnackBarMessage
|
||||
| SetServerDiagStat;
|
||||
|
||||
@@ -13,6 +13,13 @@
|
||||
//
|
||||
// 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/>.
|
||||
|
||||
// Close codes for websockets defined in RFC 6455
|
||||
export const WSCloseNormalClosure = 1000;
|
||||
export const WSCloseCloseGoingAway = 1001;
|
||||
export const WSClosePolicyViolation = 1008;
|
||||
export const WSCloseInternalServerErr = 1011;
|
||||
|
||||
export const wsProtocol = (protocol: string): string => {
|
||||
let wsProtocol = "ws";
|
||||
if (protocol === "https:") {
|
||||
|
||||
@@ -1,11 +1,7 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "es2015",
|
||||
"lib": [
|
||||
"dom",
|
||||
"dom.iterable",
|
||||
"esnext"
|
||||
],
|
||||
"lib": ["dom", "dom.iterable", "esnext"],
|
||||
"allowJs": true,
|
||||
"skipLibCheck": true,
|
||||
"esModuleInterop": true,
|
||||
@@ -20,7 +16,5 @@
|
||||
"jsx": "react-jsx",
|
||||
"downlevelIteration": true
|
||||
},
|
||||
"include": [
|
||||
"src"
|
||||
]
|
||||
"include": ["src"]
|
||||
}
|
||||
|
||||
@@ -8901,10 +8901,10 @@ prepend-http@^1.0.0:
|
||||
resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-1.0.4.tgz#d4f4562b0ce3696e41ac52d0e002e57a635dc6dc"
|
||||
integrity sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw=
|
||||
|
||||
prettier@^1.19.1:
|
||||
version "1.19.1"
|
||||
resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.19.1.tgz#f7d7f5ff8a9cd872a7be4ca142095956a60797cb"
|
||||
integrity sha512-s7PoyDv/II1ObgQunCbB9PdLmUcBZcnWOcxDh7O0N/UwDEsHyqkW+Qh28jW+mVuCdx7gLB0BotYI1Y6uI9iyew==
|
||||
prettier@2.2.1:
|
||||
version "2.2.1"
|
||||
resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.2.1.tgz#795a1a78dd52f073da0cd42b21f9c91381923ff5"
|
||||
integrity sha512-PqyhM2yCjg/oKkFPtTGUojv7gnZAoG80ttl45O6x2Ug/rMJw4wcc9k6aaf2hibP7BGVCCM33gZoGjyvt9mm16Q==
|
||||
|
||||
pretty-bytes@^5.1.0:
|
||||
version "5.4.1"
|
||||
|
||||
94
restapi/admin_health_info.go
Normal file
94
restapi/admin_health_info.go
Normal file
@@ -0,0 +1,94 @@
|
||||
// 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"
|
||||
"log"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"errors"
|
||||
|
||||
"github.com/gorilla/websocket"
|
||||
madmin "github.com/minio/minio/pkg/madmin"
|
||||
)
|
||||
|
||||
// startHealthInfo starts fetching mc.ServerHealthInfo and
|
||||
// sends messages with the corresponding data on the websocket connection
|
||||
func startHealthInfo(ctx context.Context, conn WSConn, client MinioAdmin, deadline *time.Duration) error {
|
||||
if deadline == nil {
|
||||
return errors.New("duration can't be nil on startHealthInfo")
|
||||
}
|
||||
|
||||
// Fetch info of all servers (cluster or single server)
|
||||
healthDataTypes := []madmin.HealthDataType{
|
||||
madmin.HealthDataTypePerfDrive,
|
||||
madmin.HealthDataTypePerfNet,
|
||||
madmin.HealthDataTypeMinioInfo,
|
||||
madmin.HealthDataTypeMinioConfig,
|
||||
madmin.HealthDataTypeSysCPU,
|
||||
madmin.HealthDataTypeSysDiskHw,
|
||||
madmin.HealthDataTypeSysDocker,
|
||||
madmin.HealthDataTypeSysOsInfo,
|
||||
madmin.HealthDataTypeSysLoad,
|
||||
madmin.HealthDataTypeSysMem,
|
||||
madmin.HealthDataTypeSysNet,
|
||||
madmin.HealthDataTypeSysProcess,
|
||||
}
|
||||
|
||||
healthChan := client.serverHealthInfo(ctx, healthDataTypes, *deadline)
|
||||
// wait for events to occur
|
||||
for {
|
||||
select {
|
||||
// return if context ends
|
||||
case <-ctx.Done():
|
||||
return nil
|
||||
case adminHealthInfo, ok := <-healthChan:
|
||||
// zero value returned because the channel is closed and empty
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
if adminHealthInfo.Error != "" {
|
||||
return errors.New(adminHealthInfo.Error)
|
||||
}
|
||||
// Serialize message to be sent
|
||||
bytes, err := json.Marshal(adminHealthInfo)
|
||||
if err != nil {
|
||||
log.Println("error on json.Marshal:", err)
|
||||
return err
|
||||
}
|
||||
// Send Message through websocket connection
|
||||
err = conn.writeMessage(websocket.TextMessage, bytes)
|
||||
if err != nil {
|
||||
log.Println("error writeMessage:", err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// getHealthInfoOptionsFromReq gets duration for startHealthInfo request
|
||||
// path come as : `/health-info?deadline=2h`
|
||||
func getHealthInfoOptionsFromReq(req *http.Request) (*time.Duration, error) {
|
||||
deadlineDuration, err := time.ParseDuration(req.FormValue("deadline"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &deadlineDuration, nil
|
||||
}
|
||||
192
restapi/admin_health_info_test.go
Normal file
192
restapi/admin_health_info_test.go
Normal file
@@ -0,0 +1,192 @@
|
||||
// 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"
|
||||
"errors"
|
||||
"reflect"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
madmin "github.com/minio/minio/pkg/madmin"
|
||||
)
|
||||
|
||||
// assigning mock at runtime instead of compile time
|
||||
var minioServerHealthInfoMock func(ctx context.Context, healthDataTypes []madmin.HealthDataType, deadline time.Duration) <-chan madmin.HealthInfo
|
||||
|
||||
// mock function serverHealthInfo
|
||||
func (ac adminClientMock) serverHealthInfo(ctx context.Context, healthDataTypes []madmin.HealthDataType, deadline time.Duration) <-chan madmin.HealthInfo {
|
||||
return minioServerHealthInfoMock(ctx, healthDataTypes, deadline)
|
||||
}
|
||||
|
||||
func Test_serverHealthInfo(t *testing.T) {
|
||||
var testReceiver chan madmin.HealthInfo
|
||||
|
||||
ctx := context.Background()
|
||||
client := adminClientMock{}
|
||||
mockWSConn := mockConn{}
|
||||
deadlineDuration, _ := time.ParseDuration("1h")
|
||||
|
||||
type args struct {
|
||||
deadline time.Duration
|
||||
wsWriteMock func(messageType int, data []byte) error
|
||||
mockMessages []madmin.HealthInfo
|
||||
}
|
||||
tests := []struct {
|
||||
test string
|
||||
args args
|
||||
wantError error
|
||||
}{
|
||||
{
|
||||
test: "Return simple health info, no errors",
|
||||
args: args{
|
||||
deadline: deadlineDuration,
|
||||
mockMessages: []madmin.HealthInfo{
|
||||
madmin.HealthInfo{
|
||||
Perf: madmin.PerfInfo{
|
||||
NetParallel: madmin.ServerNetHealthInfo{
|
||||
Addr: "someaddress",
|
||||
},
|
||||
},
|
||||
},
|
||||
madmin.HealthInfo{
|
||||
Perf: madmin.PerfInfo{
|
||||
NetParallel: madmin.ServerNetHealthInfo{
|
||||
Addr: "otheraddress",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
wsWriteMock: func(messageType int, data []byte) error {
|
||||
// mock connection WriteMessage() no error
|
||||
// emulate that receiver gets the message written
|
||||
var t madmin.HealthInfo
|
||||
_ = json.Unmarshal(data, &t)
|
||||
testReceiver <- t
|
||||
return nil
|
||||
|
||||
},
|
||||
},
|
||||
wantError: nil,
|
||||
},
|
||||
{
|
||||
test: "Return simple health info2, no errors",
|
||||
args: args{
|
||||
deadline: deadlineDuration,
|
||||
mockMessages: []madmin.HealthInfo{
|
||||
madmin.HealthInfo{
|
||||
Perf: madmin.PerfInfo{
|
||||
NetParallel: madmin.ServerNetHealthInfo{
|
||||
Addr: "address",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
wsWriteMock: func(messageType int, data []byte) error {
|
||||
// mock connection WriteMessage() no error
|
||||
// emulate that receiver gets the message written
|
||||
var t madmin.HealthInfo
|
||||
_ = json.Unmarshal(data, &t)
|
||||
testReceiver <- t
|
||||
return nil
|
||||
|
||||
},
|
||||
},
|
||||
wantError: nil,
|
||||
},
|
||||
{
|
||||
test: "Handle error on ws write",
|
||||
args: args{
|
||||
deadline: deadlineDuration,
|
||||
mockMessages: []madmin.HealthInfo{
|
||||
madmin.HealthInfo{
|
||||
Perf: madmin.PerfInfo{
|
||||
NetParallel: madmin.ServerNetHealthInfo{
|
||||
Addr: "address",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
wsWriteMock: func(messageType int, data []byte) error {
|
||||
// mock connection WriteMessage() no error
|
||||
// emulate that receiver gets the message written
|
||||
var t madmin.HealthInfo
|
||||
_ = json.Unmarshal(data, &t)
|
||||
return errors.New("error on write")
|
||||
|
||||
},
|
||||
},
|
||||
wantError: errors.New("error on write"),
|
||||
},
|
||||
{
|
||||
test: "Handle error on health function",
|
||||
args: args{
|
||||
deadline: deadlineDuration,
|
||||
mockMessages: []madmin.HealthInfo{
|
||||
madmin.HealthInfo{
|
||||
Error: "error on healthInfo",
|
||||
},
|
||||
},
|
||||
wsWriteMock: func(messageType int, data []byte) error {
|
||||
// mock connection WriteMessage() no error
|
||||
// emulate that receiver gets the message written
|
||||
var t madmin.HealthInfo
|
||||
_ = json.Unmarshal(data, &t)
|
||||
return nil
|
||||
|
||||
},
|
||||
},
|
||||
wantError: errors.New("error on healthInfo"),
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.test, func(t *testing.T) {
|
||||
// make testReceiver channel
|
||||
testReceiver = make(chan madmin.HealthInfo, len(tt.args.mockMessages))
|
||||
// mock function same for all tests, changes mockMessages
|
||||
minioServerHealthInfoMock = func(ctx context.Context, healthDataTypes []madmin.HealthDataType, deadline time.Duration) <-chan madmin.HealthInfo {
|
||||
respChan := make(chan madmin.HealthInfo)
|
||||
go func(ch chan madmin.HealthInfo) {
|
||||
defer close(ch)
|
||||
for _, info := range tt.args.mockMessages {
|
||||
ch <- info
|
||||
}
|
||||
}(respChan)
|
||||
return respChan
|
||||
}
|
||||
connWriteMessageMock = tt.args.wsWriteMock
|
||||
err := startHealthInfo(ctx, mockWSConn, client, &deadlineDuration)
|
||||
// close test mock channel
|
||||
close(testReceiver)
|
||||
// check that the TestReceiver got the same number of data from Console.
|
||||
index := 0
|
||||
for info := range testReceiver {
|
||||
if !reflect.DeepEqual(info, tt.args.mockMessages[index]) {
|
||||
t.Errorf("startHealthInfo() got: %v, want: %v", info, tt.args.mockMessages[index])
|
||||
return
|
||||
}
|
||||
index++
|
||||
}
|
||||
if !reflect.DeepEqual(err, tt.wantError) {
|
||||
t.Errorf("startHealthInfo() error: %v, wantError: %v", err, tt.wantError)
|
||||
return
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -22,6 +22,7 @@ import (
|
||||
"net/http"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"time"
|
||||
|
||||
"github.com/minio/console/models"
|
||||
mcCmd "github.com/minio/mc/cmd"
|
||||
@@ -105,6 +106,8 @@ type MinioAdmin interface {
|
||||
addRemoteBucket(ctx context.Context, bucket string, target *madmin.BucketTarget) (string, error)
|
||||
// Account password management
|
||||
changePassword(ctx context.Context, accessKey, secretKey string) error
|
||||
|
||||
serverHealthInfo(ctx context.Context, healthDataTypes []madmin.HealthDataType, deadline time.Duration) <-chan madmin.HealthInfo
|
||||
}
|
||||
|
||||
// Interface implementation
|
||||
@@ -286,11 +289,15 @@ func (ac adminClient) addRemoteBucket(ctx context.Context, bucket string, target
|
||||
return ac.client.SetRemoteTarget(ctx, bucket, target)
|
||||
}
|
||||
|
||||
// addRemoteBucket sets up a remote target for this bucket
|
||||
func (ac adminClient) setBucketQuota(ctx context.Context, bucket string, quota *madmin.BucketQuota) error {
|
||||
return ac.client.SetBucketQuota(ctx, bucket, quota)
|
||||
}
|
||||
|
||||
// serverHealthInfo implements mc.ServerHealthInfo - Connect to a minio server and call Health Info Management API
|
||||
func (ac adminClient) serverHealthInfo(ctx context.Context, healthDataTypes []madmin.HealthDataType, deadline time.Duration) <-chan madmin.HealthInfo {
|
||||
return ac.client.ServerHealthInfo(ctx, healthDataTypes, deadline)
|
||||
}
|
||||
|
||||
func newMAdminClient(sessionClaims *models.Principal) (*madmin.AdminClient, error) {
|
||||
adminClient, err := newAdminFromClaims(sessionClaims)
|
||||
if err != nil {
|
||||
|
||||
@@ -80,7 +80,7 @@ func startWatch(ctx context.Context, conn WSConn, wsc MCClient, options *watchOp
|
||||
|
||||
// getWatchOptionsFromReq gets bucket name, events, prefix, suffix from a websocket
|
||||
// watch path if defined.
|
||||
// path come as : `/watch/<namespace>/<tenantName>/bucket1` and query
|
||||
// path come as : `/watch/bucket1` and query
|
||||
// params come on request form
|
||||
func getWatchOptionsFromReq(req *http.Request) (*watchOptions, error) {
|
||||
wOptions := watchOptions{}
|
||||
|
||||
@@ -22,6 +22,7 @@ import (
|
||||
"net"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/go-openapi/errors"
|
||||
"github.com/gorilla/websocket"
|
||||
@@ -132,6 +133,19 @@ func serveWS(w http.ResponseWriter, req *http.Request) {
|
||||
return
|
||||
}
|
||||
go wsAdminClient.console()
|
||||
case strings.HasPrefix(wsPath, `/health-info`):
|
||||
deadline, err := getHealthInfoOptionsFromReq(req)
|
||||
if err != nil {
|
||||
log.Println("error getting health info options:", err)
|
||||
closeWsConn(conn)
|
||||
return
|
||||
}
|
||||
wsAdminClient, err := newWebSocketAdminClient(conn, session)
|
||||
if err != nil {
|
||||
closeWsConn(conn)
|
||||
return
|
||||
}
|
||||
go wsAdminClient.healthInfo(deadline)
|
||||
case strings.HasPrefix(wsPath, `/heal`):
|
||||
hOptions, err := getHealOptionsFromReq(req)
|
||||
if err != nil {
|
||||
@@ -308,10 +322,26 @@ func (wsc *wsAdminClient) heal(opts *healOptions) {
|
||||
sendWsCloseMessage(wsc.conn, err)
|
||||
}
|
||||
|
||||
func (wsc *wsAdminClient) healthInfo(deadline *time.Duration) {
|
||||
defer func() {
|
||||
log.Println("health info stopped")
|
||||
// close connection after return
|
||||
wsc.conn.close()
|
||||
}()
|
||||
log.Println("health info started")
|
||||
|
||||
ctx := wsReadClientCtx(wsc.conn)
|
||||
|
||||
err := startHealthInfo(ctx, wsc.conn, wsc.client, deadline)
|
||||
|
||||
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) {
|
||||
if err != nil {
|
||||
log.Print("original ws error: ", err.Error())
|
||||
// If connection exceeded read deadline send Close
|
||||
// Message Policy Violation code since we don't want
|
||||
// to let the receiver figure out the read deadline.
|
||||
@@ -322,7 +352,7 @@ func sendWsCloseMessage(conn WSConn, err error) {
|
||||
return
|
||||
}
|
||||
// else, internal server error
|
||||
conn.writeMessage(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseInternalServerErr, err.Error()))
|
||||
conn.writeMessage(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseInternalServerErr, errorGeneric.Error()))
|
||||
return
|
||||
}
|
||||
// normal closure
|
||||
|
||||
Reference in New Issue
Block a user