diff --git a/web/locales/en/plugin__netobserv-plugin.json b/web/locales/en/plugin__netobserv-plugin.json index 7b99bb977..adbf2371d 100644 --- a/web/locales/en/plugin__netobserv-plugin.json +++ b/web/locales/en/plugin__netobserv-plugin.json @@ -168,21 +168,25 @@ "Namespaces": "Namespaces", "Pods": "Pods", "Recommendations": "Recommendations", + "The example outlined in the table demonstrates a scenario that is tailored to your workload. Consider this example only as a baseline from which adjustments can be made to accommodate your needs.": "The example outlined in the table demonstrates a scenario that is tailored to your workload. Consider this example only as a baseline from which adjustments can be made to accommodate your needs.", "vCPU": "vCPU", "Memory": "Memory", "LokiStack size": "LokiStack size", "Kafka": "Kafka", - "Estimation": "Estimation", - "Sampling": "Sampling", - "(current)": "(current)", + "Performance tuning and estimation": "Performance tuning and estimation", + "The sampling interval is one of the main settings used to balance performance and accuracy. The lower the interval, the higher the accuracy.": "The sampling interval is one of the main settings used to balance performance and accuracy. The lower the interval, the higher the accuracy.", + "Use the slider below to configure the desired sampling interval.": "Use the slider below to configure the desired sampling interval.", + "Sampling interval": "Sampling interval", + "The estimations are based on the number of nodes in the cluster and the sampling rate. They do not take into account the number of namespaces or pods, as their impact is comparatively lower than that of nodes.\nThey are calculated using a linear regression model based on data collected from various OpenShift clusters. Actual resource consumption may vary depending on your specific workload and cluster configuration.": "The estimations are based on the number of nodes in the cluster and the sampling rate. They do not take into account the number of namespaces or pods, as their impact is comparatively lower than that of nodes.\nThey are calculated using a linear regression model based on data collected from various OpenShift clusters. Actual resource consumption may vary depending on your specific workload and cluster configuration.", "There is some issue in this form view. Please select \"YAML view\" for full control.": "There is some issue in this form view. Please select \"YAML view\" for full control.", "Note: Some fields may not be represented in this form view. Please select \"YAML view\" for full control.": "Note: Some fields may not be represented in this form view. Please select \"YAML view\" for full control.", + "(see more...)": "(see more...)", "Remove {{singularLabel}}": "Remove {{singularLabel}}", "Add {{singularLabel}}": "Add {{singularLabel}}", "Error": "Error", "Fix the following errors:": "Fix the following errors:", - "Enabled": "Enabled", - "Disabled": "Disabled", + "True": "True", + "False": "False", "Select {{title}}": "Select {{title}}", "Configure via:": "Configure via:", "Form view": "Form view", @@ -201,20 +205,22 @@ "Create FlowCollector": "Create FlowCollector", "Network Observability FlowCollector setup": "Network Observability FlowCollector setup", "Overview": "Overview", - "Network Observability Operator deploys a monitoring pipeline that consists in:\n - an eBPF agent, that generates network flows from captured packets\n - flowlogs-pipeline, a component that collects, enriches and exports these flows\n - a Console plugin for flows visualization with powerful filtering options, a topology representation and more\n\nFlow data is then available in multiple ways, each optional:\n - As Prometheus metrics\n - As raw flow logs stored in Grafana Loki\n - As raw flow logs exported to a collector\n\nThe FlowCollector resource is used to configure the operator and its managed components.\nThis setup will guide you on the common aspects of the FlowCollector configuration.": "Network Observability Operator deploys a monitoring pipeline that consists in:\n - an eBPF agent, that generates network flows from captured packets\n - flowlogs-pipeline, a component that collects, enriches and exports these flows\n - a Console plugin for flows visualization with powerful filtering options, a topology representation and more\n\nFlow data is then available in multiple ways, each optional:\n - As Prometheus metrics\n - As raw flow logs stored in Grafana Loki\n - As raw flow logs exported to a collector\n\nThe FlowCollector resource is used to configure the operator and its managed components.\nThis setup will guide you on the common aspects of the FlowCollector configuration.", + "The FlowCollector resource is used to configure the Network Observability operator and its managed components. When it is created, network flows start being collected.": "The FlowCollector resource is used to configure the Network Observability operator and its managed components. When it is created, network flows start being collected.", + "This wizard is a helper to create a first FlowCollector resource. It does not cover all the available configuration options, but only the most common ones.\nFor advanced configuration, please use YAML or the": "This wizard is a helper to create a first FlowCollector resource. It does not cover all the available configuration options, but only the most common ones.\nFor advanced configuration, please use YAML or the", + "FlowCollector form": "FlowCollector form", + ", which includes more options such as:\n- Filtering options\n- Configuring custom exporters\n- Custom labels based on IP\n- Pod identification for secondary networks\n- Performance fine-tuning\nYou can always edit a FlowCollector later when you start with the simplified configuration.": ", which includes more options such as:\n- Filtering options\n- Configuring custom exporters\n- Custom labels based on IP\n- Pod identification for secondary networks\n- Performance fine-tuning\nYou can always edit a FlowCollector later when you start with the simplified configuration.", "Operator configuration": "Operator configuration", - "Capture": "Capture", - "Pipeline": "Pipeline", - "Storage": "Storage", - "Integration": "Integration", + "Processing": "Processing", "Consumption": "Consumption", - "Review": "Review", + "Submit": "Submit", "Network Observability FlowMetric setup": "Network Observability FlowMetric setup", - "You can create custom metrics out of the flowlogs data using the FlowMetric API. In every flowlogs data that is collected, there are a number of fields labeled per log, such as source name and destination name. These fields can be leveraged as Prometheus labels to enable the customization of cluster information on your dashboard.\nThis setup will guide you on the common aspects of the FlowMetric configuration.": "You can create custom metrics out of the flowlogs data using the FlowMetric API. In every flowlogs data that is collected, there are a number of fields labeled per log, such as source name and destination name. These fields can be leveraged as Prometheus labels to enable the customization of cluster information on your dashboard.\nThis setup will guide you on the common aspects of the FlowMetric configuration.", - "General configuration": "General configuration", + "You can create custom metrics out of the network flows using the FlowMetric API. A FlowCollector resource must be created as well in order to produce the flows. Each flow consists in a set of fields with values, such as source name and destination name. These fields can be leveraged as Prometheus labels to enable customized metrics and dashboards.": "You can create custom metrics out of the network flows using the FlowMetric API. A FlowCollector resource must be created as well in order to produce the flows. Each flow consists in a set of fields with values, such as source name and destination name. These fields can be leveraged as Prometheus labels to enable customized metrics and dashboards.", + "This simplified setup guides you through the common aspects of the FlowMetric configuration. For advanced configuration, please use YAML or the ": "This simplified setup guides you through the common aspects of the FlowMetric configuration. For advanced configuration, please use YAML or the ", + "FlowMetric form": "FlowMetric form", + "Resource configuration": "Resource configuration", "Metric": "Metric", "Data": "Data", - "Charts": "Charts", + "Review": "Review", "Update {{kind}}": "Update {{kind}}", "Create {{kind}}": "Create {{kind}}", "Update by completing the form. Current values are from the existing resource.": "Update by completing the form. Current values are from the existing resource.", @@ -400,6 +406,7 @@ "from": "from", "in": "in", "Configuration": "Configuration", + "Sampling": "Sampling", "Max chunk age": "Max chunk age", "Version": "Version", "Number": "Number", diff --git a/web/src/components/forms/config/uiSchema.ts b/web/src/components/forms/config/uiSchema.ts index 4879a00b8..d0ffc10f0 100644 --- a/web/src/components/forms/config/uiSchema.ts +++ b/web/src/components/forms/config/uiSchema.ts @@ -45,16 +45,16 @@ export const FlowCollectorUISchema: UiSchema = { }, caCert: { 'ui:title': 'CA certificate', - 'ui:order': ['certFile', 'certKey', 'name', 'namespace', 'type'] + 'ui:order': ['certFile', 'certKey', 'name', 'namespace', 'type', '*'] }, userCert: { 'ui:title': 'User certificate when using mTLS', - 'ui:order': ['certFile', 'certKey', 'name', 'namespace', 'type'] + 'ui:order': ['certFile', 'certKey', 'name', 'namespace', 'type', '*'] }, insecureSkipVerify: { 'ui:title': 'Insecure skip verify' }, - 'ui:order': ['enable', 'caCert', 'userCert', 'insecureSkipVerify'] + 'ui:order': ['enable', 'caCert', 'userCert', 'insecureSkipVerify', '*'] }, sasl: { 'ui:title': 'SASL', @@ -63,15 +63,15 @@ export const FlowCollectorUISchema: UiSchema = { }, clientIDReference: { 'ui:title': 'Client ID reference', - 'ui:order': ['file', 'name', 'namespace', 'type'] + 'ui:order': ['file', 'name', 'namespace', 'type', '*'] }, clientSecretReference: { 'ui:title': 'Client secret reference', - 'ui:order': ['file', 'name', 'namespace', 'type'] + 'ui:order': ['file', 'name', 'namespace', 'type', '*'] }, - 'ui:order': ['type', 'clientIDReference', 'clientSecretReference'] + 'ui:order': ['type', 'clientIDReference', 'clientSecretReference', '*'] }, - 'ui:order': ['address', 'topic', 'tls', 'sasl'] + 'ui:order': ['address', 'topic', 'tls', 'sasl', '*'] }, agent: { 'ui:title': 'Agent configuration', @@ -213,18 +213,18 @@ export const FlowCollectorUISchema: UiSchema = { port: { 'ui:title': 'Port' }, - 'ui:order': ['port', 'tls'], + 'ui:order': ['port', 'tls', '*'], tls: { - 'ui:order': ['type', 'insecureSkipVerify', 'provided', 'providedCaFile'], + 'ui:order': ['type', 'insecureSkipVerify', 'provided', 'providedCaFile', '*'], provided: { - 'ui:order': ['certFile', 'certKey', 'name', 'namespace', 'type'] + 'ui:order': ['certFile', 'certKey', 'name', 'namespace', 'type', '*'] }, providedCaFile: { - 'ui:order': ['file', 'name', 'namespace', 'type'] + 'ui:order': ['file', 'name', 'namespace', 'type', '*'] } } }, - 'ui:order': ['enable', 'disableAlerts', 'server'] + 'ui:order': ['enable', 'disableAlerts', 'server', '*'] }, cacheMaxFlows: { 'ui:title': 'Cache max flows' @@ -243,21 +243,21 @@ export const FlowCollectorUISchema: UiSchema = { resources: { 'ui:title': 'Resource Requirements', 'ui:widget': 'hidden', - 'ui:order': ['claims', 'limits', 'requests'], + 'ui:order': ['claims', 'limits', 'requests', '*'], claims: { items: { - 'ui:order': ['name', 'request'] + 'ui:order': ['name', 'request', '*'] } } }, advanced: { 'ui:title': 'Advanced configuration', 'ui:widget': 'hidden', - 'ui:order': ['env', 'scheduling'], + 'ui:order': ['env', 'scheduling', '*'], scheduling: { 'ui:widget': 'hidden', affinity: { - 'ui:order': ['nodeAffinity', 'podAffinity', 'podAntiAffinity'], + 'ui:order': ['nodeAffinity', 'podAffinity', 'podAntiAffinity', '*'], nodeAffinity: { 'ui:order': [ 'preferredDuringSchedulingIgnoredDuringExecution', @@ -265,17 +265,17 @@ export const FlowCollectorUISchema: UiSchema = { ], preferredDuringSchedulingIgnoredDuringExecution: { items: { - 'ui:order': ['preference', 'weight'], + 'ui:order': ['preference', 'weight', '*'], preference: { - 'ui:order': ['matchExpressions', 'matchFields'], + 'ui:order': ['matchExpressions', 'matchFields', '*'], matchExpressions: { items: { - 'ui:order': ['key', 'operator', 'values'] + 'ui:order': ['key', 'operator', 'values', '*'] } }, matchFields: { items: { - 'ui:order': ['key', 'operator', 'values'] + 'ui:order': ['key', 'operator', 'values', '*'] } } } @@ -284,15 +284,15 @@ export const FlowCollectorUISchema: UiSchema = { requiredDuringSchedulingIgnoredDuringExecution: { nodeSelectorTerms: { items: { - 'ui:order': ['matchExpressions', 'matchFields'], + 'ui:order': ['matchExpressions', 'matchFields', '*'], matchExpressions: { items: { - 'ui:order': ['key', 'operator', 'values'] + 'ui:order': ['key', 'operator', 'values', '*'] } }, matchFields: { items: { - 'ui:order': ['key', 'operator', 'values'] + 'ui:order': ['key', 'operator', 'values', '*'] } } } @@ -306,7 +306,7 @@ export const FlowCollectorUISchema: UiSchema = { ], preferredDuringSchedulingIgnoredDuringExecution: { items: { - 'ui:order': ['podAffinityTerm', 'weight'], + 'ui:order': ['podAffinityTerm', 'weight', '*'], podAffinityTerm: { 'ui:order': [ 'topologyKey', @@ -317,18 +317,18 @@ export const FlowCollectorUISchema: UiSchema = { 'namespaces' ], labelSelector: { - 'ui:order': ['matchExpressions', 'matchLabels'], + 'ui:order': ['matchExpressions', 'matchLabels', '*'], matchExpressions: { items: { - 'ui:order': ['key', 'operator', 'values'] + 'ui:order': ['key', 'operator', 'values', '*'] } } }, namespaceSelector: { - 'ui:order': ['matchExpressions', 'matchLabels'], + 'ui:order': ['matchExpressions', 'matchLabels', '*'], matchExpressions: { items: { - 'ui:order': ['key', 'operator', 'values'] + 'ui:order': ['key', 'operator', 'values', '*'] } } } @@ -346,18 +346,18 @@ export const FlowCollectorUISchema: UiSchema = { 'namespaces' ], labelSelector: { - 'ui:order': ['matchExpressions', 'matchLabels'], + 'ui:order': ['matchExpressions', 'matchLabels', '*'], matchExpressions: { items: { - 'ui:order': ['key', 'operator', 'values'] + 'ui:order': ['key', 'operator', 'values', '*'] } } }, namespaceSelector: { - 'ui:order': ['matchExpressions', 'matchLabels'], + 'ui:order': ['matchExpressions', 'matchLabels', '*'], matchExpressions: { items: { - 'ui:order': ['key', 'operator', 'values'] + 'ui:order': ['key', 'operator', 'values', '*'] } } } @@ -371,7 +371,7 @@ export const FlowCollectorUISchema: UiSchema = { ], preferredDuringSchedulingIgnoredDuringExecution: { items: { - 'ui:order': ['podAffinityTerm', 'weight'], + 'ui:order': ['podAffinityTerm', 'weight', '*'], podAffinityTerm: { 'ui:order': [ 'topologyKey', @@ -382,18 +382,18 @@ export const FlowCollectorUISchema: UiSchema = { 'namespaces' ], labelSelector: { - 'ui:order': ['matchExpressions', 'matchLabels'], + 'ui:order': ['matchExpressions', 'matchLabels', '*'], matchExpressions: { items: { - 'ui:order': ['key', 'operator', 'values'] + 'ui:order': ['key', 'operator', 'values', '*'] } } }, namespaceSelector: { - 'ui:order': ['matchExpressions', 'matchLabels'], + 'ui:order': ['matchExpressions', 'matchLabels', '*'], matchExpressions: { items: { - 'ui:order': ['key', 'operator', 'values'] + 'ui:order': ['key', 'operator', 'values', '*'] } } } @@ -411,18 +411,18 @@ export const FlowCollectorUISchema: UiSchema = { 'namespaces' ], labelSelector: { - 'ui:order': ['matchExpressions', 'matchLabels'], + 'ui:order': ['matchExpressions', 'matchLabels', '*'], matchExpressions: { items: { - 'ui:order': ['key', 'operator', 'values'] + 'ui:order': ['key', 'operator', 'values', '*'] } } }, namespaceSelector: { - 'ui:order': ['matchExpressions', 'matchLabels'], + 'ui:order': ['matchExpressions', 'matchLabels', '*'], matchExpressions: { items: { - 'ui:order': ['key', 'operator', 'values'] + 'ui:order': ['key', 'operator', 'values', '*'] } } } @@ -432,10 +432,10 @@ export const FlowCollectorUISchema: UiSchema = { }, tolerations: { items: { - 'ui:order': ['effect', 'key', 'operator', 'tolerationSeconds', 'value'] + 'ui:order': ['effect', 'key', 'operator', 'tolerationSeconds', 'value', '*'] } }, - 'ui:order': ['affinity', 'nodeSelector', 'priorityClassName', 'tolerations'] + 'ui:order': ['affinity', 'nodeSelector', 'priorityClassName', 'tolerations', '*'] } }, 'ui:order': [ @@ -455,7 +455,7 @@ export const FlowCollectorUISchema: UiSchema = { 'advanced' ] }, - 'ui:order': ['ipfix', 'type', 'ebpf'] + 'ui:order': ['ipfix', 'type', 'ebpf', '*'] }, processor: { 'ui:title': 'Processor configuration', @@ -463,10 +463,10 @@ export const FlowCollectorUISchema: UiSchema = { 'ui:title': 'Filters', 'ui:widget': 'hidden', items: { - 'ui:order': ['allOf', 'outputTarget', 'sampling'], + 'ui:order': ['allOf', 'outputTarget', 'sampling', '*'], allOf: { items: { - 'ui:order': ['field', 'matchType', 'value'] + 'ui:order': ['field', 'matchType', 'value', '*'] } } } @@ -493,10 +493,10 @@ export const FlowCollectorUISchema: UiSchema = { customLabels: { 'ui:title': 'Custom labels', items: { - 'ui:order': ['cidrs', 'name'] + 'ui:order': ['cidrs', 'name', '*'] } }, - 'ui:order': ['openShiftAutoDetect', 'customLabels'] + 'ui:order': ['openShiftAutoDetect', 'customLabels', '*'] }, logTypes: { 'ui:title': 'Log types', @@ -516,7 +516,7 @@ export const FlowCollectorUISchema: UiSchema = { sampling: { 'ui:title': 'Sampling' }, - 'ui:order': ['mode', 'sampling'] + 'ui:order': ['mode', 'sampling', '*'] }, kafkaConsumerQueueCapacity: { 'ui:title': 'Kafka consumer queue capacity', @@ -533,74 +533,74 @@ export const FlowCollectorUISchema: UiSchema = { controlFieldValue: 'Kafka', controlFieldName: 'deploymentModel' }, - 'ui:order': ['maxReplicas', 'metrics', 'minReplicas', 'status'], + 'ui:order': ['maxReplicas', 'metrics', 'minReplicas', 'status', '*'], metrics: { items: { - 'ui:order': ['type', 'containerResource', 'external', 'object', 'pods', 'resource'], + 'ui:order': ['type', 'containerResource', 'external', 'object', 'pods', 'resource', '*'], containerResource: { - 'ui:order': ['container', 'name', 'target'], + 'ui:order': ['container', 'name', 'target', '*'], target: { - 'ui:order': ['type', 'averageUtilization', 'averageValue', 'value'] + 'ui:order': ['type', 'averageUtilization', 'averageValue', 'value', '*'] } }, external: { - 'ui:order': ['metric', 'target'], + 'ui:order': ['metric', 'target', '*'], metric: { - 'ui:order': ['name', 'selector'], + 'ui:order': ['name', 'selector', '*'], selector: { - 'ui:order': ['matchExpressions', 'matchLabels'], + 'ui:order': ['matchExpressions', 'matchLabels', '*'], matchExpressions: { items: { - 'ui:order': ['key', 'operator', 'values'] + 'ui:order': ['key', 'operator', 'values', '*'] } } } }, target: { - 'ui:order': ['type', 'averageUtilization', 'averageValue', 'value'] + 'ui:order': ['type', 'averageUtilization', 'averageValue', 'value', '*'] } }, object: { - 'ui:order': ['describedObject', 'metric', 'target'], + 'ui:order': ['describedObject', 'metric', 'target', '*'], describedObject: { - 'ui:order': ['kind', 'name', 'apiVersion'] + 'ui:order': ['kind', 'name', 'apiVersion', '*'] }, metric: { - 'ui:order': ['name', 'selector'], + 'ui:order': ['name', 'selector', '*'], selector: { - 'ui:order': ['matchExpressions', 'matchLabels'], + 'ui:order': ['matchExpressions', 'matchLabels', '*'], matchExpressions: { items: { - 'ui:order': ['key', 'operator', 'values'] + 'ui:order': ['key', 'operator', 'values', '*'] } } } }, target: { - 'ui:order': ['type', 'averageUtilization', 'averageValue', 'value'] + 'ui:order': ['type', 'averageUtilization', 'averageValue', 'value', '*'] } }, pods: { - 'ui:order': ['metric', 'target'], + 'ui:order': ['metric', 'target', '*'], metric: { - 'ui:order': ['name', 'selector'], + 'ui:order': ['name', 'selector', '*'], selector: { - 'ui:order': ['matchExpressions', 'matchLabels'], + 'ui:order': ['matchExpressions', 'matchLabels', '*'], matchExpressions: { items: { - 'ui:order': ['key', 'operator', 'values'] + 'ui:order': ['key', 'operator', 'values', '*'] } } } }, target: { - 'ui:order': ['type', 'averageUtilization', 'averageValue', 'value'] + 'ui:order': ['type', 'averageUtilization', 'averageValue', 'value', '*'] } }, resource: { - 'ui:order': ['name', 'target'], + 'ui:order': ['name', 'target', '*'], target: { - 'ui:order': ['type', 'averageUtilization', 'averageValue', 'value'] + 'ui:order': ['type', 'averageUtilization', 'averageValue', 'value', '*'] } } } @@ -643,7 +643,7 @@ export const FlowCollectorUISchema: UiSchema = { controlFieldValue: 'Provided', controlFieldName: 'type' }, - 'ui:order': ['certFile', 'certKey', 'name', 'namespace', 'type'] + 'ui:order': ['certFile', 'certKey', 'name', 'namespace', 'type', '*'] }, providedCaFile: { 'ui:title': 'CA', @@ -652,30 +652,33 @@ export const FlowCollectorUISchema: UiSchema = { controlFieldValue: 'Provided', controlFieldName: 'type' }, - 'ui:order': ['file', 'name', 'namespace', 'type'] + 'ui:order': ['file', 'name', 'namespace', 'type', '*'] }, - 'ui:order': ['type', 'insecureSkipVerify', 'provided', 'providedCaFile'] + 'ui:order': ['type', 'insecureSkipVerify', 'provided', 'providedCaFile', '*'] }, port: { 'ui:title': 'Port' }, - 'ui:order': ['tls', 'port'] - }, - disableAlerts: { - 'ui:title': 'Disable alerts' + 'ui:order': ['tls', 'port', '*'] }, includeList: { 'ui:title': 'Include list' }, - 'ui:order': ['server', 'disableAlerts', 'includeList'] + alerts: { + 'ui:title': 'Alerts' + }, + disableAlerts: { + 'ui:title': 'Disable alerts' + }, + 'ui:order': ['server', 'includeList', 'alerts', 'disableAlerts', '*'] }, resources: { 'ui:title': 'Resource Requirements', 'ui:widget': 'hidden', - 'ui:order': ['claims', 'limits', 'requests'], + 'ui:order': ['claims', 'limits', 'requests', '*'], claims: { items: { - 'ui:order': ['name', 'request'] + 'ui:order': ['name', 'request', '*'] } } }, @@ -701,9 +704,9 @@ export const FlowCollectorUISchema: UiSchema = { }, scheduling: { 'ui:widget': 'hidden', - 'ui:order': ['affinity', 'nodeSelector', 'priorityClassName', 'tolerations'], + 'ui:order': ['affinity', 'nodeSelector', 'priorityClassName', 'tolerations', '*'], affinity: { - 'ui:order': ['nodeAffinity', 'podAffinity', 'podAntiAffinity'], + 'ui:order': ['nodeAffinity', 'podAffinity', 'podAntiAffinity', '*'], nodeAffinity: { 'ui:order': [ 'preferredDuringSchedulingIgnoredDuringExecution', @@ -711,17 +714,17 @@ export const FlowCollectorUISchema: UiSchema = { ], preferredDuringSchedulingIgnoredDuringExecution: { items: { - 'ui:order': ['preference', 'weight'], + 'ui:order': ['preference', 'weight', '*'], preference: { - 'ui:order': ['matchExpressions', 'matchFields'], + 'ui:order': ['matchExpressions', 'matchFields', '*'], matchExpressions: { items: { - 'ui:order': ['key', 'operator', 'values'] + 'ui:order': ['key', 'operator', 'values', '*'] } }, matchFields: { items: { - 'ui:order': ['key', 'operator', 'values'] + 'ui:order': ['key', 'operator', 'values', '*'] } } } @@ -730,15 +733,15 @@ export const FlowCollectorUISchema: UiSchema = { requiredDuringSchedulingIgnoredDuringExecution: { nodeSelectorTerms: { items: { - 'ui:order': ['matchExpressions', 'matchFields'], + 'ui:order': ['matchExpressions', 'matchFields', '*'], matchExpressions: { items: { - 'ui:order': ['key', 'operator', 'values'] + 'ui:order': ['key', 'operator', 'values', '*'] } }, matchFields: { items: { - 'ui:order': ['key', 'operator', 'values'] + 'ui:order': ['key', 'operator', 'values', '*'] } } } @@ -752,7 +755,7 @@ export const FlowCollectorUISchema: UiSchema = { ], preferredDuringSchedulingIgnoredDuringExecution: { items: { - 'ui:order': ['podAffinityTerm', 'weight'], + 'ui:order': ['podAffinityTerm', 'weight', '*'], podAffinityTerm: { 'ui:order': [ 'topologyKey', @@ -763,18 +766,18 @@ export const FlowCollectorUISchema: UiSchema = { 'namespaces' ], labelSelector: { - 'ui:order': ['matchExpressions', 'matchLabels'], + 'ui:order': ['matchExpressions', 'matchLabels', '*'], matchExpressions: { items: { - 'ui:order': ['key', 'operator', 'values'] + 'ui:order': ['key', 'operator', 'values', '*'] } } }, namespaceSelector: { - 'ui:order': ['matchExpressions', 'matchLabels'], + 'ui:order': ['matchExpressions', 'matchLabels', '*'], matchExpressions: { items: { - 'ui:order': ['key', 'operator', 'values'] + 'ui:order': ['key', 'operator', 'values', '*'] } } } @@ -792,18 +795,18 @@ export const FlowCollectorUISchema: UiSchema = { 'namespaces' ], labelSelector: { - 'ui:order': ['matchExpressions', 'matchLabels'], + 'ui:order': ['matchExpressions', 'matchLabels', '*'], matchExpressions: { items: { - 'ui:order': ['key', 'operator', 'values'] + 'ui:order': ['key', 'operator', 'values', '*'] } } }, namespaceSelector: { - 'ui:order': ['matchExpressions', 'matchLabels'], + 'ui:order': ['matchExpressions', 'matchLabels', '*'], matchExpressions: { items: { - 'ui:order': ['key', 'operator', 'values'] + 'ui:order': ['key', 'operator', 'values', '*'] } } } @@ -817,7 +820,7 @@ export const FlowCollectorUISchema: UiSchema = { ], preferredDuringSchedulingIgnoredDuringExecution: { items: { - 'ui:order': ['podAffinityTerm', 'weight'], + 'ui:order': ['podAffinityTerm', 'weight', '*'], podAffinityTerm: { 'ui:order': [ 'topologyKey', @@ -828,18 +831,18 @@ export const FlowCollectorUISchema: UiSchema = { 'namespaces' ], labelSelector: { - 'ui:order': ['matchExpressions', 'matchLabels'], + 'ui:order': ['matchExpressions', 'matchLabels', '*'], matchExpressions: { items: { - 'ui:order': ['key', 'operator', 'values'] + 'ui:order': ['key', 'operator', 'values', '*'] } } }, namespaceSelector: { - 'ui:order': ['matchExpressions', 'matchLabels'], + 'ui:order': ['matchExpressions', 'matchLabels', '*'], matchExpressions: { items: { - 'ui:order': ['key', 'operator', 'values'] + 'ui:order': ['key', 'operator', 'values', '*'] } } } @@ -857,18 +860,18 @@ export const FlowCollectorUISchema: UiSchema = { 'namespaces' ], labelSelector: { - 'ui:order': ['matchExpressions', 'matchLabels'], + 'ui:order': ['matchExpressions', 'matchLabels', '*'], matchExpressions: { items: { - 'ui:order': ['key', 'operator', 'values'] + 'ui:order': ['key', 'operator', 'values', '*'] } } }, namespaceSelector: { - 'ui:order': ['matchExpressions', 'matchLabels'], + 'ui:order': ['matchExpressions', 'matchLabels', '*'], matchExpressions: { items: { - 'ui:order': ['key', 'operator', 'values'] + 'ui:order': ['key', 'operator', 'values', '*'] } } } @@ -878,7 +881,7 @@ export const FlowCollectorUISchema: UiSchema = { }, tolerations: { items: { - 'ui:order': ['effect', 'key', 'operator', 'tolerationSeconds', 'value'] + 'ui:order': ['effect', 'key', 'operator', 'tolerationSeconds', 'value', '*'] } } }, @@ -892,7 +895,7 @@ export const FlowCollectorUISchema: UiSchema = { 'ui:title': 'Index', 'ui:widget': 'arrayCheckboxes' }, - 'ui:order': ['name', 'index'] + 'ui:order': ['name', 'index', '*'] } }, healthPort: { @@ -969,23 +972,23 @@ export const FlowCollectorUISchema: UiSchema = { }, caCert: { 'ui:title': 'CA certificate', - 'ui:order': ['certFile', 'certKey', 'name', 'namespace', 'type'] + 'ui:order': ['certFile', 'certKey', 'name', 'namespace', 'type', '*'] }, userCert: { 'ui:title': 'User certificate when using mTLS', - 'ui:order': ['certFile', 'certKey', 'name', 'namespace', 'type'] + 'ui:order': ['certFile', 'certKey', 'name', 'namespace', 'type', '*'] }, insecureSkipVerify: { 'ui:title': 'Insecure skip verify' }, - 'ui:order': ['enable', 'caCert', 'userCert', 'insecureSkipVerify'] + 'ui:order': ['enable', 'caCert', 'userCert', 'insecureSkipVerify', '*'] }, - 'ui:order': ['forwardUserToken', 'url', 'tls'] + 'ui:order': ['forwardUserToken', 'url', 'tls', '*'] }, timeout: { 'ui:title': 'Timeout' }, - 'ui:order': ['enable', 'mode', 'manual', 'timeout'] + 'ui:order': ['enable', 'mode', 'manual', 'timeout', '*'] } }, loki: { @@ -1019,7 +1022,7 @@ export const FlowCollectorUISchema: UiSchema = { tenantID: { 'ui:title': 'Tenant id' }, - 'ui:order': ['authToken', 'ingesterUrl', 'querierUrl', 'statusUrl', 'tenantID', 'statusTls', 'tls'], + 'ui:order': ['authToken', 'ingesterUrl', 'querierUrl', 'statusUrl', 'tenantID', 'statusTls', 'tls', '*'], statusTls: { 'ui:title': 'TLS configuration', enable: { @@ -1027,16 +1030,16 @@ export const FlowCollectorUISchema: UiSchema = { }, caCert: { 'ui:title': 'CA certificate', - 'ui:order': ['certFile', 'certKey', 'name', 'namespace', 'type'] + 'ui:order': ['certFile', 'certKey', 'name', 'namespace', 'type', '*'] }, userCert: { 'ui:title': 'User certificate when using mTLS', - 'ui:order': ['certFile', 'certKey', 'name', 'namespace', 'type'] + 'ui:order': ['certFile', 'certKey', 'name', 'namespace', 'type', '*'] }, insecureSkipVerify: { 'ui:title': 'Insecure skip verify' }, - 'ui:order': ['enable', 'caCert', 'userCert', 'insecureSkipVerify'] + 'ui:order': ['enable', 'caCert', 'userCert', 'insecureSkipVerify', '*'] }, tls: { 'ui:title': 'TLS configuration', @@ -1045,16 +1048,16 @@ export const FlowCollectorUISchema: UiSchema = { }, caCert: { 'ui:title': 'CA certificate', - 'ui:order': ['certFile', 'certKey', 'name', 'namespace', 'type'] + 'ui:order': ['certFile', 'certKey', 'name', 'namespace', 'type', '*'] }, userCert: { 'ui:title': 'User certificate when using mTLS', - 'ui:order': ['certFile', 'certKey', 'name', 'namespace', 'type'] + 'ui:order': ['certFile', 'certKey', 'name', 'namespace', 'type', '*'] }, insecureSkipVerify: { 'ui:title': 'Insecure skip verify' }, - 'ui:order': ['enable', 'caCert', 'userCert', 'insecureSkipVerify'] + 'ui:order': ['enable', 'caCert', 'userCert', 'insecureSkipVerify', '*'] } }, monolithic: { @@ -1071,7 +1074,7 @@ export const FlowCollectorUISchema: UiSchema = { url: { 'ui:title': 'Url' }, - 'ui:order': ['tenantID', 'url', 'tls'], + 'ui:order': ['tenantID', 'url', 'tls', '*'], tls: { 'ui:title': 'TLS configuration', enable: { @@ -1079,16 +1082,16 @@ export const FlowCollectorUISchema: UiSchema = { }, caCert: { 'ui:title': 'CA certificate', - 'ui:order': ['certFile', 'certKey', 'name', 'namespace', 'type'] + 'ui:order': ['certFile', 'certKey', 'name', 'namespace', 'type', '*'] }, userCert: { 'ui:title': 'User certificate when using mTLS', - 'ui:order': ['certFile', 'certKey', 'name', 'namespace', 'type'] + 'ui:order': ['certFile', 'certKey', 'name', 'namespace', 'type', '*'] }, insecureSkipVerify: { 'ui:title': 'Insecure skip verify' }, - 'ui:order': ['enable', 'caCert', 'userCert', 'insecureSkipVerify'] + 'ui:order': ['enable', 'caCert', 'userCert', 'insecureSkipVerify', '*'] } }, microservices: { @@ -1108,7 +1111,7 @@ export const FlowCollectorUISchema: UiSchema = { tenantID: { 'ui:title': 'Tenant id' }, - 'ui:order': ['ingesterUrl', 'querierUrl', 'tenantID', 'tls'], + 'ui:order': ['ingesterUrl', 'querierUrl', 'tenantID', 'tls', '*'], tls: { 'ui:title': 'TLS configuration', enable: { @@ -1116,16 +1119,16 @@ export const FlowCollectorUISchema: UiSchema = { }, caCert: { 'ui:title': 'CA certificate', - 'ui:order': ['certFile', 'certKey', 'name', 'namespace', 'type'] + 'ui:order': ['certFile', 'certKey', 'name', 'namespace', 'type', '*'] }, userCert: { 'ui:title': 'User certificate when using mTLS', - 'ui:order': ['certFile', 'certKey', 'name', 'namespace', 'type'] + 'ui:order': ['certFile', 'certKey', 'name', 'namespace', 'type', '*'] }, insecureSkipVerify: { 'ui:title': 'Insecure skip verify' }, - 'ui:order': ['enable', 'caCert', 'userCert', 'insecureSkipVerify'] + 'ui:order': ['enable', 'caCert', 'userCert', 'insecureSkipVerify', '*'] } }, lokiStack: { @@ -1142,7 +1145,7 @@ export const FlowCollectorUISchema: UiSchema = { namespace: { 'ui:title': 'Namespace' }, - 'ui:order': ['name', 'namespace'] + 'ui:order': ['name', 'namespace', '*'] }, readTimeout: { 'ui:title': 'Read timeout', @@ -1175,7 +1178,7 @@ export const FlowCollectorUISchema: UiSchema = { writeMinBackoff: { 'ui:title': 'Write min backoff' }, - 'ui:order': ['staticLabels', 'writeMaxRetries', 'writeMaxBackoff', 'writeMinBackoff'] + 'ui:order': ['staticLabels', 'writeMaxRetries', 'writeMaxBackoff', 'writeMinBackoff', '*'] }, 'ui:order': [ 'enable', @@ -1211,15 +1214,15 @@ export const FlowCollectorUISchema: UiSchema = { portNames: { 'ui:title': 'Port names' }, - 'ui:order': ['enable', 'portNames'] + 'ui:order': ['enable', 'portNames', '*'] }, resources: { 'ui:title': 'Resource Requirements', 'ui:widget': 'hidden', - 'ui:order': ['claims', 'limits', 'requests'], + 'ui:order': ['claims', 'limits', 'requests', '*'], claims: { items: { - 'ui:order': ['name', 'request'] + 'ui:order': ['name', 'request', '*'] } } }, @@ -1227,7 +1230,7 @@ export const FlowCollectorUISchema: UiSchema = { 'ui:title': 'Quick filters', 'ui:widget': 'hidden', items: { - 'ui:order': ['filter', 'name', 'default'] + 'ui:order': ['filter', 'name', 'default', '*'] } }, replicas: { @@ -1236,74 +1239,74 @@ export const FlowCollectorUISchema: UiSchema = { autoscaler: { 'ui:title': 'Horizontal pod autoscaler', 'ui:widget': 'hidden', - 'ui:order': ['maxReplicas', 'metrics', 'minReplicas', 'status'], + 'ui:order': ['maxReplicas', 'metrics', 'minReplicas', 'status', '*'], metrics: { items: { - 'ui:order': ['type', 'containerResource', 'external', 'object', 'pods', 'resource'], + 'ui:order': ['type', 'containerResource', 'external', 'object', 'pods', 'resource', '*'], containerResource: { - 'ui:order': ['container', 'name', 'target'], + 'ui:order': ['container', 'name', 'target', '*'], target: { - 'ui:order': ['type', 'averageUtilization', 'averageValue', 'value'] + 'ui:order': ['type', 'averageUtilization', 'averageValue', 'value', '*'] } }, external: { - 'ui:order': ['metric', 'target'], + 'ui:order': ['metric', 'target', '*'], metric: { - 'ui:order': ['name', 'selector'], + 'ui:order': ['name', 'selector', '*'], selector: { - 'ui:order': ['matchExpressions', 'matchLabels'], + 'ui:order': ['matchExpressions', 'matchLabels', '*'], matchExpressions: { items: { - 'ui:order': ['key', 'operator', 'values'] + 'ui:order': ['key', 'operator', 'values', '*'] } } } }, target: { - 'ui:order': ['type', 'averageUtilization', 'averageValue', 'value'] + 'ui:order': ['type', 'averageUtilization', 'averageValue', 'value', '*'] } }, object: { - 'ui:order': ['describedObject', 'metric', 'target'], + 'ui:order': ['describedObject', 'metric', 'target', '*'], describedObject: { - 'ui:order': ['kind', 'name', 'apiVersion'] + 'ui:order': ['kind', 'name', 'apiVersion', '*'] }, metric: { - 'ui:order': ['name', 'selector'], + 'ui:order': ['name', 'selector', '*'], selector: { - 'ui:order': ['matchExpressions', 'matchLabels'], + 'ui:order': ['matchExpressions', 'matchLabels', '*'], matchExpressions: { items: { - 'ui:order': ['key', 'operator', 'values'] + 'ui:order': ['key', 'operator', 'values', '*'] } } } }, target: { - 'ui:order': ['type', 'averageUtilization', 'averageValue', 'value'] + 'ui:order': ['type', 'averageUtilization', 'averageValue', 'value', '*'] } }, pods: { - 'ui:order': ['metric', 'target'], + 'ui:order': ['metric', 'target', '*'], metric: { - 'ui:order': ['name', 'selector'], + 'ui:order': ['name', 'selector', '*'], selector: { - 'ui:order': ['matchExpressions', 'matchLabels'], + 'ui:order': ['matchExpressions', 'matchLabels', '*'], matchExpressions: { items: { - 'ui:order': ['key', 'operator', 'values'] + 'ui:order': ['key', 'operator', 'values', '*'] } } } }, target: { - 'ui:order': ['type', 'averageUtilization', 'averageValue', 'value'] + 'ui:order': ['type', 'averageUtilization', 'averageValue', 'value', '*'] } }, resource: { - 'ui:order': ['name', 'target'], + 'ui:order': ['name', 'target', '*'], target: { - 'ui:order': ['type', 'averageUtilization', 'averageValue', 'value'] + 'ui:order': ['type', 'averageUtilization', 'averageValue', 'value', '*'] } } } @@ -1312,11 +1315,11 @@ export const FlowCollectorUISchema: UiSchema = { advanced: { 'ui:title': 'Advanced configuration', 'ui:widget': 'hidden', - 'ui:order': ['args', 'env', 'port', 'register', 'scheduling'], + 'ui:order': ['args', 'env', 'port', 'register', 'scheduling', '*'], scheduling: { 'ui:widget': 'hidden', affinity: { - 'ui:order': ['nodeAffinity', 'podAffinity', 'podAntiAffinity'], + 'ui:order': ['nodeAffinity', 'podAffinity', 'podAntiAffinity', '*'], nodeAffinity: { 'ui:order': [ 'preferredDuringSchedulingIgnoredDuringExecution', @@ -1324,17 +1327,17 @@ export const FlowCollectorUISchema: UiSchema = { ], preferredDuringSchedulingIgnoredDuringExecution: { items: { - 'ui:order': ['preference', 'weight'], + 'ui:order': ['preference', 'weight', '*'], preference: { - 'ui:order': ['matchExpressions', 'matchFields'], + 'ui:order': ['matchExpressions', 'matchFields', '*'], matchExpressions: { items: { - 'ui:order': ['key', 'operator', 'values'] + 'ui:order': ['key', 'operator', 'values', '*'] } }, matchFields: { items: { - 'ui:order': ['key', 'operator', 'values'] + 'ui:order': ['key', 'operator', 'values', '*'] } } } @@ -1343,15 +1346,15 @@ export const FlowCollectorUISchema: UiSchema = { requiredDuringSchedulingIgnoredDuringExecution: { nodeSelectorTerms: { items: { - 'ui:order': ['matchExpressions', 'matchFields'], + 'ui:order': ['matchExpressions', 'matchFields', '*'], matchExpressions: { items: { - 'ui:order': ['key', 'operator', 'values'] + 'ui:order': ['key', 'operator', 'values', '*'] } }, matchFields: { items: { - 'ui:order': ['key', 'operator', 'values'] + 'ui:order': ['key', 'operator', 'values', '*'] } } } @@ -1365,7 +1368,7 @@ export const FlowCollectorUISchema: UiSchema = { ], preferredDuringSchedulingIgnoredDuringExecution: { items: { - 'ui:order': ['podAffinityTerm', 'weight'], + 'ui:order': ['podAffinityTerm', 'weight', '*'], podAffinityTerm: { 'ui:order': [ 'topologyKey', @@ -1376,18 +1379,18 @@ export const FlowCollectorUISchema: UiSchema = { 'namespaces' ], labelSelector: { - 'ui:order': ['matchExpressions', 'matchLabels'], + 'ui:order': ['matchExpressions', 'matchLabels', '*'], matchExpressions: { items: { - 'ui:order': ['key', 'operator', 'values'] + 'ui:order': ['key', 'operator', 'values', '*'] } } }, namespaceSelector: { - 'ui:order': ['matchExpressions', 'matchLabels'], + 'ui:order': ['matchExpressions', 'matchLabels', '*'], matchExpressions: { items: { - 'ui:order': ['key', 'operator', 'values'] + 'ui:order': ['key', 'operator', 'values', '*'] } } } @@ -1405,18 +1408,18 @@ export const FlowCollectorUISchema: UiSchema = { 'namespaces' ], labelSelector: { - 'ui:order': ['matchExpressions', 'matchLabels'], + 'ui:order': ['matchExpressions', 'matchLabels', '*'], matchExpressions: { items: { - 'ui:order': ['key', 'operator', 'values'] + 'ui:order': ['key', 'operator', 'values', '*'] } } }, namespaceSelector: { - 'ui:order': ['matchExpressions', 'matchLabels'], + 'ui:order': ['matchExpressions', 'matchLabels', '*'], matchExpressions: { items: { - 'ui:order': ['key', 'operator', 'values'] + 'ui:order': ['key', 'operator', 'values', '*'] } } } @@ -1430,7 +1433,7 @@ export const FlowCollectorUISchema: UiSchema = { ], preferredDuringSchedulingIgnoredDuringExecution: { items: { - 'ui:order': ['podAffinityTerm', 'weight'], + 'ui:order': ['podAffinityTerm', 'weight', '*'], podAffinityTerm: { 'ui:order': [ 'topologyKey', @@ -1441,18 +1444,18 @@ export const FlowCollectorUISchema: UiSchema = { 'namespaces' ], labelSelector: { - 'ui:order': ['matchExpressions', 'matchLabels'], + 'ui:order': ['matchExpressions', 'matchLabels', '*'], matchExpressions: { items: { - 'ui:order': ['key', 'operator', 'values'] + 'ui:order': ['key', 'operator', 'values', '*'] } } }, namespaceSelector: { - 'ui:order': ['matchExpressions', 'matchLabels'], + 'ui:order': ['matchExpressions', 'matchLabels', '*'], matchExpressions: { items: { - 'ui:order': ['key', 'operator', 'values'] + 'ui:order': ['key', 'operator', 'values', '*'] } } } @@ -1470,18 +1473,18 @@ export const FlowCollectorUISchema: UiSchema = { 'namespaces' ], labelSelector: { - 'ui:order': ['matchExpressions', 'matchLabels'], + 'ui:order': ['matchExpressions', 'matchLabels', '*'], matchExpressions: { items: { - 'ui:order': ['key', 'operator', 'values'] + 'ui:order': ['key', 'operator', 'values', '*'] } } }, namespaceSelector: { - 'ui:order': ['matchExpressions', 'matchLabels'], + 'ui:order': ['matchExpressions', 'matchLabels', '*'], matchExpressions: { items: { - 'ui:order': ['key', 'operator', 'values'] + 'ui:order': ['key', 'operator', 'values', '*'] } } } @@ -1491,10 +1494,10 @@ export const FlowCollectorUISchema: UiSchema = { }, tolerations: { items: { - 'ui:order': ['effect', 'key', 'operator', 'tolerationSeconds', 'value'] + 'ui:order': ['effect', 'key', 'operator', 'tolerationSeconds', 'value', '*'] } }, - 'ui:order': ['affinity', 'nodeSelector', 'priorityClassName', 'tolerations'] + 'ui:order': ['affinity', 'nodeSelector', 'priorityClassName', 'tolerations', '*'] } }, 'ui:order': [ @@ -1522,7 +1525,7 @@ export const FlowCollectorUISchema: UiSchema = { controlFieldName: 'enable' } }, - 'ui:order': ['enable', 'additionalNamespaces'] + 'ui:order': ['enable', 'additionalNamespaces', '*'] }, exporters: { 'ui:title': 'Exporters', @@ -1547,7 +1550,7 @@ export const FlowCollectorUISchema: UiSchema = { transport: { 'ui:title': 'Transport' }, - 'ui:order': ['targetHost', 'targetPort', 'transport'] + 'ui:order': ['targetHost', 'targetPort', 'transport', '*'] }, kafka: { 'ui:title': 'Kafka configuration', @@ -1570,16 +1573,16 @@ export const FlowCollectorUISchema: UiSchema = { }, caCert: { 'ui:title': 'CA certificate', - 'ui:order': ['certFile', 'certKey', 'name', 'namespace', 'type'] + 'ui:order': ['certFile', 'certKey', 'name', 'namespace', 'type', '*'] }, userCert: { 'ui:title': 'User certificate when using mTLS', - 'ui:order': ['certFile', 'certKey', 'name', 'namespace', 'type'] + 'ui:order': ['certFile', 'certKey', 'name', 'namespace', 'type', '*'] }, insecureSkipVerify: { 'ui:title': 'Insecure skip verify' }, - 'ui:order': ['enable', 'caCert', 'userCert', 'insecureSkipVerify'] + 'ui:order': ['enable', 'caCert', 'userCert', 'insecureSkipVerify', '*'] }, sasl: { 'ui:title': 'SASL', @@ -1588,15 +1591,15 @@ export const FlowCollectorUISchema: UiSchema = { }, clientIDReference: { 'ui:title': 'Client ID reference', - 'ui:order': ['file', 'name', 'namespace', 'type'] + 'ui:order': ['file', 'name', 'namespace', 'type', '*'] }, clientSecretReference: { 'ui:title': 'Client secret reference', - 'ui:order': ['file', 'name', 'namespace', 'type'] + 'ui:order': ['file', 'name', 'namespace', 'type', '*'] }, - 'ui:order': ['type', 'clientIDReference', 'clientSecretReference'] + 'ui:order': ['type', 'clientIDReference', 'clientSecretReference', '*'] }, - 'ui:order': ['address', 'topic', 'tls', 'sasl'] + 'ui:order': ['address', 'topic', 'tls', 'sasl', '*'] }, openTelemetry: { 'ui:title': 'OpenTelemetry configuration', @@ -1627,7 +1630,7 @@ export const FlowCollectorUISchema: UiSchema = { output: { 'ui:title': 'Output field' }, - 'ui:order': ['input', 'multiplier', 'output'] + 'ui:order': ['input', 'multiplier', 'output', '*'] } }, metrics: { @@ -1638,7 +1641,7 @@ export const FlowCollectorUISchema: UiSchema = { pushTimeInterval: { 'ui:title': 'Push time interval' }, - 'ui:order': ['enable', 'pushTimeInterval'] + 'ui:order': ['enable', 'pushTimeInterval', '*'] }, tls: { 'ui:title': 'TLS configuration', @@ -1647,20 +1650,30 @@ export const FlowCollectorUISchema: UiSchema = { }, caCert: { 'ui:title': 'CA certificate', - 'ui:order': ['certFile', 'certKey', 'name', 'namespace', 'type'] + 'ui:order': ['certFile', 'certKey', 'name', 'namespace', 'type', '*'] }, userCert: { 'ui:title': 'User certificate when using mTLS', - 'ui:order': ['certFile', 'certKey', 'name', 'namespace', 'type'] + 'ui:order': ['certFile', 'certKey', 'name', 'namespace', 'type', '*'] }, insecureSkipVerify: { 'ui:title': 'Insecure skip verify' }, - 'ui:order': ['enable', 'caCert', 'userCert', 'insecureSkipVerify'] + 'ui:order': ['enable', 'caCert', 'userCert', 'insecureSkipVerify', '*'] }, - 'ui:order': ['targetHost', 'targetPort', 'protocol', 'fieldsMapping', 'headers', 'logs', 'metrics', 'tls'] + 'ui:order': [ + 'targetHost', + 'targetPort', + 'protocol', + 'fieldsMapping', + 'headers', + 'logs', + 'metrics', + 'tls', + '*' + ] }, - 'ui:order': ['type', 'ipfix', 'kafka', 'openTelemetry'] + 'ui:order': ['type', 'ipfix', 'kafka', 'openTelemetry', '*'] } }, 'ui:order': [ @@ -1689,7 +1702,8 @@ export const FlowMetricUISchema: UiSchema = { 'ui:title': 'Name' }, namespace: { - 'ui:title': 'Namespace' + 'ui:title': 'Namespace', + 'ui:description': 'It must match the one configured in FlowCollector.' }, labels: { 'ui:widget': 'hidden' @@ -1742,7 +1756,7 @@ export const FlowMetricUISchema: UiSchema = { value: { 'ui:title': 'Value' }, - 'ui:order': ['field', 'matchType', 'value'] + 'ui:order': ['field', 'matchType', 'value', '*'] } }, charts: { @@ -1775,10 +1789,10 @@ export const FlowMetricUISchema: UiSchema = { top: { 'ui:title': 'Top' }, - 'ui:order': ['promQL', 'legend', 'top'] + 'ui:order': ['promQL', 'legend', 'top', '*'] } }, - 'ui:order': ['dashboardName', 'sectionName', 'title', 'unit', 'type', 'queries'] + 'ui:order': ['dashboardName', 'sectionName', 'title', 'unit', 'type', 'queries', '*'] } }, 'ui:order': [ diff --git a/web/src/components/forms/consumption.tsx b/web/src/components/forms/consumption.tsx index 9be268db6..ab6d91b66 100644 --- a/web/src/components/forms/consumption.tsx +++ b/web/src/components/forms/consumption.tsx @@ -4,9 +4,9 @@ import { PrometheusResponse, usePrometheusPoll } from '@openshift-console/dynamic-plugin-sdk'; -import { Flex, FlexItem, Spinner, Text, TextVariants } from '@patternfly/react-core'; -import { WarningTriangleIcon } from '@patternfly/react-icons'; -import { Table, Tbody, Td, Th, Thead, Tr } from '@patternfly/react-table'; +import { Flex, FlexItem, Slider, Spinner, Text, TextVariants } from '@patternfly/react-core'; +import { InfoAltIcon, WarningTriangleIcon } from '@patternfly/react-icons'; +import { Table, TableVariant, Tbody, Td, Th, Thead, Tr } from '@patternfly/react-table'; import _ from 'lodash'; import React, { FC } from 'react'; import { useTranslation } from 'react-i18next'; @@ -14,7 +14,7 @@ import './forms.css'; export type ResourceCalculatorProps = { flowCollector: K8sResourceKind | null; - setSampling?: (sampling: number) => void; + setSampling: (sampling: number) => void; }; export const Consumption: FC = ({ flowCollector, setSampling }) => { @@ -31,12 +31,12 @@ export const Consumption: FC = ({ flowCollector, setSam }); const getCurrentSampling = React.useCallback(() => { - return flowCollector?.spec?.agent?.ebpf?.sampling || 50; + return (flowCollector?.spec?.agent?.ebpf?.sampling as number) || 50; }, [flowCollector?.spec?.agent?.ebpf?.sampling]); const getSamplings = React.useCallback(() => { const current = getCurrentSampling(); - let samplings = [1, 25, 50, 100, 125, 150]; + let samplings = [1, 25, 50, 100, 500, 1000]; if (!samplings.includes(current)) { samplings.push(current); samplings = _.sortBy(samplings); @@ -44,6 +44,15 @@ export const Consumption: FC = ({ flowCollector, setSam return samplings; }, [getCurrentSampling]); + const getSamplingIndex = React.useCallback(() => { + const current = getCurrentSampling(); + const idx = getSamplings().indexOf(current); + if (idx < 0) { + return 0; + } + return idx; + }, [getSamplings, getCurrentSampling]); + const loadingComponent = () => ; const errorComponent = () => ; @@ -105,7 +114,7 @@ export const Consumption: FC = ({ flowCollector, setSam {t('Cluster metrics')} - +
@@ -132,7 +141,13 @@ export const Consumption: FC = ({ flowCollector, setSam {t('Recommendations')} -
{t('Bandwidth')}
+ + {t( + // eslint-disable-next-line max-len + 'The example outlined in the table demonstrates a scenario that is tailored to your workload. Consider this example only as a baseline from which adjustments can be made to accommodate your needs.' + )} + +
@@ -156,11 +171,27 @@ export const Consumption: FC = ({ flowCollector, setSam
{t('vCPU')}
- {t('Estimation')} - + {t('Performance tuning and estimation')} + + {t( + // eslint-disable-next-line max-len + 'The sampling interval is one of the main settings used to balance performance and accuracy. The lower the interval, the higher the accuracy.' + )} +
+ {t('Use the slider below to configure the desired sampling interval.')} +
+ ({ value: i, label: String(s) }))} + max={getSamplings().length - 1} + onChange={(_, value) => { + setSampling(getSamplings()[value]); + }} + /> +
- + @@ -177,7 +208,7 @@ export const Consumption: FC = ({ flowCollector, setSam isRowSelected={current} onClick={() => setSampling && setSampling(sampling)} > - + @@ -185,6 +216,13 @@ export const Consumption: FC = ({ flowCollector, setSam })}
{t('Sampling')}{t('Sampling interval')} {t('vCPU')} {t('Memory')}
{`${sampling} ${current ? t('(current)') : ''}`}{sampling} {`${estimate.cpu}vCPUs`} {`${estimate.memory}GiB`}
+ + {' '} + {t( + // eslint-disable-next-line max-len + 'The estimations are based on the number of nodes in the cluster and the sampling rate. They do not take into account the number of namespaces or pods, as their impact is comparatively lower than that of nodes.\nThey are calculated using a linear regression model based on data collected from various OpenShift clusters. Actual resource consumption may vary depending on your specific workload and cluster configuration.' + )} +
); diff --git a/web/src/components/forms/dynamic-form/dynamic-form.css b/web/src/components/forms/dynamic-form/dynamic-form.css index a990d5360..d80ead8c6 100644 --- a/web/src/components/forms/dynamic-form/dynamic-form.css +++ b/web/src/components/forms/dynamic-form/dynamic-form.css @@ -40,4 +40,12 @@ .checkboxes-container { padding: 1rem; -} \ No newline at end of file +} + +pre.backticks { + display: inline; +} + +#open-flow-collector-form, #open-flow-metrics-form { + padding: 0; +} diff --git a/web/src/components/forms/dynamic-form/fields.tsx b/web/src/components/forms/dynamic-form/fields.tsx index 696f552a5..fd7db0510 100644 --- a/web/src/components/forms/dynamic-form/fields.tsx +++ b/web/src/components/forms/dynamic-form/fields.tsx @@ -23,6 +23,21 @@ export const Description: React.FC<{ padding?: boolean; }> = ({ id, label, description, border, padding }) => { const isDarkTheme = useTheme(); + const { t } = useTranslation('plugin__netobserv-plugin'); + + const formatText = React.useCallback((rawText: string) => { + const tokenized = rawText.replaceAll(/(`[a-zA-Z0-9_.]+`)/g, '@@@$1@@@'); + const tokens = tokenized.split('@@@'); + return tokens.map(t => { + if (t === '') { + return null; + } + if (t.startsWith('`') && t.endsWith('`') && t.length > 2) { + return
{t.substring(1, t.length - 1)}
; + } + return t; + }); + }, []); if (!description) { return null; @@ -30,7 +45,7 @@ export const Description: React.FC<{ const desc = description.replaceAll('
', ''); const parts = desc.split('\n'); - let content = <>{desc}; + let content = <>{formatText(desc)}; if (parts.length > 1) { content = ( {content}} > ); diff --git a/web/src/components/forms/dynamic-form/widgets.tsx b/web/src/components/forms/dynamic-form/widgets.tsx index 2024a22a3..e560888f5 100644 --- a/web/src/components/forms/dynamic-form/widgets.tsx +++ b/web/src/components/forms/dynamic-form/widgets.tsx @@ -9,7 +9,7 @@ import { MenuToggleElement, Switch } from '@patternfly/react-core'; -import { getSchemaType, UIOptionsType, WidgetProps } from '@rjsf/utils'; +import { getSchemaType, WidgetProps } from '@rjsf/utils'; import classNames from 'classnames'; import * as _ from 'lodash'; import * as React from 'react'; @@ -94,8 +94,8 @@ export const SwitchWidget: React.FC = props => { onBlur={onBlur && (event => onBlur(id, event.target.value))} onChange={(_event, v) => onChange(v, undefined, id)} onFocus={onFocus && (event => onFocus(id, event.target.value))} - label={t('Enabled')} - labelOff={t('Disabled')} + label={t('True')} + labelOff={t('False')} /> ); }; @@ -173,12 +173,7 @@ export const ArrayCheckboxesWidget: React.FC = props => { errFunc} onDropPropertyClick={() => errFunc} - description={ - - } + description={} {...{ ...props, // eslint-disable-next-line @typescript-eslint/no-explicit-any @@ -189,7 +184,7 @@ export const ArrayCheckboxesWidget: React.FC = props => { > 4 ? 'row' : 'column' }} onBlur={() => onBlur(id, value)} onFocus={() => onFocus(id, value)} > diff --git a/web/src/components/forms/flowCollector-wizard.tsx b/web/src/components/forms/flowCollector-wizard.tsx index 116b3d327..1180ec46e 100644 --- a/web/src/components/forms/flowCollector-wizard.tsx +++ b/web/src/components/forms/flowCollector-wizard.tsx @@ -1,15 +1,20 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ -import { ResourceYAMLEditor } from '@openshift-console/dynamic-plugin-sdk'; -import { PageSection, Title, Wizard, WizardStep, WizardStepType } from '@patternfly/react-core'; +import { + Button, + PageSection, + Title, + Wizard, + WizardFooterWrapper, + WizardStep, + WizardStepType +} from '@patternfly/react-core'; import { RJSFSchema } from '@rjsf/utils'; import validator from '@rjsf/validator-ajv8'; import _ from 'lodash'; import React, { FC } from 'react'; import { useTranslation } from 'react-i18next'; import { useParams } from 'react-router-dom-v5-compat'; -import { ContextSingleton } from '../../utils/context'; -import { flowCollectorStatusPath } from '../../utils/url'; -import { safeYAMLToJS } from '../../utils/yaml'; +import { flowCollectorEditPath, flowCollectorNewPath, flowCollectorStatusPath } from '../../utils/url'; import DynamicLoader, { navigate } from '../dynamic-loader/dynamic-loader'; import { FlowCollectorUISchema } from './config/uiSchema'; import Consumption from './consumption'; @@ -54,49 +59,26 @@ export const FlowCollectorWizard: FC = props => { [data, paths, schema] ); - const step = React.useCallback( - (id, name: string) => { - return ( - - {form()} - - ); - }, - [form] - ); - const onStepChange = React.useCallback((_event: React.MouseEvent, step: WizardStepType) => { switch (step.id) { case 'overview': setPaths(defaultPaths); break; - case 'capture': + case 'processing': setPaths([ - 'spec.agent.ebpf.sampling', + 'spec.deploymentModel', + 'spec.kafka.address', + 'spec.kafka.topic', + 'spec.kafka.tls', 'spec.agent.ebpf.privileged', 'spec.agent.ebpf.features', 'spec.processor.clusterName', - 'spec.processor.multiClusterDeployment', 'spec.processor.addZone' ]); break; - case 'pipeline': - setPaths([ - 'spec.deploymentModel', - 'spec.kafka', - 'spec.processor.advanced.secondaryNetworks.items', - 'spec.exporters.items' - ]); - break; case 'loki': setPaths(['spec.loki']); break; - case 'prom': - setPaths(['spec.prometheus.querier']); - break; - case 'console': - setPaths(['spec.consolePlugin.enable', 'spec.consolePlugin.replicas']); - break; default: setPaths([]); } @@ -119,19 +101,39 @@ export const FlowCollectorWizard: FC = props => { group="flows.netobserv.io" version="v1beta2" kind="FlowCollector" - name={params.name || props.name} + name={params.name || props.name || 'cluster'} // fallback on cluster to ensure it doesn't already exists + skipCRError onSuccess={() => { navigate(flowCollectorStatusPath); }} > {ctx => { + // redirect to edit page if resource already exists or is created while using the wizard + // We can't handle edition here since this page doesn't include ResourceYAMLEditor + // which handle reload / update buttons + if (ctx.data.metadata?.resourceVersion) { + navigate(flowCollectorEditPath); + } // first init schema & data when watch resource query got results if (schema == null) { setSchema(ctx.schema); } if (data == null) { - setData(ctx.data); + // slightly modify default example when creating a new resource + if (params.name !== 'cluster') { + const updatedData = _.cloneDeep(ctx.data) as any; + if (!updatedData.spec) { + updatedData.spec = {}; + } + if (!updatedData.spec.loki) { + updatedData.spec.loki = {}; + } + updatedData.spec.loki.mode = 'LokiStack'; // default to lokistack + setData(updatedData); + } else { + setData(ctx.data); + } } return ( @@ -146,44 +148,52 @@ export const FlowCollectorWizard: FC = props => { {t( // eslint-disable-next-line max-len - 'Network Observability Operator deploys a monitoring pipeline that consists in:\n - an eBPF agent, that generates network flows from captured packets\n - flowlogs-pipeline, a component that collects, enriches and exports these flows\n - a Console plugin for flows visualization with powerful filtering options, a topology representation and more\n\nFlow data is then available in multiple ways, each optional:\n - As Prometheus metrics\n - As raw flow logs stored in Grafana Loki\n - As raw flow logs exported to a collector\n\nThe FlowCollector resource is used to configure the operator and its managed components.\nThis setup will guide you on the common aspects of the FlowCollector configuration.' + 'The FlowCollector resource is used to configure the Network Observability operator and its managed components. When it is created, network flows start being collected.' + )} +

+ {t( + // eslint-disable-next-line max-len + 'This wizard is a helper to create a first FlowCollector resource. It does not cover all the available configuration options, but only the most common ones.\nFor advanced configuration, please use YAML or the' + )}{' '} + + {t( + // eslint-disable-next-line max-len + ', which includes more options such as:\n- Filtering options\n- Configuring custom exporters\n- Custom labels based on IP\n- Pod identification for secondary networks\n- Performance fine-tuning\nYou can always edit a FlowCollector later when you start with the simplified configuration.' )}

{t('Operator configuration')}
{form(ctx.errors)} - + {form(ctx.errors)} - + {form(ctx.errors)} - - {form(ctx.errors)} - - - - - } + name={t('Consumption')} + id="consumption" + footer={ + + + + + } > - { - const updatedData = safeYAMLToJS(content); - setData(updatedData); - ctx.onSubmit(updatedData); - }} - /> + <>{!_.isEmpty(ctx.errors) && } diff --git a/web/src/components/forms/flowMetric-wizard.tsx b/web/src/components/forms/flowMetric-wizard.tsx index d373b69f9..10281da00 100644 --- a/web/src/components/forms/flowMetric-wizard.tsx +++ b/web/src/components/forms/flowMetric-wizard.tsx @@ -1,6 +1,6 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ import { ResourceYAMLEditor } from '@openshift-console/dynamic-plugin-sdk'; -import { PageSection, Title, Wizard, WizardStep, WizardStepType } from '@patternfly/react-core'; +import { Button, PageSection, Title, Wizard, WizardStep, WizardStepType } from '@patternfly/react-core'; import { RJSFSchema } from '@rjsf/utils'; import validator from '@rjsf/validator-ajv8'; import _ from 'lodash'; @@ -8,6 +8,7 @@ import React, { FC } from 'react'; import { useTranslation } from 'react-i18next'; import { useParams } from 'react-router-dom-v5-compat'; import { ContextSingleton } from '../../utils/context'; +import { flowMetricNewPath } from '../../utils/url'; import { safeYAMLToJS } from '../../utils/yaml'; import DynamicLoader, { back, navigate } from '../dynamic-loader/dynamic-loader'; import { FlowMetricUISchema } from './config/uiSchema'; @@ -59,13 +60,10 @@ export const FlowMetricWizard: FC = props => { setPaths(defaultPaths); break; case 'metric': - setPaths(['spec.metricName', 'spec.type', 'spec.buckets', 'spec.valueField', 'spec.divider', 'spec.labels']); + setPaths(['spec.metricName', 'spec.type', 'spec.valueField', 'spec.labels', 'spec.buckets']); break; case 'data': - setPaths(['spec.flatten', 'spec.remap', 'spec.direction', 'spec.filters']); - break; - case 'charts': - setPaths(['spec.charts']); + setPaths(['spec.remap', 'spec.direction', 'spec.filters']); break; default: setPaths([]); @@ -83,6 +81,7 @@ export const FlowMetricWizard: FC = props => { onSuccess={() => { back(); }} + ignoreCSVExample={true} > {ctx => { @@ -111,10 +110,26 @@ export const FlowMetricWizard: FC = props => { {t( // eslint-disable-next-line max-len - 'You can create custom metrics out of the flowlogs data using the FlowMetric API. In every flowlogs data that is collected, there are a number of fields labeled per log, such as source name and destination name. These fields can be leveraged as Prometheus labels to enable the customization of cluster information on your dashboard.\nThis setup will guide you on the common aspects of the FlowMetric configuration.' + 'You can create custom metrics out of the network flows using the FlowMetric API. A FlowCollector resource must be created as well in order to produce the flows. Each flow consists in a set of fields with values, such as source name and destination name. These fields can be leveraged as Prometheus labels to enable customized metrics and dashboards.' + )} +
+
+ {t( + // eslint-disable-next-line max-len + 'This simplified setup guides you through the common aspects of the FlowMetric configuration. For advanced configuration, please use YAML or the ' )} + + {'.'}

- {t('General configuration')} + {t('Resource configuration')}
{form(ctx.errors)}
@@ -124,9 +139,6 @@ export const FlowMetricWizard: FC = props => { {form(ctx.errors)} - - {form(ctx.errors)} - void; children: JSX.Element; skipErrors?: boolean; + skipCRError?: boolean; + ignoreCSVExample?: boolean; }; export type ResourceWatcherContext = { @@ -67,7 +69,9 @@ export const ResourceWatcher: FC = ({ namespace, onSuccess, children, - skipErrors + skipErrors, + skipCRError, + ignoreCSVExample }) => { if (!group || !version || !kind) { throw new Error('ResourceForm error: apiVersion and kind must be provided'); @@ -112,7 +116,7 @@ export const ResourceWatcher: FC = ({ const model = useK8sModel(group, version, kind); const [errors, setErrors] = React.useState([]); - if (!skipErrors && (csvLoadError || crdLoadError || crLoadError)) { + if (!skipErrors && (csvLoadError || crdLoadError || (!skipCRError && crLoadError))) { return ( = ({ isLokiRelated={false} /> ); - } else if (!csvLoaded || !crdLoaded || !crLoaded) { + } else if (!csvLoaded || !crdLoaded || (!skipCRError && !crLoaded)) { return ( @@ -130,20 +134,21 @@ export const ResourceWatcher: FC = ({ const data = cr ? { apiVersion: `${group}/${version}`, kind, ...cr } - : matchingCSVs?.items?.length + : !ignoreCSVExample && matchingCSVs?.items?.length ? exampleForModel( matchingCSVs.items.find(csv => csv.spec.customresourcedefinitions?.owned?.some(crd => crd.kind === kind)), group, version, kind ) - : {}; + : { apiVersion: `${group}/${version}`, kind }; const schema = crd?.spec?.versions?.find(v => v.name === version)?.schema?.openAPIV3Schema || null; // force name and namespace to be present in the form when namespaced if (crd?.spec?.scope === 'Namespaced') { data.metadata = { ...data.metadata, - namespace: namespace || 'default', + namespace: data.metadata?.namespace || 'netobserv', // for now, keep namespace if exists, or use netobserv by default + // namespace: namespace || 'netobserv', TODO: uncomment alongside with https://issues.redhat.com/browse/NETOBSERV-1690 name: name }; if (schema?.properties?.metadata) { @@ -151,6 +156,7 @@ export const ResourceWatcher: FC = ({ name: { type: 'string' }, namespace: { type: 'string' } }; + (schema.properties.metadata as any).required = ['name', 'namespace']; } } return ( diff --git a/web/src/utils/url.ts b/web/src/utils/url.ts index 815451b0a..4681c5ff4 100644 --- a/web/src/utils/url.ts +++ b/web/src/utils/url.ts @@ -5,6 +5,7 @@ export const netflowTrafficPath = '/netflow-traffic'; export const flowCollectorNewPath = '/k8s/cluster/flows.netobserv.io~v1beta2~FlowCollector/~new'; export const flowCollectorEditPath = '/k8s/cluster/flows.netobserv.io~v1beta2~FlowCollector/cluster'; export const flowCollectorStatusPath = '/k8s/cluster/flows.netobserv.io~v1beta2~FlowCollector/status'; +export const flowMetricNewPath = '/k8s/cluster/flows.netobserv.io~v1alpha1~FlowMetric/~new'; // React-router query argument (not backend routes) export enum URLParam {