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: