Compare commits

...

6 Commits

Author SHA1 Message Date
Cesar N
2d94018e3c Release v0.36.0 (#2988) 2023-08-10 11:59:15 -07:00
Alex
72bb9d0ca1 Update Settings Page components (#2986)
Signed-off-by: Benjamin Perez <benjamin@bexsoft.net>
2023-08-09 18:30:42 -06:00
Javier Adriel
93bd0d65e2 Show LDAP configuration (#2985) 2023-08-09 12:06:17 -06:00
Prakash Senthil Vel
495b0f0068 truncate prefixes above the selected level to make the download layout flat (#2987) 2023-08-09 10:29:41 -07:00
Cesar Celis Hernandez
3275b6a6d8 Removing unneeded password (#2983) 2023-08-04 13:45:41 -06:00
Harshavardhana
fed5aa1599 fix: dateTime usage only available in go1.20 (#2982)
instead use a simpler normalizer for timestamp
to be user friendly without spaces.

For example

```
selected_files_20091110T230000Z.zip
```
2023-08-03 20:21:44 -07:00
39 changed files with 281 additions and 451 deletions

View File

@@ -26,7 +26,7 @@ jobs:
runs-on: ${{ matrix.os }}
strategy:
matrix:
go-version: [ 1.20.x ]
go-version: [ 1.19.x, 1.20.x ]
os: [ ubuntu-latest ]
steps:
- name: Check out code

View File

@@ -2,8 +2,40 @@
# Changelog
## Release v0.34.0
## Release v0.36.0
Features:
- Updated Settings page components
Bug Fix:
- Show LDAP Enabled value LDAP configuration
- Download multiple objects in same path as they were selected
## Release v0.35.1
Bug Fix:
- Change timestamp format for zip creation
## Release v0.35.0
Features:
- Add Exclude Folders and Exclude Prefixes during bucket creation
- Download multiple selected objects as zip and ignore deleted objects
- Updated Call Home, Inspet, Profile and Health components
Bug Fix:
- Remove extra white spaces for configuration strings
- Allow Create New Path in bucket view when having right permissions
## Release v0.34.0
Features:
- Updated Buckets components
Bug Fix:
@@ -12,9 +44,13 @@ Bug Fix:
- Updated Download Handler
- Fixes issue with rewind
- Avoid 1 hour expiration for IDP credentials
---
## Release v0.33.0
Features:
- Updated OpenID, LDAP components
Bug Fix:
@@ -22,21 +58,27 @@ Bug Fix:
- Fixed security issues
- Fixed navigation issues in Object Browser
- Fixed Dashboard metrics
---
## Release v0.32.0
Features:
- Updated Users and Groups components
- Added placeholder image for Help Menu
Bug Fix:
- Fixed memory leak in WebSocket API for Object Browser
- Fixed memory leak in WebSocket API for Object Browser
---
## Release v0.31.0
*Breaking Changes:*
**Breaking Changes:**
- *Removed support for Standalone Deployments*
- **Removed support for Standalone Deployments**
Features:
@@ -48,7 +90,9 @@ Bug Fix:
- Fixed Download folders issue in Object Browser
- Added missing Notification Events (ILM & REPLICA) in Events Notification Page
- Fixed Security Vulnerability for `semver` dependency
---
## Release v0.30.0
Features:

View File

@@ -1,7 +1,7 @@
{
"files": {
"main.css": "./static/css/main.02c1b6fd.css",
"main.js": "./static/js/main.72f8505e.js",
"main.js": "./static/js/main.e7dbe970.js",
"static/js/1260.a025e586.chunk.js": "./static/js/1260.a025e586.chunk.js",
"static/js/6914.9ab32297.chunk.js": "./static/js/6914.9ab32297.chunk.js",
"static/js/9121.0c08c03c.chunk.js": "./static/js/9121.0c08c03c.chunk.js",
@@ -9,7 +9,7 @@
"static/js/6272.3c9cfd32.chunk.js": "./static/js/6272.3c9cfd32.chunk.js",
"static/js/5088.8ed4fd39.chunk.js": "./static/js/5088.8ed4fd39.chunk.js",
"static/js/6122.8195a695.chunk.js": "./static/js/6122.8195a695.chunk.js",
"static/js/808.0827cf07.chunk.js": "./static/js/808.0827cf07.chunk.js",
"static/js/808.82a99299.chunk.js": "./static/js/808.82a99299.chunk.js",
"static/js/8060.060ad94e.chunk.js": "./static/js/8060.060ad94e.chunk.js",
"static/js/9635.de2db92c.chunk.js": "./static/js/9635.de2db92c.chunk.js",
"static/js/2338.f2a282fa.chunk.js": "./static/js/2338.f2a282fa.chunk.js",
@@ -30,10 +30,10 @@
"static/js/191.fd21cdcc.chunk.js": "./static/js/191.fd21cdcc.chunk.js",
"static/js/1329.79996c21.chunk.js": "./static/js/1329.79996c21.chunk.js",
"static/js/7614.ec6383d7.chunk.js": "./static/js/7614.ec6383d7.chunk.js",
"static/js/1690.fa631de9.chunk.js": "./static/js/1690.fa631de9.chunk.js",
"static/js/1690.a2a38bf8.chunk.js": "./static/js/1690.a2a38bf8.chunk.js",
"static/js/6491.de57d3e6.chunk.js": "./static/js/6491.de57d3e6.chunk.js",
"static/js/4902.ded1f1f5.chunk.js": "./static/js/4902.ded1f1f5.chunk.js",
"static/js/841.31434f9c.chunk.js": "./static/js/841.31434f9c.chunk.js",
"static/js/1432.e37a4cf1.chunk.js": "./static/js/1432.e37a4cf1.chunk.js",
"static/js/3171.5a1ef12c.chunk.js": "./static/js/3171.5a1ef12c.chunk.js",
"static/js/8360.965868c4.chunk.js": "./static/js/8360.965868c4.chunk.js",
"static/js/6577.d4559543.chunk.js": "./static/js/6577.d4559543.chunk.js",
@@ -43,7 +43,7 @@
"static/js/977.7dc10141.chunk.js": "./static/js/977.7dc10141.chunk.js",
"static/js/6686.0c756aaf.chunk.js": "./static/js/6686.0c756aaf.chunk.js",
"static/js/6158.62f2536c.chunk.js": "./static/js/6158.62f2536c.chunk.js",
"static/js/738.eed2fa45.chunk.js": "./static/js/738.eed2fa45.chunk.js",
"static/js/4770.08d9efec.chunk.js": "./static/js/4770.08d9efec.chunk.js",
"static/js/8305.b35690ca.chunk.js": "./static/js/8305.b35690ca.chunk.js",
"static/js/4414.930d89fc.chunk.js": "./static/js/4414.930d89fc.chunk.js",
"static/js/8833.27a34b4e.chunk.js": "./static/js/8833.27a34b4e.chunk.js",
@@ -105,7 +105,7 @@
"static/js/4882.9c5a4b90.chunk.js": "./static/js/4882.9c5a4b90.chunk.js",
"static/js/134.117c7fbe.chunk.js": "./static/js/134.117c7fbe.chunk.js",
"static/js/3801.e125737d.chunk.js": "./static/js/3801.e125737d.chunk.js",
"static/js/5515.a73ad9ea.chunk.js": "./static/js/5515.a73ad9ea.chunk.js",
"static/js/2098.0b7ec983.chunk.js": "./static/js/2098.0b7ec983.chunk.js",
"static/js/9062.9dba9f0e.chunk.js": "./static/js/9062.9dba9f0e.chunk.js",
"static/js/6624.237b6430.chunk.js": "./static/js/6624.237b6430.chunk.js",
"static/js/8098.86babd6f.chunk.js": "./static/js/8098.86babd6f.chunk.js",
@@ -142,7 +142,7 @@
"static/media/placeholderimage.png": "./static/media/placeholderimage.077ea48bd1ef1f4a883f.png",
"index.html": "./index.html",
"main.02c1b6fd.css.map": "./static/css/main.02c1b6fd.css.map",
"main.72f8505e.js.map": "./static/js/main.72f8505e.js.map",
"main.e7dbe970.js.map": "./static/js/main.e7dbe970.js.map",
"1260.a025e586.chunk.js.map": "./static/js/1260.a025e586.chunk.js.map",
"6914.9ab32297.chunk.js.map": "./static/js/6914.9ab32297.chunk.js.map",
"9121.0c08c03c.chunk.js.map": "./static/js/9121.0c08c03c.chunk.js.map",
@@ -150,7 +150,7 @@
"6272.3c9cfd32.chunk.js.map": "./static/js/6272.3c9cfd32.chunk.js.map",
"5088.8ed4fd39.chunk.js.map": "./static/js/5088.8ed4fd39.chunk.js.map",
"6122.8195a695.chunk.js.map": "./static/js/6122.8195a695.chunk.js.map",
"808.0827cf07.chunk.js.map": "./static/js/808.0827cf07.chunk.js.map",
"808.82a99299.chunk.js.map": "./static/js/808.82a99299.chunk.js.map",
"8060.060ad94e.chunk.js.map": "./static/js/8060.060ad94e.chunk.js.map",
"9635.de2db92c.chunk.js.map": "./static/js/9635.de2db92c.chunk.js.map",
"2338.f2a282fa.chunk.js.map": "./static/js/2338.f2a282fa.chunk.js.map",
@@ -171,10 +171,10 @@
"191.fd21cdcc.chunk.js.map": "./static/js/191.fd21cdcc.chunk.js.map",
"1329.79996c21.chunk.js.map": "./static/js/1329.79996c21.chunk.js.map",
"7614.ec6383d7.chunk.js.map": "./static/js/7614.ec6383d7.chunk.js.map",
"1690.fa631de9.chunk.js.map": "./static/js/1690.fa631de9.chunk.js.map",
"1690.a2a38bf8.chunk.js.map": "./static/js/1690.a2a38bf8.chunk.js.map",
"6491.de57d3e6.chunk.js.map": "./static/js/6491.de57d3e6.chunk.js.map",
"4902.ded1f1f5.chunk.js.map": "./static/js/4902.ded1f1f5.chunk.js.map",
"841.31434f9c.chunk.js.map": "./static/js/841.31434f9c.chunk.js.map",
"1432.e37a4cf1.chunk.js.map": "./static/js/1432.e37a4cf1.chunk.js.map",
"3171.5a1ef12c.chunk.js.map": "./static/js/3171.5a1ef12c.chunk.js.map",
"8360.965868c4.chunk.js.map": "./static/js/8360.965868c4.chunk.js.map",
"6577.d4559543.chunk.js.map": "./static/js/6577.d4559543.chunk.js.map",
@@ -184,7 +184,7 @@
"977.7dc10141.chunk.js.map": "./static/js/977.7dc10141.chunk.js.map",
"6686.0c756aaf.chunk.js.map": "./static/js/6686.0c756aaf.chunk.js.map",
"6158.62f2536c.chunk.js.map": "./static/js/6158.62f2536c.chunk.js.map",
"738.eed2fa45.chunk.js.map": "./static/js/738.eed2fa45.chunk.js.map",
"4770.08d9efec.chunk.js.map": "./static/js/4770.08d9efec.chunk.js.map",
"8305.b35690ca.chunk.js.map": "./static/js/8305.b35690ca.chunk.js.map",
"4414.930d89fc.chunk.js.map": "./static/js/4414.930d89fc.chunk.js.map",
"8833.27a34b4e.chunk.js.map": "./static/js/8833.27a34b4e.chunk.js.map",
@@ -246,7 +246,7 @@
"4882.9c5a4b90.chunk.js.map": "./static/js/4882.9c5a4b90.chunk.js.map",
"134.117c7fbe.chunk.js.map": "./static/js/134.117c7fbe.chunk.js.map",
"3801.e125737d.chunk.js.map": "./static/js/3801.e125737d.chunk.js.map",
"5515.a73ad9ea.chunk.js.map": "./static/js/5515.a73ad9ea.chunk.js.map",
"2098.0b7ec983.chunk.js.map": "./static/js/2098.0b7ec983.chunk.js.map",
"9062.9dba9f0e.chunk.js.map": "./static/js/9062.9dba9f0e.chunk.js.map",
"6624.237b6430.chunk.js.map": "./static/js/6624.237b6430.chunk.js.map",
"8098.86babd6f.chunk.js.map": "./static/js/8098.86babd6f.chunk.js.map",
@@ -263,6 +263,6 @@
},
"entrypoints": [
"static/css/main.02c1b6fd.css",
"static/js/main.72f8505e.js"
"static/js/main.e7dbe970.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.72f8505e.js"></script><link href="./static/css/main.02c1b6fd.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.e7dbe970.js"></script><link href="./static/css/main.02c1b6fd.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

View File

@@ -0,0 +1,2 @@
"use strict";(self.webpackChunkportal_ui=self.webpackChunkportal_ui||[]).push([[4770],{59447:function(e,n,t){var a=t(93433),r=t(29439),l=t(72791),i=t(26181),o=t.n(i),c=t(29945),u=t(80184);n.Z=function(e){var n=e.elements,t=e.name,i=e.label,s=e.tooltip,f=void 0===s?"":s,d=e.commonPlaceholder,v=void 0===d?"":d,h=e.onChange,m=e.withBorder,p=void 0!==m&&m,x=(0,l.useState)([""]),g=(0,r.Z)(x,2),j=g[0],b=g[1],y=(0,l.createRef)();(0,l.useEffect)((function(){if(1===j.length&&""===j[0]&&n&&""!==n){var e=n.split(",");e.push(""),b(e)}}),[n,j]),(0,l.useEffect)((function(){if(j.length>1){var e=y.current;e&&e.scrollIntoView(!1)}}),[j,y]);var w=(0,l.useCallback)((function(e){h(e)}),[h]),k=(0,l.useRef)(!0);(0,l.useEffect)((function(){if(k.current)k.current=!1;else{var e=j.filter((function(e){return""!==e.trim()})).join(",");w(e)}}),[j]);var C=function(e){e.persist();var n=(0,a.Z)(j),t=o()(e.target,"dataset.index","0");n[parseInt(t)]=e.target.value,b(n)},Z=j.map((function(e,n){return(0,u.jsx)(c.Wzg,{id:"".concat(t,"-").concat(n.toString()),label:"",name:"".concat(t,"-").concat(n.toString()),value:j[n],onChange:C,index:n,placeholder:v,overlayIcon:n===j.length-1?(0,u.jsx)(c.dtP,{}):null,overlayAction:function(){!function(e){if(""!==e[e.length-1].trim()){var n=(0,a.Z)(e);n.push(""),b(n)}}(j)}},"csv-multi-".concat(t,"-").concat(n.toString()))}));return(0,u.jsx)(l.Fragment,{children:(0,u.jsxs)(c.xuv,{sx:{display:"flex"},className:"inputItem",children:[(0,u.jsxs)(c.AZs,{sx:{alignItems:"flex-start"},children:[(0,u.jsx)("span",{children:i}),""!==f&&(0,u.jsx)(c.xuv,{sx:{marginLeft:5,display:"flex",alignItems:"center","& .min-icon":{width:13}},children:(0,u.jsx)(c.ua7,{tooltip:f,placement:"top",children:(0,u.jsx)(c.xuv,{className:f,children:(0,u.jsx)(c.byK,{})})})})]}),(0,u.jsxs)(c.xuv,{withBorders:p,sx:{width:"100%",overflowY:"auto",height:150,position:"relative"},children:[Z,(0,u.jsx)("div",{ref:y})]})]})})}},94770:function(e,n,t){t.r(n),t.d(n,{valueDef:function(){return u}});var a=t(93433),r=t(29439),l=t(72791),i=t(29945),o=t(59447),c=t(80184),u=function(e,n,t){var a="on|off"===n?"off":"";if(t.length>0){var r=t.find((function(n){return n.key===e}));r&&(a=r.value||"")}return a};n.default=function(e){var n=e.onChange,t=e.fields,s=e.defaultVals,f=e.overrideEnv,d=(0,l.useState)([]),v=(0,r.Z)(d,2),h=v[0],m=v[1],p=t||[],x=s||[];(0,l.useEffect)((function(){var e=t.map((function(e){return{key:e.name,value:u(e.name,e.type,x)}}));m(e)}),[t,s]),(0,l.useEffect)((function(){n(h)}),[h]);var g=function(e,n,t){var r=(0,a.Z)(h);n=n.trim(),r[t]={key:e,value:n},m(r)},j=function(e,n){var t=h[n];if(t){var a=null===f||void 0===f?void 0:f["".concat(t.key)];if(a)return(0,c.jsx)(i.bSr,{label:e.label,actionButton:(0,c.jsx)(i.rjZ,{item:!0,sx:{display:"flex",justifyContent:"flex-end",paddingRight:"10px"},children:(0,c.jsx)(i.ua7,{tooltip:"This value is set from the ".concat(a.overrideEnv," environment variable"),placement:"left",children:(0,c.jsx)(i.zD0,{style:{width:20}})})}),sx:{width:"100%"},children:a.value})}switch(e.type){case"on|off":var r=t?t.value:"off";return(0,c.jsx)(i.rsf,{onChange:function(t){var a=t.target.checked?"on":"off";g(e.name,a,n)},id:e.name,name:e.name,label:e.label,value:"switch_on",tooltip:e.tooltip,checked:"on"===r});case"csv":return(0,c.jsx)(o.Z,{elements:t?t.value:"",label:e.label,name:e.name,onChange:function(t){var a="";a=Array.isArray(t)?t.join(","):t,g(e.name,a,n)},tooltip:e.tooltip,commonPlaceholder:e.placeholder,withBorder:!0});case"comment":return(0,c.jsx)(i.q5m,{id:e.name,name:e.name,label:e.label,tooltip:e.tooltip,value:t?t.value:"",onChange:function(t){return g(e.name,t.target.value,n)},placeholder:e.placeholder});default:return(0,c.jsx)(i.Wzg,{id:e.name,name:e.name,label:e.label,tooltip:e.tooltip,value:t?t.value:"",onChange:function(t){return g(e.name,t.target.value,n)},placeholder:e.placeholder})}};return(0,c.jsx)(i.ltY,{withBorders:!1,containerPadding:!1,children:p.map((function(e,n){return(0,c.jsx)(l.Fragment,{children:j(e,n)},e.name)}))})}}}]);
//# sourceMappingURL=4770.08d9efec.chunk.js.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

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

@@ -1,181 +0,0 @@
import React, { useEffect, useState } from "react";
import { Box, Tab, TabProps } from "@mui/material";
import { TabContext, TabList, TabPanel } from "@mui/lab";
import withStyles from "@mui/styles/withStyles";
import { Theme, useTheme } from "@mui/material/styles";
import createStyles from "@mui/styles/createStyles";
import useMediaQuery from "@mui/material/useMediaQuery";
import { useLocation } from "react-router-dom";
export type TabItemProps = {
tabConfig: TabProps | any;
content?: JSX.Element | JSX.Element[];
};
type VerticalTabsProps = {
classes: any;
children: TabItemProps[];
selectedTab?: string;
routes?: any;
isRouteTabs?: boolean;
};
const styles = (theme: Theme) =>
createStyles({
tabsContainer: {
display: "flex",
height: "100%",
width: "100%",
},
tabsHeaderContainer: {
width: "300px",
background: "#F8F8F8",
borderRight: "1px solid #EAEAEA",
"& .MuiTabs-root": {
"& .MuiTabs-indicator": {
display: "none",
},
"& .MuiTab-root": {
display: "flex",
flexFlow: "row",
alignItems: "center",
justifyContent: "flex-start",
borderBottom: "1px solid #EAEAEA",
"& .MuiSvgIcon-root": {
marginRight: 8,
marginBottom: 0,
},
"&.Mui-selected": {
background: "#E5E5E5",
fontWeight: 600,
},
},
"&. MuiTabs-scroller": {
display: "none",
},
},
},
tabContentContainer: {
width: "100%",
"& .MuiTabPanel-root": {
height: "100%",
},
},
tabPanel: {
height: "100%",
},
/*Below md breakpoint make it horizontal and style it for scrolling tabs*/
"@media (max-width: 900px)": {
tabsContainer: {
flexFlow: "column",
flexDirection: "column",
},
tabsHeaderContainer: {
width: "100%",
borderBottom: " 1px solid #EAEAEA",
"& .MuiTabs-root .MuiTabs-scroller .MuiButtonBase-root": {
borderBottom: " 0px",
},
},
},
});
const tabStripStyle = {
minHeight: 60,
};
const VerticalTabs = ({
children,
classes,
selectedTab = "0",
routes,
isRouteTabs,
}: VerticalTabsProps) => {
const theme = useTheme();
const { pathname = "" } = useLocation();
const isSmallScreen = useMediaQuery(theme.breakpoints.down("md"));
const [value, setValue] = useState(selectedTab);
const headerList: TabProps[] = [];
const contentList: React.ReactNode[] = [];
useEffect(() => {
if (isRouteTabs) {
const tabConfigElement = children.find(
(item) => item.tabConfig.to === pathname,
);
if (tabConfigElement) {
setValue(tabConfigElement.tabConfig.value);
}
}
}, [isRouteTabs, children, pathname]);
if (!children) return null;
children.forEach((child) => {
headerList.push(child.tabConfig);
contentList.push(child.content);
});
const handleChange = (event: React.SyntheticEvent, newValue: string) => {
setValue(newValue);
};
return (
<TabContext value={`${value}`}>
<Box className={classes.tabsContainer}>
<Box className={classes.tabsHeaderContainer}>
<TabList
onChange={handleChange}
orientation={isSmallScreen ? "horizontal" : "vertical"}
variant={isSmallScreen ? "scrollable" : "standard"}
scrollButtons="auto"
className={classes.tabList}
>
{headerList.map((item, index) => {
if (item) {
return (
<Tab
className={classes.tabHeader}
key={`v-tab-${index}`}
value={`${index}`}
style={tabStripStyle}
{...item}
disableRipple
disableTouchRipple
focusRipple={true}
/>
);
}
return null;
})}
</TabList>
</Box>
<Box className={classes.tabContentContainer}>
{!isRouteTabs
? contentList.map((item, index) => {
return (
<TabPanel
classes={{ ...classes.tabPanel }}
key={`v-tab-p-${index}`}
value={`${index}`}
>
{item ? item : null}
</TabPanel>
);
})
: null}
{isRouteTabs ? (
<div className={classes.tabPanel}>{routes}</div>
) : null}
</Box>
</Box>
</TabContext>
);
};
export default withStyles(styles)(VerticalTabs);

View File

@@ -15,27 +15,31 @@
// along with this program. If not, see <http://www.gnu.org/licenses/>.
import React, { Fragment, useCallback, useEffect, useState } from "react";
import { Theme } from "@mui/material/styles";
import createStyles from "@mui/styles/createStyles";
import withStyles from "@mui/styles/withStyles";
import Grid from "@mui/material/Grid";
import {
Box,
Grid,
HelpBox,
PageLayout,
ScreenTitle,
SettingsIcon,
Tabs,
} from "mds";
import { configurationElements } from "../utils";
import {
actionsTray,
containerForHeader,
searchField,
} from "../../Common/FormComponents/common/styleLibrary";
import { HelpBox, PageLayout, SettingsIcon } from "mds";
import { Link, Navigate, Route, Routes, useLocation } from "react-router-dom";
import VerticalTabs from "../../Common/VerticalTabs/VerticalTabs";
import ScreenTitle from "../../Common/ScreenTitle/ScreenTitle";
Navigate,
Route,
Routes,
useLocation,
useNavigate,
} from "react-router-dom";
import ConfigurationForm from "./ConfigurationForm";
import { IAM_PAGES } from "../../../../common/SecureComponent/permissions";
import PageHeaderWrapper from "../../Common/PageHeaderWrapper/PageHeaderWrapper";
import ExportConfigButton from "./ExportConfigButton";
import ImportConfigButton from "./ImportConfigButton";
import { Box } from "@mui/material";
import HelpMenu from "../../HelpMenu";
import { setErrorSnackMessage, setHelpName } from "../../../../systemSlice";
import { useAppDispatch } from "../../../../store";
@@ -43,26 +47,6 @@ import { api } from "../../../../api";
import { IElement } from "../types";
import { errorToHandler } from "../../../../api/errors";
interface IConfigurationOptions {
classes: any;
}
const styles = (theme: Theme) =>
createStyles({
settingsOptionsContainer: {
display: "flex" as const,
flexDirection: "row" as const,
justifyContent: "flex-start" as const,
flexWrap: "wrap" as const,
border: "#E5E5E5 1px solid",
borderRadius: 2,
backgroundColor: "#fff",
},
...searchField,
...actionsTray,
...containerForHeader,
});
const getRoutePath = (path: string) => {
return `${IAM_PAGES.SETTINGS}/${path}`;
};
@@ -71,9 +55,10 @@ const getRoutePath = (path: string) => {
const NON_SUB_SYS_CONFIG_ITEMS = ["region"];
const IGNORED_CONFIG_SUB_SYS = ["cache"]; // cache config is not supported.
const ConfigurationOptions = ({ classes }: IConfigurationOptions) => {
const ConfigurationOptions = () => {
const { pathname = "" } = useLocation();
const dispatch = useAppDispatch();
const navigate = useNavigate();
const [configSubSysList, setConfigSubSysList] = useState<string[]>([]);
const fetchConfigSubSysList = useCallback(async () => {
@@ -99,8 +84,6 @@ const ConfigurationOptions = ({ classes }: IConfigurationOptions) => {
});
}, [dispatch]);
let selConfigTab = pathname.substring(pathname.lastIndexOf("/") + 1);
selConfigTab = selConfigTab === "settings" ? "region" : selConfigTab;
useEffect(() => {
fetchConfigSubSysList();
dispatch(setHelpName("settings_Region"));
@@ -121,59 +104,57 @@ const ConfigurationOptions = ({ classes }: IConfigurationOptions) => {
<Fragment>
<PageHeaderWrapper label={"Settings"} actions={<HelpMenu />} />
<PageLayout>
<Grid item xs={12}>
<div
id="settings-container"
className={classes.settingsOptionsContainer}
>
<ScreenTitle
icon={<SettingsIcon />}
title={"MinIO Configuration:"}
actions={
<Box
sx={{
display: "flex",
gap: 2,
}}
>
<ImportConfigButton />
<ExportConfigButton />
</Box>
}
/>
<VerticalTabs
selectedTab={selConfigTab}
isRouteTabs
routes={
<Routes>
{availableConfigSubSys.map((element) => (
<Route
key={`configItem-${element.configuration_label}`}
path={`${element.configuration_id}`}
element={<ConfigurationForm />}
/>
))}
<Grid item xs={12} id={"settings-container"}>
<ScreenTitle
icon={<SettingsIcon />}
title={"MinIO Configuration:"}
actions={
<Box
sx={{
display: "flex",
gap: 10,
}}
>
<ImportConfigButton />
<ExportConfigButton />
</Box>
}
sx={{ marginBottom: 15 }}
/>
<Tabs
currentTabOrPath={pathname}
onTabClick={(path) => {
navigate(path);
}}
useRouteTabs
options={availableConfigSubSys.map((element) => {
const { configuration_id, configuration_label, icon } = element;
return {
tabConfig: {
id: `settings-tab-${configuration_label}`,
label: configuration_label,
value: configuration_id,
icon: icon,
to: getRoutePath(configuration_id),
},
};
})}
routes={
<Routes>
{availableConfigSubSys.map((element) => (
<Route
path={"/"}
element={<Navigate to={`${IAM_PAGES.SETTINGS}/region`} />}
key={`configItem-${element.configuration_label}`}
path={`${element.configuration_id}`}
element={<ConfigurationForm />}
/>
</Routes>
}
>
{availableConfigSubSys.map((element) => {
const { configuration_id, configuration_label, icon } = element;
return {
tabConfig: {
label: configuration_label,
value: configuration_id,
icon: icon,
component: Link,
to: getRoutePath(configuration_id),
},
};
})}
</VerticalTabs>
</div>
))}
<Route
path={"/"}
element={<Navigate to={`${IAM_PAGES.SETTINGS}/region`} />}
/>
</Routes>
}
/>
</Grid>
<Grid item xs={12} sx={{ paddingTop: "15px" }}>
<HelpBox
@@ -203,4 +184,4 @@ const ConfigurationOptions = ({ classes }: IConfigurationOptions) => {
);
};
export default withStyles(styles)(ConfigurationOptions);
export default ConfigurationOptions;

View File

@@ -1,3 +1,19 @@
// 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 { Button, UploadIcon } from "mds";
import useApi from "../../Common/Hooks/useApi";

View File

@@ -1,3 +1,19 @@
// 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, useEffect, useRef, useState } from "react";
import { Button, DownloadIcon } from "mds";
import useApi from "../../Common/Hooks/useApi";

View File

@@ -14,38 +14,27 @@
// 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 { Theme } from "@mui/material/styles";
import createStyles from "@mui/styles/createStyles";
import withStyles from "@mui/styles/withStyles";
import Grid from "@mui/material/Grid";
import { IElementValue, IOverrideEnv, KVField } from "../Configurations/types";
import React, { Fragment, useEffect, useState } from "react";
import {
formFieldStyles,
modalBasic,
} from "../Common/FormComponents/common/styleLibrary";
CommentBox,
ConsoleIcon,
FormLayout,
Grid,
InputBox,
ReadBox,
Switch,
Tooltip,
} from "mds";
import { IElementValue, IOverrideEnv, KVField } from "../Configurations/types";
import CSVMultiSelector from "../Common/FormComponents/CSVMultiSelector/CSVMultiSelector";
import CommentBoxWrapper from "../Common/FormComponents/CommentBoxWrapper/CommentBoxWrapper";
import PredefinedList from "../Common/FormComponents/PredefinedList/PredefinedList";
import { ConsoleIcon, InputBox, Switch, Tooltip } from "mds";
interface IConfGenericProps {
onChange: (newValue: IElementValue[]) => void;
fields: KVField[];
defaultVals?: IElementValue[];
overrideEnv?: IOverrideEnv;
classes: any;
}
const styles = (theme: Theme) =>
createStyles({
...formFieldStyles,
formFieldRow: {
...formFieldStyles.formFieldRow,
},
...modalBasic,
});
// Function to get defined values,
//we make this because the backed sometimes don't return all the keys when there is an initial configuration
export const valueDef = (
@@ -71,7 +60,6 @@ const ConfTargetGeneric = ({
fields,
defaultVals,
overrideEnv,
classes,
}: IConfGenericProps) => {
const [valueHolder, setValueHolder] = useState<IElementValue[]>([]);
const fieldsElements = !fields ? [] : fields;
@@ -113,9 +101,8 @@ const ConfTargetGeneric = ({
if (override) {
return (
<PredefinedList
<ReadBox
label={field.label}
content={override.value}
actionButton={
<Grid
item
@@ -133,7 +120,10 @@ const ConfTargetGeneric = ({
</Tooltip>
</Grid>
}
/>
sx={{ width: "100%" }}
>
{override.value}
</ReadBox>
);
}
}
@@ -180,15 +170,13 @@ const ConfTargetGeneric = ({
);
case "comment":
return (
<CommentBoxWrapper
<CommentBox
id={field.name}
name={field.name}
label={field.label}
tooltip={field.tooltip}
value={holderItem ? holderItem.value : ""}
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
setValueElement(field.name, e.target.value, item)
}
onChange={(e) => setValueElement(field.name, e.target.value, item)}
placeholder={field.placeholder}
/>
);
@@ -210,16 +198,12 @@ const ConfTargetGeneric = ({
};
return (
<Grid container>
<Grid xs={12} item className={classes.fieldBox}>
{fieldsElements.map((field, item) => (
<Grid item xs={12} key={field.name} className={classes.formFieldRow}>
{fieldDefinition(field, item)}
</Grid>
))}
</Grid>
</Grid>
<FormLayout withBorders={false} containerPadding={false}>
{fieldsElements.map((field, item) => (
<Fragment key={field.name}>{fieldDefinition(field, item)}</Fragment>
))}
</FormLayout>
);
};
export default withStyles(styles)(ConfTargetGeneric);
export default ConfTargetGeneric;

View File

@@ -15,20 +15,13 @@
// along with this program. If not, see <http://www.gnu.org/licenses/>.
import React, { Fragment, useCallback, useEffect, useState } from "react";
import { Button, Loader } from "mds";
import { useLocation, useNavigate } from "react-router-dom";
import get from "lodash/get";
import { Theme } from "@mui/material/styles";
import createStyles from "@mui/styles/createStyles";
import withStyles from "@mui/styles/withStyles";
import { Box } from "@mui/material";
import Grid from "@mui/material/Grid";
import ConfTargetGeneric from "../ConfTargetGeneric";
import {
fieldBasic,
settingsCommon,
} from "../../Common/FormComponents/common/styleLibrary";
import { Box, Button, Grid, Loader } from "mds";
import { useLocation, useNavigate } from "react-router-dom";
import { useSelector } from "react-redux";
import { api } from "api";
import { Configuration, ConfigurationKV } from "api/consoleApi";
import { errorToHandler } from "api/errors";
import {
fieldsConfigurations,
overrideFields,
@@ -40,7 +33,6 @@ import {
IOverrideEnv,
KVField,
} from "../../Configurations/types";
import ResetConfigurationModal from "./ResetConfigurationModal";
import {
configurationIsLoading,
setErrorSnackMessage,
@@ -50,31 +42,16 @@ import {
} from "../../../../systemSlice";
import { AppState, useAppDispatch } from "../../../../store";
import WebhookSettings from "../WebhookSettings/WebhookSettings";
import { useSelector } from "react-redux";
import { api } from "api";
import { Configuration, ConfigurationKV } from "api/consoleApi";
import { errorToHandler } from "api/errors";
const styles = (theme: Theme) =>
createStyles({
...fieldBasic,
...settingsCommon,
settingsFormContainer: {
display: "grid",
gridTemplateColumns: "1fr",
gridGap: "10px",
},
});
import ConfTargetGeneric from "../ConfTargetGeneric";
import ResetConfigurationModal from "./ResetConfigurationModal";
interface IAddNotificationEndpointProps {
selectedConfiguration: IConfigurationElement;
classes: any;
className?: string;
}
const EditConfiguration = ({
selectedConfiguration,
classes,
className = "",
}: IAddNotificationEndpointProps) => {
const dispatch = useAppDispatch();
@@ -238,7 +215,15 @@ const EditConfiguration = ({
flexFlow: "column",
}}
>
<Grid item xs={12} className={classes.settingsFormContainer}>
<Grid
item
xs={12}
sx={{
display: "grid",
gridTemplateColumns: "1fr",
gap: "10px",
}}
>
<ConfTargetGeneric
fields={
fieldsConfigurations[
@@ -263,6 +248,7 @@ const EditConfiguration = ({
}}
>
<Button
type={"button"}
id={"restore-defaults"}
variant="secondary"
onClick={resetConfigurationMOpen}
@@ -287,4 +273,4 @@ const EditConfiguration = ({
);
};
export default withStyles(styles)(EditConfiguration);
export default EditConfiguration;

View File

@@ -15,11 +15,11 @@
// along with this program. If not, see <http://www.gnu.org/licenses/>.
import React, { Fragment, useState } from "react";
import { Button, Grid } from "mds";
import InputBoxWrapper from "../../Common/FormComponents/InputBoxWrapper/InputBoxWrapper";
import { Button, FormLayout, Grid, InputBox } from "mds";
import { api } from "api";
import { errorToHandler } from "api/errors";
import ModalWrapper from "../../Common/ModalWrapper/ModalWrapper";
import { Webhook } from "@mui/icons-material";
import { formFieldStyles } from "../../Common/FormComponents/common/styleLibrary";
import CallToActionIcon from "@mui/icons-material/CallToAction";
import PendingActionsIcon from "@mui/icons-material/PendingActions";
import {
@@ -30,8 +30,7 @@ import {
} from "../../../../systemSlice";
import { useAppDispatch } from "../../../../store";
import { LinearProgress } from "@mui/material";
import { api } from "api";
import { errorToHandler } from "api/errors";
import { modalStyleUtils } from "../../Common/FormComponents/common/styleLibrary";
interface IEndpointModal {
open: boolean;
@@ -148,8 +147,8 @@ const AddEndpointModal = ({ open, type, onCloseEndpoint }: IEndpointModal) => {
onClose={onCloseEndpoint}
titleIcon={icon}
>
<Grid item xs={12} sx={{ ...formFieldStyles.formFieldRow }}>
<InputBoxWrapper
<FormLayout containerPadding={false} withBorders={false}>
<InputBox
id="name"
name="name"
onChange={(event: React.ChangeEvent<HTMLInputElement>) => {
@@ -167,9 +166,7 @@ const AddEndpointModal = ({ open, type, onCloseEndpoint }: IEndpointModal) => {
pattern={"^(?=.*[a-zA-Z0-9]).{1,}$"}
required
/>
</Grid>
<Grid item xs={12} sx={{ ...formFieldStyles.formFieldRow }}>
<InputBoxWrapper
<InputBox
id="endpoint"
name="endpoint"
onChange={(event: React.ChangeEvent<HTMLInputElement>) => {
@@ -190,9 +187,7 @@ const AddEndpointModal = ({ open, type, onCloseEndpoint }: IEndpointModal) => {
}
required
/>
</Grid>
<Grid item xs={12} sx={{ ...formFieldStyles.formFieldRow }}>
<InputBoxWrapper
<InputBox
id="auth-token"
name="auth-token"
onChange={(event: React.ChangeEvent<HTMLInputElement>) => {
@@ -202,7 +197,7 @@ const AddEndpointModal = ({ open, type, onCloseEndpoint }: IEndpointModal) => {
label="Auth Token"
value={authToken}
/>
</Grid>
</FormLayout>
{saving && (
<Grid
item
@@ -214,14 +209,7 @@ const AddEndpointModal = ({ open, type, onCloseEndpoint }: IEndpointModal) => {
<LinearProgress />
</Grid>
)}
<Grid
item
xs={12}
sx={{
display: "flex",
justifyContent: "flex-end",
}}
>
<Grid item xs={12} sx={modalStyleUtils.modalButtonBar}>
<Button
id={"reset"}
type="button"

View File

@@ -14,9 +14,10 @@
// 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 { ConfirmDeleteIcon } from "mds";
import { DialogContentText } from "@mui/material";
import { api } from "api";
import { errorToHandler } from "api/errors";
import ConfirmDialog from "../../Common/ModalWrapper/ConfirmDialog";
import {
configurationIsLoading,
@@ -24,8 +25,6 @@ import {
setServerNeedsRestart,
} from "../../../../systemSlice";
import { useAppDispatch } from "../../../../store";
import { api } from "api";
import { errorToHandler } from "api/errors";
interface IDeleteWebhookEndpoint {
modalOpen: boolean;
@@ -38,7 +37,6 @@ const DeleteWebhookEndpoint = ({
modalOpen,
onClose,
selectedARN,
type,
}: IDeleteWebhookEndpoint) => {
const [deleteLoading, setDeleteLoading] = useState<boolean>(false);
@@ -84,10 +82,10 @@ const DeleteWebhookEndpoint = ({
titleIcon={<ConfirmDeleteIcon />}
onClose={onClose}
confirmationContent={
<DialogContentText>
<Fragment>
{`${message} `}
<strong>{selectedARN}</strong>?
</DialogContentText>
</Fragment>
}
/>
);

View File

@@ -50,7 +50,6 @@ import HelpMenu from "../../HelpMenu";
const enabledConfigLDAP = [
"server_addr",
"lookup_bind_dn",
"lookup_bind_password",
"user_dn_search_base_dn",
"user_dn_search_filter",
];
@@ -90,17 +89,16 @@ const IDPLDAPConfigurationDetails = () => {
if (
enabledConfigLDAP.includes(item.key) &&
item.value &&
item.value !== "" &&
item.value !== "off"
((item.value && item.value !== "" && item.value !== "off") ||
(item.env_override &&
item.env_override.value !== "" &&
item.env_override.value !== "off"))
) {
totalCoincidences++;
}
});
const hasConfig = totalCoincidences === enabledConfigLDAP.length;
if ((!enabled || enabled.value === "on") && hasConfig) {
if (hasConfig && enabled && enabled.value !== "off") {
setIsEnabled(true);
} else {
setIsEnabled(false);

View File

@@ -43,11 +43,10 @@ test("All vertical tab items exist", async (t) => {
const settingsHealTabExists = elements.settingsHealTab.exists;
const settingsScannerTabExists = elements.settingsScannerTab.exists;
const settingsEtcdTabExists = elements.settingsEtcdTab.exists;
const settingsOpenIdTabExists = elements.settingsOpenIdTab.exists;
const settingsLdapTabExists = elements.settingsLdapTab.exists;
const settingsLoggerWebhookTabExists =
elements.settingsLoggerWebhookTab.exists;
const settingsAuditWebhookTabExists = elements.settingsAuditWebhookTab.exists;
const settingsAuditKafkaTabExists = elements.settingsAuditKafkaTab.exists;
await t
.navigateTo("http://localhost:9090/settings/configurations")
.expect(settingsRegionTabExists)
@@ -65,5 +64,7 @@ test("All vertical tab items exist", async (t) => {
.expect(settingsLoggerWebhookTabExists)
.ok()
.expect(settingsAuditWebhookTabExists)
.ok()
.expect(settingsAuditKafkaTabExists)
.ok();
});

View File

@@ -123,45 +123,41 @@ export const settingsWindow = Selector("#settings-container");
//----------------------------------------------------
// Settings page vertical tabs
//----------------------------------------------------
export const settingsRegionTab = Selector(".MuiTab-root").withAttribute(
"href",
"/settings/configurations/region",
export const settingsRegionTab = Selector("button").withAttribute(
"id",
"settings-tab-Region",
);
export const settingsCompressionTab = Selector(".MuiTab-root").withAttribute(
"href",
"/settings/configurations/compression",
export const settingsCompressionTab = Selector("button").withAttribute(
"id",
"settings-tab-Compression",
);
export const settingsApiTab = Selector(".MuiTab-root").withAttribute(
"href",
"/settings/configurations/api",
export const settingsApiTab = Selector("button").withAttribute(
"id",
"settings-tab-API",
);
export const settingsHealTab = Selector(".MuiTab-root").withAttribute(
"href",
"/settings/configurations/heal",
export const settingsHealTab = Selector("button").withAttribute(
"id",
"settings-tab-Heal",
);
export const settingsScannerTab = Selector(".MuiTab-root").withAttribute(
"href",
"/settings/configurations/scanner",
export const settingsScannerTab = Selector("button").withAttribute(
"id",
"settings-tab-Scanner",
);
export const settingsEtcdTab = Selector(".MuiTab-root").withAttribute(
"href",
"/settings/configurations/etcd",
export const settingsEtcdTab = Selector("button").withAttribute(
"id",
"settings-tab-Etcd",
);
export const settingsOpenIdTab = Selector(".MuiTab-root").withAttribute(
"href",
"/settings/configurations/identity_openid",
export const settingsLoggerWebhookTab = Selector("button").withAttribute(
"id",
"settings-tab-Logger Webhook",
);
export const settingsLdapTab = Selector(".MuiTab-root").withAttribute(
"href",
"/settings/configurations/identity_ldap",
export const settingsAuditWebhookTab = Selector("button").withAttribute(
"id",
"settings-tab-Audit Webhook",
);
export const settingsLoggerWebhookTab = Selector(".MuiTab-root").withAttribute(
"href",
"/settings/configurations/logger_webhook",
);
export const settingsAuditWebhookTab = Selector(".MuiTab-root").withAttribute(
"href",
"/settings/configurations/audit_webhook",
export const settingsAuditKafkaTab = Selector("button").withAttribute(
"id",
"settings-tab-Audit Kafka",
);
//----------------------------------------------------

View File

@@ -722,7 +722,10 @@ func getMultipleFilesDownloadResponse(session *models.Principal, params objectAp
continue
}
f, err := addToZip(dObj, objectData.LastModified)
prefixes := strings.Split(dObj, "/")
// truncate upper level prefixes to make the download as flat at the current level.
objectName := prefixes[len(prefixes)-1]
f, err := addToZip(objectName, objectData.LastModified)
if err != nil {
// Ignore errors, move to next
continue
@@ -741,7 +744,7 @@ func getMultipleFilesDownloadResponse(session *models.Principal, params objectAp
defer resp.Close()
// indicate it's a download / inline content to the browser, and the size of the object
fileName := "selected_files_" + time.Now().UTC().Format(time.DateTime)
fileName := "selected_files_" + strings.ReplaceAll(strings.ReplaceAll(time.Now().UTC().Format(time.RFC3339), ":", ""), "-", "")
rw.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=\"%s.zip\"", fileName))
rw.Header().Set("Content-Type", "application/zip")