Deprecated Lifecycle and Tiering UI (#3470)

Signed-off-by: Benjamin Perez <benjamin@bexsoft.net>
This commit is contained in:
Alex
2024-11-11 17:36:14 -06:00
committed by GitHub
parent 4e5dcf0fc3
commit 2ca484c691
107 changed files with 18 additions and 16906 deletions

View File

@@ -1,112 +0,0 @@
// This file is part of MinIO Console Server
// Copyright (c) 2023 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
import { expect, Page } from "@playwright/test";
import { test as baseTest } from "./fixtures/baseFixture";
import { minioadminFile } from "./consts";
import { BucketsListPage } from "./pom/BucketsListPage";
import { CreateBucketPage } from "./pom/CreateBucketPage";
import { BucketSummaryPage } from "./pom/BucketSummaryPage";
type LifeCycleObjectVersionFx = {
activeBucketName: string;
bucketsListPage: BucketsListPage;
createBucketPage: CreateBucketPage;
bucketSummaryPage: any;
};
const test = baseTest.extend<LifeCycleObjectVersionFx>({
activeBucketName: "",
bucketListPage: async ({ page }: { page: Page }, use: any) => {
let bucketListPage = new BucketsListPage(page);
await bucketListPage.loadPage();
await bucketListPage.goToCreateBucket();
await use(bucketListPage);
},
createBucketPage: async ({ page }: { page: Page }, use: any) => {
let createBucketPage = new CreateBucketPage(page);
await use(createBucketPage);
},
//bucket name is dynamic in parallel test runs.
bucketSummaryPage: async ({ page }: { page: Page }, use: any) => {
await use((bucketName: string) => {
return new BucketSummaryPage(page, bucketName);
});
},
});
test.use({ storageState: minioadminFile });
const versionedBucketName = "versioned-bucket";
const nonVersionedBucketName = "non-versioned-bucket";
test.describe("Add Lifecycle Rule Modal in bucket settings tests for object version ", () => {
test("Test if Object Version selector is present in Lifecycle rule modal", async ({
page,
bucketListPage,
createBucketPage,
bucketSummaryPage,
}) => {
await test.step("Create Versioned Bucket", async () => {
await createBucketPage.createVersionedBucket(versionedBucketName);
await bucketListPage.clickOnBucketRow(versionedBucketName);
bucketSummaryPage = bucketSummaryPage(versionedBucketName);
await bucketSummaryPage.clickOnTab("lifecycle"); //Tab Text is used.
});
await test.step("Check if object version option is available on a versioned bucket", async () => {
const objectVersionsEl = await bucketSummaryPage.getObjectVersionOption();
await expect(await objectVersionsEl).toHaveText("Current Version");
await expect(await objectVersionsEl).toBeTruthy();
await bucketSummaryPage.getLocator("#close").click();
});
await test.step("Clean up bucket and verify the clean up", async () => {
await bucketSummaryPage.confirmDeleteBucket();
const existBukCount =
await bucketListPage.isBucketExist(versionedBucketName);
await expect(existBukCount).toEqual(0);
});
});
test("Test if Object Version selector is NOT present in Lifecycle rule modal", async ({
page,
createBucketPage,
bucketListPage,
bucketSummaryPage,
}) => {
await test.step("Create NON Versioned Bucket and navigate to lifecycle settings in summary page", async () => {
await createBucketPage.createBucket(nonVersionedBucketName);
await bucketListPage.clickOnBucketRow(nonVersionedBucketName);
bucketSummaryPage = bucketSummaryPage(versionedBucketName);
await bucketSummaryPage.clickOnTab("lifecycle");
});
await test.step("Check if object version option is NOT available on a non versioned bucket", async () => {
const objectVersionsEl = await bucketSummaryPage.getObjectVersionOption();
await expect(await objectVersionsEl.count()).toEqual(0);
await bucketSummaryPage.getLocator("#close").click();
});
await test.step("Clean up bucket and verify the clean up", async () => {
await bucketSummaryPage.confirmDeleteBucket();
const existBukCount = await bucketListPage.isBucketExist(
nonVersionedBucketName,
);
await expect(existBukCount).toEqual(0);
});
});
});

View File

@@ -1,45 +0,0 @@
import { Page, Locator } from "@playwright/test";
export class BucketSummaryPage {
page: Page;
bucketName: string;
/* Locators */
deleteBucketBtn: Locator | undefined;
constructor(page: Page, bucketName: string) {
this.page = page;
this.bucketName = bucketName;
this.initLocators();
}
getLocator(selector: string): Locator {
const page = this.page;
const locator: Locator = page.locator(`${selector}`);
return locator;
}
initLocators() {
this.deleteBucketBtn = this.getLocator("#delete-bucket-button");
}
async loadPage() {
await this.clickOnTab(`Summary`);
}
async clickOnTab(tabID: string) {
await this.getLocator(`#${tabID}`).click();
// await page.goto(`${BUCKET_LIST_PAGE}/${this.bucketName}/admin/${tabName}`);
}
async confirmDeleteBucket() {
await this.getLocator("#delete-bucket-button").click();
await this.getLocator("#confirm-ok").click();
}
async getObjectVersionOption() {
await this.page.getByRole("button", { name: "Add Lifecycle Rule" }).click();
return this.getLocator("#object_version-select > div").nth(0);
}
}

View File

@@ -1,58 +0,0 @@
import { Page, Locator } from "@playwright/test";
import { BUCKET_LIST_PAGE } from "../consts";
export class BucketsListPage {
page: Page;
/* Locators */
createBucketBtn: Locator | undefined;
refreshBucketsBtn: Locator | undefined;
setReplicationBtn: Locator | undefined;
bucketListItemPrefix = "#manageBucket-";
constructor(page: Page) {
this.page = page;
this.initLocators();
}
getLocator(selector: string): Locator {
const page = this.page;
const locator: Locator = page.locator(`${selector}`);
return locator;
}
initLocators() {
this.createBucketBtn = this.getLocator("#create-bucket");
this.refreshBucketsBtn = this.getLocator("#refresh-buckets");
this.setReplicationBtn = this.getLocator("#set-replication");
}
locateBucket(bucketName: string): Locator {
const bucketRow = this.getLocator(
`${this.bucketListItemPrefix}${bucketName}`,
);
return bucketRow;
}
async clickOnBucketRow(bucketName: string) {
const bucketRow = this.locateBucket(bucketName);
await this.page.waitForTimeout(2500);
await this.refreshBucketsBtn.click();
await bucketRow.click();
}
async goToCreateBucket() {
await this.createBucketBtn?.click();
}
async isBucketExist(bucketName: string) {
const existBukCount = await this.locateBucket(bucketName).count();
return existBukCount;
}
async loadPage() {
const page = this.page;
await page.goto(BUCKET_LIST_PAGE);
}
}

View File

@@ -1,114 +0,0 @@
import { Page, Locator } from "@playwright/test";
import { BUCKET_LIST_PAGE } from "../consts";
export class CreateBucketPage {
page: Page;
/* Locators */
submitBtn: Locator | undefined;
clearBtn: Locator | undefined;
bucketNameInput: Locator | undefined;
versioningToggle: Locator | undefined;
lockingToggle: Locator | undefined;
quotaToggle: Locator | undefined;
bucketNamingRules: Locator | undefined;
bucketRetentionToggle: Locator | undefined;
quotaSizeInput: Locator | undefined;
retentionModeRadio: Locator | undefined;
retentionValidity: Locator | undefined;
constructor(page: Page) {
this.page = page;
this.initLocators();
}
getLocator(selector: string): Locator {
const page = this.page;
const locator: Locator = page.locator(`${selector}`);
return locator;
}
initLocators() {
this.submitBtn = this.getLocator("#create-bucket");
this.clearBtn = this.getLocator("#clear");
this.versioningToggle = this.getLocator("#versioned-switch");
this.lockingToggle = this.getLocator("#locking-switch");
this.quotaToggle = this.getLocator("#bucket_quota-switch");
this.bucketNamingRules = this.getLocator("#toggle-naming-rules");
this.bucketNameInput = this.getLocator("#bucket-name");
}
//Lazy/Conditional selectors Note: These respective methods must be called before using them.
onVersioningToggleOn() {
this.bucketRetentionToggle = this.getLocator("#bucket_retention");
}
onBucketQuotaToggleOn() {
this.quotaSizeInput = this.getLocator("#quota_size");
}
onRetentionToggleOn() {
this.retentionModeRadio = this.getLocator("#retention_mode");
this.retentionValidity = this.getLocator("#retention_validity");
}
loadPage() {
const page = this.page;
page.goto(BUCKET_LIST_PAGE);
}
async fillBucketName(bucketName: string) {
await this.bucketNameInput?.click();
await this.bucketNameInput?.fill(bucketName);
}
async toggleBucketNamingRules() {
await this.bucketNamingRules?.click();
}
async toggleVersioning() {
await this.versioningToggle?.check();
this.onVersioningToggleOn();
//expect to be on
}
async toggleObjectLocking() {
await this.lockingToggle?.click();
this.onVersioningToggleOn();
this.onRetentionToggleOn();
}
async toggleBucketQuota() {
await this.quotaToggle?.click();
this.onBucketQuotaToggleOn();
}
async toggleRetention() {
await this.bucketRetentionToggle?.click();
}
async submitForm() {
await this.submitBtn?.click();
}
//Convenience Methods for easy testing
//create a bucket without any features like versioning, locking, quota etc.
async createBucket(bucketName: string) {
await this.fillBucketName(bucketName);
await this.submitForm();
}
//create a bucket with versioning feature
async createVersionedBucket(bucketName: string) {
await this.fillBucketName(bucketName);
await this.toggleVersioning();
await this.submitForm();
}
//create a bucket with locking feature
async goToCreateBucket() {
await this.submitBtn?.click();
}
}

View File

@@ -894,189 +894,6 @@ export interface GetBucketRetentionConfig {
validity?: number;
}
export interface BucketLifecycleResponse {
lifecycle?: ObjectBucketLifecycle[];
}
export interface ExpirationResponse {
date?: string;
/** @format int64 */
days?: number;
delete_marker?: boolean;
delete_all?: boolean;
/** @format int64 */
noncurrent_expiration_days?: number;
/** @format int64 */
newer_noncurrent_expiration_versions?: number;
}
export interface TransitionResponse {
date?: string;
storage_class?: string;
/** @format int64 */
days?: number;
/** @format int64 */
noncurrent_transition_days?: number;
noncurrent_storage_class?: string;
}
export interface LifecycleTag {
key?: string;
value?: string;
}
export interface ObjectBucketLifecycle {
id?: string;
prefix?: string;
status?: string;
expiration?: ExpirationResponse;
transition?: TransitionResponse;
tags?: LifecycleTag[];
}
export interface AddBucketLifecycle {
/** ILM Rule type (Expiry or transition) */
type?: "expiry" | "transition";
/** Non required field, it matches a prefix to perform ILM operations on it */
prefix?: string;
/** Non required field, tags to match ILM files */
tags?: string;
/**
* Required in case of expiry_date or transition fields are not set. it defines an expiry days for ILM
* @format int32
* @default 0
*/
expiry_days?: number;
/**
* Required in case of transition_date or expiry fields are not set. it defines a transition days for ILM
* @format int32
* @default 0
*/
transition_days?: number;
/** Required only in case of transition is set. it refers to a tier */
storage_class?: string;
/** Non required, toggle to disable or enable rule */
disable?: boolean;
/** Non required, toggle to disable or enable rule */
expired_object_delete_marker?: boolean;
/** Non required, toggle to disable or enable rule */
expired_object_delete_all?: boolean;
/**
* Non required, can be set in case of expiration is enabled
* @format int32
* @default 0
*/
noncurrentversion_expiration_days?: number;
/**
* Non required, can be set in case of transition is enabled
* @format int32
* @default 0
*/
noncurrentversion_transition_days?: number;
/**
* Non required, can be set in case of expiration is enabled
* @format int32
* @default 0
*/
newer_noncurrentversion_expiration_versions?: number;
/** Non required, can be set in case of transition is enabled */
noncurrentversion_transition_storage_class?: string;
}
export interface UpdateBucketLifecycle {
/** ILM Rule type (Expiry or transition) */
type: "expiry" | "transition";
/** Non required field, it matches a prefix to perform ILM operations on it */
prefix?: string;
/** Non required field, tags to match ILM files */
tags?: string;
/**
* Required in case of expiry_date or transition fields are not set. it defines an expiry days for ILM
* @format int32
* @default 0
*/
expiry_days?: number;
/**
* Required in case of transition_date or expiry fields are not set. it defines a transition days for ILM
* @format int32
* @default 0
*/
transition_days?: number;
/** Required only in case of transition is set. it refers to a tier */
storage_class?: string;
/** Non required, toggle to disable or enable rule */
disable?: boolean;
/** Non required, toggle to disable or enable rule */
expired_object_delete_marker?: boolean;
/** Non required, toggle to disable or enable rule */
expired_object_delete_all?: boolean;
/**
* Non required, can be set in case of expiration is enabled
* @format int32
* @default 0
*/
noncurrentversion_expiration_days?: number;
/**
* Non required, can be set in case of transition is enabled
* @format int32
* @default 0
*/
noncurrentversion_transition_days?: number;
/** Non required, can be set in case of transition is enabled */
noncurrentversion_transition_storage_class?: string;
}
export interface AddMultiBucketLifecycle {
buckets: string[];
/** ILM Rule type (Expiry or transition) */
type: "expiry" | "transition";
/** Non required field, it matches a prefix to perform ILM operations on it */
prefix?: string;
/** Non required field, tags to match ILM files */
tags?: string;
/**
* Required in case of expiry_date or transition fields are not set. it defines an expiry days for ILM
* @format int32
* @default 0
*/
expiry_days?: number;
/**
* Required in case of transition_date or expiry fields are not set. it defines a transition days for ILM
* @format int32
* @default 0
*/
transition_days?: number;
/** Required only in case of transition is set. it refers to a tier */
storage_class?: string;
/** Non required, toggle to disable or enable rule */
expired_object_delete_marker?: boolean;
/** Non required, toggle to disable or enable rule */
expired_object_delete_all?: boolean;
/**
* Non required, can be set in case of expiration is enabled
* @format int32
* @default 0
*/
noncurrentversion_expiration_days?: number;
/**
* Non required, can be set in case of transition is enabled
* @format int32
* @default 0
*/
noncurrentversion_transition_days?: number;
/** Non required, can be set in case of transition is enabled */
noncurrentversion_transition_storage_class?: string;
}
export interface MulticycleResultItem {
bucketName?: string;
error?: string;
}
export interface MultiLifecycleResult {
results?: MulticycleResultItem[];
}
export interface PrefixAccessPair {
prefix?: string;
access?: string;
@@ -1116,59 +933,6 @@ export interface PolicyArgs {
bucket_name?: string;
}
export interface TierS3 {
name?: string;
endpoint?: string;
accesskey?: string;
secretkey?: string;
bucket?: string;
prefix?: string;
region?: string;
storageclass?: string;
usage?: string;
objects?: string;
versions?: string;
}
export interface TierMinio {
name?: string;
endpoint?: string;
accesskey?: string;
secretkey?: string;
bucket?: string;
prefix?: string;
region?: string;
storageclass?: string;
usage?: string;
objects?: string;
versions?: string;
}
export interface TierAzure {
name?: string;
endpoint?: string;
accountname?: string;
accountkey?: string;
bucket?: string;
prefix?: string;
region?: string;
usage?: string;
objects?: string;
versions?: string;
}
export interface TierGcs {
name?: string;
endpoint?: string;
creds?: string;
bucket?: string;
prefix?: string;
region?: string;
usage?: string;
objects?: string;
versions?: string;
}
export interface DeleteFile {
path?: string;
versionID?: string;
@@ -1181,30 +945,6 @@ export interface UserSAs {
recursive?: boolean;
}
export interface Tier {
status?: boolean;
type?: "s3" | "gcs" | "azure" | "minio" | "unsupported";
s3?: TierS3;
gcs?: TierGcs;
azure?: TierAzure;
minio?: TierMinio;
}
export interface TierListResponse {
items?: Tier[];
}
export interface TiersNameListResponse {
items?: string[];
}
export interface TierCredentialsRequest {
access_key?: string;
secret_key?: string;
/** a base64 encoded value */
creds?: string;
}
export interface RewindItem {
last_modified?: string;
/** @format int64 */
@@ -2729,115 +2469,6 @@ export class Api<
...params,
}),
/**
* No description
*
* @tags Bucket
* @name GetBucketLifecycle
* @summary Bucket Lifecycle
* @request GET:/buckets/{bucket_name}/lifecycle
* @secure
*/
getBucketLifecycle: (bucketName: string, params: RequestParams = {}) =>
this.request<BucketLifecycleResponse, ApiError>({
path: `/buckets/${encodeURIComponent(bucketName)}/lifecycle`,
method: "GET",
secure: true,
format: "json",
...params,
}),
/**
* No description
*
* @tags Bucket
* @name AddBucketLifecycle
* @summary Add Bucket Lifecycle
* @request POST:/buckets/{bucket_name}/lifecycle
* @secure
*/
addBucketLifecycle: (
bucketName: string,
body: AddBucketLifecycle,
params: RequestParams = {},
) =>
this.request<void, ApiError>({
path: `/buckets/${encodeURIComponent(bucketName)}/lifecycle`,
method: "POST",
body: body,
secure: true,
type: ContentType.Json,
...params,
}),
/**
* No description
*
* @tags Bucket
* @name AddMultiBucketLifecycle
* @summary Add Multi Bucket Lifecycle
* @request POST:/buckets/multi-lifecycle
* @secure
*/
addMultiBucketLifecycle: (
body: AddMultiBucketLifecycle,
params: RequestParams = {},
) =>
this.request<MultiLifecycleResult, ApiError>({
path: `/buckets/multi-lifecycle`,
method: "POST",
body: body,
secure: true,
type: ContentType.Json,
format: "json",
...params,
}),
/**
* No description
*
* @tags Bucket
* @name UpdateBucketLifecycle
* @summary Update Lifecycle rule
* @request PUT:/buckets/{bucket_name}/lifecycle/{lifecycle_id}
* @secure
*/
updateBucketLifecycle: (
bucketName: string,
lifecycleId: string,
body: UpdateBucketLifecycle,
params: RequestParams = {},
) =>
this.request<void, ApiError>({
path: `/buckets/${encodeURIComponent(bucketName)}/lifecycle/${encodeURIComponent(lifecycleId)}`,
method: "PUT",
body: body,
secure: true,
type: ContentType.Json,
...params,
}),
/**
* No description
*
* @tags Bucket
* @name DeleteBucketLifecycleRule
* @summary Delete Lifecycle rule
* @request DELETE:/buckets/{bucket_name}/lifecycle/{lifecycle_id}
* @secure
*/
deleteBucketLifecycleRule: (
bucketName: string,
lifecycleId: string,
params: RequestParams = {},
) =>
this.request<void, ApiError>({
path: `/buckets/${encodeURIComponent(bucketName)}/lifecycle/${encodeURIComponent(lifecycleId)}`,
method: "DELETE",
secure: true,
...params,
}),
/**
* No description
*
@@ -4073,124 +3704,6 @@ export class Api<
...params,
}),
/**
* No description
*
* @tags Tiering
* @name TiersList
* @summary Returns a list of tiers for ilm
* @request GET:/admin/tiers
* @secure
*/
tiersList: (params: RequestParams = {}) =>
this.request<TierListResponse, ApiError>({
path: `/admin/tiers`,
method: "GET",
secure: true,
format: "json",
...params,
}),
/**
* No description
*
* @tags Tiering
* @name AddTier
* @summary Allows to configure a new tier
* @request POST:/admin/tiers
* @secure
*/
addTier: (body: Tier, params: RequestParams = {}) =>
this.request<void, ApiError>({
path: `/admin/tiers`,
method: "POST",
body: body,
secure: true,
type: ContentType.Json,
...params,
}),
/**
* No description
*
* @tags Tiering
* @name TiersListNames
* @summary Returns a list of tiers' names for ilm
* @request GET:/admin/tiers/names
* @secure
*/
tiersListNames: (params: RequestParams = {}) =>
this.request<TiersNameListResponse, ApiError>({
path: `/admin/tiers/names`,
method: "GET",
secure: true,
format: "json",
...params,
}),
/**
* No description
*
* @tags Tiering
* @name GetTier
* @summary Get Tier
* @request GET:/admin/tiers/{type}/{name}
* @secure
*/
getTier: (
type: "s3" | "gcs" | "azure" | "minio",
name: string,
params: RequestParams = {},
) =>
this.request<Tier, ApiError>({
path: `/admin/tiers/${encodeURIComponent(type)}/${encodeURIComponent(name)}`,
method: "GET",
secure: true,
format: "json",
...params,
}),
/**
* No description
*
* @tags Tiering
* @name EditTierCredentials
* @summary Edit Tier Credentials
* @request PUT:/admin/tiers/{type}/{name}/credentials
* @secure
*/
editTierCredentials: (
type: "s3" | "gcs" | "azure" | "minio",
name: string,
body: TierCredentialsRequest,
params: RequestParams = {},
) =>
this.request<void, ApiError>({
path: `/admin/tiers/${encodeURIComponent(type)}/${encodeURIComponent(name)}/credentials`,
method: "PUT",
body: body,
secure: true,
type: ContentType.Json,
...params,
}),
/**
* No description
*
* @tags Tiering
* @name RemoveTier
* @summary Remove Tier
* @request DELETE:/admin/tiers/{name}/remove
* @secure
*/
removeTier: (name: string, params: RequestParams = {}) =>
this.request<void, ApiError>({
path: `/admin/tiers/${encodeURIComponent(name)}/remove`,
method: "DELETE",
secure: true,
...params,
}),
/**
* No description
*

View File

@@ -18,7 +18,6 @@ export const IAM_ROLES = {
BUCKET_OWNER: "BUCKET_OWNER", // upload/delete objects from the bucket
BUCKET_VIEWER: "BUCKET_VIEWER", // only view objects on the bucket
BUCKET_ADMIN: "BUCKET_ADMIN", // administrate the bucket
BUCKET_LIFECYCLE: "BUCKET_LIFECYCLE", // can manage bucket lifecycle
};
export const IAM_SCOPES = {
@@ -51,8 +50,6 @@ export const IAM_SCOPES = {
S3_PUT_BUCKET_NOTIFICATIONS: "s3:PutBucketNotification",
S3_GET_REPLICATION_CONFIGURATION: "s3:GetReplicationConfiguration",
S3_PUT_REPLICATION_CONFIGURATION: "s3:PutReplicationConfiguration",
S3_GET_LIFECYCLE_CONFIGURATION: "s3:GetLifecycleConfiguration",
S3_PUT_LIFECYCLE_CONFIGURATION: "s3:PutLifecycleConfiguration",
S3_GET_BUCKET_OBJECT_LOCK_CONFIGURATION:
"s3:GetBucketObjectLockConfiguration",
S3_PUT_BUCKET_OBJECT_LOCK_CONFIGURATION:
@@ -68,8 +65,6 @@ export const IAM_SCOPES = {
ADMIN_SERVER_INFO: "admin:ServerInfo",
ADMIN_GET_BUCKET_QUOTA: "admin:GetBucketQuota",
ADMIN_SET_BUCKET_QUOTA: "admin:SetBucketQuota",
ADMIN_LIST_TIERS: "admin:ListTier",
ADMIN_SET_TIER: "admin:SetTier",
ADMIN_LIST_GROUPS: "admin:ListGroups",
S3_GET_OBJECT_VERSION_FOR_REPLICATION: "s3:GetObjectVersionForReplication",
S3_REPLICATE_TAGS: "s3:ReplicateTags",
@@ -194,9 +189,6 @@ export const IAM_PAGES = {
EVENT_DESTINATIONS: "/settings/event-destinations",
EVENT_DESTINATIONS_ADD: "/settings/event-destinations/add",
EVENT_DESTINATIONS_ADD_SERVICE: "/settings/event-destinations/add/:service",
TIERS: "/settings/tiers",
TIERS_ADD: "/settings/tiers/add",
TIERS_ADD_SERVICE: "/settings/tiers/add/:service",
};
// roles
@@ -242,8 +234,6 @@ export const IAM_PERMISSIONS = {
IAM_SCOPES.S3_BYPASS_GOVERNANCE_RETENTION,
IAM_SCOPES.S3_PUT_BUCKET_POLICY,
IAM_SCOPES.S3_PUT_BUCKET_NOTIFICATIONS,
IAM_SCOPES.S3_GET_LIFECYCLE_CONFIGURATION,
IAM_SCOPES.S3_PUT_LIFECYCLE_CONFIGURATION,
IAM_SCOPES.S3_LIST_MULTIPART_UPLOAD_PARTS,
IAM_SCOPES.S3_LISTEN_BUCKET_NOTIFICATIONS,
IAM_SCOPES.S3_LISTEN_NOTIFICATIONS,
@@ -267,14 +257,6 @@ export const IAM_PERMISSIONS = {
IAM_SCOPES.S3_GET_ACTIONS,
IAM_SCOPES.S3_PUT_ACTIONS,
],
[IAM_ROLES.BUCKET_LIFECYCLE]: [
IAM_SCOPES.S3_GET_LIFECYCLE_CONFIGURATION,
IAM_SCOPES.S3_PUT_LIFECYCLE_CONFIGURATION,
IAM_SCOPES.S3_GET_ACTIONS,
IAM_SCOPES.S3_PUT_ACTIONS,
IAM_SCOPES.ADMIN_LIST_TIERS,
IAM_SCOPES.ADMIN_SET_TIER,
],
};
// application pages/routes and required scopes/roles
@@ -366,17 +348,6 @@ export const IAM_PAGES_PERMISSIONS = {
IAM_SCOPES.ADMIN_SERVER_INFO, // displays notifications endpoints
IAM_SCOPES.ADMIN_CONFIG_UPDATE, // displays create notification button
],
[IAM_PAGES.TIERS]: [
IAM_SCOPES.ADMIN_LIST_TIERS, // display tiers list
],
[IAM_PAGES.TIERS_ADD]: [
IAM_SCOPES.ADMIN_SET_TIER, // display "add tier" button / shows add service tier page
IAM_SCOPES.ADMIN_LIST_TIERS, // display tiers list
],
[IAM_PAGES.TIERS_ADD_SERVICE]: [
IAM_SCOPES.ADMIN_SET_TIER, // display "add tier" button / shows add service tier page
IAM_SCOPES.ADMIN_LIST_TIERS, // display tiers list
],
[IAM_PAGES.TOOLS_LOGS]: [IAM_SCOPES.ADMIN_GET_CONSOLE_LOG],
[IAM_PAGES.TOOLS_AUDITLOGS]: [IAM_SCOPES.ADMIN_HEALTH_INFO],
[IAM_PAGES.TOOLS_WATCH]: [

View File

@@ -1,493 +0,0 @@
// This file is part of MinIO Console Server
// Copyright (c) 2021 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
import React, { Fragment, useEffect, useState } from "react";
import get from "lodash/get";
import {
Accordion,
AlertIcon,
Button,
FormLayout,
Grid,
HelpTip,
InputBox,
LifecycleConfigIcon,
ProgressBar,
RadioGroup,
Select,
Switch,
} from "mds";
import { useSelector } from "react-redux";
import { api } from "api";
import { BucketVersioningResponse } from "api/consoleApi";
import { errorToHandler } from "api/errors";
import { modalStyleUtils } from "../../Common/FormComponents/common/styleLibrary";
import { selDistSet, setModalErrorSnackMessage } from "../../../../systemSlice";
import { useAppDispatch } from "../../../../store";
import { ITiersDropDown } from "../types";
import ModalWrapper from "../../Common/ModalWrapper/ModalWrapper";
import QueryMultiSelector from "../../Common/FormComponents/QueryMultiSelector/QueryMultiSelector";
import InputUnitMenu from "../../Common/FormComponents/InputUnitMenu/InputUnitMenu";
import { IAM_PAGES } from "common/SecureComponent/permissions";
interface IReplicationModal {
open: boolean;
closeModalAndRefresh: (refresh: boolean) => any;
bucketName: string;
}
const AddLifecycleModal = ({
open,
closeModalAndRefresh,
bucketName,
}: IReplicationModal) => {
const dispatch = useAppDispatch();
const distributedSetup = useSelector(selDistSet);
const [loadingTiers, setLoadingTiers] = useState<boolean>(true);
const [tiersList, setTiersList] = useState<ITiersDropDown[]>([]);
const [addLoading, setAddLoading] = useState(false);
const [versioningInfo, setVersioningInfo] =
useState<BucketVersioningResponse | null>(null);
const [prefix, setPrefix] = useState("");
const [tags, setTags] = useState<string>("");
const [storageClass, setStorageClass] = useState("");
const [ilmType, setIlmType] = useState<"expiry" | "transition">("expiry");
const [targetVersion, setTargetVersion] = useState<"current" | "noncurrent">(
"current",
);
const [lifecycleDays, setLifecycleDays] = useState<string>("");
const [isFormValid, setIsFormValid] = useState<boolean>(false);
const [expiredObjectDM, setExpiredObjectDM] = useState<boolean>(false);
const [expiredAllVersionsDM, setExpiredAllVersionsDM] =
useState<boolean>(false);
const [loadingVersioning, setLoadingVersioning] = useState<boolean>(true);
const [expandedAdv, setExpandedAdv] = useState<boolean>(false);
const [expanded, setExpanded] = useState<boolean>(false);
const [expiryUnit, setExpiryUnit] = useState<string>("days");
/*To be removed on component replacement*/
const formFieldRowFilter = {
"& .MuiPaper-root": { padding: 0 },
};
useEffect(() => {
if (loadingTiers) {
api.admin
.tiersListNames()
.then((res) => {
const tiersList: string[] | null = get(res.data, "items", []);
if (tiersList !== null && tiersList.length >= 1) {
const objList = tiersList.map((tierName: string) => {
return { label: tierName, value: tierName };
});
setTiersList(objList);
if (objList.length > 0) {
setStorageClass(objList[0].value);
}
}
setLoadingTiers(false);
})
.catch((err) => {
setLoadingTiers(false);
dispatch(setModalErrorSnackMessage(errorToHandler(err.error)));
});
}
}, [dispatch, loadingTiers]);
useEffect(() => {
let valid = true;
if (ilmType !== "expiry") {
if (storageClass === "") {
valid = false;
}
}
if (!lifecycleDays || parseInt(lifecycleDays) === 0) {
valid = false;
}
if (parseInt(lifecycleDays) > 2147483647) {
//values over int32 cannot be parsed
valid = false;
}
setIsFormValid(valid);
}, [ilmType, lifecycleDays, storageClass]);
useEffect(() => {
if (loadingVersioning && distributedSetup) {
api.buckets
.getBucketVersioning(bucketName)
.then((res) => {
setVersioningInfo(res.data);
setLoadingVersioning(false);
})
.catch((err) => {
dispatch(setModalErrorSnackMessage(errorToHandler(err)));
setLoadingVersioning(false);
});
}
}, [loadingVersioning, dispatch, bucketName, distributedSetup]);
const addRecord = () => {
let rules = {};
if (ilmType === "expiry") {
let expiry: { [key: string]: number } = {};
if (targetVersion === "current") {
expiry["expiry_days"] = parseInt(lifecycleDays);
} else if (expiryUnit === "days") {
expiry["noncurrentversion_expiration_days"] = parseInt(lifecycleDays);
} else {
expiry["newer_noncurrentversion_expiration_versions"] =
parseInt(lifecycleDays);
}
rules = {
...expiry,
};
} else {
let transition: { [key: string]: number | string } = {};
if (targetVersion === "current") {
transition["transition_days"] = parseInt(lifecycleDays);
transition["storage_class"] = storageClass;
} else if (expiryUnit === "days") {
transition["noncurrentversion_transition_days"] =
parseInt(lifecycleDays);
transition["noncurrentversion_transition_storage_class"] = storageClass;
}
rules = {
...transition,
};
}
const lifecycleInsert = {
type: ilmType,
prefix,
tags,
expired_object_delete_marker: expiredObjectDM,
expired_object_delete_all: expiredAllVersionsDM,
...rules,
};
api.buckets
.addBucketLifecycle(bucketName, lifecycleInsert)
.then(() => {
setAddLoading(false);
closeModalAndRefresh(true);
})
.catch((err) => {
setAddLoading(false);
dispatch(setModalErrorSnackMessage(errorToHandler(err)));
});
};
return (
<ModalWrapper
modalOpen={open}
onClose={() => {
closeModalAndRefresh(false);
}}
title="Add Lifecycle Rule"
titleIcon={<LifecycleConfigIcon />}
>
{loadingTiers && (
<Grid container>
<Grid item xs={12}>
<ProgressBar />
</Grid>
</Grid>
)}
{!loadingTiers && (
<form
noValidate
autoComplete="off"
onSubmit={(e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
setAddLoading(true);
addRecord();
}}
>
<FormLayout withBorders={false} containerPadding={false}>
<RadioGroup
currentValue={ilmType}
id="ilm_type"
name="ilm_type"
label="Type of Lifecycle"
onChange={(e) => {
setIlmType(e.target.value as "expiry" | "transition");
}}
selectorOptions={[
{ value: "expiry", label: "Expiry" },
{ value: "transition", label: "Transition" },
]}
helpTip={
<Fragment>
Select{" "}
<a
target="blank"
href="https://min.io/docs/minio/kubernetes/upstream/administration/object-management/create-lifecycle-management-expiration-rule.html"
>
Expiry
</a>{" "}
to delete Objects per this rule. Select{" "}
<a
target="blank"
href="https://min.io/docs/minio/kubernetes/upstream/administration/object-management/transition-objects-to-minio.html"
>
Transition
</a>{" "}
to move Objects to a remote storage{" "}
<a
target="blank"
href="https://min.io/docs/minio/windows/administration/object-management/transition-objects-to-minio.html#configure-the-remote-storage-tier"
>
Tier
</a>{" "}
per this rule.
</Fragment>
}
helpTipPlacement="right"
/>
{versioningInfo?.status === "Enabled" && (
<Select
value={targetVersion}
id="object_version"
name="object_version"
label="Object Version"
onChange={(value) => {
setTargetVersion(value as "current" | "noncurrent");
}}
options={[
{ value: "current", label: "Current Version" },
{ value: "noncurrent", label: "Non-Current Version" },
]}
helpTip={
<Fragment>
Select whether to apply the rule to current or non-current
Object
<a
target="blank"
href="https://min.io/docs/minio/kubernetes/upstream/administration/object-management/create-lifecycle-management-expiration-rule.html#expire-versioned-objects"
>
{" "}
Versions
</a>
</Fragment>
}
helpTipPlacement="right"
/>
)}
<InputBox
error={
lifecycleDays && !isFormValid
? parseInt(lifecycleDays) <= 0
? `Number of ${expiryUnit} to retain must be greater than zero`
: parseInt(lifecycleDays) > 2147483647
? `Number of ${expiryUnit} must be less than or equal to 2147483647`
: ""
: ""
}
id="expiry_days"
name="expiry_days"
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
if (e.target.validity.valid) {
setLifecycleDays(e.target.value);
}
}}
pattern={"[0-9]*"}
label="After"
value={lifecycleDays}
overlayObject={
<Fragment>
<Grid container sx={{ justifyContent: "center" }}>
<InputUnitMenu
id={"expire-current-unit"}
unitSelected={expiryUnit}
unitsList={[
{ label: "Days", value: "days" },
{ label: "Versions", value: "versions" },
]}
disabled={
targetVersion !== "noncurrent" || ilmType !== "expiry"
}
onUnitChange={(newValue) => {
setExpiryUnit(newValue);
}}
/>
{ilmType === "expiry" && targetVersion === "noncurrent" && (
<HelpTip
content={
<Fragment>
Select to set expiry by days or newer noncurrent
versions
</Fragment>
}
placement="right"
>
{" "}
<AlertIcon style={{ width: 15, height: 15 }} />
</HelpTip>
)}
</Grid>
</Fragment>
}
/>
{ilmType === "expiry" ? (
<Fragment />
) : (
<Select
label="To Tier"
id="storage_class"
name="storage_class"
value={storageClass}
onChange={(value) => {
setStorageClass(value as string);
}}
options={tiersList}
helpTip={
<Fragment>
Configure a{" "}
<a
href={IAM_PAGES.TIERS_ADD}
color="secondary"
style={{ textDecoration: "underline" }}
>
remote tier
</a>{" "}
to receive transitioned Objects
</Fragment>
}
helpTipPlacement="right"
/>
)}
<Grid item xs={12} sx={formFieldRowFilter}>
<Accordion
title={"Filters"}
id={"lifecycle-filters"}
expanded={expanded}
onTitleClick={() => setExpanded(!expanded)}
>
<Grid item xs={12}>
<InputBox
id="prefix"
name="prefix"
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
setPrefix(e.target.value);
}}
label="Prefix"
value={prefix}
/>
</Grid>
<Grid item xs={12}>
<QueryMultiSelector
name="tags"
label="Tags"
elements={""}
onChange={(vl: string) => {
setTags(vl);
}}
keyPlaceholder="Tag Key"
valuePlaceholder="Tag Value"
withBorder
/>
</Grid>
</Accordion>
</Grid>
{ilmType === "expiry" && targetVersion === "noncurrent" && (
<Grid item xs={12} sx={formFieldRowFilter}>
<Accordion
title={"Advanced"}
id={"lifecycle-advanced-filters"}
expanded={expandedAdv}
onTitleClick={() => setExpandedAdv(!expandedAdv)}
sx={{ marginTop: 15 }}
>
<Grid item xs={12}>
<Switch
value="expired_delete_marker"
id="expired_delete_marker"
name="expired_delete_marker"
checked={expiredObjectDM}
onChange={(
event: React.ChangeEvent<HTMLInputElement>,
) => {
setExpiredObjectDM(event.target.checked);
}}
label={"Expire Delete Marker"}
description={
"Remove the reference to the object if no versions are left"
}
/>
<Switch
value="expired_delete_all"
id="expired_delete_all"
name="expired_delete_all"
checked={expiredAllVersionsDM}
onChange={(
event: React.ChangeEvent<HTMLInputElement>,
) => {
setExpiredAllVersionsDM(event.target.checked);
}}
label={"Expire All Versions"}
description={
"Removes all the versions of the object already expired"
}
/>
</Grid>
</Accordion>
</Grid>
)}
<Grid item xs={12} sx={modalStyleUtils.modalButtonBar}>
<Button
id={"reset"}
type="button"
variant="regular"
disabled={addLoading}
onClick={() => {
closeModalAndRefresh(false);
}}
label={"Cancel"}
/>
<Button
id={"save-lifecycle"}
type="submit"
variant="callAction"
color="primary"
disabled={addLoading || !isFormValid}
label={"Save"}
/>
</Grid>
{addLoading && (
<Grid item xs={12}>
<ProgressBar />
</Grid>
)}
</FormLayout>
</form>
)}
</ModalWrapper>
);
};
export default AddLifecycleModal;

View File

@@ -88,9 +88,6 @@ const BucketEventsPanel = withSuspense(
const BucketReplicationPanel = withSuspense(
React.lazy(() => import("./BucketReplicationPanel")),
);
const BucketLifecyclePanel = withSuspense(
React.lazy(() => import("./BucketLifecyclePanel")),
);
const BucketDetails = () => {
const dispatch = useAppDispatch();
@@ -143,7 +140,6 @@ const BucketDetails = () => {
const manageBucketRoutes: Record<string, any> = {
events: "/admin/events",
replication: "/admin/replication",
lifecycle: "/admin/lifecycle",
access: "/admin/access",
prefix: "/admin/prefix",
};
@@ -330,21 +326,6 @@ const BucketDetails = () => {
to: getRoutePath("replication"),
},
},
{
tabConfig: {
label: "Lifecycle",
id: "lifecycle",
disabled:
!distributedSetup ||
!hasPermission(bucketName, [
IAM_SCOPES.S3_GET_LIFECYCLE_CONFIGURATION,
IAM_SCOPES.S3_PUT_LIFECYCLE_CONFIGURATION,
IAM_SCOPES.S3_GET_ACTIONS,
IAM_SCOPES.S3_PUT_ACTIONS,
]),
to: getRoutePath("lifecycle"),
},
},
{
tabConfig: {
label: "Access",
@@ -379,10 +360,6 @@ const BucketDetails = () => {
element={<BucketReplicationPanel />}
/>
)}
{distributedSetup && (
<Route path="lifecycle" element={<BucketLifecyclePanel />} />
)}
<Route path="access" element={<AccessDetailsPanel />} />
<Route path="prefix" element={<AccessRulePanel />} />
<Route

View File

@@ -1,396 +0,0 @@
// This file is part of MinIO Console Server
// Copyright (c) 2021 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
import React, { Fragment, useEffect, useState } from "react";
import get from "lodash/get";
import {
AddIcon,
Button,
DataTable,
Grid,
HelpBox,
SectionTitle,
TiersIcon,
HelpTip,
} from "mds";
import { useSelector } from "react-redux";
import { api } from "api";
import { ObjectBucketLifecycle } from "api/consoleApi";
import { LifeCycleItem } from "../types";
import {
hasPermission,
SecureComponent,
} from "../../../../common/SecureComponent";
import { IAM_SCOPES } from "../../../../common/SecureComponent/permissions";
import { selBucketDetailsLoading } from "./bucketDetailsSlice";
import { useParams } from "react-router-dom";
import { setHelpName } from "../../../../systemSlice";
import { useAppDispatch } from "../../../../store";
import DeleteBucketLifecycleRule from "./DeleteBucketLifecycleRule";
import EditLifecycleConfiguration from "./EditLifecycleConfiguration";
import AddLifecycleModal from "./AddLifecycleModal";
import TooltipWrapper from "../../Common/TooltipWrapper/TooltipWrapper";
const BucketLifecyclePanel = () => {
const loadingBucket = useSelector(selBucketDetailsLoading);
const params = useParams();
const [loadingLifecycle, setLoadingLifecycle] = useState<boolean>(true);
const [lifecycleRecords, setLifecycleRecords] = useState<
ObjectBucketLifecycle[]
>([]);
const [addLifecycleOpen, setAddLifecycleOpen] = useState<boolean>(false);
const [editLifecycleOpen, setEditLifecycleOpen] = useState<boolean>(false);
const [selectedLifecycleRule, setSelectedLifecycleRule] =
useState<LifeCycleItem | null>(null);
const [deleteLifecycleOpen, setDeleteLifecycleOpen] =
useState<boolean>(false);
const [selectedID, setSelectedID] = useState<string | null>(null);
const dispatch = useAppDispatch();
const bucketName = params.bucketName || "";
const displayLifeCycleRules = hasPermission(bucketName, [
IAM_SCOPES.S3_GET_LIFECYCLE_CONFIGURATION,
IAM_SCOPES.S3_GET_ACTIONS,
]);
useEffect(() => {
if (loadingBucket) {
setLoadingLifecycle(true);
}
}, [loadingBucket, setLoadingLifecycle]);
useEffect(() => {
dispatch(setHelpName("bucket_detail_lifecycle"));
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
useEffect(() => {
if (loadingLifecycle) {
if (displayLifeCycleRules) {
api.buckets
.getBucketLifecycle(bucketName)
.then((res) => {
const records = get(res.data, "lifecycle", []);
setLifecycleRecords(records || []);
setLoadingLifecycle(false);
})
.catch((err) => {
console.error(err.error);
setLifecycleRecords([]);
setLoadingLifecycle(false);
});
} else {
setLoadingLifecycle(false);
}
}
}, [
loadingLifecycle,
setLoadingLifecycle,
bucketName,
displayLifeCycleRules,
]);
const closeEditLCAndRefresh = (refresh: boolean) => {
setEditLifecycleOpen(false);
setSelectedLifecycleRule(null);
if (refresh) {
setLoadingLifecycle(true);
}
};
const closeAddLCAndRefresh = (refresh: boolean) => {
setAddLifecycleOpen(false);
if (refresh) {
setLoadingLifecycle(true);
}
};
const closeDelLCRefresh = (refresh: boolean) => {
setDeleteLifecycleOpen(false);
setSelectedID(null);
if (refresh) {
setLoadingLifecycle(true);
}
};
const renderStorageClass = (objectST: any) => {
let stClass = get(objectST, "transition.storage_class", "");
stClass = get(objectST, "transition.noncurrent_storage_class", stClass);
return stClass;
};
const lifecycleColumns = [
{
label: "Type",
renderFullObject: true,
renderFunction: (el: LifeCycleItem) => {
if (!el) {
return <Fragment />;
}
if (
el.expiration &&
(el.expiration.days > 0 ||
el.expiration.noncurrent_expiration_days ||
(el.expiration.newer_noncurrent_expiration_versions &&
el.expiration.newer_noncurrent_expiration_versions > 0))
) {
return <span>Expiry</span>;
}
if (
el.transition &&
(el.transition.days > 0 || el.transition.noncurrent_transition_days)
) {
return <span>Transition</span>;
}
return <Fragment />;
},
},
{
label: "Version",
renderFullObject: true,
renderFunction: (el: LifeCycleItem) => {
if (!el) {
return <Fragment />;
}
if (el.expiration) {
if (el.expiration.days > 0) {
return <span>Current</span>;
} else if (
el.expiration.noncurrent_expiration_days ||
el.expiration.newer_noncurrent_expiration_versions
) {
return <span>Non-Current</span>;
}
}
if (el.transition) {
if (el.transition.days > 0) {
return <span>Current</span>;
} else if (el.transition.noncurrent_transition_days) {
return <span>Non-Current</span>;
}
}
},
},
{
label: "Expire Delete Marker",
elementKey: "expire_delete_marker",
renderFunction: (el: LifeCycleItem) => {
if (!el) {
return <Fragment />;
}
if (el.expiration && el.expiration.delete_marker !== undefined) {
return <span>{el.expiration.delete_marker ? "true" : "false"}</span>;
} else {
return <Fragment />;
}
},
renderFullObject: true,
},
{
label: "Tier",
elementKey: "storage_class",
renderFunction: renderStorageClass,
renderFullObject: true,
},
{
label: "Prefix",
elementKey: "prefix",
},
{
label: "After",
renderFullObject: true,
renderFunction: (el: LifeCycleItem) => {
if (!el) {
return <Fragment />;
}
if (el.transition) {
if (el.transition.days > 0) {
return <span>{el.transition.days} days</span>;
} else if (el.transition.noncurrent_transition_days) {
return <span>{el.transition.noncurrent_transition_days} days</span>;
}
}
if (el.expiration) {
if (el.expiration.days > 0) {
return <span>{el.expiration.days} days</span>;
} else if (el.expiration.noncurrent_expiration_days) {
return <span>{el.expiration.noncurrent_expiration_days} days</span>;
} else {
return (
<span>
{el.expiration.newer_noncurrent_expiration_versions} versions
</span>
);
}
}
},
},
{
label: "Status",
elementKey: "status",
},
];
const lifecycleActions = [
{
type: "view",
onClick(valueToSend: any): any {
setSelectedLifecycleRule(valueToSend);
setEditLifecycleOpen(true);
},
},
{
type: "delete",
onClick(valueToDelete: string): any {
setSelectedID(valueToDelete);
setDeleteLifecycleOpen(true);
},
sendOnlyId: true,
},
];
return (
<Fragment>
{editLifecycleOpen && selectedLifecycleRule && (
<EditLifecycleConfiguration
open={editLifecycleOpen}
closeModalAndRefresh={closeEditLCAndRefresh}
selectedBucket={bucketName}
lifecycleRule={selectedLifecycleRule}
/>
)}
{addLifecycleOpen && (
<AddLifecycleModal
open={addLifecycleOpen}
bucketName={bucketName}
closeModalAndRefresh={closeAddLCAndRefresh}
/>
)}
{deleteLifecycleOpen && selectedID && (
<DeleteBucketLifecycleRule
id={selectedID}
bucket={bucketName}
deleteOpen={deleteLifecycleOpen}
onCloseAndRefresh={closeDelLCRefresh}
/>
)}
<SectionTitle
separator
sx={{ marginBottom: 15 }}
actions={
<SecureComponent
scopes={[
IAM_SCOPES.S3_PUT_LIFECYCLE_CONFIGURATION,
IAM_SCOPES.S3_PUT_ACTIONS,
]}
resource={bucketName}
matchAll
errorProps={{ disabled: true }}
>
<TooltipWrapper tooltip={"Add Lifecycle Rule"}>
<Button
id={"add-bucket-lifecycle-rule"}
onClick={() => {
setAddLifecycleOpen(true);
}}
label={"Add Lifecycle Rule"}
icon={<AddIcon />}
variant={"callAction"}
/>
</TooltipWrapper>
</SecureComponent>
}
>
<HelpTip
content={
<Fragment>
MinIO derives its behavior and syntax from{" "}
<a
target="blank"
href="https://docs.aws.amazon.com/AmazonS3/latest/userguide/object-lifecycle-mgmt.html"
>
S3 lifecycle
</a>{" "}
for compatibility in migrating workloads and lifecycle rules from
S3 to MinIO.
</Fragment>
}
placement="right"
>
Lifecycle Rules
</HelpTip>
</SectionTitle>
<Grid container>
<Grid item xs={12}>
<SecureComponent
scopes={[
IAM_SCOPES.S3_GET_LIFECYCLE_CONFIGURATION,
IAM_SCOPES.S3_GET_ACTIONS,
]}
resource={bucketName}
errorProps={{ disabled: true }}
>
<DataTable
itemActions={lifecycleActions}
columns={lifecycleColumns}
isLoading={loadingLifecycle}
records={lifecycleRecords}
entityName="Lifecycle"
customEmptyMessage="There are no Lifecycle rules yet"
idField="id"
customPaperHeight={"400px"}
/>
</SecureComponent>
</Grid>
{!loadingLifecycle && (
<Grid item xs={12}>
<br />
<HelpBox
title={"Lifecycle Rules"}
iconComponent={<TiersIcon />}
help={
<Fragment>
MinIO Object Lifecycle Management allows creating rules for
time or date based automatic transition or expiry of objects.
For object transition, MinIO automatically moves the object to
a configured remote storage tier.
<br />
<br />
You can learn more at our{" "}
<a
href="https://min.io/docs/minio/linux/administration/object-management/object-lifecycle-management.html?ref=con"
target="_blank"
rel="noopener"
>
documentation
</a>
.
</Fragment>
}
/>
</Grid>
)}
</Grid>
</Fragment>
);
};
export default BucketLifecyclePanel;

View File

@@ -1,78 +0,0 @@
// This file is part of MinIO Console Server
// Copyright (c) 2022 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
import React, { useEffect, useState, Fragment } from "react";
import { ConfirmDeleteIcon } from "mds";
import { api } from "api";
import { errorToHandler } from "api/errors";
import { setErrorSnackMessage } from "../../../../systemSlice";
import { useAppDispatch } from "../../../../store";
import ConfirmDialog from "../../Common/ModalWrapper/ConfirmDialog";
interface IDeleteLifecycleRule {
deleteOpen: boolean;
onCloseAndRefresh: (refresh: boolean) => any;
bucket: string;
id: string;
}
const DeleteBucketLifecycleRule = ({
onCloseAndRefresh,
deleteOpen,
bucket,
id,
}: IDeleteLifecycleRule) => {
const dispatch = useAppDispatch();
const [deletingRule, setDeletingRule] = useState<boolean>(false);
useEffect(() => {
if (deletingRule) {
api.buckets
.deleteBucketLifecycleRule(bucket, id)
.then(() => {
setDeletingRule(false);
onCloseAndRefresh(true);
})
.catch((err) => {
setDeletingRule(false);
dispatch(setErrorSnackMessage(errorToHandler(err.error)));
});
}
}, [deletingRule, bucket, id, onCloseAndRefresh, dispatch]);
const onConfirmDelete = () => {
setDeletingRule(true);
};
return (
<ConfirmDialog
title={`Delete Lifecycle Rule`}
confirmText={"Delete"}
isOpen={deleteOpen}
isLoading={deletingRule}
onConfirm={onConfirmDelete}
titleIcon={<ConfirmDeleteIcon />}
onClose={() => onCloseAndRefresh(false)}
confirmationContent={
<Fragment>
Are you sure you want to delete the <strong>{id}</strong> rule?
</Fragment>
}
/>
);
};
export default DeleteBucketLifecycleRule;

View File

@@ -1,564 +0,0 @@
// This file is part of MinIO Console Server
// Copyright (c) 2021 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
import React, { Fragment, useEffect, useState } from "react";
import get from "lodash/get";
import {
Accordion,
Button,
FormLayout,
Grid,
InputBox,
LifecycleConfigIcon,
Loader,
ProgressBar,
RadioGroup,
Select,
Switch,
} from "mds";
import { api } from "api";
import { ApiError } from "api/consoleApi";
import { modalStyleUtils } from "../../Common/FormComponents/common/styleLibrary";
import { ITiersDropDown, LifeCycleItem } from "../types";
import {
setErrorSnackMessage,
setModalErrorSnackMessage,
} from "../../../../systemSlice";
import { useAppDispatch } from "../../../../store";
import ModalWrapper from "../../Common/ModalWrapper/ModalWrapper";
import QueryMultiSelector from "../../Common/FormComponents/QueryMultiSelector/QueryMultiSelector";
import { errorToHandler } from "../../../../api/errors";
interface IAddUserContentProps {
closeModalAndRefresh: (reload: boolean) => void;
selectedBucket: string;
lifecycleRule: LifeCycleItem;
open: boolean;
}
const EditLifecycleConfiguration = ({
closeModalAndRefresh,
selectedBucket,
lifecycleRule,
open,
}: IAddUserContentProps) => {
const dispatch = useAppDispatch();
const [loadingTiers, setLoadingTiers] = useState<boolean>(true);
const [addLoading, setAddLoading] = useState<boolean>(false);
const [tags, setTags] = useState<string>("");
const [enabled, setEnabled] = useState<boolean>(false);
const [tiersList, setTiersList] = useState<ITiersDropDown[]>([]);
const [prefix, setPrefix] = useState("");
const [storageClass, setStorageClass] = useState("");
const [NCTransitionSC, setNCTransitionSC] = useState("");
const [expiredObjectDM, setExpiredObjectDM] = useState<boolean>(false);
const [expiredAllVersionsDM, setExpiredAllVersionsDM] =
useState<boolean>(false);
const [NCExpirationDays, setNCExpirationDays] = useState<string>("0");
const [NCTransitionDays, setNCTransitionDays] = useState<string>("0");
const [ilmType, setIlmType] = useState<"transition" | "expiry">("expiry");
const [expiryDays, setExpiryDays] = useState<string>("0");
const [transitionDays, setTransitionDays] = useState<string>("0");
const [isFormValid, setIsFormValid] = useState<boolean>(false);
const [expandedAdv, setExpandedAdv] = useState<boolean>(false);
const [expanded, setExpanded] = useState<boolean>(false);
const ILM_TYPES = [
{ value: "expiry", label: "Expiry" },
{ value: "transition", label: "Transition" },
];
useEffect(() => {
if (loadingTiers) {
api.admin
.tiersListNames()
.then((res) => {
const tiersList: string[] | null = get(res.data, "items", []);
if (tiersList !== null && tiersList.length >= 1) {
const objList = tiersList.map((tierName: string) => {
return { label: tierName, value: tierName };
});
setTiersList(objList);
if (objList.length > 0) {
setStorageClass(lifecycleRule.transition?.storage_class || "");
}
}
setLoadingTiers(false);
})
.catch((err) => {
setLoadingTiers(false);
dispatch(setModalErrorSnackMessage(errorToHandler(err.error)));
});
}
}, [dispatch, loadingTiers, lifecycleRule.transition?.storage_class]);
useEffect(() => {
let valid = true;
if (ilmType !== "expiry") {
if (
(transitionDays !== "0" && storageClass === "") ||
(NCTransitionDays !== "0" && NCTransitionSC === "")
) {
valid = false;
}
}
setIsFormValid(valid);
}, [
ilmType,
expiryDays,
transitionDays,
storageClass,
NCTransitionDays,
NCTransitionSC,
]);
useEffect(() => {
if (lifecycleRule.status === "Enabled") {
setEnabled(true);
}
let transitionMode = false;
if (lifecycleRule.transition) {
if (
lifecycleRule.transition.days &&
lifecycleRule.transition.days !== 0
) {
setTransitionDays(lifecycleRule.transition.days.toString());
setIlmType("transition");
transitionMode = true;
}
if (
lifecycleRule.transition.noncurrent_transition_days &&
lifecycleRule.transition.noncurrent_transition_days !== 0
) {
setNCTransitionDays(
lifecycleRule.transition.noncurrent_transition_days.toString(),
);
setIlmType("transition");
transitionMode = true;
}
// Fallback to old rules by date
if (
lifecycleRule.transition.date &&
lifecycleRule.transition.date !== "0001-01-01T00:00:00Z"
) {
setIlmType("transition");
transitionMode = true;
}
}
if (lifecycleRule.expiration) {
if (
lifecycleRule.expiration.days &&
lifecycleRule.expiration.days !== 0
) {
setExpiryDays(lifecycleRule.expiration.days.toString());
setIlmType("expiry");
transitionMode = false;
}
if (
lifecycleRule.expiration.noncurrent_expiration_days &&
lifecycleRule.expiration.noncurrent_expiration_days !== 0
) {
setNCExpirationDays(
lifecycleRule.expiration.noncurrent_expiration_days.toString(),
);
setIlmType("expiry");
transitionMode = false;
}
// Fallback to old rules by date
if (
lifecycleRule.expiration.date &&
lifecycleRule.expiration.date !== "0001-01-01T00:00:00Z"
) {
setIlmType("expiry");
transitionMode = false;
}
}
// Transition fields
if (transitionMode) {
setStorageClass(lifecycleRule.transition?.storage_class || "");
setNCTransitionDays(
lifecycleRule.transition?.noncurrent_transition_days?.toString() || "0",
);
setNCTransitionSC(
lifecycleRule.transition?.noncurrent_storage_class || "",
);
} else {
// Expiry fields
setNCExpirationDays(
lifecycleRule.expiration?.noncurrent_expiration_days?.toString() || "0",
);
}
setExpiredObjectDM(!!lifecycleRule.expiration?.delete_marker);
setExpiredAllVersionsDM(!!lifecycleRule.expiration?.delete_all);
setPrefix(lifecycleRule.prefix || "");
if (lifecycleRule.tags) {
const tgs = lifecycleRule.tags.reduce(
(stringLab: string, currItem: any, index: number) => {
return `${stringLab}${index !== 0 ? "&" : ""}${currItem.key}=${
currItem.value
}`;
},
"",
);
setTags(tgs);
}
}, [lifecycleRule]);
const saveRecord = (event: React.FormEvent) => {
event.preventDefault();
if (addLoading) {
return;
}
setAddLoading(true);
if (selectedBucket !== null && lifecycleRule !== null) {
let rules = {};
if (ilmType === "expiry") {
let expiry: { [key: string]: number } = {};
if (
lifecycleRule.expiration?.days &&
lifecycleRule.expiration?.days > 0
) {
expiry["expiry_days"] = parseInt(expiryDays);
}
if (lifecycleRule.expiration?.noncurrent_expiration_days) {
expiry["noncurrentversion_expiration_days"] =
parseInt(NCExpirationDays);
}
rules = {
...expiry,
};
} else {
let transition: { [key: string]: number | string } = {};
if (
lifecycleRule.transition?.days &&
lifecycleRule.transition?.days > 0
) {
transition["transition_days"] = parseInt(transitionDays);
transition["storage_class"] = storageClass;
}
if (lifecycleRule.transition?.noncurrent_transition_days) {
transition["noncurrentversion_transition_days"] =
parseInt(NCTransitionDays);
transition["noncurrentversion_transition_storage_class"] =
NCTransitionSC;
}
rules = {
...transition,
};
}
const lifecycleUpdate = {
type: ilmType,
disable: !enabled,
prefix,
tags,
expired_object_delete_marker: expiredObjectDM,
expired_object_delete_all: expiredAllVersionsDM,
...rules,
};
api.buckets
.updateBucketLifecycle(
selectedBucket,
lifecycleRule.id,
lifecycleUpdate,
)
.then((res) => {
setAddLoading(false);
closeModalAndRefresh(true);
})
.catch(async (eRes) => {
setAddLoading(false);
const err = (await eRes.json()) as ApiError;
dispatch(setErrorSnackMessage(errorToHandler(err)));
});
}
};
let objectVersion = "";
if (lifecycleRule.expiration) {
if (lifecycleRule.expiration.days > 0) {
objectVersion = "Current Version";
} else if (lifecycleRule.expiration.noncurrent_expiration_days) {
objectVersion = "Non-Current Version";
}
}
if (lifecycleRule.transition) {
if (lifecycleRule.transition.days > 0) {
objectVersion = "Current Version";
} else if (lifecycleRule.transition.noncurrent_transition_days) {
objectVersion = "Non-Current Version";
}
}
return (
<ModalWrapper
onClose={() => {
closeModalAndRefresh(false);
}}
modalOpen={open}
title={"Edit Lifecycle Configuration"}
titleIcon={<LifecycleConfigIcon />}
>
{!loadingTiers ? (
<form
noValidate
autoComplete="off"
onSubmit={(e: React.FormEvent<HTMLFormElement>) => {
saveRecord(e);
}}
>
<FormLayout containerPadding={false} withBorders={false}>
<Switch
label="Status"
indicatorLabels={["Enabled", "Disabled"]}
checked={enabled}
value={"user_enabled"}
id="rule_status"
name="rule_status"
onChange={(e) => {
setEnabled(e.target.checked);
}}
/>
<InputBox
id="id"
name="id"
label="Id"
value={lifecycleRule.id}
onChange={() => {}}
disabled
/>
{ilmType ? (
<RadioGroup
currentValue={ilmType}
id="rule_type"
name="rule_type"
label="Rule Type"
selectorOptions={ILM_TYPES}
onChange={() => {}}
disableOptions
/>
) : null}
<InputBox
id="object-version"
name="object-version"
label="Object Version"
value={objectVersion}
onChange={() => {}}
disabled
/>
{ilmType === "expiry" && lifecycleRule.expiration?.days && (
<InputBox
type="number"
id="expiry_days"
name="expiry_days"
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
setExpiryDays(e.target.value);
}}
label="Expiry Days"
value={expiryDays}
min="0"
/>
)}
{ilmType === "expiry" &&
lifecycleRule.expiration?.noncurrent_expiration_days && (
<InputBox
type="number"
id="noncurrentversion_expiration_days"
name="noncurrentversion_expiration_days"
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
setNCExpirationDays(e.target.value);
}}
label="Non-current Expiration Days"
value={NCExpirationDays}
min="0"
/>
)}
{ilmType === "transition" && lifecycleRule.transition?.days && (
<Fragment>
<InputBox
type="number"
id="transition_days"
name="transition_days"
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
setTransitionDays(e.target.value);
}}
label="Transition Days"
value={transitionDays}
min="0"
/>
<Select
label="Tier"
id="storage_class"
name="storage_class"
value={storageClass}
onChange={(value) => {
setStorageClass(value);
}}
options={tiersList}
/>
</Fragment>
)}
{ilmType === "transition" &&
lifecycleRule.transition?.noncurrent_transition_days && (
<Fragment>
<InputBox
type="number"
id="noncurrentversion_transition_days"
name="noncurrentversion_transition_days"
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
setNCTransitionDays(e.target.value);
}}
label="Non-current Transition Days"
value={NCTransitionDays}
min="0"
/>
<Select
label="Non-current Version Transition Storage Class"
id="noncurrentversion_t_SC"
name="noncurrentversion_t_SC"
value={NCTransitionSC}
onChange={(value) => {
setNCTransitionSC(value);
}}
options={tiersList}
/>
</Fragment>
)}
<Grid item xs={12}>
<Accordion
title={"Filters"}
id={"lifecycle-filters"}
expanded={expanded}
onTitleClick={() => setExpanded(!expanded)}
>
<InputBox
id="prefix"
name="prefix"
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
setPrefix(e.target.value);
}}
label="Prefix"
value={prefix}
/>
<QueryMultiSelector
name="tags"
label="Tags"
elements={tags}
onChange={(vl: string) => {
setTags(vl);
}}
keyPlaceholder="Tag Key"
valuePlaceholder="Tag Value"
withBorder
/>
</Accordion>
</Grid>
{ilmType === "expiry" &&
lifecycleRule.expiration?.noncurrent_expiration_days && (
<Grid item xs={12}>
<Accordion
title={"Advanced"}
id={"lifecycle-advanced-filters"}
expanded={expandedAdv}
onTitleClick={() => setExpandedAdv(!expandedAdv)}
sx={{ marginTop: 15 }}
>
<Switch
value="expired_delete_marker"
id="expired_delete_marker"
name="expired_delete_marker"
checked={expiredObjectDM}
onChange={(
event: React.ChangeEvent<HTMLInputElement>,
) => {
setExpiredObjectDM(event.target.checked);
}}
label={"Expired Object Delete Marker"}
/>
<Switch
value="expired_delete_all"
id="expired_delete_all"
name="expired_delete_all"
checked={expiredAllVersionsDM}
onChange={(
event: React.ChangeEvent<HTMLInputElement>,
) => {
setExpiredAllVersionsDM(event.target.checked);
}}
label={"Expired All Versions"}
/>
</Accordion>
</Grid>
)}
<Grid item xs={12} sx={modalStyleUtils.modalButtonBar}>
<Button
id={"cancel"}
type="button"
variant="regular"
disabled={addLoading}
onClick={() => {
closeModalAndRefresh(false);
}}
label={"Cancel"}
/>
<Button
id={"save"}
type="submit"
variant="callAction"
color="primary"
disabled={addLoading || !isFormValid}
label={"Save"}
/>
</Grid>
{addLoading && (
<Grid item xs={12}>
<ProgressBar />
</Grid>
)}
</FormLayout>
</form>
) : (
<Loader style={{ width: 16, height: 16 }} />
)}
</ModalWrapper>
);
};
export default EditLifecycleConfiguration;

