From d1d3d91fc1326134e26c5d2ce34e458bba8562c1 Mon Sep 17 00:00:00 2001 From: Prakash Senthil Vel <23444145+prakashsvmx@users.noreply.github.com> Date: Thu, 14 Apr 2022 07:21:43 +0000 Subject: [PATCH] Site replication status (#1834) Site replication status UI Site replication status ui-test Address review comment by Alex Add functional test for API Add integration tests for status API --- models/site_replication_status_response.go | 97 ++++++ .../src/common/SecureComponent/permissions.ts | 9 +- .../SummaryItems/RBIconButton.tsx | 5 +- .../EntityReplicationLookup.tsx | 210 +++++++++++++ .../LookupStatus/BucketEntityStatus.tsx | 129 ++++++++ .../LookupStatus/EntityNotFound.tsx | 29 ++ .../LookupStatus/GroupEntityStatus.tsx | 99 +++++++ .../LookupStatus/LookupStatusTable.tsx | 142 +++++++++ .../LookupStatus/PolicyEntityStatus.tsx | 85 ++++++ .../LookupStatus/UserEntityStatus.tsx | 91 ++++++ .../SiteReplication/LookupStatus/Utils.tsx | 49 ++++ .../SiteReplication/ReplicationSites.tsx | 104 ------- .../SiteReplication/SiteReplication.tsx | 13 + .../SiteReplication/SiteReplicationStatus.tsx | 215 ++++++++++++++ portal-ui/src/screens/Console/Console.tsx | 5 + .../BasicDashboard/StatusCountCard.tsx | 9 +- .../tests/permissions-2/site-replication.ts | 2 +- replication/admin_api_int_replication_test.go | 61 ++++ restapi/admin_replication_status.go | 92 ++++++ restapi/admin_site_replication_test.go | 63 ++++ restapi/client-admin.go | 12 + restapi/configure_console.go | 1 + restapi/embedded_spec.go | 206 +++++++++++++ .../admin_api/get_site_replication_status.go | 88 ++++++ .../get_site_replication_status_parameters.go | 275 ++++++++++++++++++ .../get_site_replication_status_responses.go | 133 +++++++++ .../get_site_replication_status_urlbuilder.go | 167 +++++++++++ restapi/operations/console_api.go | 12 + swagger-console.yml | 72 +++++ 29 files changed, 2365 insertions(+), 110 deletions(-) create mode 100644 models/site_replication_status_response.go create mode 100644 portal-ui/src/screens/Console/Configurations/SiteReplication/EntityReplicationLookup.tsx create mode 100644 portal-ui/src/screens/Console/Configurations/SiteReplication/LookupStatus/BucketEntityStatus.tsx create mode 100644 portal-ui/src/screens/Console/Configurations/SiteReplication/LookupStatus/EntityNotFound.tsx create mode 100644 portal-ui/src/screens/Console/Configurations/SiteReplication/LookupStatus/GroupEntityStatus.tsx create mode 100644 portal-ui/src/screens/Console/Configurations/SiteReplication/LookupStatus/LookupStatusTable.tsx create mode 100644 portal-ui/src/screens/Console/Configurations/SiteReplication/LookupStatus/PolicyEntityStatus.tsx create mode 100644 portal-ui/src/screens/Console/Configurations/SiteReplication/LookupStatus/UserEntityStatus.tsx create mode 100644 portal-ui/src/screens/Console/Configurations/SiteReplication/LookupStatus/Utils.tsx create mode 100644 portal-ui/src/screens/Console/Configurations/SiteReplication/SiteReplicationStatus.tsx create mode 100644 restapi/admin_replication_status.go create mode 100644 restapi/operations/admin_api/get_site_replication_status.go create mode 100644 restapi/operations/admin_api/get_site_replication_status_parameters.go create mode 100644 restapi/operations/admin_api/get_site_replication_status_responses.go create mode 100644 restapi/operations/admin_api/get_site_replication_status_urlbuilder.go diff --git a/models/site_replication_status_response.go b/models/site_replication_status_response.go new file mode 100644 index 000000000..4e8b3c886 --- /dev/null +++ b/models/site_replication_status_response.go @@ -0,0 +1,97 @@ +// Code generated by go-swagger; DO NOT EDIT. + +// 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 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 . +// + +package models + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "context" + + "github.com/go-openapi/strfmt" + "github.com/go-openapi/swag" +) + +// SiteReplicationStatusResponse site replication status response +// +// swagger:model siteReplicationStatusResponse +type SiteReplicationStatusResponse struct { + + // bucket stats + BucketStats interface{} `json:"bucketStats,omitempty"` + + // enabled + Enabled bool `json:"enabled,omitempty"` + + // group stats + GroupStats interface{} `json:"groupStats,omitempty"` + + // max buckets + MaxBuckets int64 `json:"maxBuckets,omitempty"` + + // max groups + MaxGroups int64 `json:"maxGroups,omitempty"` + + // max policies + MaxPolicies int64 `json:"maxPolicies,omitempty"` + + // max users + MaxUsers int64 `json:"maxUsers,omitempty"` + + // policy stats + PolicyStats interface{} `json:"policyStats,omitempty"` + + // sites + Sites interface{} `json:"sites,omitempty"` + + // stats summary + StatsSummary interface{} `json:"statsSummary,omitempty"` + + // user stats + UserStats interface{} `json:"userStats,omitempty"` +} + +// Validate validates this site replication status response +func (m *SiteReplicationStatusResponse) Validate(formats strfmt.Registry) error { + return nil +} + +// ContextValidate validates this site replication status response based on context it is used +func (m *SiteReplicationStatusResponse) ContextValidate(ctx context.Context, formats strfmt.Registry) error { + return nil +} + +// MarshalBinary interface implementation +func (m *SiteReplicationStatusResponse) MarshalBinary() ([]byte, error) { + if m == nil { + return nil, nil + } + return swag.WriteJSON(m) +} + +// UnmarshalBinary interface implementation +func (m *SiteReplicationStatusResponse) UnmarshalBinary(b []byte) error { + var res SiteReplicationStatusResponse + if err := swag.ReadJSON(b, &res); err != nil { + return err + } + *m = res + return nil +} diff --git a/portal-ui/src/common/SecureComponent/permissions.ts b/portal-ui/src/common/SecureComponent/permissions.ts index 3efc6779e..06cb078de 100644 --- a/portal-ui/src/common/SecureComponent/permissions.ts +++ b/portal-ui/src/common/SecureComponent/permissions.ts @@ -162,6 +162,7 @@ export const IAM_PAGES = { TIERS_ADD: "/settings/tiers/add", TIERS_ADD_SERVICE: "/settings/tiers/add/:service", SITE_REPLICATION: "/settings/site-replication", + SITE_REPLICATION_STATUS: "/settings/site-replication/status", /* Operator */ TENANTS: "/tenants", @@ -185,7 +186,7 @@ export const IAM_PAGES = { NAMESPACE_TENANT_POOLS_ADD: "/namespaces/:tenantNamespace/tenants/:tenantName/add-pool", NAMESPACE_TENANT_POOLS_EDIT: - "/namespaces/:tenantNamespace/tenants/:tenantName/edit-pool", + "/namespaces/:tenantNamespace/tenants/:tenantName/edit-pool", NAMESPACE_TENANT_VOLUMES: "/namespaces/:tenantNamespace/tenants/:tenantName/volumes", NAMESPACE_TENANT_LICENSE: @@ -384,7 +385,11 @@ export const IAM_PAGES_PERMISSIONS = { IAM_SCOPES.ADMIN_SERVER_INFO, IAM_SCOPES.ADMIN_CONFIG_UPDATE, ], -}; + [IAM_PAGES.SITE_REPLICATION_STATUS]: [ + IAM_SCOPES.ADMIN_SERVER_INFO, + IAM_SCOPES.ADMIN_CONFIG_UPDATE, + ], +} export const S3_ALL_RESOURCES = "arn:aws:s3:::*"; export const CONSOLE_UI_RESOURCE = "console-ui"; diff --git a/portal-ui/src/screens/Console/Buckets/BucketDetails/SummaryItems/RBIconButton.tsx b/portal-ui/src/screens/Console/Buckets/BucketDetails/SummaryItems/RBIconButton.tsx index 04ac52949..0ba029229 100644 --- a/portal-ui/src/screens/Console/Buckets/BucketDetails/SummaryItems/RBIconButton.tsx +++ b/portal-ui/src/screens/Console/Buckets/BucketDetails/SummaryItems/RBIconButton.tsx @@ -28,6 +28,7 @@ type DeleteButtonProps = { tooltip?: string; classes?: any; icon?: React.ReactNode; + showLabelAlways?:boolean; [x: string]: any; }; @@ -80,6 +81,7 @@ const RBIconButton = (props: RBIconProps) => { tooltip, icon = null, className = "", + showLabelAlways=false, ...restProps } = props; @@ -98,7 +100,8 @@ const RBIconButton = (props: RBIconProps) => { "& span": { fontSize: 14, "@media (max-width: 900px)": { - display: "none", + display: showLabelAlways?"inline":"none", + marginRight:showLabelAlways?"7px":"" }, }, }} diff --git a/portal-ui/src/screens/Console/Configurations/SiteReplication/EntityReplicationLookup.tsx b/portal-ui/src/screens/Console/Configurations/SiteReplication/EntityReplicationLookup.tsx new file mode 100644 index 000000000..d5377e82e --- /dev/null +++ b/portal-ui/src/screens/Console/Configurations/SiteReplication/EntityReplicationLookup.tsx @@ -0,0 +1,210 @@ +// 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 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 . + +import React, { useState } from "react"; +import { Box, Grid } from "@mui/material"; +import SelectWrapper from "../../Common/FormComponents/SelectWrapper/SelectWrapper"; +import InputBoxWrapper from "../../Common/FormComponents/InputBoxWrapper/InputBoxWrapper"; +import RBIconButton from "../../Buckets/BucketDetails/SummaryItems/RBIconButton"; +import { ClustersIcon } from "../../../../icons"; +import useApi from "../../Common/Hooks/useApi"; +import { StatsResponseType } from "./SiteReplicationStatus"; +import BucketEntityStatus from "./LookupStatus/BucketEntityStatus"; +import Loader from "../../Common/Loader/Loader"; +import PolicyEntityStatus from "./LookupStatus/PolicyEntityStatus"; +import GroupEntityStatus from "./LookupStatus/GroupEntityStatus"; +import UserEntityStatus from "./LookupStatus/UserEntityStatus"; + +const EntityReplicationLookup = () => { + const [entityType, setEntityType] = useState("bucket"); + const [entityValue, setEntityValue] = useState(""); + + const [stats, setStats] = useState({}); + const [statsLoaded, setStatsLoaded] = useState(false); + + const [isStatsLoading, invokeSiteStatsApi] = useApi( + (res: any) => { + setStats(res); + setStatsLoaded(true); + }, + (err: any) => { + setStats({}); + setStatsLoaded(true); + } + ); + + const { + bucketStats = {}, + sites = {}, + userStats = {}, + policyStats = {}, + groupStats = {}, + } = stats || {}; + + const getStats = (entityType: string = "", entityValue: string = "") => { + setStatsLoaded(false); + if (entityType && entityValue) { + let url = `api/v1/admin/site-replication/status?buckets=false&entityType=${entityType}&entityValue=${entityValue}&groups=false&policies=false&users=false`; + invokeSiteStatsApi("GET", url); + } + }; + + return ( + + + + View Replication Status for a: + + + { + setEntityType(e.target.value); + setStatsLoaded(false); + }} + label="" + value={entityType} + options={[ + { + label: "Bucket", + value: "bucket", + }, + { + label: "User", + value: "user", + }, + { + label: "Group", + value: "group", + }, + { + label: "Policy", + value: "policy", + }, + ]} + disabled={false} + /> + + + + ) => { + setEntityValue(e.target.value); + setStatsLoaded(false); + }} + placeholder={`test-${entityType}`} + label="" + value={entityValue} + /> + + + { + getStats(entityType, entityValue); + }} + text={`View `} + tooltip={"View across sites"} + icon={} + color={"primary"} + showLabelAlways + disabled={!entityValue || !entityType} + /> + + + + {isStatsLoading ? ( + + + + ) : null} + + {statsLoaded ? ( + + {!isStatsLoading && entityType === "bucket" && entityValue ? ( + + ) : null} + + {!isStatsLoading && entityType === "user" && entityValue ? ( + + ) : null} + + {!isStatsLoading && entityType === "group" && entityValue ? ( + + ) : null} + + {!isStatsLoading && entityType === "policy" && entityValue ? ( + + ) : null} + + ) : null} + + ); +}; + +export default EntityReplicationLookup; diff --git a/portal-ui/src/screens/Console/Configurations/SiteReplication/LookupStatus/BucketEntityStatus.tsx b/portal-ui/src/screens/Console/Configurations/SiteReplication/LookupStatus/BucketEntityStatus.tsx new file mode 100644 index 000000000..8f9dbac94 --- /dev/null +++ b/portal-ui/src/screens/Console/Configurations/SiteReplication/LookupStatus/BucketEntityStatus.tsx @@ -0,0 +1,129 @@ +// 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 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 . + +import React from "react"; +import { StatsResponseType } from "../SiteReplicationStatus"; +import LookupStatusTable from "./LookupStatusTable"; +import { EntityNotFound, isEntityNotFound, syncStatus } from "./Utils"; + +type BucketEntityStatusProps = Partial & { + lookupValue?: string; +}; +const BucketEntityStatus = ({ + bucketStats = {}, + sites = {}, + lookupValue = "", +}: BucketEntityStatusProps) => { + const rowsForStatus = [ + "Tags", + "Policy", + "Quota", + "Retention", + "Encryption", + "Replication", + ]; + + const bucketSites: Record = bucketStats[lookupValue] || {}; + + if (!lookupValue) return null; + + const siteKeys = Object.keys(sites); + + const notFound = isEntityNotFound(sites, bucketSites, "HasBucket"); + const resultMatrix: any = []; + if (notFound) { + return ; + } else { + const row = []; + for (let sCol = 0; sCol < siteKeys.length; sCol++) { + if (sCol === 0) { + row.push(""); + } + /** + * ---------------------------------- + * | | sit-0 | site-1 | + * ----------------------------------- + */ + row.push(sites[siteKeys[sCol]].name); + } + resultMatrix.push(row); + for (let fi = 0; fi < rowsForStatus.length; fi++) { + /** + * ------------------------------------------------- + * | Feature Name | site-0-status | site-1-status | + * -------------------------------------------------- + */ + const sfRow = []; + const feature = rowsForStatus[fi]; + let sbStatus: string | boolean = ""; + + for (let si = 0; si < siteKeys.length; si++) { + const bucketSiteDeploymentId = sites[siteKeys[si]].deploymentID; + + const rSite = bucketSites[bucketSiteDeploymentId]; + + if (si === 0) { + sfRow.push(feature); + } + + switch (fi) { + case 0: + sbStatus = syncStatus(rSite.TagMismatch, rSite.HasTagsSet); + sfRow.push(sbStatus); + break; + case 1: + sbStatus = syncStatus(rSite.PolicyMismatch, rSite.HasPolicySet); + sfRow.push(sbStatus); + break; + case 2: + sbStatus = syncStatus(rSite.QuotaCfgMismatch, rSite.HasQuotaCfgSet); + sfRow.push(sbStatus); + break; + case 3: + sbStatus = syncStatus( + rSite.OLockConfigMismatch, + rSite.HasOLockConfigSet + ); + sfRow.push(sbStatus); + break; + case 4: + sbStatus = syncStatus(rSite.SSEConfigMismatch, rSite.HasSSECfgSet); + sfRow.push(sbStatus); + break; + case 5: + sbStatus = syncStatus( + rSite.ReplicationCfgMismatch, + rSite.HasReplicationCfg + ); + sfRow.push(sbStatus); + break; + } + } + + resultMatrix.push(sfRow); + } + } + + return ( + + ); +}; + +export default BucketEntityStatus; diff --git a/portal-ui/src/screens/Console/Configurations/SiteReplication/LookupStatus/EntityNotFound.tsx b/portal-ui/src/screens/Console/Configurations/SiteReplication/LookupStatus/EntityNotFound.tsx new file mode 100644 index 000000000..97c0c455f --- /dev/null +++ b/portal-ui/src/screens/Console/Configurations/SiteReplication/LookupStatus/EntityNotFound.tsx @@ -0,0 +1,29 @@ +import React from "react"; +import { Box } from "@mui/material"; + +const EntityNotFound = ({ + entityType, + entityValue, +}: { + entityType: string; + entityValue: string; +}) => { + return ( + + {entityType}:{" "} + + {entityValue} + {" "} + not found. + + ); +}; + +export default EntityNotFound; diff --git a/portal-ui/src/screens/Console/Configurations/SiteReplication/LookupStatus/GroupEntityStatus.tsx b/portal-ui/src/screens/Console/Configurations/SiteReplication/LookupStatus/GroupEntityStatus.tsx new file mode 100644 index 000000000..3df52a80a --- /dev/null +++ b/portal-ui/src/screens/Console/Configurations/SiteReplication/LookupStatus/GroupEntityStatus.tsx @@ -0,0 +1,99 @@ +// 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 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 . + +import React from "react"; +import { StatsResponseType } from "../SiteReplicationStatus"; +import LookupStatusTable from "./LookupStatusTable"; +import { EntityNotFound, isEntityNotFound, syncStatus } from "./Utils"; + +type GroupEntityStatusProps = Partial & { + lookupValue?: string; +}; +const UserEntityStatus = ({ + groupStats = {}, + sites = {}, + lookupValue = "", +}: GroupEntityStatusProps) => { + const rowsForStatus = ["Info", "Policy mapping"]; + + const groupSites: Record = groupStats[lookupValue] || {}; + + if (!lookupValue) return null; + + const siteKeys = Object.keys(sites); + const notFound = isEntityNotFound(sites, groupSites, "HasGroup"); + const resultMatrix: any = []; + if (notFound) { + return ; + } else { + const row = []; + for (let sCol = 0; sCol < siteKeys.length; sCol++) { + if (sCol === 0) { + row.push(""); + } + /** + * ---------------------------------- + * | | sit-0 | site-1 | + * ----------------------------------- + */ + row.push(sites[siteKeys[sCol]].name); + } + resultMatrix.push(row); + for (let fi = 0; fi < rowsForStatus.length; fi++) { + /** + * ------------------------------------------------- + * | Feature Name | site-0-status | site-1-status | + * -------------------------------------------------- + */ + const sfRow = []; + const feature = rowsForStatus[fi]; + let sbStatus: string | boolean = ""; + + for (let si = 0; si < siteKeys.length; si++) { + const bucketSiteDeploymentId = sites[siteKeys[si]].deploymentID; + + const rSite = groupSites[bucketSiteDeploymentId]; + + if (si === 0) { + sfRow.push(feature); + } + + switch (fi) { + case 0: + sbStatus = syncStatus(rSite.GroupDescMismatch, rSite.HasGroup); + sfRow.push(sbStatus); + break; + case 1: + sbStatus = syncStatus(rSite.PolicyMismatch, rSite.HasPolicyMapping); + sfRow.push(sbStatus); + break; + } + } + + resultMatrix.push(sfRow); + } + } + + return ( + + ); +}; + +export default UserEntityStatus; diff --git a/portal-ui/src/screens/Console/Configurations/SiteReplication/LookupStatus/LookupStatusTable.tsx b/portal-ui/src/screens/Console/Configurations/SiteReplication/LookupStatus/LookupStatusTable.tsx new file mode 100644 index 000000000..4959b92db --- /dev/null +++ b/portal-ui/src/screens/Console/Configurations/SiteReplication/LookupStatus/LookupStatusTable.tsx @@ -0,0 +1,142 @@ +// 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 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 . + +import React from "react"; +import { Box } from "@mui/material"; +import { CircleIcon } from "../../../../../icons"; + +const LookupStatusTable = ({ + matrixData = [], + entityName = "", + entityType = "", +}: { + matrixData: any; + entityName: string; + entityType: string; +}) => { + //Assumes 1st row should be a header row. + const [header = [], ...rows] = matrixData; + + const tableHeader = header.map((hC: string, hcIdx: number) => { + return ( + + {hC} + + ); + }); + + const tableRowsToRender = rows.map((r: any, rIdx: number) => { + return ( + + {r.map((v: any, cIdx: number) => { + let indicator = null; + + if (cIdx === 0) { + indicator = v; + } else if (v === "") { + indicator = ""; + } + if (v === true) { + indicator = ( + + + + ); + } else if (v === false) { + indicator = ( + + + + ); + } + + return ( + + {indicator} + + ); + })} + + ); + }); + + return ( + + + Replication status for {entityType}: {entityName}. + + + + {tableHeader} + + {tableRowsToRender} +
+
+ ); +}; + +export default LookupStatusTable; diff --git a/portal-ui/src/screens/Console/Configurations/SiteReplication/LookupStatus/PolicyEntityStatus.tsx b/portal-ui/src/screens/Console/Configurations/SiteReplication/LookupStatus/PolicyEntityStatus.tsx new file mode 100644 index 000000000..10ddcabc3 --- /dev/null +++ b/portal-ui/src/screens/Console/Configurations/SiteReplication/LookupStatus/PolicyEntityStatus.tsx @@ -0,0 +1,85 @@ +// 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 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 . + +import React from "react"; +import { StatsResponseType } from "../SiteReplicationStatus"; +import LookupStatusTable from "./LookupStatusTable"; +import { EntityNotFound, isEntityNotFound, syncStatus } from "./Utils"; + +type PolicyEntityStatusProps = Partial & { + lookupValue?: string; +}; +const PolicyEntityStatus = ({ + policyStats = {}, + sites = {}, + lookupValue = "", +}: PolicyEntityStatusProps) => { + const rowsForStatus = ["Policy"]; + + const policySites: Record = policyStats[lookupValue] || {}; + + if (!lookupValue) return null; + + const siteKeys = Object.keys(sites); + const notFound = isEntityNotFound(sites, policySites, "HasPolicy"); + const resultMatrix: any = []; + if (notFound) { + return ; + } else { + const row = []; + for (let sCol = 0; sCol < siteKeys.length; sCol++) { + if (sCol === 0) { + row.push(""); + } + row.push(sites[siteKeys[sCol]].name); + } + resultMatrix.push(row); + for (let fi = 0; fi < rowsForStatus.length; fi++) { + const sfRow = []; + const feature = rowsForStatus[fi]; + let sbStatus: string | boolean = ""; + + for (let si = 0; si < siteKeys.length; si++) { + const bucketSiteDeploymentId = sites[siteKeys[si]].deploymentID; + + const rSite = policySites[bucketSiteDeploymentId]; + + if (si === 0) { + sfRow.push(feature); + } + + switch (fi) { + case 0: + sbStatus = syncStatus(rSite.PolicyMismatch, rSite.HasPolicy); + sfRow.push(sbStatus); + break; + } + } + + resultMatrix.push(sfRow); + } + } + + return ( + + ); +}; + +export default PolicyEntityStatus; diff --git a/portal-ui/src/screens/Console/Configurations/SiteReplication/LookupStatus/UserEntityStatus.tsx b/portal-ui/src/screens/Console/Configurations/SiteReplication/LookupStatus/UserEntityStatus.tsx new file mode 100644 index 000000000..86430b755 --- /dev/null +++ b/portal-ui/src/screens/Console/Configurations/SiteReplication/LookupStatus/UserEntityStatus.tsx @@ -0,0 +1,91 @@ +// 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 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 . + +import React from "react"; +import { StatsResponseType } from "../SiteReplicationStatus"; +import LookupStatusTable from "./LookupStatusTable"; +import { EntityNotFound, isEntityNotFound, syncStatus } from "./Utils"; + +type PolicyEntityStatusProps = Partial & { + lookupValue?: string; +}; +const UserEntityStatus = ({ + userStats = {}, + sites = {}, + lookupValue = "", +}: PolicyEntityStatusProps) => { + const rowsForStatus = ["Info", "Policy mapping"]; + + const userSites: Record = userStats[lookupValue] || {}; + + if (!lookupValue) return null; + + const siteKeys = Object.keys(sites); + + const notFound = isEntityNotFound(sites, userSites, "HasUser"); + + const resultMatrix: any = []; + if (notFound) { + return ; + } else { + const row = []; + for (let sCol = 0; sCol < siteKeys.length; sCol++) { + if (sCol === 0) { + row.push(""); + } + row.push(sites[siteKeys[sCol]].name); + } + resultMatrix.push(row); + for (let fi = 0; fi < rowsForStatus.length; fi++) { + const sfRow = []; + const feature = rowsForStatus[fi]; + let sbStatus: string | boolean = ""; + + for (let si = 0; si < siteKeys.length; si++) { + const bucketSiteDeploymentId = sites[siteKeys[si]].deploymentID; + + const rSite = userSites[bucketSiteDeploymentId]; + + if (si === 0) { + sfRow.push(feature); + } + + switch (fi) { + case 0: + sbStatus = syncStatus(rSite.UserInfoMismatch, rSite.HasUser); + sfRow.push(sbStatus); + break; + case 1: + sbStatus = syncStatus(rSite.PolicyMismatch, rSite.HasPolicyMapping); + sfRow.push(sbStatus); + break; + } + } + + resultMatrix.push(sfRow); + } + } + + return ( + + ); +}; + +export default UserEntityStatus; diff --git a/portal-ui/src/screens/Console/Configurations/SiteReplication/LookupStatus/Utils.tsx b/portal-ui/src/screens/Console/Configurations/SiteReplication/LookupStatus/Utils.tsx new file mode 100644 index 000000000..622bc692f --- /dev/null +++ b/portal-ui/src/screens/Console/Configurations/SiteReplication/LookupStatus/Utils.tsx @@ -0,0 +1,49 @@ +import { Box } from "@mui/material"; +import React from "react"; +import { StatsResponseType } from "../SiteReplicationStatus"; + +export function syncStatus(mismatch: boolean, set: boolean): string | boolean { + if (!set) { + return ""; + } + return !mismatch; +} + +export function isEntityNotFound( + sites: Partial, + lookupList: Partial, + lookupKey: string +) { + const siteKeys: string[] = Object.keys(sites); + return siteKeys.find((sk: string) => { + // there is no way to find the type of this ! as it is an entry in the structure itself. + // @ts-ignore + const result: Record = lookupList[sk] || {}; + return !result[lookupKey]; + }); +} + +export const EntityNotFound = ({ + entityType, + entityValue, +}: { + entityType: string; + entityValue: string; +}) => { + return ( + + {entityType}:{" "} + + {entityValue} + {" "} + not found. + + ); +}; diff --git a/portal-ui/src/screens/Console/Configurations/SiteReplication/ReplicationSites.tsx b/portal-ui/src/screens/Console/Configurations/SiteReplication/ReplicationSites.tsx index 60598e94b..23371709e 100644 --- a/portal-ui/src/screens/Console/Configurations/SiteReplication/ReplicationSites.tsx +++ b/portal-ui/src/screens/Console/Configurations/SiteReplication/ReplicationSites.tsx @@ -15,15 +15,9 @@ // along with this program. If not, see . import React, { useState } from "react"; -import ListSubheader from "@mui/material/ListSubheader"; import List from "@mui/material/List"; import ListItemButton from "@mui/material/ListItemButton"; -import Collapse from "@mui/material/Collapse"; import { Box, Button, DialogContentText, Tooltip } from "@mui/material"; -import { - MenuCollapsedIcon, - MenuExpandedIcon, -} from "../../../../icons/SidebarMenus"; import { ReplicationSite } from "./SiteReplication"; import RBIconButton from "../../Buckets/BucketDetails/SummaryItems/RBIconButton"; import TrashIcon from "../../../../icons/TrashIcon"; @@ -67,16 +61,10 @@ const ReplicationSites = ({ onRefresh: () => void; classes: any; }) => { - const [expanded, setExpanded] = React.useState(""); - const [deleteSiteKey, setIsDeleteSiteKey] = useState(""); const [editSite, setEditSite] = useState(null); const [editEndPointName, setEditEndPointName] = useState(""); - const handleClick = (key: string) => { - setExpanded(key); - }; - const [isEditing, invokeSiteEditApi] = useApi( (res: any) => { if (res.success) { @@ -103,7 +91,6 @@ const ReplicationSites = ({ }); }; - const hasExpand = false; //siteInfo.isCurrent to b let isValidEndPointUrl = false; try { @@ -139,20 +126,11 @@ const ReplicationSites = ({ {sites.map((siteInfo, index) => { const key = `${siteInfo.name}`; - const isExpanded = expanded === siteInfo.name; - const handleToggle = () => { - if (!isExpanded) { - handleClick(key); - } else { - handleClick(""); - } - }; return ( - {hasExpand ? ( - - {isExpanded ? ( - - ) : ( - - )} - - ) : ( - - )} - {isExpanded ? ( - - - Replication status - - - - Status info - - - ) : null} {deleteSiteKey === key ? ( + } + onClick={(e) => { + e.preventDefault(); + history.push(IAM_PAGES.SITE_REPLICATION_STATUS); + }} + /> ) : null} . + +import React, { Fragment, useEffect, useState } from "react"; +import PageHeader from "../../Common/PageHeader/PageHeader"; +import PageLayout from "../../Common/Layout/PageLayout"; +import { Box, Grid } from "@mui/material"; +import useApi from "../../Common/Hooks/useApi"; +import BackLink from "../../../../common/BackLink"; +import { IAM_PAGES } from "../../../../common/SecureComponent/permissions"; +import ScreenTitle from "../../Common/ScreenTitle/ScreenTitle"; +import StatusCountCard from "../../Dashboard/BasicDashboard/StatusCountCard"; +import { + BucketsIcon, + GroupsIcon, + IAMPoliciesIcon, + RefreshIcon, + UsersIcon, +} from "../../../../icons"; +import RBIconButton from "../../Buckets/BucketDetails/SummaryItems/RBIconButton"; +import EntityReplicationLookup from "./EntityReplicationLookup"; +import Loader from "../../Common/Loader/Loader"; + +export type StatsResponseType = { + maxBuckets?: number; + bucketStats?: Record; + maxGroups?: number; + groupStats?: Record; + maxUsers?: number; + userStats?: Record; + maxPolicies?: number; + policyStats?: Record; + sites?: Record; +}; + +const SREntityStatus = ({ + maxValue = 0, + entityStatObj = {}, + entityTextPlural = "", + icon = null, +}: { + maxValue: number; + entityStatObj: Record; + entityTextPlural: string; + icon?: React.ReactNode; +}) => { + const statEntityLen = Object.keys(entityStatObj || {})?.length; + return ( + + + + ); +}; + +const SiteReplicationStatus = () => { + const [stats, setStats] = useState({}); + + const [isStatsLoading, invokeSiteStatsApi] = useApi( + (res: any) => { + setStats(res); + }, + (err: any) => { + setStats({}); + } + ); + + const { + maxBuckets = 0, + bucketStats = {}, + maxGroups = 0, + groupStats = {}, + maxUsers = 0, + userStats = {}, + maxPolicies = 0, + policyStats = {}, + } = stats || {}; + + const getStats = () => { + let url = `api/v1/admin/site-replication/status?buckets=true&groups=true&policies=true&users=true`; + invokeSiteStatsApi("GET", url); + }; + + useEffect(() => { + getStats(); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + return ( + + + } + /> + + + { + getStats(); + }} + tooltip={"Refresh"} + text={"Refresh"} + showLabelAlways + icon={} + color="primary" + variant={"contained"} + /> + + } + /> + + {!isStatsLoading ? ( + + } + /> + } + /> + } + /> + } + /> + + ) : ( + + + + )} + + + + + + + ); +}; + +export default SiteReplicationStatus; diff --git a/portal-ui/src/screens/Console/Console.tsx b/portal-ui/src/screens/Console/Console.tsx index 4d03ab4b6..c4849ff3e 100644 --- a/portal-ui/src/screens/Console/Console.tsx +++ b/portal-ui/src/screens/Console/Console.tsx @@ -121,6 +121,7 @@ const AddPool = React.lazy( const SiteReplication = React.lazy( () => import("./Configurations/SiteReplication/SiteReplication") ); +const SiteReplicationStatus = React.lazy(() => import("./Configurations/SiteReplication/SiteReplicationStatus")); const styles = (theme: Theme) => createStyles({ root: { @@ -373,6 +374,10 @@ const Console = ({ component: SiteReplication, path: IAM_PAGES.SITE_REPLICATION, }, + { + component:SiteReplicationStatus, + path:IAM_PAGES.SITE_REPLICATION_STATUS, + }, { component: Account, path: IAM_PAGES.ACCOUNT, diff --git a/portal-ui/src/screens/Console/Dashboard/BasicDashboard/StatusCountCard.tsx b/portal-ui/src/screens/Console/Dashboard/BasicDashboard/StatusCountCard.tsx index 4b0216159..fcf61a17c 100644 --- a/portal-ui/src/screens/Console/Dashboard/BasicDashboard/StatusCountCard.tsx +++ b/portal-ui/src/screens/Console/Dashboard/BasicDashboard/StatusCountCard.tsx @@ -23,11 +23,15 @@ export const StatusCountCard = ({ offlineCount = 0, icon = null, label = "", + okStatusText = "Online", + notOkStatusText = "Offline", }: { icon: any; onlineCount: number; offlineCount: number; label: string; + okStatusText?: string; + notOkStatusText?: string; }) => { return ( -
Online
+
{okStatusText}
@@ -130,7 +134,8 @@ export const StatusCountCard = ({ }, }} > -
Offline
+ {" "} +
{notOkStatusText}
diff --git a/portal-ui/tests/permissions-2/site-replication.ts b/portal-ui/tests/permissions-2/site-replication.ts index 359455918..5cadffa73 100644 --- a/portal-ui/tests/permissions-2/site-replication.ts +++ b/portal-ui/tests/permissions-2/site-replication.ts @@ -29,7 +29,7 @@ export const addSitesBtn = Selector("button").withText("Add Sites"); // Command to invoke the test locally: testcafe chrome tests/permissions/site-replication.ts /* End Local Testing config block */ -fixture("Site Replication for user with Admin permissions") +fixture("Site Replication Status for user with Admin permissions") .page(testDomainUrl) .beforeEach(async (t) => { await t.useRole(roles.settings); diff --git a/replication/admin_api_int_replication_test.go b/replication/admin_api_int_replication_test.go index e8b708ff8..722251bb5 100644 --- a/replication/admin_api_int_replication_test.go +++ b/replication/admin_api_int_replication_test.go @@ -203,3 +203,64 @@ func TestDeleteSiteReplicationInfo(t *testing.T) { fmt.Println("TestDeleteReplicationInfo: ", response.StatusCode) } + +// Status API + +func makeStatusExecuteReq(method string, url string) (*http.Response, error) { + + client := &http.Client{ + Timeout: 10 * time.Second, + } + + request, err := http.NewRequest( + method, + url, + nil, + ) + + if err != nil { + return nil, err + } + request.Header.Add("Cookie", fmt.Sprintf("token=%s", token)) + request.Header.Add("Content-Type", "application/json") + response, err := client.Do(request) + + if err != nil { + return nil, err + } + + return response, nil +} + +func TestGetSiteReplicationStatus(t *testing.T) { + assert := assert.New(t) + + reqUrls := [5]string{ + //default + "http://localhost:9090/api/v1/admin/site-replication/status?users=true&groups=true&buckets=true&policies=true", + //specific bucket lookup + "http://localhost:9090/api/v1/admin/site-replication/status?users=false&groups=false&buckets=false&policies=false&entityValue=test-bucket&entityType=bucket", + //specific-user lookup + "http://localhost:9090/api/v1/admin/site-replication/status?users=false&groups=false&buckets=false&policies=false&entityValue=test-user&entityType=user", + //specific-group lookup + "http://localhost:9090/api/v1/admin/site-replication/status?users=false&groups=false&buckets=false&policies=false&entityValue=test-group&entityType=group", + //specific-policy lookup + "http://localhost:9090/api/v1/admin/site-replication/status?users=false&groups=false&buckets=false&policies=false&entityValue=test-policies&entityType=policiy", + } + + for i, url := range reqUrls { + + response, err := makeStatusExecuteReq("GET", url) + + tgt := &models.SiteReplicationStatusResponse{} + json.NewDecoder(response.Body).Decode(tgt) + + if err != nil { + log.Println(err) + return + } + if response != nil { + assert.Equal(200, response.StatusCode, "Status Code for", i) + } + } +} diff --git a/restapi/admin_replication_status.go b/restapi/admin_replication_status.go new file mode 100644 index 000000000..9f48e92dc --- /dev/null +++ b/restapi/admin_replication_status.go @@ -0,0 +1,92 @@ +// 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 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 . + +package restapi + +import ( + "context" + + "github.com/go-openapi/runtime/middleware" + "github.com/minio/console/models" + "github.com/minio/console/restapi/operations" + "github.com/minio/console/restapi/operations/admin_api" + "github.com/minio/madmin-go" +) + +func registerSiteReplicationStatusHandler(api *operations.ConsoleAPI) { + + api.AdminAPIGetSiteReplicationStatusHandler = admin_api.GetSiteReplicationStatusHandlerFunc(func(params admin_api.GetSiteReplicationStatusParams, session *models.Principal) middleware.Responder { + rInfo, err := getSRStatusResponse(session, params) + if err != nil { + return admin_api.NewGetSiteReplicationStatusDefault(500).WithPayload(prepareError(err)) + } + return admin_api.NewGetSiteReplicationStatusOK().WithPayload(rInfo) + + }) +} + +func getSRStatusResponse(session *models.Principal, params admin_api.GetSiteReplicationStatusParams) (info *models.SiteReplicationStatusResponse, err error) { + + mAdmin, err := NewMinioAdminClient(session) + if err != nil { + return nil, err + } + adminClient := AdminClient{Client: mAdmin} + ctx := context.Background() + + res, err := getSRStats(ctx, adminClient, params) + + if err != nil { + return nil, err + } + return res, nil +} + +func getSRStats(ctx context.Context, client MinioAdmin, params admin_api.GetSiteReplicationStatusParams) (info *models.SiteReplicationStatusResponse, err error) { + + srParams := madmin.SRStatusOptions{ + Buckets: *params.Buckets, + Policies: *params.Policies, + Users: *params.Users, + Groups: *params.Groups, + } + if params.EntityType != nil && params.EntityValue != nil { + srParams.Entity = madmin.GetSREntityType(*params.EntityType) + srParams.EntityValue = *params.EntityValue + } + + srInfo, err := client.getSiteReplicationStatus(ctx, srParams) + + retInfo := models.SiteReplicationStatusResponse{ + BucketStats: &srInfo.BucketStats, + Enabled: srInfo.Enabled, + GroupStats: srInfo.GroupStats, + MaxBuckets: int64(srInfo.MaxBuckets), + MaxGroups: int64(srInfo.MaxGroups), + MaxPolicies: int64(srInfo.MaxPolicies), + MaxUsers: int64(srInfo.MaxUsers), + PolicyStats: &srInfo.PolicyStats, + Sites: &srInfo.Sites, + StatsSummary: srInfo.StatsSummary, + UserStats: &srInfo.UserStats, + } + + if err != nil { + return nil, err + } + return &retInfo, nil + +} diff --git a/restapi/admin_site_replication_test.go b/restapi/admin_site_replication_test.go index b7103c97d..1e2db982f 100644 --- a/restapi/admin_site_replication_test.go +++ b/restapi/admin_site_replication_test.go @@ -52,6 +52,12 @@ func (ac adminClientMock) deleteSiteReplicationInfo(ctx context.Context, removeR return deleteSiteReplicationInfoMock(ctx, removeReq) } +var getSiteReplicationStatus func(ctx context.Context, params madmin.SRStatusOptions) (*madmin.SRStatusInfo, error) + +func (ac adminClientMock) getSiteReplicationStatus(ctx context.Context, params madmin.SRStatusOptions) (*madmin.SRStatusInfo, error) { + return getSiteReplicationStatus(ctx, params) +} + func TestGetSiteReplicationInfo(t *testing.T) { assert := assert.New(t) // mock minIO client @@ -243,3 +249,60 @@ func TestDeleteSiteReplicationInfo(t *testing.T) { assert.Equal(expValueMock, srInfo, fmt.Sprintf("Failed on %s: length of lists is not the same", function)) } + +func TestSiteReplicationStatus(t *testing.T) { + assert := assert.New(t) + // mock minIO client + adminClient := adminClientMock{} + + function := "getSiteReplicationStatus()" + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + retValueMock := madmin.SRStatusInfo{ + Enabled: true, + MaxBuckets: 0, + MaxUsers: 0, + MaxGroups: 0, + MaxPolicies: 0, + Sites: nil, + StatsSummary: nil, + BucketStats: nil, + PolicyStats: nil, + UserStats: nil, + GroupStats: nil, + } + + expValueMock := &madmin.SRStatusInfo{ + Enabled: true, + MaxBuckets: 0, + MaxUsers: 0, + MaxGroups: 0, + MaxPolicies: 0, + Sites: nil, + StatsSummary: nil, + BucketStats: nil, + PolicyStats: nil, + UserStats: nil, + GroupStats: nil, + } + + getSiteReplicationStatus = func(ctx context.Context, params madmin.SRStatusOptions) (info *madmin.SRStatusInfo, err error) { + return &retValueMock, nil + } + + reqValues := madmin.SRStatusOptions{ + Buckets: true, + Policies: true, + Users: true, + Groups: true, + } + srInfo, err := adminClient.getSiteReplicationStatus(ctx, reqValues) + + if err != nil { + assert.Error(err) + } + + assert.Equal(expValueMock, srInfo, fmt.Sprintf("Failed on %s: expected result is not same", function)) + +} diff --git a/restapi/client-admin.go b/restapi/client-admin.go index f5eed17b4..72d7294f6 100644 --- a/restapi/client-admin.go +++ b/restapi/client-admin.go @@ -119,6 +119,9 @@ type MinioAdmin interface { addSiteReplicationInfo(ctx context.Context, sites []madmin.PeerSite) (*madmin.ReplicateAddStatus, error) editSiteReplicationInfo(ctx context.Context, site madmin.PeerInfo) (*madmin.ReplicateEditStatus, error) deleteSiteReplicationInfo(ctx context.Context, removeReq madmin.SRRemoveReq) (*madmin.ReplicateRemoveStatus, error) + + //Replication status + getSiteReplicationStatus(ctx context.Context, params madmin.SRStatusOptions) (*madmin.SRStatusInfo, error) } // Interface implementation @@ -547,3 +550,12 @@ func (ac AdminClient) deleteSiteReplicationInfo(ctx context.Context, removeReq m ErrDetail: res.ErrDetail, }, nil } + +func (ac AdminClient) getSiteReplicationStatus(ctx context.Context, params madmin.SRStatusOptions) (*madmin.SRStatusInfo, error) { + + res, err := ac.Client.SRStatusInfo(ctx, params) + if err != nil { + return nil, err + } + return &res, nil +} diff --git a/restapi/configure_console.go b/restapi/configure_console.go index 1182215bf..09d9e10b7 100644 --- a/restapi/configure_console.go +++ b/restapi/configure_console.go @@ -132,6 +132,7 @@ func configureAPI(api *operations.ConsoleAPI) http.Handler { registerNodesHandler(api) registerSiteReplicationHandler(api) + registerSiteReplicationStatusHandler(api) // Operator Console diff --git a/restapi/embedded_spec.go b/restapi/embedded_spec.go index 70271b598..79c979423 100644 --- a/restapi/embedded_spec.go +++ b/restapi/embedded_spec.go @@ -429,6 +429,71 @@ func init() { } } }, + "/admin/site-replication/status": { + "get": { + "tags": [ + "AdminAPI" + ], + "summary": "Display overall site replication status", + "operationId": "GetSiteReplicationStatus", + "parameters": [ + { + "type": "boolean", + "default": true, + "description": "Include Bucket stats", + "name": "buckets", + "in": "query" + }, + { + "type": "boolean", + "default": true, + "description": "Include Group stats", + "name": "groups", + "in": "query" + }, + { + "type": "boolean", + "default": true, + "description": "Include Policies stats", + "name": "policies", + "in": "query" + }, + { + "type": "boolean", + "default": true, + "description": "Include Policies stats", + "name": "users", + "in": "query" + }, + { + "type": "string", + "description": "Entity Type to lookup", + "name": "entityType", + "in": "query" + }, + { + "type": "string", + "description": "Entity Value to lookup", + "name": "entityValue", + "in": "query" + } + ], + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/siteReplicationStatusResponse" + } + }, + "default": { + "description": "Generic error response.", + "schema": { + "$ref": "#/definitions/error" + } + } + } + } + }, "/admin/tiers": { "get": { "tags": [ @@ -6311,6 +6376,44 @@ func init() { } } }, + "siteReplicationStatusResponse": { + "type": "object", + "properties": { + "bucketStats": { + "type": "object" + }, + "enabled": { + "type": "boolean" + }, + "groupStats": { + "type": "object" + }, + "maxBuckets": { + "type": "integer" + }, + "maxGroups": { + "type": "integer" + }, + "maxPolicies": { + "type": "integer" + }, + "maxUsers": { + "type": "integer" + }, + "policyStats": { + "type": "object" + }, + "sites": { + "type": "object" + }, + "statsSummary": { + "type": "object" + }, + "userStats": { + "type": "object" + } + } + }, "startProfilingItem": { "type": "object", "properties": { @@ -7250,6 +7353,71 @@ func init() { } } }, + "/admin/site-replication/status": { + "get": { + "tags": [ + "AdminAPI" + ], + "summary": "Display overall site replication status", + "operationId": "GetSiteReplicationStatus", + "parameters": [ + { + "type": "boolean", + "default": true, + "description": "Include Bucket stats", + "name": "buckets", + "in": "query" + }, + { + "type": "boolean", + "default": true, + "description": "Include Group stats", + "name": "groups", + "in": "query" + }, + { + "type": "boolean", + "default": true, + "description": "Include Policies stats", + "name": "policies", + "in": "query" + }, + { + "type": "boolean", + "default": true, + "description": "Include Policies stats", + "name": "users", + "in": "query" + }, + { + "type": "string", + "description": "Entity Type to lookup", + "name": "entityType", + "in": "query" + }, + { + "type": "string", + "description": "Entity Value to lookup", + "name": "entityValue", + "in": "query" + } + ], + "responses": { + "200": { + "description": "A successful response.", + "schema": { + "$ref": "#/definitions/siteReplicationStatusResponse" + } + }, + "default": { + "description": "Generic error response.", + "schema": { + "$ref": "#/definitions/error" + } + } + } + } + }, "/admin/tiers": { "get": { "tags": [ @@ -13258,6 +13426,44 @@ func init() { } } }, + "siteReplicationStatusResponse": { + "type": "object", + "properties": { + "bucketStats": { + "type": "object" + }, + "enabled": { + "type": "boolean" + }, + "groupStats": { + "type": "object" + }, + "maxBuckets": { + "type": "integer" + }, + "maxGroups": { + "type": "integer" + }, + "maxPolicies": { + "type": "integer" + }, + "maxUsers": { + "type": "integer" + }, + "policyStats": { + "type": "object" + }, + "sites": { + "type": "object" + }, + "statsSummary": { + "type": "object" + }, + "userStats": { + "type": "object" + } + } + }, "startProfilingItem": { "type": "object", "properties": { diff --git a/restapi/operations/admin_api/get_site_replication_status.go b/restapi/operations/admin_api/get_site_replication_status.go new file mode 100644 index 000000000..9f50916ac --- /dev/null +++ b/restapi/operations/admin_api/get_site_replication_status.go @@ -0,0 +1,88 @@ +// Code generated by go-swagger; DO NOT EDIT. + +// 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 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 . +// + +package admin_api + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the generate command + +import ( + "net/http" + + "github.com/go-openapi/runtime/middleware" + + "github.com/minio/console/models" +) + +// GetSiteReplicationStatusHandlerFunc turns a function with the right signature into a get site replication status handler +type GetSiteReplicationStatusHandlerFunc func(GetSiteReplicationStatusParams, *models.Principal) middleware.Responder + +// Handle executing the request and returning a response +func (fn GetSiteReplicationStatusHandlerFunc) Handle(params GetSiteReplicationStatusParams, principal *models.Principal) middleware.Responder { + return fn(params, principal) +} + +// GetSiteReplicationStatusHandler interface for that can handle valid get site replication status params +type GetSiteReplicationStatusHandler interface { + Handle(GetSiteReplicationStatusParams, *models.Principal) middleware.Responder +} + +// NewGetSiteReplicationStatus creates a new http.Handler for the get site replication status operation +func NewGetSiteReplicationStatus(ctx *middleware.Context, handler GetSiteReplicationStatusHandler) *GetSiteReplicationStatus { + return &GetSiteReplicationStatus{Context: ctx, Handler: handler} +} + +/* GetSiteReplicationStatus swagger:route GET /admin/site-replication/status AdminAPI getSiteReplicationStatus + +Display overall site replication status + +*/ +type GetSiteReplicationStatus struct { + Context *middleware.Context + Handler GetSiteReplicationStatusHandler +} + +func (o *GetSiteReplicationStatus) ServeHTTP(rw http.ResponseWriter, r *http.Request) { + route, rCtx, _ := o.Context.RouteInfo(r) + if rCtx != nil { + *r = *rCtx + } + var Params = NewGetSiteReplicationStatusParams() + uprinc, aCtx, err := o.Context.Authorize(r, route) + if err != nil { + o.Context.Respond(rw, r, route.Produces, route, err) + return + } + if aCtx != nil { + *r = *aCtx + } + var principal *models.Principal + if uprinc != nil { + principal = uprinc.(*models.Principal) // this is really a models.Principal, I promise + } + + if err := o.Context.BindValidRequest(r, route, &Params); err != nil { // bind params + o.Context.Respond(rw, r, route.Produces, route, err) + return + } + + res := o.Handler.Handle(Params, principal) // actually handle the request + o.Context.Respond(rw, r, route.Produces, route, res) + +} diff --git a/restapi/operations/admin_api/get_site_replication_status_parameters.go b/restapi/operations/admin_api/get_site_replication_status_parameters.go new file mode 100644 index 000000000..59b715e40 --- /dev/null +++ b/restapi/operations/admin_api/get_site_replication_status_parameters.go @@ -0,0 +1,275 @@ +// Code generated by go-swagger; DO NOT EDIT. + +// 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 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 . +// + +package admin_api + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "net/http" + + "github.com/go-openapi/errors" + "github.com/go-openapi/runtime" + "github.com/go-openapi/runtime/middleware" + "github.com/go-openapi/strfmt" + "github.com/go-openapi/swag" +) + +// NewGetSiteReplicationStatusParams creates a new GetSiteReplicationStatusParams object +// with the default values initialized. +func NewGetSiteReplicationStatusParams() GetSiteReplicationStatusParams { + + var ( + // initialize parameters with default values + + bucketsDefault = bool(true) + + groupsDefault = bool(true) + policiesDefault = bool(true) + usersDefault = bool(true) + ) + + return GetSiteReplicationStatusParams{ + Buckets: &bucketsDefault, + + Groups: &groupsDefault, + + Policies: &policiesDefault, + + Users: &usersDefault, + } +} + +// GetSiteReplicationStatusParams contains all the bound params for the get site replication status operation +// typically these are obtained from a http.Request +// +// swagger:parameters GetSiteReplicationStatus +type GetSiteReplicationStatusParams struct { + + // HTTP Request Object + HTTPRequest *http.Request `json:"-"` + + /*Include Bucket stats + In: query + Default: true + */ + Buckets *bool + /*Entity Type to lookup + In: query + */ + EntityType *string + /*Entity Value to lookup + In: query + */ + EntityValue *string + /*Include Group stats + In: query + Default: true + */ + Groups *bool + /*Include Policies stats + In: query + Default: true + */ + Policies *bool + /*Include Policies stats + In: query + Default: true + */ + Users *bool +} + +// BindRequest both binds and validates a request, it assumes that complex things implement a Validatable(strfmt.Registry) error interface +// for simple values it will use straight method calls. +// +// To ensure default values, the struct must have been initialized with NewGetSiteReplicationStatusParams() beforehand. +func (o *GetSiteReplicationStatusParams) BindRequest(r *http.Request, route *middleware.MatchedRoute) error { + var res []error + + o.HTTPRequest = r + + qs := runtime.Values(r.URL.Query()) + + qBuckets, qhkBuckets, _ := qs.GetOK("buckets") + if err := o.bindBuckets(qBuckets, qhkBuckets, route.Formats); err != nil { + res = append(res, err) + } + + qEntityType, qhkEntityType, _ := qs.GetOK("entityType") + if err := o.bindEntityType(qEntityType, qhkEntityType, route.Formats); err != nil { + res = append(res, err) + } + + qEntityValue, qhkEntityValue, _ := qs.GetOK("entityValue") + if err := o.bindEntityValue(qEntityValue, qhkEntityValue, route.Formats); err != nil { + res = append(res, err) + } + + qGroups, qhkGroups, _ := qs.GetOK("groups") + if err := o.bindGroups(qGroups, qhkGroups, route.Formats); err != nil { + res = append(res, err) + } + + qPolicies, qhkPolicies, _ := qs.GetOK("policies") + if err := o.bindPolicies(qPolicies, qhkPolicies, route.Formats); err != nil { + res = append(res, err) + } + + qUsers, qhkUsers, _ := qs.GetOK("users") + if err := o.bindUsers(qUsers, qhkUsers, route.Formats); err != nil { + res = append(res, err) + } + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +// bindBuckets binds and validates parameter Buckets from query. +func (o *GetSiteReplicationStatusParams) bindBuckets(rawData []string, hasKey bool, formats strfmt.Registry) error { + var raw string + if len(rawData) > 0 { + raw = rawData[len(rawData)-1] + } + + // Required: false + // AllowEmptyValue: false + + if raw == "" { // empty values pass all other validations + // Default values have been previously initialized by NewGetSiteReplicationStatusParams() + return nil + } + + value, err := swag.ConvertBool(raw) + if err != nil { + return errors.InvalidType("buckets", "query", "bool", raw) + } + o.Buckets = &value + + return nil +} + +// bindEntityType binds and validates parameter EntityType from query. +func (o *GetSiteReplicationStatusParams) bindEntityType(rawData []string, hasKey bool, formats strfmt.Registry) error { + var raw string + if len(rawData) > 0 { + raw = rawData[len(rawData)-1] + } + + // Required: false + // AllowEmptyValue: false + + if raw == "" { // empty values pass all other validations + return nil + } + o.EntityType = &raw + + return nil +} + +// bindEntityValue binds and validates parameter EntityValue from query. +func (o *GetSiteReplicationStatusParams) bindEntityValue(rawData []string, hasKey bool, formats strfmt.Registry) error { + var raw string + if len(rawData) > 0 { + raw = rawData[len(rawData)-1] + } + + // Required: false + // AllowEmptyValue: false + + if raw == "" { // empty values pass all other validations + return nil + } + o.EntityValue = &raw + + return nil +} + +// bindGroups binds and validates parameter Groups from query. +func (o *GetSiteReplicationStatusParams) bindGroups(rawData []string, hasKey bool, formats strfmt.Registry) error { + var raw string + if len(rawData) > 0 { + raw = rawData[len(rawData)-1] + } + + // Required: false + // AllowEmptyValue: false + + if raw == "" { // empty values pass all other validations + // Default values have been previously initialized by NewGetSiteReplicationStatusParams() + return nil + } + + value, err := swag.ConvertBool(raw) + if err != nil { + return errors.InvalidType("groups", "query", "bool", raw) + } + o.Groups = &value + + return nil +} + +// bindPolicies binds and validates parameter Policies from query. +func (o *GetSiteReplicationStatusParams) bindPolicies(rawData []string, hasKey bool, formats strfmt.Registry) error { + var raw string + if len(rawData) > 0 { + raw = rawData[len(rawData)-1] + } + + // Required: false + // AllowEmptyValue: false + + if raw == "" { // empty values pass all other validations + // Default values have been previously initialized by NewGetSiteReplicationStatusParams() + return nil + } + + value, err := swag.ConvertBool(raw) + if err != nil { + return errors.InvalidType("policies", "query", "bool", raw) + } + o.Policies = &value + + return nil +} + +// bindUsers binds and validates parameter Users from query. +func (o *GetSiteReplicationStatusParams) bindUsers(rawData []string, hasKey bool, formats strfmt.Registry) error { + var raw string + if len(rawData) > 0 { + raw = rawData[len(rawData)-1] + } + + // Required: false + // AllowEmptyValue: false + + if raw == "" { // empty values pass all other validations + // Default values have been previously initialized by NewGetSiteReplicationStatusParams() + return nil + } + + value, err := swag.ConvertBool(raw) + if err != nil { + return errors.InvalidType("users", "query", "bool", raw) + } + o.Users = &value + + return nil +} diff --git a/restapi/operations/admin_api/get_site_replication_status_responses.go b/restapi/operations/admin_api/get_site_replication_status_responses.go new file mode 100644 index 000000000..b0cfcb7e5 --- /dev/null +++ b/restapi/operations/admin_api/get_site_replication_status_responses.go @@ -0,0 +1,133 @@ +// Code generated by go-swagger; DO NOT EDIT. + +// 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 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 . +// + +package admin_api + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "net/http" + + "github.com/go-openapi/runtime" + + "github.com/minio/console/models" +) + +// GetSiteReplicationStatusOKCode is the HTTP code returned for type GetSiteReplicationStatusOK +const GetSiteReplicationStatusOKCode int = 200 + +/*GetSiteReplicationStatusOK A successful response. + +swagger:response getSiteReplicationStatusOK +*/ +type GetSiteReplicationStatusOK struct { + + /* + In: Body + */ + Payload *models.SiteReplicationStatusResponse `json:"body,omitempty"` +} + +// NewGetSiteReplicationStatusOK creates GetSiteReplicationStatusOK with default headers values +func NewGetSiteReplicationStatusOK() *GetSiteReplicationStatusOK { + + return &GetSiteReplicationStatusOK{} +} + +// WithPayload adds the payload to the get site replication status o k response +func (o *GetSiteReplicationStatusOK) WithPayload(payload *models.SiteReplicationStatusResponse) *GetSiteReplicationStatusOK { + o.Payload = payload + return o +} + +// SetPayload sets the payload to the get site replication status o k response +func (o *GetSiteReplicationStatusOK) SetPayload(payload *models.SiteReplicationStatusResponse) { + o.Payload = payload +} + +// WriteResponse to the client +func (o *GetSiteReplicationStatusOK) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) { + + rw.WriteHeader(200) + if o.Payload != nil { + payload := o.Payload + if err := producer.Produce(rw, payload); err != nil { + panic(err) // let the recovery middleware deal with this + } + } +} + +/*GetSiteReplicationStatusDefault Generic error response. + +swagger:response getSiteReplicationStatusDefault +*/ +type GetSiteReplicationStatusDefault struct { + _statusCode int + + /* + In: Body + */ + Payload *models.Error `json:"body,omitempty"` +} + +// NewGetSiteReplicationStatusDefault creates GetSiteReplicationStatusDefault with default headers values +func NewGetSiteReplicationStatusDefault(code int) *GetSiteReplicationStatusDefault { + if code <= 0 { + code = 500 + } + + return &GetSiteReplicationStatusDefault{ + _statusCode: code, + } +} + +// WithStatusCode adds the status to the get site replication status default response +func (o *GetSiteReplicationStatusDefault) WithStatusCode(code int) *GetSiteReplicationStatusDefault { + o._statusCode = code + return o +} + +// SetStatusCode sets the status to the get site replication status default response +func (o *GetSiteReplicationStatusDefault) SetStatusCode(code int) { + o._statusCode = code +} + +// WithPayload adds the payload to the get site replication status default response +func (o *GetSiteReplicationStatusDefault) WithPayload(payload *models.Error) *GetSiteReplicationStatusDefault { + o.Payload = payload + return o +} + +// SetPayload sets the payload to the get site replication status default response +func (o *GetSiteReplicationStatusDefault) SetPayload(payload *models.Error) { + o.Payload = payload +} + +// WriteResponse to the client +func (o *GetSiteReplicationStatusDefault) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) { + + rw.WriteHeader(o._statusCode) + if o.Payload != nil { + payload := o.Payload + if err := producer.Produce(rw, payload); err != nil { + panic(err) // let the recovery middleware deal with this + } + } +} diff --git a/restapi/operations/admin_api/get_site_replication_status_urlbuilder.go b/restapi/operations/admin_api/get_site_replication_status_urlbuilder.go new file mode 100644 index 000000000..6c74d123c --- /dev/null +++ b/restapi/operations/admin_api/get_site_replication_status_urlbuilder.go @@ -0,0 +1,167 @@ +// Code generated by go-swagger; DO NOT EDIT. + +// 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 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 . +// + +package admin_api + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the generate command + +import ( + "errors" + "net/url" + golangswaggerpaths "path" + + "github.com/go-openapi/swag" +) + +// GetSiteReplicationStatusURL generates an URL for the get site replication status operation +type GetSiteReplicationStatusURL struct { + Buckets *bool + EntityType *string + EntityValue *string + Groups *bool + Policies *bool + Users *bool + + _basePath string + // avoid unkeyed usage + _ struct{} +} + +// WithBasePath sets the base path for this url builder, only required when it's different from the +// base path specified in the swagger spec. +// When the value of the base path is an empty string +func (o *GetSiteReplicationStatusURL) WithBasePath(bp string) *GetSiteReplicationStatusURL { + o.SetBasePath(bp) + return o +} + +// SetBasePath sets the base path for this url builder, only required when it's different from the +// base path specified in the swagger spec. +// When the value of the base path is an empty string +func (o *GetSiteReplicationStatusURL) SetBasePath(bp string) { + o._basePath = bp +} + +// Build a url path and query string +func (o *GetSiteReplicationStatusURL) Build() (*url.URL, error) { + var _result url.URL + + var _path = "/admin/site-replication/status" + + _basePath := o._basePath + if _basePath == "" { + _basePath = "/api/v1" + } + _result.Path = golangswaggerpaths.Join(_basePath, _path) + + qs := make(url.Values) + + var bucketsQ string + if o.Buckets != nil { + bucketsQ = swag.FormatBool(*o.Buckets) + } + if bucketsQ != "" { + qs.Set("buckets", bucketsQ) + } + + var entityTypeQ string + if o.EntityType != nil { + entityTypeQ = *o.EntityType + } + if entityTypeQ != "" { + qs.Set("entityType", entityTypeQ) + } + + var entityValueQ string + if o.EntityValue != nil { + entityValueQ = *o.EntityValue + } + if entityValueQ != "" { + qs.Set("entityValue", entityValueQ) + } + + var groupsQ string + if o.Groups != nil { + groupsQ = swag.FormatBool(*o.Groups) + } + if groupsQ != "" { + qs.Set("groups", groupsQ) + } + + var policiesQ string + if o.Policies != nil { + policiesQ = swag.FormatBool(*o.Policies) + } + if policiesQ != "" { + qs.Set("policies", policiesQ) + } + + var usersQ string + if o.Users != nil { + usersQ = swag.FormatBool(*o.Users) + } + if usersQ != "" { + qs.Set("users", usersQ) + } + + _result.RawQuery = qs.Encode() + + return &_result, nil +} + +// Must is a helper function to panic when the url builder returns an error +func (o *GetSiteReplicationStatusURL) Must(u *url.URL, err error) *url.URL { + if err != nil { + panic(err) + } + if u == nil { + panic("url can't be nil") + } + return u +} + +// String returns the string representation of the path with query string +func (o *GetSiteReplicationStatusURL) String() string { + return o.Must(o.Build()).String() +} + +// BuildFull builds a full url with scheme, host, path and query string +func (o *GetSiteReplicationStatusURL) BuildFull(scheme, host string) (*url.URL, error) { + if scheme == "" { + return nil, errors.New("scheme is required for a full url on GetSiteReplicationStatusURL") + } + if host == "" { + return nil, errors.New("host is required for a full url on GetSiteReplicationStatusURL") + } + + base, err := o.Build() + if err != nil { + return nil, err + } + + base.Scheme = scheme + base.Host = host + return base, nil +} + +// StringFull returns the string representation of a complete url +func (o *GetSiteReplicationStatusURL) StringFull(scheme, host string) string { + return o.Must(o.BuildFull(scheme, host)).String() +} diff --git a/restapi/operations/console_api.go b/restapi/operations/console_api.go index 4b8f1e9cb..9d89a2add 100644 --- a/restapi/operations/console_api.go +++ b/restapi/operations/console_api.go @@ -225,6 +225,9 @@ func NewConsoleAPI(spec *loads.Document) *ConsoleAPI { AdminAPIGetSiteReplicationInfoHandler: admin_api.GetSiteReplicationInfoHandlerFunc(func(params admin_api.GetSiteReplicationInfoParams, principal *models.Principal) middleware.Responder { return middleware.NotImplemented("operation admin_api.GetSiteReplicationInfo has not yet been implemented") }), + AdminAPIGetSiteReplicationStatusHandler: admin_api.GetSiteReplicationStatusHandlerFunc(func(params admin_api.GetSiteReplicationStatusParams, principal *models.Principal) middleware.Responder { + return middleware.NotImplemented("operation admin_api.GetSiteReplicationStatus has not yet been implemented") + }), AdminAPIGetTierHandler: admin_api.GetTierHandlerFunc(func(params admin_api.GetTierParams, principal *models.Principal) middleware.Responder { return middleware.NotImplemented("operation admin_api.GetTier has not yet been implemented") }), @@ -591,6 +594,8 @@ type ConsoleAPI struct { UserAPIGetServiceAccountPolicyHandler user_api.GetServiceAccountPolicyHandler // AdminAPIGetSiteReplicationInfoHandler sets the operation handler for the get site replication info operation AdminAPIGetSiteReplicationInfoHandler admin_api.GetSiteReplicationInfoHandler + // AdminAPIGetSiteReplicationStatusHandler sets the operation handler for the get site replication status operation + AdminAPIGetSiteReplicationStatusHandler admin_api.GetSiteReplicationStatusHandler // AdminAPIGetTierHandler sets the operation handler for the get tier operation AdminAPIGetTierHandler admin_api.GetTierHandler // AdminAPIGetUserInfoHandler sets the operation handler for the get user info operation @@ -973,6 +978,9 @@ func (o *ConsoleAPI) Validate() error { if o.AdminAPIGetSiteReplicationInfoHandler == nil { unregistered = append(unregistered, "admin_api.GetSiteReplicationInfoHandler") } + if o.AdminAPIGetSiteReplicationStatusHandler == nil { + unregistered = append(unregistered, "admin_api.GetSiteReplicationStatusHandler") + } if o.AdminAPIGetTierHandler == nil { unregistered = append(unregistered, "admin_api.GetTierHandler") } @@ -1492,6 +1500,10 @@ func (o *ConsoleAPI) initHandlerCache() { if o.handlers["GET"] == nil { o.handlers["GET"] = make(map[string]http.Handler) } + o.handlers["GET"]["/admin/site-replication/status"] = admin_api.NewGetSiteReplicationStatus(o.context, o.AdminAPIGetSiteReplicationStatusHandler) + if o.handlers["GET"] == nil { + o.handlers["GET"] = make(map[string]http.Handler) + } o.handlers["GET"]["/admin/tiers/{type}/{name}"] = admin_api.NewGetTier(o.context, o.AdminAPIGetTierHandler) if o.handlers["GET"] == nil { o.handlers["GET"] = make(map[string]http.Handler) diff --git a/swagger-console.yml b/swagger-console.yml index e9f6f1178..d352e092c 100644 --- a/swagger-console.yml +++ b/swagger-console.yml @@ -2504,6 +2504,52 @@ paths: tags: - AdminAPI + /admin/site-replication/status: + get: + summary: Display overall site replication status + operationId: GetSiteReplicationStatus + parameters: + - name: buckets + description: Include Bucket stats + in: query + type: boolean + default: true + - name: groups + description: Include Group stats + in: query + type: boolean + default: true + - name: policies + description: Include Policies stats + in: query + type: boolean + default: true + - name: users + description: Include Policies stats + in: query + type: boolean + default: true + - name: entityType + description: Entity Type to lookup + in: query + type: string + required: false + - name: entityValue + description: Entity Value to lookup + in: query + type: string + required: false + responses: + 200: + description: A successful response. + schema: + $ref: "#/definitions/siteReplicationStatusResponse" + default: + description: Generic error response. + schema: + $ref: "#/definitions/error" + tags: + - AdminAPI /admin/tiers: get: @@ -3923,6 +3969,32 @@ definitions: serviceAccountAccessKey: type: string + siteReplicationStatusResponse: + type: object + properties: + enabled: + type: boolean + maxBuckets: + type: integer + maxUsers: + type: integer + maxGroups: + type: integer + maxPolicies: + type: integer + sites: + type: object + statsSummary: + type: object + bucketStats: + type: object + policyStats: + type: object + userStats: + type: object + groupStats: + type: object + updateUser: type: object required: