Support for Hop into tenants (#878)

Signed-off-by: Daniel Valdivia <18384552+dvaldivia@users.noreply.github.com>
This commit is contained in:
Daniel Valdivia
2021-07-21 15:09:49 -07:00
committed by GitHub
parent a6aa0f14c1
commit 6f7c026e4c
18 changed files with 191 additions and 25 deletions

View File

@@ -64,7 +64,6 @@ func serveProxy(responseWriter http.ResponseWriter, req *http.Request) {
return
}
//STSSessionToken := currToken.Value
STSSessionToken := claims.STSSessionToken
opClientClientSet, err := cluster.OperatorClient(STSSessionToken)
@@ -98,7 +97,6 @@ func serveProxy(responseWriter http.ResponseWriter, req *http.Request) {
h := sha1.New()
h.Write([]byte(nsTenant))
log.Printf("Proxying request for %s/%s", namespace, tenantName)
tenantCookieName := fmt.Sprintf("token-%x", string(h.Sum(nil)))
tenantCookie, err := req.Cookie(tenantCookieName)
if err != nil {
@@ -113,7 +111,7 @@ func serveProxy(responseWriter http.ResponseWriter, req *http.Request) {
return
}
currentSecret, err := clientSet.CoreV1().Secrets(namespace).Get(req.Context(), tenant.Spec.CredsSecret.Name, metav1.GetOptions{})
currentSecret, err := clientSet.CoreV1().Secrets(tenant.Namespace).Get(req.Context(), tenant.Spec.CredsSecret.Name, metav1.GetOptions{})
if err != nil {
log.Println(err)
responseWriter.WriteHeader(500)

View File

@@ -44,6 +44,7 @@ var (
changePassword = "/account/change-password"
tenants = "/tenants"
tenantsDetail = "/namespaces/:tenantNamespace/tenants/:tenantName"
tenantHop = "/namespaces/:tenantNamespace/tenants/:tenantName/hop"
podsDetail = "/namespaces/:tenantNamespace/tenants/:tenantName/pods/:podName"
tenantsDetailSummary = "/namespaces/:tenantNamespace/tenants/:tenantName/summary"
tenantsDetailMetrics = "/namespaces/:tenantNamespace/tenants/:tenantName/metrics"
@@ -324,6 +325,7 @@ var endpointRules = map[string]ConfigurationActionSet{
var operatorRules = map[string]ConfigurationActionSet{
tenants: tenantsActionSet,
tenantsDetail: tenantsActionSet,
tenantHop: tenantsActionSet,
tenantsDetailSummary: tenantsActionSet,
tenantsDetailMetrics: tenantsActionSet,
tenantsDetailPods: tenantsActionSet,

View File

@@ -116,7 +116,7 @@ func TestOperatorOnlyEndpoints(t *testing.T) {
"admin:*",
},
},
want: 13,
want: 14,
},
{
name: "Operator Only - all s3 endpoints",
@@ -125,7 +125,7 @@ func TestOperatorOnlyEndpoints(t *testing.T) {
"s3:*",
},
},
want: 13,
want: 14,
},
{
name: "Operator Only - all admin and s3 endpoints",
@@ -135,14 +135,14 @@ func TestOperatorOnlyEndpoints(t *testing.T) {
"s3:*",
},
},
want: 13,
want: 14,
},
{
name: "Operator Only - default endpoints",
args: args{
[]string{},
},
want: 13,
want: 14,
},
}

View File

@@ -5,20 +5,20 @@
"main.js.map": "/static/js/main.439bfd00.chunk.js.map",
"runtime-main.js": "/static/js/runtime-main.43a31377.js",
"runtime-main.js.map": "/static/js/runtime-main.43a31377.js.map",
"static/css/2.c5a51b70.chunk.css": "/static/css/2.c5a51b70.chunk.css",
"static/js/2.55ef6ad7.chunk.js": "/static/js/2.55ef6ad7.chunk.js",
"static/js/2.55ef6ad7.chunk.js.map": "/static/js/2.55ef6ad7.chunk.js.map",
"static/css/2.20c81d8d.chunk.css": "/static/css/2.20c81d8d.chunk.css",
"static/js/2.02087608.chunk.js": "/static/js/2.02087608.chunk.js",
"static/js/2.02087608.chunk.js.map": "/static/js/2.02087608.chunk.js.map",
"index.html": "/index.html",
"static/css/2.c5a51b70.chunk.css.map": "/static/css/2.c5a51b70.chunk.css.map",
"static/css/2.20c81d8d.chunk.css.map": "/static/css/2.20c81d8d.chunk.css.map",
"static/css/main.8cfac526.chunk.css.map": "/static/css/main.8cfac526.chunk.css.map",
"static/js/2.55ef6ad7.chunk.js.LICENSE.txt": "/static/js/2.55ef6ad7.chunk.js.LICENSE.txt",
"static/js/2.02087608.chunk.js.LICENSE.txt": "/static/js/2.02087608.chunk.js.LICENSE.txt",
"static/media/minio_console_logo.0837460e.svg": "/static/media/minio_console_logo.0837460e.svg",
"static/media/minio_operator_logo.1312b7c9.svg": "/static/media/minio_operator_logo.1312b7c9.svg"
},
"entrypoints": [
"static/js/runtime-main.43a31377.js",
"static/css/2.c5a51b70.chunk.css",
"static/js/2.55ef6ad7.chunk.js",
"static/css/2.20c81d8d.chunk.css",
"static/js/2.02087608.chunk.js",
"static/css/main.8cfac526.chunk.css",
"static/js/main.439bfd00.chunk.js"
]

View File

@@ -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="#000000"/><meta name="description" content="MinIO Console"/><link href="https://fonts.googleapis.com/css2?family=Lato:wght@400;500;700;900&display=swap" rel="stylesheet"/><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.c5a51b70.chunk.css" rel="stylesheet"><link href="/static/css/main.8cfac526.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.55ef6ad7.chunk.js"></script><script src="/static/js/main.439bfd00.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="#000000"/><meta name="description" content="MinIO Console"/><link href="https://fonts.googleapis.com/css2?family=Lato:wght@400;500;700;900&display=swap" rel="stylesheet"/><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.c5a51b70.chunk.css" rel="stylesheet"><link href="/static/css/main.8cfac526.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.55ef6ad7.chunk.js"></script><script src="/static/js/main.439bfd00.chunk.js"></script></body></html>

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

View File

@@ -58,6 +58,7 @@ import HealthInfo from "./HealthInfo/HealthInfo";
import Storage from "./Storage/Storage";
import PodDetails from "./Tenants/TenantDetails/pods/PodDetails";
import Metrics from "./Dashboard/Metrics";
import Hop from "./Tenants/TenantDetails/hop/Hop";
const drawerWidth = 245;
@@ -359,6 +360,10 @@ const Console = ({
component: TenantDetails,
path: "/namespaces/:tenantNamespace/tenants/:tenantName",
},
{
component: Hop,
path: "/namespaces/:tenantNamespace/tenants/:tenantName/hop",
},
{
component: PodDetails,
path: "/namespaces/:tenantNamespace/tenants/:tenantName/pods/:podName",
@@ -410,12 +415,19 @@ const Console = ({
const location = useLocation();
let hideMenu = false;
if (location.pathname === "/metrics") {
hideMenu = true;
} else if (location.pathname.endsWith("/hop")) {
hideMenu = true;
}
return (
<Fragment>
{session.status === "ok" ? (
<div className={classes.root}>
<CssBaseline />
{location.pathname !== "/metrics" && (
{!hideMenu && (
<Drawer
variant="permanent"
classes={{

View File

@@ -26,8 +26,8 @@ import Tab from "@material-ui/core/Tab";
import { setErrorSnackMessage } from "../../../../actions";
import {
setTenantDetailsLoad,
setTenantName,
setTenantInfo,
setTenantName,
setTenantTab,
} from "../actions";
import { ITenant } from "../ListTenants/types";
@@ -193,7 +193,7 @@ const TenantDetails = ({
label={
<Fragment>
<Link to={"/tenants"} className={classes.breadcrumLink}>
Tenant
Tenants
</Link>
{` > ${match.params["tenantName"]}`}
<IconButton

View File

@@ -14,7 +14,7 @@
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
import React, { Fragment, useState } from "react";
import React, { useState } from "react";
import { connect } from "react-redux";
import get from "lodash/get";
import { createStyles, Theme, withStyles } from "@material-ui/core/styles";

View File

@@ -31,6 +31,7 @@ import { ITenant } from "../ListTenants/types";
import UsageBarWrapper from "../../Common/UsageBarWrapper/UsageBarWrapper";
import UpdateTenantModal from "./UpdateTenantModal";
import { AppState } from "../../../../store";
import history from "./../../../../history";
interface ITenantsSummary {
classes: any;
@@ -319,6 +320,19 @@ const TenantSummary = ({
</tr>
</tbody>
</table>
<Button
size={"small"}
color={"primary"}
variant="contained"
style={{ textDecoration: "none !important" }}
onClick={() => {
history.push(
`/namespaces/${tenantNamespace}/tenants/${tenantName}/hop`
);
}}
>
Management UI
</Button>
</Fragment>
)}
</Grid>

View File

@@ -0,0 +1,137 @@
// This file is part of MinIO Console Server
// Copyright (c) 2021 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
import React, { Fragment, useState } from "react";
import { createStyles, Theme, withStyles } from "@material-ui/core/styles";
import { Link } from "react-router-dom";
import { CircularProgress, IconButton } from "@material-ui/core";
import RefreshIcon from "@material-ui/icons/Refresh";
import PageHeader from "../../../Common/PageHeader/PageHeader";
import { containerForHeader } from "../../../Common/FormComponents/common/styleLibrary";
import ExitToAppIcon from "@material-ui/icons/ExitToApp";
import history from "./../../../../../history";
interface IHopSimple {
classes: any;
match: any;
}
const styles = (theme: Theme) =>
createStyles({
breadcrumLink: {
textDecoration: "none",
color: "black",
},
iframeStyle: {
border: 0,
position: "absolute",
height: "calc(100vh - 77px)",
width: "100%",
},
divContainer: {
position: "absolute",
left: 0,
top: 77,
height: "calc(100vh - 77px)",
width: "100%",
},
loader: {
width: 100,
margin: "auto",
marginTop: 80,
},
...containerForHeader(theme.spacing(4)),
});
const Hop = ({ classes, match }: IHopSimple) => {
const [loading, setLoading] = useState<boolean>(true);
const tenantName = match.params["tenantName"];
const tenantNamespace = match.params["tenantNamespace"];
const consoleFrame = React.useRef<HTMLIFrameElement>(null);
return (
<React.Fragment>
<PageHeader
label={
<Fragment>
<Link to={"/tenants"} className={classes.breadcrumLink}>
Tenants
</Link>
{` > `}
<Link
to={`/namespaces/${tenantNamespace}/tenants/${tenantName}`}
className={classes.breadcrumLink}
>
{match.params["tenantName"]}
</Link>
{` > Management`}
</Fragment>
}
actions={
<React.Fragment>
<IconButton
color="primary"
aria-label="Refresh List"
component="span"
onClick={() => {
if (
consoleFrame !== null &&
consoleFrame.current !== null &&
consoleFrame.current.contentDocument !== null
) {
consoleFrame.current.contentDocument.location.reload(true);
}
}}
>
<RefreshIcon />
</IconButton>
<IconButton
color="primary"
aria-label="Refresh List"
component="span"
onClick={() => {
history.push(
`/namespaces/${tenantNamespace}/tenants/${tenantName}`
);
}}
>
<ExitToAppIcon />
</IconButton>
</React.Fragment>
}
/>
<div className={classes.divContainer}>
{loading && (
<div className={classes.loader}>
<CircularProgress />
</div>
)}
<iframe
ref={consoleFrame}
className={classes.iframeStyle}
title={"metrics"}
src={`/api/proxy/${tenantNamespace}/${tenantName}/`}
onLoad={(val) => {
setLoading(false);
}}
/>
</div>
</React.Fragment>
);
};
export default withStyles(styles)(Hop);