Define base for assets and support for sub path (#1247)

* Added correct mime type to files

* Define Base for Assets

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

* lint

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

* Make things relative

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

* hop styling

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

Co-authored-by: Benjamin Perez <benjamin@bexsoft.net>
This commit is contained in:
Daniel Valdivia
2021-11-22 21:02:16 -08:00
committed by GitHub
parent 820fa61b43
commit 53d278a91e
11 changed files with 171 additions and 126 deletions

View File

@@ -185,7 +185,8 @@ func serveProxy(responseWriter http.ResponseWriter, req *http.Request) {
responseWriter.WriteHeader(500)
return
}
targetURL.Path = strings.Replace(req.URL.Path, fmt.Sprintf("/api/proxy/%s/%s", tenant.Namespace, tenant.Name), "", -1)
tenantBase := fmt.Sprintf("/api/proxy/%s/%s", tenant.Namespace, tenant.Name)
targetURL.Path = strings.Replace(req.URL.Path, tenantBase, "", -1)
proxiedCookie := &http.Cookie{
Name: "token",
@@ -207,8 +208,17 @@ func serveProxy(responseWriter http.ResponseWriter, req *http.Request) {
CheckRedirect: func(req *http.Request, via []*http.Request) error {
return http.ErrUseLastResponse
}}
// are we proxying something with cp=y? (console proxy) then add cpb (console proxy base) so the console
// on the other side updates the <base href="" /> to this value overriding sub path or root
if v := req.URL.Query().Get("cp"); v == "y" {
q := req.URL.Query()
q.Add("cpb", tenantBase)
req.URL.RawQuery = q.Encode()
}
// copy query params
targetURL.RawQuery = req.URL.Query().Encode()
proxRequest, err := http.NewRequest(req.Method, targetURL.String(), req.Body)
if err != nil {
log.Println(err)

View File

@@ -2,51 +2,52 @@
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<base href="/" />
<meta content="width=device-width, initial-scale=1" name="viewport" />
<meta
name="theme-color"
content="#081C42"
media="(prefers-color-scheme: light)"
name="theme-color"
/>
<meta
name="theme-color"
content="#081C42"
media="(prefers-color-scheme: dark)"
name="theme-color"
/>
<meta name="description" content="MinIO Console" />
<meta content="MinIO Console" name="description" />
<!--
manifest.json provides metadata used when your web app is installed on a
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
-->
<link href="%PUBLIC_URL%/styles/root-styles.css" rel="stylesheet" />
<link
href="%PUBLIC_URL%/apple-icon-180x180.png"
rel="apple-touch-icon"
sizes="180x180"
href="%PUBLIC_URL%/apple-icon-180x180.png"
/>
<link
rel="icon"
type="image/png"
sizes="32x32"
href="%PUBLIC_URL%/favicon-32x32.png"
rel="icon"
sizes="32x32"
type="image/png"
/>
<link
rel="icon"
type="image/png"
sizes="96x96"
href="%PUBLIC_URL%/favicon-96x96.png"
/>
<link
rel="icon"
sizes="96x96"
type="image/png"
sizes="16x16"
href="%PUBLIC_URL%/favicon-16x16.png"
/>
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<link
rel="mask-icon"
href="%PUBLIC_URL%/safari-pinned-tab.svg"
href="%PUBLIC_URL%/favicon-16x16.png"
rel="icon"
sizes="16x16"
type="image/png"
/>
<link href="%PUBLIC_URL%/manifest.json" rel="manifest" />
<link
color="#3a4e54"
href="%PUBLIC_URL%/safari-pinned-tab.svg"
rel="mask-icon"
/>
<!--
Notice the use of %PUBLIC_URL% in the tags above.
@@ -71,8 +72,8 @@
"
cx="44"
cy="44"
r="20.2"
fill="none"
r="20.2"
stroke-width="3.6"
></circle>
</svg>

View File

@@ -20,8 +20,8 @@ import { connect } from "react-redux";
import { AppState } from "./store";
import {
consoleOperatorMode,
userLoggedIn,
setDistributedMode,
userLoggedIn,
} from "./actions";
import api from "./common/api";
import { saveSessionResponse } from "./screens/Console/actions";

View File

@@ -17,12 +17,14 @@
import request from "superagent";
import get from "lodash/get";
import { clearSession } from "../utils";
import { baseUrl } from "../../history";
import { ErrorResponseHandler } from "../types";
export class API {
invoke(method: string, url: string, data?: object) {
const targetURL = `${baseUrl}${url}`.replace(/\/\//g, "/");
let targetURL = url;
if (targetURL[0] === "/") {
targetURL = targetURL.substr(1);
}
return request(method, targetURL)
.send(data)
.then((res) => res.body)

View File

@@ -3,13 +3,4 @@ import { BrowserHistoryBuildOptions } from "history/createBrowserHistory";
let browserHistoryOpts: BrowserHistoryBuildOptions = {};
export let baseUrl = "";
if (`${window.location.pathname}`.startsWith("/api/proxy/")) {
// grab from api to the tenant name (/api/proxy/namespace/tenant)
const urlParts = `${window.location.pathname}`.split("/").slice(0, 5);
browserHistoryOpts.basename = urlParts.join("/");
baseUrl = `${urlParts.join("/")}/`;
}
export default createBrowserHistory(browserHistoryOpts);

View File

@@ -66,7 +66,6 @@ import RewindEnable from "./RewindEnable";
import DeleteMultipleObjects from "./DeleteMultipleObjects";
import PreviewFileModal from "../Preview/PreviewFileModal";
import { baseUrl } from "../../../../../../history";
import ScreenTitle from "../../../../Common/ScreenTitle/ScreenTitle";
import AddFolderIcon from "../../../../../../icons/AddFolderIcon";
import HistoryIcon from "../../../../../../icons/HistoryIcon";
@@ -597,7 +596,7 @@ const ListObjects = ({
}
e.preventDefault();
let files = e.target.files;
let uploadUrl = `${baseUrl}/api/v1/buckets/${bucketName}/objects/upload`;
let uploadUrl = `api/v1/buckets/${bucketName}/objects/upload`;
if (encodedPath !== "") {
uploadUrl = `${uploadUrl}?prefix=${encodedPath}`;
}

View File

@@ -63,7 +63,7 @@ const PageHeader = ({
return (
<Grid
container
className={classes.headerContainer}
className={`${classes.headerContainer} page-header`}
direction="row"
alignItems="center"
>

View File

@@ -95,9 +95,7 @@ const Dashboard = React.lazy(() => import("./Dashboard/Dashboard"));
const Account = React.lazy(() => import("./Account/Account"));
const Users = React.lazy(() => import("./Users/Users"));
const Groups = React.lazy(() => import("./Groups/Groups"));
const ConfigurationMain = React.lazy(
() => import("./Configurations/ConfigurationMain")
);
const TenantDetails = React.lazy(
() => import("./Tenants/TenantDetails/TenantDetails")
);

View File

@@ -461,7 +461,7 @@ const Menu = ({
component: NavLink,
to: "/license",
name: "License",
icon: <LicenseIcon />,
icon: LicenseIcon,
},
{
...documentation,

View File

@@ -19,7 +19,7 @@ import { Theme } from "@mui/material/styles";
import createStyles from "@mui/styles/createStyles";
import withStyles from "@mui/styles/withStyles";
import { Link } from "react-router-dom";
import { CircularProgress, IconButton } from "@mui/material";
import { Box, CircularProgress, IconButton } from "@mui/material";
import PageHeader from "../../../Common/PageHeader/PageHeader";
import { containerForHeader } from "../../../Common/FormComponents/common/styleLibrary";
import ExitToAppIcon from "@mui/icons-material/ExitToApp";
@@ -46,8 +46,8 @@ const styles = (theme: Theme) =>
divContainer: {
position: "absolute",
left: 0,
top: 77,
height: "calc(100vh - 77px)",
top: 80,
height: "calc(100vh - 81px)",
width: "100%",
},
loader: {
@@ -55,6 +55,11 @@ const styles = (theme: Theme) =>
margin: "auto",
marginTop: 80,
},
pageHeader: {
borderBottom: "1px solid #dedede",
},
...containerForHeader(theme.spacing(4)),
});
@@ -66,7 +71,8 @@ const Hop = ({ classes, match }: IHopSimple) => {
const consoleFrame = React.useRef<HTMLIFrameElement>(null);
return (
<React.Fragment>
<Fragment>
<Box className={classes.pageHeader}>
<PageHeader
label={
<Fragment>
@@ -106,7 +112,9 @@ const Hop = ({ classes, match }: IHopSimple) => {
if (loc.indexOf("cp=y") < 0) {
const next = `${loc}${add}cp=y`;
consoleFrame.current.contentDocument.location.replace(next);
consoleFrame.current.contentDocument.location.replace(
next
);
} else {
consoleFrame.current.contentDocument.location.reload();
}
@@ -132,6 +140,7 @@ const Hop = ({ classes, match }: IHopSimple) => {
</React.Fragment>
}
/>
</Box>
<div className={classes.divContainer}>
{loading && (
<div className={classes.loader}>
@@ -148,7 +157,7 @@ const Hop = ({ classes, match }: IHopSimple) => {
}}
/>
</div>
</React.Fragment>
</Fragment>
);
};

View File

@@ -21,14 +21,16 @@ package restapi
import (
"bytes"
"crypto/tls"
"fmt"
"io"
"io/fs"
"log"
"net"
"net/http"
"os"
"path/filepath"
"regexp"
"strings"
"sync"
"time"
"github.com/klauspost/compress/gzhttp"
@@ -50,6 +52,9 @@ var additionalServerFlags = struct {
CertsDir string `long:"certs-dir" description:"path to certs directory" env:"CONSOLE_CERTS_DIR"`
}{}
var subPath = "/"
var subPathOnce sync.Once
func configureFlags(api *operations.ConsoleAPI) {
api.CommandLineOptionsGroups = []swag.CommandLineOptionsGroup{
{
@@ -251,8 +256,6 @@ func FileServerMiddleware(next http.Handler) http.Handler {
})
}
var reHrefIndex = regexp.MustCompile(`(?m)((href|src)="(.\/).*?")`)
type notFoundRedirectRespWr struct {
http.ResponseWriter // We embed http.ResponseWriter
status int
@@ -274,9 +277,15 @@ func (w *notFoundRedirectRespWr) Write(p []byte) (int, error) {
func handleSPA(w http.ResponseWriter, r *http.Request) {
basePath := "/"
// For SPA mode we will replace relative paths with absolute unless we receive query param cp=y
// For SPA mode we will replace root base with a sub path if configured unless we received cp=y and cpb=/NEW/BASE
if v := r.URL.Query().Get("cp"); v == "y" {
basePath = "./"
if base := r.URL.Query().Get("cpb"); base != "" {
// make sure the subpath has a trailing slash
if !strings.HasSuffix(base, "/") {
base = fmt.Sprintf("%s/", base)
}
basePath = base
}
}
indexPage, err := portal_ui.GetStaticAssets().Open("build/index.html")
@@ -291,16 +300,21 @@ func handleSPA(w http.ResponseWriter, r *http.Request) {
return
}
if basePath != "./" {
indexPageStr := string(indexPageBytes)
for _, match := range reHrefIndex.FindAllStringSubmatch(indexPageStr, -1) {
toReplace := strings.Replace(match[1], match[3], basePath, 1)
indexPageStr = strings.Replace(indexPageStr, match[1], toReplace, 1)
}
indexPageBytes = []byte(indexPageStr)
// if we have a seeded basePath. This should override CONSOLE_SUBPATH every time, thus the `if else`
if basePath != "/" {
indexPageBytes = replaceBaseInIndex(indexPageBytes, basePath)
// if we have a custom subpath replace it in
} else if getSubPath() != "/" {
indexPageBytes = replaceBaseInIndex(indexPageBytes, getSubPath())
}
w.Header().Set("Content-Type", "text/html; charset=utf-8")
mimeType := mimedb.TypeByExtension(filepath.Ext(r.URL.Path))
if mimeType == "application/octet-stream" {
mimeType = "text/html"
}
w.Header().Set("Content-Type", mimeType)
http.ServeContent(w, r, "index.html", time.Now(), bytes.NewReader(indexPageBytes))
}
@@ -335,3 +349,24 @@ func configureServer(s *http.Server, _, _ string) {
// Turn-off random logging by Go net/http
s.ErrorLog = log.New(&nullWriter{}, "", 0)
}
func getSubPath() string {
subPathOnce.Do(func() {
if v := os.Getenv("CONSOLE_SUBPATH"); v != "" {
// make sure the subpath has a trailing slash
if !strings.HasSuffix(v, "/") {
v = fmt.Sprintf("%s/", v)
}
subPath = v
}
})
return subPath
}
func replaceBaseInIndex(indexPageBytes []byte, basePath string) []byte {
indexPageStr := string(indexPageBytes)
newBase := fmt.Sprintf("<base href=\"%s\"/>", basePath)
indexPageStr = strings.Replace(indexPageStr, "<base href=\"/\"/>", newBase, 1)
indexPageBytes = []byte(indexPageStr)
return indexPageBytes
}