mirror of
https://github.com/OpenMaxIO/openmaxio-object-browser
synced 2026-07-01 15:51:18 -07:00
Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7e4d34958e | ||
|
|
9d6ee7f9b0 | ||
|
|
aa16e75b39 | ||
|
|
283a00bde2 | ||
|
|
0c78359832 |
@@ -15,7 +15,7 @@ spec:
|
||||
serviceAccountName: console-sa
|
||||
containers:
|
||||
- name: console
|
||||
image: minio/console:v0.8.1
|
||||
image: minio/console:v0.8.2
|
||||
imagePullPolicy: "IfNotPresent"
|
||||
args:
|
||||
- server
|
||||
|
||||
@@ -15,7 +15,7 @@ spec:
|
||||
serviceAccountName: console-sa
|
||||
containers:
|
||||
- name: console
|
||||
image: minio/console:v0.8.1
|
||||
image: minio/console:v0.8.2
|
||||
imagePullPolicy: "IfNotPresent"
|
||||
env:
|
||||
- name: CONSOLE_OPERATOR_MODE
|
||||
|
||||
@@ -55,6 +55,9 @@ type BucketObject struct {
|
||||
// legal hold status
|
||||
LegalHoldStatus string `json:"legal_hold_status,omitempty"`
|
||||
|
||||
// metadata
|
||||
Metadata map[string]string `json:"metadata,omitempty"`
|
||||
|
||||
// name
|
||||
Name string `json:"name,omitempty"`
|
||||
|
||||
@@ -70,6 +73,9 @@ type BucketObject struct {
|
||||
// tags
|
||||
Tags map[string]string `json:"tags,omitempty"`
|
||||
|
||||
// user metadata
|
||||
UserMetadata map[string]string `json:"user_metadata,omitempty"`
|
||||
|
||||
// user tags
|
||||
UserTags map[string]string `json:"user_tags,omitempty"`
|
||||
|
||||
|
||||
@@ -39,7 +39,7 @@ type ListBucketsResponse struct {
|
||||
// list of resulting buckets
|
||||
Buckets []*Bucket `json:"buckets"`
|
||||
|
||||
// number of buckets accessible to tenant user
|
||||
// number of buckets accessible to the user
|
||||
Total int64 `json:"total,omitempty"`
|
||||
}
|
||||
|
||||
|
||||
@@ -59,7 +59,7 @@ const (
|
||||
// Image versions
|
||||
const (
|
||||
KESImageVersion = "minio/kes:v0.13.4"
|
||||
ConsoleImageDefaultVersion = "minio/console:v0.8.1"
|
||||
ConsoleImageDefaultVersion = "minio/console:v0.8.2"
|
||||
)
|
||||
|
||||
// K8s
|
||||
|
||||
@@ -38,6 +38,9 @@ var (
|
||||
errLicenseNotFound = errors.New("license not found")
|
||||
errAvoidSelfAccountDelete = errors.New("logged in user cannot be deleted by itself")
|
||||
errAccessDenied = errors.New("access denied")
|
||||
errTooFewNodes = errors.New("at least 4 nodes are required in cluster")
|
||||
errTooFewSchedulableNodes = errors.New("at least 4 schedulable nodes are required in cluster")
|
||||
errFewerThanFourNodes = errors.New("at least 4 nodes are required in request")
|
||||
)
|
||||
|
||||
// prepareError receives an error object and parse it against k8sErrors, returns the right error code paired with a generic error message
|
||||
@@ -156,6 +159,18 @@ func prepareError(err ...error) *models.Error {
|
||||
if errors.Is(err[0], errRemoteTierExists) {
|
||||
errorMessage = err[0].Error()
|
||||
}
|
||||
if errors.Is(err[0], errTooFewNodes) {
|
||||
errorCode = 507
|
||||
errorMessage = errTooFewNodes.Error()
|
||||
}
|
||||
if errors.Is(err[0], errTooFewSchedulableNodes) {
|
||||
errorCode = 507
|
||||
errorMessage = errTooFewSchedulableNodes.Error()
|
||||
}
|
||||
if errors.Is(err[0], errFewerThanFourNodes) {
|
||||
errorCode = 507
|
||||
errorMessage = errFewerThanFourNodes.Error()
|
||||
}
|
||||
}
|
||||
return &models.Error{Code: errorCode, Message: swag.String(errorMessage), DetailedMessage: swag.String(err[0].Error())}
|
||||
}
|
||||
|
||||
@@ -56,8 +56,8 @@ func registerNodesHandlers(api *operations.OperatorAPI) {
|
||||
|
||||
// getMaxAllocatableMemory get max allocatable memory given a desired number of nodes
|
||||
func getMaxAllocatableMemory(ctx context.Context, clientset v1.CoreV1Interface, numNodes int32) (*models.MaxAllocatableMemResponse, error) {
|
||||
if numNodes == 0 {
|
||||
return nil, errors.New("error NumNodes must be greated than 0")
|
||||
if numNodes < 4 {
|
||||
return nil, errors.New("error NumNodes must be at least 4")
|
||||
}
|
||||
|
||||
// get all nodes from cluster
|
||||
@@ -65,6 +65,18 @@ func getMaxAllocatableMemory(ctx context.Context, clientset v1.CoreV1Interface,
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(nodes.Items) < int(numNodes) {
|
||||
return nil, errTooFewNodes
|
||||
}
|
||||
activeNodes := 0
|
||||
for i := 0; i < len(nodes.Items); i++ {
|
||||
if !nodes.Items[i].Spec.Unschedulable {
|
||||
activeNodes++
|
||||
}
|
||||
}
|
||||
if activeNodes < int(numNodes) {
|
||||
return nil, errTooFewSchedulableNodes
|
||||
}
|
||||
|
||||
availableMemSizes := []int64{}
|
||||
OUTER:
|
||||
|
||||
@@ -1036,7 +1036,7 @@ func Test_UpdateTenantAction(t *testing.T) {
|
||||
},
|
||||
params: operator_api.UpdateTenantParams{
|
||||
Body: &models.UpdateTenantRequest{
|
||||
ConsoleImage: "minio/console:v0.8.1",
|
||||
ConsoleImage: "minio/console:v0.8.2",
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -22,50 +22,43 @@ import (
|
||||
|
||||
// endpoints definition
|
||||
var (
|
||||
configuration = "/settings"
|
||||
users = "/users"
|
||||
usersDetail = "/users/:userName+"
|
||||
groups = "/groups"
|
||||
iamPolicies = "/policies"
|
||||
policiesDetail = "/policies/:policyName"
|
||||
dashboard = "/dashboard"
|
||||
metrics = "/metrics"
|
||||
profiling = "/profiling"
|
||||
buckets = "/buckets"
|
||||
bucketsDetail = "/buckets/:bucketName"
|
||||
bucketsDetailSummary = "/buckets/:bucketName/summary"
|
||||
bucketsDetailEvents = "/buckets/:bucketName/events"
|
||||
bucketsDetailReplication = "/buckets/:bucketName/replication"
|
||||
bucketsDetailLifecycle = "/buckets/:bucketName/lifecycle"
|
||||
bucketsDetailAccess = "/buckets/:bucketName/access"
|
||||
bucketsDetailAccessPolicies = "/buckets/:bucketName/access/policies"
|
||||
bucketsDetailAccessUsers = "/buckets/:bucketName/access/users"
|
||||
serviceAccounts = "/account"
|
||||
changePassword = "/account/change-password"
|
||||
tenants = "/tenants"
|
||||
tenantsDetail = "/namespaces/:tenantNamespace/tenants/:tenantName"
|
||||
tenantHop = "/namespaces/:tenantNamespace/tenants/:tenantName/hop"
|
||||
podsDetail = "/namespaces/:tenantNamespace/tenants/:tenantName/pods/:podName"
|
||||
tenantsDetailSummary = "/namespaces/:tenantNamespace/tenants/:tenantName/summary"
|
||||
tenantsDetailMetrics = "/namespaces/:tenantNamespace/tenants/:tenantName/metrics"
|
||||
tenantsDetailPods = "/namespaces/:tenantNamespace/tenants/:tenantName/pods"
|
||||
tenantsDetailPools = "/namespaces/:tenantNamespace/tenants/:tenantName/pools"
|
||||
tenantsDetailLicense = "/namespaces/:tenantNamespace/tenants/:tenantName/license"
|
||||
tenantsDetailSecurity = "/namespaces/:tenantNamespace/tenants/:tenantName/security"
|
||||
storage = "/storage"
|
||||
storageVolumes = "/storage/volumes"
|
||||
storageDrives = "/storage/drives"
|
||||
remoteBuckets = "/remote-buckets"
|
||||
replication = "/replication"
|
||||
objectBrowser = "/object-browser/:bucket/*"
|
||||
objectBrowserBucket = "/object-browser/:bucket"
|
||||
mainObjectBrowser = "/object-browser"
|
||||
license = "/license"
|
||||
watch = "/watch"
|
||||
heal = "/heal"
|
||||
trace = "/trace"
|
||||
logs = "/logs"
|
||||
healthInfo = "/health-info"
|
||||
configuration = "/settings"
|
||||
users = "/users"
|
||||
usersDetail = "/users/:userName+"
|
||||
groups = "/groups"
|
||||
iamPolicies = "/policies"
|
||||
policiesDetail = "/policies/:policyName"
|
||||
dashboard = "/dashboard"
|
||||
metrics = "/metrics"
|
||||
profiling = "/profiling"
|
||||
buckets = "/buckets"
|
||||
bucketsDetail = "/buckets/*"
|
||||
serviceAccounts = "/account"
|
||||
changePassword = "/account/change-password"
|
||||
tenants = "/tenants"
|
||||
tenantsDetail = "/namespaces/:tenantNamespace/tenants/:tenantName"
|
||||
tenantHop = "/namespaces/:tenantNamespace/tenants/:tenantName/hop"
|
||||
podsDetail = "/namespaces/:tenantNamespace/tenants/:tenantName/pods/:podName"
|
||||
tenantsDetailSummary = "/namespaces/:tenantNamespace/tenants/:tenantName/summary"
|
||||
tenantsDetailMetrics = "/namespaces/:tenantNamespace/tenants/:tenantName/metrics"
|
||||
tenantsDetailPods = "/namespaces/:tenantNamespace/tenants/:tenantName/pods"
|
||||
tenantsDetailPools = "/namespaces/:tenantNamespace/tenants/:tenantName/pools"
|
||||
tenantsDetailLicense = "/namespaces/:tenantNamespace/tenants/:tenantName/license"
|
||||
tenantsDetailSecurity = "/namespaces/:tenantNamespace/tenants/:tenantName/security"
|
||||
storage = "/storage"
|
||||
storageVolumes = "/storage/volumes"
|
||||
storageDrives = "/storage/drives"
|
||||
remoteBuckets = "/remote-buckets"
|
||||
replication = "/replication"
|
||||
objectBrowser = "/object-browser/:bucket/*"
|
||||
objectBrowserBucket = "/object-browser/:bucket"
|
||||
mainObjectBrowser = "/object-browser"
|
||||
license = "/license"
|
||||
watch = "/watch"
|
||||
heal = "/heal"
|
||||
trace = "/trace"
|
||||
logs = "/logs"
|
||||
healthInfo = "/health-info"
|
||||
)
|
||||
|
||||
type ConfigurationActionSet struct {
|
||||
@@ -288,37 +281,30 @@ var displayRules = map[string]func() bool{
|
||||
|
||||
// endpointRules contains the mapping between endpoints and ActionSets, additional rules can be added here
|
||||
var endpointRules = map[string]ConfigurationActionSet{
|
||||
configuration: configurationActionSet,
|
||||
users: usersActionSet,
|
||||
usersDetail: usersActionSet,
|
||||
groups: groupsActionSet,
|
||||
iamPolicies: iamPoliciesActionSet,
|
||||
policiesDetail: iamPoliciesActionSet,
|
||||
dashboard: dashboardActionSet,
|
||||
metrics: dashboardActionSet,
|
||||
profiling: profilingActionSet,
|
||||
buckets: bucketsActionSet,
|
||||
bucketsDetail: bucketsActionSet,
|
||||
bucketsDetailSummary: bucketsActionSet,
|
||||
bucketsDetailEvents: bucketsActionSet,
|
||||
bucketsDetailReplication: bucketsActionSet,
|
||||
bucketsDetailLifecycle: bucketsActionSet,
|
||||
bucketsDetailAccess: bucketsActionSet,
|
||||
bucketsDetailAccessPolicies: bucketsActionSet,
|
||||
bucketsDetailAccessUsers: bucketsActionSet,
|
||||
serviceAccounts: serviceAccountsActionSet,
|
||||
changePassword: changePasswordActionSet,
|
||||
remoteBuckets: remoteBucketsActionSet,
|
||||
replication: replicationActionSet,
|
||||
objectBrowser: objectBrowserActionSet,
|
||||
mainObjectBrowser: objectBrowserActionSet,
|
||||
objectBrowserBucket: objectBrowserActionSet,
|
||||
license: licenseActionSet,
|
||||
watch: watchActionSet,
|
||||
heal: healActionSet,
|
||||
trace: traceActionSet,
|
||||
logs: logsActionSet,
|
||||
healthInfo: healthInfoActionSet,
|
||||
configuration: configurationActionSet,
|
||||
users: usersActionSet,
|
||||
usersDetail: usersActionSet,
|
||||
groups: groupsActionSet,
|
||||
iamPolicies: iamPoliciesActionSet,
|
||||
policiesDetail: iamPoliciesActionSet,
|
||||
dashboard: dashboardActionSet,
|
||||
metrics: dashboardActionSet,
|
||||
profiling: profilingActionSet,
|
||||
buckets: bucketsActionSet,
|
||||
bucketsDetail: bucketsActionSet,
|
||||
serviceAccounts: serviceAccountsActionSet,
|
||||
changePassword: changePasswordActionSet,
|
||||
remoteBuckets: remoteBucketsActionSet,
|
||||
replication: replicationActionSet,
|
||||
objectBrowser: objectBrowserActionSet,
|
||||
mainObjectBrowser: objectBrowserActionSet,
|
||||
objectBrowserBucket: objectBrowserActionSet,
|
||||
license: licenseActionSet,
|
||||
watch: watchActionSet,
|
||||
heal: healActionSet,
|
||||
trace: traceActionSet,
|
||||
logs: logsActionSet,
|
||||
healthInfo: healthInfoActionSet,
|
||||
}
|
||||
|
||||
// operatorRules contains the mapping between endpoints and ActionSets for operator only mode
|
||||
|
||||
@@ -81,7 +81,7 @@ func TestGetAuthorizedEndpoints(t *testing.T) {
|
||||
"s3:*",
|
||||
},
|
||||
},
|
||||
want: 15,
|
||||
want: 8,
|
||||
},
|
||||
{
|
||||
name: "all admin and s3 endpoints",
|
||||
@@ -91,7 +91,7 @@ func TestGetAuthorizedEndpoints(t *testing.T) {
|
||||
"s3:*",
|
||||
},
|
||||
},
|
||||
want: 31,
|
||||
want: 24,
|
||||
},
|
||||
{
|
||||
name: "Console User - default endpoints",
|
||||
|
||||
@@ -1,25 +1,25 @@
|
||||
{
|
||||
"files": {
|
||||
"main.css": "/static/css/main.8cfac526.chunk.css",
|
||||
"main.js": "/static/js/main.2d317177.chunk.js",
|
||||
"main.js.map": "/static/js/main.2d317177.chunk.js.map",
|
||||
"main.js": "/static/js/main.68952acf.chunk.js",
|
||||
"main.js.map": "/static/js/main.68952acf.chunk.js.map",
|
||||
"runtime-main.js": "/static/js/runtime-main.43a31377.js",
|
||||
"runtime-main.js.map": "/static/js/runtime-main.43a31377.js.map",
|
||||
"static/css/2.60e04a19.chunk.css": "/static/css/2.60e04a19.chunk.css",
|
||||
"static/js/2.e135a1be.chunk.js": "/static/js/2.e135a1be.chunk.js",
|
||||
"static/js/2.e135a1be.chunk.js.map": "/static/js/2.e135a1be.chunk.js.map",
|
||||
"static/js/2.d3eeac57.chunk.js": "/static/js/2.d3eeac57.chunk.js",
|
||||
"static/js/2.d3eeac57.chunk.js.map": "/static/js/2.d3eeac57.chunk.js.map",
|
||||
"index.html": "/index.html",
|
||||
"static/css/2.60e04a19.chunk.css.map": "/static/css/2.60e04a19.chunk.css.map",
|
||||
"static/css/main.8cfac526.chunk.css.map": "/static/css/main.8cfac526.chunk.css.map",
|
||||
"static/js/2.e135a1be.chunk.js.LICENSE.txt": "/static/js/2.e135a1be.chunk.js.LICENSE.txt",
|
||||
"static/js/2.d3eeac57.chunk.js.LICENSE.txt": "/static/js/2.d3eeac57.chunk.js.LICENSE.txt",
|
||||
"static/media/minio_console_logo.0837460e.svg": "/static/media/minio_console_logo.0837460e.svg",
|
||||
"static/media/minio_operator_logo.1312b7c9.svg": "/static/media/minio_operator_logo.1312b7c9.svg"
|
||||
},
|
||||
"entrypoints": [
|
||||
"static/js/runtime-main.43a31377.js",
|
||||
"static/css/2.60e04a19.chunk.css",
|
||||
"static/js/2.e135a1be.chunk.js",
|
||||
"static/js/2.d3eeac57.chunk.js",
|
||||
"static/css/main.8cfac526.chunk.css",
|
||||
"static/js/main.2d317177.chunk.js"
|
||||
"static/js/main.68952acf.chunk.js"
|
||||
]
|
||||
}
|
||||
@@ -1 +1 @@
|
||||
<!doctype html><html lang="en"><head><meta charset="utf-8"/><meta name="viewport" content="width=device-width,initial-scale=1"/><meta name="theme-color" content="#000000"/><meta name="description" content="MinIO Console"/><link href="https://fonts.googleapis.com/css2?family=Lato:wght@400;500;700;900&display=swap" rel="stylesheet"/><link href="/styles/root-styles.css" rel="stylesheet"/><link rel="apple-touch-icon" sizes="180x180" href="/apple-icon-180x180.png"/><link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png"/><link rel="icon" type="image/png" sizes="96x96" href="/favicon-96x96.png"/><link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png"/><link rel="manifest" href="/manifest.json"/><link rel="mask-icon" href="/safari-pinned-tab.svg" color="#3a4e54"/><title>MinIO Console</title><link href="/static/css/2.60e04a19.chunk.css" rel="stylesheet"><link href="/static/css/main.8cfac526.chunk.css" rel="stylesheet"></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"><div id="loader-block"><svg class="loader-svg-container" viewBox="22 22 44 44"><circle class="loader-style MuiCircularProgress-circle MuiCircularProgress-circleIndeterminate" cx="44" cy="44" r="20.2" fill="none" stroke-width="3.6"></circle></svg></div></div><script>!function(e){function r(r){for(var n,l,i=r[0],a=r[1],p=r[2],c=0,s=[];c<i.length;c++)l=i[c],Object.prototype.hasOwnProperty.call(o,l)&&o[l]&&s.push(o[l][0]),o[l]=0;for(n in a)Object.prototype.hasOwnProperty.call(a,n)&&(e[n]=a[n]);for(f&&f(r);s.length;)s.shift()();return u.push.apply(u,p||[]),t()}function t(){for(var e,r=0;r<u.length;r++){for(var t=u[r],n=!0,i=1;i<t.length;i++){var a=t[i];0!==o[a]&&(n=!1)}n&&(u.splice(r--,1),e=l(l.s=t[0]))}return e}var n={},o={1:0},u=[];function l(r){if(n[r])return n[r].exports;var t=n[r]={i:r,l:!1,exports:{}};return e[r].call(t.exports,t,t.exports,l),t.l=!0,t.exports}l.m=e,l.c=n,l.d=function(e,r,t){l.o(e,r)||Object.defineProperty(e,r,{enumerable:!0,get:t})},l.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},l.t=function(e,r){if(1&r&&(e=l(e)),8&r)return e;if(4&r&&"object"==typeof e&&e&&e.__esModule)return e;var t=Object.create(null);if(l.r(t),Object.defineProperty(t,"default",{enumerable:!0,value:e}),2&r&&"string"!=typeof e)for(var n in e)l.d(t,n,function(r){return e[r]}.bind(null,n));return t},l.n=function(e){var r=e&&e.__esModule?function(){return e.default}:function(){return e};return l.d(r,"a",r),r},l.o=function(e,r){return Object.prototype.hasOwnProperty.call(e,r)},l.p="/";var i=this["webpackJsonpportal-ui"]=this["webpackJsonpportal-ui"]||[],a=i.push.bind(i);i.push=r,i=i.slice();for(var p=0;p<i.length;p++)r(i[p]);var f=a;t()}([])</script><script src="/static/js/2.e135a1be.chunk.js"></script><script src="/static/js/main.2d317177.chunk.js"></script></body></html>
|
||||
<!doctype html><html lang="en"><head><meta charset="utf-8"/><meta name="viewport" content="width=device-width,initial-scale=1"/><meta name="theme-color" content="#000000"/><meta name="description" content="MinIO Console"/><link href="https://fonts.googleapis.com/css2?family=Lato:wght@400;500;700;900&display=swap" rel="stylesheet"/><link href="/styles/root-styles.css" rel="stylesheet"/><link rel="apple-touch-icon" sizes="180x180" href="/apple-icon-180x180.png"/><link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png"/><link rel="icon" type="image/png" sizes="96x96" href="/favicon-96x96.png"/><link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png"/><link rel="manifest" href="/manifest.json"/><link rel="mask-icon" href="/safari-pinned-tab.svg" color="#3a4e54"/><title>MinIO Console</title><link href="/static/css/2.60e04a19.chunk.css" rel="stylesheet"><link href="/static/css/main.8cfac526.chunk.css" rel="stylesheet"></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"><div id="loader-block"><svg class="loader-svg-container" viewBox="22 22 44 44"><circle class="loader-style MuiCircularProgress-circle MuiCircularProgress-circleIndeterminate" cx="44" cy="44" r="20.2" fill="none" stroke-width="3.6"></circle></svg></div></div><script>!function(e){function r(r){for(var n,l,i=r[0],a=r[1],p=r[2],c=0,s=[];c<i.length;c++)l=i[c],Object.prototype.hasOwnProperty.call(o,l)&&o[l]&&s.push(o[l][0]),o[l]=0;for(n in a)Object.prototype.hasOwnProperty.call(a,n)&&(e[n]=a[n]);for(f&&f(r);s.length;)s.shift()();return u.push.apply(u,p||[]),t()}function t(){for(var e,r=0;r<u.length;r++){for(var t=u[r],n=!0,i=1;i<t.length;i++){var a=t[i];0!==o[a]&&(n=!1)}n&&(u.splice(r--,1),e=l(l.s=t[0]))}return e}var n={},o={1:0},u=[];function l(r){if(n[r])return n[r].exports;var t=n[r]={i:r,l:!1,exports:{}};return e[r].call(t.exports,t,t.exports,l),t.l=!0,t.exports}l.m=e,l.c=n,l.d=function(e,r,t){l.o(e,r)||Object.defineProperty(e,r,{enumerable:!0,get:t})},l.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},l.t=function(e,r){if(1&r&&(e=l(e)),8&r)return e;if(4&r&&"object"==typeof e&&e&&e.__esModule)return e;var t=Object.create(null);if(l.r(t),Object.defineProperty(t,"default",{enumerable:!0,value:e}),2&r&&"string"!=typeof e)for(var n in e)l.d(t,n,function(r){return e[r]}.bind(null,n));return t},l.n=function(e){var r=e&&e.__esModule?function(){return e.default}:function(){return e};return l.d(r,"a",r),r},l.o=function(e,r){return Object.prototype.hasOwnProperty.call(e,r)},l.p="/";var i=this["webpackJsonpportal-ui"]=this["webpackJsonpportal-ui"]||[],a=i.push.bind(i);i.push=r,i=i.slice();for(var p=0;p<i.length;p++)r(i[p]);var f=a;t()}([])</script><script src="/static/js/2.d3eeac57.chunk.js"></script><script src="/static/js/main.68952acf.chunk.js"></script></body></html>
|
||||
3
portal-ui/build/static/js/2.d3eeac57.chunk.js
Normal file
3
portal-ui/build/static/js/2.d3eeac57.chunk.js
Normal file
File diff suppressed because one or more lines are too long
1
portal-ui/build/static/js/2.d3eeac57.chunk.js.map
Normal file
1
portal-ui/build/static/js/2.d3eeac57.chunk.js.map
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
2
portal-ui/build/static/js/main.68952acf.chunk.js
Normal file
2
portal-ui/build/static/js/main.68952acf.chunk.js
Normal file
File diff suppressed because one or more lines are too long
1
portal-ui/build/static/js/main.68952acf.chunk.js.map
Normal file
1
portal-ui/build/static/js/main.68952acf.chunk.js.map
Normal file
File diff suppressed because one or more lines are too long
@@ -119,6 +119,7 @@ const AccessDetails = ({
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<h1 className={classes.sectionTitle}>Access Audit</h1>
|
||||
<Tabs
|
||||
value={curTab}
|
||||
onChange={(e: React.ChangeEvent<{}>, newValue: number) => {
|
||||
|
||||
@@ -282,17 +282,16 @@ const BucketDetails = ({
|
||||
>
|
||||
<ListItemText primary="Events" />
|
||||
</ListItem>
|
||||
{canGetReplication && (
|
||||
<ListItem
|
||||
button
|
||||
selected={selectedTab === "replication"}
|
||||
onClick={() => {
|
||||
changeRoute("replication");
|
||||
}}
|
||||
>
|
||||
<ListItemText primary="Replication" />
|
||||
</ListItem>
|
||||
)}
|
||||
<ListItem
|
||||
button
|
||||
disabled={!canGetReplication}
|
||||
selected={selectedTab === "replication"}
|
||||
onClick={() => {
|
||||
changeRoute("replication");
|
||||
}}
|
||||
>
|
||||
<ListItemText primary="Replication" />
|
||||
</ListItem>
|
||||
|
||||
<ListItem
|
||||
button
|
||||
@@ -310,7 +309,7 @@ const BucketDetails = ({
|
||||
changeRoute("access");
|
||||
}}
|
||||
>
|
||||
<ListItemText primary="Audit Access" />
|
||||
<ListItemText primary="Access Audit" />
|
||||
</ListItem>
|
||||
</List>
|
||||
</Grid>
|
||||
|
||||
@@ -121,7 +121,7 @@ const BucketEventsPanel = ({
|
||||
|
||||
<Grid container>
|
||||
<Grid item xs={12} className={classes.actionsTray}>
|
||||
<h1 style={{ padding: "0px", margin: "0px" }}>Events</h1>
|
||||
<h1 className={classes.sectionTitle}>Events</h1>
|
||||
<Button
|
||||
variant="contained"
|
||||
color="primary"
|
||||
|
||||
@@ -169,7 +169,7 @@ const BucketLifecyclePanel = ({
|
||||
)}
|
||||
<Grid container>
|
||||
<Grid item xs={12} className={classes.actionsTray}>
|
||||
<h1 style={{ padding: "0px", margin: "0px" }}>Lifecycle Rules</h1>
|
||||
<h1 className={classes.sectionTitle}>Lifecycle Rules</h1>
|
||||
<Button
|
||||
variant="contained"
|
||||
color="primary"
|
||||
|
||||
@@ -61,7 +61,6 @@ const BucketReplicationPanel = ({
|
||||
BucketReplicationRule[]
|
||||
>([]);
|
||||
const [loadingPerms, setLoadingPerms] = useState<boolean>(true);
|
||||
const [canGetReplication, setCanGetReplication] = useState<boolean>(false);
|
||||
const [deleteReplicationModal, setDeleteReplicationModal] =
|
||||
useState<boolean>(false);
|
||||
const [openSetReplication, setOpenSetReplication] = useState<boolean>(false);
|
||||
@@ -102,15 +101,6 @@ const BucketReplicationPanel = ({
|
||||
} else {
|
||||
setCanPutReplication(false);
|
||||
}
|
||||
let canGetReplication = actions.find(
|
||||
(s) => s.id === "GetReplicationConfiguration"
|
||||
);
|
||||
|
||||
if (canGetReplication && canGetReplication.can) {
|
||||
setCanGetReplication(true);
|
||||
} else {
|
||||
setCanGetReplication(false);
|
||||
}
|
||||
|
||||
setLoadingPerms(false);
|
||||
})
|
||||
@@ -137,10 +127,6 @@ const BucketReplicationPanel = ({
|
||||
}
|
||||
}, [loadingReplication, setErrorSnackMessage, bucketName]);
|
||||
|
||||
if (!canGetReplication) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const closeAddReplication = () => {
|
||||
setOpenReplicationOpen(false);
|
||||
setLoadingReplication(true);
|
||||
@@ -199,21 +185,19 @@ const BucketReplicationPanel = ({
|
||||
)}
|
||||
<Grid container>
|
||||
<Grid item xs={12} className={classes.actionsTray}>
|
||||
<h1 style={{ padding: "0px", margin: "0px" }}>Replication</h1>
|
||||
|
||||
{canPutReplication && (
|
||||
<Button
|
||||
variant="contained"
|
||||
color="primary"
|
||||
startIcon={<CreateIcon />}
|
||||
size="medium"
|
||||
onClick={() => {
|
||||
setOpenReplicationOpen(true);
|
||||
}}
|
||||
>
|
||||
Add Replication Rule
|
||||
</Button>
|
||||
)}
|
||||
<h1 className={classes.sectionTitle}>Replication</h1>
|
||||
<Button
|
||||
variant="contained"
|
||||
color="primary"
|
||||
disabled={!canPutReplication}
|
||||
startIcon={<CreateIcon />}
|
||||
size="medium"
|
||||
onClick={() => {
|
||||
setOpenReplicationOpen(true);
|
||||
}}
|
||||
>
|
||||
Add Replication Rule
|
||||
</Button>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<br />
|
||||
|
||||
@@ -393,7 +393,7 @@ const BucketSummary = ({
|
||||
)}
|
||||
<Grid container>
|
||||
<Grid item xs={12} className={classes.actionsTray}>
|
||||
<h1 style={{ padding: "0px", margin: "0px" }}>Summary</h1>
|
||||
<h1 className={classes.sectionTitle}>Summary</h1>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<br />
|
||||
|
||||
@@ -14,13 +14,24 @@
|
||||
// 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 } from "react";
|
||||
import React, { useEffect, useState, Fragment } from "react";
|
||||
import { connect } from "react-redux";
|
||||
import get from "lodash/get";
|
||||
import * as reactMoment from "react-moment";
|
||||
import clsx from "clsx";
|
||||
import { createStyles, Theme, withStyles } from "@material-ui/core/styles";
|
||||
import { CircularProgress } from "@material-ui/core";
|
||||
import {
|
||||
CircularProgress,
|
||||
Paper,
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableRow,
|
||||
Tooltip,
|
||||
} from "@material-ui/core";
|
||||
import ListItem from "@material-ui/core/ListItem";
|
||||
import ListItemText from "@material-ui/core/ListItemText";
|
||||
import List from "@material-ui/core/List";
|
||||
import Grid from "@material-ui/core/Grid";
|
||||
import Chip from "@material-ui/core/Chip";
|
||||
import TextField from "@material-ui/core/TextField";
|
||||
@@ -32,10 +43,12 @@ import CloseIcon from "@material-ui/icons/Close";
|
||||
import ShareFile from "./ShareFile";
|
||||
import {
|
||||
actionsTray,
|
||||
buttonsStyles,
|
||||
containerForHeader,
|
||||
hrClass,
|
||||
searchField,
|
||||
} from "../../../../Common/FormComponents/common/styleLibrary";
|
||||
import { IFileInfo } from "./types";
|
||||
import { FileInfoResponse, IFileInfo } from "./types";
|
||||
import {
|
||||
fileDownloadStarted,
|
||||
fileIsBeingPrepared,
|
||||
@@ -43,6 +56,7 @@ import {
|
||||
} from "../../../../ObjectBrowser/actions";
|
||||
import { Route } from "../../../../ObjectBrowser/reducers";
|
||||
import { download } from "../utils";
|
||||
import { TabPanel } from "../../../../../shared/tabs";
|
||||
import history from "../../../../../../history";
|
||||
import api from "../../../../../../common/api";
|
||||
import PageHeader from "../../../../Common/PageHeader/PageHeader";
|
||||
@@ -140,6 +154,33 @@ const styles = (theme: Theme) =>
|
||||
marginRight: 0,
|
||||
},
|
||||
},
|
||||
paperContainer: {
|
||||
padding: 15,
|
||||
paddingLeft: 50,
|
||||
display: "flex",
|
||||
},
|
||||
elementTitle: {
|
||||
fontWeight: 500,
|
||||
color: "#777777",
|
||||
fontSize: 14,
|
||||
marginTop: -9,
|
||||
},
|
||||
dualCardLeft: {
|
||||
paddingRight: "5px",
|
||||
},
|
||||
dualCardRight: {
|
||||
paddingLeft: "5px",
|
||||
},
|
||||
capitalizeFirst: {
|
||||
textTransform: "capitalize",
|
||||
},
|
||||
titleCol: {
|
||||
width: "25%",
|
||||
},
|
||||
titleItem: {
|
||||
width: "35%",
|
||||
},
|
||||
|
||||
"@global": {
|
||||
".progressDetails": {
|
||||
paddingTop: 3,
|
||||
@@ -154,6 +195,8 @@ const styles = (theme: Theme) =>
|
||||
top: 3,
|
||||
},
|
||||
},
|
||||
...hrClass,
|
||||
...buttonsStyles,
|
||||
...actionsTray,
|
||||
...searchField,
|
||||
...containerForHeader(theme.spacing(4)),
|
||||
@@ -211,6 +254,9 @@ const ObjectDetails = ({
|
||||
const [versions, setVersions] = useState<IFileInfo[]>([]);
|
||||
const [filterVersion, setFilterVersion] = useState<string>("");
|
||||
const [deleteOpen, setDeleteOpen] = useState<boolean>(false);
|
||||
const [metadataLoad, setMetadataLoad] = useState<boolean>(false);
|
||||
const [metadata, setMetadata] = useState<any>({});
|
||||
const [selectedTab, setSelectedTab] = useState<number>(0);
|
||||
|
||||
const currentItem = routesList[routesList.length - 1];
|
||||
const allPathData = currentItem.route.split("/");
|
||||
@@ -241,6 +287,7 @@ const ObjectDetails = ({
|
||||
}
|
||||
|
||||
setLoadObjectData(false);
|
||||
setMetadataLoad(true);
|
||||
})
|
||||
.catch((error: ErrorResponseHandler) => {
|
||||
setErrorSnackMessage(error);
|
||||
@@ -255,6 +302,28 @@ const ObjectDetails = ({
|
||||
distributedSetup,
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
if (metadataLoad) {
|
||||
const encodedPath = encodeURIComponent(pathInBucket);
|
||||
api
|
||||
.invoke(
|
||||
"GET",
|
||||
`/api/v1/buckets/${bucketName}/objects?prefix=${encodedPath}&with_metadata=true`
|
||||
)
|
||||
.then((res: FileInfoResponse) => {
|
||||
const fileData = res.objects[0];
|
||||
let metadata = get(fileData, "user_metadata", {});
|
||||
|
||||
setMetadata(metadata);
|
||||
console.log("metadata:", res);
|
||||
setMetadataLoad(false);
|
||||
})
|
||||
.catch((error: ErrorResponseHandler) => {
|
||||
setMetadataLoad(false);
|
||||
});
|
||||
}
|
||||
}, [bucketName, metadataLoad, pathInBucket]);
|
||||
|
||||
let tagKeys: string[] = [];
|
||||
|
||||
if (actualInfo.tags) {
|
||||
@@ -381,7 +450,61 @@ const ObjectDetails = ({
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<PageHeader label={"Object Browser"} />
|
||||
<PageHeader
|
||||
label={"Object Browser > Details"}
|
||||
actions={
|
||||
<Fragment>
|
||||
<Tooltip title="Share">
|
||||
<IconButton
|
||||
color="primary"
|
||||
aria-label="share"
|
||||
onClick={() => {
|
||||
shareObject();
|
||||
}}
|
||||
disabled={actualInfo.is_delete_marker}
|
||||
>
|
||||
<ShareIcon />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
|
||||
{downloadingFiles.includes(`${bucketName}/${actualInfo.name}`) ? (
|
||||
<div className="progressDetails">
|
||||
<CircularProgress
|
||||
color="primary"
|
||||
size={17}
|
||||
variant="indeterminate"
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<Tooltip title="Download">
|
||||
<IconButton
|
||||
color="primary"
|
||||
aria-label="download"
|
||||
onClick={() => {
|
||||
downloadObject(actualInfo);
|
||||
}}
|
||||
disabled={actualInfo.is_delete_marker}
|
||||
>
|
||||
<DownloadIcon />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
)}
|
||||
|
||||
<Tooltip title="Delete Object">
|
||||
<IconButton
|
||||
color="primary"
|
||||
aria-label="delete"
|
||||
onClick={() => {
|
||||
setDeleteOpen(true);
|
||||
}}
|
||||
disabled={actualInfo.is_delete_marker}
|
||||
>
|
||||
<DeleteIcon />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
</Fragment>
|
||||
}
|
||||
/>
|
||||
{shareFileModalOpen && (
|
||||
<ShareFile
|
||||
open={shareFileModalOpen}
|
||||
@@ -437,222 +560,243 @@ const ObjectDetails = ({
|
||||
actualInfo={actualInfo}
|
||||
/>
|
||||
)}
|
||||
<Grid container>
|
||||
<Grid item xs={12} className={classes.container}>
|
||||
<Grid item xs={12} className={classes.obTitleSection}>
|
||||
<div>
|
||||
<BrowserBreadcrumbs />
|
||||
</div>
|
||||
</Grid>
|
||||
<br />
|
||||
<Grid item xs={12} className={classes.propertiesContainer}>
|
||||
{actualInfo.version_id && actualInfo.version_id !== "null" && (
|
||||
<React.Fragment>
|
||||
<div className={classes.propertiesItem}>
|
||||
<div>
|
||||
<span className={classes.propertiesItemBold}>
|
||||
Legal Hold:
|
||||
</span>
|
||||
<span className={classes.propertiesValue}>
|
||||
{actualInfo.legal_hold_status
|
||||
? actualInfo.legal_hold_status.toLowerCase()
|
||||
: "Off"}
|
||||
</span>
|
||||
</div>
|
||||
<div>
|
||||
<IconButton
|
||||
color="primary"
|
||||
aria-label="legal-hold"
|
||||
size="small"
|
||||
className={classes.propertiesIcon}
|
||||
onClick={() => {
|
||||
setLegalholdOpen(true);
|
||||
}}
|
||||
>
|
||||
<PencilIcon active={true} />
|
||||
</IconButton>
|
||||
</div>
|
||||
</div>
|
||||
<div className={classes.propertiesItem}>
|
||||
<div>
|
||||
<span className={classes.propertiesItemBold}>
|
||||
Retention:
|
||||
</span>
|
||||
<span className={classes.propertiesValue}>
|
||||
{actualInfo.retention_mode
|
||||
? actualInfo.retention_mode.toLowerCase()
|
||||
: "Undefined"}
|
||||
</span>
|
||||
</div>
|
||||
<div>
|
||||
<IconButton
|
||||
color="primary"
|
||||
aria-label="retention"
|
||||
size="small"
|
||||
className={classes.propertiesIcon}
|
||||
onClick={() => {
|
||||
openRetentionModal();
|
||||
}}
|
||||
>
|
||||
<PencilIcon active={true} />
|
||||
</IconButton>
|
||||
</div>
|
||||
</div>
|
||||
</React.Fragment>
|
||||
)}
|
||||
<div className={classes.propertiesItem}>
|
||||
<div className={classes.propertiesItemBold}>File Actions:</div>
|
||||
<div className={classes.actionsIconContainer}>
|
||||
<IconButton
|
||||
color="primary"
|
||||
aria-label="share"
|
||||
size="small"
|
||||
className={classes.actionsIcon}
|
||||
onClick={() => {
|
||||
shareObject();
|
||||
}}
|
||||
disabled={actualInfo.is_delete_marker}
|
||||
>
|
||||
<ShareIcon />
|
||||
</IconButton>
|
||||
</div>
|
||||
<div className={classes.actionsIconContainer}>
|
||||
{downloadingFiles.includes(
|
||||
`${bucketName}/${actualInfo.name}`
|
||||
) ? (
|
||||
<div className="progressDetails">
|
||||
<CircularProgress
|
||||
color="primary"
|
||||
size={17}
|
||||
variant="indeterminate"
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<IconButton
|
||||
color="primary"
|
||||
aria-label="download"
|
||||
size="small"
|
||||
className={classes.actionsIcon}
|
||||
onClick={() => {
|
||||
downloadObject(actualInfo);
|
||||
}}
|
||||
disabled={actualInfo.is_delete_marker}
|
||||
>
|
||||
<DownloadIcon />
|
||||
</IconButton>
|
||||
)}
|
||||
</div>
|
||||
<div className={classes.actionsIconContainer}>
|
||||
<IconButton
|
||||
color="primary"
|
||||
aria-label="delete"
|
||||
size="small"
|
||||
className={classes.actionsIcon}
|
||||
onClick={() => {
|
||||
setDeleteOpen(true);
|
||||
}}
|
||||
disabled={actualInfo.is_delete_marker}
|
||||
>
|
||||
<DeleteIcon />
|
||||
</IconButton>
|
||||
</div>
|
||||
</div>
|
||||
</Grid>
|
||||
|
||||
<Grid item xs={12} className={classes.tagsContainer}>
|
||||
<div className={classes.tagText}>Tags:</div>
|
||||
{tagKeys &&
|
||||
tagKeys.map((tagKey, index) => {
|
||||
const tag = get(actualInfo, `tags.${tagKey}`, "");
|
||||
if (tag !== "") {
|
||||
return (
|
||||
<Chip
|
||||
key={`chip-${index}`}
|
||||
className={classes.tag}
|
||||
size="small"
|
||||
label={`${tagKey} : ${tag}`}
|
||||
color="primary"
|
||||
deleteIcon={<CloseIcon />}
|
||||
onDelete={() => {
|
||||
deleteTag(tagKey, tag);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
})}
|
||||
<Chip
|
||||
className={classes.tag}
|
||||
icon={<AddIcon />}
|
||||
clickable
|
||||
size="small"
|
||||
label="Add tag"
|
||||
color="primary"
|
||||
variant="outlined"
|
||||
<Grid container className={classes.container}>
|
||||
<Grid item xs={12} className={classes.obTitleSection}>
|
||||
<div>
|
||||
<BrowserBreadcrumbs />
|
||||
</div>
|
||||
<hr style={{ border: 0, borderTop: "1px solid #EAEAEA" }} />
|
||||
</Grid>
|
||||
<Grid item xs={2}>
|
||||
<List component="nav" dense={true}>
|
||||
<ListItem
|
||||
button
|
||||
selected={selectedTab === 0}
|
||||
onClick={() => {
|
||||
setTagModalOpen(true);
|
||||
setSelectedTab(0);
|
||||
}}
|
||||
/>
|
||||
</Grid>
|
||||
|
||||
<Grid item xs={12} className={classes.actionsTray}>
|
||||
{actualInfo.version_id && actualInfo.version_id !== "null" && (
|
||||
<TextField
|
||||
placeholder={`Search ${objectName}`}
|
||||
className={clsx(classes.search, classes.searchField)}
|
||||
id="search-resource"
|
||||
label=""
|
||||
onChange={(val) => {
|
||||
setFilterVersion(val.target.value);
|
||||
}}
|
||||
InputProps={{
|
||||
disableUnderline: true,
|
||||
startAdornment: (
|
||||
<InputAdornment position="start">
|
||||
<SearchIcon />
|
||||
</InputAdornment>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</Grid>
|
||||
>
|
||||
<ListItemText primary="Details" />
|
||||
</ListItem>
|
||||
<ListItem
|
||||
button
|
||||
selected={selectedTab === 1}
|
||||
onClick={() => {
|
||||
setSelectedTab(1);
|
||||
}}
|
||||
disabled={
|
||||
!(actualInfo.version_id && actualInfo.version_id !== "null")
|
||||
}
|
||||
>
|
||||
<ListItemText primary="Versions" />
|
||||
</ListItem>
|
||||
</List>
|
||||
</Grid>
|
||||
<Grid item xs={10}>
|
||||
<Grid item xs={12}>
|
||||
{actualInfo.version_id && actualInfo.version_id !== "null" && (
|
||||
<TableWrapper
|
||||
itemActions={tableActions}
|
||||
columns={[
|
||||
{
|
||||
label: "",
|
||||
width: 20,
|
||||
renderFullObject: true,
|
||||
renderFunction: (r) => {
|
||||
const versOrd = versions.length - versions.indexOf(r);
|
||||
return `v${versOrd}`;
|
||||
},
|
||||
},
|
||||
{ label: "Version ID", elementKey: "version_id" },
|
||||
{
|
||||
label: "Last Modified",
|
||||
elementKey: "last_modified",
|
||||
renderFunction: displayParsedDate,
|
||||
},
|
||||
{
|
||||
label: "Deleted",
|
||||
width: 60,
|
||||
contentTextAlign: "center",
|
||||
renderFullObject: true,
|
||||
renderFunction: (r) => {
|
||||
const versOrd = r.is_delete_marker ? "Yes" : "No";
|
||||
return `${versOrd}`;
|
||||
},
|
||||
},
|
||||
]}
|
||||
isLoading={false}
|
||||
entityName="Versions"
|
||||
idField="version_id"
|
||||
records={filteredRecords}
|
||||
/>
|
||||
)}
|
||||
<TabPanel index={0} value={selectedTab}>
|
||||
<div className={classes.actionsTray}>
|
||||
<h1 className={classes.sectionTitle}>Details</h1>
|
||||
</div>
|
||||
<br />
|
||||
<Paper className={classes.paperContainer}>
|
||||
<Grid container>
|
||||
<Grid item xs={10}>
|
||||
<table width={"100%"}>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td className={classes.titleCol}>Legal Hold:</td>
|
||||
<td className={classes.capitalizeFirst}>
|
||||
{actualInfo.version_id &&
|
||||
actualInfo.version_id !== "null" ? (
|
||||
<Fragment>
|
||||
{actualInfo.legal_hold_status
|
||||
? actualInfo.legal_hold_status.toLowerCase()
|
||||
: "Off"}
|
||||
<IconButton
|
||||
color="primary"
|
||||
aria-label="legal-hold"
|
||||
size="small"
|
||||
className={classes.propertiesIcon}
|
||||
onClick={() => {
|
||||
setLegalholdOpen(true);
|
||||
}}
|
||||
>
|
||||
<PencilIcon active={true} />
|
||||
</IconButton>
|
||||
</Fragment>
|
||||
) : (
|
||||
"Disabled"
|
||||
)}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td className={classes.titleCol}>Retention:</td>
|
||||
<td className={classes.capitalizeFirst}>
|
||||
{actualInfo.retention_mode
|
||||
? actualInfo.retention_mode.toLowerCase()
|
||||
: "Undefined"}
|
||||
<IconButton
|
||||
color="primary"
|
||||
aria-label="retention"
|
||||
size="small"
|
||||
className={classes.propertiesIcon}
|
||||
onClick={() => {
|
||||
openRetentionModal();
|
||||
}}
|
||||
>
|
||||
<PencilIcon active={true} />
|
||||
</IconButton>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td className={classes.titleCol}>Tags:</td>
|
||||
<td>
|
||||
{tagKeys &&
|
||||
tagKeys.map((tagKey, index) => {
|
||||
const tag = get(
|
||||
actualInfo,
|
||||
`tags.${tagKey}`,
|
||||
""
|
||||
);
|
||||
if (tag !== "") {
|
||||
return (
|
||||
<Chip
|
||||
key={`chip-${index}`}
|
||||
className={classes.tag}
|
||||
size="small"
|
||||
label={`${tagKey} : ${tag}`}
|
||||
color="primary"
|
||||
deleteIcon={<CloseIcon />}
|
||||
onDelete={() => {
|
||||
deleteTag(tagKey, tag);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
})}
|
||||
<Chip
|
||||
className={classes.tag}
|
||||
icon={<AddIcon />}
|
||||
clickable
|
||||
size="small"
|
||||
label="Add tag"
|
||||
color="primary"
|
||||
variant="outlined"
|
||||
onClick={() => {
|
||||
setTagModalOpen(true);
|
||||
}}
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Paper>
|
||||
<br />
|
||||
<br />
|
||||
<Paper className={classes.paperContainer}>
|
||||
<Grid item xs={12}>
|
||||
<Grid item xs={12}>
|
||||
<h2>Object Metadata</h2>
|
||||
<hr className={classes.hr}></hr>
|
||||
</Grid>
|
||||
|
||||
<Grid item xs={12}>
|
||||
<Table className={classes.table} aria-label="simple table">
|
||||
<TableBody>
|
||||
{Object.keys(metadata).map((element) => {
|
||||
return (
|
||||
<TableRow>
|
||||
<TableCell
|
||||
component="th"
|
||||
scope="row"
|
||||
className={classes.titleItem}
|
||||
>
|
||||
{element}
|
||||
</TableCell>
|
||||
<TableCell align="right">
|
||||
{metadata[element]}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
);
|
||||
})}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Paper>
|
||||
</TabPanel>
|
||||
<TabPanel index={1} value={selectedTab}>
|
||||
<Fragment>
|
||||
<div className={classes.actionsTray}>
|
||||
<h1 className={classes.sectionTitle}>Versions</h1>
|
||||
</div>
|
||||
<br />
|
||||
<Grid item xs={12} className={classes.actionsTray}>
|
||||
{actualInfo.version_id && actualInfo.version_id !== "null" && (
|
||||
<TextField
|
||||
placeholder={`Search ${objectName}`}
|
||||
className={clsx(classes.search, classes.searchField)}
|
||||
id="search-resource"
|
||||
label=""
|
||||
onChange={(val) => {
|
||||
setFilterVersion(val.target.value);
|
||||
}}
|
||||
InputProps={{
|
||||
disableUnderline: true,
|
||||
startAdornment: (
|
||||
<InputAdornment position="start">
|
||||
<SearchIcon />
|
||||
</InputAdornment>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
{actualInfo.version_id && actualInfo.version_id !== "null" && (
|
||||
<TableWrapper
|
||||
itemActions={tableActions}
|
||||
columns={[
|
||||
{
|
||||
label: "",
|
||||
width: 20,
|
||||
renderFullObject: true,
|
||||
renderFunction: (r) => {
|
||||
const versOrd =
|
||||
versions.length - versions.indexOf(r);
|
||||
return `v${versOrd}`;
|
||||
},
|
||||
},
|
||||
{ label: "Version ID", elementKey: "version_id" },
|
||||
{
|
||||
label: "Last Modified",
|
||||
elementKey: "last_modified",
|
||||
renderFunction: displayParsedDate,
|
||||
},
|
||||
{
|
||||
label: "Deleted",
|
||||
width: 60,
|
||||
contentTextAlign: "center",
|
||||
renderFullObject: true,
|
||||
renderFunction: (r) => {
|
||||
const versOrd = r.is_delete_marker ? "Yes" : "No";
|
||||
return `${versOrd}`;
|
||||
},
|
||||
},
|
||||
]}
|
||||
isLoading={false}
|
||||
entityName="Versions"
|
||||
idField="version_id"
|
||||
records={filteredRecords}
|
||||
/>
|
||||
)}
|
||||
</Grid>
|
||||
</Fragment>
|
||||
</TabPanel>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Grid>
|
||||
|
||||
@@ -25,4 +25,10 @@ export interface IFileInfo {
|
||||
tags?: object;
|
||||
version_id: string | null;
|
||||
is_delete_marker?: boolean;
|
||||
user_metadata?: object;
|
||||
}
|
||||
|
||||
export interface FileInfoResponse {
|
||||
objects: IFileInfo[];
|
||||
total: number;
|
||||
}
|
||||
|
||||
@@ -138,6 +138,10 @@ export const containerForHeader = (bottomSpacing: any) => ({
|
||||
},
|
||||
},
|
||||
},
|
||||
sectionTitle: {
|
||||
padding: "0px",
|
||||
margin: "0px",
|
||||
},
|
||||
topSpacer: {
|
||||
height: "8px",
|
||||
},
|
||||
|
||||
@@ -19,14 +19,15 @@ import PageHeader from "../Common/PageHeader/PageHeader";
|
||||
import { Grid } from "@material-ui/core";
|
||||
import { createStyles, Theme, withStyles } from "@material-ui/core/styles";
|
||||
import { containerForHeader } from "../Common/FormComponents/common/styleLibrary";
|
||||
import Tab from "@material-ui/core/Tab";
|
||||
import Tabs from "@material-ui/core/Tabs";
|
||||
import ConfigurationsList from "./ConfigurationPanels/ConfigurationsList";
|
||||
import ListNotificationEndpoints from "./NotificationEndpoints/ListNotificationEndpoints";
|
||||
import ListTiersConfiguration from "./TiersConfiguration/ListTiersConfiguration";
|
||||
import { AppState } from "../../../store";
|
||||
import { connect } from "react-redux";
|
||||
import { ISessionResponse } from "../types";
|
||||
import List from "@material-ui/core/List";
|
||||
import ListItem from "@material-ui/core/ListItem";
|
||||
import ListItemText from "@material-ui/core/ListItemText";
|
||||
|
||||
interface IConfigurationMain {
|
||||
classes: any;
|
||||
@@ -55,40 +56,57 @@ const ConfigurationMain = ({
|
||||
return (
|
||||
<Fragment>
|
||||
<PageHeader label="Settings" />
|
||||
<Grid container>
|
||||
<Grid item xs={12} className={classes.container}>
|
||||
<Tabs
|
||||
value={selectedTab}
|
||||
indicatorColor="primary"
|
||||
textColor="primary"
|
||||
onChange={(_, newValue: number) => {
|
||||
setSelectedTab(newValue);
|
||||
}}
|
||||
aria-label="tenant-tabs"
|
||||
variant="scrollable"
|
||||
scrollButtons="auto"
|
||||
>
|
||||
<Tab label="Configurations" />
|
||||
<Tab label="Lambda Notifications" />
|
||||
<Tab label="Tiers" disabled={!distributedSetup} />
|
||||
</Tabs>
|
||||
<Grid item xs={12}>
|
||||
{selectedTab === 0 && (
|
||||
<Grid item xs={12}>
|
||||
<ConfigurationsList />
|
||||
</Grid>
|
||||
)}
|
||||
{selectedTab === 1 && (
|
||||
<Grid item xs={12}>
|
||||
<ListNotificationEndpoints />
|
||||
</Grid>
|
||||
)}
|
||||
{selectedTab === 2 && distributedSetup && (
|
||||
<Grid item xs={12}>
|
||||
<ListTiersConfiguration />
|
||||
</Grid>
|
||||
)}
|
||||
</Grid>
|
||||
<Grid container className={classes.container}>
|
||||
<Grid item xs={2}>
|
||||
<List component="nav" dense={true}>
|
||||
<ListItem
|
||||
button
|
||||
selected={selectedTab === 0}
|
||||
onClick={() => {
|
||||
setSelectedTab(0);
|
||||
}}
|
||||
>
|
||||
<ListItemText primary="Configurations" />
|
||||
</ListItem>
|
||||
<ListItem
|
||||
button
|
||||
selected={selectedTab === 1}
|
||||
onClick={() => {
|
||||
setSelectedTab(1);
|
||||
}}
|
||||
>
|
||||
<ListItemText primary="Lambda Notifications" />
|
||||
</ListItem>
|
||||
<ListItem
|
||||
button
|
||||
selected={selectedTab === 2}
|
||||
onClick={() => {
|
||||
setSelectedTab(2);
|
||||
}}
|
||||
>
|
||||
<ListItemText primary="Tiers" />
|
||||
</ListItem>
|
||||
</List>
|
||||
</Grid>
|
||||
<Grid item xs={10}>
|
||||
{selectedTab === 0 && (
|
||||
<Grid item xs={12}>
|
||||
<h1 className={classes.sectionTitle}>Configurations</h1>
|
||||
<ConfigurationsList />
|
||||
</Grid>
|
||||
)}
|
||||
{selectedTab === 1 && (
|
||||
<Grid item xs={12}>
|
||||
<h1 className={classes.sectionTitle}>Lambda Notifications</h1>
|
||||
<ListNotificationEndpoints />
|
||||
</Grid>
|
||||
)}
|
||||
{selectedTab === 2 && distributedSetup && (
|
||||
<Grid item xs={12}>
|
||||
<h1 className={classes.sectionTitle}>Tiers</h1>
|
||||
<ListTiersConfiguration />
|
||||
</Grid>
|
||||
)}
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Fragment>
|
||||
|
||||
@@ -237,39 +237,7 @@ const Console = ({
|
||||
},
|
||||
{
|
||||
component: Buckets,
|
||||
path: "/buckets/:bucketName",
|
||||
},
|
||||
{
|
||||
component: Buckets,
|
||||
path: "/buckets/:bucketName/summary",
|
||||
},
|
||||
{
|
||||
component: Buckets,
|
||||
path: "/buckets/:bucketName/events",
|
||||
},
|
||||
{
|
||||
component: Buckets,
|
||||
path: "/buckets/:bucketName/replication",
|
||||
},
|
||||
{
|
||||
component: Buckets,
|
||||
path: "/buckets/:bucketName/lifecycle",
|
||||
},
|
||||
{
|
||||
component: Buckets,
|
||||
path: "/buckets/:bucketName/access",
|
||||
},
|
||||
{
|
||||
component: Buckets,
|
||||
path: "/buckets/:bucketName/access",
|
||||
},
|
||||
{
|
||||
component: Buckets,
|
||||
path: "/buckets/:bucketName/access/policies",
|
||||
},
|
||||
{
|
||||
component: Buckets,
|
||||
path: "/buckets/:bucketName/access/users",
|
||||
path: "/buckets/*",
|
||||
},
|
||||
{
|
||||
component: ObjectBrowser,
|
||||
|
||||
@@ -249,7 +249,7 @@ const DirectCSIMain = ({
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
||||
<h1 className={classes.sectionTitle}>Drives</h1>
|
||||
<Grid item xs={12} className={classes.actionsTray}>
|
||||
<TextField
|
||||
placeholder="Search Drives"
|
||||
|
||||
@@ -17,10 +17,8 @@
|
||||
import React, { Fragment, useState } from "react";
|
||||
import { connect } from "react-redux";
|
||||
import PageHeader from "../Common/PageHeader/PageHeader";
|
||||
import { Grid } from "@material-ui/core";
|
||||
import { Grid, List, ListItem, ListItemText } from "@material-ui/core";
|
||||
import { createStyles, Theme, withStyles } from "@material-ui/core/styles";
|
||||
import Tab from "@material-ui/core/Tab";
|
||||
import Tabs from "@material-ui/core/Tabs";
|
||||
import { containerForHeader } from "../Common/FormComponents/common/styleLibrary";
|
||||
import ErrorLogs from "./ErrorLogs/ErrorLogs";
|
||||
import LogsSearchMain from "./LogSearch/LogsSearchMain";
|
||||
@@ -50,39 +48,43 @@ const LogsMain = ({ classes, features }: ILogsMainProps) => {
|
||||
return (
|
||||
<Fragment>
|
||||
<PageHeader label="Logs" />
|
||||
<Grid container>
|
||||
<Grid item xs={12} className={classes.container}>
|
||||
<Fragment>
|
||||
<Grid item xs={12} className={classes.headerLabel}>
|
||||
All Logs
|
||||
</Grid>
|
||||
<Tabs
|
||||
value={currentTab}
|
||||
onChange={(e: React.ChangeEvent<{}>, newValue: number) => {
|
||||
setCurrentTab(newValue);
|
||||
<Grid container className={classes.container}>
|
||||
<Grid item xs={2}>
|
||||
<List component="nav" dense={true}>
|
||||
<ListItem
|
||||
button
|
||||
selected={currentTab === 0}
|
||||
onClick={() => {
|
||||
setCurrentTab(0);
|
||||
}}
|
||||
indicatorColor="primary"
|
||||
textColor="primary"
|
||||
aria-label="cluster-tabs"
|
||||
variant="scrollable"
|
||||
scrollButtons="auto"
|
||||
>
|
||||
<Tab label="Error Logs" />
|
||||
{logSearchEnabled && <Tab label="Logs Search" />}
|
||||
</Tabs>
|
||||
<Grid item xs={12}>
|
||||
{currentTab === 0 && (
|
||||
<Grid item xs={12}>
|
||||
<ErrorLogs />
|
||||
</Grid>
|
||||
)}
|
||||
{currentTab === 1 && logSearchEnabled && (
|
||||
<Grid item xs={12}>
|
||||
<LogsSearchMain />
|
||||
</Grid>
|
||||
)}
|
||||
</Grid>
|
||||
</Fragment>
|
||||
<ListItemText primary="Error Logs" />
|
||||
</ListItem>
|
||||
<ListItem
|
||||
button
|
||||
selected={currentTab === 1}
|
||||
disabled={!logSearchEnabled}
|
||||
onClick={() => {
|
||||
setCurrentTab(1);
|
||||
}}
|
||||
>
|
||||
<ListItemText primary="Audit Logs" />
|
||||
</ListItem>
|
||||
</List>
|
||||
</Grid>
|
||||
<Grid item xs={10}>
|
||||
{currentTab === 0 && (
|
||||
<Fragment>
|
||||
<h1 className={classes.sectionTitle}>Error Logs</h1>
|
||||
<ErrorLogs />
|
||||
</Fragment>
|
||||
)}
|
||||
{currentTab === 1 && logSearchEnabled && (
|
||||
<Fragment>
|
||||
<h1 className={classes.sectionTitle}>Audit Logs</h1>
|
||||
<LogsSearchMain />
|
||||
</Fragment>
|
||||
)}
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Fragment>
|
||||
|
||||
@@ -18,14 +18,14 @@ import { Policy } from "./types";
|
||||
import { connect } from "react-redux";
|
||||
import { createStyles, Theme, withStyles } from "@material-ui/core/styles";
|
||||
import {
|
||||
actionsTray,
|
||||
containerForHeader,
|
||||
modalBasic,
|
||||
searchField,
|
||||
} from "../Common/FormComponents/common/styleLibrary";
|
||||
import Paper from "@material-ui/core/Paper";
|
||||
import Grid from "@material-ui/core/Grid";
|
||||
import { Button, LinearProgress } from "@material-ui/core";
|
||||
import Tabs from "@material-ui/core/Tabs";
|
||||
import Tab from "@material-ui/core/Tab";
|
||||
import TableWrapper from "../Common/TableWrapper/TableWrapper";
|
||||
import api from "../../../common/api";
|
||||
import PageHeader from "../Common/PageHeader/PageHeader";
|
||||
@@ -36,11 +36,10 @@ import CodeMirrorWrapper from "../Common/FormComponents/CodeMirrorWrapper/CodeMi
|
||||
import history from "../../../history";
|
||||
import InputAdornment from "@material-ui/core/InputAdornment";
|
||||
import SearchIcon from "@material-ui/icons/Search";
|
||||
import {
|
||||
actionsTray,
|
||||
searchField,
|
||||
} from "../Common/FormComponents/common/styleLibrary";
|
||||
import TextField from "@material-ui/core/TextField";
|
||||
import ListItem from "@material-ui/core/ListItem";
|
||||
import ListItemText from "@material-ui/core/ListItemText";
|
||||
import List from "@material-ui/core/List";
|
||||
|
||||
interface IPolicyDetailsProps {
|
||||
classes: any;
|
||||
@@ -300,141 +299,167 @@ const PolicyDetails = ({
|
||||
</Fragment>
|
||||
}
|
||||
/>
|
||||
<Grid item xs={12} className={classes.container}>
|
||||
<Grid item xs={12}>
|
||||
<Tabs
|
||||
value={selectedTab}
|
||||
indicatorColor="primary"
|
||||
textColor="primary"
|
||||
onChange={(_, newValue: number) => {
|
||||
setSelectedTab(newValue);
|
||||
}}
|
||||
aria-label="policy-tabs"
|
||||
>
|
||||
<Tab label="Details" />
|
||||
<Tab label="Users" />
|
||||
<Tab label="Groups" />
|
||||
</Tabs>
|
||||
</Grid>
|
||||
{selectedTab === 0 && (
|
||||
<Paper className={classes.paperContainer}>
|
||||
<form
|
||||
noValidate
|
||||
autoComplete="off"
|
||||
onSubmit={(e: React.FormEvent<HTMLFormElement>) => {
|
||||
saveRecord(e);
|
||||
<Grid container className={classes.container}>
|
||||
<Grid item xs={2}>
|
||||
<List component="nav" dense={true}>
|
||||
<ListItem
|
||||
button
|
||||
selected={selectedTab === 0}
|
||||
onClick={() => {
|
||||
setSelectedTab(0);
|
||||
}}
|
||||
>
|
||||
<ListItemText primary="Details" />
|
||||
</ListItem>
|
||||
<ListItem
|
||||
button
|
||||
selected={selectedTab === 1}
|
||||
onClick={() => {
|
||||
setSelectedTab(1);
|
||||
}}
|
||||
>
|
||||
<ListItemText primary="Users" />
|
||||
</ListItem>
|
||||
<ListItem
|
||||
button
|
||||
selected={selectedTab === 2}
|
||||
onClick={() => {
|
||||
setSelectedTab(2);
|
||||
}}
|
||||
>
|
||||
<ListItemText primary="Groups" />
|
||||
</ListItem>
|
||||
</List>
|
||||
</Grid>
|
||||
<Grid item xs={10}>
|
||||
{selectedTab === 0 && (
|
||||
<Fragment>
|
||||
<h1 className={classes.sectionTitle}>Edit Policy</h1>
|
||||
<Paper className={classes.paperContainer}>
|
||||
<form
|
||||
noValidate
|
||||
autoComplete="off"
|
||||
onSubmit={(e: React.FormEvent<HTMLFormElement>) => {
|
||||
saveRecord(e);
|
||||
}}
|
||||
>
|
||||
<Grid container>
|
||||
<Grid item xs={12} className={classes.formScrollable}>
|
||||
<CodeMirrorWrapper
|
||||
value={policyDefinition}
|
||||
onBeforeChange={(editor, data, value) => {
|
||||
setPolicyDefinition(value);
|
||||
}}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12} className={classes.buttonContainer}>
|
||||
{!policy && (
|
||||
<button
|
||||
type="button"
|
||||
color="primary"
|
||||
className={classes.clearButton}
|
||||
onClick={() => {
|
||||
resetForm();
|
||||
}}
|
||||
>
|
||||
Clear
|
||||
</button>
|
||||
)}
|
||||
|
||||
<Button
|
||||
type="submit"
|
||||
variant="contained"
|
||||
color="primary"
|
||||
disabled={addLoading || !validSave}
|
||||
>
|
||||
Save
|
||||
</Button>
|
||||
</Grid>
|
||||
{addLoading && (
|
||||
<Grid item xs={12}>
|
||||
<LinearProgress />
|
||||
</Grid>
|
||||
)}
|
||||
</Grid>
|
||||
</form>
|
||||
</Paper>
|
||||
</Fragment>
|
||||
)}
|
||||
{selectedTab === 1 && (
|
||||
<Fragment>
|
||||
<h1 className={classes.sectionTitle}>Users</h1>
|
||||
<Grid container>
|
||||
<Grid item xs={12} className={classes.formScrollable}>
|
||||
<CodeMirrorWrapper
|
||||
label={`${policy ? "Edit" : "Write"} Policy`}
|
||||
value={policyDefinition}
|
||||
onBeforeChange={(editor, data, value) => {
|
||||
setPolicyDefinition(value);
|
||||
<Grid item xs={12} className={classes.actionsTray}>
|
||||
<TextField
|
||||
placeholder="Search Users"
|
||||
className={classes.searchField}
|
||||
id="search-resource"
|
||||
label=""
|
||||
onChange={(val) => {
|
||||
setFilterUsers(val.target.value);
|
||||
}}
|
||||
InputProps={{
|
||||
disableUnderline: true,
|
||||
startAdornment: (
|
||||
<InputAdornment position="start">
|
||||
<SearchIcon />
|
||||
</InputAdornment>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12} className={classes.buttonContainer}>
|
||||
{!policy && (
|
||||
<button
|
||||
type="button"
|
||||
color="primary"
|
||||
className={classes.clearButton}
|
||||
onClick={() => {
|
||||
resetForm();
|
||||
}}
|
||||
>
|
||||
Clear
|
||||
</button>
|
||||
)}
|
||||
|
||||
<Button
|
||||
type="submit"
|
||||
variant="contained"
|
||||
color="primary"
|
||||
disabled={addLoading || !validSave}
|
||||
>
|
||||
Save
|
||||
</Button>
|
||||
<Grid item xs={12} className={classes.actionsTray}>
|
||||
<br />
|
||||
</Grid>
|
||||
{addLoading && (
|
||||
<Grid item xs={12}>
|
||||
<LinearProgress />
|
||||
</Grid>
|
||||
)}
|
||||
<TableWrapper
|
||||
itemActions={userTableActions}
|
||||
columns={[{ label: "Name", elementKey: "name" }]}
|
||||
isLoading={loadingUsers}
|
||||
records={filteredUsers}
|
||||
entityName="Users"
|
||||
idField="name"
|
||||
/>
|
||||
</Grid>
|
||||
</form>
|
||||
</Paper>
|
||||
)}
|
||||
{selectedTab === 1 && (
|
||||
<Grid container>
|
||||
<Grid item xs={12} className={classes.actionsTray}>
|
||||
<TextField
|
||||
placeholder="Search Users"
|
||||
className={classes.searchField}
|
||||
id="search-resource"
|
||||
label=""
|
||||
onChange={(val) => {
|
||||
setFilterUsers(val.target.value);
|
||||
}}
|
||||
InputProps={{
|
||||
disableUnderline: true,
|
||||
startAdornment: (
|
||||
<InputAdornment position="start">
|
||||
<SearchIcon />
|
||||
</InputAdornment>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12} className={classes.actionsTray}>
|
||||
<br />
|
||||
</Grid>
|
||||
<TableWrapper
|
||||
itemActions={userTableActions}
|
||||
columns={[{ label: "Name", elementKey: "name" }]}
|
||||
isLoading={loadingUsers}
|
||||
records={filteredUsers}
|
||||
entityName="Users"
|
||||
idField="name"
|
||||
/>
|
||||
</Grid>
|
||||
)}
|
||||
{selectedTab === 2 && (
|
||||
<Grid container>
|
||||
<Grid item xs={12} className={classes.actionsTray}>
|
||||
<TextField
|
||||
placeholder="Search Groups"
|
||||
className={classes.searchField}
|
||||
id="search-resource"
|
||||
label=""
|
||||
onChange={(val) => {
|
||||
setFilterGroups(val.target.value);
|
||||
}}
|
||||
InputProps={{
|
||||
disableUnderline: true,
|
||||
startAdornment: (
|
||||
<InputAdornment position="start">
|
||||
<SearchIcon />
|
||||
</InputAdornment>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12} className={classes.actionsTray}>
|
||||
<br />
|
||||
</Grid>
|
||||
<TableWrapper
|
||||
itemActions={[]}
|
||||
columns={[{ label: "Name", elementKey: "name" }]}
|
||||
isLoading={loadingGroups}
|
||||
records={filteredGroups}
|
||||
entityName="Groups"
|
||||
idField="name"
|
||||
/>
|
||||
</Grid>
|
||||
)}
|
||||
</Fragment>
|
||||
)}
|
||||
{selectedTab === 2 && (
|
||||
<Fragment>
|
||||
<h1 className={classes.sectionTitle}>Groups</h1>
|
||||
<Grid container>
|
||||
<Grid item xs={12} className={classes.actionsTray}>
|
||||
<TextField
|
||||
placeholder="Search Groups"
|
||||
className={classes.searchField}
|
||||
id="search-resource"
|
||||
label=""
|
||||
onChange={(val) => {
|
||||
setFilterGroups(val.target.value);
|
||||
}}
|
||||
InputProps={{
|
||||
disableUnderline: true,
|
||||
startAdornment: (
|
||||
<InputAdornment position="start">
|
||||
<SearchIcon />
|
||||
</InputAdornment>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12} className={classes.actionsTray}>
|
||||
<br />
|
||||
</Grid>
|
||||
<TableWrapper
|
||||
itemActions={[]}
|
||||
columns={[{ label: "Name", elementKey: "name" }]}
|
||||
isLoading={loadingGroups}
|
||||
records={filteredGroups}
|
||||
entityName="Groups"
|
||||
idField="name"
|
||||
/>
|
||||
</Grid>
|
||||
</Fragment>
|
||||
)}
|
||||
</Grid>
|
||||
</Grid>
|
||||
</React.Fragment>
|
||||
);
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
|
||||
import React, { Fragment, useState, useEffect } from "react";
|
||||
import { createStyles, Theme, withStyles } from "@material-ui/core/styles";
|
||||
import { Grid, Tab, Tabs } from "@material-ui/core";
|
||||
import { Grid, ListItem, ListItemText, Tab, Tabs } from "@material-ui/core";
|
||||
import { Route, Router, Switch, Redirect } from "react-router-dom";
|
||||
import {
|
||||
actionsTray,
|
||||
@@ -27,6 +27,7 @@ import history from "../../../history";
|
||||
import PageHeader from "../Common/PageHeader/PageHeader";
|
||||
import StoragePVCs from "./StoragePVCs";
|
||||
import DirectCSIDrives from "../DirectCSI/DirectCSIDrives";
|
||||
import List from "@material-ui/core/List";
|
||||
|
||||
interface IStorageProps {
|
||||
classes: any;
|
||||
@@ -41,9 +42,6 @@ const styles = (theme: Theme) =>
|
||||
color: "#000",
|
||||
marginTop: 4,
|
||||
},
|
||||
tabsContainer: {
|
||||
marginBottom: 15,
|
||||
},
|
||||
...actionsTray,
|
||||
...searchField,
|
||||
...containerForHeader(theme.spacing(4)),
|
||||
@@ -59,27 +57,37 @@ const Storage = ({ classes, match }: IStorageProps) => {
|
||||
setSelectedTab(index);
|
||||
}, [match]);
|
||||
|
||||
const routeChange = (e: React.ChangeEvent<{}>, newValue: number) => {
|
||||
const routeChange = (newValue: number) => {
|
||||
history.push(routes[newValue]);
|
||||
};
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<PageHeader label={"Storage"} />
|
||||
<Grid container>
|
||||
<Grid item xs={12} className={classes.container}>
|
||||
<Grid item xs={12} className={classes.tabsContainer}>
|
||||
<Tabs
|
||||
value={selectedTab}
|
||||
onChange={routeChange}
|
||||
indicatorColor="primary"
|
||||
textColor="primary"
|
||||
aria-label="cluster-tabs"
|
||||
<Grid container className={classes.container}>
|
||||
<Grid item xs={2}>
|
||||
<List component="nav" dense={true}>
|
||||
<ListItem
|
||||
button
|
||||
selected={selectedTab === 0}
|
||||
onClick={() => {
|
||||
routeChange(0);
|
||||
}}
|
||||
>
|
||||
<Tab label="Volumes" />
|
||||
<Tab label="Drives" />
|
||||
</Tabs>
|
||||
</Grid>
|
||||
<ListItemText primary="Volumes" />
|
||||
</ListItem>
|
||||
<ListItem
|
||||
button
|
||||
selected={selectedTab === 1}
|
||||
onClick={() => {
|
||||
routeChange(1);
|
||||
}}
|
||||
>
|
||||
<ListItemText primary="Drives" />
|
||||
</ListItem>
|
||||
</List>
|
||||
</Grid>
|
||||
<Grid item xs={10}>
|
||||
<Router history={history}>
|
||||
<Switch>
|
||||
<Route path={routes[0]} component={StoragePVCs} />
|
||||
|
||||
@@ -98,9 +98,10 @@ const StorageVolumes = ({
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<h1 className={classes.sectionTitle}>Volumes</h1>
|
||||
<Grid item xs={12} className={classes.actionsTray}>
|
||||
<TextField
|
||||
placeholder="Search PVC"
|
||||
placeholder="Search Volumes (PVCs)"
|
||||
className={classes.searchField}
|
||||
id="search-resource"
|
||||
label=""
|
||||
|
||||
@@ -332,7 +332,7 @@ const Configure = ({
|
||||
label="Console's Image"
|
||||
value={consoleImage}
|
||||
error={validationErrors["consoleImage"] || ""}
|
||||
placeholder="E.g. minio/console:v0.8.1"
|
||||
placeholder="E.g. minio/console:v0.8.2"
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
|
||||
@@ -100,6 +100,8 @@ const TenantSize = ({
|
||||
selectedStorageClass,
|
||||
}: ITenantSizeProps) => {
|
||||
const [validationErrors, setValidationErrors] = useState<any>({});
|
||||
const [errorFlag, setErrorFlag] = useState<boolean>(false);
|
||||
const [nodeError, setNodeError] = useState<string>("");
|
||||
const usableInformation = ecParityCalc.storageFactors.find(
|
||||
(element) => element.erasureCode === ecParity
|
||||
);
|
||||
@@ -131,7 +133,6 @@ const TenantSize = ({
|
||||
clusterSizeFactor
|
||||
);
|
||||
const memoSize = setMemoryResource(memSize, clusterSizeBytes, maxMemSize);
|
||||
|
||||
updateField("memorySize", memoSize);
|
||||
}, [maxAllocableMemo, memoryNode, sizeFactor, updateField, volumeSize]);
|
||||
|
||||
@@ -146,8 +147,9 @@ const TenantSize = ({
|
||||
const maxMemory = res.max_memory ? res.max_memory : 0;
|
||||
updateField("maxAllocableMemo", maxMemory);
|
||||
})
|
||||
.catch((err: ErrorResponseHandler) => {
|
||||
updateField("maxAllocableMemo", 0);
|
||||
.catch((err: any) => {
|
||||
setErrorFlag(true);
|
||||
setNodeError(err.errorMessage);
|
||||
console.error(err);
|
||||
});
|
||||
}
|
||||
@@ -227,7 +229,6 @@ const TenantSize = ({
|
||||
|
||||
useEffect(() => {
|
||||
const parsedSize = getBytes(volumeSize, sizeFactor, true);
|
||||
|
||||
const commonValidation = commonFormValidation([
|
||||
{
|
||||
fieldKey: "nodes",
|
||||
@@ -236,6 +237,13 @@ const TenantSize = ({
|
||||
customValidation: parseInt(nodes) < 4,
|
||||
customValidationMessage: "Number of nodes cannot be less than 4",
|
||||
},
|
||||
{
|
||||
fieldKey: "nodes",
|
||||
required: true,
|
||||
value: nodes,
|
||||
customValidation: errorFlag,
|
||||
customValidationMessage: nodeError,
|
||||
},
|
||||
{
|
||||
fieldKey: "volume_size",
|
||||
required: true,
|
||||
@@ -288,6 +296,8 @@ const TenantSize = ({
|
||||
limitSize,
|
||||
selectedStorageClass,
|
||||
isPageValid,
|
||||
errorFlag,
|
||||
nodeError
|
||||
]);
|
||||
|
||||
/* End Validation of pages */
|
||||
|
||||
@@ -124,6 +124,7 @@ const PodsSummary = ({
|
||||
/>
|
||||
)}
|
||||
<div className={classes.topSpacer} />
|
||||
<h1 className={classes.sectionTitle}>Pods</h1>
|
||||
<TableWrapper
|
||||
columns={[
|
||||
{ label: "Name", elementKey: "name" },
|
||||
|
||||
@@ -104,6 +104,7 @@ const PoolsSummary = ({
|
||||
/>
|
||||
)}
|
||||
<div className={classes.topSpacer} />
|
||||
<h1 className={classes.sectionTitle}>Pools</h1>
|
||||
<Grid container>
|
||||
<Grid item xs={12} className={classes.actionsTray}>
|
||||
<TextField
|
||||
|
||||
@@ -18,12 +18,11 @@ import React, { Fragment, useEffect, useState } from "react";
|
||||
import { connect } from "react-redux";
|
||||
import { Link, Redirect, Route, Router, Switch } from "react-router-dom";
|
||||
import { createStyles, Theme, withStyles } from "@material-ui/core/styles";
|
||||
import { IconButton, Menu, MenuItem } from "@material-ui/core";
|
||||
import { IconButton, Tooltip } from "@material-ui/core";
|
||||
import get from "lodash/get";
|
||||
import Grid from "@material-ui/core/Grid";
|
||||
import MoreVertIcon from "@material-ui/icons/MoreVert";
|
||||
import RefreshIcon from "@material-ui/icons/Refresh";
|
||||
import { setErrorSnackMessage } from "../../../../actions";
|
||||
import { setErrorSnackMessage, setSnackBarMessage } from "../../../../actions";
|
||||
import {
|
||||
setTenantDetailsLoad,
|
||||
setTenantInfo,
|
||||
@@ -49,6 +48,9 @@ import TenantSecurity from "./TenantSecurity";
|
||||
import List from "@material-ui/core/List";
|
||||
import ListItem from "@material-ui/core/ListItem";
|
||||
import ListItemText from "@material-ui/core/ListItemText";
|
||||
import { DeleteIcon } from "../../../../icons";
|
||||
import DeleteTenant from "../ListTenants/DeleteTenant";
|
||||
import PencilIcon from "../../Common/TableWrapper/TableActionIcons/PencilIcon";
|
||||
|
||||
interface ITenantDetailsProps {
|
||||
classes: any;
|
||||
@@ -57,8 +59,10 @@ interface ITenantDetailsProps {
|
||||
loadingTenant: boolean;
|
||||
currentTab: string;
|
||||
selectedTenant: string;
|
||||
tenantInfo: ITenant | null;
|
||||
selectedNamespace: string;
|
||||
setErrorSnackMessage: typeof setErrorSnackMessage;
|
||||
setSnackBarMessage: typeof setSnackBarMessage;
|
||||
setTenantDetailsLoad: typeof setTenantDetailsLoad;
|
||||
setTenantName: typeof setTenantName;
|
||||
setTenantInfo: typeof setTenantInfo;
|
||||
@@ -90,8 +94,10 @@ const TenantDetails = ({
|
||||
loadingTenant,
|
||||
currentTab,
|
||||
selectedTenant,
|
||||
tenantInfo,
|
||||
selectedNamespace,
|
||||
setErrorSnackMessage,
|
||||
setSnackBarMessage,
|
||||
setTenantDetailsLoad,
|
||||
setTenantName,
|
||||
setTenantInfo,
|
||||
@@ -102,6 +108,7 @@ const TenantDetails = ({
|
||||
const tenantName = match.params["tenantName"];
|
||||
const tenantNamespace = match.params["tenantNamespace"];
|
||||
const [anchorEl, setAnchorEl] = React.useState(null);
|
||||
const [deleteOpen, setDeleteOpen] = useState<boolean>(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (!loadingTenant) {
|
||||
@@ -188,6 +195,19 @@ const TenantDetails = ({
|
||||
);
|
||||
};
|
||||
|
||||
const confirmDeleteTenant = () => {
|
||||
setDeleteOpen(true);
|
||||
};
|
||||
|
||||
const closeDeleteModalAndRefresh = (reloadData: boolean) => {
|
||||
setDeleteOpen(false);
|
||||
|
||||
if (reloadData) {
|
||||
setSnackBarMessage("Tenant Deleted");
|
||||
history.push(`/tenants`);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
{yamlScreenOpen && (
|
||||
@@ -198,6 +218,13 @@ const TenantDetails = ({
|
||||
namespace={tenantNamespace}
|
||||
/>
|
||||
)}
|
||||
{deleteOpen && tenantInfo !== null && (
|
||||
<DeleteTenant
|
||||
deleteOpen={deleteOpen}
|
||||
selectedTenant={tenantInfo}
|
||||
closeDeleteModalAndRefresh={closeDeleteModalAndRefresh}
|
||||
/>
|
||||
)}
|
||||
<PageHeader
|
||||
label={
|
||||
<Fragment>
|
||||
@@ -205,38 +232,47 @@ const TenantDetails = ({
|
||||
Tenants
|
||||
</Link>
|
||||
{` > ${match.params["tenantName"]}`}
|
||||
<IconButton
|
||||
aria-label="more"
|
||||
aria-controls="long-menu"
|
||||
aria-haspopup="true"
|
||||
onClick={handleTenantMenu}
|
||||
>
|
||||
<MoreVertIcon />
|
||||
</IconButton>
|
||||
<Menu
|
||||
id="long-menu"
|
||||
anchorEl={anchorEl}
|
||||
keepMounted
|
||||
open={Boolean(anchorEl)}
|
||||
onClose={editYaml}
|
||||
>
|
||||
<MenuItem key="yaml" onClick={editYaml}>
|
||||
Edit YAML
|
||||
</MenuItem>
|
||||
</Menu>
|
||||
</Fragment>
|
||||
}
|
||||
actions={
|
||||
<IconButton
|
||||
color="primary"
|
||||
aria-label="Refresh List"
|
||||
component="span"
|
||||
onClick={() => {
|
||||
setTenantDetailsLoad(true);
|
||||
}}
|
||||
>
|
||||
<RefreshIcon />
|
||||
</IconButton>
|
||||
<Fragment>
|
||||
<Tooltip title={"Delete"}>
|
||||
<IconButton
|
||||
color="primary"
|
||||
aria-label="Delete"
|
||||
component="span"
|
||||
onClick={() => {
|
||||
confirmDeleteTenant();
|
||||
}}
|
||||
>
|
||||
<DeleteIcon />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
<Tooltip title={"Edit YAML"}>
|
||||
<IconButton
|
||||
color="primary"
|
||||
aria-label="Edit YAML"
|
||||
component="span"
|
||||
onClick={() => {
|
||||
editYaml();
|
||||
}}
|
||||
>
|
||||
<PencilIcon active={true} />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
<Tooltip title={"Refresh"}>
|
||||
<IconButton
|
||||
color="primary"
|
||||
aria-label="Refresh List"
|
||||
component="span"
|
||||
onClick={() => {
|
||||
setTenantDetailsLoad(true);
|
||||
}}
|
||||
>
|
||||
<RefreshIcon />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
</Fragment>
|
||||
}
|
||||
/>
|
||||
<Grid item xs={12} className={classes.container} />
|
||||
@@ -347,10 +383,12 @@ const mapState = (state: AppState) => ({
|
||||
currentTab: state.tenants.tenantDetails.currentTab,
|
||||
selectedTenant: state.tenants.tenantDetails.currentTenant,
|
||||
selectedNamespace: state.tenants.tenantDetails.currentNamespace,
|
||||
tenantInfo: state.tenants.tenantDetails.tenantInfo,
|
||||
});
|
||||
|
||||
const connector = connect(mapState, {
|
||||
setErrorSnackMessage,
|
||||
setSnackBarMessage,
|
||||
setTenantDetailsLoad,
|
||||
setTenantName,
|
||||
setTenantInfo,
|
||||
|
||||
@@ -98,6 +98,7 @@ const TenantLicense = ({
|
||||
return (
|
||||
<Fragment>
|
||||
<div className={classes.topSpacer} />
|
||||
<h1 className={classes.sectionTitle}>License</h1>
|
||||
{loadingTenant ? (
|
||||
<div className={classes.loaderAlign}>
|
||||
<CircularProgress />
|
||||
|
||||
@@ -59,6 +59,7 @@ const TenantMetrics = ({ classes, match }: ITenantMetrics) => {
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<h1 className={classes.sectionTitle}>Metrics</h1>
|
||||
{loading && (
|
||||
<div style={{ marginTop: "80px" }}>
|
||||
<LinearProgress />
|
||||
|
||||
@@ -427,6 +427,7 @@ const TenantSecurity = ({
|
||||
</Paper>
|
||||
) : (
|
||||
<Fragment>
|
||||
<h1 className={classes.sectionTitle}>Security</h1>
|
||||
<Paper className={classes.paperContainer}>
|
||||
<Grid item xs={12} className={classes.title}>
|
||||
<FormSwitchWrapper
|
||||
|
||||
@@ -176,6 +176,7 @@ const TenantSummary = ({
|
||||
/>
|
||||
)}
|
||||
<div className={classes.topSpacer} />
|
||||
<h1 className={classes.sectionTitle}>Summary</h1>
|
||||
<Paper className={classes.paperContainer}>
|
||||
<Grid container>
|
||||
<Grid item xs={8}>
|
||||
|
||||
@@ -308,7 +308,7 @@ const UserDetails = ({ classes, match }: IUserDetailsProps) => {
|
||||
<List component="nav" dense={true}>
|
||||
<ListItem
|
||||
button
|
||||
selected={curTab == 0}
|
||||
selected={curTab === 0}
|
||||
onClick={() => {
|
||||
setCurTab(0);
|
||||
}}
|
||||
@@ -317,7 +317,7 @@ const UserDetails = ({ classes, match }: IUserDetailsProps) => {
|
||||
</ListItem>
|
||||
<ListItem
|
||||
button
|
||||
selected={curTab == 1}
|
||||
selected={curTab === 1}
|
||||
onClick={() => {
|
||||
setCurTab(1);
|
||||
}}
|
||||
@@ -326,7 +326,7 @@ const UserDetails = ({ classes, match }: IUserDetailsProps) => {
|
||||
</ListItem>
|
||||
<ListItem
|
||||
button
|
||||
selected={curTab == 2}
|
||||
selected={curTab === 2}
|
||||
onClick={() => {
|
||||
setCurTab(2);
|
||||
}}
|
||||
@@ -339,7 +339,7 @@ const UserDetails = ({ classes, match }: IUserDetailsProps) => {
|
||||
<Grid item xs={12}>
|
||||
<TabPanel index={0} value={curTab}>
|
||||
<div className={classes.actionsTray}>
|
||||
<h1 style={{ padding: "0px", margin: "0px" }}>Groups</h1>
|
||||
<h1 className={classes.sectionTitle}>Groups</h1>
|
||||
<Button
|
||||
variant="contained"
|
||||
color="primary"
|
||||
@@ -364,9 +364,7 @@ const UserDetails = ({ classes, match }: IUserDetailsProps) => {
|
||||
</TabPanel>
|
||||
<TabPanel index={1} value={curTab}>
|
||||
<div className={classes.actionsTray}>
|
||||
<h1 style={{ padding: "0px", margin: "0px" }}>
|
||||
Service Accounts
|
||||
</h1>
|
||||
<h1 className={classes.sectionTitle}>Service Accounts</h1>
|
||||
<Button
|
||||
variant="contained"
|
||||
color="primary"
|
||||
@@ -384,7 +382,7 @@ const UserDetails = ({ classes, match }: IUserDetailsProps) => {
|
||||
</TabPanel>
|
||||
<TabPanel index={2} value={curTab}>
|
||||
<div className={classes.actionsTray}>
|
||||
<h1 style={{ padding: "0px", margin: "0px" }}>Policies</h1>
|
||||
<h1 className={classes.sectionTitle}>Policies</h1>
|
||||
<Button
|
||||
variant="contained"
|
||||
color="primary"
|
||||
|
||||
@@ -263,7 +263,7 @@ const Login = ({ classes, userLoggedIn }: ILoginProps) => {
|
||||
})
|
||||
.catch((err) => {
|
||||
setLoginSending(false);
|
||||
setError(err.message);
|
||||
setError({ detailedError: "", errorMessage: err.message });
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@@ -1014,6 +1014,11 @@ func init() {
|
||||
"type": "boolean",
|
||||
"name": "with_versions",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "boolean",
|
||||
"name": "with_metadata",
|
||||
"in": "query"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
@@ -3480,6 +3485,12 @@ func init() {
|
||||
"legal_hold_status": {
|
||||
"type": "string"
|
||||
},
|
||||
"metadata": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
@@ -3499,6 +3510,12 @@ func init() {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"user_metadata": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"user_tags": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
@@ -6402,6 +6419,11 @@ func init() {
|
||||
"type": "boolean",
|
||||
"name": "with_versions",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "boolean",
|
||||
"name": "with_metadata",
|
||||
"in": "query"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
@@ -8990,6 +9012,12 @@ func init() {
|
||||
"legal_hold_status": {
|
||||
"type": "string"
|
||||
},
|
||||
"metadata": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
@@ -9009,6 +9037,12 @@ func init() {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"user_metadata": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"user_tags": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
|
||||
@@ -65,6 +65,10 @@ type ListObjectsParams struct {
|
||||
/*
|
||||
In: query
|
||||
*/
|
||||
WithMetadata *bool
|
||||
/*
|
||||
In: query
|
||||
*/
|
||||
WithVersions *bool
|
||||
}
|
||||
|
||||
@@ -94,6 +98,11 @@ func (o *ListObjectsParams) BindRequest(r *http.Request, route *middleware.Match
|
||||
res = append(res, err)
|
||||
}
|
||||
|
||||
qWithMetadata, qhkWithMetadata, _ := qs.GetOK("with_metadata")
|
||||
if err := o.bindWithMetadata(qWithMetadata, qhkWithMetadata, route.Formats); err != nil {
|
||||
res = append(res, err)
|
||||
}
|
||||
|
||||
qWithVersions, qhkWithVersions, _ := qs.GetOK("with_versions")
|
||||
if err := o.bindWithVersions(qWithVersions, qhkWithVersions, route.Formats); err != nil {
|
||||
res = append(res, err)
|
||||
@@ -159,6 +168,29 @@ func (o *ListObjectsParams) bindRecursive(rawData []string, hasKey bool, formats
|
||||
return nil
|
||||
}
|
||||
|
||||
// bindWithMetadata binds and validates parameter WithMetadata from query.
|
||||
func (o *ListObjectsParams) bindWithMetadata(rawData []string, hasKey bool, formats strfmt.Registry) error {
|
||||
var raw string
|
||||
if len(rawData) > 0 {
|
||||
raw = rawData[len(rawData)-1]
|
||||
}
|
||||
|
||||
// Required: false
|
||||
// AllowEmptyValue: false
|
||||
|
||||
if raw == "" { // empty values pass all other validations
|
||||
return nil
|
||||
}
|
||||
|
||||
value, err := swag.ConvertBool(raw)
|
||||
if err != nil {
|
||||
return errors.InvalidType("with_metadata", "query", "bool", raw)
|
||||
}
|
||||
o.WithMetadata = &value
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// bindWithVersions binds and validates parameter WithVersions from query.
|
||||
func (o *ListObjectsParams) bindWithVersions(rawData []string, hasKey bool, formats strfmt.Registry) error {
|
||||
var raw string
|
||||
|
||||
@@ -37,6 +37,7 @@ type ListObjectsURL struct {
|
||||
|
||||
Prefix *string
|
||||
Recursive *bool
|
||||
WithMetadata *bool
|
||||
WithVersions *bool
|
||||
|
||||
_basePath string
|
||||
@@ -96,6 +97,14 @@ func (o *ListObjectsURL) Build() (*url.URL, error) {
|
||||
qs.Set("recursive", recursiveQ)
|
||||
}
|
||||
|
||||
var withMetadataQ string
|
||||
if o.WithMetadata != nil {
|
||||
withMetadataQ = swag.FormatBool(*o.WithMetadata)
|
||||
}
|
||||
if withMetadataQ != "" {
|
||||
qs.Set("with_metadata", withMetadataQ)
|
||||
}
|
||||
|
||||
var withVersionsQ string
|
||||
if o.WithVersions != nil {
|
||||
withVersionsQ = swag.FormatBool(*o.WithVersions)
|
||||
|
||||
@@ -145,6 +145,8 @@ func getListObjectsResponse(session *models.Principal, params user_api.ListObjec
|
||||
var prefix string
|
||||
var recursive bool
|
||||
var withVersions bool
|
||||
var withMetadata bool
|
||||
|
||||
if params.Prefix != nil {
|
||||
prefix = *params.Prefix
|
||||
}
|
||||
@@ -154,6 +156,9 @@ func getListObjectsResponse(session *models.Principal, params user_api.ListObjec
|
||||
if isErasureBackend() && params.WithVersions != nil {
|
||||
withVersions = *params.WithVersions
|
||||
}
|
||||
if params.WithMetadata != nil {
|
||||
withMetadata = *params.WithMetadata
|
||||
}
|
||||
// bucket request needed to proceed
|
||||
if params.BucketName == "" {
|
||||
return nil, prepareError(errBucketNameNotInRequest)
|
||||
@@ -166,7 +171,7 @@ func getListObjectsResponse(session *models.Principal, params user_api.ListObjec
|
||||
// defining the client to be used
|
||||
minioClient := minioClient{client: mClient}
|
||||
|
||||
objs, err := listBucketObjects(params.HTTPRequest.Context(), minioClient, params.BucketName, prefix, recursive, withVersions)
|
||||
objs, err := listBucketObjects(params.HTTPRequest.Context(), minioClient, params.BucketName, prefix, recursive, withVersions, withMetadata)
|
||||
if err != nil {
|
||||
return nil, prepareError(err)
|
||||
}
|
||||
@@ -179,12 +184,13 @@ func getListObjectsResponse(session *models.Principal, params user_api.ListObjec
|
||||
}
|
||||
|
||||
// listBucketObjects gets an array of objects in a bucket
|
||||
func listBucketObjects(ctx context.Context, client MinioClient, bucketName string, prefix string, recursive, withVersions bool) ([]*models.BucketObject, error) {
|
||||
func listBucketObjects(ctx context.Context, client MinioClient, bucketName string, prefix string, recursive, withVersions bool, withMetadata bool) ([]*models.BucketObject, error) {
|
||||
var objects []*models.BucketObject
|
||||
for lsObj := range client.listObjects(ctx, bucketName, minio.ListObjectsOptions{Prefix: prefix, Recursive: recursive, WithVersions: withVersions}) {
|
||||
for lsObj := range client.listObjects(ctx, bucketName, minio.ListObjectsOptions{Prefix: prefix, Recursive: recursive, WithVersions: withVersions, WithMetadata: withMetadata}) {
|
||||
if lsObj.Err != nil {
|
||||
return nil, lsObj.Err
|
||||
}
|
||||
|
||||
obj := &models.BucketObject{
|
||||
Name: lsObj.Key,
|
||||
Size: lsObj.Size,
|
||||
@@ -194,6 +200,7 @@ func listBucketObjects(ctx context.Context, client MinioClient, bucketName strin
|
||||
IsLatest: lsObj.IsLatest,
|
||||
IsDeleteMarker: lsObj.IsDeleteMarker,
|
||||
UserTags: lsObj.UserTags,
|
||||
UserMetadata: lsObj.UserMetadata,
|
||||
}
|
||||
// only if single object with or without versions; get legalhold, retention and tags
|
||||
if !lsObj.IsDeleteMarker && prefix != "" && !strings.HasSuffix(prefix, "/") {
|
||||
|
||||
@@ -107,6 +107,7 @@ func Test_listObjects(t *testing.T) {
|
||||
prefix string
|
||||
recursive bool
|
||||
withVersions bool
|
||||
withMetadata bool
|
||||
listFunc func(ctx context.Context, bucket string, opts minio.ListObjectsOptions) <-chan minio.ObjectInfo
|
||||
objectLegalHoldFunc func(ctx context.Context, bucketName, objectName string, opts minio.GetObjectLegalHoldOptions) (status *minio.LegalHoldStatus, err error)
|
||||
objectRetentionFunc func(ctx context.Context, bucketName, objectName, versionID string) (mode *minio.RetentionMode, retainUntilDate *time.Time, err error)
|
||||
@@ -125,6 +126,7 @@ func Test_listObjects(t *testing.T) {
|
||||
prefix: "prefix",
|
||||
recursive: true,
|
||||
withVersions: false,
|
||||
withMetadata: false,
|
||||
listFunc: func(ctx context.Context, bucket string, opts minio.ListObjectsOptions) <-chan minio.ObjectInfo {
|
||||
objectStatCh := make(chan minio.ObjectInfo, 1)
|
||||
go func(objectStatCh chan<- minio.ObjectInfo) {
|
||||
@@ -201,6 +203,7 @@ func Test_listObjects(t *testing.T) {
|
||||
prefix: "prefix",
|
||||
recursive: true,
|
||||
withVersions: false,
|
||||
withMetadata: false,
|
||||
listFunc: func(ctx context.Context, bucket string, opts minio.ListObjectsOptions) <-chan minio.ObjectInfo {
|
||||
objectStatCh := make(chan minio.ObjectInfo, 1)
|
||||
defer close(objectStatCh)
|
||||
@@ -235,6 +238,7 @@ func Test_listObjects(t *testing.T) {
|
||||
prefix: "prefix",
|
||||
recursive: true,
|
||||
withVersions: false,
|
||||
withMetadata: false,
|
||||
listFunc: func(ctx context.Context, bucket string, opts minio.ListObjectsOptions) <-chan minio.ObjectInfo {
|
||||
objectStatCh := make(chan minio.ObjectInfo, 1)
|
||||
go func(objectStatCh chan<- minio.ObjectInfo) {
|
||||
@@ -286,6 +290,7 @@ func Test_listObjects(t *testing.T) {
|
||||
prefix: "prefix",
|
||||
recursive: true,
|
||||
withVersions: false,
|
||||
withMetadata: false,
|
||||
listFunc: func(ctx context.Context, bucket string, opts minio.ListObjectsOptions) <-chan minio.ObjectInfo {
|
||||
objectStatCh := make(chan minio.ObjectInfo, 1)
|
||||
go func(objectStatCh chan<- minio.ObjectInfo) {
|
||||
@@ -361,6 +366,7 @@ func Test_listObjects(t *testing.T) {
|
||||
prefix: "prefix",
|
||||
recursive: true,
|
||||
withVersions: false,
|
||||
withMetadata: false,
|
||||
listFunc: func(ctx context.Context, bucket string, opts minio.ListObjectsOptions) <-chan minio.ObjectInfo {
|
||||
objectStatCh := make(chan minio.ObjectInfo, 1)
|
||||
go func(objectStatCh chan<- minio.ObjectInfo) {
|
||||
@@ -408,6 +414,7 @@ func Test_listObjects(t *testing.T) {
|
||||
prefix: "prefix/folder/",
|
||||
recursive: true,
|
||||
withVersions: false,
|
||||
withMetadata: false,
|
||||
listFunc: func(ctx context.Context, bucket string, opts minio.ListObjectsOptions) <-chan minio.ObjectInfo {
|
||||
objectStatCh := make(chan minio.ObjectInfo, 1)
|
||||
go func(objectStatCh chan<- minio.ObjectInfo) {
|
||||
@@ -475,6 +482,7 @@ func Test_listObjects(t *testing.T) {
|
||||
prefix: "",
|
||||
recursive: true,
|
||||
withVersions: false,
|
||||
withMetadata: false,
|
||||
listFunc: func(ctx context.Context, bucket string, opts minio.ListObjectsOptions) <-chan minio.ObjectInfo {
|
||||
objectStatCh := make(chan minio.ObjectInfo, 1)
|
||||
go func(objectStatCh chan<- minio.ObjectInfo) {
|
||||
@@ -540,7 +548,7 @@ func Test_listObjects(t *testing.T) {
|
||||
minioGetObjectLegalHoldMock = tt.args.objectLegalHoldFunc
|
||||
minioGetObjectRetentionMock = tt.args.objectRetentionFunc
|
||||
minioGetObjectTaggingMock = tt.args.objectGetTaggingFunc
|
||||
resp, err := listBucketObjects(ctx, minClient, tt.args.bucketName, tt.args.prefix, tt.args.recursive, tt.args.withVersions)
|
||||
resp, err := listBucketObjects(ctx, minClient, tt.args.bucketName, tt.args.prefix, tt.args.recursive, tt.args.withVersions, tt.args.withMetadata)
|
||||
if !reflect.DeepEqual(err, tt.wantError) {
|
||||
t.Errorf("listBucketObjects() error: %v, wantErr: %v", err, tt.wantError)
|
||||
return
|
||||
|
||||
@@ -329,6 +329,10 @@ paths:
|
||||
in: query
|
||||
required: false
|
||||
type: boolean
|
||||
- name: with_metadata
|
||||
in: query
|
||||
required: false
|
||||
type: boolean
|
||||
responses:
|
||||
200:
|
||||
description: A successful response.
|
||||
@@ -2273,6 +2277,14 @@ definitions:
|
||||
type: object
|
||||
additionalProperties:
|
||||
type: string
|
||||
metadata:
|
||||
type: object
|
||||
additionalProperties:
|
||||
type: string
|
||||
user_metadata:
|
||||
type: object
|
||||
additionalProperties:
|
||||
type: string
|
||||
|
||||
makeBucketRequest:
|
||||
type: object
|
||||
|
||||
Reference in New Issue
Block a user