mirror of
https://github.com/OpenMaxIO/openmaxio-object-browser
synced 2026-07-01 15:51:18 -07:00
Compare commits
14 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2cae87aaed | ||
|
|
b82441813d | ||
|
|
5dfba3f6c8 | ||
|
|
1b9902a5be | ||
|
|
d6944ccd3b | ||
|
|
ebaa1947de | ||
|
|
9d61af7060 | ||
|
|
1b225e0901 | ||
|
|
7a864d2631 | ||
|
|
dfca19092a | ||
|
|
61cf397a02 | ||
|
|
d31528e2b5 | ||
|
|
8fd1e0db9c | ||
|
|
3d27cd2bd3 |
2
.github/workflows/go.yml
vendored
2
.github/workflows/go.yml
vendored
@@ -14,7 +14,7 @@ jobs:
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
go-version: [1.16.x]
|
||||
go-version: [1.16.x, 1.17.x]
|
||||
os: [ubuntu-latest]
|
||||
steps:
|
||||
- name: Set up Go ${{ matrix.go-version }} on ${{ matrix.os }}
|
||||
|
||||
2
.github/workflows/lint.yml
vendored
2
.github/workflows/lint.yml
vendored
@@ -14,7 +14,7 @@ jobs:
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
go-version: [1.16.x]
|
||||
go-version: [1.16.x, 1.17.x]
|
||||
os: [ubuntu-latest]
|
||||
steps:
|
||||
- name: Set up Go ${{ matrix.go-version }} on ${{ matrix.os }}
|
||||
|
||||
2
go.mod
2
go.mod
@@ -23,7 +23,7 @@ require (
|
||||
github.com/minio/minio-go/v7 v7.0.14
|
||||
github.com/minio/operator v0.0.0-20210812082324-26350f153661
|
||||
github.com/minio/operator/logsearchapi v0.0.0-20210812082324-26350f153661
|
||||
github.com/minio/pkg v1.1.3
|
||||
github.com/minio/pkg v1.1.5
|
||||
github.com/minio/selfupdate v0.3.1
|
||||
github.com/mitchellh/go-homedir v1.1.0
|
||||
github.com/rs/xid v1.2.1
|
||||
|
||||
4
go.sum
4
go.sum
@@ -893,8 +893,8 @@ github.com/minio/operator/logsearchapi v0.0.0-20210812082324-26350f153661/go.mod
|
||||
github.com/minio/pkg v1.0.3/go.mod h1:obU54TZ9QlMv0TRaDgQ/JTzf11ZSXxnSfLrm4tMtBP8=
|
||||
github.com/minio/pkg v1.0.4/go.mod h1:obU54TZ9QlMv0TRaDgQ/JTzf11ZSXxnSfLrm4tMtBP8=
|
||||
github.com/minio/pkg v1.0.8/go.mod h1:32x/3OmGB0EOi1N+3ggnp+B5VFkSBBB9svPMVfpnf14=
|
||||
github.com/minio/pkg v1.1.3 h1:J4vGnlNSxc/o9gDOQMZ3k0L3koA7ZgBQ7GRMrUpt/OY=
|
||||
github.com/minio/pkg v1.1.3/go.mod h1:32x/3OmGB0EOi1N+3ggnp+B5VFkSBBB9svPMVfpnf14=
|
||||
github.com/minio/pkg v1.1.5 h1:phwKkJBQdVLyxOXC3RChPVGLtebplzQJ5jJ3l/HBvnk=
|
||||
github.com/minio/pkg v1.1.5/go.mod h1:32x/3OmGB0EOi1N+3ggnp+B5VFkSBBB9svPMVfpnf14=
|
||||
github.com/minio/selfupdate v0.3.1 h1:BWEFSNnrZVMUWXbXIgLDNDjbejkmpAmZvy/nCz1HlEs=
|
||||
github.com/minio/selfupdate v0.3.1/go.mod h1:b8ThJzzH7u2MkF6PcIra7KaXO9Khf6alWPvMSyTDCFM=
|
||||
github.com/minio/sha256-simd v0.1.1/go.mod h1:B5e1o+1/KgNmWrSQK08Y6Z1Vb5pwIktudl0J58iy0KM=
|
||||
|
||||
@@ -15,7 +15,7 @@ spec:
|
||||
serviceAccountName: console-sa
|
||||
containers:
|
||||
- name: console
|
||||
image: minio/console:v0.10.2
|
||||
image: minio/console:v0.10.4
|
||||
imagePullPolicy: "IfNotPresent"
|
||||
env:
|
||||
- name: CONSOLE_OPERATOR_MODE
|
||||
|
||||
@@ -55,8 +55,10 @@ func registerLoginHandlers(api *operations.OperatorAPI) {
|
||||
}
|
||||
// Custom response writer to set the session cookies
|
||||
return middleware.ResponderFunc(func(w http.ResponseWriter, p runtime.Producer) {
|
||||
cookie := restapi.NewSessionCookieForConsole(loginResponse.SessionID)
|
||||
http.SetCookie(w, &cookie)
|
||||
cookies := restapi.NewSessionCookieForConsole(loginResponse.SessionID)
|
||||
for _, cookie := range cookies {
|
||||
http.SetCookie(w, &cookie)
|
||||
}
|
||||
user_api.NewLoginCreated().WithPayload(loginResponse).WriteResponse(w, p)
|
||||
})
|
||||
})
|
||||
@@ -67,8 +69,10 @@ func registerLoginHandlers(api *operations.OperatorAPI) {
|
||||
}
|
||||
// Custom response writer to set the session cookies
|
||||
return middleware.ResponderFunc(func(w http.ResponseWriter, p runtime.Producer) {
|
||||
cookie := restapi.NewSessionCookieForConsole(loginResponse.SessionID)
|
||||
http.SetCookie(w, &cookie)
|
||||
cookies := restapi.NewSessionCookieForConsole(loginResponse.SessionID)
|
||||
for _, cookie := range cookies {
|
||||
http.SetCookie(w, &cookie)
|
||||
}
|
||||
user_api.NewLoginOauth2AuthCreated().WithPayload(loginResponse).WriteResponse(w, p)
|
||||
})
|
||||
})
|
||||
@@ -79,8 +83,10 @@ func registerLoginHandlers(api *operations.OperatorAPI) {
|
||||
}
|
||||
// Custom response writer to set the session cookies
|
||||
return middleware.ResponderFunc(func(w http.ResponseWriter, p runtime.Producer) {
|
||||
cookie := restapi.NewSessionCookieForConsole(loginResponse.SessionID)
|
||||
http.SetCookie(w, &cookie)
|
||||
cookies := restapi.NewSessionCookieForConsole(loginResponse.SessionID)
|
||||
for _, cookie := range cookies {
|
||||
http.SetCookie(w, &cookie)
|
||||
}
|
||||
user_api.NewLoginOperatorCreated().WithPayload(loginResponse).WriteResponse(w, p)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -287,6 +287,7 @@ func decrypt(ciphertext []byte, associatedData []byte) ([]byte, error) {
|
||||
func GetTokenFromRequest(r *http.Request) (string, error) {
|
||||
// Token might come either as a Cookie or as a Header
|
||||
// if not set in cookie, check if it is set on Header.
|
||||
|
||||
tokenCookie, err := r.Cookie("token")
|
||||
if err != nil {
|
||||
return "", ErrNoAuthToken
|
||||
@@ -295,7 +296,17 @@ func GetTokenFromRequest(r *http.Request) (string, error) {
|
||||
if tokenCookie.Expires.After(currentTime) {
|
||||
return "", errTokenExpired
|
||||
}
|
||||
return strings.TrimSpace(tokenCookie.Value), nil
|
||||
|
||||
mergeToken := strings.TrimSpace(tokenCookie.Value)
|
||||
for _, cookie := range r.Cookies() {
|
||||
// any cookie with token%d structure
|
||||
if cookie.Name != "token" && !strings.HasPrefix(cookie.Name, "token-") && strings.HasPrefix(cookie.Name, "token") {
|
||||
mergeToken = fmt.Sprintf("%s%s", mergeToken, strings.TrimSpace(cookie.Value))
|
||||
}
|
||||
}
|
||||
|
||||
return mergeToken, nil
|
||||
|
||||
}
|
||||
|
||||
func GetClaimsFromTokenInRequest(req *http.Request) (*models.Principal, error) {
|
||||
|
||||
@@ -1,23 +1,23 @@
|
||||
{
|
||||
"files": {
|
||||
"main.css": "./static/css/main.96c516b4.chunk.css",
|
||||
"main.js": "./static/js/main.cbf18a78.chunk.js",
|
||||
"main.js.map": "./static/js/main.cbf18a78.chunk.js.map",
|
||||
"main.css": "./static/css/main.e33a67ba.chunk.css",
|
||||
"main.js": "./static/js/main.e81f26fe.chunk.js",
|
||||
"main.js.map": "./static/js/main.e81f26fe.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.cb056e1c.chunk.css": "./static/css/2.cb056e1c.chunk.css",
|
||||
"static/js/2.d2f64bf3.chunk.js": "./static/js/2.d2f64bf3.chunk.js",
|
||||
"static/js/2.d2f64bf3.chunk.js.map": "./static/js/2.d2f64bf3.chunk.js.map",
|
||||
"static/css/2.5b1f144e.chunk.css": "./static/css/2.5b1f144e.chunk.css",
|
||||
"static/js/2.dd760fd2.chunk.js": "./static/js/2.dd760fd2.chunk.js",
|
||||
"static/js/2.dd760fd2.chunk.js.map": "./static/js/2.dd760fd2.chunk.js.map",
|
||||
"index.html": "./index.html",
|
||||
"static/css/2.cb056e1c.chunk.css.map": "./static/css/2.cb056e1c.chunk.css.map",
|
||||
"static/css/main.96c516b4.chunk.css.map": "./static/css/main.96c516b4.chunk.css.map",
|
||||
"static/js/2.d2f64bf3.chunk.js.LICENSE.txt": "./static/js/2.d2f64bf3.chunk.js.LICENSE.txt"
|
||||
"static/css/2.5b1f144e.chunk.css.map": "./static/css/2.5b1f144e.chunk.css.map",
|
||||
"static/css/main.e33a67ba.chunk.css.map": "./static/css/main.e33a67ba.chunk.css.map",
|
||||
"static/js/2.dd760fd2.chunk.js.LICENSE.txt": "./static/js/2.dd760fd2.chunk.js.LICENSE.txt"
|
||||
},
|
||||
"entrypoints": [
|
||||
"static/js/runtime-main.30f8243a.js",
|
||||
"static/css/2.cb056e1c.chunk.css",
|
||||
"static/js/2.d2f64bf3.chunk.js",
|
||||
"static/css/main.96c516b4.chunk.css",
|
||||
"static/js/main.cbf18a78.chunk.js"
|
||||
"static/css/2.5b1f144e.chunk.css",
|
||||
"static/js/2.dd760fd2.chunk.js",
|
||||
"static/css/main.e33a67ba.chunk.css",
|
||||
"static/js/main.e81f26fe.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.cb056e1c.chunk.css" rel="stylesheet"><link href="./static/css/main.96c516b4.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.d2f64bf3.chunk.js"></script><script src="./static/js/main.cbf18a78.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.5b1f144e.chunk.css" rel="stylesheet"><link href="./static/css/main.e33a67ba.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.dd760fd2.chunk.js"></script><script src="./static/js/main.e81f26fe.chunk.js"></script></body></html>
|
||||
@@ -1,2 +1,2 @@
|
||||
.ReactVirtualized__Table__headerRow{font-weight:700;text-transform:uppercase}.ReactVirtualized__Table__headerRow,.ReactVirtualized__Table__row{display:flex;flex-direction:row;align-items:center}.ReactVirtualized__Table__headerTruncatedText{display:inline-block;max-width:100%;white-space:nowrap;text-overflow:ellipsis;overflow:hidden}.ReactVirtualized__Table__headerColumn,.ReactVirtualized__Table__rowColumn{margin-right:10px;min-width:0}.ReactVirtualized__Table__rowColumn{text-overflow:ellipsis;white-space:nowrap}.ReactVirtualized__Table__headerColumn:first-of-type,.ReactVirtualized__Table__rowColumn:first-of-type{margin-left:10px}.ReactVirtualized__Table__sortableHeaderColumn{cursor:pointer}.ReactVirtualized__Table__sortableHeaderIconContainer{display:flex;align-items:center}.ReactVirtualized__Table__sortableHeaderIcon{flex:0 0 24px;height:1em;width:1em;fill:currentColor}.react-grid-layout{position:relative;transition:height .2s ease}.react-grid-item{transition:all .2s ease;transition-property:left,top}.react-grid-item img{pointer-events:none;-webkit-user-select:none;-ms-user-select:none;user-select:none}.react-grid-item.cssTransforms{transition-property:-webkit-transform;transition-property:transform;transition-property:transform,-webkit-transform}.react-grid-item.resizing{z-index:1;will-change:width,height}.react-grid-item.react-draggable-dragging{transition:none;z-index:3;will-change:transform}.react-grid-item.dropping{visibility:hidden}.react-grid-item.react-grid-placeholder{background:red;opacity:.2;transition-duration:.1s;z-index:2;-webkit-user-select:none;-ms-user-select:none;-o-user-select:none;user-select:none}.react-grid-item>.react-resizable-handle{position:absolute;width:20px;height:20px}.react-grid-item>.react-resizable-handle:after{content:"";position:absolute;right:3px;bottom:3px;width:5px;height:5px;border-right:2px solid rgba(0,0,0,.4);border-bottom:2px solid rgba(0,0,0,.4)}.react-resizable-hide>.react-resizable-handle{display:none}.react-grid-item>.react-resizable-handle.react-resizable-handle-sw{bottom:0;left:0;cursor:sw-resize;-webkit-transform:rotate(90deg);transform:rotate(90deg)}.react-grid-item>.react-resizable-handle.react-resizable-handle-se{bottom:0;right:0;cursor:se-resize}.react-grid-item>.react-resizable-handle.react-resizable-handle-nw{top:0;left:0;cursor:nw-resize;-webkit-transform:rotate(180deg);transform:rotate(180deg)}.react-grid-item>.react-resizable-handle.react-resizable-handle-ne{top:0;right:0;cursor:ne-resize;-webkit-transform:rotate(270deg);transform:rotate(270deg)}.react-grid-item>.react-resizable-handle.react-resizable-handle-e,.react-grid-item>.react-resizable-handle.react-resizable-handle-w{top:50%;margin-top:-10px;cursor:ew-resize}.react-grid-item>.react-resizable-handle.react-resizable-handle-w{left:0;-webkit-transform:rotate(135deg);transform:rotate(135deg)}.react-grid-item>.react-resizable-handle.react-resizable-handle-e{right:0;-webkit-transform:rotate(315deg);transform:rotate(315deg)}.react-grid-item>.react-resizable-handle.react-resizable-handle-n,.react-grid-item>.react-resizable-handle.react-resizable-handle-s{left:50%;margin-left:-10px;cursor:ns-resize}.react-grid-item>.react-resizable-handle.react-resizable-handle-n{top:0;-webkit-transform:rotate(225deg);transform:rotate(225deg)}.react-grid-item>.react-resizable-handle.react-resizable-handle-s{bottom:0;-webkit-transform:rotate(45deg);transform:rotate(45deg)}.react-resizable{position:relative}.react-resizable-handle{position:absolute;width:20px;height:20px;background-repeat:no-repeat;background-origin:content-box;box-sizing:border-box;background-image:url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHN0eWxlPSJiYWNrZ3JvdW5kLWNvbG9yOiNmZmZmZmYwMCIgd2lkdGg9IjYiIGhlaWdodD0iNiI+PHBhdGggZD0iTTYgNkgwVjQuMmg0LjJWMEg2djZ6IiBvcGFjaXR5PSIuMzAyIi8+PC9zdmc+");background-position:100% 100%;padding:0 3px 3px 0}.react-resizable-handle-sw{bottom:0;left:0;cursor:sw-resize;-webkit-transform:rotate(90deg);transform:rotate(90deg)}.react-resizable-handle-se{bottom:0;right:0;cursor:se-resize}.react-resizable-handle-nw{top:0;left:0;cursor:nw-resize;-webkit-transform:rotate(180deg);transform:rotate(180deg)}.react-resizable-handle-ne{top:0;right:0;cursor:ne-resize;-webkit-transform:rotate(270deg);transform:rotate(270deg)}.react-resizable-handle-e,.react-resizable-handle-w{top:50%;margin-top:-10px;cursor:ew-resize}.react-resizable-handle-w{left:0;-webkit-transform:rotate(135deg);transform:rotate(135deg)}.react-resizable-handle-e{right:0;-webkit-transform:rotate(315deg);transform:rotate(315deg)}.react-resizable-handle-n,.react-resizable-handle-s{left:50%;margin-left:-10px;cursor:ns-resize}.react-resizable-handle-n{top:0;-webkit-transform:rotate(225deg);transform:rotate(225deg)}.react-resizable-handle-s{bottom:0;-webkit-transform:rotate(45deg);transform:rotate(45deg)}
|
||||
/*# sourceMappingURL=2.cb056e1c.chunk.css.map */
|
||||
/*# sourceMappingURL=2.5b1f144e.chunk.css.map */
|
||||
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
3
portal-ui/build/static/js/2.dd760fd2.chunk.js
Normal file
3
portal-ui/build/static/js/2.dd760fd2.chunk.js
Normal file
File diff suppressed because one or more lines are too long
1
portal-ui/build/static/js/2.dd760fd2.chunk.js.map
Normal file
1
portal-ui/build/static/js/2.dd760fd2.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
2
portal-ui/build/static/js/main.e81f26fe.chunk.js
Normal file
2
portal-ui/build/static/js/main.e81f26fe.chunk.js
Normal file
File diff suppressed because one or more lines are too long
1
portal-ui/build/static/js/main.e81f26fe.chunk.js.map
Normal file
1
portal-ui/build/static/js/main.e81f26fe.chunk.js.map
Normal file
File diff suppressed because one or more lines are too long
@@ -1,130 +1,135 @@
|
||||
/*Lato Font import*/
|
||||
|
||||
@font-face {
|
||||
font-family: 'Lato';
|
||||
src: url('./fonts/Lato/Lato-BlackItalic.eot');
|
||||
src: url('./fonts/Lato/Lato-BlackItalic.eot?#iefix') format('embedded-opentype'),
|
||||
url('./fonts/Lato/Lato-BlackItalic.woff2') format('woff2'),
|
||||
url('./fonts/Lato/Lato-BlackItalic.woff') format('woff'),
|
||||
url('./fonts/Lato/Lato-BlackItalic.ttf') format('truetype'),
|
||||
url('./fonts/Lato/Lato-BlackItalic.svg#Lato-BlackItalic') format('svg');
|
||||
font-family: "Lato";
|
||||
src: url("./fonts/Lato/Lato-BlackItalic.eot");
|
||||
src: url("./fonts/Lato/Lato-BlackItalic.eot?#iefix")
|
||||
format("embedded-opentype"),
|
||||
url("./fonts/Lato/Lato-BlackItalic.woff2") format("woff2"),
|
||||
url("./fonts/Lato/Lato-BlackItalic.woff") format("woff"),
|
||||
url("./fonts/Lato/Lato-BlackItalic.ttf") format("truetype"),
|
||||
url("./fonts/Lato/Lato-BlackItalic.svg#Lato-BlackItalic") format("svg");
|
||||
font-weight: 900;
|
||||
font-style: italic;
|
||||
font-display: swap;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Lato';
|
||||
src: url('./fonts/Lato/Lato-Bold.eot');
|
||||
src: url('./fonts/Lato/Lato-Bold.eot?#iefix') format('embedded-opentype'),
|
||||
url('./fonts/Lato/Lato-Bold.woff2') format('woff2'),
|
||||
url('./fonts/Lato/Lato-Bold.woff') format('woff'),
|
||||
url('./fonts/Lato/Lato-Bold.ttf') format('truetype'),
|
||||
url('./fonts/Lato/Lato-Bold.svg#Lato-Bold') format('svg');
|
||||
font-family: "Lato";
|
||||
src: url("./fonts/Lato/Lato-Bold.eot");
|
||||
src: url("./fonts/Lato/Lato-Bold.eot?#iefix") format("embedded-opentype"),
|
||||
url("./fonts/Lato/Lato-Bold.woff2") format("woff2"),
|
||||
url("./fonts/Lato/Lato-Bold.woff") format("woff"),
|
||||
url("./fonts/Lato/Lato-Bold.ttf") format("truetype"),
|
||||
url("./fonts/Lato/Lato-Bold.svg#Lato-Bold") format("svg");
|
||||
font-weight: bold;
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Lato';
|
||||
src: url('./fonts/Lato/Lato-BoldItalic.eot');
|
||||
src: url('./fonts/Lato/Lato-BoldItalic.eot?#iefix') format('embedded-opentype'),
|
||||
url('./fonts/Lato/Lato-BoldItalic.woff2') format('woff2'),
|
||||
url('./fonts/Lato/Lato-BoldItalic.woff') format('woff'),
|
||||
url('./fonts/Lato/Lato-BoldItalic.ttf') format('truetype'),
|
||||
url('./fonts/Lato/Lato-BoldItalic.svg#Lato-BoldItalic') format('svg');
|
||||
font-family: "Lato";
|
||||
src: url("./fonts/Lato/Lato-BoldItalic.eot");
|
||||
src: url("./fonts/Lato/Lato-BoldItalic.eot?#iefix")
|
||||
format("embedded-opentype"),
|
||||
url("./fonts/Lato/Lato-BoldItalic.woff2") format("woff2"),
|
||||
url("./fonts/Lato/Lato-BoldItalic.woff") format("woff"),
|
||||
url("./fonts/Lato/Lato-BoldItalic.ttf") format("truetype"),
|
||||
url("./fonts/Lato/Lato-BoldItalic.svg#Lato-BoldItalic") format("svg");
|
||||
font-weight: bold;
|
||||
font-style: italic;
|
||||
font-display: swap;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Lato';
|
||||
src: url('./fonts/Lato/Lato-Light.eot');
|
||||
src: url('./fonts/Lato/Lato-Light.eot?#iefix') format('embedded-opentype'),
|
||||
url('./fonts/Lato/Lato-Light.woff2') format('woff2'),
|
||||
url('./fonts/Lato/Lato-Light.woff') format('woff'),
|
||||
url('./fonts/Lato/Lato-Light.ttf') format('truetype'),
|
||||
url('./fonts/Lato/Lato-Light.svg#Lato-Light') format('svg');
|
||||
font-family: "Lato";
|
||||
src: url("./fonts/Lato/Lato-Light.eot");
|
||||
src: url("./fonts/Lato/Lato-Light.eot?#iefix") format("embedded-opentype"),
|
||||
url("./fonts/Lato/Lato-Light.woff2") format("woff2"),
|
||||
url("./fonts/Lato/Lato-Light.woff") format("woff"),
|
||||
url("./fonts/Lato/Lato-Light.ttf") format("truetype"),
|
||||
url("./fonts/Lato/Lato-Light.svg#Lato-Light") format("svg");
|
||||
font-weight: 300;
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Lato';
|
||||
src: url('./fonts/Lato/Lato-Black.eot');
|
||||
src: url('./fonts/Lato/Lato-Black.eot?#iefix') format('embedded-opentype'),
|
||||
url('./fonts/Lato/Lato-Black.woff2') format('woff2'),
|
||||
url('./fonts/Lato/Lato-Black.woff') format('woff'),
|
||||
url('./fonts/Lato/Lato-Black.ttf') format('truetype'),
|
||||
url('./fonts/Lato/Lato-Black.svg#Lato-Black') format('svg');
|
||||
font-family: "Lato";
|
||||
src: url("./fonts/Lato/Lato-Black.eot");
|
||||
src: url("./fonts/Lato/Lato-Black.eot?#iefix") format("embedded-opentype"),
|
||||
url("./fonts/Lato/Lato-Black.woff2") format("woff2"),
|
||||
url("./fonts/Lato/Lato-Black.woff") format("woff"),
|
||||
url("./fonts/Lato/Lato-Black.ttf") format("truetype"),
|
||||
url("./fonts/Lato/Lato-Black.svg#Lato-Black") format("svg");
|
||||
font-weight: 900;
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Lato';
|
||||
src: url('./fonts/Lato/Lato-Italic.eot');
|
||||
src: url('./fonts/Lato/Lato-Italic.eot?#iefix') format('embedded-opentype'),
|
||||
url('./fonts/Lato/Lato-Italic.woff2') format('woff2'),
|
||||
url('./fonts/Lato/Lato-Italic.woff') format('woff'),
|
||||
url('./fonts/Lato/Lato-Italic.ttf') format('truetype'),
|
||||
url('./fonts/Lato/Lato-Italic.svg#Lato-Italic') format('svg');
|
||||
font-family: "Lato";
|
||||
src: url("./fonts/Lato/Lato-Italic.eot");
|
||||
src: url("./fonts/Lato/Lato-Italic.eot?#iefix") format("embedded-opentype"),
|
||||
url("./fonts/Lato/Lato-Italic.woff2") format("woff2"),
|
||||
url("./fonts/Lato/Lato-Italic.woff") format("woff"),
|
||||
url("./fonts/Lato/Lato-Italic.ttf") format("truetype"),
|
||||
url("./fonts/Lato/Lato-Italic.svg#Lato-Italic") format("svg");
|
||||
font-weight: normal;
|
||||
font-style: italic;
|
||||
font-display: swap;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Lato Hairline';
|
||||
src: url('./fonts/Lato/Lato-Hairline.eot');
|
||||
src: url('./fonts/Lato/Lato-Hairline.eot?#iefix') format('embedded-opentype'),
|
||||
url('./fonts/Lato/Lato-Hairline.woff2') format('woff2'),
|
||||
url('./fonts/Lato/Lato-Hairline.woff') format('woff'),
|
||||
url('./fonts/Lato/Lato-Hairline.ttf') format('truetype'),
|
||||
url('./fonts/Lato/Lato-Hairline.svg#Lato-Hairline') format('svg');
|
||||
font-family: "Lato Hairline";
|
||||
src: url("./fonts/Lato/Lato-Hairline.eot");
|
||||
src: url("./fonts/Lato/Lato-Hairline.eot?#iefix") format("embedded-opentype"),
|
||||
url("./fonts/Lato/Lato-Hairline.woff2") format("woff2"),
|
||||
url("./fonts/Lato/Lato-Hairline.woff") format("woff"),
|
||||
url("./fonts/Lato/Lato-Hairline.ttf") format("truetype"),
|
||||
url("./fonts/Lato/Lato-Hairline.svg#Lato-Hairline") format("svg");
|
||||
font-weight: 300;
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Lato';
|
||||
src: url('./fonts/Lato/Lato-Regular.eot');
|
||||
src: url('./fonts/Lato/Lato-Regular.eot?#iefix') format('embedded-opentype'),
|
||||
url('./fonts/Lato/Lato-Regular.woff2') format('woff2'),
|
||||
url('./fonts/Lato/Lato-Regular.woff') format('woff'),
|
||||
url('./fonts/Lato/Lato-Regular.ttf') format('truetype'),
|
||||
url('./fonts/Lato/Lato-Regular.svg#Lato-Regular') format('svg');
|
||||
font-family: "Lato";
|
||||
src: url("./fonts/Lato/Lato-Regular.eot");
|
||||
src: url("./fonts/Lato/Lato-Regular.eot?#iefix") format("embedded-opentype"),
|
||||
url("./fonts/Lato/Lato-Regular.woff2") format("woff2"),
|
||||
url("./fonts/Lato/Lato-Regular.woff") format("woff"),
|
||||
url("./fonts/Lato/Lato-Regular.ttf") format("truetype"),
|
||||
url("./fonts/Lato/Lato-Regular.svg#Lato-Regular") format("svg");
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Lato Hairline';
|
||||
src: url('./fonts/Lato/Lato-HairlineItalic.eot');
|
||||
src: url('./fonts/Lato/Lato-HairlineItalic.eot?#iefix') format('embedded-opentype'),
|
||||
url('./fonts/Lato/Lato-HairlineItalic.woff2') format('woff2'),
|
||||
url('./fonts/Lato/Lato-HairlineItalic.woff') format('woff'),
|
||||
url('./fonts/Lato/Lato-HairlineItalic.ttf') format('truetype'),
|
||||
url('./fonts/Lato/Lato-HairlineItalic.svg#Lato-HairlineItalic') format('svg');
|
||||
font-family: "Lato Hairline";
|
||||
src: url("./fonts/Lato/Lato-HairlineItalic.eot");
|
||||
src: url("./fonts/Lato/Lato-HairlineItalic.eot?#iefix")
|
||||
format("embedded-opentype"),
|
||||
url("./fonts/Lato/Lato-HairlineItalic.woff2") format("woff2"),
|
||||
url("./fonts/Lato/Lato-HairlineItalic.woff") format("woff"),
|
||||
url("./fonts/Lato/Lato-HairlineItalic.ttf") format("truetype"),
|
||||
url("./fonts/Lato/Lato-HairlineItalic.svg#Lato-HairlineItalic")
|
||||
format("svg");
|
||||
font-weight: 300;
|
||||
font-style: italic;
|
||||
font-display: swap;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Lato';
|
||||
src: url('./fonts/Lato/Lato-LightItalic.eot');
|
||||
src: url('./fonts/Lato/Lato-LightItalic.eot?#iefix') format('embedded-opentype'),
|
||||
url('./fonts/Lato/Lato-LightItalic.woff2') format('woff2'),
|
||||
url('./fonts/Lato/Lato-LightItalic.woff') format('woff'),
|
||||
url('./fonts/Lato/Lato-LightItalic.ttf') format('truetype'),
|
||||
url('./fonts/Lato/Lato-LightItalic.svg#Lato-LightItalic') format('svg');
|
||||
font-family: "Lato";
|
||||
src: url("./fonts/Lato/Lato-LightItalic.eot");
|
||||
src: url("./fonts/Lato/Lato-LightItalic.eot?#iefix")
|
||||
format("embedded-opentype"),
|
||||
url("./fonts/Lato/Lato-LightItalic.woff2") format("woff2"),
|
||||
url("./fonts/Lato/Lato-LightItalic.woff") format("woff"),
|
||||
url("./fonts/Lato/Lato-LightItalic.ttf") format("truetype"),
|
||||
url("./fonts/Lato/Lato-LightItalic.svg#Lato-LightItalic") format("svg");
|
||||
font-weight: 300;
|
||||
font-style: italic;
|
||||
font-display: swap;
|
||||
|
||||
@@ -76,6 +76,9 @@ export const deleteCookie = (name: string) => {
|
||||
export const clearSession = () => {
|
||||
storage.removeItem("token");
|
||||
deleteCookie("token");
|
||||
for (let i = 1; i < 10; i++) {
|
||||
deleteCookie(`token${i}`);
|
||||
}
|
||||
};
|
||||
|
||||
// timeFromDate gets time string from date input
|
||||
@@ -556,3 +559,29 @@ export const prettyNumber = (usage: number | undefined) => {
|
||||
|
||||
return usage.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",");
|
||||
};
|
||||
|
||||
export const representationNumber = (number: number | undefined) => {
|
||||
if (number === undefined) {
|
||||
return "0";
|
||||
}
|
||||
|
||||
let returnValue = number.toString();
|
||||
let unit = "";
|
||||
|
||||
if (number > 999 && number < 1000000) {
|
||||
returnValue = (number / 1000).toFixed(1); // convert to K, numbers > 999
|
||||
unit = "K";
|
||||
} else if (number >= 1000000 && number < 1000000000) {
|
||||
returnValue = (number / 1000000).toFixed(1); // convert to M, numbers >= 1 million
|
||||
unit = "M";
|
||||
} else if (number >= 1000000000) {
|
||||
returnValue = (number / 1000000000).toFixed(1); // convert to B, numbers >= 1 billion
|
||||
unit = "B";
|
||||
}
|
||||
|
||||
if (returnValue.endsWith(".0")) {
|
||||
returnValue = returnValue.slice(0, -2);
|
||||
}
|
||||
|
||||
return `${returnValue}${unit}`;
|
||||
};
|
||||
|
||||
38
portal-ui/src/icons/UptimeIcon.tsx
Normal file
38
portal-ui/src/icons/UptimeIcon.tsx
Normal file
@@ -0,0 +1,38 @@
|
||||
// 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 "@material-ui/core";
|
||||
|
||||
const UptimeIcon = (props: SvgIconProps) => {
|
||||
return (
|
||||
<SvgIcon {...props}>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 206.043 254.417">
|
||||
<path
|
||||
data-name="Sustracci\xF3n 3"
|
||||
d="M194.711 153.049H184.19c.012-.6.016-1.138.016-1.653a81.643 81.643 0 00-1.061-13.146h-64.129V60.127l25.357 11.174 44.693 33.842 4.191 33.167a52.729 52.729 0 015.316 4.4 22 22 0 013.371 3.923c.928 1.466 1.24 2.7.932 3.672-.575 1.822-3.321 2.744-8.165 2.744z"
|
||||
fill="#e3e3e3"
|
||||
/>
|
||||
<path
|
||||
data-name="Uni\xF3n 9"
|
||||
d="M0 151.401a102.413 102.413 0 016.553-36.173 102.779 102.779 0 0118.063-30.583 103.552 103.552 0 0112.6-12.447 103.819 103.819 0 0114.568-10.149 102.151 102.151 0 0133.875-12.207l.58-.1v22.724l-.393.088a80.116 80.116 0 00-25.221 10.222 81.119 81.119 0 00-20.129 17.684 80.667 80.667 0 00-13.328 23.446 80.291 80.291 0 00-4.822 27.494 80.772 80.772 0 0080.682 80.678 80.772 80.772 0 0080.684-80.678 80.257 80.257 0 00-4.957-27.862 80.6 80.6 0 00-13.686-23.672 81.1 81.1 0 00-20.631-17.694 79.844 79.844 0 00-25.793-9.942l-.4-.083v-22.65l.576.088a101.976 101.976 0 0134.443 11.887 104.181 104.181 0 0114.84 10.109 105.131 105.131 0 0112.836 12.477 102.82 102.82 0 0118.416 30.8 102.374 102.374 0 016.7 36.542 103.136 103.136 0 01-103.02 103.018A103.137 103.137 0 010 151.401zm103.584 9.849a9.94 9.94 0 01-1.012-.054c-4.676-.093-9.285-3.011-9.285-8.749V30.28L82.496 40.331c-8.852 8.248-22.311-4.3-13.459-12.541L96.014 2.649a10.033 10.033 0 0113.582 0l26.982 25.141c8.854 8.243-4.611 20.789-13.469 12.541L112.328 30.28v112.971h41a9 9 0 019 9 9 9 0 01-9 8.994z"
|
||||
/>
|
||||
</svg>
|
||||
</SvgIcon>
|
||||
);
|
||||
};
|
||||
|
||||
export default UptimeIcon;
|
||||
@@ -98,3 +98,4 @@ export { default as FileVideoIcon } from "./FileVideoIcon";
|
||||
|
||||
export { default as ArrowRightIcon } from "./ArrowRightIcon";
|
||||
export { default as CalendarIcon } from "./CalendarIcon";
|
||||
export { default as UptimeIcon } from "./UptimeIcon";
|
||||
|
||||
@@ -88,6 +88,9 @@ const styles = (theme: Theme) =>
|
||||
theaderSearchLabel: {
|
||||
color: theme.palette.grey["400"],
|
||||
},
|
||||
addBucket: {
|
||||
marginRight: 8,
|
||||
},
|
||||
theaderSearch: {
|
||||
borderColor: theme.palette.grey["200"],
|
||||
"& .MuiInputBase-input": {
|
||||
@@ -102,9 +105,6 @@ const styles = (theme: Theme) =>
|
||||
},
|
||||
},
|
||||
},
|
||||
addBucket: {
|
||||
marginRight: 8,
|
||||
},
|
||||
actionHeaderItems: {
|
||||
"@media (min-width: 320px)": {
|
||||
marginTop: 8,
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import React, { useState } from "react";
|
||||
import React, { useCallback, useEffect, useState } from "react";
|
||||
import ModalWrapper from "../../../../Common/ModalWrapper/ModalWrapper";
|
||||
import { Button, Grid } from "@material-ui/core";
|
||||
import InputBoxWrapper from "../../../../Common/FormComponents/InputBoxWrapper/InputBoxWrapper";
|
||||
@@ -54,24 +54,51 @@ const CreateFolderModal = ({
|
||||
classes,
|
||||
}: ICreateFolder) => {
|
||||
const [pathUrl, setPathUrl] = useState("");
|
||||
const [nameInputError, setNameInputError] = useState<string>("");
|
||||
const [isFormValid, setIsFormValid] = useState<boolean>(false);
|
||||
|
||||
const currentPath = `${bucketName}/${folderName}`;
|
||||
const currentPath = `${bucketName}/${atob(folderName)}`;
|
||||
|
||||
const resetForm = () => {
|
||||
setPathUrl("");
|
||||
};
|
||||
|
||||
const createProcess = () => {
|
||||
const newPath = `/buckets/${bucketName}/browse/${
|
||||
folderName !== "" ? `${folderName}/` : ""
|
||||
}${pathUrl}`;
|
||||
|
||||
let folderPath = "";
|
||||
if (folderName !== "") {
|
||||
const decodedFolderName = atob(folderName);
|
||||
folderPath = decodedFolderName.endsWith("/")
|
||||
? decodedFolderName
|
||||
: `${decodedFolderName}/`;
|
||||
}
|
||||
const newPath = `/buckets/${bucketName}/browse/${btoa(
|
||||
`${folderPath}${pathUrl}`
|
||||
)}/`;
|
||||
history.push(newPath);
|
||||
|
||||
setFileModeEnabled(false);
|
||||
onClose();
|
||||
};
|
||||
|
||||
const validPathURL = useCallback(() => {
|
||||
const patternAgainst = /^[a-zA-Z0-9*'#-\[\]_/&.@\s()]+$/; // Only allow uppercase, numbers, dashes and underscores
|
||||
if (patternAgainst.test(pathUrl)) {
|
||||
setNameInputError("");
|
||||
return true;
|
||||
}
|
||||
setNameInputError(
|
||||
"Please verify the folder path contains valid characters only (letters, numbers and some special characters)."
|
||||
);
|
||||
return false;
|
||||
}, [pathUrl]);
|
||||
|
||||
useEffect(() => {
|
||||
let valid = true;
|
||||
if (pathUrl.trim().length === 0 || !validPathURL()) {
|
||||
valid = false;
|
||||
}
|
||||
setIsFormValid(valid);
|
||||
}, [pathUrl]);
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<ModalWrapper
|
||||
@@ -91,6 +118,8 @@ const CreateFolderModal = ({
|
||||
onChange={(e) => {
|
||||
setPathUrl(e.target.value);
|
||||
}}
|
||||
required
|
||||
error={nameInputError}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12} className={classes.buttonContainer}>
|
||||
@@ -106,7 +135,7 @@ const CreateFolderModal = ({
|
||||
type="submit"
|
||||
variant="contained"
|
||||
color="primary"
|
||||
disabled={pathUrl.trim() === ""}
|
||||
disabled={!isFormValid}
|
||||
onClick={createProcess}
|
||||
>
|
||||
Go
|
||||
|
||||
@@ -325,12 +325,18 @@ const ListObjects = ({
|
||||
if (rewindDate) {
|
||||
setLoadingRewind(true);
|
||||
const rewindParsed = rewindDate.toISOString();
|
||||
|
||||
let pathPrefix = "";
|
||||
if (internalPaths) {
|
||||
const decodedPath = atob(internalPaths);
|
||||
pathPrefix = decodedPath.endsWith("/")
|
||||
? decodedPath
|
||||
: decodedPath + "/";
|
||||
}
|
||||
api
|
||||
.invoke(
|
||||
"GET",
|
||||
`/api/v1/buckets/${bucketName}/rewind/${rewindParsed}?prefix=${
|
||||
internalPaths ? `${internalPaths}/` : ""
|
||||
`/api/v1/buckets/${bucketName}/rewind/${rewindParsed}${
|
||||
pathPrefix ? `?prefix=${btoa(pathPrefix)}` : ``
|
||||
}`
|
||||
)
|
||||
.then((res: RewindObjectList) => {
|
||||
@@ -364,17 +370,24 @@ const ListObjects = ({
|
||||
|
||||
useEffect(() => {
|
||||
if (loading) {
|
||||
let extraPath = "";
|
||||
let pathPrefix = "";
|
||||
if (internalPaths) {
|
||||
extraPath = `?prefix=${internalPaths}/`;
|
||||
const decodedPath = atob(internalPaths);
|
||||
pathPrefix = decodedPath.endsWith("/")
|
||||
? decodedPath
|
||||
: decodedPath + "/";
|
||||
}
|
||||
|
||||
let currentTimestamp = Date.now() + 0;
|
||||
let currentTimestamp = Date.now();
|
||||
setLoadingStartTime(currentTimestamp);
|
||||
setLoadingMessage(defLoading);
|
||||
|
||||
api
|
||||
.invoke("GET", `/api/v1/buckets/${bucketName}/objects${extraPath}`)
|
||||
.invoke(
|
||||
"GET",
|
||||
`/api/v1/buckets/${bucketName}/objects${
|
||||
pathPrefix ? `?prefix=${btoa(pathPrefix)}` : ``
|
||||
}`
|
||||
)
|
||||
.then((res: BucketObjectsList) => {
|
||||
const records: BucketObject[] = res.objects || [];
|
||||
const folders: BucketObject[] = [];
|
||||
@@ -389,19 +402,26 @@ const ListObjects = ({
|
||||
files.push(record);
|
||||
}
|
||||
});
|
||||
|
||||
const recordsInElement = [...folders, ...files];
|
||||
|
||||
setRecords(recordsInElement);
|
||||
// In case no objects were retrieved, We check if item is a file
|
||||
if (!res.objects && extraPath !== "") {
|
||||
if (!res.objects && pathPrefix !== "") {
|
||||
if (rewindEnabled) {
|
||||
const rewindParsed = rewindDate.toISOString();
|
||||
|
||||
let pathPrefix = "";
|
||||
if (internalPaths) {
|
||||
const decodedPath = atob(internalPaths);
|
||||
pathPrefix = decodedPath.endsWith("/")
|
||||
? decodedPath
|
||||
: decodedPath + "/";
|
||||
}
|
||||
|
||||
api
|
||||
.invoke(
|
||||
"GET",
|
||||
`/api/v1/buckets/${bucketName}/rewind/${rewindParsed}?prefix=${
|
||||
internalPaths ? `${internalPaths}/` : ""
|
||||
`/api/v1/buckets/${bucketName}/rewind/${rewindParsed}${
|
||||
pathPrefix ? `?prefix=${btoa(pathPrefix)}` : ``
|
||||
}`
|
||||
)
|
||||
.then((res: RewindObjectList) => {
|
||||
@@ -426,7 +446,9 @@ const ListObjects = ({
|
||||
api
|
||||
.invoke(
|
||||
"GET",
|
||||
`/api/v1/buckets/${bucketName}/objects?prefix=${internalPaths}`
|
||||
`/api/v1/buckets/${bucketName}/objects${
|
||||
internalPaths ? `?prefix=${internalPaths}` : ``
|
||||
}`
|
||||
)
|
||||
.then((res: BucketObjectsList) => {
|
||||
//It is a file since it has elements in the object, setting file flag and waiting for component mount
|
||||
@@ -497,7 +519,7 @@ const ListObjects = ({
|
||||
setCreateFolderOpen(false);
|
||||
};
|
||||
|
||||
const upload = (e: any, bucketName: string, path: string) => {
|
||||
const upload = (e: any, bucketName: string, encodedPath: string) => {
|
||||
if (
|
||||
e === null ||
|
||||
e === undefined ||
|
||||
@@ -509,12 +531,11 @@ const ListObjects = ({
|
||||
e.preventDefault();
|
||||
let files = e.target.files;
|
||||
let uploadUrl = `${baseUrl}/api/v1/buckets/${bucketName}/objects/upload`;
|
||||
if (path !== "") {
|
||||
const encodedPath = encodeURIComponent(path);
|
||||
if (encodedPath !== "") {
|
||||
uploadUrl = `${uploadUrl}?prefix=${encodedPath}`;
|
||||
}
|
||||
let xhr = new XMLHttpRequest();
|
||||
const areMultipleFiles = files.length > 1 ? true : false;
|
||||
const areMultipleFiles = files.length > 1;
|
||||
const errorMessage = `An error occurred while uploading the file${
|
||||
areMultipleFiles ? "s" : ""
|
||||
}.`;
|
||||
@@ -602,30 +623,20 @@ const ListObjects = ({
|
||||
};
|
||||
|
||||
const openPath = (idElement: string) => {
|
||||
const currentPath = get(match, "url", `/buckets/${bucketName}`);
|
||||
|
||||
// Element is a folder, we redirect to it
|
||||
if (idElement.endsWith("/")) {
|
||||
const idElementClean = idElement
|
||||
.substr(0, idElement.length - 1)
|
||||
.split("/");
|
||||
const lastIndex = idElementClean.length - 1;
|
||||
const newPath = `${currentPath}/${idElementClean[lastIndex]}`;
|
||||
|
||||
history.push(newPath);
|
||||
return;
|
||||
}
|
||||
// Element is a file. we open details here
|
||||
const pathInArray = idElement.split("/");
|
||||
const fileName = pathInArray[pathInArray.length - 1];
|
||||
const newPath = `${currentPath}/${fileName}`;
|
||||
|
||||
const newPath = `/buckets/${bucketName}/browse${
|
||||
idElement ? `/${btoa(idElement)}` : ``
|
||||
}`;
|
||||
history.push(newPath);
|
||||
return;
|
||||
};
|
||||
|
||||
const uploadObject = (e: any): void => {
|
||||
upload(e, bucketName, `${internalPaths}/`);
|
||||
let pathPrefix = "";
|
||||
if (internalPaths) {
|
||||
const decodedPath = atob(internalPaths);
|
||||
pathPrefix = decodedPath.endsWith("/") ? decodedPath : decodedPath + "/";
|
||||
}
|
||||
upload(e, bucketName, btoa(pathPrefix));
|
||||
};
|
||||
|
||||
const openPreview = (fileObject: BucketObject) => {
|
||||
@@ -884,7 +895,10 @@ const ListObjects = ({
|
||||
|
||||
const ccPath = internalPaths.split("/").pop();
|
||||
|
||||
const pageTitle = ccPath !== "" ? ccPath : "/";
|
||||
const pageTitle = ccPath !== "" ? atob(ccPath) : "/";
|
||||
// console.log("pageTitle", pageTitle);
|
||||
const currentPath = pageTitle.split("/").filter((i: string) => i !== "");
|
||||
// console.log("currentPath", currentPath);
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
@@ -948,12 +962,14 @@ const ListObjects = ({
|
||||
<FolderIcon width={40} />
|
||||
</Fragment>
|
||||
}
|
||||
title={pageTitle}
|
||||
title={
|
||||
currentPath.length > 0 ? currentPath[currentPath.length - 1] : "/"
|
||||
}
|
||||
subTitle={
|
||||
<Fragment>
|
||||
<BrowserBreadcrumbs
|
||||
bucketName={bucketName}
|
||||
internalPaths={internalPaths}
|
||||
internalPaths={pageTitle}
|
||||
/>
|
||||
</Fragment>
|
||||
}
|
||||
|
||||
@@ -107,7 +107,7 @@ const RewindEnable = ({
|
||||
name="status"
|
||||
checked={rewindEnableButton}
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setRewindEnableButton(false);
|
||||
setRewindEnableButton(e.target.checked);
|
||||
}}
|
||||
label={"Current Status"}
|
||||
indicatorLabels={["Enabled", "Disabled"]}
|
||||
|
||||
@@ -247,6 +247,7 @@ const ObjectDetails = ({
|
||||
const [selectedTag, setSelectedTag] = useState<string[]>(["", ""]);
|
||||
const [legalholdOpen, setLegalholdOpen] = useState<boolean>(false);
|
||||
const [actualInfo, setActualInfo] = useState<IFileInfo | null>(null);
|
||||
const [objectToShare, setObjectToShare] = useState<IFileInfo | null>(null);
|
||||
const [versions, setVersions] = useState<IFileInfo[]>([]);
|
||||
const [filterVersion, setFilterVersion] = useState<string>("");
|
||||
const [deleteOpen, setDeleteOpen] = useState<boolean>(false);
|
||||
@@ -255,17 +256,23 @@ const ObjectDetails = ({
|
||||
const [selectedTab, setSelectedTab] = useState<number>(0);
|
||||
|
||||
const internalPaths = get(match.params, "subpaths", "");
|
||||
const internalPathsDecoded = atob(internalPaths) || "";
|
||||
const bucketName = match.params["bucketName"];
|
||||
const allPathData = internalPaths.split("/");
|
||||
const currentItem = allPathData.pop();
|
||||
const allPathData = internalPathsDecoded.split("/");
|
||||
const currentItem = allPathData.pop() || "";
|
||||
|
||||
// calculate object name to display
|
||||
let objectNameArray: string[] = [];
|
||||
if (actualInfo) {
|
||||
objectNameArray = actualInfo.name.split("/");
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (loadObjectData) {
|
||||
const encodedPath = encodeURIComponent(internalPaths);
|
||||
api
|
||||
.invoke(
|
||||
"GET",
|
||||
`/api/v1/buckets/${bucketName}/objects?prefix=${encodedPath}${
|
||||
`/api/v1/buckets/${bucketName}/objects?prefix=${internalPaths}${
|
||||
distributedSetup ? "&with_versions=true" : ""
|
||||
}`
|
||||
)
|
||||
@@ -299,11 +306,10 @@ const ObjectDetails = ({
|
||||
|
||||
useEffect(() => {
|
||||
if (metadataLoad) {
|
||||
const encodedPath = encodeURIComponent(internalPaths);
|
||||
api
|
||||
.invoke(
|
||||
"GET",
|
||||
`/api/v1/buckets/${bucketName}/objects?prefix=${encodedPath}&with_metadata=true`
|
||||
`/api/v1/buckets/${bucketName}/objects?prefix=${internalPaths}&with_metadata=true`
|
||||
)
|
||||
.then((res: FileInfoResponse) => {
|
||||
const fileData = res.objects[0];
|
||||
@@ -340,6 +346,7 @@ const ObjectDetails = ({
|
||||
};
|
||||
|
||||
const closeShareModal = () => {
|
||||
setObjectToShare(null);
|
||||
setShareFileModalOpen(false);
|
||||
};
|
||||
|
||||
@@ -367,8 +374,11 @@ const ObjectDetails = ({
|
||||
const tableActions: ItemActions[] = [
|
||||
{
|
||||
type: "share",
|
||||
onClick: shareObject,
|
||||
sendOnlyId: true,
|
||||
onClick: (item: any) => {
|
||||
setObjectToShare(item);
|
||||
shareObject();
|
||||
},
|
||||
sendOnlyId: false,
|
||||
disableButtonFunction: (item: string) => {
|
||||
const element = versions.find((elm) => elm.version_id === item);
|
||||
if (element && element.is_delete_marker) {
|
||||
@@ -445,7 +455,7 @@ const ObjectDetails = ({
|
||||
open={shareFileModalOpen}
|
||||
closeModalAndRefresh={closeShareModal}
|
||||
bucketName={bucketName}
|
||||
dataObject={actualInfo}
|
||||
dataObject={objectToShare || actualInfo}
|
||||
/>
|
||||
)}
|
||||
{retentionModalOpen && actualInfo && (
|
||||
@@ -479,7 +489,7 @@ const ObjectDetails = ({
|
||||
<DeleteTagModal
|
||||
deleteOpen={deleteTagModalOpen}
|
||||
currentTags={actualInfo.tags}
|
||||
selectedObject={internalPaths}
|
||||
selectedObject={actualInfo.name}
|
||||
versionId={actualInfo.version_id}
|
||||
bucketName={bucketName}
|
||||
onCloseAndUpdate={closeDeleteTagModal}
|
||||
@@ -490,7 +500,7 @@ const ObjectDetails = ({
|
||||
<SetLegalHoldModal
|
||||
open={legalholdOpen}
|
||||
closeModalAndRefresh={closeLegalholdModal}
|
||||
objectName={internalPaths}
|
||||
objectName={actualInfo.name}
|
||||
bucketName={bucketName}
|
||||
actualInfo={actualInfo}
|
||||
/>
|
||||
@@ -512,12 +522,16 @@ const ObjectDetails = ({
|
||||
<ObjectBrowserIcon width={40} />
|
||||
</Fragment>
|
||||
}
|
||||
title={currentItem}
|
||||
title={
|
||||
objectNameArray.length > 0
|
||||
? objectNameArray[objectNameArray.length - 1]
|
||||
: actualInfo.name
|
||||
}
|
||||
subTitle={
|
||||
<Fragment>
|
||||
<BrowserBreadcrumbs
|
||||
bucketName={bucketName}
|
||||
internalPaths={internalPaths}
|
||||
internalPaths={actualInfo.name}
|
||||
/>
|
||||
</Fragment>
|
||||
}
|
||||
@@ -655,7 +669,7 @@ const ObjectDetails = ({
|
||||
<td className={classes.capitalizeFirst}>
|
||||
{actualInfo.retention_mode
|
||||
? actualInfo.retention_mode.toLowerCase()
|
||||
: "Undefined"}
|
||||
: "None"}
|
||||
<IconButton
|
||||
color="primary"
|
||||
aria-label="retention"
|
||||
|
||||
@@ -76,7 +76,9 @@ const SetLegalHoldModal = ({
|
||||
api
|
||||
.invoke(
|
||||
"PUT",
|
||||
`/api/v1/buckets/${bucketName}/objects/legalhold?prefix=${objectName}&version_id=${versionId}`,
|
||||
`/api/v1/buckets/${bucketName}/objects/legalhold?prefix=${btoa(
|
||||
objectName
|
||||
)}&version_id=${versionId}`,
|
||||
{ status: legalHoldEnabled ? "enabled" : "disabled" }
|
||||
)
|
||||
.then(() => {
|
||||
|
||||
@@ -119,7 +119,9 @@ const SetRetention = ({
|
||||
api
|
||||
.invoke(
|
||||
"PUT",
|
||||
`/api/v1/buckets/${bucketName}/objects/retention?prefix=${selectedObject}&version_id=${versionId}`,
|
||||
`/api/v1/buckets/${bucketName}/objects/retention?prefix=${btoa(
|
||||
selectedObject
|
||||
)}&version_id=${versionId}`,
|
||||
{
|
||||
expires: expireDate,
|
||||
mode: type,
|
||||
@@ -142,7 +144,9 @@ const SetRetention = ({
|
||||
api
|
||||
.invoke(
|
||||
"DELETE",
|
||||
`/api/v1/buckets/${bucketName}/objects/retention?prefix=${selectedObject}&version_id=${versionId}`
|
||||
`/api/v1/buckets/${bucketName}/objects/retention?prefix=${btoa(
|
||||
selectedObject
|
||||
)}&version_id=${versionId}`
|
||||
)
|
||||
.then(() => {
|
||||
setIsSaving(false);
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import React, { useEffect, useState } from "react";
|
||||
import React, { useEffect, useState, Fragment } from "react";
|
||||
import get from "lodash/get";
|
||||
import { connect } from "react-redux";
|
||||
import { createStyles, Theme, withStyles } from "@material-ui/core/styles";
|
||||
@@ -34,6 +34,7 @@ import api from "../../../../../../common/api";
|
||||
import ModalWrapper from "../../../../Common/ModalWrapper/ModalWrapper";
|
||||
import PredefinedList from "../../../../Common/FormComponents/PredefinedList/PredefinedList";
|
||||
import DaysSelector from "../../../../Common/FormComponents/DaysSelector/DaysSelector";
|
||||
import { LinearProgress } from "@material-ui/core";
|
||||
|
||||
const styles = (theme: Theme) =>
|
||||
createStyles({
|
||||
@@ -68,9 +69,11 @@ const ShareFile = ({
|
||||
setModalErrorSnackMessage,
|
||||
}: IShareFileProps) => {
|
||||
const [shareURL, setShareURL] = useState<string>("");
|
||||
const [isLoadingVersion, setIsLoadingVersion] = useState<boolean>(true);
|
||||
const [isLoadingFile, setIsLoadingFile] = useState<boolean>(false);
|
||||
const [selectedDate, setSelectedDate] = useState<string>("");
|
||||
const [dateValid, setDateValid] = useState<boolean>(true);
|
||||
const [versionID, setVersionID] = useState<string>("null");
|
||||
|
||||
const initialDate = new Date();
|
||||
|
||||
@@ -85,7 +88,49 @@ const ShareFile = ({
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (dateValid) {
|
||||
// In case version is undefined, we get the latest version of the object
|
||||
if (dataObject.version_id === undefined) {
|
||||
// In case it is not distributed setup, then we default to "null";
|
||||
if (distributedSetup) {
|
||||
api
|
||||
.invoke(
|
||||
"GET",
|
||||
`/api/v1/buckets/${bucketName}/objects?prefix=${btoa(
|
||||
dataObject.name
|
||||
)}${distributedSetup ? "&with_versions=true" : ""}`
|
||||
)
|
||||
.then((res: IFileInfo[]) => {
|
||||
const result = get(res, "objects", []);
|
||||
|
||||
const latestVersion = result.find(
|
||||
(elem: IFileInfo) => elem.is_latest
|
||||
);
|
||||
|
||||
if (latestVersion) {
|
||||
setVersionID(latestVersion.version_id);
|
||||
return;
|
||||
}
|
||||
|
||||
// Version couldn't ve retrieved, we default
|
||||
setVersionID("null");
|
||||
})
|
||||
.catch((error: ErrorResponseHandler) => {
|
||||
setModalErrorSnackMessage(error);
|
||||
});
|
||||
|
||||
setIsLoadingVersion(false);
|
||||
return;
|
||||
}
|
||||
setVersionID("null");
|
||||
setIsLoadingVersion(false);
|
||||
return;
|
||||
}
|
||||
setVersionID(dataObject.version_id || "null");
|
||||
setIsLoadingVersion(false);
|
||||
}, [bucketName, dataObject, distributedSetup, setModalErrorSnackMessage]);
|
||||
|
||||
useEffect(() => {
|
||||
if (dateValid && !isLoadingVersion) {
|
||||
setIsLoadingFile(true);
|
||||
setShareURL("");
|
||||
|
||||
@@ -95,14 +140,12 @@ const ShareFile = ({
|
||||
const diffDate = slDate.getTime() - currDate.getTime();
|
||||
|
||||
if (diffDate > 0) {
|
||||
const versID = distributedSetup ? dataObject.version_id : "null";
|
||||
|
||||
api
|
||||
.invoke(
|
||||
"GET",
|
||||
`/api/v1/buckets/${bucketName}/objects/share?prefix=${
|
||||
`/api/v1/buckets/${bucketName}/objects/share?prefix=${btoa(
|
||||
dataObject.name
|
||||
}&version_id=${versID || "null"}${
|
||||
)}&version_id=${versionID}${
|
||||
selectedDate !== "" ? `&expires=${diffDate}ms` : ""
|
||||
}`
|
||||
)
|
||||
@@ -125,6 +168,8 @@ const ShareFile = ({
|
||||
setShareURL,
|
||||
setModalErrorSnackMessage,
|
||||
distributedSetup,
|
||||
isLoadingVersion,
|
||||
versionID,
|
||||
]);
|
||||
|
||||
return (
|
||||
@@ -137,42 +182,51 @@ const ShareFile = ({
|
||||
}}
|
||||
>
|
||||
<Grid container className={classes.modalContent}>
|
||||
<Grid item xs={12} className={classes.moduleDescription}>
|
||||
This module generates a temporary URL with integrated access
|
||||
credentials for sharing objects for up to 7 days.
|
||||
<br />
|
||||
The temporary URL expires after the configured time limit.
|
||||
</Grid>
|
||||
<Grid item xs={12} className={classes.dateContainer}>
|
||||
<DaysSelector
|
||||
initialDate={initialDate}
|
||||
id="date"
|
||||
label="Active for"
|
||||
maxDays={7}
|
||||
onChange={dateChanged}
|
||||
entity="Link"
|
||||
/>
|
||||
</Grid>
|
||||
<Grid container item xs={12}>
|
||||
<Grid item xs={10}>
|
||||
<PredefinedList content={shareURL} />
|
||||
{isLoadingVersion && (
|
||||
<Grid item xs={12}>
|
||||
<LinearProgress />
|
||||
</Grid>
|
||||
<Grid item xs={2} className={classes.copyButtonContainer}>
|
||||
<CopyToClipboard text={shareURL}>
|
||||
<Button
|
||||
variant="contained"
|
||||
color="primary"
|
||||
startIcon={<CopyIcon />}
|
||||
onClick={() => {
|
||||
setModalSnackMessage("Share URL Copied to clipboard");
|
||||
}}
|
||||
disabled={shareURL === "" || isLoadingFile}
|
||||
>
|
||||
Copy
|
||||
</Button>
|
||||
</CopyToClipboard>
|
||||
</Grid>
|
||||
</Grid>
|
||||
)}
|
||||
{!isLoadingVersion && (
|
||||
<Fragment>
|
||||
<Grid item xs={12} className={classes.moduleDescription}>
|
||||
This module generates a temporary URL with integrated access
|
||||
credentials for sharing objects for up to 7 days.
|
||||
<br />
|
||||
The temporary URL expires after the configured time limit.
|
||||
</Grid>
|
||||
<Grid item xs={12} className={classes.dateContainer}>
|
||||
<DaysSelector
|
||||
initialDate={initialDate}
|
||||
id="date"
|
||||
label="Active for"
|
||||
maxDays={7}
|
||||
onChange={dateChanged}
|
||||
entity="Link"
|
||||
/>
|
||||
</Grid>
|
||||
<Grid container item xs={12}>
|
||||
<Grid item xs={10}>
|
||||
<PredefinedList content={shareURL} />
|
||||
</Grid>
|
||||
<Grid item xs={2} className={classes.copyButtonContainer}>
|
||||
<CopyToClipboard text={shareURL}>
|
||||
<Button
|
||||
variant="contained"
|
||||
color="primary"
|
||||
startIcon={<CopyIcon />}
|
||||
onClick={() => {
|
||||
setModalSnackMessage("Share URL Copied to clipboard");
|
||||
}}
|
||||
disabled={shareURL === "" || isLoadingFile}
|
||||
>
|
||||
Copy
|
||||
</Button>
|
||||
</CopyToClipboard>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Fragment>
|
||||
)}
|
||||
</Grid>
|
||||
</ModalWrapper>
|
||||
</React.Fragment>
|
||||
|
||||
@@ -72,7 +72,7 @@ const PreviewFile = ({
|
||||
let path = "";
|
||||
|
||||
if (object) {
|
||||
const encodedPath = encodeURIComponent(object.name);
|
||||
const encodedPath = btoa(object.name);
|
||||
path = `${window.location.origin}/api/v1/buckets/${bucketName}/objects/download?preview=true&prefix=${encodedPath}`;
|
||||
if (object.version_id) {
|
||||
path = path.concat(`&version_id=${object.version_id}`);
|
||||
|
||||
@@ -14,8 +14,6 @@
|
||||
// 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 { isNullOrUndefined } from "util";
|
||||
|
||||
export const download = (
|
||||
bucketName: string,
|
||||
objectPath: string,
|
||||
@@ -25,9 +23,9 @@ export const download = (
|
||||
) => {
|
||||
const anchor = document.createElement("a");
|
||||
document.body.appendChild(anchor);
|
||||
const encodedPath = encodeURIComponent(objectPath);
|
||||
const encodedPath = btoa(objectPath);
|
||||
let path = `/api/v1/buckets/${bucketName}/objects/download?prefix=${encodedPath}`;
|
||||
if (!isNullOrUndefined(versionID) && versionID !== "null") {
|
||||
if (versionID) {
|
||||
path = path.concat(`&version_id=${versionID}`);
|
||||
}
|
||||
window.location.href = path;
|
||||
|
||||
@@ -0,0 +1,245 @@
|
||||
// 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/>.
|
||||
|
||||
// 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, useCallback } from "react";
|
||||
import { connect } from "react-redux";
|
||||
import get from "lodash/get";
|
||||
import ArrowRightIcon from "@material-ui/icons/ArrowRight";
|
||||
import ErrorOutlineIcon from "@material-ui/icons/ErrorOutline";
|
||||
import CloseIcon from "@material-ui/icons/Close";
|
||||
import { createStyles, Theme, withStyles } from "@material-ui/core/styles";
|
||||
import { AppState } from "../../../../../store";
|
||||
import { setErrorSnackMessage } from "../../../../../actions";
|
||||
import { snackBarMessage } from "../../../../../types";
|
||||
import { setModalErrorSnackMessage } from "../../../../../actions";
|
||||
|
||||
interface ImodalErrorProps {
|
||||
customStyle?: any;
|
||||
classes: any;
|
||||
modalSnackMessage: snackBarMessage;
|
||||
displayErrorMessage: typeof setErrorSnackMessage;
|
||||
}
|
||||
|
||||
const styles = (theme: Theme) =>
|
||||
createStyles({
|
||||
modalErrorContainer: {
|
||||
position: "absolute",
|
||||
marginTop: 10,
|
||||
width: "80%",
|
||||
backgroundColor: "#fff",
|
||||
border: "#C72C48 1px solid",
|
||||
borderLeftWidth: 12,
|
||||
borderRadius: 3,
|
||||
zIndex: 1000,
|
||||
padding: "10px 15px",
|
||||
left: "50%",
|
||||
transform: "translateX(-50%)",
|
||||
opacity: 0,
|
||||
transitionDuration: "0.2s",
|
||||
},
|
||||
modalErrorShow: {
|
||||
opacity: 1,
|
||||
},
|
||||
closeButton: {
|
||||
position: "absolute",
|
||||
right: 5,
|
||||
fontSize: "small",
|
||||
border: 0,
|
||||
backgroundColor: "#fff",
|
||||
cursor: "pointer",
|
||||
},
|
||||
errorTitle: {
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
},
|
||||
errorLabel: {
|
||||
color: "#000",
|
||||
fontSize: 18,
|
||||
fontWeight: 500,
|
||||
marginLeft: 5,
|
||||
marginRight: 25,
|
||||
},
|
||||
messageIcon: {
|
||||
color: "#C72C48",
|
||||
display: "flex",
|
||||
"& svg": {
|
||||
width: 32,
|
||||
height: 32,
|
||||
},
|
||||
},
|
||||
simpleError: {
|
||||
marginTop: 5,
|
||||
padding: "2px 5px",
|
||||
fontSize: 16,
|
||||
color: "#000",
|
||||
},
|
||||
detailsButton: {
|
||||
color: "#9C9C9C",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
border: 0,
|
||||
backgroundColor: "transparent",
|
||||
paddingLeft: 5,
|
||||
fontSize: 14,
|
||||
transformDuration: "0.3s",
|
||||
cursor: "pointer",
|
||||
},
|
||||
extraDetailsContainer: {
|
||||
fontStyle: "italic",
|
||||
color: "#9C9C9C",
|
||||
lineHeight: 0,
|
||||
padding: "0 10px",
|
||||
transition: "all .2s ease-in-out",
|
||||
overflow: "hidden",
|
||||
},
|
||||
extraDetailsOpen: {
|
||||
lineHeight: 1,
|
||||
padding: "3px 10px",
|
||||
},
|
||||
arrowElement: {
|
||||
marginLeft: -5,
|
||||
},
|
||||
arrowOpen: {
|
||||
transform: "rotateZ(90deg)",
|
||||
transformDuration: "0.3s",
|
||||
},
|
||||
});
|
||||
|
||||
var timerI: any;
|
||||
|
||||
const startHideTimer = (callbackFunction: () => void) => {
|
||||
timerI = setInterval(callbackFunction, 10000);
|
||||
};
|
||||
|
||||
const stopHideTimer = () => {
|
||||
clearInterval(timerI);
|
||||
};
|
||||
|
||||
const ModalError = ({
|
||||
classes,
|
||||
modalSnackMessage,
|
||||
displayErrorMessage,
|
||||
customStyle,
|
||||
}: ImodalErrorProps) => {
|
||||
const [detailsOpen, setDetailsOpen] = useState<boolean>(false);
|
||||
const [displayErrorMsg, setDisplayErrorMsg] = useState<boolean>(false);
|
||||
|
||||
const closeErrorMessage = useCallback(() => {
|
||||
setDisplayErrorMsg(false);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (!displayErrorMsg) {
|
||||
displayErrorMessage({ detailedError: "", errorMessage: "" });
|
||||
setDetailsOpen(false);
|
||||
//clearInterval(timerI);
|
||||
}
|
||||
}, [displayErrorMessage, displayErrorMsg]);
|
||||
|
||||
useEffect(() => {
|
||||
if (
|
||||
modalSnackMessage.message !== "" &&
|
||||
modalSnackMessage.type === "error"
|
||||
) {
|
||||
//Error message received, we trigger the animation
|
||||
setDisplayErrorMsg(true);
|
||||
//startHideTimer(closeErrorMessage);
|
||||
}
|
||||
}, [closeErrorMessage, modalSnackMessage.message, modalSnackMessage.type]);
|
||||
|
||||
const detailsToggle = () => {
|
||||
setDetailsOpen(!detailsOpen);
|
||||
};
|
||||
|
||||
const message = get(modalSnackMessage, "message", "");
|
||||
const messageDetails = get(modalSnackMessage, "detailedErrorMsg", "");
|
||||
|
||||
if (modalSnackMessage.type !== "error" || message === "") {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<div
|
||||
className={`${classes.modalErrorContainer} ${
|
||||
displayErrorMsg ? classes.modalErrorShow : ""
|
||||
}`}
|
||||
style={customStyle}
|
||||
onMouseOver={stopHideTimer}
|
||||
onMouseLeave={() => startHideTimer(closeErrorMessage)}
|
||||
>
|
||||
<button className={classes.closeButton} onClick={closeErrorMessage}>
|
||||
<CloseIcon />
|
||||
</button>
|
||||
<div className={classes.errorTitle}>
|
||||
<span className={classes.messageIcon}>
|
||||
<ErrorOutlineIcon />
|
||||
</span>
|
||||
<span className={classes.errorLabel}>{message}</span>
|
||||
</div>
|
||||
{messageDetails !== "" && (
|
||||
<Fragment>
|
||||
<div className={classes.detailsContainerLink}>
|
||||
<button className={classes.detailsButton} onClick={detailsToggle}>
|
||||
Details
|
||||
<ArrowRightIcon
|
||||
className={`${classes.arrowElement} ${
|
||||
detailsOpen ? classes.arrowOpen : ""
|
||||
}`}
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
className={`${classes.extraDetailsContainer} ${
|
||||
detailsOpen ? classes.extraDetailsOpen : ""
|
||||
}`}
|
||||
>
|
||||
{messageDetails}
|
||||
</div>
|
||||
</Fragment>
|
||||
)}
|
||||
</div>
|
||||
</Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
const mapState = (state: AppState) => ({
|
||||
modalSnackMessage: state.system.modalSnackBar,
|
||||
});
|
||||
|
||||
const mapDispatchToProps = {
|
||||
displayErrorMessage: setModalErrorSnackMessage,
|
||||
};
|
||||
|
||||
const connector = connect(mapState, mapDispatchToProps);
|
||||
|
||||
export default connector(withStyles(styles)(ModalError));
|
||||
@@ -132,6 +132,7 @@ export const radioIcons = {
|
||||
|
||||
export const containerForHeader = (bottomSpacing: any) => ({
|
||||
container: {
|
||||
position: "relative" as const,
|
||||
padding: "8px 16px 0",
|
||||
"& h6": {
|
||||
color: "#777777",
|
||||
@@ -417,6 +418,7 @@ export const widgetCommon = {
|
||||
minWidth: 280,
|
||||
maxWidth: 1185,
|
||||
border: "#eef1f4 2px solid",
|
||||
backgroundColor: "#fff",
|
||||
borderRadius: 10,
|
||||
width: "100%",
|
||||
padding: 16,
|
||||
@@ -429,6 +431,8 @@ export const widgetCommon = {
|
||||
borderBottom: "#eef1f4 1px solid",
|
||||
paddingBottom: 14,
|
||||
marginBottom: 5,
|
||||
display: "flex" as const,
|
||||
justifyContent: "space-between" as const,
|
||||
},
|
||||
contentContainer: {
|
||||
justifyContent: "center" as const,
|
||||
@@ -468,6 +472,39 @@ export const widgetCommon = {
|
||||
overflow: "hidden" as const,
|
||||
textOverflow: "ellipsis" as const,
|
||||
},
|
||||
zoomChartCont: {
|
||||
position: "relative" as const,
|
||||
height: 340,
|
||||
width: "100%",
|
||||
},
|
||||
zoomChartIcon: {
|
||||
backgroundColor: "transparent",
|
||||
border: 0,
|
||||
padding: 0,
|
||||
cursor: "pointer",
|
||||
"& svg": {
|
||||
color: "#D0D0D0",
|
||||
height: 16,
|
||||
},
|
||||
"&:hover": {
|
||||
"& svg": {
|
||||
color: "#404143",
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export const widgetContainerCommon = {
|
||||
widgetPanelDelimiter: {
|
||||
padding: 10,
|
||||
},
|
||||
dashboardRow: {
|
||||
display: "flex" as const,
|
||||
flexDirection: "row" as const,
|
||||
justifyContent: "flex-start" as const,
|
||||
flexWrap: "wrap" as const,
|
||||
maxWidth: 1180,
|
||||
},
|
||||
};
|
||||
|
||||
export const tooltipCommon = {
|
||||
|
||||
@@ -23,6 +23,7 @@ import { snackBarCommon } from "../FormComponents/common/styleLibrary";
|
||||
import { AppState } from "../../../../store";
|
||||
import { snackBarMessage } from "../../../../types";
|
||||
import { setModalSnackMessage } from "../../../../actions";
|
||||
import ModalError from "../FormComponents/ModalError/ModalError";
|
||||
|
||||
interface IModalProps {
|
||||
classes: any;
|
||||
@@ -124,6 +125,10 @@ const ModalWrapper = ({
|
||||
}: IModalProps) => {
|
||||
const [openSnackbar, setOpenSnackbar] = useState<boolean>(false);
|
||||
|
||||
useEffect(() => {
|
||||
setModalSnackMessage("");
|
||||
}, [setModalSnackMessage]);
|
||||
|
||||
useEffect(() => {
|
||||
if (modalSnackMessage) {
|
||||
if (modalSnackMessage.message === "") {
|
||||
@@ -131,7 +136,9 @@ const ModalWrapper = ({
|
||||
return;
|
||||
}
|
||||
// Open SnackBar
|
||||
setOpenSnackbar(true);
|
||||
if (modalSnackMessage.type !== "error") {
|
||||
setOpenSnackbar(true);
|
||||
}
|
||||
}
|
||||
}, [modalSnackMessage]);
|
||||
|
||||
@@ -169,6 +176,7 @@ const ModalWrapper = ({
|
||||
{...customSize}
|
||||
>
|
||||
<div className={classes.dialogContainer}>
|
||||
<ModalError />
|
||||
<Snackbar
|
||||
open={openSnackbar}
|
||||
className={classes.snackBarModal}
|
||||
|
||||
@@ -25,6 +25,7 @@ import DriveInfoCard from "./DriveInfoCard";
|
||||
import CommonCard from "../CommonCard";
|
||||
import TabSelector from "../../Common/TabSelector/TabSelector";
|
||||
import GeneralUsePaginator from "../../Common/GeneralUsePaginator/GeneralUsePaginator";
|
||||
import { widgetContainerCommon } from "../../Common/FormComponents/common/styleLibrary";
|
||||
|
||||
const styles = (theme: Theme) =>
|
||||
createStyles({
|
||||
@@ -48,6 +49,7 @@ const styles = (theme: Theme) =>
|
||||
maxWidth: 1185,
|
||||
width: "100%",
|
||||
},
|
||||
...widgetContainerCommon,
|
||||
});
|
||||
|
||||
interface IDashboardProps {
|
||||
@@ -93,10 +95,12 @@ const BasicDashboard = ({ classes, usage }: IDashboardProps) => {
|
||||
}
|
||||
return 0;
|
||||
});
|
||||
} else return [];
|
||||
}
|
||||
|
||||
return [];
|
||||
};
|
||||
|
||||
const serverArray = makeServerArray(usage);
|
||||
const serverArray = makeServerArray(usage || null);
|
||||
|
||||
const usageToRepresent = prettyUsage(
|
||||
usage && usage.usage ? usage.usage.toString() : "0"
|
||||
@@ -129,29 +133,65 @@ const BasicDashboard = ({ classes, usage }: IDashboardProps) => {
|
||||
<Grid item xs={12} className={classes.generalStatusTitle}>
|
||||
General Status
|
||||
</Grid>
|
||||
<Grid item xs={12} className={classes.generalStatusCards}>
|
||||
<CommonCard
|
||||
title={"All Buckets"}
|
||||
metricValue={usage ? prettyNumber(usage.buckets) : 0}
|
||||
extraMargin
|
||||
/>
|
||||
<CommonCard
|
||||
title={"Usage"}
|
||||
metricValue={usageToRepresent.total}
|
||||
metricUnit={usageToRepresent.unit}
|
||||
extraMargin
|
||||
/>
|
||||
<CommonCard
|
||||
title={"Total Objects"}
|
||||
metricValue={usage ? prettyNumber(usage.objects) : 0}
|
||||
extraMargin
|
||||
/>
|
||||
<CommonCard
|
||||
title={"Servers"}
|
||||
metricValue={usage ? prettyNumber(serverArray.length) : 0}
|
||||
subMessage={{ message: "Total" }}
|
||||
extraMargin
|
||||
/>
|
||||
<Grid item xs={12} className={classes.dashboardRow}>
|
||||
<Grid
|
||||
item
|
||||
xs={7}
|
||||
sm={8}
|
||||
md={6}
|
||||
lg={3}
|
||||
className={classes.widgetPanelDelimiter}
|
||||
>
|
||||
<CommonCard
|
||||
title={"All Buckets"}
|
||||
metricValue={usage ? prettyNumber(usage.buckets) : 0}
|
||||
extraMargin
|
||||
/>
|
||||
</Grid>
|
||||
<Grid
|
||||
item
|
||||
xs={7}
|
||||
sm={8}
|
||||
md={6}
|
||||
lg={3}
|
||||
className={classes.widgetPanelDelimiter}
|
||||
>
|
||||
<CommonCard
|
||||
title={"Usage"}
|
||||
metricValue={usageToRepresent.total}
|
||||
metricUnit={usageToRepresent.unit}
|
||||
extraMargin
|
||||
/>
|
||||
</Grid>
|
||||
<Grid
|
||||
item
|
||||
xs={7}
|
||||
sm={8}
|
||||
md={6}
|
||||
lg={3}
|
||||
className={classes.widgetPanelDelimiter}
|
||||
>
|
||||
<CommonCard
|
||||
title={"Total Objects"}
|
||||
metricValue={usage ? prettyNumber(usage.objects) : 0}
|
||||
extraMargin
|
||||
/>
|
||||
</Grid>
|
||||
<Grid
|
||||
item
|
||||
xs={7}
|
||||
sm={8}
|
||||
md={6}
|
||||
lg={3}
|
||||
className={classes.widgetPanelDelimiter}
|
||||
>
|
||||
<CommonCard
|
||||
title={"Servers"}
|
||||
metricValue={usage ? prettyNumber(serverArray.length) : 0}
|
||||
subMessage={{ message: "Total" }}
|
||||
extraMargin
|
||||
/>
|
||||
</Grid>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<TabSelector
|
||||
|
||||
@@ -32,7 +32,7 @@ export default function Chart() {
|
||||
return (
|
||||
<React.Fragment>
|
||||
<Title>Today</Title>
|
||||
<ResponsiveContainer>
|
||||
<ResponsiveContainer width="99%">
|
||||
<LineChart
|
||||
data={data}
|
||||
margin={{
|
||||
|
||||
@@ -46,7 +46,9 @@ const styles = (theme: Theme) =>
|
||||
...widgetCommon,
|
||||
cardRoot: {
|
||||
...widgetCommon.singleValueContainer,
|
||||
maxWidth: 280,
|
||||
"&.MuiPaper-root": {
|
||||
borderRadius: 10,
|
||||
},
|
||||
},
|
||||
cardsContainer: {
|
||||
maxHeight: 440,
|
||||
|
||||
@@ -20,41 +20,37 @@ import Grid from "@material-ui/core/Grid";
|
||||
import ScheduleIcon from "@material-ui/icons/Schedule";
|
||||
import WatchLaterIcon from "@material-ui/icons/WatchLater";
|
||||
import { createStyles, Theme, withStyles } from "@material-ui/core/styles";
|
||||
import { Button } from "@material-ui/core";
|
||||
import { actionsTray } from "../../Common/FormComponents/common/styleLibrary";
|
||||
import { IDashboardPanel, widgetType } from "./types";
|
||||
import { Button, GridSize } from "@material-ui/core";
|
||||
import {
|
||||
actionsTray,
|
||||
widgetContainerCommon,
|
||||
} from "../../Common/FormComponents/common/styleLibrary";
|
||||
import { IDashboardPanel } from "./types";
|
||||
import { getWidgetsWithValue, panelsConfiguration } from "./utils";
|
||||
import { TabPanel } from "../../../shared/tabs";
|
||||
import { ErrorResponseHandler } from "../../../../common/types";
|
||||
import { setErrorSnackMessage } from "../../../../actions";
|
||||
import SingleValueWidget from "./Widgets/SingleValueWidget";
|
||||
import LinearGraphWidget from "./Widgets/LinearGraphWidget";
|
||||
import BarChartWidget from "./Widgets/BarChartWidget";
|
||||
import PieChartWidget from "./Widgets/PieChartWidget";
|
||||
import SingleRepWidget from "./Widgets/SingleRepWidget";
|
||||
import DateTimePickerWrapper from "../../Common/FormComponents/DateTimePickerWrapper/DateTimePickerWrapper";
|
||||
import api from "../../../../common/api";
|
||||
import SyncIcon from "../../../../icons/SyncIcon";
|
||||
import TabSelector from "../../Common/TabSelector/TabSelector";
|
||||
import SimpleWidget from "./Widgets/SimpleWidget";
|
||||
import MergedWidgets from "./MergedWidgets";
|
||||
import { componentToUse } from "./widgetUtils";
|
||||
import ZoomWidget from "./ZoomWidget";
|
||||
import { AppState } from "../../../../store";
|
||||
|
||||
interface IPrDashboard {
|
||||
classes: any;
|
||||
displayErrorMessage: typeof setErrorSnackMessage;
|
||||
apiPrefix?: string;
|
||||
zoomOpen: boolean;
|
||||
zoomWidget: null | IDashboardPanel;
|
||||
}
|
||||
|
||||
const styles = (theme: Theme) =>
|
||||
createStyles({
|
||||
...actionsTray,
|
||||
widgetsContainer: {
|
||||
position: "relative",
|
||||
display: "flex",
|
||||
flexGrow: 1,
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
},
|
||||
...widgetContainerCommon,
|
||||
syncButton: {
|
||||
"&.MuiButton-root .MuiButton-iconSizeMedium > *:first-child": {
|
||||
fontSize: 18,
|
||||
@@ -71,9 +67,6 @@ const styles = (theme: Theme) =>
|
||||
flexWrap: "wrap",
|
||||
maxWidth: 1180,
|
||||
},
|
||||
widgetPanelDelimiter: {
|
||||
margin: 10,
|
||||
},
|
||||
schedulerIcon: {
|
||||
opacity: 0.4,
|
||||
fontSize: 10,
|
||||
@@ -88,6 +81,8 @@ const PrDashboard = ({
|
||||
classes,
|
||||
displayErrorMessage,
|
||||
apiPrefix = "admin",
|
||||
zoomOpen,
|
||||
zoomWidget,
|
||||
}: IPrDashboard) => {
|
||||
const [timeStart, setTimeStart] = useState<any>(null);
|
||||
const [timeEnd, setTimeEnd] = useState<any>(null);
|
||||
@@ -98,92 +93,16 @@ const PrDashboard = ({
|
||||
|
||||
const panels = useCallback(
|
||||
(tabName: string, filterPanels?: number[][] | null) => {
|
||||
const componentToUse = (value: IDashboardPanel, index: number) => {
|
||||
switch (value.type) {
|
||||
case widgetType.singleValue:
|
||||
return (
|
||||
<SingleValueWidget
|
||||
title={value.title}
|
||||
panelItem={value}
|
||||
timeStart={timeStart}
|
||||
timeEnd={timeEnd}
|
||||
propLoading={loading}
|
||||
apiPrefix={apiPrefix}
|
||||
/>
|
||||
);
|
||||
case widgetType.simpleWidget:
|
||||
return (
|
||||
<SimpleWidget
|
||||
title={value.title}
|
||||
panelItem={value}
|
||||
timeStart={timeStart}
|
||||
timeEnd={timeEnd}
|
||||
propLoading={loading}
|
||||
apiPrefix={apiPrefix}
|
||||
iconWidget={value.widgetIcon}
|
||||
/>
|
||||
);
|
||||
case widgetType.pieChart:
|
||||
return (
|
||||
<PieChartWidget
|
||||
title={value.title}
|
||||
panelItem={value}
|
||||
timeStart={timeStart}
|
||||
timeEnd={timeEnd}
|
||||
propLoading={loading}
|
||||
apiPrefix={apiPrefix}
|
||||
/>
|
||||
);
|
||||
case widgetType.linearGraph:
|
||||
case widgetType.areaGraph:
|
||||
return (
|
||||
<LinearGraphWidget
|
||||
title={value.title}
|
||||
panelItem={value}
|
||||
timeStart={timeStart}
|
||||
timeEnd={timeEnd}
|
||||
propLoading={loading}
|
||||
hideYAxis={value.disableYAxis}
|
||||
xAxisFormatter={value.xAxisFormatter}
|
||||
yAxisFormatter={value.yAxisFormatter}
|
||||
apiPrefix={apiPrefix}
|
||||
areaWidget={value.type === widgetType.areaGraph}
|
||||
/>
|
||||
);
|
||||
case widgetType.barChart:
|
||||
return (
|
||||
<BarChartWidget
|
||||
title={value.title}
|
||||
panelItem={value}
|
||||
timeStart={timeStart}
|
||||
timeEnd={timeEnd}
|
||||
propLoading={loading}
|
||||
apiPrefix={apiPrefix}
|
||||
/>
|
||||
);
|
||||
case widgetType.singleRep:
|
||||
const fillColor = value.fillColor ? value.fillColor : value.color;
|
||||
return (
|
||||
<SingleRepWidget
|
||||
title={value.title}
|
||||
panelItem={value}
|
||||
timeStart={timeStart}
|
||||
timeEnd={timeEnd}
|
||||
propLoading={loading}
|
||||
color={value.color as string}
|
||||
fillColor={fillColor as string}
|
||||
apiPrefix={apiPrefix}
|
||||
/>
|
||||
);
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
return filterPanels?.map((panelLine, indexLine) => {
|
||||
const totalPanelsContained = panelLine.length;
|
||||
|
||||
const perc = 100 / totalPanelsContained;
|
||||
let perc = Math.floor(12 / totalPanelsContained);
|
||||
|
||||
if (perc < 1) {
|
||||
perc = 1;
|
||||
} else if (perc > 12) {
|
||||
perc = 12;
|
||||
}
|
||||
|
||||
return (
|
||||
<Grid
|
||||
@@ -198,33 +117,51 @@ const PrDashboard = ({
|
||||
);
|
||||
|
||||
return (
|
||||
<div
|
||||
<Grid
|
||||
key={`widget-${panelInline}-${indexPanel}`}
|
||||
className={classes.widgetPanelDelimiter}
|
||||
style={{ width: `calc(${perc}% - 20px)` }}
|
||||
item
|
||||
xs={7}
|
||||
sm={8}
|
||||
md={6}
|
||||
lg={perc as GridSize}
|
||||
>
|
||||
{panelInfo ? (
|
||||
<Fragment>
|
||||
{panelInfo.mergedPanels ? (
|
||||
<Fragment>
|
||||
<MergedWidgets
|
||||
title={panelInfo.title}
|
||||
leftComponent={componentToUse(
|
||||
panelInfo.mergedPanels[0],
|
||||
0
|
||||
)}
|
||||
rightComponent={componentToUse(
|
||||
panelInfo.mergedPanels[1],
|
||||
1
|
||||
)}
|
||||
/>
|
||||
</Fragment>
|
||||
) : (
|
||||
componentToUse(panelInfo, indexPanel)
|
||||
)}
|
||||
</Fragment>
|
||||
) : null}
|
||||
</div>
|
||||
<Grid item xs={12}>
|
||||
{panelInfo ? (
|
||||
<Fragment>
|
||||
{panelInfo.mergedPanels ? (
|
||||
<Fragment>
|
||||
<MergedWidgets
|
||||
title={panelInfo.title}
|
||||
leftComponent={componentToUse(
|
||||
panelInfo.mergedPanels[0],
|
||||
timeStart,
|
||||
timeEnd,
|
||||
loading,
|
||||
apiPrefix
|
||||
)}
|
||||
rightComponent={componentToUse(
|
||||
panelInfo.mergedPanels[1],
|
||||
timeStart,
|
||||
timeEnd,
|
||||
loading,
|
||||
apiPrefix
|
||||
)}
|
||||
/>
|
||||
</Fragment>
|
||||
) : (
|
||||
componentToUse(
|
||||
panelInfo,
|
||||
timeStart,
|
||||
timeEnd,
|
||||
loading,
|
||||
apiPrefix
|
||||
)
|
||||
)}
|
||||
</Fragment>
|
||||
) : null}
|
||||
</Grid>
|
||||
</Grid>
|
||||
);
|
||||
})}
|
||||
</Grid>
|
||||
@@ -292,11 +229,11 @@ const PrDashboard = ({
|
||||
}, [loading, fetchUsage]);
|
||||
|
||||
const summaryPanels = [
|
||||
[66, 44, 500, 501],
|
||||
[50, 502],
|
||||
[80, 81, 1],
|
||||
[68, 52],
|
||||
[63, 70],
|
||||
[66, 50, 44, 500],
|
||||
[501, 502, 61, 62],
|
||||
];
|
||||
const resourcesPanels = [
|
||||
[76, 77],
|
||||
@@ -307,6 +244,16 @@ const PrDashboard = ({
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
{zoomOpen && (
|
||||
<ZoomWidget
|
||||
modalOpen={zoomOpen}
|
||||
timeStart={timeStart}
|
||||
timeEnd={timeEnd}
|
||||
widgetRender={0}
|
||||
value={zoomWidget}
|
||||
apiPrefix={apiPrefix}
|
||||
/>
|
||||
)}
|
||||
<Grid
|
||||
item
|
||||
xs={12}
|
||||
@@ -353,7 +300,7 @@ const PrDashboard = ({
|
||||
setCurTab(newValue);
|
||||
}}
|
||||
tabOptions={[
|
||||
{ label: "Summary" },
|
||||
{ label: "Usage" },
|
||||
{ label: "Traffic" },
|
||||
{ label: "Resources" },
|
||||
]}
|
||||
@@ -373,11 +320,13 @@ const PrDashboard = ({
|
||||
</Fragment>
|
||||
);
|
||||
};
|
||||
/*
|
||||
<
|
||||
*/
|
||||
|
||||
const connector = connect(null, {
|
||||
const mapState = (state: AppState) => ({
|
||||
zoomOpen: state.dashboard.zoom.openZoom,
|
||||
zoomWidget: state.dashboard.zoom.widgetRender,
|
||||
});
|
||||
|
||||
const connector = connect(mapState, {
|
||||
displayErrorMessage: setErrorSnackMessage,
|
||||
});
|
||||
|
||||
|
||||
@@ -28,6 +28,7 @@ import {
|
||||
import { MaterialUiPickersDate } from "@material-ui/pickers/typings/date";
|
||||
import { CircularProgress } from "@material-ui/core";
|
||||
import { createStyles, Theme, withStyles } from "@material-ui/core/styles";
|
||||
import ZoomOutMapIcon from "@material-ui/icons/ZoomOutMap";
|
||||
import { IBarChartConfiguration } from "./types";
|
||||
import { widgetCommon } from "../../../Common/FormComponents/common/styleLibrary";
|
||||
import BarChartTooltip from "./tooltips/BarChartTooltip";
|
||||
@@ -36,6 +37,7 @@ import { IDashboardPanel } from "../types";
|
||||
import { widgetDetailsToPanel } from "../utils";
|
||||
import { ErrorResponseHandler } from "../../../../../common/types";
|
||||
import api from "../../../../../common/api";
|
||||
import { openZoomPage } from "../../actions";
|
||||
|
||||
interface IBarChartWidget {
|
||||
classes: any;
|
||||
@@ -46,6 +48,8 @@ interface IBarChartWidget {
|
||||
propLoading: boolean;
|
||||
displayErrorMessage: any;
|
||||
apiPrefix: string;
|
||||
zoomActivated?: boolean;
|
||||
openZoomPage: typeof openZoomPage;
|
||||
}
|
||||
|
||||
const styles = (theme: Theme) =>
|
||||
@@ -84,6 +88,8 @@ const BarChartWidget = ({
|
||||
propLoading,
|
||||
displayErrorMessage,
|
||||
apiPrefix,
|
||||
zoomActivated = false,
|
||||
openZoomPage,
|
||||
}: IBarChartWidget) => {
|
||||
const [loading, setLoading] = useState<boolean>(true);
|
||||
const [data, setData] = useState<any>([]);
|
||||
@@ -147,16 +153,32 @@ const BarChartWidget = ({
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={classes.singleValueContainer}>
|
||||
<div className={classes.titleContainer}>{title}</div>
|
||||
<div className={zoomActivated ? "" : classes.singleValueContainer}>
|
||||
{!zoomActivated && (
|
||||
<div className={classes.titleContainer}>
|
||||
{title}{" "}
|
||||
<button
|
||||
onClick={() => {
|
||||
openZoomPage(panelItem);
|
||||
}}
|
||||
className={classes.zoomChartIcon}
|
||||
>
|
||||
<ZoomOutMapIcon />
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
{loading && (
|
||||
<div className={classes.loadingAlign}>
|
||||
<CircularProgress />
|
||||
</div>
|
||||
)}
|
||||
{!loading && (
|
||||
<div className={classes.contentContainer}>
|
||||
<ResponsiveContainer>
|
||||
<div
|
||||
className={
|
||||
zoomActivated ? classes.zoomChartCont : classes.contentContainer
|
||||
}
|
||||
>
|
||||
<ResponsiveContainer width="99%">
|
||||
<BarChart
|
||||
data={data as object[]}
|
||||
layout={"vertical"}
|
||||
@@ -178,7 +200,7 @@ const BarChartWidget = ({
|
||||
dataKey={bar.dataKey}
|
||||
fill={bar.color}
|
||||
background={bar.background}
|
||||
barSize={12}
|
||||
barSize={zoomActivated ? 25 : 12}
|
||||
>
|
||||
{barChartConfiguration.length === 1 ? (
|
||||
<Fragment>
|
||||
@@ -214,6 +236,7 @@ const BarChartWidget = ({
|
||||
|
||||
const connector = connect(null, {
|
||||
displayErrorMessage: setErrorSnackMessage,
|
||||
openZoomPage: openZoomPage,
|
||||
});
|
||||
|
||||
export default withStyles(styles)(connector(BarChartWidget));
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import React, { useEffect, useState } from "react";
|
||||
import React, { Fragment, useEffect, useState } from "react";
|
||||
import { connect } from "react-redux";
|
||||
import {
|
||||
Area,
|
||||
@@ -28,6 +28,7 @@ import {
|
||||
import { CircularProgress } from "@material-ui/core";
|
||||
import { MaterialUiPickersDate } from "@material-ui/pickers/typings/date";
|
||||
import { createStyles, Theme, withStyles } from "@material-ui/core/styles";
|
||||
import ZoomOutMapIcon from "@material-ui/icons/ZoomOutMap";
|
||||
import { ILinearGraphConfiguration } from "./types";
|
||||
import { widgetCommon } from "../../../Common/FormComponents/common/styleLibrary";
|
||||
import { IDashboardPanel } from "../types";
|
||||
@@ -36,6 +37,7 @@ import { widgetDetailsToPanel } from "../utils";
|
||||
import { ErrorResponseHandler } from "../../../../../common/types";
|
||||
import api from "../../../../../common/api";
|
||||
import LineChartTooltip from "./tooltips/LineChartTooltip";
|
||||
import { openZoomPage } from "../../actions";
|
||||
|
||||
interface ILinearGraphWidget {
|
||||
classes: any;
|
||||
@@ -50,6 +52,8 @@ interface ILinearGraphWidget {
|
||||
yAxisFormatter?: (item: string) => string;
|
||||
xAxisFormatter?: (item: string) => string;
|
||||
areaWidget?: boolean;
|
||||
zoomActivated?: boolean;
|
||||
openZoomPage: typeof openZoomPage;
|
||||
}
|
||||
|
||||
const styles = (theme: Theme) =>
|
||||
@@ -61,6 +65,9 @@ const styles = (theme: Theme) =>
|
||||
height: "100%",
|
||||
flexGrow: 1,
|
||||
},
|
||||
verticalAlignment: {
|
||||
flexDirection: "column",
|
||||
},
|
||||
chartCont: {
|
||||
position: "relative",
|
||||
height: 140,
|
||||
@@ -70,7 +77,7 @@ const styles = (theme: Theme) =>
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
flex: "0 1 auto",
|
||||
height: 130,
|
||||
maxHeight: 130,
|
||||
margin: 0,
|
||||
overflowY: "auto",
|
||||
position: "relative",
|
||||
@@ -99,6 +106,8 @@ const LinearGraphWidget = ({
|
||||
areaWidget = false,
|
||||
yAxisFormatter = (item: string) => item,
|
||||
xAxisFormatter = (item: string) => item,
|
||||
zoomActivated = false,
|
||||
openZoomPage,
|
||||
}: ILinearGraphWidget) => {
|
||||
const [loading, setLoading] = useState<boolean>(true);
|
||||
const [data, setData] = useState<object[]>([]);
|
||||
@@ -174,14 +183,34 @@ const LinearGraphWidget = ({
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={classes.singleValueContainer}>
|
||||
<div className={classes.titleContainer}>{title}</div>
|
||||
<div className={classes.containerElements}>
|
||||
<div className={zoomActivated ? "" : classes.singleValueContainer}>
|
||||
{!zoomActivated && (
|
||||
<div className={classes.titleContainer}>
|
||||
{title}{" "}
|
||||
<button
|
||||
onClick={() => {
|
||||
openZoomPage(panelItem);
|
||||
}}
|
||||
className={classes.zoomChartIcon}
|
||||
>
|
||||
<ZoomOutMapIcon />
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
<div
|
||||
className={
|
||||
zoomActivated ? classes.verticalAlignment : classes.containerElements
|
||||
}
|
||||
>
|
||||
{loading && <CircularProgress className={classes.loadingAlign} />}
|
||||
{!loading && (
|
||||
<React.Fragment>
|
||||
<div className={classes.chartCont}>
|
||||
<ResponsiveContainer>
|
||||
<div
|
||||
className={
|
||||
zoomActivated ? classes.zoomChartCont : classes.chartCont
|
||||
}
|
||||
>
|
||||
<ResponsiveContainer width="99%">
|
||||
<AreaChart
|
||||
data={data}
|
||||
margin={{
|
||||
@@ -267,24 +296,33 @@ const LinearGraphWidget = ({
|
||||
</ResponsiveContainer>
|
||||
</div>
|
||||
{!areaWidget && (
|
||||
<div className={classes.legendChart}>
|
||||
{linearConfiguration.map((section, index) => {
|
||||
return (
|
||||
<div
|
||||
className={classes.singleLegendContainer}
|
||||
key={`legend-${section.keyLabel}-${index.toString()}`}
|
||||
>
|
||||
<Fragment>
|
||||
{zoomActivated && (
|
||||
<Fragment>
|
||||
<strong>Series</strong>
|
||||
<br />
|
||||
<br />
|
||||
</Fragment>
|
||||
)}
|
||||
<div className={classes.legendChart}>
|
||||
{linearConfiguration.map((section, index) => {
|
||||
return (
|
||||
<div
|
||||
className={classes.colorContainer}
|
||||
style={{ backgroundColor: section.lineColor }}
|
||||
/>
|
||||
<div className={classes.legendLabel}>
|
||||
{section.keyLabel}
|
||||
className={classes.singleLegendContainer}
|
||||
key={`legend-${section.keyLabel}-${index.toString()}`}
|
||||
>
|
||||
<div
|
||||
className={classes.colorContainer}
|
||||
style={{ backgroundColor: section.lineColor }}
|
||||
/>
|
||||
<div className={classes.legendLabel}>
|
||||
{section.keyLabel}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</Fragment>
|
||||
)}
|
||||
</React.Fragment>
|
||||
)}
|
||||
@@ -295,6 +333,7 @@ const LinearGraphWidget = ({
|
||||
|
||||
const connector = connect(null, {
|
||||
displayErrorMessage: setErrorSnackMessage,
|
||||
openZoomPage: openZoomPage,
|
||||
});
|
||||
|
||||
export default withStyles(styles)(connector(LinearGraphWidget));
|
||||
|
||||
@@ -143,7 +143,7 @@ const PieChartWidget = ({
|
||||
{middleLabel && splitSizeMetric(middleLabel)}
|
||||
</span>
|
||||
<div className={classes.chartContainer}>
|
||||
<ResponsiveContainer>
|
||||
<ResponsiveContainer width="99%">
|
||||
<PieChart margin={{ top: 5, bottom: 5 }}>
|
||||
{dataOuter && (
|
||||
<Pie
|
||||
|
||||
@@ -27,6 +27,10 @@ import { widgetDetailsToPanel } from "../utils";
|
||||
import { CircularProgress } from "@material-ui/core";
|
||||
import { ErrorResponseHandler } from "../../../../../common/types";
|
||||
import api from "../../../../../common/api";
|
||||
import {
|
||||
prettyNumber,
|
||||
representationNumber,
|
||||
} from "../../../../../common/utils";
|
||||
|
||||
interface ISingleRepWidget {
|
||||
classes: any;
|
||||
@@ -119,7 +123,7 @@ const SingleRepWidget = ({
|
||||
)}
|
||||
{!loading && (
|
||||
<div className={classes.contentContainer}>
|
||||
<ResponsiveContainer>
|
||||
<ResponsiveContainer width="99%">
|
||||
<AreaChart data={data}>
|
||||
<defs>
|
||||
<linearGradient id={gradientID} x1="0" y1="0" x2="0" y2="1">
|
||||
@@ -144,10 +148,12 @@ const SingleRepWidget = ({
|
||||
textAnchor="start"
|
||||
dominantBaseline="auto"
|
||||
fontWeight={700}
|
||||
fontSize={70}
|
||||
fontSize={65}
|
||||
fill={"#07193E"}
|
||||
>
|
||||
{result ? result.innerLabel : ""}
|
||||
{result
|
||||
? representationNumber(parseInt(result.innerLabel || "0"))
|
||||
: ""}
|
||||
</text>
|
||||
</AreaChart>
|
||||
</ResponsiveContainer>
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
// This file is part of MinIO Console Server
|
||||
// Copyright (c) 2021 MinIO, Inc.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import React, { Fragment } from "react";
|
||||
import { connect } from "react-redux";
|
||||
import ModalWrapper from "../../Common/ModalWrapper/ModalWrapper";
|
||||
import { IDashboardPanel } from "./types";
|
||||
import { componentToUse } from "./widgetUtils";
|
||||
import { closeZoomPage } from "../actions";
|
||||
|
||||
interface IZoomWidget {
|
||||
widgetRender: number;
|
||||
value: IDashboardPanel | null;
|
||||
modalOpen: boolean;
|
||||
timeStart: any;
|
||||
timeEnd: any;
|
||||
apiPrefix: string;
|
||||
onCloseAction: typeof closeZoomPage;
|
||||
}
|
||||
const ZoomWidget = ({
|
||||
value,
|
||||
modalOpen,
|
||||
timeStart,
|
||||
timeEnd,
|
||||
apiPrefix,
|
||||
onCloseAction,
|
||||
}: IZoomWidget) => {
|
||||
if (!value) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<ModalWrapper
|
||||
title={value.title}
|
||||
onClose={() => {
|
||||
onCloseAction();
|
||||
}}
|
||||
modalOpen={modalOpen}
|
||||
wideLimit={false}
|
||||
noContentPadding
|
||||
>
|
||||
<Fragment>
|
||||
{componentToUse(value, timeStart, timeEnd, true, apiPrefix, true)}
|
||||
</Fragment>
|
||||
</ModalWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
const connector = connect(null, {
|
||||
onCloseAction: closeZoomPage,
|
||||
});
|
||||
|
||||
export default connector(ZoomWidget);
|
||||
@@ -21,12 +21,13 @@ import {
|
||||
getTimeFromTimestamp,
|
||||
niceBytes,
|
||||
niceDays,
|
||||
representationNumber,
|
||||
textToRGBColor,
|
||||
units,
|
||||
} from "../../../../common/utils";
|
||||
import HealIcon from "../../../../icons/HealIcon";
|
||||
import DiagnosticsIcon from "../../../../icons/DiagnosticsIcon";
|
||||
import HistoryIcon from "../../../../icons/HistoryIcon";
|
||||
import { UptimeIcon } from "../../../../icons";
|
||||
|
||||
const colorsMain = [
|
||||
"#C4D4E9",
|
||||
@@ -54,12 +55,12 @@ export const panelsConfiguration: IDashboardPanel[] = [
|
||||
title: "Uptime",
|
||||
data: "N/A",
|
||||
type: widgetType.simpleWidget,
|
||||
widgetIcon: <HistoryIcon />,
|
||||
widgetIcon: <UptimeIcon />,
|
||||
labelDisplayFunction: niceDays,
|
||||
},
|
||||
{
|
||||
id: 50,
|
||||
title: "Current Usable Capacity",
|
||||
title: "Capacity",
|
||||
data: [],
|
||||
dataOuter: [{ name: "outer", value: 100 }],
|
||||
widgetConfiguration: {
|
||||
@@ -143,7 +144,7 @@ export const panelsConfiguration: IDashboardPanel[] = [
|
||||
},
|
||||
{
|
||||
id: 66,
|
||||
title: "Number of Buckets",
|
||||
title: "Buckets",
|
||||
data: [],
|
||||
innerLabel: "N/A",
|
||||
type: widgetType.singleRep,
|
||||
@@ -152,7 +153,7 @@ export const panelsConfiguration: IDashboardPanel[] = [
|
||||
},
|
||||
{
|
||||
id: 44,
|
||||
title: "Number of Objects",
|
||||
title: "Objects",
|
||||
data: [],
|
||||
innerLabel: "N/A",
|
||||
type: widgetType.singleRep,
|
||||
@@ -161,7 +162,7 @@ export const panelsConfiguration: IDashboardPanel[] = [
|
||||
},
|
||||
{
|
||||
id: 63,
|
||||
title: "S3 API Data Received Rate",
|
||||
title: "API Data Received Rate",
|
||||
data: [],
|
||||
widgetConfiguration: [
|
||||
{
|
||||
@@ -213,7 +214,7 @@ export const panelsConfiguration: IDashboardPanel[] = [
|
||||
},
|
||||
{
|
||||
id: 60,
|
||||
title: "S3 API Request Rate",
|
||||
title: "API Request Rate",
|
||||
data: [],
|
||||
widgetConfiguration: [
|
||||
{
|
||||
@@ -229,7 +230,7 @@ export const panelsConfiguration: IDashboardPanel[] = [
|
||||
},
|
||||
{
|
||||
id: 70,
|
||||
title: "S3 API Data Sent Rate",
|
||||
title: "API Data Sent Rate",
|
||||
data: [],
|
||||
widgetConfiguration: [
|
||||
{
|
||||
@@ -296,7 +297,7 @@ export const panelsConfiguration: IDashboardPanel[] = [
|
||||
},
|
||||
{
|
||||
id: 71,
|
||||
title: "S3 API Request Error Rate",
|
||||
title: "API Request Error Rate",
|
||||
data: [],
|
||||
widgetConfiguration: [
|
||||
{
|
||||
@@ -398,13 +399,13 @@ export const panelsConfiguration: IDashboardPanel[] = [
|
||||
mergedPanels: [
|
||||
{
|
||||
id: 53,
|
||||
title: "Online Servers",
|
||||
title: "Online",
|
||||
data: "N/A",
|
||||
type: widgetType.singleValue,
|
||||
},
|
||||
{
|
||||
id: 69,
|
||||
title: "Offline Servers",
|
||||
title: "Offline",
|
||||
data: "N/A",
|
||||
type: widgetType.singleValue,
|
||||
},
|
||||
@@ -416,25 +417,25 @@ export const panelsConfiguration: IDashboardPanel[] = [
|
||||
mergedPanels: [
|
||||
{
|
||||
id: 9,
|
||||
title: "Online Disks",
|
||||
title: "Online",
|
||||
data: "N/A",
|
||||
type: widgetType.singleValue,
|
||||
},
|
||||
{
|
||||
id: 78,
|
||||
title: "Offline Disks",
|
||||
title: "Offline",
|
||||
data: "N/A",
|
||||
type: widgetType.singleValue,
|
||||
},
|
||||
],
|
||||
title: "Disks",
|
||||
title: "Drives",
|
||||
},
|
||||
{
|
||||
id: 502,
|
||||
mergedPanels: [
|
||||
{
|
||||
id: 65,
|
||||
title: "Inbound Traffic",
|
||||
title: "Upload",
|
||||
data: "N/A",
|
||||
type: widgetType.singleValue,
|
||||
|
||||
@@ -442,14 +443,14 @@ export const panelsConfiguration: IDashboardPanel[] = [
|
||||
},
|
||||
{
|
||||
id: 64,
|
||||
title: "Outbound Traffic",
|
||||
title: "Download",
|
||||
data: "N/A",
|
||||
type: widgetType.singleValue,
|
||||
|
||||
labelDisplayFunction: niceBytes,
|
||||
},
|
||||
],
|
||||
title: "Total S3 Traffic",
|
||||
title: "Network",
|
||||
},
|
||||
];
|
||||
|
||||
@@ -791,15 +792,29 @@ export const widgetDetailsToPanel = (
|
||||
return panelItem;
|
||||
};
|
||||
|
||||
const verifyNumeric = (item: string) => {
|
||||
return !isNaN(parseFloat(item));
|
||||
};
|
||||
|
||||
export const splitSizeMetric = (val: string) => {
|
||||
const splittedText = val.split(" ");
|
||||
// Value is not a size metric, we return as common string
|
||||
|
||||
const singleValue = () => {
|
||||
let vl = val;
|
||||
|
||||
if (verifyNumeric(val)) {
|
||||
vl = representationNumber(parseFloat(val));
|
||||
}
|
||||
return <Fragment>{vl}</Fragment>;
|
||||
};
|
||||
|
||||
if (splittedText.length !== 2) {
|
||||
return <Fragment>{val}</Fragment>;
|
||||
return singleValue();
|
||||
}
|
||||
|
||||
if (!units.includes(splittedText[1])) {
|
||||
return <Fragment>{val}</Fragment>;
|
||||
return singleValue();
|
||||
}
|
||||
|
||||
return (
|
||||
|
||||
@@ -0,0 +1,115 @@
|
||||
// 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 { IDashboardPanel, widgetType } from "./types";
|
||||
import BarChartWidget from "./Widgets/BarChartWidget";
|
||||
import LinearGraphWidget from "./Widgets/LinearGraphWidget";
|
||||
import PieChartWidget from "./Widgets/PieChartWidget";
|
||||
import SimpleWidget from "./Widgets/SimpleWidget";
|
||||
import SingleRepWidget from "./Widgets/SingleRepWidget";
|
||||
import SingleValueWidget from "./Widgets/SingleValueWidget";
|
||||
|
||||
export const componentToUse = (
|
||||
value: IDashboardPanel,
|
||||
timeStart: any,
|
||||
timeEnd: any,
|
||||
loading: boolean,
|
||||
apiPrefix: string,
|
||||
zoomActivated: boolean = false
|
||||
) => {
|
||||
switch (value.type) {
|
||||
case widgetType.singleValue:
|
||||
return (
|
||||
<SingleValueWidget
|
||||
title={value.title}
|
||||
panelItem={value}
|
||||
timeStart={timeStart}
|
||||
timeEnd={timeEnd}
|
||||
propLoading={loading}
|
||||
apiPrefix={apiPrefix}
|
||||
/>
|
||||
);
|
||||
case widgetType.simpleWidget:
|
||||
return (
|
||||
<SimpleWidget
|
||||
title={value.title}
|
||||
panelItem={value}
|
||||
timeStart={timeStart}
|
||||
timeEnd={timeEnd}
|
||||
propLoading={loading}
|
||||
apiPrefix={apiPrefix}
|
||||
iconWidget={value.widgetIcon}
|
||||
/>
|
||||
);
|
||||
case widgetType.pieChart:
|
||||
return (
|
||||
<PieChartWidget
|
||||
title={value.title}
|
||||
panelItem={value}
|
||||
timeStart={timeStart}
|
||||
timeEnd={timeEnd}
|
||||
propLoading={loading}
|
||||
apiPrefix={apiPrefix}
|
||||
/>
|
||||
);
|
||||
case widgetType.linearGraph:
|
||||
case widgetType.areaGraph:
|
||||
return (
|
||||
<LinearGraphWidget
|
||||
title={value.title}
|
||||
panelItem={value}
|
||||
timeStart={timeStart}
|
||||
timeEnd={timeEnd}
|
||||
propLoading={loading}
|
||||
hideYAxis={value.disableYAxis}
|
||||
xAxisFormatter={value.xAxisFormatter}
|
||||
yAxisFormatter={value.yAxisFormatter}
|
||||
apiPrefix={apiPrefix}
|
||||
areaWidget={value.type === widgetType.areaGraph}
|
||||
zoomActivated={zoomActivated}
|
||||
/>
|
||||
);
|
||||
case widgetType.barChart:
|
||||
return (
|
||||
<BarChartWidget
|
||||
title={value.title}
|
||||
panelItem={value}
|
||||
timeStart={timeStart}
|
||||
timeEnd={timeEnd}
|
||||
propLoading={loading}
|
||||
apiPrefix={apiPrefix}
|
||||
zoomActivated={zoomActivated}
|
||||
/>
|
||||
);
|
||||
case widgetType.singleRep:
|
||||
const fillColor = value.fillColor ? value.fillColor : value.color;
|
||||
return (
|
||||
<SingleRepWidget
|
||||
title={value.title}
|
||||
panelItem={value}
|
||||
timeStart={timeStart}
|
||||
timeEnd={timeEnd}
|
||||
propLoading={loading}
|
||||
color={value.color as string}
|
||||
fillColor={fillColor as string}
|
||||
apiPrefix={apiPrefix}
|
||||
/>
|
||||
);
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
};
|
||||
44
portal-ui/src/screens/Console/Dashboard/actions.ts
Normal file
44
portal-ui/src/screens/Console/Dashboard/actions.ts
Normal file
@@ -0,0 +1,44 @@
|
||||
// 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 { IDashboardPanel } from "./Prometheus/types";
|
||||
|
||||
export const DASHBOARD_OPEN_ZOOM = "DASHBOARD/OPEN_ZOOM";
|
||||
export const DASHBOARD_CLOSE_ZOOM = "DASHBOARD/CLOSE_ZOOM";
|
||||
|
||||
interface OpenChartZoom {
|
||||
type: typeof DASHBOARD_OPEN_ZOOM;
|
||||
widget: IDashboardPanel;
|
||||
}
|
||||
|
||||
interface CloseChartZoom {
|
||||
type: typeof DASHBOARD_CLOSE_ZOOM;
|
||||
}
|
||||
|
||||
export type ZoomActionTypes = OpenChartZoom | CloseChartZoom;
|
||||
|
||||
export function openZoomPage(widget: IDashboardPanel) {
|
||||
return {
|
||||
type: DASHBOARD_OPEN_ZOOM,
|
||||
widget,
|
||||
};
|
||||
}
|
||||
|
||||
export function closeZoomPage() {
|
||||
return {
|
||||
type: DASHBOARD_CLOSE_ZOOM,
|
||||
};
|
||||
}
|
||||
59
portal-ui/src/screens/Console/Dashboard/reducer.ts
Normal file
59
portal-ui/src/screens/Console/Dashboard/reducer.ts
Normal file
@@ -0,0 +1,59 @@
|
||||
// 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 { zoomState } from "./types";
|
||||
import {
|
||||
ZoomActionTypes,
|
||||
DASHBOARD_OPEN_ZOOM,
|
||||
DASHBOARD_CLOSE_ZOOM,
|
||||
} from "./actions";
|
||||
|
||||
export interface DashboardState {
|
||||
zoom: zoomState;
|
||||
}
|
||||
|
||||
const initialState: DashboardState = {
|
||||
zoom: {
|
||||
openZoom: false,
|
||||
widgetRender: null,
|
||||
},
|
||||
};
|
||||
|
||||
export function dashboardReducer(
|
||||
state = initialState,
|
||||
action: ZoomActionTypes
|
||||
): DashboardState {
|
||||
switch (action.type) {
|
||||
case DASHBOARD_OPEN_ZOOM:
|
||||
return {
|
||||
...state,
|
||||
zoom: {
|
||||
openZoom: true,
|
||||
widgetRender: { ...action.widget },
|
||||
},
|
||||
};
|
||||
case DASHBOARD_CLOSE_ZOOM:
|
||||
return {
|
||||
...state,
|
||||
zoom: {
|
||||
openZoom: false,
|
||||
widgetRender: null,
|
||||
},
|
||||
};
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
}
|
||||
@@ -14,6 +14,8 @@
|
||||
// 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 { IDashboardPanel } from "./Prometheus/types";
|
||||
|
||||
export interface Usage {
|
||||
usage: number;
|
||||
buckets: number;
|
||||
@@ -45,3 +47,8 @@ export interface IDriveInfo {
|
||||
usedSpace: number;
|
||||
availableSpace: number;
|
||||
}
|
||||
|
||||
export interface zoomState {
|
||||
openZoom: boolean;
|
||||
widgetRender: null | IDashboardPanel;
|
||||
}
|
||||
@@ -55,20 +55,16 @@ const BrowserBreadcrumbs = ({
|
||||
paths = `/${internalPaths}`;
|
||||
}
|
||||
|
||||
const splitPaths = paths.split("/");
|
||||
|
||||
const splitPaths = paths.split("/").filter((path) => path !== "");
|
||||
const listBreadcrumbs = splitPaths.map(
|
||||
(objectItem: string, index: number) => {
|
||||
const subSplit = splitPaths.slice(1, index + 1).join("/");
|
||||
|
||||
const route = `/buckets/${bucketName}/browse${
|
||||
objectItem !== "" ? `/${subSplit}` : ""
|
||||
const subSplit = splitPaths.slice(0, index + 1).join("/");
|
||||
const route = `/buckets/${bucketName}/browse/${
|
||||
subSplit ? `${btoa(subSplit)}` : ``
|
||||
}`;
|
||||
const label = objectItem === "" ? bucketName : objectItem;
|
||||
|
||||
return (
|
||||
<React.Fragment key={`breadcrumbs-${index.toString()}`}>
|
||||
<Link to={route}>{label}</Link>
|
||||
<Link to={route}>{objectItem}</Link>
|
||||
{index < splitPaths.length - 1 && <span> / </span>}
|
||||
</React.Fragment>
|
||||
);
|
||||
@@ -95,6 +91,10 @@ const BrowserBreadcrumbs = ({
|
||||
)}
|
||||
|
||||
<Grid item xs={12} className={classes.breadcrumbs}>
|
||||
<React.Fragment>
|
||||
<Link to={`/buckets/${bucketName}/browse`}>{bucketName}</Link>
|
||||
{listBreadcrumbs.length > 0 && <span> / </span>}
|
||||
</React.Fragment>
|
||||
{listBreadcrumbs}
|
||||
</Grid>
|
||||
</React.Fragment>
|
||||
|
||||
@@ -26,6 +26,7 @@ import { bucketsReducer } from "./screens/Console/Buckets/reducers";
|
||||
import { objectBrowserReducer } from "./screens/Console/ObjectBrowser/reducers";
|
||||
import { tenantsReducer } from "./screens/Console/Tenants/reducer";
|
||||
import { directCSIReducer } from "./screens/Console/DirectCSI/reducer";
|
||||
import { dashboardReducer } from "./screens/Console/Dashboard/reducer";
|
||||
|
||||
const globalReducer = combineReducers({
|
||||
system: systemReducer,
|
||||
@@ -38,6 +39,7 @@ const globalReducer = combineReducers({
|
||||
healthInfo: healthInfoReducer,
|
||||
tenants: tenantsReducer,
|
||||
directCSI: directCSIReducer,
|
||||
dashboard: dashboardReducer,
|
||||
});
|
||||
|
||||
declare global {
|
||||
|
||||
@@ -213,7 +213,14 @@ func getUserAddResponse(session *models.Principal, params admin_api.AddUserParam
|
||||
// create a minioClient interface implementation
|
||||
// defining the client to be used
|
||||
adminClient := AdminClient{Client: mAdmin}
|
||||
var userExists bool
|
||||
|
||||
_, err = adminClient.getUserInfo(ctx, *params.Body.AccessKey)
|
||||
userExists = err == nil
|
||||
|
||||
if userExists {
|
||||
return nil, prepareError(errNonUniqueAccessKey)
|
||||
}
|
||||
user, err := addUser(
|
||||
ctx,
|
||||
adminClient,
|
||||
|
||||
@@ -33,7 +33,8 @@ var (
|
||||
errLicenseNotFound = errors.New("license not found")
|
||||
errAvoidSelfAccountDelete = errors.New("logged in user cannot be deleted by itself")
|
||||
errAccessDenied = errors.New("access denied")
|
||||
errOauth2Provider = errors.New("error contacting the external identity provider")
|
||||
errOauth2Provider = errors.New("unable to contact configured identity provider")
|
||||
errNonUniqueAccessKey = errors.New("access key already in use")
|
||||
)
|
||||
|
||||
// Tiering errors
|
||||
|
||||
@@ -39,8 +39,10 @@ func registerAccountHandlers(api *operations.ConsoleAPI) {
|
||||
}
|
||||
// Custom response writer to update the session cookies
|
||||
return middleware.ResponderFunc(func(w http.ResponseWriter, p runtime.Producer) {
|
||||
cookie := NewSessionCookieForConsole(changePasswordResponse.SessionID)
|
||||
http.SetCookie(w, &cookie)
|
||||
cookies := NewSessionCookieForConsole(changePasswordResponse.SessionID)
|
||||
for _, cookie := range cookies {
|
||||
http.SetCookie(w, &cookie)
|
||||
}
|
||||
user_api.NewLoginCreated().WithPayload(changePasswordResponse).WriteResponse(w, p)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -18,6 +18,7 @@ package restapi
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
@@ -835,13 +836,15 @@ func getBucketObjectLockingResponse(session *models.Principal, bucketName string
|
||||
func getBucketRewindResponse(session *models.Principal, params user_api.GetBucketRewindParams) (*models.RewindResponse, *models.Error) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second*20)
|
||||
defer cancel()
|
||||
|
||||
var prefix = ""
|
||||
|
||||
if params.Prefix != nil {
|
||||
prefix = *params.Prefix
|
||||
encodedPrefix := *params.Prefix
|
||||
decodedPrefix, err := base64.StdEncoding.DecodeString(encodedPrefix)
|
||||
if err != nil {
|
||||
return nil, prepareError(err)
|
||||
}
|
||||
prefix = string(decodedPrefix)
|
||||
}
|
||||
|
||||
s3Client, err := newS3BucketClient(session, params.BucketName, prefix)
|
||||
if err != nil {
|
||||
LogError("error creating S3Client: %v", err)
|
||||
|
||||
@@ -53,8 +53,10 @@ func registerLoginHandlers(api *operations.ConsoleAPI) {
|
||||
}
|
||||
// Custom response writer to set the session cookies
|
||||
return middleware.ResponderFunc(func(w http.ResponseWriter, p runtime.Producer) {
|
||||
cookie := NewSessionCookieForConsole(loginResponse.SessionID)
|
||||
http.SetCookie(w, &cookie)
|
||||
cookies := NewSessionCookieForConsole(loginResponse.SessionID)
|
||||
for _, cookie := range cookies {
|
||||
http.SetCookie(w, &cookie)
|
||||
}
|
||||
user_api.NewLoginCreated().WithPayload(loginResponse).WriteResponse(w, p)
|
||||
})
|
||||
})
|
||||
@@ -65,8 +67,10 @@ func registerLoginHandlers(api *operations.ConsoleAPI) {
|
||||
}
|
||||
// Custom response writer to set the session cookies
|
||||
return middleware.ResponderFunc(func(w http.ResponseWriter, p runtime.Producer) {
|
||||
cookie := NewSessionCookieForConsole(loginResponse.SessionID)
|
||||
http.SetCookie(w, &cookie)
|
||||
cookies := NewSessionCookieForConsole(loginResponse.SessionID)
|
||||
for _, cookie := range cookies {
|
||||
http.SetCookie(w, &cookie)
|
||||
}
|
||||
user_api.NewLoginOauth2AuthCreated().WithPayload(loginResponse).WriteResponse(w, p)
|
||||
})
|
||||
})
|
||||
@@ -77,8 +81,10 @@ func registerLoginHandlers(api *operations.ConsoleAPI) {
|
||||
}
|
||||
// Custom response writer to set the session cookies
|
||||
return middleware.ResponderFunc(func(w http.ResponseWriter, p runtime.Producer) {
|
||||
cookie := NewSessionCookieForConsole(loginResponse.SessionID)
|
||||
http.SetCookie(w, &cookie)
|
||||
cookies := NewSessionCookieForConsole(loginResponse.SessionID)
|
||||
for _, cookie := range cookies {
|
||||
http.SetCookie(w, &cookie)
|
||||
}
|
||||
user_api.NewLoginOperatorCreated().WithPayload(loginResponse).WriteResponse(w, p)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -18,6 +18,7 @@ package restapi
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
@@ -83,8 +84,21 @@ func registerObjectsHandlers(api *operations.ConsoleAPI) {
|
||||
defer resp.Close()
|
||||
|
||||
// indicate it's a download / inline content to the browser, and the size of the object
|
||||
filename := params.Prefix
|
||||
var prefixPath string
|
||||
var filename string
|
||||
if params.Prefix != "" {
|
||||
encodedPrefix := params.Prefix
|
||||
decodedPrefix, err := base64.StdEncoding.DecodeString(encodedPrefix)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
}
|
||||
|
||||
prefixPath = string(decodedPrefix)
|
||||
}
|
||||
prefixElements := strings.Split(prefixPath, "/")
|
||||
if len(prefixElements) > 0 {
|
||||
filename = prefixElements[len(prefixElements)-1]
|
||||
}
|
||||
if isPreview {
|
||||
rw.Header().Set("Content-Disposition", fmt.Sprintf("inline; filename=\"%s\"", filename))
|
||||
rw.Header().Set("X-Frame-Options", "SAMEORIGIN")
|
||||
@@ -172,9 +186,13 @@ func getListObjectsResponse(session *models.Principal, params user_api.ListObjec
|
||||
var recursive bool
|
||||
var withVersions bool
|
||||
var withMetadata bool
|
||||
|
||||
if params.Prefix != nil {
|
||||
prefix = *params.Prefix
|
||||
encodedPrefix := *params.Prefix
|
||||
decodedPrefix, err := base64.StdEncoding.DecodeString(encodedPrefix)
|
||||
if err != nil {
|
||||
return nil, prepareError(err)
|
||||
}
|
||||
prefix = string(decodedPrefix)
|
||||
}
|
||||
if params.Recursive != nil {
|
||||
recursive = *params.Recursive
|
||||
@@ -270,7 +288,16 @@ func listBucketObjects(ctx context.Context, client MinioClient, bucketName strin
|
||||
|
||||
func getDownloadObjectResponse(session *models.Principal, params user_api.DownloadObjectParams) (io.ReadCloser, *models.Error) {
|
||||
ctx := context.Background()
|
||||
s3Client, err := newS3BucketClient(session, params.BucketName, params.Prefix)
|
||||
var prefix string
|
||||
if params.Prefix != "" {
|
||||
encodedPrefix := params.Prefix
|
||||
decodedPrefix, err := base64.StdEncoding.DecodeString(encodedPrefix)
|
||||
if err != nil {
|
||||
return nil, prepareError(err)
|
||||
}
|
||||
prefix = string(decodedPrefix)
|
||||
}
|
||||
s3Client, err := newS3BucketClient(session, params.BucketName, prefix)
|
||||
if err != nil {
|
||||
return nil, prepareError(err)
|
||||
}
|
||||
@@ -465,7 +492,12 @@ func getUploadObjectResponse(session *models.Principal, params user_api.PostBuck
|
||||
func uploadFiles(ctx context.Context, client MinioClient, params user_api.PostBucketsBucketNameObjectsUploadParams) error {
|
||||
var prefix string
|
||||
if params.Prefix != nil {
|
||||
prefix = *params.Prefix
|
||||
encodedPrefix := *params.Prefix
|
||||
decodedPrefix, err := base64.StdEncoding.DecodeString(encodedPrefix)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
prefix = string(decodedPrefix)
|
||||
}
|
||||
|
||||
// parse a request body as multipart/form-data.
|
||||
@@ -507,7 +539,16 @@ func uploadFiles(ctx context.Context, client MinioClient, params user_api.PostBu
|
||||
// getShareObjectResponse returns a share object url
|
||||
func getShareObjectResponse(session *models.Principal, params user_api.ShareObjectParams) (*string, *models.Error) {
|
||||
ctx := context.Background()
|
||||
s3Client, err := newS3BucketClient(session, params.BucketName, params.Prefix)
|
||||
var prefix string
|
||||
if params.Prefix != "" {
|
||||
encodedPrefix := params.Prefix
|
||||
decodedPrefix, err := base64.StdEncoding.DecodeString(encodedPrefix)
|
||||
if err != nil {
|
||||
return nil, prepareError(err)
|
||||
}
|
||||
prefix = string(decodedPrefix)
|
||||
}
|
||||
s3Client, err := newS3BucketClient(session, params.BucketName, prefix)
|
||||
if err != nil {
|
||||
return nil, prepareError(err)
|
||||
}
|
||||
@@ -552,7 +593,16 @@ func getSetObjectLegalHoldResponse(session *models.Principal, params user_api.Pu
|
||||
// create a minioClient interface implementation
|
||||
// defining the client to be used
|
||||
minioClient := minioClient{client: mClient}
|
||||
err = setObjectLegalHold(ctx, minioClient, params.BucketName, params.Prefix, params.VersionID, *params.Body.Status)
|
||||
var prefix string
|
||||
if params.Prefix != "" {
|
||||
encodedPrefix := params.Prefix
|
||||
decodedPrefix, err := base64.StdEncoding.DecodeString(encodedPrefix)
|
||||
if err != nil {
|
||||
return prepareError(err)
|
||||
}
|
||||
prefix = string(decodedPrefix)
|
||||
}
|
||||
err = setObjectLegalHold(ctx, minioClient, params.BucketName, prefix, params.VersionID, *params.Body.Status)
|
||||
if err != nil {
|
||||
return prepareError(err)
|
||||
}
|
||||
@@ -579,7 +629,16 @@ func getSetObjectRetentionResponse(session *models.Principal, params user_api.Pu
|
||||
// create a minioClient interface implementation
|
||||
// defining the client to be used
|
||||
minioClient := minioClient{client: mClient}
|
||||
err = setObjectRetention(ctx, minioClient, params.BucketName, params.VersionID, params.Prefix, params.Body)
|
||||
var prefix string
|
||||
if params.Prefix != "" {
|
||||
encodedPrefix := params.Prefix
|
||||
decodedPrefix, err := base64.StdEncoding.DecodeString(encodedPrefix)
|
||||
if err != nil {
|
||||
return prepareError(err)
|
||||
}
|
||||
prefix = string(decodedPrefix)
|
||||
}
|
||||
err = setObjectRetention(ctx, minioClient, params.BucketName, params.VersionID, prefix, params.Body)
|
||||
if err != nil {
|
||||
return prepareError(err)
|
||||
}
|
||||
@@ -623,7 +682,16 @@ func deleteObjectRetentionResponse(session *models.Principal, params user_api.De
|
||||
// create a minioClient interface implementation
|
||||
// defining the client to be used
|
||||
minioClient := minioClient{client: mClient}
|
||||
err = deleteObjectRetention(ctx, minioClient, params.BucketName, params.Prefix, params.VersionID)
|
||||
var prefix string
|
||||
if params.Prefix != "" {
|
||||
encodedPrefix := params.Prefix
|
||||
decodedPrefix, err := base64.StdEncoding.DecodeString(encodedPrefix)
|
||||
if err != nil {
|
||||
return prepareError(err)
|
||||
}
|
||||
prefix = string(decodedPrefix)
|
||||
}
|
||||
err = deleteObjectRetention(ctx, minioClient, params.BucketName, prefix, params.VersionID)
|
||||
if err != nil {
|
||||
return prepareError(err)
|
||||
}
|
||||
@@ -649,7 +717,16 @@ func getPutObjectTagsResponse(session *models.Principal, params user_api.PutObje
|
||||
// create a minioClient interface implementation
|
||||
// defining the client to be used
|
||||
minioClient := minioClient{client: mClient}
|
||||
err = putObjectTags(ctx, minioClient, params.BucketName, params.Prefix, params.VersionID, params.Body.Tags)
|
||||
var prefix string
|
||||
if params.Prefix != "" {
|
||||
encodedPrefix := params.Prefix
|
||||
decodedPrefix, err := base64.StdEncoding.DecodeString(encodedPrefix)
|
||||
if err != nil {
|
||||
return prepareError(err)
|
||||
}
|
||||
prefix = string(decodedPrefix)
|
||||
}
|
||||
err = putObjectTags(ctx, minioClient, params.BucketName, prefix, params.VersionID, params.Body.Tags)
|
||||
if err != nil {
|
||||
return prepareError(err)
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ package restapi
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
@@ -105,22 +106,73 @@ func FileExists(filename string) bool {
|
||||
return !info.IsDir()
|
||||
}
|
||||
|
||||
func NewSessionCookieForConsole(token string) http.Cookie {
|
||||
expiration := time.Now().Add(SessionDuration)
|
||||
func NewSessionCookieForConsole(token string) []http.Cookie {
|
||||
const CookieChunk = 3800
|
||||
|
||||
return http.Cookie{
|
||||
Path: "/",
|
||||
Name: "token",
|
||||
Value: token,
|
||||
MaxAge: int(SessionDuration.Seconds()), // 45 minutes
|
||||
Expires: expiration,
|
||||
HttpOnly: true,
|
||||
// if len(GlobalPublicCerts) > 0 is true, that means Console is running with TLS enable and the browser
|
||||
// should not leak any cookie if we access the site using HTTP
|
||||
Secure: len(GlobalPublicCerts) > 0,
|
||||
// read more: https://web.dev/samesite-cookies-explained/
|
||||
SameSite: http.SameSiteLaxMode,
|
||||
expiration := time.Now().Add(SessionDuration)
|
||||
var cookies []http.Cookie
|
||||
|
||||
i := 0
|
||||
cookieIndex := 0
|
||||
|
||||
for i < len(token) {
|
||||
var until int
|
||||
if i+CookieChunk < len(token) {
|
||||
until = i + CookieChunk
|
||||
} else {
|
||||
until = len(token)
|
||||
}
|
||||
|
||||
cookieName := "token"
|
||||
if len(cookies) > 0 {
|
||||
cookieName = fmt.Sprintf("token%d", len(cookies))
|
||||
}
|
||||
|
||||
cookie := http.Cookie{
|
||||
Path: "/",
|
||||
Name: cookieName,
|
||||
Value: token[i:until],
|
||||
MaxAge: int(SessionDuration.Seconds()), // 45 minutes
|
||||
Expires: expiration,
|
||||
HttpOnly: true,
|
||||
// if len(GlobalPublicCerts) > 0 is true, that means Console is running with TLS enable and the browser
|
||||
// should not leak any cookie if we access the site using HTTP
|
||||
Secure: len(GlobalPublicCerts) > 0,
|
||||
// read more: https://web.dev/samesite-cookies-explained/
|
||||
SameSite: http.SameSiteLaxMode,
|
||||
}
|
||||
|
||||
cookies = append(cookies, cookie)
|
||||
i += until
|
||||
cookieIndex++
|
||||
}
|
||||
|
||||
// clear old cookies
|
||||
expiredDuration := time.Now().Add(-1 * time.Second)
|
||||
for i := cookieIndex; i < 10; i++ {
|
||||
cookieName := "token"
|
||||
if len(cookies) > 0 {
|
||||
cookieName = fmt.Sprintf("token%d", i)
|
||||
}
|
||||
|
||||
cookie := http.Cookie{
|
||||
Path: "/",
|
||||
Name: cookieName,
|
||||
Value: "",
|
||||
MaxAge: 0, // 45 minutes
|
||||
Expires: expiredDuration,
|
||||
HttpOnly: true,
|
||||
// if len(GlobalPublicCerts) > 0 is true, that means Console is running with TLS enable and the browser
|
||||
// should not leak any cookie if we access the site using HTTP
|
||||
Secure: len(GlobalPublicCerts) > 0,
|
||||
// read more: https://web.dev/samesite-cookies-explained/
|
||||
SameSite: http.SameSiteLaxMode,
|
||||
}
|
||||
|
||||
cookies = append(cookies, cookie)
|
||||
}
|
||||
|
||||
return cookies
|
||||
}
|
||||
|
||||
func ExpireSessionCookie() http.Cookie {
|
||||
|
||||
Reference in New Issue
Block a user