View File

@@ -1,415 +0,0 @@
// This file is part of MinIO Console Server
// Copyright (c) 2022 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
import React, { Fragment, useEffect, useState } from "react";
import {
Box,
CheckCircleIcon,
FormLayout,
Grid,
InputBox,
RadioGroup,
ReadBox,
Select,
Switch,
Tooltip,
WarnIcon,
Wizard,
} from "mds";
import get from "lodash/get";
import ModalWrapper from "../../Common/ModalWrapper/ModalWrapper";
import QueryMultiSelector from "../../Common/FormComponents/QueryMultiSelector/QueryMultiSelector";
import { ITiersDropDown } from "../types";
import { setModalErrorSnackMessage } from "../../../../systemSlice";
import { useAppDispatch } from "../../../../store";
import { api } from "api";
import { MultiLifecycleResult } from "api/consoleApi";
import { errorToHandler } from "api/errors";
interface IBulkReplicationModal {
open: boolean;
closeModalAndRefresh: (clearSelection: boolean) => any;
buckets: string[];
}
const AddBulkReplicationModal = ({
open,
closeModalAndRefresh,
buckets,
}: IBulkReplicationModal) => {
const dispatch = useAppDispatch();
const [addLoading, setAddLoading] = useState<boolean>(false);
const [loadingTiers, setLoadingTiers] = useState<boolean>(true);
const [tiersList, setTiersList] = useState<ITiersDropDown[]>([]);
const [prefix, setPrefix] = useState("");
const [tags, setTags] = useState<string>("");
const [storageClass, setStorageClass] = useState("");
const [NCTransitionSC, setNCTransitionSC] = useState("");
const [expiredObjectDM, setExpiredObjectDM] = useState<boolean>(false);
const [expiredAllVersionsDM, setExpiredAllVersionsDM] =
useState<boolean>(false);
const [NCExpirationDays, setNCExpirationDays] = useState<string>("0");
const [NCTransitionDays, setNCTransitionDays] = useState<string>("0");
const [ilmType, setIlmType] = useState<"expiry" | "transition">("expiry");
const [expiryDays, setExpiryDays] = useState<string>("0");
const [transitionDays, setTransitionDays] = useState<string>("0");
const [isFormValid, setIsFormValid] = useState<boolean>(false);
const [results, setResults] = useState<MultiLifecycleResult | null>(null);
useEffect(() => {
if (loadingTiers) {
api.admin
.tiersListNames()
.then((res) => {
const tiersList: string[] | null = get(res.data, "items", []);
if (tiersList !== null && tiersList.length >= 1) {
const objList = tiersList.map((tierName: string) => {
return { label: tierName, value: tierName };
});
setTiersList(objList);
if (objList.length > 0) {
setStorageClass(objList[0].value);
}
}
setLoadingTiers(false);
})
.catch((err) => {
setLoadingTiers(false);
dispatch(setModalErrorSnackMessage(errorToHandler(err.error)));
});
}
}, [dispatch, loadingTiers]);
useEffect(() => {
let valid = true;
if (ilmType !== "expiry") {
if (storageClass === "") {
valid = false;
}
}
setIsFormValid(valid);
}, [ilmType, expiryDays, transitionDays, storageClass]);
const LogoToShow = ({ errString }: { errString: string }) => {
switch (errString) {
case "":
return (
<Box
sx={{
paddingTop: 5,
color: "#42C91A",
}}
>
<CheckCircleIcon />
</Box>
);
case "n/a":
return null;
default:
if (errString) {
return (
<Box
sx={{
paddingTop: 5,
color: "#C72C48",
}}
>
<Tooltip tooltip={errString} placement="top">
<WarnIcon />
</Tooltip>
</Box>
);
}
}
return null;
};
const createLifecycleRules = (to: any) => {
let rules = {};
if (ilmType === "expiry") {
let expiry = {
expiry_days: parseInt(expiryDays),
};
rules = {
...expiry,
noncurrentversion_expiration_days: parseInt(NCExpirationDays),
};
} else {
let transition = {
transition_days: parseInt(transitionDays),
};
rules = {
...transition,
noncurrentversion_transition_days: parseInt(NCTransitionDays),
noncurrentversion_transition_storage_class: NCTransitionSC,
storage_class: storageClass,
};
}
const lifecycleInsert = {
buckets,
type: ilmType,
prefix,
tags,
expired_object_delete_marker: expiredObjectDM,
expired_object_delete_all: expiredAllVersionsDM,
...rules,
};
api.buckets
.addMultiBucketLifecycle(lifecycleInsert)
.then((res) => {
setAddLoading(false);
setResults(res.data);
to("++");
})
.catch((err) => {
setAddLoading(false);
dispatch(setModalErrorSnackMessage(errorToHandler(err.error)));
});
};
return (
<ModalWrapper
modalOpen={open}
onClose={() => {
closeModalAndRefresh(false);
}}
title="Set Lifecycle to multiple buckets"
>
<Wizard
loadingStep={addLoading || loadingTiers}
wizardSteps={[
{
label: "Lifecycle Configuration",
componentRender: (
<Fragment>
<FormLayout withBorders={false} containerPadding={false}>
<Grid item xs={12}>
<ReadBox
label="Local Buckets to replicate"
sx={{ maxWidth: "440px", width: "100%" }}
>
{buckets.join(", ")}
</ReadBox>
</Grid>
<h4>Remote Endpoint Configuration</h4>
<fieldset className={"inputItem"}>
<legend>Lifecycle Configuration</legend>
<RadioGroup
currentValue={ilmType}
id="quota_type"
name="quota_type"
label="ILM Rule"
onChange={(e: React.ChangeEvent<{ value: unknown }>) => {
setIlmType(e.target.value as "expiry" | "transition");
}}
selectorOptions={[
{ value: "expiry", label: "Expiry" },
{ value: "transition", label: "Transition" },
]}
/>
{ilmType === "expiry" ? (
<Fragment>
<InputBox
type="number"
id="expiry_days"
name="expiry_days"
onChange={(
e: React.ChangeEvent<HTMLInputElement>,
) => {
setExpiryDays(e.target.value);
}}
label="Expiry Days"
value={expiryDays}
min="0"
/>
<InputBox
type="number"
id="noncurrentversion_expiration_days"
name="noncurrentversion_expiration_days"
onChange={(
e: React.ChangeEvent<HTMLInputElement>,
) => {
setNCExpirationDays(e.target.value);
}}
label="Non-current Expiration Days"
value={NCExpirationDays}
min="0"
/>
</Fragment>
) : (
<Fragment>
<InputBox
type="number"
id="transition_days"
name="transition_days"
onChange={(
e: React.ChangeEvent<HTMLInputElement>,
) => {
setTransitionDays(e.target.value);
}}
label="Transition Days"
value={transitionDays}
min="0"
/>
<InputBox
type="number"
id="noncurrentversion_transition_days"
name="noncurrentversion_transition_days"
onChange={(
e: React.ChangeEvent<HTMLInputElement>,
) => {
setNCTransitionDays(e.target.value);
}}
label="Non-current Transition Days"
value={NCTransitionDays}
min="0"
/>
<InputBox
id="noncurrentversion_t_SC"
name="noncurrentversion_t_SC"
onChange={(
e: React.ChangeEvent<HTMLInputElement>,
) => {
setNCTransitionSC(e.target.value);
}}
placeholder="Set Non-current Version Transition Storage Class"
label="Non-current Version Transition Storage Class"
value={NCTransitionSC}
/>
<Select
label="Storage Class"
id="storage_class"
name="storage_class"
value={storageClass}
onChange={(value) => {
setStorageClass(value);
}}
options={tiersList}
/>
</Fragment>
)}
</fieldset>
<fieldset className={"inputItem"}>
<legend>File Configuration</legend>
<InputBox
id="prefix"
name="prefix"
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
setPrefix(e.target.value);
}}
label="Prefix"
value={prefix}
/>
<QueryMultiSelector
name="tags"
label="Tags"
elements={tags}
onChange={(vl: string) => {
setTags(vl);
}}
keyPlaceholder="Tag Key"
valuePlaceholder="Tag Value"
withBorder
/>
<Switch
value="expired_delete_marker"
id="expired_delete_marker"
name="expired_delete_marker"
checked={expiredObjectDM}
onChange={(
event: React.ChangeEvent<HTMLInputElement>,
) => {
setExpiredObjectDM(event.target.checked);
}}
label={"Expired Object Delete Marker"}
/>
<Switch
value="expired_delete_all"
id="expired_delete_all"
name="expired_delete_all"
checked={expiredAllVersionsDM}
onChange={(
event: React.ChangeEvent<HTMLInputElement>,
) => {
setExpiredAllVersionsDM(event.target.checked);
}}
label={"Expired All Versions"}
/>
</fieldset>
</FormLayout>
</Fragment>
),
buttons: [
{
type: "custom",
label: "Create Rules",
enabled: !loadingTiers && !addLoading && isFormValid,
action: createLifecycleRules,
},
],
},
{
label: "Results",
componentRender: (
<Fragment>
<h3>Multi Bucket lifecycle Assignments Results</h3>
<Grid container>
<Grid item xs={12}>
<h4>Buckets Results</h4>
{results?.results?.map((resultItem) => {
return (
<Box
sx={{
display: "grid",
gridTemplateColumns: "45px auto",
alignItems: "center",
justifyContent: "stretch",
}}
>
{LogoToShow({ errString: resultItem.error || "" })}
<span>{resultItem.bucketName}</span>
</Box>
);
})}
</Grid>
</Grid>
</Fragment>
),
buttons: [
{
type: "custom",
label: "Done",
enabled: !addLoading,
action: () => closeModalAndRefresh(true),
},
],
},
]}
forModal
/>
</ModalWrapper>
);
};
export default AddBulkReplicationModal;

View File

@@ -21,7 +21,6 @@ import {
BucketsIcon,
Button,
HelpBox,
LifecycleConfigIcon,
MultipleBucketsIcon,
PageLayout,
RefreshIcon,
@@ -38,8 +37,6 @@ import { SecureComponent } from "../../../../common/SecureComponent";
import {
CONSOLE_UI_RESOURCE,
IAM_PAGES,
IAM_PERMISSIONS,
IAM_ROLES,
IAM_SCOPES,
permissionTooltipHelper,
} from "../../../../common/SecureComponent/permissions";
@@ -56,7 +53,6 @@ import AutoColorIcon from "../../Common/Components/AutoColorIcon";
import TooltipWrapper from "../../Common/TooltipWrapper/TooltipWrapper";
import SearchBox from "../../Common/SearchBox";
import VirtualizedList from "../../Common/VirtualizedList/VirtualizedList";
import BulkLifecycleModal from "./BulkLifecycleModal";
import hasPermission from "../../../../common/SecureComponent/accessControl";
import BucketListItem from "./BucketListItem";
import BulkReplicationModal from "./BulkReplicationModal";
@@ -71,8 +67,6 @@ const ListBuckets = () => {
const [selectedBuckets, setSelectedBuckets] = useState<string[]>([]);
const [replicationModalOpen, setReplicationModalOpen] =
useState<boolean>(false);
const [lifecycleModalOpen, setLifecycleModalOpen] = useState<boolean>(false);
const [canPutLifecycle, setCanPutLifecycle] = useState<boolean>(false);
const [bulkSelect, setBulkSelect] = useState<boolean>(false);
const features = useSelector(selFeatures);
@@ -137,24 +131,6 @@ const ListBuckets = () => {
}
};
const closeBulkLifecycleModal = (unselectAll: boolean) => {
setLifecycleModalOpen(false);
if (unselectAll) {
setSelectedBuckets([]);
}
};
useEffect(() => {
var failLifecycle = false;
selectedBuckets.forEach((bucket: string) => {
hasPermission(bucket, IAM_PERMISSIONS[IAM_ROLES.BUCKET_LIFECYCLE], true)
? setCanPutLifecycle(true)
: (failLifecycle = true);
});
failLifecycle ? setCanPutLifecycle(false) : setCanPutLifecycle(true);
}, [selectedBuckets]);
const renderItemLine = (index: number) => {
const bucket = filteredRecords[index] || null;
if (bucket) {
@@ -198,13 +174,6 @@ const ListBuckets = () => {
closeModalAndRefresh={closeBulkReplicationModal}
/>
)}
{lifecycleModalOpen && (
<BulkLifecycleModal
buckets={selectedBuckets}
closeModalAndRefresh={closeBulkLifecycleModal}
open={lifecycleModalOpen}
/>
)}
{!obOnly && (
<PageHeaderWrapper label={"Buckets"} actions={<HelpMenu />} />
)}
@@ -282,33 +251,6 @@ const ListBuckets = () => {
</TooltipWrapper>
)}
<TooltipWrapper
tooltip={
!hasBuckets
? ""
: !canPutLifecycle
? permissionTooltipHelper(
IAM_PERMISSIONS[IAM_ROLES.BUCKET_LIFECYCLE],
"configure lifecycle for the selected buckets",
)
: selectedBuckets.length === 0
? bulkSelect
? "Please select at least one bucket on which to configure Lifecycle"
: "Use the Select Multiple Buckets button to choose buckets on which to configure Lifecycle"
: "Set Lifecycle"
}
>
<Button
id={"set-lifecycle"}
onClick={() => {
setLifecycleModalOpen(true);
}}
icon={<LifecycleConfigIcon />}
variant={"regular"}
disabled={selectedBuckets.length === 0 || !canPutLifecycle}
/>
</TooltipWrapper>
<TooltipWrapper
tooltip={
!hasBuckets

View File

@@ -36,43 +36,3 @@ export interface BucketReplicationRule {
export interface BucketReplication {
rules: BucketReplicationRule[];
}
interface IExpirationLifecycle {
days: number;
date: string;
delete_marker?: boolean;
delete_all?: boolean;
noncurrent_expiration_days?: number;
newer_noncurrent_expiration_versions?: number;
}
interface ITransitionLifecycle {
days: number;
date: string;
storage_class?: string;
noncurrent_transition_days?: number;
noncurrent_storage_class?: string;
}
export interface LifeCycleItem {
id: string;
prefix?: string;
expiration?: IExpirationLifecycle;
transition?: ITransitionLifecycle;
tags?: any;
status?: string;
}
interface MultiBucketResult {
bucketName: string;
error?: string;
}
interface MultiBucketResult {
results: MultiBucketResult[];
}
export interface ITiersDropDown {
label: string;
value: string;
}

View File

@@ -1,64 +0,0 @@
// This file is part of MinIO Console Server
// Copyright (c) 2021 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
import React from "react";
import { HelpBox, Box, Grid, breakPoints } from "mds";
interface IDistributedOnly {
iconComponent: any;
entity: string;
}
const DistributedOnly = ({ iconComponent, entity }: IDistributedOnly) => {
return (
<Grid container>
<Grid item xs={12}>
<HelpBox
title={`${entity} not available`}
iconComponent={iconComponent}
help={
<Box
sx={{
fontSize: "14px",
[`@media (max-width: ${breakPoints.sm}px)`]: {
display: "flex",
flexFlow: "column",
},
}}
>
<span>
This feature is not available for a single-disk setup.&nbsp;
</span>
<span>
Please deploy a server in{" "}
<a
href="https://min.io/docs/minio/linux/operations/install-deploy-manage/deploy-minio-multi-node-multi-drive.html?ref=con"
target="_blank"
rel="noopener"
>
Distributed Mode
</a>{" "}
to use this feature.
</span>
</Box>
}
/>
</Grid>
</Grid>
);
};
export default DistributedOnly;

View File

@@ -660,12 +660,6 @@ const IconsScreen = () => {
LicenseIcon
</Grid>
<Grid item xs={3} sm={2} md={1}>
<cicons.LifecycleConfigIcon />
<br />
LifecycleConfigIcon
</Grid>
<Grid item xs={3} sm={2} md={1}>
<cicons.LinkIcon />
<br />
@@ -1002,18 +996,6 @@ const IconsScreen = () => {
TenantsOutlineIcon
</Grid>
<Grid item xs={3} sm={2} md={1}>
<cicons.TiersIcon />
<br />
TiersIcon
</Grid>
<Grid item xs={3} sm={2} md={1}>
<cicons.TiersNotAvailableIcon />
<br />
TiersNotAvailableIcon
</Grid>
<Grid item xs={3} sm={2} md={1}>
<cicons.ToolsIcon />
<br />

View File

@@ -1,486 +0,0 @@
// This file is part of MinIO Console Server
// Copyright (c) 2021 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
import React, { Fragment, useCallback, useEffect, useState } from "react";
import { useNavigate, useParams } from "react-router-dom";
import get from "lodash/get";
import {
BackLink,
breakPoints,
Button,
FileSelector,
Grid,
InputBox,
PageLayout,
SectionTitle,
} from "mds";
import { api } from "api";
import { errorToHandler } from "api/errors";
import { ApiError } from "api/consoleApi";
import { modalStyleUtils } from "../../Common/FormComponents/common/styleLibrary";
import {
azureServiceName,
gcsServiceName,
minioServiceName,
s3ServiceName,
tierTypes,
} from "./utils";
import { IAM_PAGES } from "../../../../common/SecureComponent/permissions";
import { setErrorSnackMessage, setHelpName } from "../../../../systemSlice";
import { useAppDispatch } from "../../../../store";
import RegionSelectWrapper from "./RegionSelectWrapper";
import PageHeaderWrapper from "../../Common/PageHeaderWrapper/PageHeaderWrapper";
import HelpMenu from "../../HelpMenu";
const AddTierConfiguration = () => {
const dispatch = useAppDispatch();
const navigate = useNavigate();
const params = useParams();
//Local States
const [saving, setSaving] = useState<boolean>(false);
// Form Items
const [name, setName] = useState<string>("");
const [endpoint, setEndpoint] = useState<string>("");
const [bucket, setBucket] = useState<string>("");
const [prefix, setPrefix] = useState<string>("");
const [region, setRegion] = useState<string>("");
const [storageClass, setStorageClass] = useState<string>("");
const [accessKey, setAccessKey] = useState<string>("");
const [secretKey, setSecretKey] = useState<string>("");
const [creds, setCreds] = useState<string>("");
const [encodedCreds, setEncodedCreds] = useState<string>("");
const [accountName, setAccountName] = useState<string>("");
const [accountKey, setAccountKey] = useState<string>("");
const [titleSelection, setTitleSelection] = useState<string>("");
const type = get(params, "service", "s3");
// Validations
const [isFormValid, setIsFormValid] = useState<boolean>(true);
const [nameInputError, setNameInputError] = useState<string>("");
// Extra validation functions
const validName = useCallback(() => {
const patternAgainst = /^[A-Z0-9-_]+$/; // Only allow uppercase, numbers, dashes and underscores
if (patternAgainst.test(name)) {
setNameInputError("");
return true;
}
setNameInputError(
"Please verify that string is uppercase only and contains valid characters (numbers, dashes & underscores).",
);
return false;
}, [name]);
//Effects
useEffect(() => {
if (saving) {
let request = {};
let fields = {
name,
endpoint,
bucket,
prefix,
region,
};
let tierType = type;
switch (type) {
case "minio":
request = {
minio: {
...fields,
accesskey: accessKey,
secretkey: secretKey,
},
};
break;
case "s3":
request = {
s3: {
...fields,
accesskey: accessKey,
secretkey: secretKey,
storageclass: storageClass,
},
};
break;
case "gcs":
request = {
gcs: {
...fields,
creds: encodedCreds,
},
};
break;
case "azure":
request = {
azure: {
...fields,
accountname: accountName,
accountkey: accountKey,
},
};
}
let payload = {
type: tierType as
| "azure"
| "s3"
| "minio"
| "gcs"
| "unsupported"
| undefined,
...request,
};
api.admin
.addTier(payload)
.then(() => {
setSaving(false);
navigate(IAM_PAGES.TIERS);
})
.catch(async (res) => {
const err = (await res.json()) as ApiError;
setSaving(false);
dispatch(setErrorSnackMessage(errorToHandler(err)));
});
}
}, [
accessKey,
accountKey,
accountName,
bucket,
encodedCreds,
endpoint,
name,
prefix,
region,
saving,
secretKey,
dispatch,
storageClass,
type,
navigate,
]);
useEffect(() => {
let valid = true;
if (type === "") {
valid = false;
}
if (name === "" || !validName()) {
valid = false;
}
if (endpoint === "") {
valid = false;
}
if (bucket === "") {
valid = false;
}
if (region === "" && type !== "minio") {
valid = false;
}
if (type === "s3" || type === "minio") {
if (accessKey === "") {
valid = false;
}
if (secretKey === "") {
valid = false;
}
}
if (type === "gcs") {
if (encodedCreds === "") {
valid = false;
}
}
if (type === "azure") {
if (accountName === "") {
valid = false;
}
if (accountKey === "") {
valid = false;
}
}
setIsFormValid(valid);
}, [
accessKey,
accountKey,
accountName,
bucket,
encodedCreds,
endpoint,
isFormValid,
name,
prefix,
region,
secretKey,
storageClass,
type,
validName,
]);
useEffect(() => {
switch (type) {
case "gcs":
setEndpoint("https://storage.googleapis.com");
setTitleSelection("Google Cloud");
break;
case "s3":
setEndpoint("https://s3.amazonaws.com");
setTitleSelection("Amazon S3");
break;
case "azure":
setEndpoint("http://blob.core.windows.net");
setTitleSelection("Azure");
break;
case "minio":
setEndpoint("");
setTitleSelection("MinIO");
}
}, [type]);
//Fetch Actions
const submitForm = (event: React.FormEvent) => {
event.preventDefault();
setSaving(true);
};
// Input actions
const updateTierName = (e: React.ChangeEvent<HTMLInputElement>) => {
setName(e.target.value.toUpperCase());
};
const targetElement = tierTypes.find((item) => item.serviceName === type);
useEffect(() => {
dispatch(setHelpName("add-tier-configuration"));
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
return (
<Fragment>
<PageHeaderWrapper
label={
<Fragment>
<BackLink
label={"Add Tier"}
onClick={() => navigate(IAM_PAGES.TIERS_ADD)}
/>
</Fragment>
}
actions={<HelpMenu />}
/>
<PageLayout>
<Grid
item
xs={12}
sx={{
border: "1px solid #eaeaea",
padding: "25px",
}}
>
<form noValidate onSubmit={submitForm}>
{type !== "" && targetElement ? (
<SectionTitle icon={targetElement.logo} sx={{ marginBottom: 20 }}>
{titleSelection ? titleSelection : ""} - Add Tier Configuration
</SectionTitle>
) : null}
<Grid
item
xs={12}
sx={{
display: "grid",
gridTemplateColumns: "1fr 1fr",
gridAutoFlow: "row",
gridRowGap: 20,
gridColumnGap: 50,
[`@media (max-width: ${breakPoints.sm}px)`]: {
gridTemplateColumns: "1fr",
gridAutoFlow: "dense",
},
}}
>
{type !== "" && (
<Fragment>
<InputBox
id="name"
name="name"
label="Name"
placeholder="Enter Name (Eg. REMOTE-TIER)"
value={name}
onChange={updateTierName}
error={nameInputError}
required
/>
<InputBox
id="endpoint"
name="endpoint"
label="Endpoint"
placeholder="Enter Endpoint"
value={endpoint}
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
setEndpoint(e.target.value);
}}
required
/>
{(type === s3ServiceName || type === minioServiceName) && (
<Fragment>
<InputBox
id="accessKey"
name="accessKey"
label="Access Key"
placeholder="Enter Access Key"
value={accessKey}
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
setAccessKey(e.target.value);
}}
required
/>
<InputBox
id="secretKey"
name="secretKey"
label="Secret Key"
placeholder="Enter Secret Key"
value={secretKey}
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
setSecretKey(e.target.value);
}}
required
/>
</Fragment>
)}
{type === gcsServiceName && (
<FileSelector
accept=".json"
id="creds"
label="Credentials"
name="creds"
returnEncodedData
onChange={(_, fileName, encodedValue) => {
if (encodedValue) {
setEncodedCreds(encodedValue);
setCreds(fileName);
}
}}
value={creds}
required
/>
)}
{type === azureServiceName && (
<Fragment>
<InputBox
id="accountName"
name="accountName"
label="Account Name"
placeholder="Enter Account Name"
value={accountName}
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
setAccountName(e.target.value);
}}
required
/>
<InputBox
id="accountKey"
name="accountKey"
label="Account Key"
placeholder="Enter Account Key"
value={accountKey}
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
setAccountKey(e.target.value);
}}
required
/>
</Fragment>
)}
<InputBox
id="bucket"
name="bucket"
label="Bucket"
placeholder="Enter Bucket"
value={bucket}
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
setBucket(e.target.value);
}}
required
/>
<InputBox
id="prefix"
name="prefix"
label="Prefix"
placeholder="Enter Prefix"
value={prefix}
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
setPrefix(e.target.value);
}}
/>
<RegionSelectWrapper
onChange={(value) => {
setRegion(value);
}}
required={type !== "minio"}
label={"Region"}
id="region"
type={type as "azure" | "s3" | "minio" | "gcs"}
/>
{type === s3ServiceName && (
<InputBox
id="storageClass"
name="storageClass"
label="Storage Class"
placeholder="Enter Storage Class"
value={storageClass}
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
setStorageClass(e.target.value);
}}
/>
)}
</Fragment>
)}
</Grid>
<Grid item xs={12} sx={modalStyleUtils.modalButtonBar}>
<Button
id={"save-tier-configuration"}
type="submit"
variant="callAction"
disabled={saving || !isFormValid}
label={"Save Tier Configuration"}
/>
</Grid>
</form>
</Grid>
</PageLayout>
</Fragment>
);
};
export default AddTierConfiguration;

View File

@@ -1,86 +0,0 @@
// This file is part of MinIO Console Server
// Copyright (c) 2024 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
import React from "react";
import { ConfirmModalIcon } from "mds";
import { api } from "api";
import { setErrorSnackMessage } from "../../../../systemSlice";
import { useAppDispatch } from "../../../../store";
import ConfirmDialog from "screens/Console/Common/ModalWrapper/ConfirmDialog";
interface ITierDeleteModal {
open: boolean;
closeModalAndRefresh: (refresh: boolean) => any;
tierName: string;
}
const DeleteTierConfirmModal = ({
open,
closeModalAndRefresh,
tierName,
}: ITierDeleteModal) => {
const dispatch = useAppDispatch();
const deleteTier = () => {
if (tierName !== "") {
api.admin
.removeTier(tierName)
.then(() => {
closeModalAndRefresh(true);
})
.catch((err) => {
err.json().then((body: any) => {
dispatch(
setErrorSnackMessage({
errorMessage: body.message,
detailedError: body.detailedMessage,
}),
);
});
closeModalAndRefresh(false);
});
} else {
setErrorSnackMessage({
errorMessage: "There was an error deleting the tier",
detailedError: "",
});
}
};
return (
<ConfirmDialog
title={`Delete Tier`}
confirmText={"Delete"}
isOpen={open}
titleIcon={<ConfirmModalIcon />}
isLoading={false}
onConfirm={() => deleteTier()}
onClose={() => closeModalAndRefresh(false)}
confirmationContent={
<React.Fragment>
Are you sure you want to delete the tier <strong>{tierName}</strong>?
<br />
<br />
<strong> Please note</strong>
<br /> Only empty tiers can be deleted. If the tier has had objects
transitioned into it, it cannot be removed.
</React.Fragment>
}
/>
);
};
export default DeleteTierConfirmModal;

View File

@@ -1,536 +0,0 @@
// This file is part of MinIO Console Server
// Copyright (c) 2021 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
import React, { Fragment, useEffect, useState } from "react";
import get from "lodash/get";
import { useSelector } from "react-redux";
import { useNavigate } from "react-router-dom";
import {
ActionLink,
AddIcon,
Box,
Button,
DataTable,
Grid,
HelpBox,
PageLayout,
ProgressBar,
RefreshIcon,
TierOfflineIcon,
TierOnlineIcon,
TiersIcon,
TiersNotAvailableIcon,
} from "mds";
import { api } from "api";
import { errorToHandler } from "api/errors";
import { Tier } from "api/consoleApi";
import { actionsTray } from "../../Common/FormComponents/common/styleLibrary";
import {
CONSOLE_UI_RESOURCE,
IAM_PAGES,
IAM_PERMISSIONS,
IAM_ROLES,
IAM_SCOPES,
} from "../../../../common/SecureComponent/permissions";
import {
hasPermission,
SecureComponent,
} from "../../../../common/SecureComponent";
import { tierTypes } from "./utils";
import {
selDistSet,
setErrorSnackMessage,
setHelpName,
} from "../../../../systemSlice";
import { useAppDispatch } from "../../../../store";
import SearchBox from "../../Common/SearchBox";
import withSuspense from "../../Common/Components/withSuspense";
import DistributedOnly from "../../Common/DistributedOnly/DistributedOnly";
import TooltipWrapper from "../../Common/TooltipWrapper/TooltipWrapper";
import PageHeaderWrapper from "../../Common/PageHeaderWrapper/PageHeaderWrapper";
import HelpMenu from "../../HelpMenu";
import DeleteTierConfirmModal from "./DeleteTierConfirmModal";
const UpdateTierCredentialsModal = withSuspense(
React.lazy(() => import("./UpdateTierCredentialsModal")),
);
const ListTiersConfiguration = () => {
const dispatch = useAppDispatch();
const navigate = useNavigate();
const distributedSetup = useSelector(selDistSet);
const [records, setRecords] = useState<Tier[]>([]);
const [filter, setFilter] = useState<string>("");
const [isLoading, setIsLoading] = useState<boolean>(true);
const [updateCredentialsOpen, setUpdateCredentialsOpen] =
useState<boolean>(false);
const [deleteTierModalOpen, setDeleteTierModalOpen] =
useState<boolean>(false);
const [selectedTier, setSelectedTier] = useState<Tier>({
type: "unsupported",
status: false,
});
const hasSetTier = hasPermission(CONSOLE_UI_RESOURCE, [
IAM_SCOPES.ADMIN_SET_TIER,
]);
useEffect(() => {
if (isLoading) {
if (distributedSetup) {
const fetchRecords = () => {
api.admin
.tiersList()
.then((res) => {
setRecords(res.data.items || []);
setIsLoading(false);
})
.catch((err) => {
dispatch(setErrorSnackMessage(errorToHandler(err.error)));
setIsLoading(false);
});
};
fetchRecords();
} else {
setIsLoading(false);
}
}
}, [isLoading, dispatch, distributedSetup]);
const filteredRecords = records.filter((b: Tier) => {
if (filter === "") {
return true;
}
const getItemName = get(b, `${b.type}.name`, "");
const getItemType = get(b, `type`, "");
return getItemName.indexOf(filter) >= 0 || getItemType.indexOf(filter) >= 0;
});
const addTier = () => {
navigate(IAM_PAGES.TIERS_ADD);
};
const renderTierName = (item: Tier) => {
const name = get(item, `${item.type}.name`, "");
if (name !== null) {
return <b>{name}</b>;
}
return "";
};
const renderTierType = (item: string) => {
const { logoXs } =
tierTypes.find((tierConf) => tierConf.serviceName === item) || {};
if (item) {
return (
<Box
sx={{
display: "flex",
alignItems: "center",
"& .min-icon": {
width: "18px",
height: "22px",
},
}}
>
{logoXs}
</Box>
);
}
return "";
};
const renderTierStatus = (item: boolean) => {
if (item) {
return (
<Grid
container
sx={{
display: "flex",
alignItems: "center",
justifyItems: "start",
color: "#4CCB92",
fontSize: "8px",
flexDirection: "column",
}}
>
<TierOnlineIcon style={{ fill: "#4CCB92", width: 14, height: 14 }} />
ONLINE
</Grid>
);
}
return (
<Grid
container
sx={{
display: "flex",
flexDirection: "column",
alignItems: "center",
color: "#C83B51",
fontSize: "8px",
}}
>
<TierOfflineIcon style={{ fill: "#C83B51", width: 14, height: 14 }} />
OFFLINE
</Grid>
);
};
const renderTierPrefix = (item: Tier) => {
const prefix = get(item, `${item.type}.prefix`, "");
if (prefix !== null) {
return prefix;
}
return "";
};
const renderTierEndpoint = (item: Tier) => {
const endpoint = get(item, `${item.type}.endpoint`, "");
if (endpoint !== null) {
return endpoint;
}
return "";
};
const renderTierBucket = (item: Tier) => {
const bucket = get(item, `${item.type}.bucket`, "");
if (bucket !== null) {
return bucket;
}
return "";
};
const renderTierRegion = (item: Tier) => {
const region = get(item, `${item.type}.region`, "");
if (region !== null) {
return region;
}
return "";
};
const renderTierUsage = (item: Tier) => {
const endpoint = get(item, `${item.type}.usage`, "");
if (endpoint !== null) {
return endpoint;
}
return "";
};
const renderTierObjects = (item: Tier) => {
const endpoint = get(item, `${item.type}.objects`, "");
if (endpoint !== null) {
return endpoint;
}
return "";
};
const renderTierVersions = (item: Tier) => {
const endpoint = get(item, `${item.type}.versions`, "");
if (endpoint !== null) {
return endpoint;
}
return "";
};
const closeTierCredentials = () => {
setUpdateCredentialsOpen(false);
};
const closeDeleteTier = () => {
setDeleteTierModalOpen(false);
setIsLoading(true);
};
useEffect(() => {
dispatch(setHelpName("list-tiers-configuration"));
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
return (
<Fragment>
{updateCredentialsOpen && (
<UpdateTierCredentialsModal
open={updateCredentialsOpen}
tierData={selectedTier}
closeModalAndRefresh={closeTierCredentials}
/>
)}
{deleteTierModalOpen && (
<DeleteTierConfirmModal
open={deleteTierModalOpen}
tierName={get(selectedTier, `${selectedTier.type}.name`, "")}
closeModalAndRefresh={closeDeleteTier}
/>
)}
<PageHeaderWrapper label="Tiers" actions={<HelpMenu />} />
<PageLayout>
{!distributedSetup ? (
<DistributedOnly
entity={"Tiers"}
iconComponent={<TiersNotAvailableIcon />}
/>
) : (
<Fragment>
<Grid item xs={12} sx={actionsTray.actionsTray}>
<SearchBox
placeholder="Filter"
onChange={setFilter}
value={filter}
sx={{
marginRight: "auto",
maxWidth: 380,
}}
/>
<Box
sx={{
display: "flex",
flexWrap: "nowrap",
gap: 5,
}}
>
<Button
id={"refresh-list"}
icon={<RefreshIcon />}
label={`Refresh List`}
onClick={() => {
setIsLoading(true);
}}
/>
<TooltipWrapper
tooltip={
hasSetTier
? ""
: "You require additional permissions in order to create a new Tier. Please ask your MinIO administrator to grant you " +
IAM_SCOPES.ADMIN_SET_TIER +
" permission in order to create a Tier."
}
>
<SecureComponent
scopes={[IAM_SCOPES.ADMIN_SET_TIER]}
resource={CONSOLE_UI_RESOURCE}
errorProps={{ disabled: true }}
>
<Button
id={"add-tier"}
icon={<AddIcon />}
label={`Create Tier`}
onClick={addTier}
variant="callAction"
/>
</SecureComponent>
</TooltipWrapper>
</Box>
</Grid>
{isLoading && <ProgressBar />}
{!isLoading && (
<Fragment>
{records.length > 0 && (
<Fragment>
<Grid item xs={12}>
<SecureComponent
scopes={[IAM_SCOPES.ADMIN_LIST_TIERS]}
resource={CONSOLE_UI_RESOURCE}
errorProps={{ disabled: true }}
>
<DataTable
itemActions={[
{
type: "edit",
onClick: (tierData: Tier) => {
setSelectedTier(tierData);
setUpdateCredentialsOpen(true);
},
},
{
type: "delete",
isDisabled: !hasPermission(
"*",
IAM_PERMISSIONS[IAM_ROLES.BUCKET_LIFECYCLE],
true,
),
onClick: (tierData: Tier) => {
setSelectedTier(tierData);
setDeleteTierModalOpen(true);
},
},
]}
columns={[
{
label: "Tier Name",
elementKey: "type",
renderFunction: renderTierName,
renderFullObject: true,
},
{
label: "Status",
elementKey: "status",
renderFunction: renderTierStatus,
width: 50,
},
{
label: "Type",
elementKey: "type",
renderFunction: renderTierType,
width: 50,
},
{
label: "Endpoint",
elementKey: "type",
renderFunction: renderTierEndpoint,
renderFullObject: true,
},
{
label: "Bucket",
elementKey: "type",
renderFunction: renderTierBucket,
renderFullObject: true,
},
{
label: "Prefix",
elementKey: "type",
renderFunction: renderTierPrefix,
renderFullObject: true,
},
{
label: "Region",
elementKey: "type",
renderFunction: renderTierRegion,
renderFullObject: true,
},
{
label: "Usage",
elementKey: "type",
renderFunction: renderTierUsage,
renderFullObject: true,
},
{
label: "Objects",
elementKey: "type",
renderFunction: renderTierObjects,
renderFullObject: true,
},
{
label: "Versions",
elementKey: "type",
renderFunction: renderTierVersions,
renderFullObject: true,
},
]}
isLoading={isLoading}
records={filteredRecords}
entityName="Tiers"
idField="service_name"
customPaperHeight={"400px"}
/>
</SecureComponent>
</Grid>
<Grid
item
xs={12}
sx={{
marginTop: "15px",
}}
>
<HelpBox
title={"Learn more about TIERS"}
iconComponent={<TiersIcon />}
help={
<Fragment>
Tiers are used by the MinIO Object Lifecycle
Management which allows creating rules for time or
date based automatic transition or expiry of
objects. For object transition, MinIO automatically
moves the object to a configured remote storage
tier.
<br />
<br />
You can learn more at our{" "}
<a
href="https://min.io/docs/minio/linux/administration/object-management/object-lifecycle-management.html?ref=con"
target="_blank"
rel="noopener"
>
documentation
</a>
.
</Fragment>
}
/>
</Grid>
</Fragment>
)}
{records.length === 0 && (
<HelpBox
title={"Tiers"}
iconComponent={<TiersIcon />}
help={
<Fragment>
Tiers are used by the MinIO Object Lifecycle Management
which allows creating rules for time or date based
automatic transition or expiry of objects. For object
transition, MinIO automatically moves the object to a
configured remote storage tier.
<br />
<br />
{hasSetTier ? (
<div>
To get started,{" "}
<ActionLink
isLoading={false}
label={""}
onClick={addTier}
>
Create Tier
</ActionLink>
.
</div>
) : (
""
)}
</Fragment>
}
/>
)}
</Fragment>
)}
</Fragment>
)}
</PageLayout>
</Fragment>
);
};
export default ListTiersConfiguration;

View File

@@ -1,103 +0,0 @@
// This file is part of MinIO Console Server
// Copyright (c) 2022 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
import React, { useState } from "react";
import { Autocomplete, InputBox, SelectorType } from "mds";
import s3Regions from "./s3-regions";
import gcsRegions from "./gcs-regions";
import azRegions from "./azure-regions";
const getRegions = (type: string): any => {
let regions: SelectorType[] = [];
if (type === "s3") {
regions = s3Regions;
}
if (type === "gcs") {
regions = gcsRegions;
}
if (type === "azure") {
regions = azRegions;
}
return regions.map((item) => ({
value: item.value,
label: `${item.label} - ${item.value}`,
}));
};
interface RegionSelectBoxProps {
label: string;
onChange: (value: string) => void;
value?: string | boolean;
id: string;
disabled?: boolean;
type: "minio" | "s3" | "gcs" | "azure";
tooltip?: string;
required?: boolean;
placeholder?: string;
}
const RegionSelectWrapper = ({
label,
onChange,
type,
tooltip = "",
required = false,
disabled,
placeholder,
}: RegionSelectBoxProps) => {
const regionList = getRegions(type);
const [value, setValue] = useState<string>("");
if (type === "minio") {
return (
<InputBox
label={label}
disabled={disabled}
required={required}
tooltip={tooltip}
value={value}
placeholder={placeholder}
id={"region-list"}
onChange={(e) => {
setValue(e.target.value);
onChange(e.target.value);
}}
/>
);
}
return (
<Autocomplete
label={label}
disabled={disabled}
required={required}
tooltip={tooltip}
options={regionList}
value={value}
placeholder={placeholder}
id={"region-list"}
onChange={(newValue) => {
setValue(newValue);
onChange(newValue);
}}
/>
);
};
export default RegionSelectWrapper;

View File

@@ -1,70 +0,0 @@
// This file is part of MinIO Console Server
// Copyright (c) 2022 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
import React from "react";
import styled from "styled-components";
import get from "lodash/get";
const TierButtonBase = styled.button(({ theme }) => ({
background: get(theme, "boxBackground", "#FFF"),
border: `${get(theme, "borderColor", "#E2E2E2")} 1px solid`,
borderRadius: 5,
height: 80,
display: "flex",
alignItems: "center",
justifyContent: "start",
marginBottom: 16,
marginRight: 8,
cursor: "pointer",
overflow: "hidden",
"&:hover": {
backgroundColor: get(theme, "buttons.regular.hover.background", "#ebebeb"),
},
"& .imageContainer": {
width: 80,
"& .min-icon": {
maxWidth: 46,
maxHeight: 46,
},
},
"& .tierNotifTitle": {
color: get(theme, "buttons.callAction.enabled.background", "#07193E"),
fontSize: 16,
fontFamily: "Inter,sans-serif",
paddingLeft: 18,
fontWeight: "bold",
},
}));
type TierTypeCardProps = {
onClick: (name: string) => void;
icon?: any;
name: string;
};
const TierTypeCard = ({ onClick, icon, name }: TierTypeCardProps) => {
return (
<TierButtonBase
onClick={() => {
onClick(name);
}}
>
<span className={"imageContainer"}>{icon}</span>
<span className={"tierNotifTitle"}>{name}</span>
</TierButtonBase>
);
};
export default TierTypeCard;

View File

@@ -1,150 +0,0 @@
// This file is part of MinIO Console Server
// Copyright (c) 2021 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
import React, { Fragment, useEffect } from "react";
import { useNavigate } from "react-router-dom";
import { tierTypes } from "./utils";
import { IAM_PAGES } from "../../../../common/SecureComponent/permissions";
import TierTypeCard from "./TierTypeCard";
import {
BackLink,
Box,
breakPoints,
FormLayout,
HelpBox,
PageLayout,
TiersIcon,
} from "mds";
import PageHeaderWrapper from "../../Common/PageHeaderWrapper/PageHeaderWrapper";
import HelpMenu from "../../HelpMenu";
import { setHelpName } from "../../../../systemSlice";
import { useAppDispatch } from "../../../../store";
const TierTypeSelector = () => {
const navigate = useNavigate();
const typeSelect = (selectName: string) => {
navigate(`${IAM_PAGES.TIERS_ADD}/${selectName}`);
};
const dispatch = useAppDispatch();
useEffect(() => {
dispatch(setHelpName("tier-type-selector"));
}, [dispatch]);
return (
<Fragment>
<PageHeaderWrapper
label={
<Fragment>
<BackLink
label="Tier Types"
onClick={() => navigate(IAM_PAGES.TIERS)}
/>
</Fragment>
}
actions={<HelpMenu />}
/>
<PageLayout>
<FormLayout
title={"Select Tier Type"}
icon={<TiersIcon />}
helpBox={
<HelpBox
iconComponent={<TiersIcon />}
title={"Tier Types"}
help={
<Fragment>
MinIO supports creating object transition lifecycle management
rules, where MinIO can automatically move an object to a
remote storage tier.
<br />
<br />
MinIO supports the following Tier types:
<br />
<ul>
<li>
<a
href="https://min.io/docs/minio/kubernetes/upstream/administration/object-management/transition-objects-to-s3.html#minio-lifecycle-management-transition-to-s3?ref=con"
target="_blank"
rel="noopener"
>
MinIO or other S3-compatible storage
</a>
</li>
<li>
<a
href="https://min.io/docs/minio/kubernetes/upstream/administration/object-management/transition-objects-to-s3.html#minio-lifecycle-management-transition-to-s3?ref=con"
target="_blank"
rel="noopener"
>
Amazon S3
</a>
</li>
<li>
<a
href="https://min.io/docs/minio/kubernetes/upstream/administration/object-management/transition-objects-to-gcs.html#minio-lifecycle-management-transition-to-gcs?ref=con"
target="_blank"
rel="noopener"
>
Google Cloud Storage
</a>
</li>
<li>
<a
href="https://min.io/docs/minio/kubernetes/upstream/administration/object-management/transition-objects-to-azure.html#minio-lifecycle-management-transition-to-azure?ref=con"
target="_blank"
rel="noopener"
>
Microsoft Azure Blob Storage
</a>
</li>
</ul>
</Fragment>
}
/>
}
>
<Box
sx={{
margin: "15px",
display: "grid",
gridGap: "20px",
gridTemplateColumns: "repeat(2, 1fr)",
[`@media (max-width: ${breakPoints.md}px)`]: {
gridTemplateColumns: "repeat(1, 1fr)",
},
}}
>
{tierTypes.map((tierType, index) => (
<TierTypeCard
key={`tierOpt-${index.toString}-${tierType.targetTitle}`}
name={tierType.targetTitle}
onClick={() => {
typeSelect(tierType.serviceName);
}}
icon={tierType.logo}
/>
))}
</Box>
</FormLayout>
</PageLayout>
</Fragment>
);
};
export default TierTypeSelector;

View File

@@ -1,217 +0,0 @@
// This file is part of MinIO Console Server
// Copyright (c) 2021 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
import React, { Fragment, useEffect, useState } from "react";
import get from "lodash/get";
import {
Button,
FileSelector,
FormLayout,
Grid,
InputBox,
LockIcon,
ProgressBar,
} from "mds";
import { Tier } from "api/consoleApi";
import { api } from "api";
import { errorToHandler } from "api/errors";
import { modalStyleUtils } from "../../Common/FormComponents/common/styleLibrary";
import { setModalErrorSnackMessage } from "../../../../systemSlice";
import { useAppDispatch } from "../../../../store";
import ModalWrapper from "../../Common/ModalWrapper/ModalWrapper";
interface ITierCredentialsModal {
open: boolean;
closeModalAndRefresh: (refresh: boolean) => any;
tierData: Tier;
}
const UpdateTierCredentialsModal = ({
open,
closeModalAndRefresh,
tierData,
}: ITierCredentialsModal) => {
const dispatch = useAppDispatch();
const [savingTiers, setSavingTiers] = useState<boolean>(false);
const [creds, setCreds] = useState<string>("");
const [encodedCreds, setEncodedCreds] = useState<string>("");
const [accountName, setAccountName] = useState<string>("");
const [accountKey, setAccountKey] = useState<string>("");
// Validations
const [isFormValid, setIsFormValid] = useState<boolean>(true);
const type = get(tierData, "type", "");
const name = get(tierData, `${type}.name`, "");
useEffect(() => {
let valid = true;
if (type === "s3" || type === "azure" || type === "minio") {
if (accountName === "" || accountKey === "") {
valid = false;
}
} else if (type === "gcs") {
if (encodedCreds === "") {
valid = false;
}
}
setIsFormValid(valid);
}, [accountKey, accountName, encodedCreds, type]);
const addRecord = () => {
let rules = {};
if (type === "s3" || type === "azure" || type === "minio") {
rules = {
access_key: accountName,
secret_key: accountKey,
};
} else if (type === "gcs") {
rules = {
creds: encodedCreds,
};
}
if (name !== "") {
api.admin
.editTierCredentials(
type as "azure" | "s3" | "minio" | "gcs",
name,
rules,
)
.then(() => {
setSavingTiers(false);
closeModalAndRefresh(true);
})
.catch((err) => {
setSavingTiers(false);
dispatch(setModalErrorSnackMessage(errorToHandler(err.error)));
});
} else {
setModalErrorSnackMessage({
errorMessage: "There was an error retrieving tier information",
detailedError: "",
});
}
};
return (
<ModalWrapper
modalOpen={open}
titleIcon={<LockIcon />}
onClose={() => {
closeModalAndRefresh(false);
}}
title={`Update Credentials - ${type} / ${name}`}
>
<form
noValidate
autoComplete="off"
onSubmit={(e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
setSavingTiers(true);
addRecord();
}}
>
<FormLayout withBorders={false} containerPadding={false}>
{(type === "s3" || type === "minio") && (
<Fragment>
<InputBox
id="accessKey"
name="accessKey"
label="Access Key"
placeholder="Enter Access Key"
value={accountName}
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
setAccountName(e.target.value);
}}
/>
<InputBox
id="secretKey"
name="secretKey"
label="Secret Key"
placeholder="Enter Secret Key"
value={accountKey}
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
setAccountKey(e.target.value);
}}
/>
</Fragment>
)}
{type === "gcs" && (
<Fragment>
<FileSelector
accept=".json"
id="creds"
label="Credentials"
name="creds"
returnEncodedData
onChange={(_, fileName, encodedValue) => {
if (encodedValue) {
setEncodedCreds(encodedValue);
setCreds(fileName);
}
}}
value={creds}
/>
</Fragment>
)}
{type === "azure" && (
<Fragment>
<InputBox
id="accountName"
name="accountName"
label="Account Name"
placeholder="Enter Account Name"
value={accountName}
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
setAccountName(e.target.value);
}}
/>
<InputBox
id="accountKey"
name="accountKey"
label="Account Key"
placeholder="Enter Account Key"
value={accountKey}
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
setAccountKey(e.target.value);
}}
/>
</Fragment>
)}
</FormLayout>
{savingTiers && (
<Grid item xs={12}>
<ProgressBar />
</Grid>
)}
<Grid item xs={12} sx={modalStyleUtils.modalButtonBar}>
<Button
id={"save-credentials"}
type="submit"
variant="callAction"
disabled={savingTiers || !isFormValid}
label={"Save"}
/>
</Grid>
</form>
</ModalWrapper>
);
};
export default UpdateTierCredentialsModal;

View File

@@ -1,321 +0,0 @@
// This file is part of MinIO Console Server
// Copyright (c) 2022 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
import { SelectorType } from "mds";
const azureRegions: SelectorType[] = [
{
label: "Asia",
value: "asia",
},
{
label: "Asia Pacific",
value: "asiapacific",
},
{
label: "Australia",
value: "australia",
},
{
label: "Australia Central",
value: "australiacentral",
},
{
label: "Australia Central 2",
value: "australiacentral2",
},
{
label: "Australia East",
value: "australiaeast",
},
{
label: "Australia Southeast",
value: "australiasoutheast",
},
{
label: "Brazil",
value: "brazil",
},
{
label: "Brazil South",
value: "brazilsouth",
},
{
label: "Brazil Southeast",
value: "brazilsoutheast",
},
{
label: "Canada",
value: "canada",
},
{
label: "Canada Central",
value: "canadacentral",
},
{
label: "Canada East",
value: "canadaeast",
},
{
label: "Central India",
value: "centralindia",
},
{
label: "Central US",
value: "centralus",
},
{
label: "Central US (Stage)",
value: "centralusstage",
},
{
label: "Central US EUAP",
value: "centraluseuap",
},
{
label: "East Asia",
value: "eastasia",
},
{
label: "East Asia (Stage)",
value: "eastasiastage",
},
{
label: "East US",
value: "eastus",
},
{
label: "East US (Stage)",
value: "eastusstage",
},
{
label: "East US 2",
value: "eastus2",
},
{
label: "East US 2 (Stage)",
value: "eastus2stage",
},
{
label: "East US 2 EUAP",
value: "eastus2euap",
},
{
label: "Europe",
value: "europe",
},
{
label: "France",
value: "france",
},
{
label: "France Central",
value: "francecentral",
},
{
label: "France South",
value: "francesouth",
},
{
label: "Germany",
value: "germany",
},
{
label: "Germany North",
value: "germanynorth",
},
{
label: "Germany West Central",
value: "germanywestcentral",
},
{
label: "Global",
value: "global",
},
{
label: "India",
value: "india",
},
{
label: "Japan",
value: "japan",
},
{
label: "Japan East",
value: "japaneast",
},
{
label: "Japan West",
value: "japanwest",
},
{
label: "Jio India Central",
value: "jioindiacentral",
},
{
label: "Jio India West",
value: "jioindiawest",
},
{
label: "Korea",
value: "korea",
},
{
label: "Korea Central",
value: "koreacentral",
},
{
label: "Korea South",
value: "koreasouth",
},
{
label: "North Central US",
value: "northcentralus",
},
{
label: "North Central US (Stage)",
value: "northcentralusstage",
},
{
label: "North Europe",
value: "northeurope",
},
{
label: "Norway",
value: "norway",
},
{
label: "Norway East",
value: "norwayeast",
},
{
label: "Norway West",
value: "norwaywest",
},
{
label: "South Africa",
value: "southafrica",
},
{
label: "South Africa North",
value: "southafricanorth",
},
{
label: "South Africa West",
value: "southafricawest",
},
{
label: "South Central US",
value: "southcentralus",
},
{
label: "South Central US (Stage)",
value: "southcentralusstage",
},
{
label: "South India",
value: "southindia",
},
{
label: "Southeast Asia",
value: "southeastasia",
},
{
label: "Southeast Asia (Stage)",
value: "southeastasiastage",
},
{
label: "Sweden Central",
value: "swedencentral",
},
{
label: "Switzerland",
value: "switzerland",
},
{
label: "Switzerland North",
value: "switzerlandnorth",
},
{
label: "Switzerland West",
value: "switzerlandwest",
},
{
label: "UAE Central",
value: "uaecentral",
},
{
label: "UAE North",
value: "uaenorth",
},
{
label: "UK South",
value: "uksouth",
},
{
label: "UK West",
value: "ukwest",
},
{
label: "United Arab Emirates",
value: "uae",
},
{
label: "United Kingdom",
value: "uk",
},
{
label: "United States",
value: "unitedstates",
},
{
label: "United States EUAP",
value: "unitedstateseuap",
},
{
label: "West Central US",
value: "westcentralus",
},
{
label: "West Europe",
value: "westeurope",
},
{
label: "West India",
value: "westindia",
},
{
label: "West US",
value: "westus",
},
{
label: "West US (Stage)",
value: "westusstage",
},
{
label: "West US 2",
value: "westus2",
},
{
label: "West US 2 (Stage)",
value: "westus2stage",
},
{
label: "West US 3",
value: "westus3",
},
];
export default azureRegions;

View File

@@ -1,51 +0,0 @@
// This file is part of MinIO Console Server
// Copyright (c) 2022 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
import { SelectorType } from "mds";
const gcsRegions: SelectorType[] = [
{ label: "Montréal", value: "NORTHAMERICA-NORTHEAST1" },
{ label: "Toronto", value: "NORTHAMERICA-NORTHEAST2" },
{ label: "Iowa", value: "US-CENTRAL1" },
{ label: "South Carolina", value: "US-EAST1" },
{ label: "Northern Virginia", value: "US-EAST4" },
{ label: "Oregon", value: "US-WEST1" },
{ label: "Los Angeles", value: "US-WEST2" },
{ label: "Salt Lake City", value: "US-WEST3" },
{ label: "Las Vegas", value: "US-WEST4" },
{ label: "São Paulo", value: "SOUTHAMERICA-EAST1" },
{ label: "Santiago", value: "SOUTHAMERICA-WEST1" },
{ label: "Warsaw", value: "EUROPE-CENTRAL2" },
{ label: "Finland", value: "EUROPE-NORTH1" },
{ label: "Belgium", value: "EUROPE-WEST1" },
{ label: "London", value: "EUROPE-WEST2" },
{ label: "Frankfurt", value: "EUROPE-WEST3" },
{ label: "Netherlands", value: "EUROPE-WEST4" },
{ label: "Zürich", value: "EUROPE-WEST6" },
{ label: "Taiwan", value: "ASIA-EAST1" },
{ label: "Hong Kong", value: "ASIA-EAST2" },
{ label: "Tokyo", value: "ASIA-NORTHEAST1" },
{ label: "Osaka", value: "ASIA-NORTHEAST2" },
{ label: "Seoul", value: "ASIA-NORTHEAST3" },
{ label: "Mumbai", value: "ASIA-SOUTH1" },
{ label: "Delhi", value: "ASIA-SOUTH2" },
{ label: "Singapore", value: "ASIA-SOUTHEAST1" },
{ label: "Jakarta", value: "ASIA-SOUTHEAST2" },
{ label: "Sydney", value: "AUSTRALIA-SOUTHEAST1" },
{ label: "Melbourne", value: "AUSTRALIA-SOUTHEAST2" },
];
export default gcsRegions;

View File

@@ -1,48 +0,0 @@
// This file is part of MinIO Console Server
// Copyright (c) 2022 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
import { SelectorType } from "mds";
const s3Regions: SelectorType[] = [
{ label: "US East (Ohio)", value: "us-east-2" },
{ label: "US East (N. Virginia)", value: "us-east-1" },
{ label: "US West (N. California)", value: "us-west-1" },
{ label: "US West (Oregon)", value: "us-west-2" },
{ label: "Africa (Cape Town)", value: "af-south-1" },
{ label: "Asia Pacific (Hong Kong)***", value: "ap-east-1" },
{ label: "Asia Pacific (Jakarta)", value: "ap-southeast-3" },
{ label: "Asia Pacific (Mumbai)", value: "ap-south-1" },
{ label: "Asia Pacific (Osaka)", value: "ap-northeast-3" },
{ label: "Asia Pacific (Seoul)", value: "ap-northeast-2" },
{ label: "Asia Pacific (Singapore)", value: "ap-southeast-1" },
{ label: "Asia Pacific (Sydney)", value: "ap-southeast-2" },
{ label: "Asia Pacific (Tokyo)", value: "ap-northeast-1" },
{ label: "Canada (Central)", value: "ca-central-1" },
{ label: "China (Beijing)", value: "cn-north-1" },
{ label: "China (Ningxia)", value: "cn-northwest-1" },
{ label: "Europe (Frankfurt)", value: "eu-central-1" },
{ label: "Europe (Ireland)", value: "eu-west-1" },
{ label: "Europe (London)", value: "eu-west-2" },
{ label: "Europe (Milan)", value: "eu-south-1" },
{ label: "Europe (Paris)", value: "eu-west-3" },
{ label: "Europe (Stockholm)", value: "eu-north-1" },
{ label: "South America (São Paulo)", value: "sa-east-1" },
{ label: "Middle East (Bahrain)", value: "me-south-1" },
{ label: "AWS GovCloud (US-East)", value: "us-gov-east-1" },
{ label: "AWS GovCloud (US-West)", value: "us-gov-west-1" },
];
export default s3Regions;

View File

@@ -1,58 +0,0 @@
// This file is part of MinIO Console Server
// Copyright (c) 2021 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
import {
AzureTierIcon,
AzureTierIconXs,
GoogleTierIcon,
GoogleTierIconXs,
MinIOTierIcon,
MinIOTierIconXs,
S3TierIcon,
S3TierIconXs,
} from "mds";
export const minioServiceName = "minio";
export const gcsServiceName = "gcs";
export const s3ServiceName = "s3";
export const azureServiceName = "azure";
export const tierTypes = [
{
serviceName: minioServiceName,
targetTitle: "MinIO",
logo: <MinIOTierIcon />,
logoXs: <MinIOTierIconXs />,
},
{
serviceName: gcsServiceName,
targetTitle: "Google Cloud Storage",
logo: <GoogleTierIcon />,
logoXs: <GoogleTierIconXs />,
},
{
serviceName: s3ServiceName,
targetTitle: "AWS S3",
logo: <S3TierIcon />,
logoXs: <S3TierIconXs />,
},
{
serviceName: azureServiceName,
targetTitle: "Azure",
logo: <AzureTierIcon />,
logoXs: <AzureTierIconXs />,
},
];

View File

@@ -40,7 +40,6 @@ import { hasPermission } from "../../common/SecureComponent";
import { IRouteRule } from "./Menu/types";
import {
menuOpen,
selDistSet,
serverIsLoading,
setServerNeedsRestart,
setSnackBarMessage,
@@ -58,17 +57,6 @@ const AddEventDestination = React.lazy(
const EventTypeSelector = React.lazy(
() => import("./EventDestinations/EventTypeSelector"),
);
const ListTiersConfiguration = React.lazy(
() => import("./Configurations/TiersConfiguration/ListTiersConfiguration"),
);
const TierTypeSelector = React.lazy(
() => import("./Configurations/TiersConfiguration/TierTypeSelector"),
);
const AddTierConfiguration = React.lazy(
() => import("./Configurations/TiersConfiguration/AddTierConfiguration"),
);
const ErrorLogs = React.lazy(() => import("./Logs/ErrorLogs/ErrorLogs"));
const LogsSearchMain = React.lazy(
() => import("./Logs/LogSearch/LogsSearchMain"),
@@ -132,7 +120,6 @@ const Console = () => {
const open = useSelector((state: AppState) => state.system.sidebarOpen);
const session = useSelector(selSession);
const features = useSelector(selFeatures);
const distributedSetup = useSelector(selDistSet);
const snackBarMessage = useSelector(
(state: AppState) => state.system.snackBar,
);
@@ -334,20 +321,6 @@ const Console = () => {
component: EventDestinations,
path: IAM_PAGES.EVENT_DESTINATIONS,
},
{
component: AddTierConfiguration,
path: IAM_PAGES.TIERS_ADD_SERVICE,
fsHidden: !distributedSetup,
},
{
component: TierTypeSelector,
path: IAM_PAGES.TIERS_ADD,
fsHidden: !distributedSetup,
},
{
component: ListTiersConfiguration,
path: IAM_PAGES.TIERS,
},
{
component: Account,
path: IAM_PAGES.ACCOUNT,

View File

@@ -72,8 +72,6 @@ const OBHeader = ({ bucketName }: IOBHeader) => {
IAM_SCOPES.S3_PUT_BUCKET_NOTIFICATIONS,
IAM_SCOPES.S3_GET_REPLICATION_CONFIGURATION,
IAM_SCOPES.S3_PUT_REPLICATION_CONFIGURATION,
IAM_SCOPES.S3_GET_LIFECYCLE_CONFIGURATION,
IAM_SCOPES.S3_PUT_LIFECYCLE_CONFIGURATION,
IAM_SCOPES.ADMIN_GET_BUCKET_QUOTA,
IAM_SCOPES.ADMIN_SET_BUCKET_QUOTA,
IAM_SCOPES.S3_PUT_BUCKET_TAGGING,

View File

@@ -41,7 +41,6 @@ import {
MonitoringMenuIcon,
ObjectBrowserIcon,
SettingsIcon,
TiersIcon,
UsersMenuIcon,
} from "mds";
import { hasPermission } from "../../common/SecureComponent";
@@ -211,13 +210,6 @@ export const validRoutes = (
icon: <LambdaIcon />,
id: "lambda",
},
{
group: "Administrator",
path: IAM_PAGES.TIERS,
name: "Tiering",
icon: <TiersIcon />,
id: "tiers",
},
{
group: "Administrator",
path: IAM_PAGES.KMS_KEYS,

View File

@@ -1,43 +0,0 @@
// This file is part of MinIO Console Server
// Copyright (c) 2022 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
import * as roles from "../utils/roles";
import * as elements from "../utils/elements";
import { tiersElement } from "../utils/elements-menu";
fixture("For user with Tiers permissions")
.page("http://localhost:9090")
.beforeEach(async (t) => {
await t.useRole(roles.tiers);
});
test("Tiers sidebar item exists", async (t) => {
await t.expect(tiersElement.exists).ok();
});
test("Add Tier button exists", async (t) => {
const createTierButtonExists = elements.createTierButton.exists;
await t
.navigateTo("http://localhost:9090/settings/tiers")
.expect(createTierButtonExists)
.ok();
});
test("Add Tier button is clickable", async (t) => {
await t
.navigateTo("http://localhost:9090/settings/tiers")
.click(elements.createTierButton);
});

View File

@@ -25,7 +25,6 @@ import {
monitoringElement,
notificationEndpointsElement,
serviceAcctsElement,
tiersElement,
usersElement,
} from "../utils/elements-menu";
@@ -58,12 +57,6 @@ test("All sidebar items exist", async (t) => {
.ok()
.expect(notificationEndpointsElement.exists)
.ok()
.expect(tiersElement.exists)
.ok()
.expect(elements.diagnosticsElement.exists)
.ok()
.expect(elements.performanceElement.exists)
.ok()
.expect(licenseExists)
.ok();
});

View File

@@ -75,10 +75,6 @@ export const configurationsElement = getMenuElement("configurations");
export const notificationEndpointsElement = getMenuElement("lambda");
export const tiersElement = getMenuElement("tiers");
export const diagnosticsElement = getMenuElement("diagnostics");
export const performanceElement = getMenuElement("performance");
export const inspectElement = getMenuElement("inspectObjects");
export const licenseElement = getMenuElement("license");

View File

@@ -31,8 +31,6 @@ export const deleteButton = Selector("button:enabled").withExactText("Delete");
export const addEventDestination = Selector("button:enabled").withText(
"Add Event Destination",
);
export const createTierButton =
Selector("button:enabled").withText("Create Tier");
export const createUserButton =
Selector("button:enabled").withText("Create User");
export const createGroupButton =