Compare commits

...

7 Commits

Author SHA1 Message Date
Daniel Valdivia
33acf45264 Release v0.9.8 (#1016)
Signed-off-by: Daniel Valdivia <18384552+dvaldivia@users.noreply.github.com>
2021-09-08 13:38:29 -07:00
jinapurapu
f229fee3ee Added version card to BasicDashboard (#1013)
* Added version card to BasicDashboard

* Added length check for servers array on version card render

Co-authored-by: Daniel Valdivia <18384552+dvaldivia@users.noreply.github.com>
Co-authored-by: Alex <33497058+bexsoft@users.noreply.github.com>
2021-09-08 11:38:32 -07:00
Bian Jiaping
21b3ed67af Eliminate usage of String.prototype.replaceAll to avoid incompatibility with old browsers (#1015) 2021-09-08 10:58:37 -07:00
Alex
90cadc76bb Applied workaround for MIME issue on windows (#1014) 2021-09-07 17:57:00 -07:00
Daniel Valdivia
b13b6db6f7 Share Icon updated (#1008) 2021-09-07 17:48:34 -05:00
adfost
1d88bb491d Disable create service account button if no policy (#1006)
* disable button if no policies

* merge fix

* finding group policies

* fixing style

Co-authored-by: Daniel Valdivia <18384552+dvaldivia@users.noreply.github.com>
Co-authored-by: Adam Stafford <adamstafford@Adams-MacBook-Pro.local>
Co-authored-by: Alex <33497058+bexsoft@users.noreply.github.com>
2021-09-07 15:02:04 -07:00
Daniel Valdivia
13cf3e6fa1 Release v0.9.7 (#1010)
Signed-off-by: Daniel Valdivia <18384552+dvaldivia@users.noreply.github.com>
2021-09-04 22:58:44 -07:00
27 changed files with 102 additions and 155 deletions

View File

@@ -15,7 +15,7 @@ spec:
serviceAccountName: console-sa
containers:
- name: console
image: minio/console:v0.9.6
image: minio/console:v0.9.8
imagePullPolicy: "IfNotPresent"
env:
- name: CONSOLE_OPERATOR_MODE

View File

@@ -37,6 +37,9 @@ type User struct {
// access key
AccessKey string `json:"accessKey,omitempty"`
// has policy
HasPolicy bool `json:"hasPolicy,omitempty"`
// member of
MemberOf []string `json:"memberOf"`

View File

@@ -1,23 +1,23 @@
{
"files": {
"main.css": "./static/css/main.8cfac526.chunk.css",
"main.js": "./static/js/main.d668582e.chunk.js",
"main.js.map": "./static/js/main.d668582e.chunk.js.map",
"main.js": "./static/js/main.ab436313.chunk.js",
"main.js.map": "./static/js/main.ab436313.chunk.js.map",
"runtime-main.js": "./static/js/runtime-main.3fe0c1ac.js",
"runtime-main.js.map": "./static/js/runtime-main.3fe0c1ac.js.map",
"static/css/2.c5a51b70.chunk.css": "./static/css/2.c5a51b70.chunk.css",
"static/js/2.941b277c.chunk.js": "./static/js/2.941b277c.chunk.js",
"static/js/2.941b277c.chunk.js.map": "./static/js/2.941b277c.chunk.js.map",
"static/js/2.732d4e60.chunk.js": "./static/js/2.732d4e60.chunk.js",
"static/js/2.732d4e60.chunk.js.map": "./static/js/2.732d4e60.chunk.js.map",
"index.html": "./index.html",
"static/css/2.c5a51b70.chunk.css.map": "./static/css/2.c5a51b70.chunk.css.map",
"static/css/main.8cfac526.chunk.css.map": "./static/css/main.8cfac526.chunk.css.map",
"static/js/2.941b277c.chunk.js.LICENSE.txt": "./static/js/2.941b277c.chunk.js.LICENSE.txt"
"static/js/2.732d4e60.chunk.js.LICENSE.txt": "./static/js/2.732d4e60.chunk.js.LICENSE.txt"
},
"entrypoints": [
"static/js/runtime-main.3fe0c1ac.js",
"static/css/2.c5a51b70.chunk.css",
"static/js/2.941b277c.chunk.js",
"static/js/2.732d4e60.chunk.js",
"static/css/main.8cfac526.chunk.css",
"static/js/main.d668582e.chunk.js"
"static/js/main.ab436313.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.941b277c.chunk.js"></script><script src="./static/js/main.d668582e.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.732d4e60.chunk.js"></script><script src="./static/js/main.ab436313.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

@@ -22,7 +22,7 @@ import { ErrorResponseHandler } from "../types";
export class API {
invoke(method: string, url: string, data?: object) {
const targetURL = `${baseUrl}${url}`.replaceAll("//", "/");
const targetURL = `${baseUrl}${url}`.replace(/\/\//g, "/");
return request(method, targetURL)
.send(data)
.then((res) => res.body)

View File

@@ -14,25 +14,26 @@
// 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 SvgIcon from "@material-ui/core/SvgIcon";
import * as React from "react";
import { SvgIcon, SvgIconProps } from "@material-ui/core";
interface IShareIcon {
width?: number;
}
const ShareIcon = ({ width = 24 }: IShareIcon) => {
const ShareIcon = (props: SvgIconProps) => {
return (
<SvgIcon style={{ width: width, height: width }}>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 13 13">
<SvgIcon {...props}>
<svg
xmlns="http://www.w3.org/2000/svg"
width="1em"
height="1em"
viewBox="0 0 255.999 243.852"
>
<path
d="M11.05 8.617v2.429h-9.1v-9.1h2.429v-1.95H0v13h13V8.617z"
className="a"
></path>
data-name="Trazado 410"
d="M251.315 67.671L207.79 25.459c-14.279-13.851-35.342 7.862-21.063 21.716l12.959 12.567a156.689 156.689 0 00-82.95 23.182 156.774 156.774 0 00-71.051 97.677 15.547 15.547 0 0011.474 18.755 15.62 15.62 0 003.655.438 15.555 15.555 0 0015.1-11.909c14.6-60.586 70.74-100.461 130.9-96.758l-3.335 4.317-15.767 16.248c-13.849 14.285 7.867 35.345 21.719 21.063l42.214-43.518a15.131 15.131 0 00-.33-21.566z"
/>
<path
d="M3.854 9.256h1.95a4.945 4.945 0 013.6-4.74v1.3l.6-.487 2.474-2.012L9.4.817v1.7a6.9 6.9 0 00-5.546 6.739z"
className="a"
></path>
data-name="Trazado 411"
d="M229.501 148.665a14.352 14.352 0 00-14.348 14.351v52.134H28.703V28.703h126.71a14.352 14.352 0 0014.351-14.351A14.353 14.353 0 00155.413.001h-130.1A25.34 25.34 0 00.002 25.314v193.228a25.339 25.339 0 0025.311 25.311h193.23a25.339 25.339 0 0025.311-25.311v-55.526a14.353 14.353 0 00-14.353-14.351z"
/>
</svg>
</SvgIcon>
);

View File

@@ -18,14 +18,13 @@ import isString from "lodash/isString";
import { Link } from "react-router-dom";
import { createStyles, withStyles } from "@material-ui/core/styles";
import { IconButton } from "@material-ui/core";
import ShareIcon from "./TableActionIcons/ShareIcon";
import CloudIcon from "./TableActionIcons/CloudIcon";
import ConsoleIcon from "./TableActionIcons/ConsoleIcon";
import DisableIcon from "./TableActionIcons/DisableIcon";
import FormatDriveIcon from "./TableActionIcons/FormatDriveIcon";
import EditIcon from "../../../../icons/EditIcon";
import TrashIcon from "../../../../icons/TrashIcon";
import { IAMPoliciesIcon, PreviewIcon } from "../../../../icons";
import { IAMPoliciesIcon, PreviewIcon, ShareIcon } from "../../../../icons";
import DownloadIcon from "../../../../icons/DownloadIcon";
const styles = () =>
@@ -65,7 +64,7 @@ const defineIcon = (type: string, selected: boolean) => {
case "description":
return <IAMPoliciesIcon />;
case "share":
return <ShareIcon active={selected} />;
return <ShareIcon />;
case "cloud":
return <CloudIcon active={selected} />;
case "console":

View File

@@ -1,20 +0,0 @@
import React from "react";
import { IIcon, selected, unSelected } from "./common";
const DeleteIcon = ({ active = false }: IIcon) => {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
viewBox="0 0 10.402 13"
>
<path
fill={active ? selected : unSelected}
d="M6.761 1V0H3.64v1H.004v1h10.4V1zM.004 2.998l1.672 10h7.052l1.673-10zm3.412 8.243l-.552-6.478h.653l.553 6.472zm3.569 0h-.653l.551-6.472h.654z"
/>
</svg>
);
};
export default DeleteIcon;

View File

@@ -1,24 +0,0 @@
import React from "react";
import { IIcon, selected, unSelected } from "./common";
const DeleteIcon = ({ active = false }: IIcon) => {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
viewBox="0 0 13 12.996"
>
<path
fill={active ? selected : unSelected}
d="M11.05 9.096v1.95h-9.1v-1.95H0v3.9h13v-3.9z"
></path>
<path
fill={active ? selected : unSelected}
d="M6.5 9.75L9 6.672H7.475V0h-1.95v6.672H4z"
></path>
</svg>
);
};
export default DeleteIcon;

View File

@@ -1,24 +0,0 @@
import React from "react";
import { IIcon, selected, unSelected } from "./common";
import { SvgIcon } from "@material-ui/core";
const PencilIcon = ({ active = false }: IIcon) => {
return (
<SvgIcon>
<svg
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
viewBox="0 0 13.833 13.833"
>
<path
fill={active ? selected : unSelected}
d="M2.934,16H0V13.066L10.607,2.459a1,1,0,0,1,1.414,0l1.52,1.52a1,1,0,0,1,0,1.414Z"
transform="translate(0 -2.167)"
/>
</svg>
</SvgIcon>
);
};
export default PencilIcon;

View File

@@ -1,26 +0,0 @@
import React from "react";
import { IIcon, selected, unSelected } from "./common";
const ShareIcon = ({ active = false }: IIcon) => {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
viewBox="0 0 13 13"
>
<path
fill={active ? selected : unSelected}
d="M11.05 8.617v2.429h-9.1v-9.1h2.429v-1.95H0v13h13V8.617z"
className="a"
></path>
<path
fill={active ? selected : unSelected}
d="M3.854 9.256h1.95a4.945 4.945 0 013.6-4.74v1.3l.6-.487 2.474-2.012L9.4.817v1.7a6.9 6.9 0 00-5.546 6.739z"
className="a"
></path>
</svg>
);
};
export default ShareIcon;

View File

@@ -1,21 +0,0 @@
import React from "react";
import { IIcon, selected, unSelected } from "./common";
const ViewIcon = ({ active = false }: IIcon) => {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
viewBox="0 0 16 11.856"
>
<path
fill={active ? selected : unSelected}
d="M-54,8l1.764,2.614A7.52,7.52,0,0,0-46,13.928h0a7.52,7.52,0,0,0,6.234-3.314L-38,8l-1.764-2.614A7.52,7.52,0,0,0-46,2.072h0a7.52,7.52,0,0,0-6.234,3.314Zm10.286,0A2.285,2.285,0,0,1-46,10.286,2.285,2.285,0,0,1-48.286,8,2.285,2.285,0,0,1-46,5.714,2.285,2.285,0,0,1-43.714,8Zm1.3,0A3.59,3.59,0,0,1-46,11.59,3.59,3.59,0,0,1-49.59,8,3.59,3.59,0,0,1-46,4.41,3.59,3.59,0,0,1-42.41,8Z"
transform="translate(54 -2.072)"
/>
</svg>
);
};
export default ViewIcon;

View File

@@ -138,6 +138,20 @@ const BasicDashboard = ({ classes, usage }: IDashboardProps) => {
/>
</Card>
</Grid>
<Grid item xs={6}>
<Card className={classes.cardRoot}>
{usage
? usage.servers.length !== 0 && (
<CardHeader
avatar={<TotalObjectsIcon />}
title="MinIO Version"
subheader={usage ? usage.servers[0].version : 0}
/>
)
: 0}
</Card>
</Grid>
<Grid item xs={6} />
</Grid>

View File

@@ -94,8 +94,7 @@ const DriveInfoCard = ({ classes, drive }: ICardProps) => {
return classes.redState;
case "ok":
return classes.greenState;
default:
return classes.greyState;
deefault: return classes.greyState;
}
};

View File

@@ -151,6 +151,7 @@ const UserDetails = ({ classes, match }: IUserDetailsProps) => {
const [changeUserPasswordModalOpen, setChangeUserPasswordModalOpen] =
useState<boolean>(false);
const [deleteOpen, setDeleteOpen] = useState<boolean>(false);
const [hasPolicy, setHasPolicy] = useState<boolean>(false);
const userName = match.params["userName"];
@@ -188,6 +189,7 @@ const UserDetails = ({ classes, match }: IUserDetailsProps) => {
}
setCurrentPolicies(currentPolicies);
setEnabled(res.status === "enabled");
setHasPolicy(res.hasPolicy);
setLoading(false);
})
.catch((err: ErrorResponseHandler) => {
@@ -382,7 +384,11 @@ const UserDetails = ({ classes, match }: IUserDetailsProps) => {
/>
</TabPanel>
<TabPanel index={1} value={curTab}>
<UserServiceAccountsPanel user={userName} classes={classes} />
<UserServiceAccountsPanel
user={userName}
classes={classes}
hasPolicy={hasPolicy}
/>
</TabPanel>
<TabPanel index={2} value={curTab}>
<div className={classes.actionsTray}>

View File

@@ -38,6 +38,7 @@ interface IUserServiceAccountsProps {
classes: any;
user: string;
setErrorSnackMessage: typeof setErrorSnackMessage;
hasPolicy: boolean;
}
const styles = (theme: Theme) =>
@@ -53,6 +54,7 @@ const UserServiceAccountsPanel = ({
classes,
user,
setErrorSnackMessage,
hasPolicy,
}: IUserServiceAccountsProps) => {
const [records, setRecords] = useState<string[]>([]);
const [loading, setLoading] = useState<boolean>(false);
@@ -75,7 +77,6 @@ const UserServiceAccountsPanel = ({
.invoke("GET", `/api/v1/user/${user}/service-accounts`)
.then((res: string[]) => {
const serviceAccounts = res.sort(stringSort);
setLoading(false);
setRecords(serviceAccounts);
})
@@ -169,6 +170,7 @@ const UserServiceAccountsPanel = ({
setAddScreenOpen(true);
setSelectedServiceAccount(null);
}}
disabled={!hasPolicy}
>
Create service account
</Button>

View File

@@ -281,11 +281,35 @@ func getUserInfoResponse(session *models.Principal, params admin_api.GetUserInfo
return nil, prepareError(err)
}
var policies []string
if user.PolicyName == "" {
policies = []string{}
} else {
policies = strings.Split(user.PolicyName, ",")
}
hasPolicy := true
if len(policies) == 0 {
hasPolicy = false
for i := 0; i < len(user.MemberOf); i++ {
group, err := adminClient.getGroupDescription(ctx, user.MemberOf[i])
if err != nil {
continue
}
if group.Policy != "" {
hasPolicy = true
break
}
}
}
userInformation := &models.User{
AccessKey: params.Name,
MemberOf: user.MemberOf,
Policy: strings.Split(user.PolicyName, ","),
Policy: policies,
Status: string(user.Status),
HasPolicy: hasPolicy,
}
return userInformation, nil

View File

@@ -240,6 +240,11 @@ var reHrefIndex = regexp.MustCompile(`(?m)((href|src)="(.\/).*?")`)
// wrapHandlerSinglePageApplication handles a http.FileServer returning a 404 and overrides it with index.html
func wrapHandlerSinglePageApplication(h http.Handler) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
// This is used to enforce application/javascript MIME on Windows (https://github.com/golang/go/issues/32350)
if strings.HasSuffix(r.URL.Path, ".js") {
w.Header().Set("Content-Type", "application/javascript")
}
nfrw := &notFoundRedirectRespWr{ResponseWriter: w}
h.ServeHTTP(nfrw, r)
if nfrw.status == 404 || r.URL.String() == "/" {

View File

@@ -5454,6 +5454,9 @@ func init() {
"accessKey": {
"type": "string"
},
"hasPolicy": {
"type": "boolean"
},
"memberOf": {
"type": "array",
"items": {
@@ -11049,6 +11052,9 @@ func init() {
"accessKey": {
"type": "string"
},
"hasPolicy": {
"type": "boolean"
},
"memberOf": {
"type": "array",
"items": {

View File

@@ -2485,6 +2485,9 @@ definitions:
type: string
status:
type: string
hasPolicy:
type: boolean
listUsersResponse:
type: object
properties: