Compare commits

...

7 Commits

Author SHA1 Message Date
Alex
543076eaac Release v0.26.1 (#2759)
Signed-off-by: Benjamin Perez <benjamin@bexsoft.net>
Co-authored-by: Benjamin Perez <benjamin@bexsoft.net>
2023-04-05 13:11:39 -07:00
Alex
cbf1ddeb4c Added support for root credentials login with LDAP enabled (#2758)
Signed-off-by: Benjamin Perez <benjamin@bexsoft.net>
2023-04-04 15:38:32 -06:00
Daniel Valdivia
3746adcc13 Update Login Message (#2757) 2023-04-04 12:24:10 +02:00
Alex
854b984850 Display explicit errors if websocket connection cannot be stablished (#2756)
Signed-off-by: Benjamin Perez <benjamin@bexsoft.net>
2023-04-03 18:10:59 -06:00
Alex
62fa0e2043 Updated Entities results panel styles (#2753)
Signed-off-by: Benjamin Perez <benjamin@bexsoft.net>
2023-03-31 22:09:37 -06:00
Javier Adriel
4f5b1b0aa7 Show modal to restart minio after subnet register (#2752) 2023-03-31 17:31:54 -06:00
MinIO Bot
0e362c2106 mds-released-v0.3.3 (#2750)
Co-authored-by: MinIO Bot <minio.bot@minio.io>
2023-03-30 14:11:56 +09:00
32 changed files with 472 additions and 380 deletions

View File

@@ -1,7 +1,7 @@
{
"files": {
"main.css": "./static/css/main.57e739f5.css",
"main.js": "./static/js/main.078385cd.js",
"main.js": "./static/js/main.46564223.js",
"static/js/1260.a025e586.chunk.js": "./static/js/1260.a025e586.chunk.js",
"static/js/6914.8835970e.chunk.js": "./static/js/6914.8835970e.chunk.js",
"static/js/9121.4999947f.chunk.js": "./static/js/9121.4999947f.chunk.js",
@@ -35,7 +35,7 @@
"static/js/191.b50b4104.chunk.js": "./static/js/191.b50b4104.chunk.js",
"static/js/1329.98d80e22.chunk.js": "./static/js/1329.98d80e22.chunk.js",
"static/js/7614.527036f5.chunk.js": "./static/js/7614.527036f5.chunk.js",
"static/js/5351.41f5c7df.chunk.js": "./static/js/5351.41f5c7df.chunk.js",
"static/js/1690.4e6b342c.chunk.js": "./static/js/1690.4e6b342c.chunk.js",
"static/js/6491.83aa26f7.chunk.js": "./static/js/6491.83aa26f7.chunk.js",
"static/js/4902.f064f175.chunk.js": "./static/js/4902.f064f175.chunk.js",
"static/js/1432.6e142c2c.chunk.js": "./static/js/1432.6e142c2c.chunk.js",
@@ -44,7 +44,7 @@
"static/js/6577.4b3c8b41.chunk.js": "./static/js/6577.4b3c8b41.chunk.js",
"static/js/3875.5db2c08f.chunk.js": "./static/js/3875.5db2c08f.chunk.js",
"static/js/3115.da3a98f3.chunk.js": "./static/js/3115.da3a98f3.chunk.js",
"static/js/5522.72ab8875.chunk.js": "./static/js/5522.72ab8875.chunk.js",
"static/js/5522.9c77c391.chunk.js": "./static/js/5522.9c77c391.chunk.js",
"static/js/977.4c29a863.chunk.js": "./static/js/977.4c29a863.chunk.js",
"static/js/6686.ede27280.chunk.js": "./static/js/6686.ede27280.chunk.js",
"static/js/9059.155bb503.chunk.js": "./static/js/9059.155bb503.chunk.js",
@@ -52,7 +52,7 @@
"static/js/6247.af0fb000.chunk.js": "./static/js/6247.af0fb000.chunk.js",
"static/js/4414.ff5f95b9.chunk.js": "./static/js/4414.ff5f95b9.chunk.js",
"static/js/8833.4ba5da15.chunk.js": "./static/js/8833.4ba5da15.chunk.js",
"static/js/1516.147495bb.chunk.js": "./static/js/1516.147495bb.chunk.js",
"static/js/1516.d1e7e873.chunk.js": "./static/js/1516.d1e7e873.chunk.js",
"static/js/483.b039dcc0.chunk.js": "./static/js/483.b039dcc0.chunk.js",
"static/js/4114.9ce1a962.chunk.js": "./static/js/4114.9ce1a962.chunk.js",
"static/js/6895.143165e2.chunk.js": "./static/js/6895.143165e2.chunk.js",
@@ -140,7 +140,7 @@
"static/media/Inter-Regular.woff2": "./static/media/Inter-Regular.c8ba52b05a9ef10f4758.woff2",
"index.html": "./index.html",
"main.57e739f5.css.map": "./static/css/main.57e739f5.css.map",
"main.078385cd.js.map": "./static/js/main.078385cd.js.map",
"main.46564223.js.map": "./static/js/main.46564223.js.map",
"1260.a025e586.chunk.js.map": "./static/js/1260.a025e586.chunk.js.map",
"6914.8835970e.chunk.js.map": "./static/js/6914.8835970e.chunk.js.map",
"9121.4999947f.chunk.js.map": "./static/js/9121.4999947f.chunk.js.map",
@@ -174,7 +174,7 @@
"191.b50b4104.chunk.js.map": "./static/js/191.b50b4104.chunk.js.map",
"1329.98d80e22.chunk.js.map": "./static/js/1329.98d80e22.chunk.js.map",
"7614.527036f5.chunk.js.map": "./static/js/7614.527036f5.chunk.js.map",
"5351.41f5c7df.chunk.js.map": "./static/js/5351.41f5c7df.chunk.js.map",
"1690.4e6b342c.chunk.js.map": "./static/js/1690.4e6b342c.chunk.js.map",
"6491.83aa26f7.chunk.js.map": "./static/js/6491.83aa26f7.chunk.js.map",
"4902.f064f175.chunk.js.map": "./static/js/4902.f064f175.chunk.js.map",
"1432.6e142c2c.chunk.js.map": "./static/js/1432.6e142c2c.chunk.js.map",
@@ -183,7 +183,7 @@
"6577.4b3c8b41.chunk.js.map": "./static/js/6577.4b3c8b41.chunk.js.map",
"3875.5db2c08f.chunk.js.map": "./static/js/3875.5db2c08f.chunk.js.map",
"3115.da3a98f3.chunk.js.map": "./static/js/3115.da3a98f3.chunk.js.map",
"5522.72ab8875.chunk.js.map": "./static/js/5522.72ab8875.chunk.js.map",
"5522.9c77c391.chunk.js.map": "./static/js/5522.9c77c391.chunk.js.map",
"977.4c29a863.chunk.js.map": "./static/js/977.4c29a863.chunk.js.map",
"6686.ede27280.chunk.js.map": "./static/js/6686.ede27280.chunk.js.map",
"9059.155bb503.chunk.js.map": "./static/js/9059.155bb503.chunk.js.map",
@@ -191,7 +191,7 @@
"6247.af0fb000.chunk.js.map": "./static/js/6247.af0fb000.chunk.js.map",
"4414.ff5f95b9.chunk.js.map": "./static/js/4414.ff5f95b9.chunk.js.map",
"8833.4ba5da15.chunk.js.map": "./static/js/8833.4ba5da15.chunk.js.map",
"1516.147495bb.chunk.js.map": "./static/js/1516.147495bb.chunk.js.map",
"1516.d1e7e873.chunk.js.map": "./static/js/1516.d1e7e873.chunk.js.map",
"483.b039dcc0.chunk.js.map": "./static/js/483.b039dcc0.chunk.js.map",
"4114.9ce1a962.chunk.js.map": "./static/js/4114.9ce1a962.chunk.js.map",
"6895.143165e2.chunk.js.map": "./static/js/6895.143165e2.chunk.js.map",
@@ -260,6 +260,6 @@
},
"entrypoints": [
"static/css/main.57e739f5.css",
"static/js/main.078385cd.js"
"static/js/main.46564223.js"
]
}

View File

@@ -1 +1 @@
<!doctype html><html lang="en"><head><meta charset="utf-8"/><base href="/"/><meta content="width=device-width,initial-scale=1" name="viewport"/><meta content="#081C42" media="(prefers-color-scheme: light)" name="theme-color"/><meta content="#081C42" media="(prefers-color-scheme: dark)" name="theme-color"/><meta content="MinIO Console" name="description"/><meta name="minio-license" content="apgl"/><link href="./styles/root-styles.css" rel="stylesheet"/><link href="./apple-icon-180x180.png" rel="apple-touch-icon" sizes="180x180"/><link href="./favicon-32x32.png" rel="icon" sizes="32x32" type="image/png"/><link href="./favicon-96x96.png" rel="icon" sizes="96x96" type="image/png"/><link href="./favicon-16x16.png" rel="icon" sizes="16x16" type="image/png"/><link href="./manifest.json" rel="manifest"/><link color="#3a4e54" href="./safari-pinned-tab.svg" rel="mask-icon"/><title>MinIO Console</title><script defer="defer" src="./static/js/main.078385cd.js"></script><link href="./static/css/main.57e739f5.css" rel="stylesheet"></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"><div id="preload"><img src="./images/background.svg"/> <img src="./images/background-wave-orig2.svg"/></div><div id="loader-block"><img src="./Loader.svg"/></div></div></body></html>
<!doctype html><html lang="en"><head><meta charset="utf-8"/><base href="/"/><meta content="width=device-width,initial-scale=1" name="viewport"/><meta content="#081C42" media="(prefers-color-scheme: light)" name="theme-color"/><meta content="#081C42" media="(prefers-color-scheme: dark)" name="theme-color"/><meta content="MinIO Console" name="description"/><meta name="minio-license" content="apgl"/><link href="./styles/root-styles.css" rel="stylesheet"/><link href="./apple-icon-180x180.png" rel="apple-touch-icon" sizes="180x180"/><link href="./favicon-32x32.png" rel="icon" sizes="32x32" type="image/png"/><link href="./favicon-96x96.png" rel="icon" sizes="96x96" type="image/png"/><link href="./favicon-16x16.png" rel="icon" sizes="16x16" type="image/png"/><link href="./manifest.json" rel="manifest"/><link color="#3a4e54" href="./safari-pinned-tab.svg" rel="mask-icon"/><title>MinIO Console</title><script defer="defer" src="./static/js/main.46564223.js"></script><link href="./static/css/main.57e739f5.css" rel="stylesheet"></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"><div id="preload"><img src="./images/background.svg"/> <img src="./images/background-wave-orig2.svg"/></div><div id="loader-block"><img src="./Loader.svg"/></div></div></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

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

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

View File

@@ -18,7 +18,7 @@
"local-storage-fallback": "^4.1.1",
"lodash": "^4.17.21",
"luxon": "^3.3.0",
"mds": "https://github.com/minio/mds.git#v0.3.2",
"mds": "https://github.com/minio/mds.git#v0.3.3",
"minio": "^7.0.32",
"react": "^18.1.0",
"react-component-export-image": "^1.0.6",

View File

@@ -26,6 +26,7 @@ import { containerForHeader } from "../../Common/FormComponents/common/styleLibr
import ListObjects from "../ListBuckets/Objects/ListObjects/ListObjects";
import { IAM_SCOPES } from "../../../../common/SecureComponent/permissions";
import {
errorInConnection,
newMessage,
resetMessages,
setIsOpeningOD,
@@ -70,7 +71,8 @@ let wsInFlight: boolean = false;
const initWSConnection = (
openCallback?: () => void,
onMessageCallback?: (message: IMessageEvent) => void
onMessageCallback?: (message: IMessageEvent) => void,
connErrorCallback?: (message: string) => void
) => {
if (wsInFlight) {
return;
@@ -104,10 +106,17 @@ const initWSConnection = (
const reconnectFn = () => {
if (errorCounter <= 5) {
initWSConnection(() => {}, onMessageCallback);
initWSConnection(() => {}, onMessageCallback, connErrorCallback);
errorCounter += 1;
} else {
console.error("Websocket not available.");
console.error(
"Websocket not available. Please review that your environment settings are enabled to allow websocket connections and that requests are made from the same origin."
);
if (connErrorCallback) {
connErrorCallback(
"Couldn't establish WebSocket connection. Please review your configuration and try again."
);
}
}
};
@@ -245,6 +254,7 @@ const BrowserHandler = () => {
try {
const newRequestID = currentRequestID + 1;
dispatch(resetMessages());
dispatch(errorInConnection(false));
const request: WebsocketRequest = {
bucket_name: bucketName,
@@ -266,7 +276,18 @@ const BrowserHandler = () => {
const dupRequest = () => {
initWSRequest(path, date);
};
initWSConnection(dupRequest, onMessageCallBack);
const fatalWSError = (message: string) => {
dispatch(
setErrorSnackMessage({
errorMessage: message,
detailedError: message,
})
);
dispatch(errorInConnection(true));
};
initWSConnection(dupRequest, onMessageCallBack, fatalWSError);
}
},
[bucketName, rewindEnabled, showDeleted, dispatch, onMessageCallBack]

View File

@@ -117,9 +117,13 @@ const ListObjectsTable = ({ internalPaths }: IListObjectTable) => {
const selectedObjects = useSelector(
(state: AppState) => state.objectBrowser.selectedObjects
);
const connectionError = useSelector(
(state: AppState) => state.objectBrowser.connectionError
);
const anonymousMode = useSelector(
(state: AppState) => state.system.anonymousMode
);
const displayListObjects = hasPermission(bucketName, [
IAM_SCOPES.S3_LIST_BUCKET,
IAM_SCOPES.S3_ALL_LIST_BUCKET,
@@ -228,6 +232,21 @@ const ListObjectsTable = ({ internalPaths }: IListObjectTable) => {
return elements;
};
let errorMessage =
!displayListObjects && !anonymousMode
? permissionTooltipHelper(
[IAM_SCOPES.S3_LIST_BUCKET, IAM_SCOPES.S3_ALL_LIST_BUCKET],
"view Objects in this bucket"
)
: `This location is empty${
!rewindEnabled ? ", please try uploading a new file" : ""
}`;
if (connectionError) {
errorMessage =
"Objects List unavailable. Please review your WebSockets configuration and try again";
}
return (
<TableWrapper
itemActions={tableActions}
@@ -241,16 +260,7 @@ const ListObjectsTable = ({ internalPaths }: IListObjectTable) => {
} ${detailsOpen ? "actionsPanelOpen" : ""}`}
selectedItems={selectedObjects}
onSelect={!anonymousMode ? selectListObjects : undefined}
customEmptyMessage={
!displayListObjects && !anonymousMode
? permissionTooltipHelper(
[IAM_SCOPES.S3_LIST_BUCKET, IAM_SCOPES.S3_ALL_LIST_BUCKET],
"view Objects in this bucket"
)
: `This location is empty${
!rewindEnabled ? ", please try uploading a new file" : ""
}`
}
customEmptyMessage={errorMessage}
sortConfig={{
currentSort: currentSortField,
currentDirection: sortDirection,

View File

@@ -27,13 +27,15 @@ import {
InputBox,
Loader,
RemoveIcon,
SearchIcon,
SectionTitle,
UptimeIcon,
TimeIcon,
} from "mds";
import PolicySelectors from "../../Policies/PolicySelectors";
import { useSelector } from "react-redux";
import { LDAPEntitiesResponse } from "./types";
import { DateTime } from "luxon";
import LDAPResultsBlock from "./LDAPResultsBlock";
const LDAPEntitiesQuery = () => {
const dispatch = useAppDispatch();
@@ -107,90 +109,128 @@ const LDAPEntitiesQuery = () => {
};
return (
<Box sx={{ marginTop: 15, paddingTop: 0 }} withBorders>
<Box sx={{ marginTop: 15, paddingTop: 0 }}>
<Grid container sx={{ marginTop: 5 }}>
<Grid item sm={12} md={6} lg={5} sx={{ padding: 10, paddingTop: 0 }}>
<SectionTitle separator>Query Filters</SectionTitle>
<SectionTitle>Query Filters</SectionTitle>
<Box sx={{ padding: "0 10px" }}>
<h4>Users</h4>
<Box
sx={{
overflowY: "auto",
minHeight: 220,
maxHeight: 250,
"& > div > div": {
width: "100%",
},
}}
>
{users.map((userDat, index) => {
return (
<InputBox
id={`search-user-${index}`}
key={`search-user-${index}`}
value={userDat}
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
const usersElements = [...users];
usersElements[index] = e.target.value;
setUsers(usersElements);
}}
overlayIcon={
users.length === index + 1 ? <AddIcon /> : <RemoveIcon />
}
overlayAction={() => {
alterUsersList(users.length === index + 1, index);
}}
/>
);
})}
<Box
sx={{
padding: "0 10px",
display: "flex",
flexDirection: "column",
gap: 40,
}}
>
<Box sx={{ padding: "10px 26px" }} withBorders>
<Box sx={{ display: "flex" }}>
<h4 style={{ margin: 0, marginBottom: 10, fontSize: 14 }}>
Users
</h4>
</Box>
<Box
sx={{
overflowY: "auto",
minHeight: 50,
maxHeight: 250,
"& > div > div": {
width: "100%",
},
}}
>
{users.map((userDat, index) => {
return (
<InputBox
id={`search-user-${index}`}
key={`search-user-${index}`}
value={userDat}
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
const usersElements = [...users];
usersElements[index] = e.target.value;
setUsers(usersElements);
}}
overlayIcon={
users.length === index + 1 ? (
<AddIcon />
) : (
<RemoveIcon />
)
}
overlayAction={() => {
alterUsersList(users.length === index + 1, index);
}}
/>
);
})}
</Box>
</Box>
<h4>Groups</h4>
<Box
sx={{
overflowY: "auto",
minHeight: 220,
maxHeight: 250,
"& > div > div": {
width: "100%",
},
}}
>
{groups.map((groupDat, index) => {
return (
<InputBox
id={`search-group-${index}`}
key={`search-group-${index}`}
value={groupDat}
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
const groupsElements = [...groups];
groupsElements[index] = e.target.value;
setGroups(groupsElements);
}}
overlayIcon={
groups.length === index + 1 ? <AddIcon /> : <RemoveIcon />
}
overlayAction={() => {
alterGroupsList(groups.length === index + 1, index);
}}
/>
);
})}
<Box sx={{ padding: "10px 26px" }} withBorders>
<h4 style={{ margin: 0, marginBottom: 10, fontSize: 14 }}>
Groups
</h4>
<Box
sx={{
overflowY: "auto",
minHeight: 50,
maxHeight: "calc(100vh - 340px)",
"& > div > div": {
width: "100%",
},
}}
>
{groups.map((groupDat, index) => {
return (
<InputBox
id={`search-group-${index}`}
key={`search-group-${index}`}
value={groupDat}
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
const groupsElements = [...groups];
groupsElements[index] = e.target.value;
setGroups(groupsElements);
}}
overlayIcon={
groups.length === index + 1 ? (
<AddIcon />
) : (
<RemoveIcon />
)
}
overlayAction={() => {
alterGroupsList(groups.length === index + 1, index);
}}
/>
);
})}
</Box>
</Box>
<h4>Policies</h4>
<Box
sx={{
minHeight: 220,
maxHeight: "calc(100vh - 740px)",
}}
>
<PolicySelectors selectedPolicy={selectedPolicies} noTitle />
<Box sx={{ padding: "10px 26px" }} withBorders>
<h4 style={{ margin: 0, marginBottom: 10, fontSize: 14 }}>
Policies
</h4>
<Box
sx={{
minHeight: 265,
maxHeight: "calc(100vh - 740px)",
}}
>
<PolicySelectors selectedPolicy={selectedPolicies} noTitle />
</Box>
</Box>
</Box>
</Grid>
<Grid item sm={12} md={6} lg={7} sx={{ padding: 10, paddingTop: 0 }}>
<Grid
item
sm={12}
md={6}
lg={7}
sx={{
padding: 10,
paddingTop: 0,
display: "flex",
flexDirection: "column",
}}
>
{loading ? (
<Box sx={{ textAlign: "center" }}>
<Loader />
@@ -198,8 +238,6 @@ const LDAPEntitiesQuery = () => {
) : (
<Fragment>
<SectionTitle
separator
sx={{ marginBottom: 15 }}
actions={
<Box
sx={{
@@ -211,8 +249,13 @@ const LDAPEntitiesQuery = () => {
>
{results?.timestamp ? (
<Fragment>
<UptimeIcon
style={{ width: 18, height: 18, marginRight: 5 }}
<TimeIcon
style={{
width: 14,
height: 14,
marginRight: 5,
fill: "#BEBFBF",
}}
/>
{DateTime.fromISO(results.timestamp).toFormat(
"D HH:mm:ss"
@@ -224,114 +267,30 @@ const LDAPEntitiesQuery = () => {
</Box>
}
>
Results
Query Results
</SectionTitle>
{results ? (
<Box>
<Box
sx={{
backgroundColor: "#FBFAFA",
padding: "8px 22px",
flexGrow: 1,
overflowY: "auto",
}}
>
{!results.groups && !results.users && !results.policies && (
<Box sx={{ textAlign: "center" }}>
<h4>No Results Available</h4>
</Box>
)}
{!!results.groups && (
<Box className={"resultElement"}>
<SectionTitle separator sx={{ fontSize: 12 }}>
Group Mappings
</SectionTitle>
<Box sx={{ padding: "0 15px" }}>
{results.groups.map((groupData, index) => {
return (
<Fragment key={`policy-res-${index}`}>
<h4>{groupData.group}</h4>
{groupData.policies && (
<Fragment>
Policies:
<ul>
{groupData.policies.map(
(policy, index2) => (
<li key={`policy-group-${index2}`}>
{policy}
</li>
)
)}
</ul>
</Fragment>
)}
</Fragment>
);
})}
</Box>
</Box>
<LDAPResultsBlock results={results} entityName={"Group"} />
)}
{!!results.users && (
<Box className={"resultElement"}>
<SectionTitle separator sx={{ fontSize: 12 }}>
User Mappings
</SectionTitle>
<Box sx={{ padding: "0 15px" }}>
{results.users.map((groupData, index) => {
return (
<Fragment key={`users-res-${index}`}>
<h4>{groupData.user}</h4>
{groupData.policies && (
<Fragment>
Policies:
<ul>
{groupData.policies.map(
(policy, index2) => (
<li key={`policy-users-${index2}`}>
{policy}
</li>
)
)}
</ul>
</Fragment>
)}
</Fragment>
);
})}
</Box>
</Box>
<LDAPResultsBlock results={results} entityName={"User"} />
)}
{!!results.policies && (
<Box className={"resultElement"}>
<SectionTitle separator sx={{ fontSize: 12 }}>
Policy Mappings
</SectionTitle>
<Box sx={{ padding: "0 15px" }}>
{results.policies.map((groupData, index) => {
return (
<Fragment key={`policy-map-${index}`}>
<h4>{groupData.policy}</h4>
{groupData.groups && (
<Fragment>
Groups:
<ul>
{groupData.groups.map((group, index2) => (
<li key={`policy-map-group-${index}`}>
{group}
</li>
))}
</ul>
</Fragment>
)}
{groupData.users && (
<Fragment>
Users:
<ul>
{groupData.users.map((user, index3) => (
<li key={`policy-map-user-${index}`}>
{user}
</li>
))}
</ul>
</Fragment>
)}
</Fragment>
);
})}
</Box>
</Box>
<LDAPResultsBlock results={results} entityName={"Policy"} />
)}
</Box>
) : (
@@ -342,12 +301,22 @@ const LDAPEntitiesQuery = () => {
</Grid>
</Grid>
<Grid container>
<Grid item xs={12} sx={{ display: "flex", justifyContent: "flex-end" }}>
<Grid
item
xs={12}
sx={{
display: "flex",
justifyContent: "flex-start",
marginTop: 45,
padding: "0 20px",
}}
>
<Button
id={"search-entity"}
type={"button"}
variant={"callAction"}
onClick={searchEntities}
icon={<SearchIcon />}
>
Search
</Button>

View File

@@ -0,0 +1,165 @@
// This file is part of MinIO Console Server
// Copyright (c) 2023 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 { Box, CollapseCaret, GroupsMenuIcon, SectionTitle } from "mds";
import { LDAPEntitiesResponse } from "./types";
interface IResultBlock {
entityName: "Group" | "User" | "Policy";
results: LDAPEntitiesResponse;
}
interface IEntityResultName {
name: string;
}
interface IEntityResultItem {
blockName: "Policies" | "Groups" | "Users";
results: string[];
}
const EntityResultTitle = ({ name }: IEntityResultName) => {
return (
<h4>
<CollapseCaret style={{ transform: "rotateZ(90deg)" }} />
{name}
</h4>
);
};
const EntityResultItems = ({ blockName, results }: IEntityResultItem) => {
return (
<Fragment>
<strong>{blockName}:</strong>
<ul>
{results.map((res, index) => (
<li key={`policy-${blockName}-${index}`}>{res}</li>
))}
</ul>
</Fragment>
);
};
const LDAPResultsBlock = ({ entityName, results }: IResultBlock) => {
let entityLength = 0;
switch (entityName) {
case "Group":
entityLength = results.groups?.length || 0;
break;
case "Policy":
entityLength = results.policies?.length || 0;
break;
case "User":
entityLength = results.users?.length || 0;
break;
}
return (
<Box
className={"resultElement"}
sx={{
marginTop: 50,
"&:first-of-type": {
marginTop: 0,
},
}}
>
<SectionTitle
separator
sx={{ fontSize: 12 }}
icon={<GroupsMenuIcon style={{ width: 17, height: 17 }} />}
actions={
<Box sx={{ fontSize: 14 }}>
<strong>{entityLength}</strong> Entit
{entityLength === 1 ? "y" : "ies"} Found
</Box>
}
>
{entityName} Mappings
</SectionTitle>
<Box
className={"resultsList"}
sx={{
h4: {
borderBottom: "#e2e2e2 1px solid",
padding: "12px 0",
margin: 0,
marginBottom: 15,
display: "flex",
alignItems: "center",
"& svg": {
marginRight: 10,
fill: "#3C77A7",
},
},
}}
>
{entityName === "Group" &&
results.groups?.map((groupData, index) => {
return (
<Fragment key={`policy-res-${index}`}>
<EntityResultTitle name={groupData.group} />
{groupData.policies && (
<EntityResultItems
blockName={"Policies"}
results={groupData.policies}
/>
)}
</Fragment>
);
})}
{entityName === "User" &&
results.users?.map((groupData, index) => {
return (
<Fragment key={`users-res-${index}`}>
<EntityResultTitle name={groupData.user} />
{groupData.policies && (
<EntityResultItems
blockName={"Policies"}
results={groupData.policies}
/>
)}
</Fragment>
);
})}
{entityName === "Policy" &&
results.policies?.map((groupData, index) => {
return (
<Fragment key={`policy-map-${index}`}>
<EntityResultTitle name={groupData.policy} />
{groupData.groups && (
<EntityResultItems
blockName={"Groups"}
results={groupData.groups}
/>
)}
{groupData.users && (
<EntityResultItems
blockName={"Users"}
results={groupData.users}
/>
)}
</Fragment>
);
})}
</Box>
</Box>
);
};
export default LDAPResultsBlock;

View File

@@ -36,6 +36,7 @@ const initialState: ObjectBrowserState = {
objectDetailsOpen: false,
loadingVersions: true,
loadingObjectInfo: true,
connectionError: false,
rewind: {
...defaultRewind,
},
@@ -365,6 +366,14 @@ export const objectBrowserSlice = createSlice({
setAnonymousAccessOpen: (state, action: PayloadAction<boolean>) => {
state.anonymousAccessOpen = action.payload;
},
errorInConnection: (state, action: PayloadAction<boolean>) => {
state.connectionError = action.payload;
if (action.payload) {
state.loadingObjects = false;
state.loadingObjectInfo = false;
state.objectDetailsOpen = false;
}
},
},
});
export const {
@@ -412,6 +421,7 @@ export const {
setSelectedBucket,
setLongFileOpen,
setAnonymousAccessOpen,
errorInConnection,
} = objectBrowserSlice.actions;
export default objectBrowserSlice.reducer;

View File

@@ -96,6 +96,7 @@ export interface ObjectBrowserState {
retentionConfig: IRetentionConfig | null;
longFileOpen: boolean;
anonymousAccessOpen: boolean;
connectionError: boolean;
}
export interface ObjectManager {

View File

@@ -24,7 +24,10 @@ import RegisterHelpBox from "./RegisterHelpBox";
import { SubnetLoginRequest, SubnetLoginResponse } from "../License/types";
import api from "../../../common/api";
import { useAppDispatch } from "../../../store";
import { setErrorSnackMessage } from "../../../systemSlice";
import {
setErrorSnackMessage,
setServerNeedsRestart,
} from "../../../systemSlice";
import { ErrorResponseHandler } from "../../../common/types";
import { spacingUtils } from "../Common/FormComponents/common/styleLibrary";
import { Theme } from "@mui/material/styles";
@@ -66,6 +69,7 @@ const ApiKeyRegister = ({ classes, registerEndpoint }: IApiKeyRegister) => {
.then((resp: SubnetLoginResponse) => {
setLoading(false);
if (resp && resp.registered) {
dispatch(setServerNeedsRestart(true));
navigate(IAM_PAGES.LICENSE);
}
})

View File

@@ -26,7 +26,10 @@ import { useSelector } from "react-redux";
import CommentBoxWrapper from "../Common/FormComponents/CommentBoxWrapper/CommentBoxWrapper";
import useApi from "../Common/Hooks/useApi";
import { fetchLicenseInfo } from "./registerThunks";
import { setErrorSnackMessage } from "../../../systemSlice";
import {
setErrorSnackMessage,
setServerNeedsRestart,
} from "../../../systemSlice";
const OfflineRegistration = () => {
const dispatch = useAppDispatch();
@@ -47,6 +50,7 @@ const OfflineRegistration = () => {
const [isSaving, invokeApplyLicenseApi] = useApi(
() => {
dispatch(fetchLicenseInfo());
dispatch(setServerNeedsRestart(true));
},
(err) => {
dispatch(setErrorSnackMessage(err));

View File

@@ -1,124 +0,0 @@
// This file is part of MinIO Console Server
// Copyright (c) 2022 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public APIKey as published by
// the Free Software Foundation, either version 3 of the APIKey, 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 APIKey for more details.
//
// You should have received a copy of the GNU Affero General Public APIKey
// along with this program. If not, see <http://www.gnu.org/APIKeys/>.
import React, { Fragment, useCallback, useEffect, useState } from "react";
import { Theme } from "@mui/material/styles";
import createStyles from "@mui/styles/createStyles";
import {
actionsTray,
containerForHeader,
searchField,
spacingUtils,
} from "../Common/FormComponents/common/styleLibrary";
import withStyles from "@mui/styles/withStyles";
import { Box } from "@mui/material";
import PageLayout from "../Common/Layout/PageLayout";
import api from "../../../common/api";
import { ErrorResponseHandler } from "../../../common/types";
import Tabs from "@mui/material/Tabs";
import Tab from "@mui/material/Tab";
import { TabPanel } from "../../shared/tabs";
import { ClusterRegistered } from "./utils";
import ApiKeyRegister from "./ApiKeyRegister";
import PageHeaderWrapper from "../Common/PageHeaderWrapper/PageHeaderWrapper";
interface IRegister {
classes: any;
}
const styles = (theme: Theme) =>
createStyles({
...actionsTray,
...searchField,
...spacingUtils,
...containerForHeader,
});
const RegisterOperator = ({ classes }: IRegister) => {
const [apiKeyRegistered, setAPIKeyRegistered] = useState<boolean>(false);
const [curTab, setCurTab] = useState<number>(0);
const fetchAPIKeyInfo = useCallback(() => {
api
.invoke("GET", `/api/v1/subnet/apikey/info`)
.then((res: any) => {
setAPIKeyRegistered(true);
})
.catch((err: ErrorResponseHandler) => {
setAPIKeyRegistered(false);
});
}, []);
useEffect(() => {
fetchAPIKeyInfo();
}, [fetchAPIKeyInfo]);
const apiKeyRegistration = (
<Fragment>
<Box
sx={{
border: "1px solid #eaeaea",
borderRadius: "2px",
display: "flex",
flexFlow: "column",
padding: "43px",
}}
>
{apiKeyRegistered ? (
<ClusterRegistered email={"Operator"} />
) : (
<ApiKeyRegister registerEndpoint={"/api/v1/subnet/apikey/register"} />
)}
</Box>
</Fragment>
);
return (
<Fragment>
<PageHeaderWrapper
label="Register to MinIO Subscription Network"
actions={<React.Fragment />}
/>
<PageLayout>
<Tabs
value={curTab}
onChange={(e: React.ChangeEvent<{}>, newValue: number) => {
setCurTab(newValue);
}}
indicatorColor="primary"
textColor="primary"
aria-label="cluster-tabs"
variant="scrollable"
scrollButtons="auto"
>
<Tab
label="API Key"
id="simple-tab-0"
aria-controls="simple-tabpanel-1"
/>
</Tabs>
<TabPanel index={0} value={curTab}>
{apiKeyRegistration}
</TabPanel>
</PageLayout>
</Fragment>
);
};
export default withStyles(styles)(RegisterOperator);

View File

@@ -35,7 +35,10 @@ import {
SubnetRegisterRequest,
} from "../License/types";
import { ErrorResponseHandler } from "../../../common/types";
import { setErrorSnackMessage } from "../../../systemSlice";
import {
setErrorSnackMessage,
setServerNeedsRestart,
} from "../../../systemSlice";
import { createAsyncThunk } from "@reduxjs/toolkit";
import { AppState } from "../../../store";
import { hasPermission } from "../../../common/SecureComponent";
@@ -104,6 +107,7 @@ export const callRegister = createAsyncThunk(
.invoke("POST", "/api/v1/subnet/register", request)
.then(() => {
dispatch(setLoading(false));
dispatch(setServerNeedsRestart(true));
dispatch(resetRegisterForm());
dispatch(fetchLicenseInfo());
})

View File

@@ -377,22 +377,21 @@ const Login = () => {
</a>
</Fragment>
}
promoHeader={<Fragment>Multi-Cloud Object&nbsp;Store</Fragment>}
promoHeader={
<span style={{ fontSize: 28 }}>High-Performance Object Store</span>
}
promoInfo={
<Fragment>
MinIO's high-performance, Kubernetes-native object store is licensed
under GNU AGPL v3 and is available on every cloud - public, private
and edge. For more information on the terms of the license or to
learn more about commercial licensing options visit the{" "}
<a
href={"https://min.io/pricing?ref=con"}
target="_blank"
rel="noopener"
>
pricing page
<span style={{ fontSize: 14, lineHeight: 1 }}>
MinIO is a cloud-native object store built to run on any
infrastructure - public, private or edge clouds. Primary use cases
include data lakes, databases, AI/ML, SaaS applications and fast
backup & recovery. MinIO is dual licensed under GNU AGPL v3 and
commercial license. To learn more, visit{" "}
<a href={"https://min.io/?ref=con"} target="_blank" rel="noopener">
www.min.io
</a>
.
</Fragment>
</span>
}
/>
</Fragment>

View File

@@ -4877,10 +4877,10 @@ destroy@1.2.0:
resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.2.0.tgz#4803735509ad8be552934c67df614f94e66fa015"
integrity sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==
detect-gpu@^5.0.16:
version "5.0.16"
resolved "https://registry.yarnpkg.com/detect-gpu/-/detect-gpu-5.0.16.tgz#a42054724f4a75d667add68ad1073c80d29ef733"
integrity sha512-6+o6Sy+FzgQJG7Ray0fN7B4kRGGPuyaM5FHXJ4N3sLcQhsUO9+NEw9emM7vxN7DroZGG16ZydCX6kpgDmNXyKQ==
detect-gpu@^5.0.17:
version "5.0.17"
resolved "https://registry.yarnpkg.com/detect-gpu/-/detect-gpu-5.0.17.tgz#fe4bbf8fc630aa68e1e770f4e63240212bdfbd38"
integrity sha512-eAnZDpYeDASfzFYmGULBEiK0P4q1naMxDzOtMkQEBcFdesu1LjnsOraZHQ7+BGgCTOcKWvI1rxjh5FvICst0xw==
dependencies:
webgl-constants "^1.1.1"
@@ -8316,12 +8316,12 @@ mdn-data@2.0.4:
resolved "https://registry.yarnpkg.com/mdn-data/-/mdn-data-2.0.4.tgz#699b3c38ac6f1d728091a64650b65d388502fd5b"
integrity sha512-iV3XNKw06j5Q7mi6h+9vbx23Tv7JkjEVgKHW4pimwyDGWm0OIQntJJ+u1C6mg6mK1EaTv42XQ7w76yuzH7M2cA==
"mds@https://github.com/minio/mds.git#v0.3.2":
version "0.3.2"
resolved "https://github.com/minio/mds.git#85654388adaec4d2624b61bee439c7d9cd5801f7"
"mds@https://github.com/minio/mds.git#v0.3.3":
version "0.3.3"
resolved "https://github.com/minio/mds.git#309b13379f64ec66a0e1f2670e7e8611b84e3215"
dependencies:
"@types/styled-components" "^5.1.25"
detect-gpu "^5.0.16"
detect-gpu "^5.0.17"
react-virtualized "^9.22.3"
styled-components "^5.3.9"

View File

@@ -329,6 +329,25 @@ func (s consoleSTSAssumeRole) IsExpired() bool {
return s.stsAssumeRole.IsExpired()
}
func stsCredentials(minioURL, accessKey, secretKey, location string) (*credentials.Credentials, error) {
if accessKey == "" || secretKey == "" {
return nil, errors.New("credentials endpoint, access and secret key are mandatory for AssumeRoleSTS")
}
opts := credentials.STSAssumeRoleOptions{
AccessKey: accessKey,
SecretKey: secretKey,
Location: location,
DurationSeconds: int(xjwt.GetConsoleSTSDuration().Seconds()),
}
stsAssumeRole := &credentials.STSAssumeRole{
Client: GetConsoleHTTPClient(minioURL),
STSEndpoint: minioURL,
Options: opts,
}
consoleSTSWrapper := consoleSTSAssumeRole{stsAssumeRole: stsAssumeRole}
return credentials.New(consoleSTSWrapper), nil
}
func NewConsoleCredentials(accessKey, secretKey, location string) (*credentials.Credentials, error) {
minioURL := getMinIOServer()
@@ -341,27 +360,37 @@ func NewConsoleCredentials(accessKey, secretKey, location string) (*credentials.
if err != nil {
return nil, err
}
// We verify if LDAP credentials are correct and no error is returned
_, err = creds.Get()
if err != nil && strings.Contains(strings.ToLower(err.Error()), "not found") {
// We try to use STS Credentials in case LDAP credentials are incorrect.
stsCreds, errSTS := stsCredentials(minioURL, accessKey, secretKey, location)
// If there is an error with STS too, then we return the original LDAP error
if errSTS != nil {
LogError("error in STS credentials for LDAP case: %v ", errSTS)
// We return LDAP result
return creds, nil
}
_, err := stsCreds.Get()
// There is an error with STS credentials, We return the result of LDAP as STS is not a priority in this case.
if err != nil {
return creds, nil
}
return stsCreds, nil
}
return creds, nil
}
// default authentication for Console is via STS (Security Token Service) against MinIO
default:
{
if accessKey == "" || secretKey == "" {
return nil, errors.New("credentials endpoint, access and secret key are mandatory for AssumeRoleSTS")
}
opts := credentials.STSAssumeRoleOptions{
AccessKey: accessKey,
SecretKey: secretKey,
Location: location,
DurationSeconds: int(xjwt.GetConsoleSTSDuration().Seconds()),
}
stsAssumeRole := &credentials.STSAssumeRole{
Client: GetConsoleHTTPClient(minioURL),
STSEndpoint: minioURL,
Options: opts,
}
consoleSTSWrapper := consoleSTSAssumeRole{stsAssumeRole: stsAssumeRole}
return credentials.New(consoleSTSWrapper), nil
return stsCredentials(minioURL, accessKey, secretKey, location)
}
}
}