Replaces create tenant functionality (#278)
Co-authored-by: Benjamin Perez <benjamin@bexsoft.net>
This commit is contained in:
333
portal-ui/src/common/types.ts
Normal file
333
portal-ui/src/common/types.ts
Normal file
@@ -0,0 +1,333 @@
|
|||||||
|
// This file is part of MinIO Console Server
|
||||||
|
// Copyright (c) 2020 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/>.
|
||||||
|
|
||||||
|
/* Copyright (c) 2020 MinIO, Inc. All rights reserved. */
|
||||||
|
|
||||||
|
export interface ITenantsObject {
|
||||||
|
tenants: ITenant[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ITenant {
|
||||||
|
creation_date: string;
|
||||||
|
deletion_date: string;
|
||||||
|
currentState: string;
|
||||||
|
image: string;
|
||||||
|
console_image: string;
|
||||||
|
instance_count: string;
|
||||||
|
name: string;
|
||||||
|
namespace?: string;
|
||||||
|
total_size: string;
|
||||||
|
used_size: string;
|
||||||
|
volume_count: string;
|
||||||
|
volume_size: string;
|
||||||
|
volumes_per_server?: string;
|
||||||
|
zone_count: string;
|
||||||
|
zones?: IZoneModel[];
|
||||||
|
used_capacity?: string;
|
||||||
|
endpoint?: string;
|
||||||
|
storage_class?: string;
|
||||||
|
enable_prometheus: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IVolumeConfiguration {
|
||||||
|
size: string;
|
||||||
|
storage_class_name: string;
|
||||||
|
labels?: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ITenantCreator {
|
||||||
|
name: string;
|
||||||
|
service_name: string;
|
||||||
|
enable_console: boolean;
|
||||||
|
enable_prometheus: boolean;
|
||||||
|
enable_tls: boolean;
|
||||||
|
access_key: string;
|
||||||
|
secret_key: string;
|
||||||
|
image: string;
|
||||||
|
console_image: string;
|
||||||
|
zones: IZoneModel[];
|
||||||
|
namespace: string;
|
||||||
|
erasureCodingParity: number;
|
||||||
|
tls?: ITLSTenantConfiguration;
|
||||||
|
encryption?: IEncryptionConfiguration;
|
||||||
|
idp?: IIDPConfiguration;
|
||||||
|
annotations?: Object;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ITenantUpdateObject {
|
||||||
|
image: string;
|
||||||
|
image_registry?: IRegistryObject;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IRegistryObject {
|
||||||
|
registry: string;
|
||||||
|
username: string;
|
||||||
|
password: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ITenantUsage {
|
||||||
|
used: string;
|
||||||
|
disk_used: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IAffinityModel {
|
||||||
|
podAntiAffinity: IPodAntiAffinityModel;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IPodAntiAffinityModel {
|
||||||
|
requiredDuringSchedulingIgnoredDuringExecution: IPodAffinityTerm[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IPodAffinityTerm {
|
||||||
|
labelSelector: IPodAffinityTermLabelSelector;
|
||||||
|
topologyKey: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IPodAffinityTermLabelSelector {
|
||||||
|
matchExpressions: IMatchExpressionItem[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IMatchExpressionItem {
|
||||||
|
key: string;
|
||||||
|
operator: string;
|
||||||
|
values: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ITolerationModel {
|
||||||
|
effect: string;
|
||||||
|
key: string;
|
||||||
|
operator: string;
|
||||||
|
value?: string;
|
||||||
|
tolerationSeconds?: ITolerationSeconds;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ITolerationSeconds {
|
||||||
|
seconds: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IResourceModel {
|
||||||
|
requests: IResourceRequests;
|
||||||
|
limits?: IResourceLimits;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IResourceRequests {
|
||||||
|
memory: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IResourceLimits {
|
||||||
|
memory: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ITLSTenantConfiguration {
|
||||||
|
minio: ITLSConfiguration;
|
||||||
|
console: ITLSConfiguration;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ITLSConfiguration {
|
||||||
|
crt: string;
|
||||||
|
key: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IEncryptionConfiguration {
|
||||||
|
server: ITLSConfiguration;
|
||||||
|
client: ITLSConfiguration;
|
||||||
|
master_key?: string;
|
||||||
|
gemalto?: IGemaltoConfig;
|
||||||
|
aws?: IAWSConfig;
|
||||||
|
vault?: IVaultConfig;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IVaultConfig {
|
||||||
|
endpoint: string;
|
||||||
|
engine?: string;
|
||||||
|
namespace?: string;
|
||||||
|
prefix?: string;
|
||||||
|
approle: IApproleConfig;
|
||||||
|
tls: IVaultTLSConfig;
|
||||||
|
status: IVaultStatusConfig;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IGemaltoConfig {
|
||||||
|
keysecure: IKeysecureConfig;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IAWSConfig {
|
||||||
|
secretsmanager: ISecretsManagerConfig;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IApproleConfig {
|
||||||
|
engine: string;
|
||||||
|
id: string;
|
||||||
|
secret: string;
|
||||||
|
retry: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IVaultTLSConfig {
|
||||||
|
key: string;
|
||||||
|
crt: string;
|
||||||
|
ca: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IVaultStatusConfig {
|
||||||
|
ping: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IKeysecureConfig {
|
||||||
|
endpoint: string;
|
||||||
|
credentials: IGemaltoCredentials;
|
||||||
|
tls: IGemaltoTLS;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IGemaltoCredentials {
|
||||||
|
token: string;
|
||||||
|
domain: string;
|
||||||
|
retry?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IGemaltoTLS {
|
||||||
|
ca: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ISecretsManagerConfig {
|
||||||
|
endpoint: string;
|
||||||
|
region: string;
|
||||||
|
kmskey?: string;
|
||||||
|
credentials: IAWSCredentials;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IAWSCredentials {
|
||||||
|
accesskey: string;
|
||||||
|
secretkey: string;
|
||||||
|
token?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IIDPConfiguration {
|
||||||
|
oidc?: IOpenIDConfiguration;
|
||||||
|
active_directory: IActiveDirectoryConfiguration;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IOpenIDConfiguration {
|
||||||
|
url: string;
|
||||||
|
client_id: string;
|
||||||
|
secret_id: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IActiveDirectoryConfiguration {
|
||||||
|
url: string;
|
||||||
|
skip_tls_verification: boolean;
|
||||||
|
server_insecure: boolean;
|
||||||
|
user_search_filter: string;
|
||||||
|
group_Search_base_dn: string;
|
||||||
|
group_search_filter: string;
|
||||||
|
group_name_attribute: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IStorageDistribution {
|
||||||
|
error: number;
|
||||||
|
nodes: number;
|
||||||
|
persistentVolumes: number;
|
||||||
|
disks: number;
|
||||||
|
pvSize: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IErasureCodeCalc {
|
||||||
|
error: number;
|
||||||
|
maxEC: string;
|
||||||
|
erasureCodeSet: number;
|
||||||
|
rawCapacity: string;
|
||||||
|
storageFactors: IStorageFactors[];
|
||||||
|
defaultEC: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IStorageFactors {
|
||||||
|
erasureCode: string;
|
||||||
|
storageFactor: number;
|
||||||
|
maxCapacity: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ITenantHealthInList {
|
||||||
|
name: string;
|
||||||
|
namespace: string;
|
||||||
|
status?: string;
|
||||||
|
message?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ITenantsListHealthRequest {
|
||||||
|
tenants: ITenantHealthInList[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IMaxAllocatableMemoryRequest {
|
||||||
|
num_nodes: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IMaxAllocatableMemoryResponse {
|
||||||
|
max_memory: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IEncryptionUpdateRequest {
|
||||||
|
encryption: IEncryptionConfiguration;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IArchivedTenantsList {
|
||||||
|
tenants: IArchivedTenant[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IArchivedTenant {
|
||||||
|
namespace: string;
|
||||||
|
tenant: string;
|
||||||
|
number_volumes: number;
|
||||||
|
capacity: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IZoneModel {
|
||||||
|
name?: string;
|
||||||
|
servers: number;
|
||||||
|
volumes_per_server: number;
|
||||||
|
volume_configuration: IVolumeConfiguration;
|
||||||
|
affinity?: IAffinityModel;
|
||||||
|
tolerations?: ITolerationModel[];
|
||||||
|
resources?: IResourceModel;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IUpdateZone {
|
||||||
|
zones: IZoneModel[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface INode {
|
||||||
|
name: string;
|
||||||
|
freeSpace: string;
|
||||||
|
totalSpace: string;
|
||||||
|
disks: IDisk[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IStorageType {
|
||||||
|
freeSpace: string;
|
||||||
|
totalSpace: string;
|
||||||
|
storageClasses: string[];
|
||||||
|
nodes: INode[];
|
||||||
|
schedulableNodes: INode[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IDisk {
|
||||||
|
name: string;
|
||||||
|
freeSpace: string;
|
||||||
|
totalSpace: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ICapacity {
|
||||||
|
value: string;
|
||||||
|
unit: string;
|
||||||
|
}
|
||||||
@@ -15,6 +15,10 @@
|
|||||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
import storage from "local-storage-fallback";
|
import storage from "local-storage-fallback";
|
||||||
|
import { ICapacity, IStorageType, IZoneModel } from "./types";
|
||||||
|
|
||||||
|
const minStReq = 1073741824; // Minimal Space required for MinIO
|
||||||
|
const minMemReq = 2147483648; // Minimal Memory required for MinIO in bytes
|
||||||
|
|
||||||
export const units = [
|
export const units = [
|
||||||
"B",
|
"B",
|
||||||
@@ -28,6 +32,8 @@ export const units = [
|
|||||||
"YiB",
|
"YiB",
|
||||||
];
|
];
|
||||||
export const k8sUnits = ["Ki", "Mi", "Gi", "Ti", "Pi", "Ei"];
|
export const k8sUnits = ["Ki", "Mi", "Gi", "Ti", "Pi", "Ei"];
|
||||||
|
export const k8sCalcUnits = ["B", ...k8sUnits];
|
||||||
|
|
||||||
export const niceBytes = (x: string) => {
|
export const niceBytes = (x: string) => {
|
||||||
let l = 0,
|
let l = 0,
|
||||||
n = parseInt(x, 10) || 0;
|
n = parseInt(x, 10) || 0;
|
||||||
@@ -90,12 +96,19 @@ export const k8sfactorForDropdown = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
//getBytes, converts from a value and a unit from units array to bytes
|
//getBytes, converts from a value and a unit from units array to bytes
|
||||||
export const getBytes = (value: string, unit: string) => {
|
export const getBytes = (
|
||||||
|
value: string,
|
||||||
|
unit: string,
|
||||||
|
fork8s: boolean = false
|
||||||
|
) => {
|
||||||
const vl: number = parseFloat(value);
|
const vl: number = parseFloat(value);
|
||||||
const powFactor = units.findIndex((element) => element === unit);
|
|
||||||
|
const unitsTake = fork8s ? k8sCalcUnits : units;
|
||||||
|
|
||||||
|
const powFactor = unitsTake.findIndex((element) => element === unit);
|
||||||
|
|
||||||
if (powFactor == -1) {
|
if (powFactor == -1) {
|
||||||
return 0;
|
return "0";
|
||||||
}
|
}
|
||||||
const factor = Math.pow(1024, powFactor);
|
const factor = Math.pow(1024, powFactor);
|
||||||
const total = vl * factor;
|
const total = vl * factor;
|
||||||
@@ -105,6 +118,220 @@ export const getBytes = (value: string, unit: string) => {
|
|||||||
|
|
||||||
//getTotalSize gets the total size of a value & unit
|
//getTotalSize gets the total size of a value & unit
|
||||||
export const getTotalSize = (value: string, unit: string) => {
|
export const getTotalSize = (value: string, unit: string) => {
|
||||||
const bytes = getBytes(value, unit).toString(10);
|
const bytes = getBytes(value, unit, true).toString();
|
||||||
return niceBytes(bytes);
|
return niceBytes(bytes);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const setMemoryResource = (
|
||||||
|
memorySize: number,
|
||||||
|
capacitySize: string,
|
||||||
|
maxMemorySize: number
|
||||||
|
) => {
|
||||||
|
// value always comes as Gi
|
||||||
|
const requestedSizeBytes = getBytes(memorySize.toString(10), "Gi", true);
|
||||||
|
const memReqSize = parseInt(requestedSizeBytes, 10);
|
||||||
|
if (maxMemorySize === 0) {
|
||||||
|
return {
|
||||||
|
error: "There is no memory available for the selected number of nodes",
|
||||||
|
request: 0,
|
||||||
|
limit: 0,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (maxMemorySize < minMemReq) {
|
||||||
|
return {
|
||||||
|
error: "There are not enough memory resources available",
|
||||||
|
request: 0,
|
||||||
|
limit: 0,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (memReqSize < minMemReq) {
|
||||||
|
return {
|
||||||
|
error: "The requested memory size must be greater than 2Gi",
|
||||||
|
request: 0,
|
||||||
|
limit: 0,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (memReqSize > maxMemorySize) {
|
||||||
|
return {
|
||||||
|
error:
|
||||||
|
"The requested memory is greater than the max available memory for the selected number of nodes",
|
||||||
|
request: 0,
|
||||||
|
limit: 0,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const capSize = parseInt(capacitySize, 10);
|
||||||
|
let memLimitSize = memReqSize;
|
||||||
|
// set memory limit based on the capacitySize
|
||||||
|
// if capacity size is lower than 1TiB we use the limit equal to request
|
||||||
|
if (capSize >= parseInt(getBytes("1", "Pi", true), 10)) {
|
||||||
|
memLimitSize = Math.max(
|
||||||
|
memReqSize,
|
||||||
|
parseInt(getBytes("64", "Gi", true), 10)
|
||||||
|
);
|
||||||
|
} else if (capSize >= parseInt(getBytes("100", "Ti"), 10)) {
|
||||||
|
memLimitSize = Math.max(
|
||||||
|
memReqSize,
|
||||||
|
parseInt(getBytes("32", "Gi", true), 10)
|
||||||
|
);
|
||||||
|
} else if (capSize >= parseInt(getBytes("10", "Ti"), 10)) {
|
||||||
|
memLimitSize = Math.max(
|
||||||
|
memReqSize,
|
||||||
|
parseInt(getBytes("16", "Gi", true), 10)
|
||||||
|
);
|
||||||
|
} else if (capSize >= parseInt(getBytes("1", "Ti"), 10)) {
|
||||||
|
memLimitSize = Math.max(
|
||||||
|
memReqSize,
|
||||||
|
parseInt(getBytes("8", "Gi", true), 10)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
error: "",
|
||||||
|
request: memReqSize,
|
||||||
|
limit: memLimitSize,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export const calculateDistribution = (
|
||||||
|
capacityToUse: ICapacity,
|
||||||
|
forcedNodes: number = 0,
|
||||||
|
limitSize: number = 0
|
||||||
|
) => {
|
||||||
|
let numberOfNodes = {};
|
||||||
|
const requestedSizeBytes = getBytes(
|
||||||
|
capacityToUse.value,
|
||||||
|
capacityToUse.unit,
|
||||||
|
true
|
||||||
|
);
|
||||||
|
|
||||||
|
if (parseInt(requestedSizeBytes, 10) < minStReq) {
|
||||||
|
return {
|
||||||
|
error: "The zone size must be greater than 1Gi",
|
||||||
|
nodes: 0,
|
||||||
|
persistentVolumes: 0,
|
||||||
|
disks: 0,
|
||||||
|
pvSize: 0,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (forcedNodes < 4) {
|
||||||
|
return {
|
||||||
|
error: "Number of nodes cannot be less than 4",
|
||||||
|
nodes: 0,
|
||||||
|
persistentVolumes: 0,
|
||||||
|
disks: 0,
|
||||||
|
pvSize: 0,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
numberOfNodes = calculateStorage(requestedSizeBytes, forcedNodes, limitSize);
|
||||||
|
|
||||||
|
return numberOfNodes;
|
||||||
|
};
|
||||||
|
|
||||||
|
const calculateStorage = (
|
||||||
|
requestedBytes: string,
|
||||||
|
forcedNodes: number,
|
||||||
|
limitSize: number
|
||||||
|
) => {
|
||||||
|
// Size validation
|
||||||
|
const intReqBytes = parseInt(requestedBytes, 10);
|
||||||
|
const maxDiskSize = minStReq * 256; // 256 GiB
|
||||||
|
|
||||||
|
// We get the distribution
|
||||||
|
return structureCalc(forcedNodes, intReqBytes, maxDiskSize, limitSize);
|
||||||
|
};
|
||||||
|
|
||||||
|
const structureCalc = (
|
||||||
|
nodes: number,
|
||||||
|
desiredCapacity: number,
|
||||||
|
maxDiskSize: number,
|
||||||
|
maxClusterSize: number,
|
||||||
|
disksPerNode: number = 0
|
||||||
|
) => {
|
||||||
|
if (
|
||||||
|
isNaN(nodes) ||
|
||||||
|
isNaN(desiredCapacity) ||
|
||||||
|
isNaN(maxDiskSize) ||
|
||||||
|
isNaN(maxClusterSize)
|
||||||
|
) {
|
||||||
|
return {
|
||||||
|
error: "Some provided data is invalid, please try again.",
|
||||||
|
nodes: 0,
|
||||||
|
persistentVolumes: 0,
|
||||||
|
disks: 0,
|
||||||
|
volumePerDisk: 0,
|
||||||
|
}; // Invalid Data
|
||||||
|
}
|
||||||
|
|
||||||
|
let persistentVolumeSize = 0;
|
||||||
|
let numberPersistentVolumes = 0;
|
||||||
|
let volumesPerServer = 0;
|
||||||
|
|
||||||
|
if (disksPerNode === 0) {
|
||||||
|
persistentVolumeSize = Math.floor(
|
||||||
|
Math.min(desiredCapacity / Math.max(4, nodes), maxDiskSize)
|
||||||
|
); // pVS = min((desiredCapacity / max(4 | nodes)) | maxDiskSize)
|
||||||
|
|
||||||
|
numberPersistentVolumes = desiredCapacity / persistentVolumeSize; // nPV = dC / pVS
|
||||||
|
volumesPerServer = numberPersistentVolumes / nodes; // vPS = nPV / n
|
||||||
|
}
|
||||||
|
|
||||||
|
if (disksPerNode) {
|
||||||
|
volumesPerServer = disksPerNode;
|
||||||
|
numberPersistentVolumes = volumesPerServer * nodes;
|
||||||
|
persistentVolumeSize = Math.floor(
|
||||||
|
desiredCapacity / numberPersistentVolumes
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Volumes are not exact, we force the volumes number & minimize the volume size
|
||||||
|
if (volumesPerServer % 1 > 0) {
|
||||||
|
volumesPerServer = Math.ceil(volumesPerServer); // Increment of volumes per server
|
||||||
|
numberPersistentVolumes = volumesPerServer * nodes; // nPV = vPS * n
|
||||||
|
persistentVolumeSize = Math.floor(
|
||||||
|
desiredCapacity / numberPersistentVolumes
|
||||||
|
); // pVS = dC / nPV
|
||||||
|
|
||||||
|
const limitSize = persistentVolumeSize * volumesPerServer * nodes; // lS = pVS * vPS * n
|
||||||
|
|
||||||
|
if (limitSize > maxClusterSize) {
|
||||||
|
return {
|
||||||
|
error: "We were not able to allocate this server.",
|
||||||
|
nodes: 0,
|
||||||
|
persistentVolumes: 0,
|
||||||
|
disks: 0,
|
||||||
|
volumePerDisk: 0,
|
||||||
|
}; // Cannot allocate this server
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (persistentVolumeSize < minStReq) {
|
||||||
|
return {
|
||||||
|
error:
|
||||||
|
"Disk Size with this combination would be less than 1Gi, please try another combination",
|
||||||
|
nodes: 0,
|
||||||
|
persistentVolumes: 0,
|
||||||
|
disks: 0,
|
||||||
|
volumePerDisk: 0,
|
||||||
|
}; // Cannot allocate this volume size
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
error: "",
|
||||||
|
nodes,
|
||||||
|
persistentVolumes: numberPersistentVolumes,
|
||||||
|
disks: volumesPerServer,
|
||||||
|
pvSize: persistentVolumeSize,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
// Zone Name Generator
|
||||||
|
export const generateZoneName = (zones: IZoneModel[]) => {
|
||||||
|
const zoneCounter = zones.length;
|
||||||
|
|
||||||
|
return `zone-${zoneCounter}`;
|
||||||
|
};
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ import {
|
|||||||
DialogContent,
|
DialogContent,
|
||||||
DialogContentText,
|
DialogContentText,
|
||||||
DialogTitle,
|
DialogTitle,
|
||||||
LinearProgress
|
LinearProgress,
|
||||||
} from "@material-ui/core";
|
} from "@material-ui/core";
|
||||||
import api from "../../../../common/api";
|
import api from "../../../../common/api";
|
||||||
import { BucketList } from "../types";
|
import { BucketList } from "../types";
|
||||||
@@ -32,8 +32,8 @@ import Typography from "@material-ui/core/Typography";
|
|||||||
const styles = (theme: Theme) =>
|
const styles = (theme: Theme) =>
|
||||||
createStyles({
|
createStyles({
|
||||||
errorBlock: {
|
errorBlock: {
|
||||||
color: "red"
|
color: "red",
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
interface IDeleteBucketProps {
|
interface IDeleteBucketProps {
|
||||||
@@ -54,7 +54,7 @@ class DeleteBucket extends React.Component<
|
|||||||
> {
|
> {
|
||||||
state: IDeleteBucketState = {
|
state: IDeleteBucketState = {
|
||||||
deleteLoading: false,
|
deleteLoading: false,
|
||||||
deleteError: ""
|
deleteError: "",
|
||||||
};
|
};
|
||||||
|
|
||||||
removeRecord() {
|
removeRecord() {
|
||||||
@@ -66,23 +66,23 @@ class DeleteBucket extends React.Component<
|
|||||||
this.setState({ deleteLoading: true }, () => {
|
this.setState({ deleteLoading: true }, () => {
|
||||||
api
|
api
|
||||||
.invoke("DELETE", `/api/v1/buckets/${selectedBucket}`, {
|
.invoke("DELETE", `/api/v1/buckets/${selectedBucket}`, {
|
||||||
name: selectedBucket
|
name: selectedBucket,
|
||||||
})
|
})
|
||||||
.then((res: BucketList) => {
|
.then((res: BucketList) => {
|
||||||
this.setState(
|
this.setState(
|
||||||
{
|
{
|
||||||
deleteLoading: false,
|
deleteLoading: false,
|
||||||
deleteError: ""
|
deleteError: "",
|
||||||
},
|
},
|
||||||
() => {
|
() => {
|
||||||
this.props.closeDeleteModalAndRefresh(true);
|
this.props.closeDeleteModalAndRefresh(true);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
})
|
})
|
||||||
.catch(err => {
|
.catch((err) => {
|
||||||
this.setState({
|
this.setState({
|
||||||
deleteLoading: false,
|
deleteLoading: false,
|
||||||
deleteError: err
|
deleteError: err,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -0,0 +1,113 @@
|
|||||||
|
// This file is part of MinIO Console Server
|
||||||
|
// Copyright (c) 2020 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 { Grid, InputLabel, Tooltip } from "@material-ui/core";
|
||||||
|
import HelpIcon from "@material-ui/icons/Help";
|
||||||
|
import { createStyles, Theme, withStyles } from "@material-ui/core/styles";
|
||||||
|
import { fieldBasic, tooltipHelper } from "../common/styleLibrary";
|
||||||
|
import { fileProcess } from "./utils";
|
||||||
|
|
||||||
|
interface InputBoxProps {
|
||||||
|
label: string;
|
||||||
|
classes: any;
|
||||||
|
onChange: (e: string) => void;
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
disabled?: boolean;
|
||||||
|
tooltip?: string;
|
||||||
|
required?: boolean;
|
||||||
|
error?: string;
|
||||||
|
accept?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const styles = (theme: Theme) =>
|
||||||
|
createStyles({
|
||||||
|
...fieldBasic,
|
||||||
|
...tooltipHelper,
|
||||||
|
textBoxContainer: {
|
||||||
|
flexGrow: 1,
|
||||||
|
position: "relative",
|
||||||
|
},
|
||||||
|
errorState: {
|
||||||
|
color: "#b53b4b",
|
||||||
|
fontSize: 14,
|
||||||
|
position: "absolute",
|
||||||
|
top: 7,
|
||||||
|
right: 7,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const FileSelector = ({
|
||||||
|
label,
|
||||||
|
classes,
|
||||||
|
onChange,
|
||||||
|
id,
|
||||||
|
name,
|
||||||
|
disabled = false,
|
||||||
|
tooltip = "",
|
||||||
|
required,
|
||||||
|
error = "",
|
||||||
|
accept = "",
|
||||||
|
}: InputBoxProps) => {
|
||||||
|
return (
|
||||||
|
<React.Fragment>
|
||||||
|
<Grid
|
||||||
|
item
|
||||||
|
xs={12}
|
||||||
|
className={`${classes.fieldContainer} ${
|
||||||
|
error !== "" ? classes.errorInField : ""
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
{label !== "" && (
|
||||||
|
<InputLabel
|
||||||
|
htmlFor={id}
|
||||||
|
className={`${error !== "" ? classes.fieldLabelError : ""} ${
|
||||||
|
classes.inputLabel
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<span>
|
||||||
|
{label}
|
||||||
|
{required ? "*" : ""}
|
||||||
|
</span>
|
||||||
|
{tooltip !== "" && (
|
||||||
|
<div className={classes.tooltipContainer}>
|
||||||
|
<Tooltip title={tooltip} placement="top-start">
|
||||||
|
<HelpIcon className={classes.tooltip} />
|
||||||
|
</Tooltip>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</InputLabel>
|
||||||
|
)}
|
||||||
|
<div className={classes.textBoxContainer}>
|
||||||
|
<input
|
||||||
|
type="file"
|
||||||
|
name={name}
|
||||||
|
onChange={(e) => {
|
||||||
|
fileProcess(e, (data: any) => {
|
||||||
|
onChange(data);
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
accept={accept}
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</Grid>
|
||||||
|
</React.Fragment>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default withStyles(styles)(FileSelector);
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
// This file is part of MinIO Console Server
|
||||||
|
// Copyright (c) 2020 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/>.
|
||||||
|
|
||||||
|
export const fileProcess = (evt: any, callback: any) => {
|
||||||
|
const file = evt.target.files[0];
|
||||||
|
const reader = new FileReader();
|
||||||
|
reader.readAsDataURL(file);
|
||||||
|
|
||||||
|
reader.onload = () => {
|
||||||
|
// reader.readAsDataURL(file) output will be something like: data:application/x-x509-ca-cert;base64,LS0tLS1CRUdJTiBDRVJUSU
|
||||||
|
// we care only about the actual base64 part (everything after "data:application/x-x509-ca-cert;base64,")
|
||||||
|
const fileBase64 = reader.result;
|
||||||
|
if (fileBase64) {
|
||||||
|
const fileArray = fileBase64.toString().split("base64,");
|
||||||
|
|
||||||
|
if (fileArray.length === 2) {
|
||||||
|
callback(fileArray[1]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
@@ -42,6 +42,7 @@ interface SelectProps {
|
|||||||
onChange: (
|
onChange: (
|
||||||
e: React.ChangeEvent<{ name?: string | undefined; value: unknown }>
|
e: React.ChangeEvent<{ name?: string | undefined; value: unknown }>
|
||||||
) => void;
|
) => void;
|
||||||
|
disabled?: boolean;
|
||||||
classes: any;
|
classes: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -51,7 +52,7 @@ const styles = (theme: Theme) =>
|
|||||||
...tooltipHelper,
|
...tooltipHelper,
|
||||||
inputLabel: {
|
inputLabel: {
|
||||||
...fieldBasic.inputLabel,
|
...fieldBasic.inputLabel,
|
||||||
width: 116,
|
width: 215,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -88,6 +89,7 @@ const SelectWrapper = ({
|
|||||||
label,
|
label,
|
||||||
tooltip = "",
|
tooltip = "",
|
||||||
value,
|
value,
|
||||||
|
disabled = false,
|
||||||
}: SelectProps) => {
|
}: SelectProps) => {
|
||||||
return (
|
return (
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
@@ -111,6 +113,7 @@ const SelectWrapper = ({
|
|||||||
value={value}
|
value={value}
|
||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
input={<SelectStyled />}
|
input={<SelectStyled />}
|
||||||
|
disabled={disabled}
|
||||||
>
|
>
|
||||||
{options.map((option) => (
|
{options.map((option) => (
|
||||||
<MenuItem
|
<MenuItem
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -19,6 +19,8 @@ export interface IValidation {
|
|||||||
required: boolean;
|
required: boolean;
|
||||||
pattern?: RegExp;
|
pattern?: RegExp;
|
||||||
customPatternMessage?: string;
|
customPatternMessage?: string;
|
||||||
|
customValidation?: boolean;
|
||||||
|
customValidationMessage?: string;
|
||||||
value: string;
|
value: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -31,12 +33,18 @@ export const commonFormValidation = (fieldsValidate: IValidation[]) => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (field.customValidation && field.customValidationMessage) {
|
||||||
|
returnErrors[field.fieldKey] = field.customValidationMessage;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (field.pattern && field.customPatternMessage) {
|
if (field.pattern && field.customPatternMessage) {
|
||||||
const rgx = new RegExp(field.pattern, "g");
|
const rgx = new RegExp(field.pattern, "g");
|
||||||
|
|
||||||
if (!field.value.match(rgx)) {
|
if (!field.value.match(rgx)) {
|
||||||
returnErrors[field.fieldKey] = field.customPatternMessage;
|
returnErrors[field.fieldKey] = field.customPatternMessage;
|
||||||
}
|
}
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user