Added Service Accounts page to settings (#128)
Co-authored-by: Benjamin Perez <benjamin@bexsoft.net>
This commit is contained in:
@@ -20,17 +20,18 @@ import iampolicy "github.com/minio/minio/pkg/iam/policy"
|
||||
|
||||
// endpoints definition
|
||||
var (
|
||||
configuration = "/configurations-list"
|
||||
users = "/users"
|
||||
groups = "/groups"
|
||||
iamPolicies = "/policies"
|
||||
dashboard = "/dashboard"
|
||||
profiling = "/profiling"
|
||||
trace = "/trace"
|
||||
logs = "/logs"
|
||||
watch = "/watch"
|
||||
notifications = "/notification-endpoints"
|
||||
buckets = "/buckets"
|
||||
configuration = "/configurations-list"
|
||||
users = "/users"
|
||||
groups = "/groups"
|
||||
iamPolicies = "/policies"
|
||||
dashboard = "/dashboard"
|
||||
profiling = "/profiling"
|
||||
trace = "/trace"
|
||||
logs = "/logs"
|
||||
watch = "/watch"
|
||||
notifications = "/notification-endpoints"
|
||||
buckets = "/buckets"
|
||||
serviceAccounts = "/service-accounts"
|
||||
)
|
||||
|
||||
type ConfigurationActionSet struct {
|
||||
@@ -179,19 +180,26 @@ var bucketsActionSet = ConfigurationActionSet{
|
||||
),
|
||||
}
|
||||
|
||||
// serviceAccountsActionSet contains the list of admin actions required for this endpoint to work
|
||||
var serviceAccountsActionSet = ConfigurationActionSet{
|
||||
actionTypes: iampolicy.NewActionSet(),
|
||||
actions: iampolicy.NewActionSet(),
|
||||
}
|
||||
|
||||
// endpointRules contains the mapping between endpoints and ActionSets, additional rules can be added here
|
||||
var endpointRules = map[string]ConfigurationActionSet{
|
||||
configuration: configurationActionSet,
|
||||
users: usersActionSet,
|
||||
groups: groupsActionSet,
|
||||
iamPolicies: iamPoliciesActionSet,
|
||||
dashboard: dashboardActionSet,
|
||||
profiling: profilingActionSet,
|
||||
trace: traceActionSet,
|
||||
logs: logsActionSet,
|
||||
watch: watchActionSet,
|
||||
notifications: notificationsActionSet,
|
||||
buckets: bucketsActionSet,
|
||||
configuration: configurationActionSet,
|
||||
users: usersActionSet,
|
||||
groups: groupsActionSet,
|
||||
iamPolicies: iamPoliciesActionSet,
|
||||
dashboard: dashboardActionSet,
|
||||
profiling: profilingActionSet,
|
||||
trace: traceActionSet,
|
||||
logs: logsActionSet,
|
||||
watch: watchActionSet,
|
||||
notifications: notificationsActionSet,
|
||||
buckets: bucketsActionSet,
|
||||
serviceAccounts: serviceAccountsActionSet,
|
||||
}
|
||||
|
||||
// GetActionsStringFromPolicy extract the admin/s3 actions from a given policy and return them in []string format
|
||||
|
||||
@@ -37,7 +37,7 @@ func TestGetAuthorizedEndpoints(t *testing.T) {
|
||||
args: args{
|
||||
[]string{"admin:ServerInfo"},
|
||||
},
|
||||
want: 1,
|
||||
want: 2,
|
||||
},
|
||||
{
|
||||
name: "policies endpoint",
|
||||
@@ -50,7 +50,7 @@ func TestGetAuthorizedEndpoints(t *testing.T) {
|
||||
"admin:ListUserPolicies",
|
||||
},
|
||||
},
|
||||
want: 1,
|
||||
want: 2,
|
||||
},
|
||||
{
|
||||
name: "all admin endpoints",
|
||||
@@ -59,7 +59,7 @@ func TestGetAuthorizedEndpoints(t *testing.T) {
|
||||
"admin:*",
|
||||
},
|
||||
},
|
||||
want: 9,
|
||||
want: 10,
|
||||
},
|
||||
{
|
||||
name: "all s3 endpoints",
|
||||
@@ -68,7 +68,7 @@ func TestGetAuthorizedEndpoints(t *testing.T) {
|
||||
"s3:*",
|
||||
},
|
||||
},
|
||||
want: 2,
|
||||
want: 3,
|
||||
},
|
||||
{
|
||||
name: "all admin and s3 endpoints",
|
||||
@@ -78,7 +78,7 @@ func TestGetAuthorizedEndpoints(t *testing.T) {
|
||||
"s3:*",
|
||||
},
|
||||
},
|
||||
want: 11,
|
||||
want: 12,
|
||||
},
|
||||
{
|
||||
name: "no endpoints",
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -35,7 +35,7 @@ const isLoggedIn = () => {
|
||||
};
|
||||
|
||||
const mapState = (state: AppState) => ({
|
||||
loggedIn: state.system.loggedIn
|
||||
loggedIn: state.system.loggedIn,
|
||||
});
|
||||
|
||||
const connector = connect(mapState, { userLoggedIn });
|
||||
|
||||
@@ -24,8 +24,8 @@ export class API {
|
||||
return request(method, url)
|
||||
.set("Authorization", `Bearer ${token}`)
|
||||
.send(data)
|
||||
.then(res => res.body)
|
||||
.catch(err => {
|
||||
.then((res) => res.body)
|
||||
.catch((err) => {
|
||||
// if we get unauthorized, kick out the user
|
||||
if (err.status === 401) {
|
||||
storage.removeItem("token");
|
||||
|
||||
@@ -35,36 +35,36 @@ interface IListConfiguration {
|
||||
const styles = (theme: Theme) =>
|
||||
createStyles({
|
||||
errorBlock: {
|
||||
color: "red"
|
||||
color: "red",
|
||||
},
|
||||
strongText: {
|
||||
fontWeight: 700
|
||||
fontWeight: 700,
|
||||
},
|
||||
keyName: {
|
||||
marginLeft: 5
|
||||
marginLeft: 5,
|
||||
},
|
||||
actionsTray: {
|
||||
textAlign: "right",
|
||||
"& button": {
|
||||
marginLeft: 10
|
||||
}
|
||||
marginLeft: 10,
|
||||
},
|
||||
},
|
||||
searchField: {
|
||||
background: "#FFFFFF",
|
||||
padding: 12,
|
||||
borderRadius: 5,
|
||||
boxShadow: "0px 3px 6px #00000012"
|
||||
boxShadow: "0px 3px 6px #00000012",
|
||||
},
|
||||
iconText: {
|
||||
lineHeight: "24px"
|
||||
}
|
||||
lineHeight: "24px",
|
||||
},
|
||||
});
|
||||
|
||||
const ConfigurationsList = ({ classes }: IListConfiguration) => {
|
||||
const [editScreenOpen, setEditScreenOpen] = useState(false);
|
||||
const [selectedConfiguration, setSelectedConfiguration] = useState({
|
||||
configuration_id: "",
|
||||
configuration_label: ""
|
||||
configuration_label: "",
|
||||
});
|
||||
const [error, setError] = useState("");
|
||||
const [filter, setFilter] = useState("");
|
||||
@@ -81,12 +81,12 @@ const ConfigurationsList = ({ classes }: IListConfiguration) => {
|
||||
setSelectedConfiguration(element);
|
||||
setEditScreenOpen(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
const filteredRecords: IConfigurationElement[] = configurationElements.filter(
|
||||
elementItem =>
|
||||
(elementItem) =>
|
||||
elementItem.configuration_id
|
||||
.toLocaleLowerCase()
|
||||
.includes(filter.toLocaleLowerCase())
|
||||
@@ -117,7 +117,7 @@ const ConfigurationsList = ({ classes }: IListConfiguration) => {
|
||||
className={classes.searchField}
|
||||
id="search-resource"
|
||||
label=""
|
||||
onChange={event => {
|
||||
onChange={(event) => {
|
||||
setFilter(event.target.value);
|
||||
}}
|
||||
InputProps={{
|
||||
@@ -126,7 +126,7 @@ const ConfigurationsList = ({ classes }: IListConfiguration) => {
|
||||
<InputAdornment position="start">
|
||||
<SearchIcon />
|
||||
</InputAdornment>
|
||||
)
|
||||
),
|
||||
}}
|
||||
/>
|
||||
</Grid>
|
||||
@@ -137,7 +137,7 @@ const ConfigurationsList = ({ classes }: IListConfiguration) => {
|
||||
<TableWrapper
|
||||
itemActions={tableActions}
|
||||
columns={[
|
||||
{ label: "Configuration", elementKey: "configuration_id" }
|
||||
{ label: "Configuration", elementKey: "configuration_id" },
|
||||
]}
|
||||
isLoading={false}
|
||||
records={filteredRecords}
|
||||
|
||||
@@ -44,29 +44,29 @@ interface IWebhook {
|
||||
const styles = (theme: Theme) =>
|
||||
createStyles({
|
||||
errorBlock: {
|
||||
color: "red"
|
||||
color: "red",
|
||||
},
|
||||
strongText: {
|
||||
fontWeight: 700
|
||||
fontWeight: 700,
|
||||
},
|
||||
keyName: {
|
||||
marginLeft: 5
|
||||
marginLeft: 5,
|
||||
},
|
||||
actionsTray: {
|
||||
textAlign: "right",
|
||||
"& button": {
|
||||
marginLeft: 10
|
||||
}
|
||||
marginLeft: 10,
|
||||
},
|
||||
},
|
||||
searchField: {
|
||||
background: "#FFFFFF",
|
||||
padding: 12,
|
||||
borderRadius: 5,
|
||||
boxShadow: "0px 3px 6px #00000012"
|
||||
boxShadow: "0px 3px 6px #00000012",
|
||||
},
|
||||
iconText: {
|
||||
lineHeight: "24px"
|
||||
}
|
||||
lineHeight: "24px",
|
||||
},
|
||||
});
|
||||
|
||||
const panels = {
|
||||
@@ -77,8 +77,8 @@ const panels = {
|
||||
apiURL: "",
|
||||
configuration: {
|
||||
configuration_id: "logger_webhook",
|
||||
configuration_label: "Logger Webhook"
|
||||
}
|
||||
configuration_label: "Logger Webhook",
|
||||
},
|
||||
},
|
||||
audit: {
|
||||
main: "audit",
|
||||
@@ -87,9 +87,9 @@ const panels = {
|
||||
apiURL: "",
|
||||
configuration: {
|
||||
configuration_id: "audit_webhook",
|
||||
configuration_label: "Audit Webhook"
|
||||
}
|
||||
}
|
||||
configuration_label: "Audit Webhook",
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const WebhookPanel = ({ match, classes }: IWebhookPanel) => {
|
||||
@@ -107,15 +107,15 @@ const WebhookPanel = ({ match, classes }: IWebhookPanel) => {
|
||||
return null;
|
||||
}
|
||||
|
||||
const filteredRecords: IWebhook[] = webhooks.filter(elementItem =>
|
||||
const filteredRecords: IWebhook[] = webhooks.filter((elementItem) =>
|
||||
elementItem.name.toLocaleLowerCase().includes(filter.toLocaleLowerCase())
|
||||
);
|
||||
|
||||
const tableActions = [
|
||||
{
|
||||
type: "edit",
|
||||
onClick: () => {}
|
||||
}
|
||||
onClick: () => {},
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
@@ -144,7 +144,7 @@ const WebhookPanel = ({ match, classes }: IWebhookPanel) => {
|
||||
className={classes.searchField}
|
||||
id="search-resource"
|
||||
label=""
|
||||
onChange={event => {
|
||||
onChange={(event) => {
|
||||
setFilter(event.target.value);
|
||||
}}
|
||||
InputProps={{
|
||||
@@ -153,7 +153,7 @@ const WebhookPanel = ({ match, classes }: IWebhookPanel) => {
|
||||
<InputAdornment position="start">
|
||||
<SearchIcon />
|
||||
</InputAdornment>
|
||||
)
|
||||
),
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
|
||||
@@ -31,34 +31,34 @@ export const configurationElements: IConfigurationElement[] = [
|
||||
{ configuration_id: "cache", configuration_label: "Cache Configuration" },
|
||||
{
|
||||
configuration_id: "compression",
|
||||
configuration_label: "Compression Configuration"
|
||||
configuration_label: "Compression Configuration",
|
||||
},
|
||||
{ configuration_id: "etcd", configuration_label: "Etcd Configuration" },
|
||||
{
|
||||
configuration_id: "identity_openid",
|
||||
configuration_label: "Identity Openid Configuration"
|
||||
configuration_label: "Identity Openid Configuration",
|
||||
},
|
||||
{
|
||||
configuration_id: "identity_ldap",
|
||||
configuration_label: "Identity LDAP Configuration"
|
||||
configuration_label: "Identity LDAP Configuration",
|
||||
},
|
||||
{
|
||||
configuration_id: "policy_opa",
|
||||
configuration_label: "Policy OPA Configuration"
|
||||
configuration_label: "Policy OPA Configuration",
|
||||
},
|
||||
{
|
||||
configuration_id: "kms_vault",
|
||||
configuration_label: "KMS Vault Configuration"
|
||||
configuration_label: "KMS Vault Configuration",
|
||||
},
|
||||
{ configuration_id: "kms_kes", configuration_label: "KMS KES Configuration" },
|
||||
{
|
||||
configuration_id: "logger_webhook",
|
||||
configuration_label: "Logger Webhook Configuration"
|
||||
configuration_label: "Logger Webhook Configuration",
|
||||
},
|
||||
{
|
||||
configuration_id: "audit_webhook",
|
||||
configuration_label: "Audit Webhook Configuration"
|
||||
}
|
||||
configuration_label: "Audit Webhook Configuration",
|
||||
},
|
||||
];
|
||||
|
||||
export const fieldsConfigurations: any = {
|
||||
@@ -68,7 +68,7 @@ export const fieldsConfigurations: any = {
|
||||
required: true,
|
||||
label: "name",
|
||||
tooltip: 'Name of the location of the server e.g. "us-west-rack2"',
|
||||
type: "string"
|
||||
type: "string",
|
||||
},
|
||||
{
|
||||
name: "comment",
|
||||
@@ -76,8 +76,8 @@ export const fieldsConfigurations: any = {
|
||||
label: "comment",
|
||||
tooltip: "You can add a comment to this setting",
|
||||
type: "string",
|
||||
multiline: true
|
||||
}
|
||||
multiline: true,
|
||||
},
|
||||
],
|
||||
cache: [
|
||||
{
|
||||
@@ -86,21 +86,21 @@ export const fieldsConfigurations: any = {
|
||||
label: "Drives",
|
||||
tooltip:
|
||||
'Mountpoints e.g. "/optane1" or "/optane2", you can write one per field',
|
||||
type: "csv"
|
||||
type: "csv",
|
||||
},
|
||||
{
|
||||
name: "expiry",
|
||||
required: false,
|
||||
label: "Expiry",
|
||||
tooltip: 'Cache expiry duration in days e.g. "90"',
|
||||
type: "number"
|
||||
type: "number",
|
||||
},
|
||||
{
|
||||
name: "quota",
|
||||
required: false,
|
||||
label: "Quota",
|
||||
tooltip: 'Limit cache drive usage in percentage e.g. "90"',
|
||||
type: "number"
|
||||
type: "number",
|
||||
},
|
||||
{
|
||||
name: "exclude",
|
||||
@@ -108,28 +108,28 @@ export const fieldsConfigurations: any = {
|
||||
label: "Exclude",
|
||||
tooltip:
|
||||
'Wildcard exclusion patterns e.g. "bucket/*.tmp" or "*.exe", you can write one per field',
|
||||
type: "csv"
|
||||
type: "csv",
|
||||
},
|
||||
{
|
||||
name: "after",
|
||||
required: false,
|
||||
label: "After",
|
||||
tooltip: "Minimum number of access before caching an object",
|
||||
type: "number"
|
||||
type: "number",
|
||||
},
|
||||
{
|
||||
name: "watermark_low",
|
||||
required: false,
|
||||
label: "Watermark Low",
|
||||
tooltip: "Watermark Low",
|
||||
type: "number"
|
||||
type: "number",
|
||||
},
|
||||
{
|
||||
name: "watermark_high",
|
||||
required: false,
|
||||
label: "Watermark High",
|
||||
tooltip: "Watermark High",
|
||||
type: "number"
|
||||
type: "number",
|
||||
},
|
||||
{
|
||||
name: "comment",
|
||||
@@ -137,8 +137,8 @@ export const fieldsConfigurations: any = {
|
||||
label: "Comment",
|
||||
tooltip: "You can add a comment to this setting",
|
||||
type: "string",
|
||||
multiline: true
|
||||
}
|
||||
multiline: true,
|
||||
},
|
||||
],
|
||||
compression: [
|
||||
{
|
||||
@@ -147,7 +147,7 @@ export const fieldsConfigurations: any = {
|
||||
label: "Extensions",
|
||||
tooltip:
|
||||
'Extensions to compress e.g. ".txt",".log" or ".csv", you can write one per field',
|
||||
type: "csv"
|
||||
type: "csv",
|
||||
},
|
||||
{
|
||||
name: "mime_types",
|
||||
@@ -155,8 +155,8 @@ export const fieldsConfigurations: any = {
|
||||
label: "Mime Types",
|
||||
tooltip:
|
||||
'Mime types e.g. "text/*","application/json" or "application/xml", you can write one per field',
|
||||
type: "csv"
|
||||
}
|
||||
type: "csv",
|
||||
},
|
||||
],
|
||||
etcd: [
|
||||
{
|
||||
@@ -165,35 +165,35 @@ export const fieldsConfigurations: any = {
|
||||
label: "Endpoints",
|
||||
tooltip:
|
||||
'List of etcd endpoints e.g. "http://localhost:2379", you can write one per field',
|
||||
type: "csv"
|
||||
type: "csv",
|
||||
},
|
||||
{
|
||||
name: "path_prefix",
|
||||
required: false,
|
||||
label: "Path Prefix",
|
||||
tooltip: 'namespace prefix to isolate tenants e.g. "customer1/"',
|
||||
type: "string"
|
||||
type: "string",
|
||||
},
|
||||
{
|
||||
name: "coredns_path",
|
||||
required: false,
|
||||
label: "Coredns Path",
|
||||
tooltip: 'Shared bucket DNS records, default is "/skydns"',
|
||||
type: "string"
|
||||
type: "string",
|
||||
},
|
||||
{
|
||||
name: "client_cert",
|
||||
required: false,
|
||||
label: "Client Cert",
|
||||
tooltip: "Client cert for mTLS authentication",
|
||||
type: "string"
|
||||
type: "string",
|
||||
},
|
||||
{
|
||||
name: "client_cert_key",
|
||||
required: false,
|
||||
label: "Client Cert Key",
|
||||
tooltip: "Client cert key for mTLS authentication",
|
||||
type: "string"
|
||||
type: "string",
|
||||
},
|
||||
{
|
||||
name: "comment",
|
||||
@@ -201,8 +201,8 @@ export const fieldsConfigurations: any = {
|
||||
label: "Comment",
|
||||
tooltip: "You can add a comment to this setting",
|
||||
type: "string",
|
||||
multiline: true
|
||||
}
|
||||
multiline: true,
|
||||
},
|
||||
],
|
||||
identity_openid: [
|
||||
{
|
||||
@@ -210,28 +210,28 @@ export const fieldsConfigurations: any = {
|
||||
required: false,
|
||||
label: "Config URL",
|
||||
tooltip: "Config URL for Client ID configuration",
|
||||
type: "string"
|
||||
type: "string",
|
||||
},
|
||||
{
|
||||
name: "client_id",
|
||||
required: false,
|
||||
label: "Client ID",
|
||||
type: "string"
|
||||
type: "string",
|
||||
},
|
||||
{
|
||||
name: "claim_name",
|
||||
required: false,
|
||||
label: "Claim Name",
|
||||
tooltip: "Claim Name",
|
||||
type: "string"
|
||||
type: "string",
|
||||
},
|
||||
{
|
||||
name: "claim_prefix",
|
||||
required: false,
|
||||
label: "Claim Prefix",
|
||||
tooltip: "Claim Prefix",
|
||||
type: "string"
|
||||
}
|
||||
type: "string",
|
||||
},
|
||||
],
|
||||
identity_ldap: [
|
||||
{
|
||||
@@ -239,7 +239,7 @@ export const fieldsConfigurations: any = {
|
||||
required: true,
|
||||
label: "Server ADDR",
|
||||
tooltip: 'AD/LDAP server address e.g. "myldapserver.com:636"',
|
||||
type: "string"
|
||||
type: "string",
|
||||
},
|
||||
{
|
||||
name: "username_format",
|
||||
@@ -247,7 +247,7 @@ export const fieldsConfigurations: any = {
|
||||
label: "Username Format",
|
||||
tooltip:
|
||||
'List of username bind DNs e.g. "uid=%s","cn=accounts","dc=myldapserver" or "dc=com", you can write one per field',
|
||||
type: "csv"
|
||||
type: "csv",
|
||||
},
|
||||
{
|
||||
name: "username_search_filter",
|
||||
@@ -255,7 +255,7 @@ export const fieldsConfigurations: any = {
|
||||
label: "Username Search Filter",
|
||||
tooltip:
|
||||
'User search filter, for example "(cn=%s)" or "(sAMAccountName=%s)" or "(uid=%s)"',
|
||||
type: "string"
|
||||
type: "string",
|
||||
},
|
||||
{
|
||||
name: "group_search_filter",
|
||||
@@ -263,21 +263,21 @@ export const fieldsConfigurations: any = {
|
||||
label: "Group Search Filter",
|
||||
tooltip:
|
||||
'Search filter for groups e.g. "(&(objectclass=groupOfNames)(memberUid=%s))"',
|
||||
type: "string"
|
||||
type: "string",
|
||||
},
|
||||
{
|
||||
name: "username_search_base_dn",
|
||||
required: false,
|
||||
label: "Username Search Base DN",
|
||||
tooltip: "List of username search DNs, you can write one per field",
|
||||
type: "csv"
|
||||
type: "csv",
|
||||
},
|
||||
{
|
||||
name: "group_name_attribute",
|
||||
required: false,
|
||||
label: "Group Name Attribute",
|
||||
tooltip: 'Search attribute for group name e.g. "cn"',
|
||||
type: "string"
|
||||
type: "string",
|
||||
},
|
||||
{
|
||||
name: "sts_expiry",
|
||||
@@ -285,7 +285,7 @@ export const fieldsConfigurations: any = {
|
||||
label: "STS Expiry",
|
||||
tooltip:
|
||||
'temporary credentials validity duration in s,m,h,d. Default is "1h"',
|
||||
type: "string"
|
||||
type: "string",
|
||||
},
|
||||
{
|
||||
name: "tls_skip_verify",
|
||||
@@ -293,7 +293,7 @@ export const fieldsConfigurations: any = {
|
||||
label: "TLS Skip Verify",
|
||||
tooltip:
|
||||
'Trust server TLS without verification, defaults to "off" (verify)',
|
||||
type: "on|off"
|
||||
type: "on|off",
|
||||
},
|
||||
{
|
||||
name: "server_insecure",
|
||||
@@ -301,7 +301,7 @@ export const fieldsConfigurations: any = {
|
||||
label: "Server Insecure",
|
||||
tooltip:
|
||||
'Allow plain text connection to AD/LDAP server, defaults to "off"',
|
||||
type: "on|off"
|
||||
type: "on|off",
|
||||
},
|
||||
{
|
||||
name: "comment",
|
||||
@@ -309,28 +309,28 @@ export const fieldsConfigurations: any = {
|
||||
label: "Comment",
|
||||
tooltip: "Optionally add a comment to this setting",
|
||||
type: "string",
|
||||
multiline: true
|
||||
}
|
||||
multiline: true,
|
||||
},
|
||||
],
|
||||
policy_opa: [
|
||||
{
|
||||
name: "url",
|
||||
required: true,
|
||||
label: "OPA URL",
|
||||
type: "string"
|
||||
type: "string",
|
||||
},
|
||||
{
|
||||
name: "auth_token",
|
||||
required: true,
|
||||
label: "Auth Token",
|
||||
type: "string"
|
||||
type: "string",
|
||||
},
|
||||
{
|
||||
name: "policy_opa",
|
||||
required: true,
|
||||
label: "Policy OPA",
|
||||
type: "string"
|
||||
}
|
||||
type: "string",
|
||||
},
|
||||
],
|
||||
kms_vault: [],
|
||||
kms_kes: [],
|
||||
@@ -339,29 +339,29 @@ export const fieldsConfigurations: any = {
|
||||
name: "endpoint",
|
||||
required: true,
|
||||
label: "Endpoint",
|
||||
type: "string"
|
||||
type: "string",
|
||||
},
|
||||
{
|
||||
name: "auth_token",
|
||||
required: true,
|
||||
label: "Auth Token",
|
||||
type: "string"
|
||||
}
|
||||
type: "string",
|
||||
},
|
||||
],
|
||||
audit_webhook: [
|
||||
{
|
||||
name: "endpoint",
|
||||
required: true,
|
||||
label: "Endpoint",
|
||||
type: "string"
|
||||
type: "string",
|
||||
},
|
||||
{
|
||||
name: "auth_token",
|
||||
required: true,
|
||||
label: "Auth Token",
|
||||
type: "string"
|
||||
}
|
||||
]
|
||||
type: "string",
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const commonFields = [
|
||||
@@ -371,7 +371,7 @@ const commonFields = [
|
||||
required: true,
|
||||
|
||||
tooltip: "staging dir for undelivered messages e.g. '/home/events'",
|
||||
type: "string"
|
||||
type: "string",
|
||||
},
|
||||
{
|
||||
name: "queue-limit",
|
||||
@@ -379,15 +379,15 @@ const commonFields = [
|
||||
required: false,
|
||||
|
||||
tooltip: "maximum limit for undelivered messages, defaults to '10000'",
|
||||
type: "number"
|
||||
type: "number",
|
||||
},
|
||||
{
|
||||
name: "comment",
|
||||
label: "Comment",
|
||||
required: false,
|
||||
type: "string",
|
||||
multiline: true
|
||||
}
|
||||
multiline: true,
|
||||
},
|
||||
];
|
||||
|
||||
export const notificationEndpointsFields: any = {
|
||||
@@ -398,77 +398,77 @@ export const notificationEndpointsFields: any = {
|
||||
required: true,
|
||||
|
||||
tooltip: "comma separated list of Kafka broker addresses",
|
||||
type: "string"
|
||||
type: "string",
|
||||
},
|
||||
{
|
||||
name: "topic",
|
||||
label: "Topic",
|
||||
tooltip: "Kafka topic used for bucket notifications",
|
||||
type: "string"
|
||||
type: "string",
|
||||
},
|
||||
{
|
||||
name: "sasl_username",
|
||||
label: "SASL Username",
|
||||
tooltip: "username for SASL/PLAIN or SASL/SCRAM authentication",
|
||||
type: "string"
|
||||
type: "string",
|
||||
},
|
||||
{
|
||||
name: "sasl_password",
|
||||
label: "SASL Password",
|
||||
tooltip: "password for SASL/PLAIN or SASL/SCRAM authentication",
|
||||
type: "string"
|
||||
type: "string",
|
||||
},
|
||||
{
|
||||
name: "sasl_mechanism",
|
||||
label: "SASL Mechanism",
|
||||
tooltip: "sasl authentication mechanism, default 'PLAIN'",
|
||||
type: "string"
|
||||
type: "string",
|
||||
},
|
||||
{
|
||||
name: "tls_client_auth",
|
||||
label: "TLS Client Auth",
|
||||
tooltip:
|
||||
"clientAuth determines the Kafka server's policy for TLS client auth",
|
||||
type: "string"
|
||||
type: "string",
|
||||
},
|
||||
{
|
||||
name: "sasl",
|
||||
label: "SASL",
|
||||
tooltip: "set to 'on' to enable SASL authentication",
|
||||
type: "on|off"
|
||||
type: "on|off",
|
||||
},
|
||||
{
|
||||
name: "tls",
|
||||
label: "TLS",
|
||||
tooltip: "set to 'on' to enable TLS",
|
||||
type: "on|off"
|
||||
type: "on|off",
|
||||
},
|
||||
{
|
||||
name: "tls_skip_verify",
|
||||
label: "TLS skip verify",
|
||||
tooltip:
|
||||
'trust server TLS without verification, defaults to "on" (verify)',
|
||||
type: "on|off"
|
||||
type: "on|off",
|
||||
},
|
||||
{
|
||||
name: "client_tls_cert",
|
||||
label: "client TLS cert",
|
||||
tooltip: "path to client certificate for mTLS auth",
|
||||
type: "path"
|
||||
type: "path",
|
||||
},
|
||||
{
|
||||
name: "client_tls_key",
|
||||
label: "client TLS key",
|
||||
tooltip: "path to client key for mTLS auth",
|
||||
type: "path"
|
||||
type: "path",
|
||||
},
|
||||
{
|
||||
name: "version",
|
||||
label: "Version",
|
||||
tooltip: "specify the version of the Kafka cluster e.g '2.2.0'",
|
||||
type: "string"
|
||||
type: "string",
|
||||
},
|
||||
...commonFields
|
||||
...commonFields,
|
||||
],
|
||||
[notifyAmqp]: [
|
||||
{
|
||||
@@ -477,68 +477,68 @@ export const notificationEndpointsFields: any = {
|
||||
label: "url",
|
||||
tooltip:
|
||||
"AMQP server endpoint e.g. `amqp://myuser:mypassword@localhost:5672`",
|
||||
type: "url"
|
||||
type: "url",
|
||||
},
|
||||
{
|
||||
name: "exchange",
|
||||
label: "exchange",
|
||||
tooltip: "name of the AMQP exchange",
|
||||
type: "string"
|
||||
type: "string",
|
||||
},
|
||||
{
|
||||
name: "exchange_type",
|
||||
label: "exchange_type",
|
||||
tooltip: "AMQP exchange type",
|
||||
type: "string"
|
||||
type: "string",
|
||||
},
|
||||
{
|
||||
name: "routing_key",
|
||||
label: "routing_key",
|
||||
tooltip: "routing key for publishing",
|
||||
type: "string"
|
||||
type: "string",
|
||||
},
|
||||
{
|
||||
name: "mandatory",
|
||||
label: "mandatory",
|
||||
tooltip:
|
||||
"quietly ignore undelivered messages when set to 'off', default is 'on'",
|
||||
type: "on|off"
|
||||
type: "on|off",
|
||||
},
|
||||
{
|
||||
name: "durable",
|
||||
label: "durable",
|
||||
tooltip:
|
||||
"persist queue across broker restarts when set to 'on', default is 'off'",
|
||||
type: "on|off"
|
||||
type: "on|off",
|
||||
},
|
||||
{
|
||||
name: "no_wait",
|
||||
label: "no_wait",
|
||||
tooltip:
|
||||
"non-blocking message delivery when set to 'on', default is 'off'",
|
||||
type: "on|off"
|
||||
type: "on|off",
|
||||
},
|
||||
{
|
||||
name: "internal",
|
||||
label: "internal",
|
||||
tooltip:
|
||||
"set to 'on' for exchange to be not used directly by publishers, but only when bound to other exchanges",
|
||||
type: "on|off"
|
||||
type: "on|off",
|
||||
},
|
||||
{
|
||||
name: "auto_deleted",
|
||||
label: "auto_deleted",
|
||||
tooltip:
|
||||
"auto delete queue when set to 'on', when there are no consumers",
|
||||
type: "on|off"
|
||||
type: "on|off",
|
||||
},
|
||||
{
|
||||
name: "delivery_mode",
|
||||
label: "delivery_mode",
|
||||
tooltip: "set to '1' for non-persistent or '2' for persistent queue",
|
||||
type: "number"
|
||||
type: "number",
|
||||
},
|
||||
...commonFields
|
||||
...commonFields,
|
||||
],
|
||||
[notifyRedis]: [
|
||||
{
|
||||
@@ -546,22 +546,22 @@ export const notificationEndpointsFields: any = {
|
||||
required: true,
|
||||
label: "address",
|
||||
tooltip: "Redis server's address. For example: `localhost:6379`",
|
||||
type: "address"
|
||||
type: "address",
|
||||
},
|
||||
{
|
||||
name: "key",
|
||||
required: true,
|
||||
label: "key",
|
||||
tooltip: "Redis key to store/update events, key is auto-created",
|
||||
type: "string"
|
||||
type: "string",
|
||||
},
|
||||
{
|
||||
name: "password",
|
||||
label: "password",
|
||||
tooltip: "Redis server password",
|
||||
type: "string"
|
||||
type: "string",
|
||||
},
|
||||
...commonFields
|
||||
...commonFields,
|
||||
],
|
||||
[notifyMqtt]: [
|
||||
{
|
||||
@@ -569,46 +569,46 @@ export const notificationEndpointsFields: any = {
|
||||
required: true,
|
||||
label: "broker",
|
||||
tooltip: "MQTT server endpoint e.g. `tcp://localhost:1883`",
|
||||
type: "uri"
|
||||
type: "uri",
|
||||
},
|
||||
{
|
||||
name: "topic",
|
||||
required: true,
|
||||
label: "topic",
|
||||
tooltip: "name of the MQTT topic to publish",
|
||||
type: "string"
|
||||
type: "string",
|
||||
},
|
||||
{
|
||||
name: "username",
|
||||
label: "username",
|
||||
tooltip: "MQTT username",
|
||||
type: "string"
|
||||
type: "string",
|
||||
},
|
||||
{
|
||||
name: "password",
|
||||
label: "password",
|
||||
tooltip: "MQTT password",
|
||||
type: "string"
|
||||
type: "string",
|
||||
},
|
||||
{
|
||||
name: "qos",
|
||||
label: "qos",
|
||||
tooltip: "set the quality of service priority, defaults to '0'",
|
||||
type: "number"
|
||||
type: "number",
|
||||
},
|
||||
{
|
||||
name: "keep_alive_interval",
|
||||
label: "keep_alive_interval",
|
||||
tooltip: "keep-alive interval for MQTT connections in s,m,h,d",
|
||||
type: "duration"
|
||||
type: "duration",
|
||||
},
|
||||
{
|
||||
name: "reconnect_interval",
|
||||
label: "reconnect_interval",
|
||||
tooltip: "reconnect interval for MQTT connections in s,m,h,d",
|
||||
type: "duration"
|
||||
type: "duration",
|
||||
},
|
||||
...commonFields
|
||||
...commonFields,
|
||||
],
|
||||
[notifyNats]: [
|
||||
{
|
||||
@@ -616,95 +616,95 @@ export const notificationEndpointsFields: any = {
|
||||
required: true,
|
||||
label: "address",
|
||||
tooltip: "NATS server address e.g. '0.0.0.0:4222'",
|
||||
type: "address"
|
||||
type: "address",
|
||||
},
|
||||
{
|
||||
name: "subject",
|
||||
required: true,
|
||||
label: "subject",
|
||||
tooltip: "NATS subscription subject",
|
||||
type: "string"
|
||||
type: "string",
|
||||
},
|
||||
{
|
||||
name: "username",
|
||||
label: "username",
|
||||
tooltip: "NATS username",
|
||||
type: "string"
|
||||
type: "string",
|
||||
},
|
||||
{
|
||||
name: "password",
|
||||
label: "password",
|
||||
tooltip: "NATS password",
|
||||
type: "string"
|
||||
type: "string",
|
||||
},
|
||||
{
|
||||
name: "token",
|
||||
label: "token",
|
||||
tooltip: "NATS token",
|
||||
type: "string"
|
||||
type: "string",
|
||||
},
|
||||
{
|
||||
name: "tls",
|
||||
label: "tls",
|
||||
tooltip: "set to 'on' to enable TLS",
|
||||
type: "on|off"
|
||||
type: "on|off",
|
||||
},
|
||||
{
|
||||
name: "tls_skip_verify",
|
||||
label: "tls_skip_verify",
|
||||
tooltip:
|
||||
'trust server TLS without verification, defaults to "on" (verify)',
|
||||
type: "on|off"
|
||||
type: "on|off",
|
||||
},
|
||||
{
|
||||
name: "ping_interval",
|
||||
label: "ping_interval",
|
||||
tooltip: "client ping commands interval in s,m,h,d. Disabled by default",
|
||||
type: "duration"
|
||||
type: "duration",
|
||||
},
|
||||
{
|
||||
name: "streaming",
|
||||
label: "streaming",
|
||||
tooltip: "set to 'on', to use streaming NATS server",
|
||||
type: "on|off"
|
||||
type: "on|off",
|
||||
},
|
||||
{
|
||||
name: "streaming_async",
|
||||
label: "streaming_async",
|
||||
tooltip: "set to 'on', to enable asynchronous publish",
|
||||
type: "on|off"
|
||||
type: "on|off",
|
||||
},
|
||||
{
|
||||
name: "streaming_max_pub_acks_in_flight",
|
||||
label: "streaming_max_pub_acks_in_flight",
|
||||
tooltip: "number of messages to publish without waiting for ACKs",
|
||||
type: "number"
|
||||
type: "number",
|
||||
},
|
||||
{
|
||||
name: "streaming_cluster_id",
|
||||
label: "streaming_cluster_id",
|
||||
tooltip: "unique ID for NATS streaming cluster",
|
||||
type: "string"
|
||||
type: "string",
|
||||
},
|
||||
{
|
||||
name: "cert_authority",
|
||||
label: "cert_authority",
|
||||
tooltip: "path to certificate chain of the target NATS server",
|
||||
type: "string"
|
||||
type: "string",
|
||||
},
|
||||
{
|
||||
name: "client_cert",
|
||||
label: "client_cert",
|
||||
tooltip: "client cert for NATS mTLS auth",
|
||||
type: "string"
|
||||
type: "string",
|
||||
},
|
||||
{
|
||||
name: "client_key",
|
||||
label: "client_key",
|
||||
tooltip: "client cert key for NATS mTLS auth",
|
||||
type: "string"
|
||||
type: "string",
|
||||
},
|
||||
...commonFields
|
||||
...commonFields,
|
||||
],
|
||||
[notifyElasticsearch]: [
|
||||
{
|
||||
@@ -713,7 +713,7 @@ export const notificationEndpointsFields: any = {
|
||||
label: "url",
|
||||
tooltip:
|
||||
"Elasticsearch server's address, with optional authentication info",
|
||||
type: "url"
|
||||
type: "url",
|
||||
},
|
||||
{
|
||||
name: "index",
|
||||
@@ -721,7 +721,7 @@ export const notificationEndpointsFields: any = {
|
||||
label: "index",
|
||||
tooltip:
|
||||
"Elasticsearch index to store/update events, index is auto-created",
|
||||
type: "string"
|
||||
type: "string",
|
||||
},
|
||||
{
|
||||
name: "format",
|
||||
@@ -729,9 +729,9 @@ export const notificationEndpointsFields: any = {
|
||||
label: "format",
|
||||
tooltip:
|
||||
"'namespace' reflects current bucket/object list and 'access' reflects a journal of object operations, defaults to 'namespace'",
|
||||
type: "enum"
|
||||
type: "enum",
|
||||
},
|
||||
...commonFields
|
||||
...commonFields,
|
||||
],
|
||||
[notifyWebhooks]: [
|
||||
{
|
||||
@@ -740,15 +740,15 @@ export const notificationEndpointsFields: any = {
|
||||
label: "endpoint",
|
||||
tooltip:
|
||||
"webhook server endpoint e.g. http://localhost:8080/minio/events",
|
||||
type: "url"
|
||||
type: "url",
|
||||
},
|
||||
{
|
||||
name: "auth_token",
|
||||
label: "auth_token",
|
||||
tooltip: "opaque string or JWT authorization token",
|
||||
type: "string"
|
||||
type: "string",
|
||||
},
|
||||
...commonFields
|
||||
...commonFields,
|
||||
],
|
||||
[notifyNsq]: [
|
||||
{
|
||||
@@ -756,34 +756,34 @@ export const notificationEndpointsFields: any = {
|
||||
required: true,
|
||||
label: "nsqd_address",
|
||||
tooltip: "NSQ server address e.g. '127.0.0.1:4150'",
|
||||
type: "address"
|
||||
type: "address",
|
||||
},
|
||||
{
|
||||
name: "topic",
|
||||
required: true,
|
||||
label: "topic",
|
||||
tooltip: "NSQ topic",
|
||||
type: "string"
|
||||
type: "string",
|
||||
},
|
||||
{
|
||||
name: "tls",
|
||||
label: "tls",
|
||||
tooltip: "set to 'on' to enable TLS",
|
||||
type: "on|off"
|
||||
type: "on|off",
|
||||
},
|
||||
{
|
||||
name: "tls_skip_verify",
|
||||
label: "tls_skip_verify",
|
||||
tooltip:
|
||||
'trust server TLS without verification, defaults to "on" (verify)',
|
||||
type: "on|off"
|
||||
type: "on|off",
|
||||
},
|
||||
...commonFields
|
||||
]
|
||||
...commonFields,
|
||||
],
|
||||
};
|
||||
|
||||
export const removeEmptyFields = (formFields: IElementValue[]) => {
|
||||
const nonEmptyFields = formFields.filter(field => field.value !== "");
|
||||
const nonEmptyFields = formFields.filter((field) => field.value !== "");
|
||||
|
||||
return nonEmptyFields;
|
||||
};
|
||||
|
||||
@@ -224,6 +224,7 @@ const Console = ({
|
||||
console.log(err);
|
||||
});
|
||||
};
|
||||
|
||||
const allowedPages = session.pages.reduce(
|
||||
(result: any, item: any, index: any) => {
|
||||
result[item] = true;
|
||||
@@ -278,7 +279,7 @@ const Console = ({
|
||||
},
|
||||
{
|
||||
component: ServiceAccounts,
|
||||
path: "/service_accounts",
|
||||
path: "/service-accounts",
|
||||
},
|
||||
{
|
||||
component: WebhookPanel,
|
||||
@@ -290,6 +291,7 @@ const Console = ({
|
||||
},
|
||||
];
|
||||
const allowedRoutes = routes.filter((route: any) => allowedPages[route.path]);
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
{session.status == "ok" ? (
|
||||
|
||||
@@ -26,7 +26,7 @@ import { CreateIcon } from "../../../icons";
|
||||
import api from "../../../common/api";
|
||||
import { MinTablePaginationActions } from "../../../common/MinTablePaginationActions";
|
||||
import { GroupsList } from "./types";
|
||||
import { groupsSort } from "../../../utils/sortFunctions";
|
||||
import { stringSort } from "../../../utils/sortFunctions";
|
||||
import AddGroup from "../Groups/AddGroup";
|
||||
import DeleteGroup from "./DeleteGroup";
|
||||
import TableWrapper from "../Common/TableWrapper/TableWrapper";
|
||||
@@ -39,50 +39,50 @@ interface IGroupsProps {
|
||||
const styles = (theme: Theme) =>
|
||||
createStyles({
|
||||
seeMore: {
|
||||
marginTop: theme.spacing(3)
|
||||
marginTop: theme.spacing(3),
|
||||
},
|
||||
paper: {
|
||||
// padding: theme.spacing(2),
|
||||
display: "flex",
|
||||
overflow: "auto",
|
||||
flexDirection: "column"
|
||||
flexDirection: "column",
|
||||
},
|
||||
addSideBar: {
|
||||
width: "320px",
|
||||
padding: "20px"
|
||||
padding: "20px",
|
||||
},
|
||||
errorBlock: {
|
||||
color: "red"
|
||||
color: "red",
|
||||
},
|
||||
tableToolbar: {
|
||||
paddingLeft: theme.spacing(2),
|
||||
paddingRight: theme.spacing(0)
|
||||
paddingRight: theme.spacing(0),
|
||||
},
|
||||
wrapCell: {
|
||||
maxWidth: "200px",
|
||||
whiteSpace: "normal",
|
||||
wordWrap: "break-word"
|
||||
wordWrap: "break-word",
|
||||
},
|
||||
minTableHeader: {
|
||||
color: "#393939",
|
||||
"& tr": {
|
||||
"& th": {
|
||||
fontWeight: "bold"
|
||||
}
|
||||
}
|
||||
fontWeight: "bold",
|
||||
},
|
||||
},
|
||||
},
|
||||
actionsTray: {
|
||||
textAlign: "right",
|
||||
"& button": {
|
||||
marginLeft: 10
|
||||
}
|
||||
marginLeft: 10,
|
||||
},
|
||||
},
|
||||
searchField: {
|
||||
background: "#FFFFFF",
|
||||
padding: 12,
|
||||
borderRadius: 5,
|
||||
boxShadow: "0px 3px 6px #00000012"
|
||||
}
|
||||
boxShadow: "0px 3px 6px #00000012",
|
||||
},
|
||||
});
|
||||
|
||||
const Groups = ({ classes }: IGroupsProps) => {
|
||||
@@ -126,7 +126,7 @@ const Groups = ({ classes }: IGroupsProps) => {
|
||||
.then((res: GroupsList) => {
|
||||
let resGroups: string[] = [];
|
||||
if (res.groups !== null) {
|
||||
resGroups = res.groups.sort(groupsSort);
|
||||
resGroups = res.groups.sort(stringSort);
|
||||
}
|
||||
setRecords(resGroups);
|
||||
const total = !res.total ? 0 : res.total;
|
||||
@@ -140,7 +140,7 @@ const Groups = ({ classes }: IGroupsProps) => {
|
||||
setPage(newPage);
|
||||
}
|
||||
})
|
||||
.catch(err => {
|
||||
.catch((err) => {
|
||||
setError(err);
|
||||
isLoading(false);
|
||||
});
|
||||
@@ -162,7 +162,7 @@ const Groups = ({ classes }: IGroupsProps) => {
|
||||
}
|
||||
};
|
||||
|
||||
const filteredRecords = records.filter(elementItem =>
|
||||
const filteredRecords = records.filter((elementItem) =>
|
||||
elementItem.includes(filter)
|
||||
);
|
||||
|
||||
@@ -178,7 +178,7 @@ const Groups = ({ classes }: IGroupsProps) => {
|
||||
|
||||
const tableActions = [
|
||||
{ type: "view", onClick: viewAction },
|
||||
{ type: "delete", onClick: deleteAction }
|
||||
{ type: "delete", onClick: deleteAction },
|
||||
];
|
||||
|
||||
return (
|
||||
@@ -217,9 +217,9 @@ const Groups = ({ classes }: IGroupsProps) => {
|
||||
<InputAdornment position="start">
|
||||
<SearchIcon />
|
||||
</InputAdornment>
|
||||
)
|
||||
),
|
||||
}}
|
||||
onChange={e => {
|
||||
onChange={(e) => {
|
||||
setFilter(e.target.value);
|
||||
}}
|
||||
/>
|
||||
@@ -255,11 +255,11 @@ const Groups = ({ classes }: IGroupsProps) => {
|
||||
page: page,
|
||||
SelectProps: {
|
||||
inputProps: { "aria-label": "rows per page" },
|
||||
native: true
|
||||
native: true,
|
||||
},
|
||||
onChangePage: handleChangePage,
|
||||
onChangeRowsPerPage: handleChangeRowsPerPage,
|
||||
ActionsComponent: MinTablePaginationActions
|
||||
ActionsComponent: MinTablePaginationActions,
|
||||
}}
|
||||
/>
|
||||
</Grid>
|
||||
|
||||
@@ -38,43 +38,43 @@ interface IGroupsProps {
|
||||
const styles = (theme: Theme) =>
|
||||
createStyles({
|
||||
seeMore: {
|
||||
marginTop: theme.spacing(3)
|
||||
marginTop: theme.spacing(3),
|
||||
},
|
||||
paper: {
|
||||
// padding: theme.spacing(2),
|
||||
display: "flex",
|
||||
overflow: "auto",
|
||||
flexDirection: "column"
|
||||
flexDirection: "column",
|
||||
},
|
||||
addSideBar: {
|
||||
width: "320px",
|
||||
padding: "20px"
|
||||
padding: "20px",
|
||||
},
|
||||
errorBlock: {
|
||||
color: "red"
|
||||
color: "red",
|
||||
},
|
||||
tableToolbar: {
|
||||
paddingLeft: theme.spacing(2),
|
||||
paddingRight: theme.spacing(0)
|
||||
paddingRight: theme.spacing(0),
|
||||
},
|
||||
wrapCell: {
|
||||
maxWidth: "200px",
|
||||
whiteSpace: "normal",
|
||||
wordWrap: "break-word"
|
||||
wordWrap: "break-word",
|
||||
},
|
||||
minTableHeader: {
|
||||
color: "#393939",
|
||||
"& tr": {
|
||||
"& th": {
|
||||
fontWeight: "bold"
|
||||
}
|
||||
}
|
||||
fontWeight: "bold",
|
||||
},
|
||||
},
|
||||
},
|
||||
actionsTray: {
|
||||
textAlign: "left",
|
||||
"& button": {
|
||||
marginLeft: 10
|
||||
}
|
||||
marginLeft: 10,
|
||||
},
|
||||
},
|
||||
filterField: {
|
||||
background: "#FFFFFF",
|
||||
@@ -82,24 +82,24 @@ const styles = (theme: Theme) =>
|
||||
borderRadius: 5,
|
||||
boxShadow: "0px 3px 6px #00000012",
|
||||
width: "100%",
|
||||
zIndex: 500
|
||||
zIndex: 500,
|
||||
},
|
||||
noFound: {
|
||||
textAlign: "center",
|
||||
padding: "10px 0"
|
||||
padding: "10px 0",
|
||||
},
|
||||
tableContainer: {
|
||||
maxHeight: 250
|
||||
maxHeight: 250,
|
||||
},
|
||||
stickyHeader: {
|
||||
backgroundColor: "#fff"
|
||||
}
|
||||
backgroundColor: "#fff",
|
||||
},
|
||||
});
|
||||
|
||||
const UsersSelectors = ({
|
||||
classes,
|
||||
selectedUsers,
|
||||
setSelectedUsers
|
||||
setSelectedUsers,
|
||||
}: IGroupsProps) => {
|
||||
//Local States
|
||||
const [records, setRecords] = useState<any[]>([]);
|
||||
@@ -133,7 +133,7 @@ const UsersSelectors = ({
|
||||
elements.push(value);
|
||||
} else {
|
||||
// User has unchecked this field, we need to remove it from the list
|
||||
elements = elements.filter(element => element !== value);
|
||||
elements = elements.filter((element) => element !== value);
|
||||
}
|
||||
setSelectedUsers(elements);
|
||||
|
||||
@@ -154,13 +154,13 @@ const UsersSelectors = ({
|
||||
setError("");
|
||||
isLoading(false);
|
||||
})
|
||||
.catch(err => {
|
||||
.catch((err) => {
|
||||
setError(err);
|
||||
isLoading(false);
|
||||
});
|
||||
};
|
||||
|
||||
const filteredRecords = records.filter(elementItem =>
|
||||
const filteredRecords = records.filter((elementItem) =>
|
||||
elementItem.accessKey.includes(filter)
|
||||
);
|
||||
|
||||
@@ -185,9 +185,9 @@ const UsersSelectors = ({
|
||||
<InputAdornment position="start">
|
||||
<SearchIcon />
|
||||
</InputAdornment>
|
||||
)
|
||||
),
|
||||
}}
|
||||
onChange={e => {
|
||||
onChange={(e) => {
|
||||
setFilter(e.target.value);
|
||||
}}
|
||||
/>
|
||||
|
||||
@@ -33,12 +33,12 @@ export type LogActionTypes = LogMessageReceivedAction | LogResetMessagesAction;
|
||||
export function logMessageReceived(message: LogMessage) {
|
||||
return {
|
||||
type: LOG_MESSAGE_RECEIVED,
|
||||
message: message
|
||||
message: message,
|
||||
};
|
||||
}
|
||||
|
||||
export function logResetMessages() {
|
||||
return {
|
||||
type: LOG_RESET_MESSAGES
|
||||
type: LOG_RESET_MESSAGES,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
import {
|
||||
LOG_MESSAGE_RECEIVED,
|
||||
LOG_RESET_MESSAGES,
|
||||
LogActionTypes
|
||||
LogActionTypes,
|
||||
} from "./actions";
|
||||
import { LogMessage } from "./types";
|
||||
|
||||
@@ -26,7 +26,7 @@ export interface LogState {
|
||||
}
|
||||
|
||||
const initialState: LogState = {
|
||||
messages: []
|
||||
messages: [],
|
||||
};
|
||||
|
||||
export function logReducer(
|
||||
@@ -37,12 +37,12 @@ export function logReducer(
|
||||
case LOG_MESSAGE_RECEIVED:
|
||||
return {
|
||||
...state,
|
||||
messages: [...state.messages, action.message]
|
||||
messages: [...state.messages, action.message],
|
||||
};
|
||||
case LOG_RESET_MESSAGES:
|
||||
return {
|
||||
...state,
|
||||
messages: []
|
||||
messages: [],
|
||||
};
|
||||
default:
|
||||
return state;
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
import React from "react";
|
||||
import ListItem from "@material-ui/core/ListItem";
|
||||
import ListItemIcon from "@material-ui/core/ListItemIcon";
|
||||
import RoomServiceIcon from "@material-ui/icons/RoomService";
|
||||
import WebAssetIcon from "@material-ui/icons/WebAsset";
|
||||
import CenterFocusWeakIcon from "@material-ui/icons/CenterFocusWeak";
|
||||
import ListItemText from "@material-ui/core/ListItemText";
|
||||
@@ -136,6 +137,14 @@ class Menu extends React.Component<MenuProps> {
|
||||
name: "Buckets",
|
||||
icon: <BucketsIcon />,
|
||||
},
|
||||
{
|
||||
type: "item",
|
||||
component: NavLink,
|
||||
to: "/service-accounts",
|
||||
name: "Service Accounts",
|
||||
icon: <RoomServiceIcon />,
|
||||
forceDisplay: true,
|
||||
},
|
||||
{
|
||||
type: "item",
|
||||
component: NavLink,
|
||||
|
||||
@@ -31,14 +31,14 @@ import {
|
||||
Radio,
|
||||
RadioGroup,
|
||||
Select,
|
||||
TextField
|
||||
TextField,
|
||||
} from "@material-ui/core";
|
||||
import {
|
||||
createStyles,
|
||||
lighten,
|
||||
makeStyles,
|
||||
Theme,
|
||||
withStyles
|
||||
withStyles,
|
||||
} from "@material-ui/core/styles";
|
||||
import api from "../../../common/api";
|
||||
import clsx from "clsx";
|
||||
@@ -61,21 +61,21 @@ const useToolbarStyles = makeStyles((theme: Theme) =>
|
||||
createStyles({
|
||||
root: {
|
||||
paddingLeft: theme.spacing(2),
|
||||
paddingRight: theme.spacing(1)
|
||||
paddingRight: theme.spacing(1),
|
||||
},
|
||||
highlight:
|
||||
theme.palette.type === "light"
|
||||
? {
|
||||
color: theme.palette.secondary.main,
|
||||
backgroundColor: lighten(theme.palette.secondary.light, 0.85)
|
||||
backgroundColor: lighten(theme.palette.secondary.light, 0.85),
|
||||
}
|
||||
: {
|
||||
color: theme.palette.text.primary,
|
||||
backgroundColor: theme.palette.secondary.dark
|
||||
backgroundColor: theme.palette.secondary.dark,
|
||||
},
|
||||
title: {
|
||||
flex: "1 1 100%"
|
||||
}
|
||||
flex: "1 1 100%",
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
@@ -90,7 +90,7 @@ const EnhancedTableToolbar = (props: EnhancedTableToolbarProps) => {
|
||||
return (
|
||||
<Toolbar
|
||||
className={clsx(classes.root, {
|
||||
[classes.highlight]: numSelected > 0
|
||||
[classes.highlight]: numSelected > 0,
|
||||
})}
|
||||
>
|
||||
{numSelected > 0 ? (
|
||||
@@ -122,8 +122,8 @@ const EnhancedTableToolbar = (props: EnhancedTableToolbarProps) => {
|
||||
const styles = (theme: Theme) =>
|
||||
createStyles({
|
||||
errorBlock: {
|
||||
color: "red"
|
||||
}
|
||||
color: "red",
|
||||
},
|
||||
});
|
||||
|
||||
interface IAddPermissionContentProps {
|
||||
@@ -164,7 +164,7 @@ class AddPermissionContent extends React.Component<
|
||||
resources: [],
|
||||
buckets: [],
|
||||
bucketsError: "",
|
||||
loadingBuckets: false
|
||||
loadingBuckets: false,
|
||||
};
|
||||
|
||||
componentDidMount(): void {
|
||||
@@ -176,10 +176,10 @@ class AddPermissionContent extends React.Component<
|
||||
this.setState({
|
||||
loadingBuckets: false,
|
||||
buckets: res.buckets,
|
||||
bucketsError: ""
|
||||
bucketsError: "",
|
||||
});
|
||||
})
|
||||
.catch(err => {
|
||||
.catch((err) => {
|
||||
this.setState({ loadingBuckets: false, bucketsError: err });
|
||||
});
|
||||
});
|
||||
@@ -190,8 +190,8 @@ class AddPermissionContent extends React.Component<
|
||||
name: selectedPermission.name,
|
||||
description: selectedPermission.description,
|
||||
effect: selectedPermission.effect,
|
||||
resources: selectedPermission.resources.map(r => r.bucket_name),
|
||||
action: selectedPermission.actions[0].type
|
||||
resources: selectedPermission.resources.map((r) => r.bucket_name),
|
||||
action: selectedPermission.actions[0].type,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -204,7 +204,7 @@ class AddPermissionContent extends React.Component<
|
||||
resources,
|
||||
description,
|
||||
effect,
|
||||
action
|
||||
action,
|
||||
} = this.state;
|
||||
const { selectedPermission } = this.props;
|
||||
if (addLoading) {
|
||||
@@ -219,23 +219,23 @@ class AddPermissionContent extends React.Component<
|
||||
description: description,
|
||||
effect: effect,
|
||||
resources: resources,
|
||||
actions: [action]
|
||||
actions: [action],
|
||||
})
|
||||
.then(res => {
|
||||
.then((res) => {
|
||||
this.setState(
|
||||
{
|
||||
addLoading: false,
|
||||
addError: ""
|
||||
addError: "",
|
||||
},
|
||||
() => {
|
||||
this.props.closeModalAndRefresh();
|
||||
}
|
||||
);
|
||||
})
|
||||
.catch(err => {
|
||||
.catch((err) => {
|
||||
this.setState({
|
||||
addLoading: false,
|
||||
addError: err
|
||||
addError: err,
|
||||
});
|
||||
});
|
||||
} else {
|
||||
@@ -245,23 +245,23 @@ class AddPermissionContent extends React.Component<
|
||||
description: description,
|
||||
effect: effect,
|
||||
resources: resources,
|
||||
actions: [action]
|
||||
actions: [action],
|
||||
})
|
||||
.then(res => {
|
||||
.then((res) => {
|
||||
this.setState(
|
||||
{
|
||||
addLoading: false,
|
||||
addError: ""
|
||||
addError: "",
|
||||
},
|
||||
() => {
|
||||
this.props.closeModalAndRefresh();
|
||||
}
|
||||
);
|
||||
})
|
||||
.catch(err => {
|
||||
.catch((err) => {
|
||||
this.setState({
|
||||
addLoading: false,
|
||||
addError: err
|
||||
addError: err,
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -280,14 +280,14 @@ class AddPermissionContent extends React.Component<
|
||||
name,
|
||||
description,
|
||||
effect,
|
||||
action
|
||||
action,
|
||||
} = this.state;
|
||||
|
||||
const handleSelectAllClick = (
|
||||
event: React.ChangeEvent<HTMLInputElement>
|
||||
) => {
|
||||
if (event.target.checked) {
|
||||
const newSelecteds = buckets.map(n => n.name);
|
||||
const newSelecteds = buckets.map((n) => n.name);
|
||||
this.setState({ resources: newSelecteds });
|
||||
return;
|
||||
}
|
||||
@@ -432,7 +432,7 @@ class AddPermissionContent extends React.Component<
|
||||
}
|
||||
onChange={handleSelectAllClick}
|
||||
inputProps={{
|
||||
"aria-label": "select all desserts"
|
||||
"aria-label": "select all desserts",
|
||||
}}
|
||||
/>
|
||||
</TableCell>
|
||||
@@ -452,7 +452,9 @@ class AddPermissionContent extends React.Component<
|
||||
return (
|
||||
<TableRow
|
||||
hover
|
||||
onClick={event => handleClick(event, row.name)}
|
||||
onClick={(event) =>
|
||||
handleClick(event, row.name)
|
||||
}
|
||||
role="checkbox"
|
||||
aria-checked={isItemSelected}
|
||||
tabIndex={-1}
|
||||
@@ -500,7 +502,7 @@ class AddPermissionContent extends React.Component<
|
||||
value={action}
|
||||
onChange={(event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
this.setState({
|
||||
action: (event.target as HTMLInputElement).value
|
||||
action: (event.target as HTMLInputElement).value,
|
||||
});
|
||||
}}
|
||||
>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// This file is part of MinIO Console Server
|
||||
// Copyright (c) 2019 MinIO, Inc.
|
||||
// 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
|
||||
@@ -14,363 +14,100 @@
|
||||
// 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 React, { useEffect, useState } from "react";
|
||||
import Grid from "@material-ui/core/Grid";
|
||||
import { UnControlled as CodeMirror } from "react-codemirror2";
|
||||
import Typography from "@material-ui/core/Typography";
|
||||
import { Button, FormControlLabel, LinearProgress } from "@material-ui/core";
|
||||
import {
|
||||
createStyles,
|
||||
lighten,
|
||||
makeStyles,
|
||||
Theme,
|
||||
withStyles
|
||||
} from "@material-ui/core/styles";
|
||||
import api from "../../../common/api";
|
||||
import clsx from "clsx";
|
||||
import Table from "@material-ui/core/Table";
|
||||
import TableBody from "@material-ui/core/TableBody";
|
||||
import TableCell from "@material-ui/core/TableCell";
|
||||
import TableContainer from "@material-ui/core/TableContainer";
|
||||
import TableHead from "@material-ui/core/TableHead";
|
||||
import TablePagination from "@material-ui/core/TablePagination";
|
||||
import TableRow from "@material-ui/core/TableRow";
|
||||
import Toolbar from "@material-ui/core/Toolbar";
|
||||
import Checkbox from "@material-ui/core/Checkbox";
|
||||
import IconButton from "@material-ui/core/IconButton";
|
||||
import Tooltip from "@material-ui/core/Tooltip";
|
||||
import FilterListIcon from "@material-ui/icons/FilterList";
|
||||
import { Permission, PermissionList } from "../Permissions/types";
|
||||
import {
|
||||
NewServiceAccount,
|
||||
ServiceAccount,
|
||||
ServiceAccountDetails
|
||||
} from "./types";
|
||||
import Switch from "@material-ui/core/Switch";
|
||||
import { Button, LinearProgress } from "@material-ui/core";
|
||||
import { createStyles, Theme, withStyles } from "@material-ui/core/styles";
|
||||
import { modalBasic } from "../Common/FormComponents/common/styleLibrary";
|
||||
import ModalWrapper from "../Common/ModalWrapper/ModalWrapper";
|
||||
import InputBoxWrapper from "../Common/FormComponents/InputBoxWrapper/InputBoxWrapper";
|
||||
import api from "../../../common/api";
|
||||
import "codemirror/lib/codemirror.css";
|
||||
import "codemirror/theme/material.css";
|
||||
import { NewServiceAccount } from "./types";
|
||||
|
||||
const useToolbarStyles = makeStyles((theme: Theme) =>
|
||||
createStyles({
|
||||
root: {
|
||||
paddingLeft: theme.spacing(2),
|
||||
paddingRight: theme.spacing(1)
|
||||
},
|
||||
highlight:
|
||||
theme.palette.type === "light"
|
||||
? {
|
||||
color: theme.palette.secondary.main,
|
||||
backgroundColor: lighten(theme.palette.secondary.light, 0.85)
|
||||
}
|
||||
: {
|
||||
color: theme.palette.text.primary,
|
||||
backgroundColor: theme.palette.secondary.dark
|
||||
},
|
||||
title: {
|
||||
flex: "1 1 100%"
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
interface EnhancedTableToolbarProps {
|
||||
numSelected: number;
|
||||
}
|
||||
|
||||
const EnhancedTableToolbar = (props: EnhancedTableToolbarProps) => {
|
||||
const classes = useToolbarStyles();
|
||||
const { numSelected } = props;
|
||||
|
||||
return (
|
||||
<Toolbar
|
||||
className={clsx(classes.root, {
|
||||
[classes.highlight]: numSelected > 0
|
||||
})}
|
||||
>
|
||||
{numSelected > 0 ? (
|
||||
<Typography
|
||||
className={classes.title}
|
||||
color="inherit"
|
||||
variant="subtitle1"
|
||||
>
|
||||
{numSelected} selected
|
||||
</Typography>
|
||||
) : (
|
||||
<Typography className={classes.title} variant="h6" id="tableTitle">
|
||||
Permissions
|
||||
</Typography>
|
||||
)}
|
||||
{numSelected > 0 ? (
|
||||
<span />
|
||||
) : (
|
||||
<Tooltip title="Filter list">
|
||||
<IconButton aria-label="filter list">
|
||||
<FilterListIcon />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
)}
|
||||
</Toolbar>
|
||||
);
|
||||
};
|
||||
require("codemirror/mode/javascript/javascript");
|
||||
|
||||
const styles = (theme: Theme) =>
|
||||
createStyles({
|
||||
errorBlock: {
|
||||
color: "red"
|
||||
}
|
||||
color: "red",
|
||||
},
|
||||
jsonPolicyEditor: {
|
||||
minHeight: 400,
|
||||
width: "100%",
|
||||
},
|
||||
codeMirror: {
|
||||
fontSize: 14,
|
||||
},
|
||||
buttonContainer: {
|
||||
textAlign: "right",
|
||||
},
|
||||
...modalBasic,
|
||||
});
|
||||
|
||||
interface IAddServiceAccountContentProps {
|
||||
interface IAddServiceAccountProps {
|
||||
classes: any;
|
||||
open: boolean;
|
||||
closeModalAndRefresh: (res: NewServiceAccount | null) => void;
|
||||
selectedServiceAccount: ServiceAccount | null;
|
||||
}
|
||||
|
||||
interface IAddServiceAccountContentState {
|
||||
addLoading: boolean;
|
||||
addError: string;
|
||||
name: string;
|
||||
enabled: boolean;
|
||||
selectedPermissions: Permission[];
|
||||
rowsPerPage: number;
|
||||
page: number;
|
||||
permissions: Permission[];
|
||||
permissionsError: string;
|
||||
loadingPermissions: boolean;
|
||||
loadingServiceAccount: boolean;
|
||||
}
|
||||
const AddServiceAccount = ({
|
||||
classes,
|
||||
open,
|
||||
closeModalAndRefresh,
|
||||
}: IAddServiceAccountProps) => {
|
||||
const [addSending, setAddSending] = useState(false);
|
||||
const [addError, setAddError] = useState("");
|
||||
const [policyDefinition, setPolicyDefinition] = useState("");
|
||||
|
||||
class AddServiceAccountContent extends React.Component<
|
||||
IAddServiceAccountContentProps,
|
||||
IAddServiceAccountContentState
|
||||
> {
|
||||
state: IAddServiceAccountContentState = {
|
||||
addLoading: false,
|
||||
addError: "",
|
||||
name: "",
|
||||
enabled: true,
|
||||
selectedPermissions: [],
|
||||
rowsPerPage: 5,
|
||||
page: 0,
|
||||
permissions: [],
|
||||
permissionsError: "",
|
||||
loadingPermissions: false,
|
||||
loadingServiceAccount: false
|
||||
useEffect(() => {
|
||||
if (addSending) {
|
||||
api
|
||||
.invoke("POST", "/api/v1/service-accounts", {
|
||||
policy: policyDefinition,
|
||||
})
|
||||
.then((res) => {
|
||||
setAddSending(false);
|
||||
setAddError("");
|
||||
closeModalAndRefresh(res);
|
||||
})
|
||||
.catch((err) => {
|
||||
setAddSending(false);
|
||||
setAddError(err);
|
||||
});
|
||||
}
|
||||
}, [
|
||||
addSending,
|
||||
setAddSending,
|
||||
setAddError,
|
||||
policyDefinition,
|
||||
closeModalAndRefresh,
|
||||
]);
|
||||
|
||||
const addServiceAccount = (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
setAddSending(true);
|
||||
};
|
||||
|
||||
componentDidMount(): void {
|
||||
// load a list of permissions
|
||||
this.setState({ loadingPermissions: true }, () => {
|
||||
api
|
||||
.invoke("GET", `/api/v1/permissions?limit=1000`)
|
||||
.then((res: PermissionList) => {
|
||||
this.setState({
|
||||
loadingPermissions: false,
|
||||
permissions: res.permissions,
|
||||
permissionsError: ""
|
||||
});
|
||||
})
|
||||
.catch(err => {
|
||||
this.setState({ loadingPermissions: false, permissionsError: err });
|
||||
});
|
||||
});
|
||||
|
||||
const { selectedServiceAccount } = this.props;
|
||||
if (selectedServiceAccount !== null) {
|
||||
this.setState({ loadingServiceAccount: true }, () => {
|
||||
api
|
||||
.invoke(
|
||||
"GET",
|
||||
`/api/v1/service_accounts/${selectedServiceAccount.id}`
|
||||
)
|
||||
.then((res: ServiceAccountDetails) => {
|
||||
console.log(res);
|
||||
this.setState({
|
||||
loadingServiceAccount: false,
|
||||
name: selectedServiceAccount.name,
|
||||
enabled: selectedServiceAccount.enabled,
|
||||
selectedPermissions:
|
||||
res.permissions === undefined || res.permissions === null
|
||||
? []
|
||||
: res.permissions
|
||||
});
|
||||
})
|
||||
.catch(err => {
|
||||
this.setState({ loadingServiceAccount: false });
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
saveRecord(event: React.FormEvent) {
|
||||
event.preventDefault();
|
||||
const { name, addLoading, selectedPermissions, enabled } = this.state;
|
||||
const { selectedServiceAccount } = this.props;
|
||||
if (addLoading) {
|
||||
return;
|
||||
}
|
||||
this.setState({ addLoading: true }, () => {
|
||||
if (selectedServiceAccount !== null) {
|
||||
api
|
||||
.invoke(
|
||||
"PUT",
|
||||
`/api/v1/service_accounts/${selectedServiceAccount.id}`,
|
||||
{
|
||||
id: selectedServiceAccount.id,
|
||||
name: name,
|
||||
enabled: enabled,
|
||||
permission_ids: selectedPermissions.map(p => p.id)
|
||||
}
|
||||
)
|
||||
.then(res => {
|
||||
this.setState(
|
||||
{
|
||||
addLoading: false,
|
||||
addError: ""
|
||||
},
|
||||
() => {
|
||||
this.props.closeModalAndRefresh(null);
|
||||
}
|
||||
);
|
||||
})
|
||||
.catch(err => {
|
||||
this.setState({
|
||||
addLoading: false,
|
||||
addError: err
|
||||
});
|
||||
});
|
||||
} else {
|
||||
api
|
||||
.invoke("POST", "/api/v1/service_accounts", {
|
||||
name: name,
|
||||
permission_ids: selectedPermissions.map(p => p.id)
|
||||
})
|
||||
.then((res: NewServiceAccount) => {
|
||||
this.setState(
|
||||
{
|
||||
addLoading: false,
|
||||
addError: ""
|
||||
},
|
||||
() => {
|
||||
this.props.closeModalAndRefresh(res);
|
||||
}
|
||||
);
|
||||
})
|
||||
.catch(err => {
|
||||
this.setState({
|
||||
addLoading: false,
|
||||
addError: err
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
classes,
|
||||
selectedServiceAccount,
|
||||
open,
|
||||
closeModalAndRefresh
|
||||
} = this.props;
|
||||
const {
|
||||
addLoading,
|
||||
addError,
|
||||
page,
|
||||
rowsPerPage,
|
||||
permissions,
|
||||
selectedPermissions,
|
||||
name,
|
||||
loadingServiceAccount
|
||||
} = this.state;
|
||||
|
||||
const handleSelectAllClick = (
|
||||
event: React.ChangeEvent<HTMLInputElement>
|
||||
) => {
|
||||
if (event.target.checked) {
|
||||
// const newSelecteds = permissions.map(n => n.name);
|
||||
const newSelecteds = [...permissions];
|
||||
this.setState({ selectedPermissions: newSelecteds });
|
||||
return;
|
||||
}
|
||||
this.setState({ selectedPermissions: [] });
|
||||
};
|
||||
|
||||
const handleClick = (
|
||||
event: React.MouseEvent<unknown>,
|
||||
perm: Permission
|
||||
) => {
|
||||
let newSelected: Permission[] = [...selectedPermissions];
|
||||
if (newSelected.filter(p => p.id === perm.id).length === 0) {
|
||||
newSelected.push(perm);
|
||||
} else {
|
||||
let selectedIndex = -1;
|
||||
for (let i = 0; i < newSelected.length; i++) {
|
||||
if (newSelected[i].id === perm.id) {
|
||||
selectedIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (selectedIndex >= 0) {
|
||||
newSelected = [
|
||||
...newSelected.slice(0, selectedIndex),
|
||||
...newSelected.slice(selectedIndex + 1)
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
this.setState({ selectedPermissions: newSelected });
|
||||
};
|
||||
|
||||
const handleChangePage = (event: unknown, newPage: number) => {
|
||||
this.setState({ page: newPage });
|
||||
};
|
||||
|
||||
const handleChangeRowsPerPage = (
|
||||
event: React.ChangeEvent<HTMLInputElement>
|
||||
) => {
|
||||
this.setState({ page: 0, rowsPerPage: parseInt(event.target.value, 10) });
|
||||
};
|
||||
|
||||
const isSelected = (perm: Permission) =>
|
||||
selectedPermissions.filter(p => p.id === perm.id).length > 0;
|
||||
|
||||
const emptyRows =
|
||||
rowsPerPage -
|
||||
Math.min(rowsPerPage, permissions.length - page * rowsPerPage);
|
||||
|
||||
const handleChange = (name: string) => (
|
||||
event: React.ChangeEvent<HTMLInputElement>
|
||||
) => {
|
||||
this.setState({ enabled: event.target.checked });
|
||||
};
|
||||
|
||||
return (
|
||||
<ModalWrapper
|
||||
modalOpen={open}
|
||||
onClose={() => {
|
||||
this.setState({ addError: "" }, () => {
|
||||
closeModalAndRefresh(null);
|
||||
});
|
||||
return (
|
||||
<ModalWrapper
|
||||
modalOpen={open}
|
||||
onClose={() => {
|
||||
closeModalAndRefresh(null);
|
||||
}}
|
||||
title={`Create Service Account`}
|
||||
>
|
||||
<form
|
||||
noValidate
|
||||
autoComplete="off"
|
||||
onSubmit={(e: React.FormEvent<HTMLFormElement>) => {
|
||||
addServiceAccount(e);
|
||||
}}
|
||||
title={
|
||||
selectedServiceAccount !== null
|
||||
? "Edit Service Account"
|
||||
: "Create Service Account"
|
||||
}
|
||||
>
|
||||
<form
|
||||
noValidate
|
||||
autoComplete="off"
|
||||
onSubmit={(e: React.FormEvent<HTMLFormElement>) => {
|
||||
this.saveRecord(e);
|
||||
}}
|
||||
>
|
||||
<Grid container>
|
||||
{loadingServiceAccount && (
|
||||
<Grid item xs={12}>
|
||||
<LinearProgress />
|
||||
</Grid>
|
||||
)}
|
||||
<Grid container>
|
||||
<Grid item xs={12} className={classes.formScrollable}>
|
||||
{addError !== "" && (
|
||||
<Grid item xs={12}>
|
||||
<Typography
|
||||
@@ -383,155 +120,37 @@ class AddServiceAccountContent extends React.Component<
|
||||
</Grid>
|
||||
)}
|
||||
<Grid item xs={12}>
|
||||
<InputBoxWrapper
|
||||
id="service-account-name"
|
||||
name="service-account-name"
|
||||
label="Name"
|
||||
value={name}
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
this.setState({ name: e.target.value });
|
||||
<CodeMirror
|
||||
className={classes.codeMirror}
|
||||
options={{
|
||||
mode: "javascript",
|
||||
lineNumbers: true,
|
||||
}}
|
||||
onChange={(editor, data, value) => {
|
||||
setPolicyDefinition(value);
|
||||
}}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<div className={classes.root}>
|
||||
<EnhancedTableToolbar
|
||||
numSelected={selectedPermissions.length}
|
||||
/>
|
||||
<TableContainer>
|
||||
<Table
|
||||
className={classes.table}
|
||||
aria-labelledby="tableTitle"
|
||||
size={"small"}
|
||||
aria-label="enhanced table"
|
||||
>
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
<TableCell padding="checkbox">
|
||||
<Checkbox
|
||||
indeterminate={
|
||||
selectedPermissions.length > 0 &&
|
||||
selectedPermissions.length < permissions.length
|
||||
}
|
||||
checked={
|
||||
selectedPermissions.length > 0 &&
|
||||
selectedPermissions.length === permissions.length
|
||||
}
|
||||
onChange={handleSelectAllClick}
|
||||
inputProps={{
|
||||
"aria-label": "select all desserts"
|
||||
}}
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell>Permission</TableCell>
|
||||
<TableCell>Description</TableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{permissions
|
||||
.slice(
|
||||
page * rowsPerPage,
|
||||
page * rowsPerPage + rowsPerPage
|
||||
)
|
||||
.map((row, index) => {
|
||||
const isItemSelected = isSelected(row);
|
||||
const labelId = `enhanced-table-checkbox-${index}`;
|
||||
|
||||
return (
|
||||
<TableRow
|
||||
hover
|
||||
onClick={event => handleClick(event, row)}
|
||||
role="checkbox"
|
||||
aria-checked={isItemSelected}
|
||||
tabIndex={-1}
|
||||
key={row.name}
|
||||
selected={isItemSelected}
|
||||
>
|
||||
<TableCell padding="checkbox">
|
||||
<Checkbox
|
||||
checked={isItemSelected}
|
||||
inputProps={{ "aria-labelledby": labelId }}
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell id={labelId}>{row.name}</TableCell>
|
||||
<TableCell>{row.description}</TableCell>
|
||||
</TableRow>
|
||||
);
|
||||
})}
|
||||
{emptyRows > 0 && (
|
||||
<TableRow style={{ height: 33 * emptyRows }}>
|
||||
<TableCell colSpan={6} />
|
||||
</TableRow>
|
||||
)}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
<TablePagination
|
||||
rowsPerPageOptions={[5, 10, 25]}
|
||||
component="div"
|
||||
count={permissions.length}
|
||||
rowsPerPage={rowsPerPage}
|
||||
page={page}
|
||||
onChangePage={handleChangePage}
|
||||
onChangeRowsPerPage={handleChangeRowsPerPage}
|
||||
/>
|
||||
</div>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<br />
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<FormControlLabel
|
||||
control={
|
||||
<Switch onChange={handleChange("enabled")} value="checkedA" />
|
||||
}
|
||||
label="Enabled"
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<br />
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<Button
|
||||
type="submit"
|
||||
variant="contained"
|
||||
color="primary"
|
||||
disabled={addLoading}
|
||||
>
|
||||
Save
|
||||
</Button>
|
||||
</Grid>
|
||||
{addLoading && (
|
||||
<Grid item xs={12}>
|
||||
<LinearProgress />
|
||||
</Grid>
|
||||
)}
|
||||
</Grid>
|
||||
</form>
|
||||
</ModalWrapper>
|
||||
);
|
||||
}
|
||||
}
|
||||
<Grid item xs={12} className={classes.buttonContainer}>
|
||||
<Button
|
||||
type="submit"
|
||||
variant="contained"
|
||||
color="primary"
|
||||
disabled={addSending}
|
||||
>
|
||||
Create
|
||||
</Button>
|
||||
</Grid>
|
||||
{addSending && (
|
||||
<Grid item xs={12}>
|
||||
<LinearProgress />
|
||||
</Grid>
|
||||
)}
|
||||
</Grid>
|
||||
</form>
|
||||
</ModalWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
const AddServiceAccountWrapper = withStyles(styles)(AddServiceAccountContent);
|
||||
|
||||
interface IAddServiceAccountProps {
|
||||
open: boolean;
|
||||
closeModalAndRefresh: (res: NewServiceAccount | null) => void;
|
||||
selectedServiceAccount: ServiceAccount | null;
|
||||
}
|
||||
|
||||
interface IAddServiceAccountState {}
|
||||
|
||||
class AddServiceAccount extends React.Component<
|
||||
IAddServiceAccountProps,
|
||||
IAddServiceAccountState
|
||||
> {
|
||||
state: IAddServiceAccountState = {};
|
||||
|
||||
render() {
|
||||
return <AddServiceAccountWrapper {...this.props} />;
|
||||
}
|
||||
}
|
||||
|
||||
export default AddServiceAccount;
|
||||
export default withStyles(styles)(AddServiceAccount);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// This file is part of MinIO Console Server
|
||||
// Copyright (c) 2019 MinIO, Inc.
|
||||
// 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
|
||||
@@ -17,20 +17,19 @@
|
||||
import React from "react";
|
||||
import { createStyles, Theme, withStyles } from "@material-ui/core/styles";
|
||||
import { NewServiceAccount } from "./types";
|
||||
import {
|
||||
Button,
|
||||
DialogActions,
|
||||
DialogContent,
|
||||
DialogContentText
|
||||
} from "@material-ui/core";
|
||||
import { Button } from "@material-ui/core";
|
||||
import Typography from "@material-ui/core/Typography";
|
||||
import ModalWrapper from "../Common/ModalWrapper/ModalWrapper";
|
||||
import Grid from "@material-ui/core/Grid";
|
||||
|
||||
const styles = (theme: Theme) =>
|
||||
createStyles({
|
||||
errorBlock: {
|
||||
color: "red"
|
||||
}
|
||||
color: "red",
|
||||
},
|
||||
buttonContainer: {
|
||||
textAlign: "right",
|
||||
},
|
||||
});
|
||||
|
||||
interface ICredentialsPromptProps {
|
||||
@@ -40,76 +39,69 @@ interface ICredentialsPromptProps {
|
||||
closeModal: () => void;
|
||||
}
|
||||
|
||||
interface ICredentialsPromptState {}
|
||||
const download = (filename: string, text: string) => {
|
||||
let element = document.createElement("a");
|
||||
element.setAttribute(
|
||||
"href",
|
||||
"data:text/plain;charset=utf-8," + encodeURIComponent(text)
|
||||
);
|
||||
element.setAttribute("download", filename);
|
||||
|
||||
class CredentialsPrompt extends React.Component<
|
||||
ICredentialsPromptProps,
|
||||
ICredentialsPromptState
|
||||
> {
|
||||
state: ICredentialsPromptState = {};
|
||||
element.style.display = "none";
|
||||
document.body.appendChild(element);
|
||||
|
||||
download(filename: string, text: string) {
|
||||
var element = document.createElement("a");
|
||||
element.setAttribute(
|
||||
"href",
|
||||
"data:text/plain;charset=utf-8," + encodeURIComponent(text)
|
||||
);
|
||||
element.setAttribute("download", filename);
|
||||
element.click();
|
||||
|
||||
element.style.display = "none";
|
||||
document.body.appendChild(element);
|
||||
document.body.removeChild(element);
|
||||
};
|
||||
|
||||
element.click();
|
||||
|
||||
document.body.removeChild(element);
|
||||
const CredentialsPrompt = ({
|
||||
classes,
|
||||
newServiceAccount,
|
||||
open,
|
||||
closeModal,
|
||||
}: ICredentialsPromptProps) => {
|
||||
if (!newServiceAccount) {
|
||||
return null;
|
||||
}
|
||||
|
||||
render() {
|
||||
const { classes, open, newServiceAccount } = this.props;
|
||||
|
||||
if (newServiceAccount === null) {
|
||||
return <div />;
|
||||
}
|
||||
|
||||
return (
|
||||
<ModalWrapper
|
||||
modalOpen={open}
|
||||
onClose={() => {
|
||||
this.props.closeModal();
|
||||
}}
|
||||
title="New Service Account"
|
||||
>
|
||||
<React.Fragment>
|
||||
<DialogContent>
|
||||
<DialogContentText id="alert-dialog-description">
|
||||
A new service account has been created with the following details:
|
||||
<ul>
|
||||
<li>
|
||||
<b>Access Key:</b>{" "}
|
||||
{newServiceAccount.service_account.access_key}
|
||||
</li>
|
||||
<li>
|
||||
<b>Secret Key:</b> {newServiceAccount.secret_key}
|
||||
</li>
|
||||
</ul>
|
||||
<Typography
|
||||
component="p"
|
||||
variant="body1"
|
||||
className={classes.errorBlock}
|
||||
>
|
||||
Write these down, as this is the only time the secret will be
|
||||
displayed.
|
||||
</Typography>
|
||||
</DialogContentText>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
return (
|
||||
<ModalWrapper
|
||||
modalOpen={open}
|
||||
onClose={() => {
|
||||
closeModal();
|
||||
}}
|
||||
title="New Service Account Created"
|
||||
>
|
||||
<React.Fragment>
|
||||
<Grid container>
|
||||
<Grid item xs={12} className={classes.formScrollable}>
|
||||
A new service account has been created with the following details:
|
||||
<ul>
|
||||
<li>
|
||||
<b>Access Key:</b> {newServiceAccount.accessKey}
|
||||
</li>
|
||||
<li>
|
||||
<b>Secret Key:</b> {newServiceAccount.secretKey}
|
||||
</li>
|
||||
</ul>
|
||||
<Typography
|
||||
component="p"
|
||||
variant="body1"
|
||||
className={classes.errorBlock}
|
||||
>
|
||||
Write these down, as this is the only time the secret will be
|
||||
displayed.
|
||||
</Typography>
|
||||
</Grid>
|
||||
<Grid item xs={12} className={classes.buttonContainer}>
|
||||
<Button
|
||||
onClick={() => {
|
||||
this.download(
|
||||
download(
|
||||
"credentials.json",
|
||||
JSON.stringify({
|
||||
access_key: newServiceAccount.service_account.access_key,
|
||||
secret_key: newServiceAccount.secret_key
|
||||
access_key: newServiceAccount.accessKey,
|
||||
secret_key: newServiceAccount.secretKey,
|
||||
})
|
||||
);
|
||||
}}
|
||||
@@ -119,18 +111,18 @@ class CredentialsPrompt extends React.Component<
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => {
|
||||
this.props.closeModal();
|
||||
closeModal();
|
||||
}}
|
||||
color="secondary"
|
||||
autoFocus
|
||||
>
|
||||
Done
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</React.Fragment>
|
||||
</ModalWrapper>
|
||||
);
|
||||
}
|
||||
}
|
||||
</Grid>
|
||||
</Grid>
|
||||
</React.Fragment>
|
||||
</ModalWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
export default withStyles(styles)(CredentialsPrompt);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// This file is part of MinIO Console Server
|
||||
// Copyright (c) 2019 MinIO, Inc.
|
||||
// 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
|
||||
@@ -14,152 +14,117 @@
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import React, { useState, useEffect } from "react";
|
||||
import { createStyles, Theme, withStyles } from "@material-ui/core/styles";
|
||||
import React from "react";
|
||||
import Typography from "@material-ui/core/Typography";
|
||||
import {
|
||||
Button,
|
||||
Dialog,
|
||||
DialogActions,
|
||||
DialogContent,
|
||||
DialogContentText,
|
||||
DialogTitle,
|
||||
LinearProgress
|
||||
Button,
|
||||
Dialog,
|
||||
DialogActions,
|
||||
DialogContent,
|
||||
DialogContentText,
|
||||
DialogTitle,
|
||||
LinearProgress,
|
||||
} from "@material-ui/core";
|
||||
import api from "../../../common/api";
|
||||
import { ServiceAccount, ServiceAccountsList } from "./types";
|
||||
import Typography from "@material-ui/core/Typography";
|
||||
|
||||
const styles = (theme: Theme) =>
|
||||
createStyles({
|
||||
errorBlock: {
|
||||
color: "red"
|
||||
},
|
||||
wrapText: {
|
||||
maxWidth: "200px",
|
||||
whiteSpace: "normal",
|
||||
wordWrap: "break-word"
|
||||
}
|
||||
});
|
||||
createStyles({
|
||||
errorBlock: {
|
||||
color: "red",
|
||||
},
|
||||
wrapText: {
|
||||
maxWidth: "200px",
|
||||
whiteSpace: "normal",
|
||||
wordWrap: "break-word",
|
||||
},
|
||||
});
|
||||
|
||||
interface IDeleteServiceAccountProps {
|
||||
classes: any;
|
||||
closeDeleteModalAndRefresh: (refresh: boolean) => void;
|
||||
deleteOpen: boolean;
|
||||
selectedServiceAccount: ServiceAccount | null;
|
||||
classes: any;
|
||||
closeDeleteModalAndRefresh: (refresh: boolean) => void;
|
||||
deleteOpen: boolean;
|
||||
selectedServiceAccount: string | null;
|
||||
}
|
||||
|
||||
interface IDeleteServiceAccountState {
|
||||
deleteLoading: boolean;
|
||||
deleteError: string;
|
||||
}
|
||||
const DeleteServiceAccount = ({
|
||||
classes,
|
||||
closeDeleteModalAndRefresh,
|
||||
deleteOpen,
|
||||
selectedServiceAccount,
|
||||
}: IDeleteServiceAccountProps) => {
|
||||
const [deleteLoading, setDeleteLoading] = useState(false);
|
||||
const [deleteError, setDeleteError] = useState("");
|
||||
|
||||
class DeleteServiceAccount extends React.Component<
|
||||
IDeleteServiceAccountProps,
|
||||
IDeleteServiceAccountState
|
||||
> {
|
||||
state: IDeleteServiceAccountState = {
|
||||
deleteLoading: false,
|
||||
deleteError: ""
|
||||
};
|
||||
|
||||
removeRecord() {
|
||||
const { deleteLoading } = this.state;
|
||||
const { selectedServiceAccount } = this.props;
|
||||
if (deleteLoading) {
|
||||
return;
|
||||
}
|
||||
if (selectedServiceAccount == null) {
|
||||
return;
|
||||
}
|
||||
this.setState({ deleteLoading: true }, () => {
|
||||
api
|
||||
.invoke("DELETE", `/api/v1/service_accounts/${selectedServiceAccount.id}`, {
|
||||
id: selectedServiceAccount.id
|
||||
})
|
||||
.then((res: ServiceAccountsList) => {
|
||||
this.setState(
|
||||
{
|
||||
deleteLoading: false,
|
||||
deleteError: ""
|
||||
},
|
||||
() => {
|
||||
this.props.closeDeleteModalAndRefresh(true);
|
||||
}
|
||||
);
|
||||
})
|
||||
.catch(err => {
|
||||
this.setState({
|
||||
deleteLoading: false,
|
||||
deleteError: err
|
||||
});
|
||||
});
|
||||
useEffect(() => {
|
||||
if (deleteLoading) {
|
||||
api
|
||||
.invoke("DELETE", `/api/v1/service-accounts/${selectedServiceAccount}`)
|
||||
.then(() => {
|
||||
setDeleteLoading(false);
|
||||
setDeleteError("");
|
||||
closeDeleteModalAndRefresh(true);
|
||||
})
|
||||
.catch((err) => {
|
||||
setDeleteLoading(false);
|
||||
setDeleteError(err);
|
||||
});
|
||||
}
|
||||
}, [deleteLoading, closeDeleteModalAndRefresh, selectedServiceAccount]);
|
||||
|
||||
render() {
|
||||
const { classes, deleteOpen, selectedServiceAccount } = this.props;
|
||||
const { deleteLoading, deleteError } = this.state;
|
||||
|
||||
if (selectedServiceAccount === null) {
|
||||
return <div />;
|
||||
}
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
open={deleteOpen}
|
||||
onClose={() => {
|
||||
this.setState({ deleteError: "" }, () => {
|
||||
this.props.closeDeleteModalAndRefresh(false);
|
||||
});
|
||||
}}
|
||||
aria-labelledby="alert-dialog-title"
|
||||
aria-describedby="alert-dialog-description"
|
||||
>
|
||||
<DialogTitle id="alert-dialog-title">Delete ServiceAccount</DialogTitle>
|
||||
<DialogContent>
|
||||
{deleteLoading && <LinearProgress />}
|
||||
<DialogContentText id="alert-dialog-description">
|
||||
Are you sure you want to delete service account{" "}
|
||||
<b className={classes.wrapText}>{selectedServiceAccount.name}</b>?
|
||||
{deleteError !== "" && (
|
||||
<React.Fragment>
|
||||
<br />
|
||||
<Typography
|
||||
component="p"
|
||||
variant="body1"
|
||||
className={classes.errorBlock}
|
||||
>
|
||||
{deleteError}
|
||||
</Typography>
|
||||
</React.Fragment>
|
||||
)}
|
||||
</DialogContentText>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button
|
||||
onClick={() => {
|
||||
this.setState({ deleteError: "" }, () => {
|
||||
this.props.closeDeleteModalAndRefresh(false);
|
||||
});
|
||||
}}
|
||||
color="primary"
|
||||
disabled={deleteLoading}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => {
|
||||
this.removeRecord();
|
||||
}}
|
||||
color="secondary"
|
||||
autoFocus
|
||||
>
|
||||
Delete
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
);
|
||||
const removeRecord = () => {
|
||||
if (selectedServiceAccount == null) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
setDeleteLoading(true);
|
||||
};
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
open={deleteOpen}
|
||||
onClose={() => {
|
||||
closeDeleteModalAndRefresh(false);
|
||||
}}
|
||||
aria-labelledby="alert-dialog-title"
|
||||
aria-describedby="alert-dialog-description"
|
||||
>
|
||||
<DialogTitle id="alert-dialog-title">Delete ServiceAccount</DialogTitle>
|
||||
<DialogContent>
|
||||
{deleteLoading && <LinearProgress />}
|
||||
<DialogContentText id="alert-dialog-description">
|
||||
Are you sure you want to delete service account{" "}
|
||||
<b className={classes.wrapText}>{selectedServiceAccount}</b>?
|
||||
{deleteError !== "" && (
|
||||
<React.Fragment>
|
||||
<br />
|
||||
<Typography
|
||||
component="p"
|
||||
variant="body1"
|
||||
className={classes.errorBlock}
|
||||
>
|
||||
{deleteError}
|
||||
</Typography>
|
||||
</React.Fragment>
|
||||
)}
|
||||
</DialogContentText>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button
|
||||
onClick={() => {
|
||||
closeDeleteModalAndRefresh(false);
|
||||
}}
|
||||
color="primary"
|
||||
disabled={deleteLoading}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button onClick={removeRecord} color="secondary" autoFocus>
|
||||
Delete
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
|
||||
export default withStyles(styles)(DeleteServiceAccount);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// This file is part of MinIO Console Server
|
||||
// Copyright (c) 2019 MinIO, Inc.
|
||||
// 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
|
||||
@@ -14,401 +14,295 @@
|
||||
// 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 React, { useState, useEffect } from "react";
|
||||
import { createStyles, Theme, withStyles } from "@material-ui/core/styles";
|
||||
import Table from "@material-ui/core/Table";
|
||||
import TableBody from "@material-ui/core/TableBody";
|
||||
import TableCell from "@material-ui/core/TableCell";
|
||||
import TableHead from "@material-ui/core/TableHead";
|
||||
import TableRow from "@material-ui/core/TableRow";
|
||||
import Paper from "@material-ui/core/Paper";
|
||||
import Grid from "@material-ui/core/Grid";
|
||||
import api from "../../../common/api";
|
||||
import {
|
||||
Button,
|
||||
IconButton,
|
||||
LinearProgress,
|
||||
TableFooter,
|
||||
TablePagination
|
||||
} from "@material-ui/core";
|
||||
import { Button } from "@material-ui/core";
|
||||
import Typography from "@material-ui/core/Typography";
|
||||
import {
|
||||
NewServiceAccount,
|
||||
ServiceAccount,
|
||||
ServiceAccountsList
|
||||
} from "./types";
|
||||
import { NewServiceAccount } from "./types";
|
||||
import { MinTablePaginationActions } from "../../../common/MinTablePaginationActions";
|
||||
import EditIcon from "@material-ui/icons/Edit";
|
||||
import AddServiceAccount from "./AddServiceAccount";
|
||||
import DeleteServiceAccount from "./DeleteServiceAccount";
|
||||
import CredentialsPrompt from "./CredentialsPrompt";
|
||||
import { CreateIcon, DeleteIcon } from "../../../icons";
|
||||
import Checkbox from "@material-ui/core/Checkbox";
|
||||
import PlayArrowRoundedIcon from "@material-ui/icons/PlayArrowRounded";
|
||||
import { CreateIcon } from "../../../icons";
|
||||
import TextField from "@material-ui/core/TextField";
|
||||
import InputAdornment from "@material-ui/core/InputAdornment";
|
||||
import SearchIcon from "@material-ui/icons/Search";
|
||||
import TableWrapper from "../Common/TableWrapper/TableWrapper";
|
||||
import { stringSort } from "../../../utils/sortFunctions";
|
||||
|
||||
const styles = (theme: Theme) =>
|
||||
createStyles({
|
||||
seeMore: {
|
||||
marginTop: theme.spacing(3)
|
||||
marginTop: theme.spacing(3),
|
||||
},
|
||||
paper: {
|
||||
display: "flex",
|
||||
overflow: "auto",
|
||||
flexDirection: "column"
|
||||
flexDirection: "column",
|
||||
},
|
||||
addSideBar: {
|
||||
width: "480px",
|
||||
minWidth: "320px",
|
||||
padding: "20px"
|
||||
padding: "20px",
|
||||
},
|
||||
errorBlock: {
|
||||
color: "red"
|
||||
color: "red",
|
||||
},
|
||||
tableToolbar: {
|
||||
paddingLeft: theme.spacing(2),
|
||||
paddingRight: theme.spacing(0)
|
||||
paddingRight: theme.spacing(0),
|
||||
},
|
||||
wrapCell: {
|
||||
maxWidth: "200px",
|
||||
whiteSpace: "normal",
|
||||
wordWrap: "break-word"
|
||||
wordWrap: "break-word",
|
||||
},
|
||||
minTableHeader: {
|
||||
color: "#393939",
|
||||
"& tr": {
|
||||
"& th": {
|
||||
fontWeight: "bold"
|
||||
}
|
||||
}
|
||||
fontWeight: "bold",
|
||||
},
|
||||
},
|
||||
},
|
||||
imageIcon: {
|
||||
height: "100%"
|
||||
height: "100%",
|
||||
},
|
||||
iconRoot: {
|
||||
textAlign: "center"
|
||||
textAlign: "center",
|
||||
},
|
||||
actionsTray: {
|
||||
textAlign: "right",
|
||||
"& button": {
|
||||
marginLeft: 10
|
||||
}
|
||||
marginLeft: 10,
|
||||
},
|
||||
},
|
||||
searchField: {
|
||||
background: "#FFFFFF",
|
||||
padding: 12,
|
||||
borderRadius: 5,
|
||||
boxShadow: "0px 3px 6px #00000012"
|
||||
}
|
||||
boxShadow: "0px 3px 6px #00000012",
|
||||
},
|
||||
});
|
||||
|
||||
interface IServiceAccountsProps {
|
||||
classes: any;
|
||||
}
|
||||
|
||||
interface IServiceAccountsState {
|
||||
records: ServiceAccount[];
|
||||
totalRecords: number;
|
||||
loading: boolean;
|
||||
error: string;
|
||||
deleteError: string;
|
||||
addScreenOpen: boolean;
|
||||
page: number;
|
||||
rowsPerPage: number;
|
||||
deleteOpen: boolean;
|
||||
selectedServiceAccount: ServiceAccount | null;
|
||||
showNewCredentials: boolean;
|
||||
newServiceAccount: NewServiceAccount | null;
|
||||
}
|
||||
const ServiceAccounts = ({ classes }: IServiceAccountsProps) => {
|
||||
const [records, setRecords] = useState<string[]>([]);
|
||||
const [loading, setLoading] = useState<boolean>(false);
|
||||
const [error, setError] = useState<string>("");
|
||||
const [filter, setFilter] = useState<string>("");
|
||||
const [addScreenOpen, setAddScreenOpen] = useState<boolean>(false);
|
||||
const [page, setPage] = useState<number>(0);
|
||||
const [rowsPerPage, setRowsPerPage] = useState<number>(10);
|
||||
const [deleteOpen, setDeleteOpen] = useState<boolean>(false);
|
||||
const [selectedServiceAccount, setSelectedServiceAccount] = useState<
|
||||
string | null
|
||||
>(null);
|
||||
const [showNewCredentials, setShowNewCredentials] = useState<boolean>(false);
|
||||
const [
|
||||
newServiceAccount,
|
||||
setNewServiceAccount,
|
||||
] = useState<NewServiceAccount | null>(null);
|
||||
|
||||
class ServiceAccounts extends React.Component<
|
||||
IServiceAccountsProps,
|
||||
IServiceAccountsState
|
||||
> {
|
||||
state: IServiceAccountsState = {
|
||||
records: [],
|
||||
totalRecords: 0,
|
||||
loading: false,
|
||||
error: "",
|
||||
deleteError: "",
|
||||
addScreenOpen: false,
|
||||
page: 0,
|
||||
rowsPerPage: 10,
|
||||
deleteOpen: false,
|
||||
selectedServiceAccount: null,
|
||||
showNewCredentials: false,
|
||||
newServiceAccount: null
|
||||
};
|
||||
useEffect(() => {
|
||||
fetchRecords();
|
||||
}, []);
|
||||
|
||||
fetchRecords() {
|
||||
this.setState({ loading: true }, () => {
|
||||
const { page, rowsPerPage } = this.state;
|
||||
const offset = page * rowsPerPage;
|
||||
useEffect(() => {
|
||||
if (loading) {
|
||||
api
|
||||
.invoke(
|
||||
"GET",
|
||||
`/api/v1/service_accounts?offset=${offset}&limit=${rowsPerPage}`
|
||||
)
|
||||
.then((res: ServiceAccountsList) => {
|
||||
this.setState({
|
||||
loading: false,
|
||||
records: res.service_accounts,
|
||||
totalRecords: res.total,
|
||||
error: ""
|
||||
});
|
||||
.invoke("GET", `/api/v1/service-accounts`)
|
||||
.then((res: string[]) => {
|
||||
const serviceAccounts = res.sort(stringSort);
|
||||
|
||||
setLoading(false);
|
||||
setRecords(serviceAccounts);
|
||||
setError("");
|
||||
|
||||
// if we get 0 results, and page > 0 , go down 1 page
|
||||
if (
|
||||
(res.service_accounts === undefined ||
|
||||
res.service_accounts == null ||
|
||||
res.service_accounts.length === 0) &&
|
||||
page > 0
|
||||
) {
|
||||
if ((!serviceAccounts || serviceAccounts.length === 0) && page > 0) {
|
||||
const newPage = page - 1;
|
||||
this.setState({ page: newPage }, () => {
|
||||
this.fetchRecords();
|
||||
});
|
||||
setPage(newPage);
|
||||
fetchRecords();
|
||||
}
|
||||
})
|
||||
.catch(err => {
|
||||
this.setState({ loading: false, error: err });
|
||||
.catch((err) => {
|
||||
setError(err);
|
||||
setLoading(false);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
closeAddModalAndRefresh(res: NewServiceAccount | null) {
|
||||
this.setState({ addScreenOpen: false }, () => {
|
||||
this.fetchRecords();
|
||||
});
|
||||
if (res !== null) {
|
||||
this.setState({ showNewCredentials: true, newServiceAccount: res });
|
||||
}
|
||||
}
|
||||
}, [loading, setLoading, setRecords, setError, page, setPage]);
|
||||
|
||||
closeDeleteModalAndRefresh(refresh: boolean) {
|
||||
this.setState({ deleteOpen: false }, () => {
|
||||
if (refresh) {
|
||||
this.fetchRecords();
|
||||
}
|
||||
});
|
||||
}
|
||||
const fetchRecords = () => {
|
||||
setLoading(true);
|
||||
};
|
||||
|
||||
componentDidMount(): void {
|
||||
this.fetchRecords();
|
||||
}
|
||||
const closeAddModalAndRefresh = (res: NewServiceAccount | null) => {
|
||||
setAddScreenOpen(false);
|
||||
fetchRecords();
|
||||
|
||||
closeCredentialsModal() {
|
||||
this.setState({ showNewCredentials: false, newServiceAccount: null });
|
||||
}
|
||||
if (res !== null) {
|
||||
setShowNewCredentials(true);
|
||||
setNewServiceAccount(res);
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
const { classes } = this.props;
|
||||
const {
|
||||
records,
|
||||
totalRecords,
|
||||
addScreenOpen,
|
||||
loading,
|
||||
page,
|
||||
rowsPerPage,
|
||||
deleteOpen,
|
||||
selectedServiceAccount,
|
||||
showNewCredentials,
|
||||
newServiceAccount
|
||||
} = this.state;
|
||||
const closeDeleteModalAndRefresh = (refresh: boolean) => {
|
||||
setDeleteOpen(false);
|
||||
|
||||
const handleChangePage = (event: unknown, newPage: number) => {
|
||||
this.setState({ page: newPage }, () => {
|
||||
this.fetchRecords();
|
||||
});
|
||||
};
|
||||
if (refresh) {
|
||||
fetchRecords();
|
||||
}
|
||||
};
|
||||
|
||||
const handleChangeRowsPerPage = (
|
||||
event: React.ChangeEvent<HTMLInputElement>
|
||||
) => {
|
||||
const rPP = parseInt(event.target.value, 10);
|
||||
this.setState({ page: 0, rowsPerPage: rPP }, () => {
|
||||
this.fetchRecords();
|
||||
});
|
||||
};
|
||||
const closeCredentialsModal = () => {
|
||||
setShowNewCredentials(false);
|
||||
setNewServiceAccount(null);
|
||||
};
|
||||
|
||||
const confirmDeleteServiceAccount = (
|
||||
selectedServiceAccount: ServiceAccount
|
||||
) => {
|
||||
this.setState({
|
||||
deleteOpen: true,
|
||||
selectedServiceAccount: selectedServiceAccount
|
||||
});
|
||||
};
|
||||
const handleChangePage = (event: unknown, newPage: number) => {
|
||||
setPage(newPage);
|
||||
};
|
||||
|
||||
const editServiceAccount = (selectedServiceAccount: ServiceAccount) => {
|
||||
this.setState({
|
||||
addScreenOpen: true,
|
||||
selectedServiceAccount: selectedServiceAccount
|
||||
});
|
||||
};
|
||||
const handleChangeRowsPerPage = (
|
||||
event: React.ChangeEvent<HTMLInputElement>
|
||||
) => {
|
||||
const rPP = parseInt(event.target.value, 10);
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
{addScreenOpen && (
|
||||
<AddServiceAccount
|
||||
open={addScreenOpen}
|
||||
selectedServiceAccount={selectedServiceAccount}
|
||||
closeModalAndRefresh={(res: NewServiceAccount | null) => {
|
||||
this.closeAddModalAndRefresh(res);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{deleteOpen && (
|
||||
<DeleteServiceAccount
|
||||
deleteOpen={deleteOpen}
|
||||
selectedServiceAccount={selectedServiceAccount}
|
||||
closeDeleteModalAndRefresh={(refresh: boolean) => {
|
||||
this.closeDeleteModalAndRefresh(refresh);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{showNewCredentials && (
|
||||
<CredentialsPrompt
|
||||
newServiceAccount={newServiceAccount}
|
||||
open={showNewCredentials}
|
||||
closeModal={() => {
|
||||
this.closeCredentialsModal();
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
<Grid container>
|
||||
<Grid item xs={12}>
|
||||
<Typography variant="h6">Service Accounts</Typography>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<br />
|
||||
</Grid>
|
||||
<Grid item xs={12} className={classes.actionsTray}>
|
||||
<TextField
|
||||
placeholder="Search Service Accounts"
|
||||
className={classes.searchField}
|
||||
id="search-resource"
|
||||
label=""
|
||||
InputProps={{
|
||||
disableUnderline: true,
|
||||
startAdornment: (
|
||||
<InputAdornment position="start">
|
||||
<SearchIcon />
|
||||
</InputAdornment>
|
||||
)
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
variant="contained"
|
||||
color="primary"
|
||||
startIcon={<CreateIcon />}
|
||||
onClick={() => {
|
||||
this.setState({
|
||||
addScreenOpen: true,
|
||||
selectedServiceAccount: null
|
||||
});
|
||||
}}
|
||||
>
|
||||
Create service account
|
||||
</Button>
|
||||
<Button
|
||||
variant="contained"
|
||||
color="primary"
|
||||
startIcon={<PlayArrowRoundedIcon />}
|
||||
onClick={() => {
|
||||
this.setState({
|
||||
addScreenOpen: true
|
||||
});
|
||||
}}
|
||||
>
|
||||
Change Access
|
||||
</Button>
|
||||
</Grid>
|
||||
setPage(0);
|
||||
setRowsPerPage(rPP);
|
||||
};
|
||||
|
||||
<Grid item xs={12}>
|
||||
<br />
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<Paper className={classes.paper}>
|
||||
{loading && <LinearProgress />}
|
||||
{records != null && records.length > 0 ? (
|
||||
<Table size="medium">
|
||||
<TableHead className={classes.minTableHeader}>
|
||||
<TableRow>
|
||||
<TableCell>Select</TableCell>
|
||||
<TableCell>Name</TableCell>
|
||||
<TableCell>Access Key</TableCell>
|
||||
<TableCell>Status</TableCell>
|
||||
<TableCell align="right">Actions</TableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{records.map(row => (
|
||||
<TableRow key={row.name}>
|
||||
<TableCell padding="checkbox">
|
||||
<Checkbox
|
||||
value="secondary"
|
||||
color="primary"
|
||||
inputProps={{ "aria-label": "secondary checkbox" }}
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell className={classes.wrapCell}>
|
||||
{row.name}
|
||||
</TableCell>
|
||||
<TableCell>{row.access_key}</TableCell>
|
||||
<TableCell>
|
||||
{row.enabled ? "enabled" : "disabled"}
|
||||
</TableCell>
|
||||
<TableCell align="right">
|
||||
<IconButton
|
||||
aria-label="edit"
|
||||
onClick={() => {
|
||||
editServiceAccount(row);
|
||||
}}
|
||||
>
|
||||
<EditIcon />
|
||||
</IconButton>
|
||||
<IconButton
|
||||
aria-label="delete"
|
||||
onClick={() => {
|
||||
confirmDeleteServiceAccount(row);
|
||||
}}
|
||||
>
|
||||
<DeleteIcon />
|
||||
</IconButton>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
<TableFooter>
|
||||
<TableRow>
|
||||
<TablePagination
|
||||
rowsPerPageOptions={[5, 10, 25]}
|
||||
colSpan={4}
|
||||
count={totalRecords}
|
||||
rowsPerPage={rowsPerPage}
|
||||
page={page}
|
||||
SelectProps={{
|
||||
inputProps: { "aria-label": "rows per page" },
|
||||
native: true
|
||||
}}
|
||||
onChangePage={handleChangePage}
|
||||
onChangeRowsPerPage={handleChangeRowsPerPage}
|
||||
ActionsComponent={MinTablePaginationActions}
|
||||
/>
|
||||
</TableRow>
|
||||
</TableFooter>
|
||||
</Table>
|
||||
) : (
|
||||
<div>No Service Accounts</div>
|
||||
)}
|
||||
</Paper>
|
||||
</Grid>
|
||||
const confirmDeleteServiceAccount = (selectedServiceAccount: string) => {
|
||||
setSelectedServiceAccount(selectedServiceAccount);
|
||||
setDeleteOpen(true);
|
||||
};
|
||||
|
||||
const tableActions = [
|
||||
{ type: "delete", onClick: confirmDeleteServiceAccount },
|
||||
];
|
||||
|
||||
const filteredRecords = records.filter((elementItem) =>
|
||||
elementItem.toLowerCase().includes(filter.toLowerCase())
|
||||
);
|
||||
|
||||
const beginRecord = page * rowsPerPage;
|
||||
const endRecords = beginRecord + rowsPerPage;
|
||||
|
||||
const paginatedRecords = filteredRecords.slice(beginRecord, endRecords);
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
{addScreenOpen && (
|
||||
<AddServiceAccount
|
||||
open={addScreenOpen}
|
||||
closeModalAndRefresh={(res: NewServiceAccount | null) => {
|
||||
closeAddModalAndRefresh(res);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{deleteOpen && (
|
||||
<DeleteServiceAccount
|
||||
deleteOpen={deleteOpen}
|
||||
selectedServiceAccount={selectedServiceAccount}
|
||||
closeDeleteModalAndRefresh={(refresh: boolean) => {
|
||||
closeDeleteModalAndRefresh(refresh);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{showNewCredentials && (
|
||||
<CredentialsPrompt
|
||||
newServiceAccount={newServiceAccount}
|
||||
open={showNewCredentials}
|
||||
closeModal={() => {
|
||||
closeCredentialsModal();
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
<Grid container>
|
||||
<Grid item xs={12}>
|
||||
<Typography variant="h6">Service Accounts</Typography>
|
||||
</Grid>
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
}
|
||||
<Grid item xs={12}>
|
||||
<br />
|
||||
</Grid>
|
||||
<Grid item xs={12} className={classes.actionsTray}>
|
||||
<TextField
|
||||
placeholder="Search Service Accounts"
|
||||
className={classes.searchField}
|
||||
id="search-resource"
|
||||
label=""
|
||||
InputProps={{
|
||||
disableUnderline: true,
|
||||
startAdornment: (
|
||||
<InputAdornment position="start">
|
||||
<SearchIcon />
|
||||
</InputAdornment>
|
||||
),
|
||||
}}
|
||||
onChange={(e) => {
|
||||
setFilter(e.target.value);
|
||||
setPage(0);
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
variant="contained"
|
||||
color="primary"
|
||||
startIcon={<CreateIcon />}
|
||||
onClick={() => {
|
||||
setAddScreenOpen(true);
|
||||
setSelectedServiceAccount(null);
|
||||
}}
|
||||
>
|
||||
Create service account
|
||||
</Button>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<br />
|
||||
</Grid>
|
||||
{error !== "" && (
|
||||
<Grid item xs={12}>
|
||||
<Typography
|
||||
component="p"
|
||||
variant="body1"
|
||||
className={classes.errorBlock}
|
||||
>
|
||||
{error}
|
||||
</Typography>
|
||||
</Grid>
|
||||
)}
|
||||
<Grid item xs={12}>
|
||||
<TableWrapper
|
||||
isLoading={loading}
|
||||
records={paginatedRecords}
|
||||
entityName={"Service Accounts"}
|
||||
idField={""}
|
||||
columns={[{ label: "Service Account", elementKey: "" }]}
|
||||
itemActions={tableActions}
|
||||
paginatorConfig={{
|
||||
rowsPerPageOptions: [5, 10, 25],
|
||||
colSpan: 4,
|
||||
count: records.length,
|
||||
rowsPerPage: rowsPerPage,
|
||||
page,
|
||||
SelectProps: {
|
||||
inputProps: { "aria-label": "rows per page" },
|
||||
native: true,
|
||||
},
|
||||
onChangePage: handleChangePage,
|
||||
onChangeRowsPerPage: handleChangeRowsPerPage,
|
||||
ActionsComponent: MinTablePaginationActions,
|
||||
}}
|
||||
/>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</React.Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
export default withStyles(styles)(ServiceAccounts);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// This file is part of MinIO Console Server
|
||||
// Copyright (c) 2019 MinIO, Inc.
|
||||
// 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
|
||||
@@ -14,27 +14,12 @@
|
||||
// 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 {Permission} from "../Permissions/types";
|
||||
|
||||
export interface ServiceAccount {
|
||||
id: string;
|
||||
name: string;
|
||||
slug: string;
|
||||
access_key: string;
|
||||
enabled: boolean;
|
||||
}
|
||||
|
||||
export interface ServiceAccountsList {
|
||||
service_accounts: ServiceAccount[];
|
||||
total:number;
|
||||
service_accounts: string[];
|
||||
total: number;
|
||||
}
|
||||
|
||||
export interface NewServiceAccount {
|
||||
service_account: ServiceAccount,
|
||||
secret_key:string,
|
||||
}
|
||||
|
||||
export interface ServiceAccountDetails {
|
||||
service_account: ServiceAccount,
|
||||
permissions: Permission[],
|
||||
accessKey: string;
|
||||
secretKey: string;
|
||||
}
|
||||
|
||||
@@ -31,15 +31,15 @@ const styles = (theme: Theme) =>
|
||||
overflow: "auto",
|
||||
"& ul": {
|
||||
margin: "4px",
|
||||
padding: "0px"
|
||||
padding: "0px",
|
||||
},
|
||||
"& ul li": {
|
||||
listStyle: "none",
|
||||
margin: "0px",
|
||||
padding: "0px",
|
||||
borderBottom: "1px solid #dedede"
|
||||
}
|
||||
}
|
||||
borderBottom: "1px solid #dedede",
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
interface ITrace {
|
||||
@@ -53,7 +53,7 @@ const Trace = ({
|
||||
classes,
|
||||
traceMessageReceived,
|
||||
traceResetMessages,
|
||||
messages
|
||||
messages,
|
||||
}: ITrace) => {
|
||||
useEffect(() => {
|
||||
traceResetMessages();
|
||||
@@ -96,7 +96,7 @@ const Trace = ({
|
||||
<h1>Trace</h1>
|
||||
<div className={classes.logList}>
|
||||
<ul>
|
||||
{messages.map(m => {
|
||||
{messages.map((m) => {
|
||||
return (
|
||||
<li key={m.key}>
|
||||
{timeFromDate(m.time)} - {m.api}[{m.statusCode} {m.statusMsg}]{" "}
|
||||
@@ -113,12 +113,12 @@ const Trace = ({
|
||||
};
|
||||
|
||||
const mapState = (state: AppState) => ({
|
||||
messages: state.trace.messages
|
||||
messages: state.trace.messages,
|
||||
});
|
||||
|
||||
const connector = connect(mapState, {
|
||||
traceMessageReceived: traceMessageReceived,
|
||||
traceResetMessages: traceResetMessages
|
||||
traceResetMessages: traceResetMessages,
|
||||
});
|
||||
|
||||
export default connector(withStyles(styles)(Trace));
|
||||
|
||||
@@ -35,12 +35,12 @@ export type TraceActionTypes =
|
||||
export function traceMessageReceived(message: TraceMessage) {
|
||||
return {
|
||||
type: TRACE_MESSAGE_RECEIVED,
|
||||
message: message
|
||||
message: message,
|
||||
};
|
||||
}
|
||||
|
||||
export function traceResetMessages() {
|
||||
return {
|
||||
type: TRACE_RESET_MESSAGES
|
||||
type: TRACE_RESET_MESSAGES,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
import {
|
||||
TRACE_MESSAGE_RECEIVED,
|
||||
TRACE_RESET_MESSAGES,
|
||||
TraceActionTypes
|
||||
TraceActionTypes,
|
||||
} from "./actions";
|
||||
import { TraceMessage } from "./types";
|
||||
|
||||
@@ -26,7 +26,7 @@ export interface TraceState {
|
||||
}
|
||||
|
||||
const initialState: TraceState = {
|
||||
messages: []
|
||||
messages: [],
|
||||
};
|
||||
|
||||
export function traceReducer(
|
||||
@@ -37,12 +37,12 @@ export function traceReducer(
|
||||
case TRACE_MESSAGE_RECEIVED:
|
||||
return {
|
||||
...state,
|
||||
messages: [...state.messages, action.message]
|
||||
messages: [...state.messages, action.message],
|
||||
};
|
||||
case TRACE_RESET_MESSAGES:
|
||||
return {
|
||||
...state,
|
||||
messages: []
|
||||
messages: [],
|
||||
};
|
||||
default:
|
||||
return state;
|
||||
|
||||
@@ -24,7 +24,7 @@ import InputAdornment from "@material-ui/core/InputAdornment";
|
||||
import SearchIcon from "@material-ui/icons/Search";
|
||||
import TextField from "@material-ui/core/TextField";
|
||||
import api from "../../../common/api";
|
||||
import { groupsSort } from "../../../utils/sortFunctions";
|
||||
import { stringSort } from "../../../utils/sortFunctions";
|
||||
import { GroupsList } from "../Groups/types";
|
||||
import get from "lodash/get";
|
||||
import TableWrapper from "../Common/TableWrapper/TableWrapper";
|
||||
@@ -38,43 +38,43 @@ interface IGroupsProps {
|
||||
const styles = (theme: Theme) =>
|
||||
createStyles({
|
||||
seeMore: {
|
||||
marginTop: theme.spacing(3)
|
||||
marginTop: theme.spacing(3),
|
||||
},
|
||||
paper: {
|
||||
// padding: theme.spacing(2),
|
||||
display: "flex",
|
||||
overflow: "auto",
|
||||
flexDirection: "column"
|
||||
flexDirection: "column",
|
||||
},
|
||||
addSideBar: {
|
||||
width: "320px",
|
||||
padding: "20px"
|
||||
padding: "20px",
|
||||
},
|
||||
errorBlock: {
|
||||
color: "red"
|
||||
color: "red",
|
||||
},
|
||||
tableToolbar: {
|
||||
paddingLeft: theme.spacing(2),
|
||||
paddingRight: theme.spacing(0)
|
||||
paddingRight: theme.spacing(0),
|
||||
},
|
||||
wrapCell: {
|
||||
maxWidth: "200px",
|
||||
whiteSpace: "normal",
|
||||
wordWrap: "break-word"
|
||||
wordWrap: "break-word",
|
||||
},
|
||||
minTableHeader: {
|
||||
color: "#393939",
|
||||
"& tr": {
|
||||
"& th": {
|
||||
fontWeight: "bold"
|
||||
}
|
||||
}
|
||||
fontWeight: "bold",
|
||||
},
|
||||
},
|
||||
},
|
||||
actionsTray: {
|
||||
textAlign: "left",
|
||||
"& button": {
|
||||
marginLeft: 10
|
||||
}
|
||||
marginLeft: 10,
|
||||
},
|
||||
},
|
||||
filterField: {
|
||||
background: "#FFFFFF",
|
||||
@@ -82,24 +82,24 @@ const styles = (theme: Theme) =>
|
||||
borderRadius: 5,
|
||||
boxShadow: "0px 3px 6px #00000012",
|
||||
width: "100%",
|
||||
zIndex: 500
|
||||
zIndex: 500,
|
||||
},
|
||||
noFound: {
|
||||
textAlign: "center",
|
||||
padding: "10px 0"
|
||||
padding: "10px 0",
|
||||
},
|
||||
tableContainer: {
|
||||
maxHeight: 200
|
||||
maxHeight: 200,
|
||||
},
|
||||
stickyHeader: {
|
||||
backgroundColor: "#fff"
|
||||
}
|
||||
backgroundColor: "#fff",
|
||||
},
|
||||
});
|
||||
|
||||
const GroupsSelectors = ({
|
||||
classes,
|
||||
selectedGroups,
|
||||
setSelectedGroups
|
||||
setSelectedGroups,
|
||||
}: IGroupsProps) => {
|
||||
// Local State
|
||||
const [records, setRecords] = useState<any[]>([]);
|
||||
@@ -129,11 +129,11 @@ const GroupsSelectors = ({
|
||||
if (!groups) {
|
||||
groups = [];
|
||||
}
|
||||
setRecords(groups.sort(groupsSort));
|
||||
setRecords(groups.sort(stringSort));
|
||||
setError("");
|
||||
isLoading(false);
|
||||
})
|
||||
.catch(err => {
|
||||
.catch((err) => {
|
||||
setError(err);
|
||||
isLoading(false);
|
||||
});
|
||||
@@ -151,14 +151,14 @@ const GroupsSelectors = ({
|
||||
elements.push(value);
|
||||
} else {
|
||||
// User has unchecked this field, we need to remove it from the list
|
||||
elements = elements.filter(element => element !== value);
|
||||
elements = elements.filter((element) => element !== value);
|
||||
}
|
||||
setSelectedGroups(elements);
|
||||
|
||||
return elements;
|
||||
};
|
||||
|
||||
const filteredRecords = records.filter(elementItem =>
|
||||
const filteredRecords = records.filter((elementItem) =>
|
||||
elementItem.includes(filter)
|
||||
);
|
||||
|
||||
@@ -183,9 +183,9 @@ const GroupsSelectors = ({
|
||||
<InputAdornment position="start">
|
||||
<SearchIcon />
|
||||
</InputAdornment>
|
||||
)
|
||||
),
|
||||
}}
|
||||
onChange={e => {
|
||||
onChange={(e) => {
|
||||
setFilter(e.target.value);
|
||||
}}
|
||||
/>
|
||||
|
||||
@@ -22,7 +22,7 @@ import {
|
||||
Grid,
|
||||
Typography,
|
||||
TextField,
|
||||
InputAdornment
|
||||
InputAdornment,
|
||||
} from "@material-ui/core";
|
||||
import SearchIcon from "@material-ui/icons/Search";
|
||||
import GroupIcon from "@material-ui/icons/Group";
|
||||
@@ -38,50 +38,50 @@ import TableWrapper from "../Common/TableWrapper/TableWrapper";
|
||||
const styles = (theme: Theme) =>
|
||||
createStyles({
|
||||
seeMore: {
|
||||
marginTop: theme.spacing(3)
|
||||
marginTop: theme.spacing(3),
|
||||
},
|
||||
paper: {
|
||||
// padding: theme.spacing(2),
|
||||
display: "flex",
|
||||
overflow: "auto",
|
||||
flexDirection: "column"
|
||||
flexDirection: "column",
|
||||
},
|
||||
addSideBar: {
|
||||
width: "320px",
|
||||
padding: "20px"
|
||||
padding: "20px",
|
||||
},
|
||||
errorBlock: {
|
||||
color: "red"
|
||||
color: "red",
|
||||
},
|
||||
tableToolbar: {
|
||||
paddingLeft: theme.spacing(2),
|
||||
paddingRight: theme.spacing(0)
|
||||
paddingRight: theme.spacing(0),
|
||||
},
|
||||
wrapCell: {
|
||||
maxWidth: "200px",
|
||||
whiteSpace: "normal",
|
||||
wordWrap: "break-word"
|
||||
wordWrap: "break-word",
|
||||
},
|
||||
minTableHeader: {
|
||||
color: "#393939",
|
||||
"& tr": {
|
||||
"& th": {
|
||||
fontWeight: "bold"
|
||||
}
|
||||
}
|
||||
fontWeight: "bold",
|
||||
},
|
||||
},
|
||||
},
|
||||
actionsTray: {
|
||||
textAlign: "right",
|
||||
"& button": {
|
||||
marginLeft: 10
|
||||
}
|
||||
marginLeft: 10,
|
||||
},
|
||||
},
|
||||
searchField: {
|
||||
background: "#FFFFFF",
|
||||
padding: 12,
|
||||
borderRadius: 5,
|
||||
boxShadow: "0px 3px 6px #00000012"
|
||||
}
|
||||
boxShadow: "0px 3px 6px #00000012",
|
||||
},
|
||||
});
|
||||
|
||||
interface IUsersProps {
|
||||
@@ -118,7 +118,7 @@ class Users extends React.Component<IUsersProps, IUsersState> {
|
||||
selectedUser: null,
|
||||
addGroupOpen: false,
|
||||
filter: "",
|
||||
checkedUsers: []
|
||||
checkedUsers: [],
|
||||
};
|
||||
|
||||
fetchRecords() {
|
||||
@@ -133,7 +133,7 @@ class Users extends React.Component<IUsersProps, IUsersState> {
|
||||
loading: false,
|
||||
records: users.sort(usersSort),
|
||||
totalRecords: users.length,
|
||||
error: ""
|
||||
error: "",
|
||||
});
|
||||
// if we get 0 results, and page > 0 , go down 1 page
|
||||
if ((!users || users.length === 0) && page > 0) {
|
||||
@@ -143,7 +143,7 @@ class Users extends React.Component<IUsersProps, IUsersState> {
|
||||
});
|
||||
}
|
||||
})
|
||||
.catch(err => {
|
||||
.catch((err) => {
|
||||
this.setState({ loading: false, error: err });
|
||||
});
|
||||
});
|
||||
@@ -191,7 +191,7 @@ class Users extends React.Component<IUsersProps, IUsersState> {
|
||||
selectedUser,
|
||||
filter,
|
||||
checkedUsers,
|
||||
addGroupOpen
|
||||
addGroupOpen,
|
||||
} = this.state;
|
||||
|
||||
const handleChangePage = (event: unknown, newPage: number) => {
|
||||
@@ -205,7 +205,7 @@ class Users extends React.Component<IUsersProps, IUsersState> {
|
||||
this.setState({ page: 0, rowsPerPage: rPP });
|
||||
};
|
||||
|
||||
const filteredRecords = records.filter(elementItem =>
|
||||
const filteredRecords = records.filter((elementItem) =>
|
||||
elementItem.accessKey.includes(filter)
|
||||
);
|
||||
|
||||
@@ -226,11 +226,11 @@ class Users extends React.Component<IUsersProps, IUsersState> {
|
||||
elements.push(value);
|
||||
} else {
|
||||
// User has unchecked this field, we need to remove it from the list
|
||||
elements = elements.filter(element => element !== value);
|
||||
elements = elements.filter((element) => element !== value);
|
||||
}
|
||||
|
||||
this.setState({
|
||||
checkedUsers: elements
|
||||
checkedUsers: elements,
|
||||
});
|
||||
|
||||
return elements;
|
||||
@@ -239,20 +239,20 @@ class Users extends React.Component<IUsersProps, IUsersState> {
|
||||
const viewAction = (selectionElement: any): void => {
|
||||
this.setState({
|
||||
addScreenOpen: true,
|
||||
selectedUser: selectionElement
|
||||
selectedUser: selectionElement,
|
||||
});
|
||||
};
|
||||
|
||||
const deleteAction = (selectionElement: any): void => {
|
||||
this.setState({
|
||||
deleteOpen: true,
|
||||
selectedUser: selectionElement
|
||||
selectedUser: selectionElement,
|
||||
});
|
||||
};
|
||||
|
||||
const tableActions = [
|
||||
{ type: "view", onClick: viewAction },
|
||||
{ type: "delete", onClick: deleteAction }
|
||||
{ type: "delete", onClick: deleteAction },
|
||||
];
|
||||
|
||||
return (
|
||||
@@ -304,9 +304,9 @@ class Users extends React.Component<IUsersProps, IUsersState> {
|
||||
<InputAdornment position="start">
|
||||
<SearchIcon />
|
||||
</InputAdornment>
|
||||
)
|
||||
),
|
||||
}}
|
||||
onChange={e => {
|
||||
onChange={(e) => {
|
||||
this.setState({ filter: e.target.value, page: 0 });
|
||||
}}
|
||||
/>
|
||||
@@ -318,7 +318,7 @@ class Users extends React.Component<IUsersProps, IUsersState> {
|
||||
onClick={() => {
|
||||
if (checkedUsers.length > 0) {
|
||||
this.setState({
|
||||
addGroupOpen: true
|
||||
addGroupOpen: true,
|
||||
});
|
||||
}
|
||||
}}
|
||||
@@ -332,7 +332,7 @@ class Users extends React.Component<IUsersProps, IUsersState> {
|
||||
onClick={() => {
|
||||
this.setState({
|
||||
addScreenOpen: true,
|
||||
selectedUser: null
|
||||
selectedUser: null,
|
||||
});
|
||||
}}
|
||||
>
|
||||
@@ -361,11 +361,11 @@ class Users extends React.Component<IUsersProps, IUsersState> {
|
||||
page: page,
|
||||
SelectProps: {
|
||||
inputProps: { "aria-label": "rows per page" },
|
||||
native: true
|
||||
native: true,
|
||||
},
|
||||
onChangePage: handleChangePage,
|
||||
onChangeRowsPerPage: handleChangeRowsPerPage,
|
||||
ActionsComponent: MinTablePaginationActions
|
||||
ActionsComponent: MinTablePaginationActions,
|
||||
}}
|
||||
/>
|
||||
</Grid>
|
||||
|
||||
@@ -14,12 +14,7 @@
|
||||
// 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 {
|
||||
Button,
|
||||
Grid,
|
||||
Typography,
|
||||
TextField
|
||||
} from "@material-ui/core";
|
||||
import { Button, Grid, Typography, TextField } from "@material-ui/core";
|
||||
import { IMessageEvent, w3cwebsocket as W3CWebSocket } from "websocket";
|
||||
import { AppState } from "../../../store";
|
||||
import { connect } from "react-redux";
|
||||
@@ -29,11 +24,7 @@ import { createStyles, Theme, withStyles } from "@material-ui/core/styles";
|
||||
import { niceBytes, timeFromDate } from "../../../common/utils";
|
||||
import { wsProtocol } from "../../../utils/wsUtils";
|
||||
import api from "../../../common/api";
|
||||
import {
|
||||
FormControl,
|
||||
MenuItem,
|
||||
Select,
|
||||
} from "@material-ui/core";
|
||||
import { FormControl, MenuItem, Select } from "@material-ui/core";
|
||||
|
||||
const styles = (theme: Theme) =>
|
||||
createStyles({
|
||||
@@ -43,27 +34,27 @@ const styles = (theme: Theme) =>
|
||||
overflow: "auto",
|
||||
"& ul": {
|
||||
margin: "4px",
|
||||
padding: "0px"
|
||||
padding: "0px",
|
||||
},
|
||||
"& ul li": {
|
||||
listStyle: "none",
|
||||
margin: "0px",
|
||||
padding: "0px",
|
||||
borderBottom: "1px solid #dedede"
|
||||
}
|
||||
borderBottom: "1px solid #dedede",
|
||||
},
|
||||
},
|
||||
actionsTray: {
|
||||
textAlign: "right",
|
||||
"& button": {
|
||||
marginLeft: 10,
|
||||
}
|
||||
},
|
||||
},
|
||||
inputField: {
|
||||
background: "#FFFFFF",
|
||||
padding: 12,
|
||||
borderRadius: 5,
|
||||
marginLeft: 10,
|
||||
boxShadow: "0px 3px 6px #00000012"
|
||||
boxShadow: "0px 3px 6px #00000012",
|
||||
},
|
||||
fieldContainer: {
|
||||
background: "#FFFFFF",
|
||||
@@ -72,8 +63,8 @@ const styles = (theme: Theme) =>
|
||||
marginLeft: 10,
|
||||
textAlign: "left",
|
||||
minWidth: "206px",
|
||||
boxShadow: "0px 3px 6px #00000012"
|
||||
}
|
||||
boxShadow: "0px 3px 6px #00000012",
|
||||
},
|
||||
});
|
||||
|
||||
interface IWatch {
|
||||
@@ -87,7 +78,7 @@ const Watch = ({
|
||||
classes,
|
||||
watchMessageReceived,
|
||||
watchResetMessages,
|
||||
messages
|
||||
messages,
|
||||
}: IWatch) => {
|
||||
const [start, setStart] = useState(false);
|
||||
const [bucketName, setBucketName] = useState("Select Bucket");
|
||||
@@ -108,7 +99,7 @@ const Watch = ({
|
||||
.catch((err: any) => {
|
||||
console.log(err);
|
||||
});
|
||||
}
|
||||
};
|
||||
useEffect(() => {
|
||||
fetchBucketList();
|
||||
}, []);
|
||||
@@ -116,13 +107,15 @@ const Watch = ({
|
||||
useEffect(() => {
|
||||
watchResetMessages();
|
||||
// begin watch if bucketName in bucketList and start pressed
|
||||
if (start && bucketList.some(bucket => bucket.name === bucketName)) {
|
||||
if (start && bucketList.some((bucket) => bucket.name === bucketName)) {
|
||||
const url = new URL(window.location.toString());
|
||||
const isDev = process.env.NODE_ENV === "development";
|
||||
const port = isDev ? "9090" : url.port;
|
||||
|
||||
const wsProt = wsProtocol(url.protocol);
|
||||
const c = new W3CWebSocket(`${wsProt}://${url.hostname}:${port}/ws/watch/${bucketName}?prefix=${prefix}&suffix=${suffix}`);
|
||||
const c = new W3CWebSocket(
|
||||
`${wsProt}://${url.hostname}:${port}/ws/watch/${bucketName}?prefix=${prefix}&suffix=${suffix}`
|
||||
);
|
||||
|
||||
let interval: any | null = null;
|
||||
if (c !== null) {
|
||||
@@ -156,9 +149,9 @@ const Watch = ({
|
||||
}
|
||||
}, [watchMessageReceived, start]);
|
||||
|
||||
const bucketNames = bucketList.map(bucketName => ({
|
||||
const bucketNames = bucketList.map((bucketName) => ({
|
||||
label: bucketName.name,
|
||||
value: bucketName.name
|
||||
value: bucketName.name,
|
||||
}));
|
||||
return (
|
||||
<React.Fragment>
|
||||
@@ -175,7 +168,9 @@ const Watch = ({
|
||||
id="bucket-name"
|
||||
name="bucket-name"
|
||||
value={bucketName}
|
||||
onChange={(e) => { setBucketName(e.target.value as string) }}
|
||||
onChange={(e) => {
|
||||
setBucketName(e.target.value as string);
|
||||
}}
|
||||
className={classes.fieldContainer}
|
||||
disabled={start}
|
||||
>
|
||||
@@ -185,8 +180,8 @@ const Watch = ({
|
||||
disabled={true}
|
||||
>
|
||||
Select Bucket
|
||||
</MenuItem>
|
||||
{bucketNames.map(option => (
|
||||
</MenuItem>
|
||||
{bucketNames.map((option) => (
|
||||
<MenuItem
|
||||
value={option.value}
|
||||
key={`select-bucket-name-${option.label}`}
|
||||
@@ -205,7 +200,9 @@ const Watch = ({
|
||||
InputProps={{
|
||||
disableUnderline: true,
|
||||
}}
|
||||
onChange={(e) => { setPrefix(e.target.value) }}
|
||||
onChange={(e) => {
|
||||
setPrefix(e.target.value);
|
||||
}}
|
||||
/>
|
||||
<TextField
|
||||
placeholder="Suffix"
|
||||
@@ -216,7 +213,9 @@ const Watch = ({
|
||||
InputProps={{
|
||||
disableUnderline: true,
|
||||
}}
|
||||
onChange={(e) => { setSuffix(e.target.value) }}
|
||||
onChange={(e) => {
|
||||
setSuffix(e.target.value);
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
type="submit"
|
||||
@@ -226,7 +225,7 @@ const Watch = ({
|
||||
onClick={() => setStart(true)}
|
||||
>
|
||||
Start
|
||||
</Button>
|
||||
</Button>
|
||||
</Grid>
|
||||
<Grid item xs={12}>
|
||||
<br />
|
||||
@@ -234,10 +233,11 @@ const Watch = ({
|
||||
</Grid>
|
||||
<div className={classes.watchList}>
|
||||
<ul>
|
||||
{messages.map(m => {
|
||||
{messages.map((m) => {
|
||||
return (
|
||||
<li key={m.key}>
|
||||
{timeFromDate(m.Time)} - {niceBytes(m.Size + "")} - {m.Type} - {m.Path}
|
||||
{timeFromDate(m.Time)} - {niceBytes(m.Size + "")} - {m.Type} -{" "}
|
||||
{m.Path}
|
||||
</li>
|
||||
);
|
||||
})}
|
||||
@@ -248,12 +248,12 @@ const Watch = ({
|
||||
};
|
||||
|
||||
const mapState = (state: AppState) => ({
|
||||
messages: state.watch.messages
|
||||
messages: state.watch.messages,
|
||||
});
|
||||
|
||||
const connector = connect(mapState, {
|
||||
watchMessageReceived: watchMessageReceived,
|
||||
watchResetMessages: watchResetMessages
|
||||
watchResetMessages: watchResetMessages,
|
||||
});
|
||||
|
||||
export default connector(withStyles(styles)(Watch));
|
||||
|
||||
@@ -28,7 +28,6 @@ interface WatchResetMessagesAction {
|
||||
type: typeof WATCH_RESET_MESSAGES;
|
||||
}
|
||||
|
||||
|
||||
export type WatchActionTypes =
|
||||
| WatchMessageReceivedAction
|
||||
| WatchResetMessagesAction;
|
||||
@@ -36,12 +35,12 @@ export type WatchActionTypes =
|
||||
export function watchMessageReceived(message: EventInfo) {
|
||||
return {
|
||||
type: WATCH_MESSAGE_RECEIVED,
|
||||
message: message
|
||||
message: message,
|
||||
};
|
||||
}
|
||||
|
||||
export function watchResetMessages() {
|
||||
return {
|
||||
type: WATCH_RESET_MESSAGES
|
||||
type: WATCH_RESET_MESSAGES,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
import {
|
||||
WATCH_MESSAGE_RECEIVED,
|
||||
WATCH_RESET_MESSAGES,
|
||||
WatchActionTypes
|
||||
WatchActionTypes,
|
||||
} from "./actions";
|
||||
import { EventInfo } from "./types";
|
||||
|
||||
@@ -26,7 +26,7 @@ export interface WatchState {
|
||||
}
|
||||
|
||||
const initialState: WatchState = {
|
||||
messages: []
|
||||
messages: [],
|
||||
};
|
||||
|
||||
export function watchReducer(
|
||||
@@ -37,12 +37,12 @@ export function watchReducer(
|
||||
case WATCH_MESSAGE_RECEIVED:
|
||||
return {
|
||||
...state,
|
||||
messages: [...state.messages, action.message]
|
||||
messages: [...state.messages, action.message],
|
||||
};
|
||||
case WATCH_RESET_MESSAGES:
|
||||
return {
|
||||
...state,
|
||||
messages: []
|
||||
messages: [],
|
||||
};
|
||||
default:
|
||||
return state;
|
||||
|
||||
@@ -35,8 +35,8 @@ const styles = (theme: Theme) =>
|
||||
createStyles({
|
||||
"@global": {
|
||||
body: {
|
||||
backgroundColor: "#F4F4F4"
|
||||
}
|
||||
backgroundColor: "#F4F4F4",
|
||||
},
|
||||
},
|
||||
paper: {
|
||||
marginTop: theme.spacing(16),
|
||||
@@ -45,48 +45,48 @@ const styles = (theme: Theme) =>
|
||||
flexDirection: "column",
|
||||
alignItems: "center",
|
||||
width: "800px",
|
||||
margin: "auto"
|
||||
margin: "auto",
|
||||
},
|
||||
avatar: {
|
||||
margin: theme.spacing(1),
|
||||
backgroundColor: theme.palette.secondary.main
|
||||
backgroundColor: theme.palette.secondary.main,
|
||||
},
|
||||
form: {
|
||||
width: "100%", // Fix IE 11 issue.
|
||||
marginTop: theme.spacing(3)
|
||||
marginTop: theme.spacing(3),
|
||||
},
|
||||
submit: {
|
||||
margin: theme.spacing(3, 0, 2)
|
||||
margin: theme.spacing(3, 0, 2),
|
||||
},
|
||||
errorBlock: {
|
||||
color: "red"
|
||||
color: "red",
|
||||
},
|
||||
mainContainer: {
|
||||
borderRadius: "3px"
|
||||
borderRadius: "3px",
|
||||
},
|
||||
theOcean: {
|
||||
borderTopLeftRadius: "3px",
|
||||
borderBottomLeftRadius: "3px",
|
||||
background:
|
||||
"transparent linear-gradient(333deg, #281B6F 1%, #271260 13%, #120D53 83%) 0% 0% no-repeat padding-box;"
|
||||
"transparent linear-gradient(333deg, #281B6F 1%, #271260 13%, #120D53 83%) 0% 0% no-repeat padding-box;",
|
||||
},
|
||||
oceanBg: {
|
||||
backgroundImage: "url(/images/BG_Illustration.svg)",
|
||||
backgroundRepeat: "no-repeat",
|
||||
backgroundPosition: "bottom left",
|
||||
height: "100%",
|
||||
width: "100%"
|
||||
width: "100%",
|
||||
},
|
||||
theLogin: {
|
||||
padding: "76px 62px 20px 62px"
|
||||
padding: "76px 62px 20px 62px",
|
||||
},
|
||||
loadingLoginStrategy: {
|
||||
textAlign: "center"
|
||||
}
|
||||
textAlign: "center",
|
||||
},
|
||||
});
|
||||
|
||||
const mapState = (state: SystemState) => ({
|
||||
loggedIn: state.loggedIn
|
||||
loggedIn: state.loggedIn,
|
||||
});
|
||||
|
||||
const connector = connect(mapState, { userLoggedIn });
|
||||
@@ -117,8 +117,8 @@ class Login extends React.Component<ILoginProps, ILoginState> {
|
||||
loading: false,
|
||||
loginStrategy: {
|
||||
loginStrategy: "",
|
||||
redirect: ""
|
||||
}
|
||||
redirect: "",
|
||||
},
|
||||
};
|
||||
|
||||
fetchConfiguration() {
|
||||
@@ -127,12 +127,12 @@ class Login extends React.Component<ILoginProps, ILoginState> {
|
||||
.invoke("GET", "/api/v1/login")
|
||||
.then((loginDetails: ILoginDetails) => {
|
||||
this.setState({
|
||||
loading: false
|
||||
loading: false,
|
||||
});
|
||||
this.setState({
|
||||
loading: false,
|
||||
loginStrategy: loginDetails,
|
||||
error: ""
|
||||
error: "",
|
||||
});
|
||||
})
|
||||
.catch((err: any) => {
|
||||
@@ -169,7 +169,7 @@ class Login extends React.Component<ILoginProps, ILoginState> {
|
||||
// therefore after login we need to use window.location redirect
|
||||
window.location.href = "/";
|
||||
})
|
||||
.catch(err => {
|
||||
.catch((err) => {
|
||||
this.setState({ error: `${err}` });
|
||||
});
|
||||
};
|
||||
|
||||
@@ -29,7 +29,7 @@ export const usersSort = (a: userInterface, b: userInterface) => {
|
||||
return 0;
|
||||
};
|
||||
|
||||
export const groupsSort = (a: string, b: string) => {
|
||||
export const stringSort = (a: string, b: string) => {
|
||||
if (a > b) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user