Merge branch 'master' of github.com:provectus/kafka-ui into ISSUE_754_acl

 Conflicts:
	kafka-ui-api/src/main/java/com/provectus/kafka/ui/util/KafkaVersion.java
This commit is contained in:
iliax 2023-03-15 13:46:29 +04:00
commit 8532085a3a
123 changed files with 1949 additions and 892 deletions

View file

@ -9,6 +9,8 @@ assignees: ''
<!--
We will close the issue without further explanation if you don't follow this template and don't provide the information requested within this template.
Don't forget to check for existing issues/discussions regarding your proposal. We might already have it.
https://github.com/provectus/kafka-ui/issues
https://github.com/provectus/kafka-ui/discussions

View file

@ -55,7 +55,7 @@ jobs:
uses: aws-actions/amazon-ecr-login@v1
- name: Build and push
id: docker_build_and_push
uses: docker/build-push-action@v3
uses: docker/build-push-action@v4
with:
builder: ${{ steps.buildx.outputs.name }}
context: kafka-ui-api

View file

@ -54,7 +54,7 @@ jobs:
registry-type: 'public'
- name: Build and push
id: docker_build_and_push
uses: docker/build-push-action@v3
uses: docker/build-push-action@v4
with:
builder: ${{ steps.buildx.outputs.name }}
context: kafka-ui-api

View file

@ -40,7 +40,7 @@ jobs:
${{ runner.os }}-buildx-
- name: Build docker image
uses: docker/build-push-action@v3
uses: docker/build-push-action@v4
with:
builder: ${{ steps.buildx.outputs.name }}
context: kafka-ui-api
@ -55,7 +55,7 @@ jobs:
cache-to: type=local,dest=/tmp/.buildx-cache
- name: Run CVE checks
uses: aquasecurity/trivy-action@0.9.1
uses: aquasecurity/trivy-action@0.9.2
with:
image-ref: "provectuslabs/kafka-ui:${{ steps.build.outputs.version }}"
format: "table"

View file

@ -53,7 +53,7 @@ jobs:
- name: Build and push
id: docker_build_and_push
uses: docker/build-push-action@v3
uses: docker/build-push-action@v4
with:
builder: ${{ steps.buildx.outputs.name }}
context: kafka-ui-api

View file

@ -72,7 +72,7 @@ jobs:
- name: Build and push
id: docker_build_and_push
uses: docker/build-push-action@v3
uses: docker/build-push-action@v4
with:
builder: ${{ steps.buildx.outputs.name }}
context: kafka-ui-api

View file

@ -57,7 +57,7 @@ jobs:
uses: aws-actions/amazon-ecr-login@v1
- name: Build and push
id: docker_build_and_push
uses: docker/build-push-action@v3
uses: docker/build-push-action@v4
with:
builder: ${{ steps.buildx.outputs.name }}
context: kafka-ui-api

View file

@ -4,36 +4,34 @@ import com.provectus.kafka.ui.model.KafkaCluster;
import java.net.URI;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import lombok.experimental.UtilityClass;
import org.opendatadiscovery.oddrn.Generator;
import org.opendatadiscovery.oddrn.model.AwsS3Path;
import org.opendatadiscovery.oddrn.model.KafkaConnectorPath;
import org.opendatadiscovery.oddrn.model.KafkaPath;
@UtilityClass
public class Oddrn {
public final class Oddrn {
private static final Generator GENERATOR = new Generator();
private Oddrn() {
}
String clusterOddrn(KafkaCluster cluster) {
static String clusterOddrn(KafkaCluster cluster) {
return KafkaPath.builder()
.cluster(bootstrapServersForOddrn(cluster.getBootstrapServers()))
.build()
.oddrn();
}
KafkaPath topicOddrnPath(KafkaCluster cluster, String topic) {
static KafkaPath topicOddrnPath(KafkaCluster cluster, String topic) {
return KafkaPath.builder()
.cluster(bootstrapServersForOddrn(cluster.getBootstrapServers()))
.topic(topic)
.build();
}
String topicOddrn(KafkaCluster cluster, String topic) {
static String topicOddrn(KafkaCluster cluster, String topic) {
return topicOddrnPath(cluster, topic).oddrn();
}
String awsS3Oddrn(String bucket, String key) {
static String awsS3Oddrn(String bucket, String key) {
return AwsS3Path.builder()
.bucket(bucket)
.key(key)
@ -41,14 +39,14 @@ public class Oddrn {
.oddrn();
}
String connectDataSourceOddrn(String connectUrl) {
static String connectDataSourceOddrn(String connectUrl) {
return KafkaConnectorPath.builder()
.host(normalizedConnectHosts(connectUrl))
.build()
.oddrn();
}
private String normalizedConnectHosts(String connectUrlStr) {
private static String normalizedConnectHosts(String connectUrlStr) {
return Stream.of(connectUrlStr.split(","))
.map(String::trim)
.sorted()
@ -61,7 +59,7 @@ public class Oddrn {
.collect(Collectors.joining(","));
}
String connectorOddrn(String connectUrl, String connectorName) {
static String connectorOddrn(String connectUrl, String connectorName) {
return KafkaConnectorPath.builder()
.host(normalizedConnectHosts(connectUrl))
.connector(connectorName)
@ -69,7 +67,7 @@ public class Oddrn {
.oddrn();
}
private String bootstrapServersForOddrn(String bootstrapServers) {
private static String bootstrapServersForOddrn(String bootstrapServers) {
return Stream.of(bootstrapServers.split(","))
.map(String::trim)
.sorted()

View file

@ -1,18 +1,18 @@
package com.provectus.kafka.ui.service.integration.odd.schema;
import com.google.common.collect.ImmutableSet;
import com.provectus.kafka.ui.service.integration.odd.Oddrn;
import com.provectus.kafka.ui.sr.model.SchemaSubject;
import java.util.ArrayList;
import java.util.List;
import lombok.experimental.UtilityClass;
import org.apache.avro.Schema;
import org.opendatadiscovery.client.model.DataSetField;
import org.opendatadiscovery.client.model.DataSetFieldType;
import org.opendatadiscovery.oddrn.model.KafkaPath;
@UtilityClass
class AvroExtractor {
final class AvroExtractor {
private AvroExtractor() {
}
static List<DataSetField> extract(SchemaSubject subject, KafkaPath topicOddrn, boolean isKey) {
var schema = new Schema.Parser().parse(subject.getSchema());
@ -31,14 +31,14 @@ class AvroExtractor {
return result;
}
private void extract(Schema schema,
String parentOddr,
String oddrn, //null for root
String name,
String doc,
Boolean nullable,
ImmutableSet<String> registeredRecords,
List<DataSetField> sink
private static void extract(Schema schema,
String parentOddr,
String oddrn, //null for root
String name,
String doc,
Boolean nullable,
ImmutableSet<String> registeredRecords,
List<DataSetField> sink
) {
switch (schema.getType()) {
case RECORD -> extractRecord(schema, parentOddr, oddrn, name, doc, nullable, registeredRecords, sink);
@ -49,12 +49,12 @@ class AvroExtractor {
}
}
private DataSetField createDataSetField(String name,
String doc,
String parentOddrn,
String oddrn,
Schema schema,
Boolean nullable) {
private static DataSetField createDataSetField(String name,
String doc,
String parentOddrn,
String oddrn,
Schema schema,
Boolean nullable) {
return new DataSetField()
.name(name)
.description(doc)
@ -63,14 +63,14 @@ class AvroExtractor {
.type(mapSchema(schema, nullable));
}
private void extractRecord(Schema schema,
String parentOddr,
String oddrn, //null for root
String name,
String doc,
Boolean nullable,
ImmutableSet<String> registeredRecords,
List<DataSetField> sink) {
private static void extractRecord(Schema schema,
String parentOddr,
String oddrn, //null for root
String name,
String doc,
Boolean nullable,
ImmutableSet<String> registeredRecords,
List<DataSetField> sink) {
boolean isRoot = oddrn == null;
if (!isRoot) {
sink.add(createDataSetField(name, doc, parentOddr, oddrn, schema, nullable));
@ -99,13 +99,13 @@ class AvroExtractor {
));
}
private void extractUnion(Schema schema,
String parentOddr,
String oddrn, //null for root
String name,
String doc,
ImmutableSet<String> registeredRecords,
List<DataSetField> sink) {
private static void extractUnion(Schema schema,
String parentOddr,
String oddrn, //null for root
String name,
String doc,
ImmutableSet<String> registeredRecords,
List<DataSetField> sink) {
boolean isRoot = oddrn == null;
boolean containsNull = schema.getTypes().stream().map(Schema::getType).anyMatch(t -> t == Schema.Type.NULL);
// if it is not root and there is only 2 values for union (null and smth else)
@ -149,14 +149,14 @@ class AvroExtractor {
}
}
private void extractArray(Schema schema,
String parentOddr,
String oddrn, //null for root
String name,
String doc,
Boolean nullable,
ImmutableSet<String> registeredRecords,
List<DataSetField> sink) {
private static void extractArray(Schema schema,
String parentOddr,
String oddrn, //null for root
String name,
String doc,
Boolean nullable,
ImmutableSet<String> registeredRecords,
List<DataSetField> sink) {
boolean isRoot = oddrn == null;
oddrn = isRoot ? parentOddr + "/array" : oddrn;
if (isRoot) {
@ -176,14 +176,14 @@ class AvroExtractor {
);
}
private void extractMap(Schema schema,
String parentOddr,
String oddrn, //null for root
String name,
String doc,
Boolean nullable,
ImmutableSet<String> registeredRecords,
List<DataSetField> sink) {
private static void extractMap(Schema schema,
String parentOddr,
String oddrn, //null for root
String name,
String doc,
Boolean nullable,
ImmutableSet<String> registeredRecords,
List<DataSetField> sink) {
boolean isRoot = oddrn == null;
oddrn = isRoot ? parentOddr + "/map" : oddrn;
if (isRoot) {
@ -214,13 +214,13 @@ class AvroExtractor {
}
private void extractPrimitive(Schema schema,
String parentOddr,
String oddrn, //null for root
String name,
String doc,
Boolean nullable,
List<DataSetField> sink) {
private static void extractPrimitive(Schema schema,
String parentOddr,
String oddrn, //null for root
String name,
String doc,
Boolean nullable,
List<DataSetField> sink) {
boolean isRoot = oddrn == null;
String primOddrn = isRoot ? (parentOddr + "/" + schema.getType()) : oddrn;
if (isRoot) {
@ -231,7 +231,7 @@ class AvroExtractor {
}
}
private DataSetFieldType.TypeEnum mapType(Schema.Type type) {
private static DataSetFieldType.TypeEnum mapType(Schema.Type type) {
return switch (type) {
case INT, LONG -> DataSetFieldType.TypeEnum.INTEGER;
case FLOAT, DOUBLE, FIXED -> DataSetFieldType.TypeEnum.NUMBER;
@ -246,14 +246,14 @@ class AvroExtractor {
};
}
private DataSetFieldType mapSchema(Schema schema, Boolean nullable) {
private static DataSetFieldType mapSchema(Schema schema, Boolean nullable) {
return new DataSetFieldType()
.logicalType(logicalType(schema))
.isNullable(nullable)
.type(mapType(schema.getType()));
}
private String logicalType(Schema schema) {
private static String logicalType(Schema schema) {
return schema.getType() == Schema.Type.RECORD
? schema.getFullName()
: schema.getType().toString().toLowerCase();

View file

@ -1,19 +1,16 @@
package com.provectus.kafka.ui.service.integration.odd.schema;
import com.provectus.kafka.ui.service.integration.odd.Oddrn;
import com.provectus.kafka.ui.sr.model.SchemaSubject;
import com.provectus.kafka.ui.sr.model.SchemaType;
import java.util.List;
import java.util.Optional;
import lombok.experimental.UtilityClass;
import org.opendatadiscovery.client.model.DataSetField;
import org.opendatadiscovery.client.model.DataSetFieldType;
import org.opendatadiscovery.oddrn.model.KafkaPath;
@UtilityClass
public class DataSetFieldsExtractors {
public final class DataSetFieldsExtractors {
public List<DataSetField> extract(SchemaSubject subject, KafkaPath topicOddrn, boolean isKey) {
public static List<DataSetField> extract(SchemaSubject subject, KafkaPath topicOddrn, boolean isKey) {
SchemaType schemaType = Optional.ofNullable(subject.getSchemaType()).orElse(SchemaType.AVRO);
return switch (schemaType) {
case AVRO -> AvroExtractor.extract(subject, topicOddrn, isKey);
@ -23,7 +20,7 @@ public class DataSetFieldsExtractors {
}
DataSetField rootField(KafkaPath topicOddrn, boolean isKey) {
static DataSetField rootField(KafkaPath topicOddrn, boolean isKey) {
var rootOddrn = topicOddrn.oddrn() + "/columns/" + (isKey ? "key" : "value");
return new DataSetField()
.name(isKey ? "key" : "value")

View file

@ -1,7 +1,6 @@
package com.provectus.kafka.ui.service.integration.odd.schema;
import com.google.common.collect.ImmutableSet;
import com.provectus.kafka.ui.service.integration.odd.Oddrn;
import com.provectus.kafka.ui.sr.model.SchemaSubject;
import io.confluent.kafka.schemaregistry.json.JsonSchema;
import java.net.URI;
@ -10,7 +9,6 @@ import java.util.List;
import java.util.Map;
import java.util.Optional;
import javax.annotation.Nullable;
import lombok.experimental.UtilityClass;
import org.everit.json.schema.ArraySchema;
import org.everit.json.schema.BooleanSchema;
import org.everit.json.schema.CombinedSchema;
@ -27,8 +25,10 @@ import org.opendatadiscovery.client.model.DataSetFieldType;
import org.opendatadiscovery.client.model.MetadataExtension;
import org.opendatadiscovery.oddrn.model.KafkaPath;
@UtilityClass
class JsonSchemaExtractor {
final class JsonSchemaExtractor {
private JsonSchemaExtractor() {
}
static List<DataSetField> extract(SchemaSubject subject, KafkaPath topicOddrn, boolean isKey) {
Schema schema = new JsonSchema(subject.getSchema()).rawSchema();
@ -46,13 +46,13 @@ class JsonSchemaExtractor {
return result;
}
private void extract(Schema schema,
String parentOddr,
String oddrn, //null for root
String name,
Boolean nullable,
ImmutableSet<String> registeredRecords,
List<DataSetField> sink) {
private static void extract(Schema schema,
String parentOddr,
String oddrn, //null for root
String name,
Boolean nullable,
ImmutableSet<String> registeredRecords,
List<DataSetField> sink) {
if (schema instanceof ReferenceSchema s) {
Optional.ofNullable(s.getReferredSchema())
.ifPresent(refSchema -> extract(refSchema, parentOddr, oddrn, name, nullable, registeredRecords, sink));
@ -73,12 +73,12 @@ class JsonSchemaExtractor {
}
}
private void extractPrimitive(Schema schema,
String parentOddr,
String oddrn, //null for root
String name,
Boolean nullable,
List<DataSetField> sink) {
private static void extractPrimitive(Schema schema,
String parentOddr,
String oddrn, //null for root
String name,
Boolean nullable,
List<DataSetField> sink) {
boolean isRoot = oddrn == null;
sink.add(
createDataSetField(
@ -93,12 +93,12 @@ class JsonSchemaExtractor {
);
}
private void extractUnknown(Schema schema,
String parentOddr,
String oddrn, //null for root
String name,
Boolean nullable,
List<DataSetField> sink) {
private static void extractUnknown(Schema schema,
String parentOddr,
String oddrn, //null for root
String name,
Boolean nullable,
List<DataSetField> sink) {
boolean isRoot = oddrn == null;
sink.add(
createDataSetField(
@ -113,13 +113,13 @@ class JsonSchemaExtractor {
);
}
private void extractObject(ObjectSchema schema,
String parentOddr,
String oddrn, //null for root
String name,
Boolean nullable,
ImmutableSet<String> registeredRecords,
List<DataSetField> sink) {
private static void extractObject(ObjectSchema schema,
String parentOddr,
String oddrn, //null for root
String name,
Boolean nullable,
ImmutableSet<String> registeredRecords,
List<DataSetField> sink) {
boolean isRoot = oddrn == null;
// schemaLocation can be null for empty object schemas (like if it used in anyOf)
@Nullable var schemaLocation = schema.getSchemaLocation();
@ -162,13 +162,13 @@ class JsonSchemaExtractor {
});
}
private void extractArray(ArraySchema schema,
String parentOddr,
String oddrn, //null for root
String name,
Boolean nullable,
ImmutableSet<String> registeredRecords,
List<DataSetField> sink) {
private static void extractArray(ArraySchema schema,
String parentOddr,
String oddrn, //null for root
String name,
Boolean nullable,
ImmutableSet<String> registeredRecords,
List<DataSetField> sink) {
boolean isRoot = oddrn == null;
oddrn = isRoot ? parentOddr + "/array" : oddrn;
if (isRoot) {
@ -208,13 +208,13 @@ class JsonSchemaExtractor {
}
}
private void extractCombined(CombinedSchema schema,
String parentOddr,
String oddrn, //null for root
String name,
Boolean nullable,
ImmutableSet<String> registeredRecords,
List<DataSetField> sink) {
private static void extractCombined(CombinedSchema schema,
String parentOddr,
String oddrn, //null for root
String name,
Boolean nullable,
ImmutableSet<String> registeredRecords,
List<DataSetField> sink) {
String combineType = "unknown";
if (schema.getCriterion() == CombinedSchema.ALL_CRITERION) {
combineType = "allOf";
@ -255,24 +255,24 @@ class JsonSchemaExtractor {
}
}
private String getDescription(Schema schema) {
private static String getDescription(Schema schema) {
return Optional.ofNullable(schema.getTitle())
.orElse(schema.getDescription());
}
private String logicalTypeName(Schema schema) {
private static String logicalTypeName(Schema schema) {
return schema.getClass()
.getSimpleName()
.replace("Schema", "");
}
private DataSetField createDataSetField(Schema schema,
String name,
String parentOddrn,
String oddrn,
DataSetFieldType.TypeEnum type,
String logicalType,
Boolean nullable) {
private static DataSetField createDataSetField(Schema schema,
String name,
String parentOddrn,
String oddrn,
DataSetFieldType.TypeEnum type,
String logicalType,
Boolean nullable) {
return new DataSetField()
.name(name)
.parentFieldOddrn(parentOddrn)
@ -286,7 +286,7 @@ class JsonSchemaExtractor {
);
}
private DataSetFieldType.TypeEnum mapType(Schema type) {
private static DataSetFieldType.TypeEnum mapType(Schema type) {
if (type instanceof NumberSchema) {
return DataSetFieldType.TypeEnum.NUMBER;
}

View file

@ -15,20 +15,17 @@ import com.google.protobuf.Timestamp;
import com.google.protobuf.UInt32Value;
import com.google.protobuf.UInt64Value;
import com.google.protobuf.Value;
import com.provectus.kafka.ui.service.integration.odd.Oddrn;
import com.provectus.kafka.ui.sr.model.SchemaSubject;
import io.confluent.kafka.schemaregistry.protobuf.ProtobufSchema;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import lombok.experimental.UtilityClass;
import org.opendatadiscovery.client.model.DataSetField;
import org.opendatadiscovery.client.model.DataSetFieldType;
import org.opendatadiscovery.client.model.DataSetFieldType.TypeEnum;
import org.opendatadiscovery.oddrn.model.KafkaPath;
@UtilityClass
class ProtoExtractor {
final class ProtoExtractor {
private static final Set<String> PRIMITIVES_WRAPPER_TYPE_NAMES = Set.of(
BoolValue.getDescriptor().getFullName(),
@ -42,7 +39,10 @@ class ProtoExtractor {
DoubleValue.getDescriptor().getFullName()
);
List<DataSetField> extract(SchemaSubject subject, KafkaPath topicOddrn, boolean isKey) {
private ProtoExtractor() {
}
static List<DataSetField> extract(SchemaSubject subject, KafkaPath topicOddrn, boolean isKey) {
Descriptor schema = new ProtobufSchema(subject.getSchema()).toDescriptor();
List<DataSetField> result = new ArrayList<>();
result.add(DataSetFieldsExtractors.rootField(topicOddrn, isKey));
@ -60,14 +60,14 @@ class ProtoExtractor {
return result;
}
private void extract(Descriptors.FieldDescriptor field,
String parentOddr,
String oddrn, //null for root
String name,
boolean nullable,
boolean repeated,
ImmutableSet<String> registeredRecords,
List<DataSetField> sink) {
private static void extract(Descriptors.FieldDescriptor field,
String parentOddr,
String oddrn, //null for root
String name,
boolean nullable,
boolean repeated,
ImmutableSet<String> registeredRecords,
List<DataSetField> sink) {
if (repeated) {
extractRepeated(field, parentOddr, oddrn, name, nullable, registeredRecords, sink);
} else if (field.getType() == Descriptors.FieldDescriptor.Type.MESSAGE) {
@ -79,12 +79,12 @@ class ProtoExtractor {
// converts some(!) Protobuf Well-known type (from google.protobuf.* packages)
// see JsonFormat::buildWellKnownTypePrinters for impl details
private boolean extractProtoWellKnownType(Descriptors.FieldDescriptor field,
String parentOddr,
String oddrn, //null for root
String name,
boolean nullable,
List<DataSetField> sink) {
private static boolean extractProtoWellKnownType(Descriptors.FieldDescriptor field,
String parentOddr,
String oddrn, //null for root
String name,
boolean nullable,
List<DataSetField> sink) {
// all well-known types are messages
if (field.getType() != Descriptors.FieldDescriptor.Type.MESSAGE) {
return false;
@ -111,13 +111,13 @@ class ProtoExtractor {
return false;
}
private void extractRepeated(Descriptors.FieldDescriptor field,
String parentOddr,
String oddrn, //null for root
String name,
boolean nullable,
ImmutableSet<String> registeredRecords,
List<DataSetField> sink) {
private static void extractRepeated(Descriptors.FieldDescriptor field,
String parentOddr,
String oddrn, //null for root
String name,
boolean nullable,
ImmutableSet<String> registeredRecords,
List<DataSetField> sink) {
sink.add(createDataSetField(name, parentOddr, oddrn, TypeEnum.LIST, "repeated", nullable));
String itemName = field.getType() == Descriptors.FieldDescriptor.Type.MESSAGE
@ -136,13 +136,13 @@ class ProtoExtractor {
);
}
private void extractMessage(Descriptors.FieldDescriptor field,
String parentOddr,
String oddrn, //null for root
String name,
boolean nullable,
ImmutableSet<String> registeredRecords,
List<DataSetField> sink) {
private static void extractMessage(Descriptors.FieldDescriptor field,
String parentOddr,
String oddrn, //null for root
String name,
boolean nullable,
ImmutableSet<String> registeredRecords,
List<DataSetField> sink) {
if (extractProtoWellKnownType(field, parentOddr, oddrn, name, nullable, sink)) {
return;
}
@ -173,12 +173,12 @@ class ProtoExtractor {
});
}
private void extractPrimitive(Descriptors.FieldDescriptor field,
String parentOddr,
String oddrn,
String name,
boolean nullable,
List<DataSetField> sink) {
private static void extractPrimitive(Descriptors.FieldDescriptor field,
String parentOddr,
String oddrn,
String name,
boolean nullable,
List<DataSetField> sink) {
sink.add(
createDataSetField(
name,
@ -191,18 +191,18 @@ class ProtoExtractor {
);
}
private String getLogicalTypeName(Descriptors.FieldDescriptor f) {
private static String getLogicalTypeName(Descriptors.FieldDescriptor f) {
return f.getType() == Descriptors.FieldDescriptor.Type.MESSAGE
? f.getMessageType().getFullName()
: f.getType().name().toLowerCase();
}
private DataSetField createDataSetField(String name,
String parentOddrn,
String oddrn,
TypeEnum type,
String logicalType,
Boolean nullable) {
private static DataSetField createDataSetField(String name,
String parentOddrn,
String oddrn,
TypeEnum type,
String logicalType,
Boolean nullable) {
return new DataSetField()
.name(name)
.parentFieldOddrn(parentOddrn)
@ -216,7 +216,7 @@ class ProtoExtractor {
}
private TypeEnum mapType(Descriptors.FieldDescriptor.Type type) {
private static TypeEnum mapType(Descriptors.FieldDescriptor.Type type) {
return switch (type) {
case INT32, INT64, SINT32, SFIXED32, SINT64, UINT32, UINT64, FIXED32, FIXED64, SFIXED64 -> TypeEnum.INTEGER;
case FLOAT, DOUBLE -> TypeEnum.NUMBER;

View file

@ -1,6 +1,7 @@
package com.provectus.kafka.ui.util;
import com.provectus.kafka.ui.config.ClustersProperties;
import static com.provectus.kafka.ui.config.ClustersProperties.TruststoreConfig;
import com.provectus.kafka.ui.connect.api.KafkaConnectClientApi;
import com.provectus.kafka.ui.model.ApplicationPropertyValidationDTO;
import com.provectus.kafka.ui.service.ReactiveAdminClient;
@ -13,38 +14,36 @@ import java.util.Optional;
import java.util.Properties;
import java.util.function.Supplier;
import javax.annotation.Nullable;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.TrustManagerFactory;
import lombok.experimental.UtilityClass;
import lombok.extern.slf4j.Slf4j;
import org.apache.kafka.clients.admin.AdminClient;
import org.apache.kafka.clients.admin.AdminClientConfig;
import org.springframework.util.ResourceUtils;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.util.function.Tuple2;
import reactor.util.function.Tuples;
@Slf4j
@UtilityClass
public class KafkaServicesValidation {
public final class KafkaServicesValidation {
private Mono<ApplicationPropertyValidationDTO> valid() {
private KafkaServicesValidation() {
}
private static Mono<ApplicationPropertyValidationDTO> valid() {
return Mono.just(new ApplicationPropertyValidationDTO().error(false));
}
private Mono<ApplicationPropertyValidationDTO> invalid(String errorMsg) {
private static Mono<ApplicationPropertyValidationDTO> invalid(String errorMsg) {
return Mono.just(new ApplicationPropertyValidationDTO().error(true).errorMessage(errorMsg));
}
private Mono<ApplicationPropertyValidationDTO> invalid(Throwable th) {
private static Mono<ApplicationPropertyValidationDTO> invalid(Throwable th) {
return Mono.just(new ApplicationPropertyValidationDTO().error(true).errorMessage(th.getMessage()));
}
/**
* Returns error msg, if any.
*/
public Optional<String> validateTruststore(ClustersProperties.TruststoreConfig truststoreConfig) {
public static Optional<String> validateTruststore(TruststoreConfig truststoreConfig) {
if (truststoreConfig.getTruststoreLocation() != null && truststoreConfig.getTruststorePassword() != null) {
try {
KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType());
@ -63,10 +62,10 @@ public class KafkaServicesValidation {
return Optional.empty();
}
public Mono<ApplicationPropertyValidationDTO> validateClusterConnection(String bootstrapServers,
Properties clusterProps,
@Nullable
ClustersProperties.TruststoreConfig ssl) {
public static Mono<ApplicationPropertyValidationDTO> validateClusterConnection(String bootstrapServers,
Properties clusterProps,
@Nullable
TruststoreConfig ssl) {
Properties properties = new Properties();
SslPropertiesUtil.addKafkaSslProperties(ssl, properties);
properties.putAll(clusterProps);
@ -93,7 +92,7 @@ public class KafkaServicesValidation {
});
}
public Mono<ApplicationPropertyValidationDTO> validateSchemaRegistry(
public static Mono<ApplicationPropertyValidationDTO> validateSchemaRegistry(
Supplier<ReactiveFailover<KafkaSrClientApi>> clientSupplier) {
ReactiveFailover<KafkaSrClientApi> client;
try {
@ -108,7 +107,7 @@ public class KafkaServicesValidation {
.onErrorResume(KafkaServicesValidation::invalid);
}
public Mono<ApplicationPropertyValidationDTO> validateConnect(
public static Mono<ApplicationPropertyValidationDTO> validateConnect(
Supplier<ReactiveFailover<KafkaConnectClientApi>> clientSupplier) {
ReactiveFailover<KafkaConnectClientApi> client;
try {
@ -123,7 +122,8 @@ public class KafkaServicesValidation {
.onErrorResume(KafkaServicesValidation::invalid);
}
public Mono<ApplicationPropertyValidationDTO> validateKsql(Supplier<ReactiveFailover<KsqlApiClient>> clientSupplier) {
public static Mono<ApplicationPropertyValidationDTO> validateKsql(
Supplier<ReactiveFailover<KsqlApiClient>> clientSupplier) {
ReactiveFailover<KsqlApiClient> client;
try {
client = clientSupplier.get();

View file

@ -1,10 +1,11 @@
package com.provectus.kafka.ui.util;
import java.util.Optional;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class KafkaVersion {
public final class KafkaVersion {
private KafkaVersion() {
}
public static Optional<Float> parse(String version) throws NumberFormatException {
try {

View file

@ -1,27 +1,17 @@
package com.provectus.kafka.ui.util;
import com.provectus.kafka.ui.config.ClustersProperties;
import io.netty.handler.ssl.SslContext;
import io.netty.handler.ssl.SslContextBuilder;
import java.io.FileInputStream;
import java.security.KeyStore;
import java.util.Properties;
import javax.annotation.Nullable;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManagerFactory;
import lombok.SneakyThrows;
import lombok.experimental.UtilityClass;
import org.apache.kafka.common.config.SslConfigs;
import org.springframework.http.client.reactive.ReactorClientHttpConnector;
import org.springframework.util.ResourceUtils;
import reactor.netty.http.client.HttpClient;
@UtilityClass
public class SslPropertiesUtil {
public final class SslPropertiesUtil {
public void addKafkaSslProperties(@Nullable ClustersProperties.TruststoreConfig truststoreConfig,
Properties sink) {
private SslPropertiesUtil() {
}
public static void addKafkaSslProperties(@Nullable ClustersProperties.TruststoreConfig truststoreConfig,
Properties sink) {
if (truststoreConfig != null && truststoreConfig.getTruststoreLocation() != null) {
sink.put(SslConfigs.SSL_TRUSTSTORE_LOCATION_CONFIG, truststoreConfig.getTruststoreLocation());
if (truststoreConfig.getTruststorePassword() != null) {

View file

@ -775,7 +775,7 @@ paths:
get:
tags:
- Consumer Groups
summary: Get consumer croups with paging support
summary: Get consumer groups with paging support
operationId: getConsumerGroupsPage
parameters:
- name: clusterName

View file

@ -16,18 +16,18 @@ public class Schema {
public static Schema createSchemaAvro() {
return new Schema().setName("schema_avro-" + randomAlphabetic(5))
.setType(SchemaType.AVRO)
.setValuePath(System.getProperty("user.dir") + "/src/main/resources/testData/schema_avro_value.json");
.setValuePath(System.getProperty("user.dir") + "/src/main/resources/testData/schemas/schema_avro_value.json");
}
public static Schema createSchemaJson() {
return new Schema().setName("schema_json-" + randomAlphabetic(5))
.setType(SchemaType.JSON)
.setValuePath(System.getProperty("user.dir") + "/src/main/resources/testData/schema_Json_Value.json");
.setValuePath(System.getProperty("user.dir") + "/src/main/resources/testData/schemas/schema_json_Value.json");
}
public static Schema createSchemaProtobuf() {
return new Schema().setName("schema_protobuf-" + randomAlphabetic(5))
.setType(SchemaType.PROTOBUF)
.setValuePath(System.getProperty("user.dir") + "/src/main/resources/testData/schema_protobuf_value.txt");
.setValuePath(System.getProperty("user.dir") + "/src/main/resources/testData/schemas/schema_protobuf_value.txt");
}
}

View file

@ -3,8 +3,11 @@ package com.provectus.kafka.ui.pages;
import com.codeborne.selenide.Condition;
import com.codeborne.selenide.ElementsCollection;
import com.codeborne.selenide.SelenideElement;
import com.codeborne.selenide.WebDriverRunner;
import com.provectus.kafka.ui.utilities.WebUtils;
import lombok.extern.slf4j.Slf4j;
import org.openqa.selenium.Keys;
import org.openqa.selenium.interactions.Actions;
import java.time.Duration;
@ -34,7 +37,7 @@ public abstract class BasePage extends WebUtils {
protected void waitUntilSpinnerDisappear() {
log.debug("\nwaitUntilSpinnerDisappear");
if (isVisible(loadingSpinner)) {
loadingSpinner.shouldBe(Condition.disappear, Duration.ofSeconds(30));
loadingSpinner.shouldBe(Condition.disappear, Duration.ofSeconds(60));
}
}
@ -42,6 +45,16 @@ public abstract class BasePage extends WebUtils {
clickByJavaScript(submitBtn);
}
protected void setJsonInputValue(SelenideElement jsonInput, String jsonConfig) {
sendKeysByActions(jsonInput, jsonConfig.replace(" ", ""));
new Actions(WebDriverRunner.getWebDriver())
.keyDown(Keys.SHIFT)
.sendKeys(Keys.PAGE_DOWN)
.keyUp(Keys.SHIFT)
.sendKeys(Keys.DELETE)
.perform();
}
protected SelenideElement getTableElement(String elementName) {
log.debug("\ngetTableElement: {}", elementName);
return $x(String.format(tableElementNameLocator, elementName));

View file

@ -21,11 +21,22 @@ public class ConnectorCreateForm extends BasePage {
}
@Step
public ConnectorCreateForm setConnectorDetails(String connectName, String configJson) {
public ConnectorCreateForm setName(String connectName) {
nameField.shouldBe(Condition.enabled).setValue(connectName);
return this;
}
@Step
public ConnectorCreateForm setConfig(String configJson) {
configField.shouldBe(Condition.enabled).click();
contentTextArea.setValue(configJson);
nameField.shouldBe(Condition.enabled).click();
setJsonInputValue(contentTextArea, configJson);
return this;
}
@Step
public ConnectorCreateForm setConnectorDetails(String connectName, String configJson) {
setName(connectName);
setConfig(configJson);
return this;
}

View file

@ -2,16 +2,20 @@ package com.provectus.kafka.ui.pages.schemas;
import com.codeborne.selenide.Condition;
import com.codeborne.selenide.SelenideElement;
import com.codeborne.selenide.WebDriverRunner;
import com.provectus.kafka.ui.api.model.CompatibilityLevel;
import com.provectus.kafka.ui.api.model.SchemaType;
import com.provectus.kafka.ui.pages.BasePage;
import io.qameta.allure.Step;
import org.openqa.selenium.Keys;
import org.openqa.selenium.interactions.Actions;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import static com.codeborne.selenide.Selenide.*;
import static org.openqa.selenium.By.id;
public class SchemaCreateForm extends BasePage {
@ -23,7 +27,8 @@ public class SchemaCreateForm extends BasePage {
protected SelenideElement compatibilityLevelList = $x("//ul[@name='compatibilityLevel']");
protected SelenideElement newSchemaTextArea = $x("//div[@id='newSchema']");
protected SelenideElement latestSchemaTextArea = $x("//div[@id='latestSchema']");
protected SelenideElement schemaVersionDdl = $$x("//ul[@role='listbox']/li[text()='Version 2']").first();
protected SelenideElement leftVersionDdl = $(id("left-select"));
protected SelenideElement rightVersionDdl = $(id("right-select"));
protected List<SelenideElement> visibleMarkers = $$x("//div[@class='ace_scroller']//div[contains(@class,'codeMarker')]");
protected List<SelenideElement> elementsCompareVersionDdl = $$x("//ul[@role='listbox']/ul/li");
protected String ddlElementLocator = "//li[@value='%s']";
@ -68,8 +73,14 @@ public class SchemaCreateForm extends BasePage {
}
@Step
public SchemaCreateForm openSchemaVersionDdl() {
schemaVersionDdl.shouldBe(Condition.enabled).click();
public SchemaCreateForm openLeftVersionDdl() {
leftVersionDdl.shouldBe(Condition.enabled).click();
return this;
}
@Step
public SchemaCreateForm openRightVersionDdl() {
rightVersionDdl.shouldBe(Condition.enabled).click();
return this;
}
@ -92,8 +103,15 @@ public class SchemaCreateForm extends BasePage {
@Step
public SchemaCreateForm setNewSchemaValue(String configJson) {
newSchemaTextArea.shouldBe(Condition.visible).click();
clearByKeyboard(newSchemaInput);
newSchemaInput.setValue(configJson);
newSchemaInput.shouldBe(Condition.enabled);
new Actions(WebDriverRunner.getWebDriver())
.sendKeys(Keys.PAGE_UP)
.keyDown(Keys.SHIFT)
.sendKeys(Keys.PAGE_DOWN)
.keyUp(Keys.SHIFT)
.sendKeys(Keys.DELETE)
.perform();
setJsonInputValue(newSchemaInput, configJson);
return this;
}

View file

@ -49,7 +49,7 @@ public class ProduceMessagePanel extends BasePage {
@Step
public ProduceMessagePanel submitProduceMessage() {
submitBtn.shouldBe(Condition.enabled).click();
clickByActions(submitBtn);
submitBtn.shouldBe(Condition.disappear);
refresh();
return this;

View file

@ -14,54 +14,55 @@ import org.openqa.selenium.chrome.ChromeOptions;
public abstract class LocalWebDriver {
private static org.openqa.selenium.WebDriver getWebDriver() {
try {
return WebDriverRunner.getWebDriver();
} catch (IllegalStateException ex) {
Configuration.headless = false;
Configuration.browser = "chrome";
Configuration.browserSize = "1920x1080";
/**screenshots and savePageSource config is needed for local debug
* optionally can be set as 'false' to not duplicate Allure report
*/
Configuration.screenshots = true;
Configuration.savePageSource = true;
Configuration.pageLoadTimeout = 120000;
Configuration.browserCapabilities = new ChromeOptions()
.addArguments("--lang=en_US");
open();
return WebDriverRunner.getWebDriver();
private static org.openqa.selenium.WebDriver getWebDriver() {
try {
return WebDriverRunner.getWebDriver();
} catch (IllegalStateException ex) {
Configuration.headless = false;
Configuration.browser = "chrome";
Configuration.browserSize = "1920x1080";
/**screenshots and savePageSource config is needed for local debug
* optionally can be set as 'false' to not duplicate Allure report
*/
Configuration.screenshots = true;
Configuration.savePageSource = true;
Configuration.pageLoadTimeout = 120000;
Configuration.browserCapabilities = new ChromeOptions()
.addArguments("--remote-allow-origins=*")
.addArguments("--lang=en_US");
open();
return WebDriverRunner.getWebDriver();
}
}
}
@Step
public static void openUrl(String url) {
if (!getWebDriver().getCurrentUrl().equals(url)) {
getWebDriver().get(url);
@Step
public static void openUrl(String url) {
if (!getWebDriver().getCurrentUrl().equals(url)) {
getWebDriver().get(url);
}
}
}
@Step
public static void browserInit() {
getWebDriver();
}
@Step
public static void browserInit() {
getWebDriver();
}
@Step
public static void browserClear() {
clearBrowserLocalStorage();
clearBrowserCookies();
refresh();
}
@Step
public static void browserClear() {
clearBrowserLocalStorage();
clearBrowserCookies();
refresh();
}
@Step
public static void browserQuit() {
getWebDriver().quit();
}
@Step
public static void browserQuit() {
getWebDriver().quit();
}
@Step
public static void loggerSetup() {
SelenideLogger.addListener("AllureSelenide", new AllureSelenide()
.screenshots(true)
.savePageSource(false));
}
@Step
public static void loggerSetup() {
SelenideLogger.addListener("AllureSelenide", new AllureSelenide()
.screenshots(true)
.savePageSource(false));
}
}

View file

@ -39,9 +39,9 @@ public class QaseResultListener extends TestListenerAdapter implements ITestList
}
@Override
public void onTestStart(ITestResult result) {
public void onTestStart(ITestResult tr) {
getQaseTestCaseListener().onTestCaseStarted();
super.onTestStart(result);
super.onTestStart(tr);
}
@Override

View file

@ -21,6 +21,15 @@ public class WebUtils {
.perform();
}
public static void sendKeysByActions(SelenideElement element, String keys) {
log.debug("\nsendKeysByActions: {} \nsend keys '{}'", element.getSearchCriteria(), keys);
element.shouldBe(Condition.enabled);
new Actions(WebDriverRunner.getWebDriver())
.moveToElement(element)
.sendKeys(element, keys)
.perform();
}
public static void clickByJavaScript(SelenideElement element) {
log.debug("\nclickByJavaScript: {}", element.getSearchCriteria());
element.shouldBe(Condition.enabled);

View file

@ -5,19 +5,12 @@
"fields": [
{
"name": "text",
"type": [
"null",
"string"
],
"type": "string",
"default": null
},
{
"name": "value",
"type": [
"null",
"string",
"long"
],
"type": "string",
"default": null
}
]

View file

@ -21,7 +21,7 @@ public class ConnectorsTest extends BaseTest {
private static final String CONNECT_NAME = "first";
private static final List<Topic> TOPIC_LIST = new ArrayList<>();
private static final List<Connector> CONNECTOR_LIST = new ArrayList<>();
private static final String MESSAGE_CONTENT = "message_content_create_topic.json";
private static final String MESSAGE_CONTENT = "testData/topics/message_content_create_topic.json";
private static final String MESSAGE_KEY = " ";
private static final Topic TOPIC_FOR_CREATE = new Topic()
.setName("topic_for_create_connector-" + randomAlphabetic(5))
@ -34,10 +34,10 @@ public class ConnectorsTest extends BaseTest {
.setMessageContent(MESSAGE_CONTENT).setMessageKey(MESSAGE_KEY);
private static final Connector CONNECTOR_FOR_DELETE = new Connector()
.setName("sink_postgres_activities_e2e_checks_for_delete-" + randomAlphabetic(5))
.setConfig(getResourceAsString("delete_connector_config.json"));
.setConfig(getResourceAsString("testData/connectors/delete_connector_config.json"));
private static final Connector CONNECTOR_FOR_UPDATE = new Connector()
.setName("sink_postgres_activities_e2e_checks_for_update-" + randomAlphabetic(5))
.setConfig(getResourceAsString("config_for_create_connector_via_api.json"));
.setConfig(getResourceAsString("testData/connectors/config_for_create_connector_via_api.json"));
@BeforeClass(alwaysRun = true)
public void beforeClass() {
@ -56,7 +56,7 @@ public class ConnectorsTest extends BaseTest {
public void createConnector() {
Connector connectorForCreate = new Connector()
.setName("sink_postgres_activities_e2e_checks-" + randomAlphabetic(5))
.setConfig(getResourceAsString("config_for_create_connector.json"));
.setConfig(getResourceAsString("testData/connectors/config_for_create_connector.json"));
navigateToConnectors();
kafkaConnectList
.clickCreateConnectorBtn();

View file

@ -57,7 +57,7 @@ public class SchemasTest extends BaseTest {
@QaseId(186)
@Test(priority = 2)
public void updateSchemaAvro() {
AVRO_API.setValuePath(System.getProperty("user.dir") + "/src/main/resources/testData/schema_avro_for_update.json");
AVRO_API.setValuePath(System.getProperty("user.dir") + "/src/main/resources/testData/schemas/schema_avro_for_update.json");
navigateToSchemaRegistryAndOpenDetails(AVRO_API.getName());
schemaDetails
.openEditSchema();
@ -74,7 +74,8 @@ public class SchemasTest extends BaseTest {
.clickSubmitButton();
schemaDetails
.waitUntilScreenReady();
Assert.assertEquals(CompatibilityLevel.CompatibilityEnum.NONE.toString(), schemaDetails.getCompatibility(), "getCompatibility()");
Assert.assertEquals(schemaDetails.getCompatibility(), CompatibilityLevel.CompatibilityEnum.NONE.toString(),
"getCompatibility()");
}
@QaseId(44)
@ -88,12 +89,12 @@ public class SchemasTest extends BaseTest {
.openCompareVersionMenu();
int versionsNumberFromDdl = schemaCreateForm
.waitUntilScreenReady()
.openSchemaVersionDdl()
.openLeftVersionDdl()
.getVersionsNumberFromList();
Assert.assertEquals(latestVersion, versionsNumberFromDdl, "Versions number is not matched");
Assert.assertEquals(versionsNumberFromDdl, latestVersion, "Versions number is not matched");
schemaCreateForm
.selectVersionFromDropDown(1);
Assert.assertEquals(53, schemaCreateForm.getMarkedLinesNumber(), "getAllMarkedLines()");
Assert.assertEquals(schemaCreateForm.getMarkedLinesNumber(), 42, "getAllMarkedLines()");
}
@QaseId(187)

View file

@ -32,7 +32,7 @@ public class MessagesTest extends BaseTest {
.setMessageKey(randomAlphabetic(5))
.setMessageContent(randomAlphabetic(10));
private static final Topic TOPIC_TO_CLEAR_AND_PURGE_MESSAGES = new Topic()
.setName("topic-to-clear-and-purge-messages-attribute-" + randomAlphabetic(5))
.setName("topic-to-clear-and-purge-messages-" + randomAlphabetic(5))
.setMessageKey(randomAlphabetic(5))
.setMessageContent(randomAlphabetic(10));
private static final Topic TOPIC_FOR_CHECK_FILTERS = new Topic()

View file

@ -11,7 +11,7 @@ import PageLoader from 'components/common/PageLoader/PageLoader';
import Dashboard from 'components/Dashboard/Dashboard';
import ClusterPage from 'components/ClusterPage/ClusterPage';
import { ThemeProvider } from 'styled-components';
import theme from 'theme/theme';
import { theme, darkTheme } from 'theme/theme';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { showServerError } from 'lib/errorHandling';
import { Toaster } from 'react-hot-toast';
@ -39,16 +39,18 @@ const queryClient = new QueryClient({
},
});
const App: React.FC = () => {
const [isDarkMode, setDarkMode] = React.useState<boolean>(false);
return (
<QueryClientProvider client={queryClient}>
<ThemeProvider theme={theme}>
<Suspense fallback={<PageLoader />}>
<GlobalSettingsProvider>
<GlobalSettingsProvider>
<ThemeProvider theme={isDarkMode ? darkTheme : theme}>
<Suspense fallback={<PageLoader />}>
<UserInfoRolesAccessProvider>
<ConfirmContextProvider>
<GlobalCSS />
<S.Layout>
<PageContainer>
<PageContainer setDarkMode={setDarkMode}>
<Routes>
{['/', '/ui', '/ui/clusters'].map((path) => (
<Route
@ -83,9 +85,9 @@ const App: React.FC = () => {
<ConfirmationModal />
</ConfirmContextProvider>
</UserInfoRolesAccessProvider>
</GlobalSettingsProvider>
</Suspense>
</ThemeProvider>
</Suspense>
</ThemeProvider>
</GlobalSettingsProvider>
</QueryClientProvider>
);
};

View file

@ -15,8 +15,8 @@ export const RestartButton = styled.div`
border-radius: 4px;
display: flex;
-webkit-align-items: center;
background: #e8e8fc;
color: #171a1c;
background: ${({ theme }) => theme.button.primary.backgroundColor.normal};
color: ${({ theme }) => theme.button.primary.color.normal};
font-size: 14px;
font-weight: 500;
height: 32px;

View file

@ -27,7 +27,7 @@ const TopicsCell: React.FC<CellContext<FullConnectorInfo, unknown>> = ({
return (
<S.TagsWrapper>
{topics?.map((t) => (
<Tag key={t} color="gray">
<Tag key={t} color="green">
<span
role="link"
onClick={(e) => navigateToTopic(e, t)}

View file

@ -1,16 +1,17 @@
import styled, { css } from 'styled-components';
export const TopicContentWrapper = styled.tr`
background-color: ${({ theme }) =>
theme.consumerTopicContent.backgroundColor};
background-color: ${({ theme }) => theme.default.backgroundColor};
& > td {
padding: 16px !important;
background-color: ${({ theme }) =>
theme.consumerTopicContent.td.backgroundColor};
}
`;
export const ContentBox = styled.div(
({ theme }) => css`
background-color: ${theme.menu.backgroundColor.normal};
background-color: ${theme.default.backgroundColor};
padding: 20px;
border-radius: 8px;
`

View file

@ -5,4 +5,5 @@ export const Toolbar = styled.div`
display: flex;
justify-content: space-between;
align-items: center;
color: ${({ theme }) => theme.default.color.normal};
`;

View file

@ -11,7 +11,7 @@ export const Wrapper = styled.div`
export const Number = styled.div`
font-size: 100px;
color: ${({ theme }) => theme.errorPage.text};
color: ${({ theme }) => theme.default.color.normal};
line-height: initial;
`;

View file

@ -19,6 +19,7 @@ export const KSQLInputsWrapper = styled.div`
export const KSQLInputHeader = styled.div`
display: flex;
justify-content: space-between;
color: ${({ theme }) => theme.default.color.normal};
`;
export const KSQLButtons = styled.div`
@ -31,6 +32,7 @@ export const StreamPropertiesContainer = styled.label`
flex-direction: column;
gap: 10px;
width: 50%;
color: ${({ theme }) => theme.default.color.normal};
`;
export const InputsContainer = styled.div`
@ -49,9 +51,18 @@ export const StreamPropertiesInputWrapper = styled.div`
width: 100%;
height: 40px;
border: 1px solid grey;
&:focus {
outline: none;
border-color: ${({ theme }) => theme.input.borderColor.focus};
&::placeholder {
color: transparent;
}
}
border-radius: 4px;
font-size: 16px;
padding-left: 15px;
background-color: ${({ theme }) => theme.input.backgroundColor.normal};
color: ${({ theme }) => theme.input.color.normal};
}
`;
@ -73,8 +84,28 @@ export const SQLEditor = styled(BaseSQLEditor)(
css`
background: ${readOnly && theme.ksqlDb.query.editor.readonly.background};
.ace-cursor {
${readOnly && theme.ksqlDb.query.editor.readonly.cursor}
${readOnly && `background: ${theme.default.transparentColor} `}
}
.ace_content {
background-color: ${theme.default.backgroundColor};
color: ${theme.default.color.normal};
}
.ace_line {
background-color: ${theme.ksqlDb.query.editor.activeLine
.backgroundColor};
}
.ace_gutter-cell {
background-color: ${theme.ksqlDb.query.editor.cell.backgroundColor};
}
.ace_gutter-layer {
background-color: ${theme.ksqlDb.query.editor.layer.backgroundColor};
color: ${theme.default.color.normal};
}
.ace_cursor {
color: ${theme.ksqlDb.query.editor.cursor};
}
.ace_print-margin {
display: none;
}

View file

@ -146,6 +146,7 @@ const QueryForm: React.FC<Props> = ({
placeholder="Key"
aria-label="key"
type="text"
autoComplete="off"
/>
)}
/>
@ -166,6 +167,7 @@ const QueryForm: React.FC<Props> = ({
placeholder="Value"
aria-label="value"
type="text"
autoComplete="off"
/>
)}
/>

View file

@ -36,6 +36,7 @@ export const Title = styled.div`
max-width: 110px;
overflow: hidden;
text-overflow: ellipsis;
color: ${({ theme }) => theme.menu.titleColor};
`;
export const StatusIconWrapper = styled.svg.attrs({

View file

@ -1,6 +1,6 @@
import React from 'react';
import { render } from 'lib/testHelpers';
import theme from 'theme/theme';
import { theme } from 'theme/theme';
import { screen } from '@testing-library/react';
import * as S from 'components/Nav/ClusterTab/ClusterTab.styled';
import { ServerStatus } from 'generated-sources';

View file

@ -28,11 +28,11 @@ export const NavbarBrand = styled.div`
`;
export const SocialLink = styled.a(
({ theme: { layout, icons } }) => css`
({ theme: { icons } }) => css`
display: block;
margin-top: 5px;
cursor: pointer;
fill: ${layout.socialLink.color};
fill: ${icons.discord.normal};
&:hover {
${DiscordIcon} {
@ -60,7 +60,7 @@ export const NavbarSocial = styled.div`
display: flex;
align-items: center;
gap: 10px;
margin: 10px;
margin: 5px 10px 5px;
`;
export const NavbarItem = styled.div`
@ -138,7 +138,10 @@ export const Hyperlink = styled(Link)(
font-weight: bold;
font-size: 12px;
line-height: 16px;
color: ${theme.menu.color.active};
color: ${theme.default.color.normal};
&:hover {
color: ${theme.default.color.normal};
}
text-decoration: none;
word-break: break-word;
cursor: pointer;

View file

@ -1,17 +1,92 @@
import React from 'react';
import Select from 'components/common/Select/Select';
import Logo from 'components/common/Logo/Logo';
import Version from 'components/Version/Version';
import GitIcon from 'components/common/Icons/GitIcon';
import DiscordIcon from 'components/common/Icons/DiscordIcon';
import AutoIcon from 'components/common/Icons/AutoIcon';
import SunIcon from 'components/common/Icons/SunIcon';
import MoonIcon from 'components/common/Icons/MoonIcon';
import * as S from './NavBar.styled';
import UserInfo from './UserInfo/UserInfo';
import * as S from './NavBar.styled';
interface Props {
onBurgerClick: () => void;
setDarkMode: (value: boolean) => void;
}
const NavBar: React.FC<Props> = ({ onBurgerClick }) => {
type ThemeDropDownValue = 'auto_theme' | 'light_theme' | 'dark_theme';
const options = [
{
label: (
<>
<AutoIcon />
<div>Auto theme</div>
</>
),
value: 'auto_theme',
},
{
label: (
<>
<SunIcon />
<div>Light theme</div>
</>
),
value: 'light_theme',
},
{
label: (
<>
<MoonIcon />
<div>Dark theme</div>
</>
),
value: 'dark_theme',
},
];
const NavBar: React.FC<Props> = ({ onBurgerClick, setDarkMode }) => {
const matchDark = window.matchMedia('(prefers-color-scheme: dark)');
const [themeMode, setThemeMode] = React.useState<ThemeDropDownValue>();
React.useLayoutEffect(() => {
const mode = localStorage.getItem('mode');
if (mode) {
setThemeMode(mode as ThemeDropDownValue);
if (mode === 'auto_theme') {
setDarkMode(matchDark.matches);
} else if (mode === 'light_theme') {
setDarkMode(false);
} else if (mode === 'dark_theme') {
setDarkMode(true);
}
} else {
setThemeMode('auto_theme');
}
}, []);
React.useEffect(() => {
if (themeMode === 'auto_theme') {
setDarkMode(matchDark.matches);
matchDark.addListener((e) => {
setDarkMode(e.matches);
});
}
}, [matchDark, themeMode]);
const onChangeThemeMode = (value: string | number) => {
setThemeMode(value as ThemeDropDownValue);
localStorage.setItem('mode', value as string);
if (value === 'light_theme') {
setDarkMode(false);
} else if (value === 'dark_theme') {
setDarkMode(true);
}
};
return (
<S.Navbar role="navigation" aria-label="Page Header">
<S.NavbarBrand>
@ -39,6 +114,12 @@ const NavBar: React.FC<Props> = ({ onBurgerClick }) => {
</S.NavbarBrand>
</S.NavbarBrand>
<S.NavbarSocial>
<Select
options={options}
value={themeMode}
onChange={onChangeThemeMode}
isThemeMode
/>
<S.SocialLink
href="https://github.com/provectus/kafka-ui"
target="_blank"

View file

@ -2,14 +2,12 @@ import React from 'react';
import { Dropdown, DropdownItem } from 'components/common/Dropdown';
import UserIcon from 'components/common/Icons/UserIcon';
import DropdownArrowIcon from 'components/common/Icons/DropdownArrowIcon';
import { useTheme } from 'styled-components';
import { useUserInfo } from 'lib/hooks/useUserInfo';
import * as S from './UserInfo.styled';
const UserInfo = () => {
const { username } = useUserInfo();
const theme = useTheme();
return username ? (
<Dropdown
@ -17,11 +15,7 @@ const UserInfo = () => {
<S.Wrapper>
<UserIcon />
<S.Text>{username}</S.Text>
<DropdownArrowIcon
isOpen={false}
style={{}}
color={theme.button.primary.invertedColors.normal}
/>
<DropdownArrowIcon isOpen={false} />
</S.Wrapper>
}
>

View file

@ -12,7 +12,15 @@ jest.mock('components/NavBar/UserInfo/UserInfo', () => () => (
describe('NavBar', () => {
beforeEach(() => {
render(<NavBar onBurgerClick={jest.fn()} />);
Object.defineProperty(window, 'matchMedia', {
writable: true,
value: jest.fn().mockImplementation(() => ({
matches: false,
addListener: jest.fn(),
})),
});
render(<NavBar onBurgerClick={jest.fn()} setDarkMode={jest.fn()} />);
});
it('correctly renders header', () => {

View file

@ -5,7 +5,9 @@ import * as S from 'components/PageContainer/PageContainer.styled';
import Nav from 'components/Nav/Nav';
import useBoolean from 'lib/hooks/useBoolean';
const PageContainer: React.FC<PropsWithChildren<unknown>> = ({ children }) => {
const PageContainer: React.FC<
PropsWithChildren<{ setDarkMode: (value: boolean) => void }>
> = ({ children, setDarkMode }) => {
const {
value: isSidebarVisible,
toggle,
@ -19,7 +21,7 @@ const PageContainer: React.FC<PropsWithChildren<unknown>> = ({ children }) => {
return (
<>
<NavBar onBurgerClick={toggle} />
<NavBar onBurgerClick={toggle} setDarkMode={setDarkMode} />
<S.Container>
<S.Sidebar aria-label="Sidebar" $visible={isSidebarVisible}>
<Nav />

View file

@ -19,9 +19,16 @@ describe('Page Container', () => {
(useClusters as jest.Mock).mockImplementation(() => ({
isSuccess: false,
}));
Object.defineProperty(window, 'matchMedia', {
writable: true,
value: jest.fn().mockImplementation(() => ({
matches: false,
addListener: jest.fn(),
})),
});
render(
<PageContainer>
<PageContainer setDarkMode={jest.fn()}>
<div>child</div>
</PageContainer>
);

View file

@ -1,11 +1,10 @@
import Heading from 'components/common/heading/Heading.styled';
import React from 'react';
import styled from 'styled-components';
import theme from 'theme/theme';
export const Wrapper = styled.div`
width: 100%;
background-color: ${theme.layout.stuffColor};
background-color: ${({ theme }) => theme.layout.stuffColor};
padding: 16px;
display: flex;
justify-content: center;
@ -14,7 +13,7 @@ export const Wrapper = styled.div`
max-height: 700px;
& > * {
background-color: ${theme.panelColor};
background-color: ${({ theme }) => theme.default.backgroundColor};
padding: 24px;
overflow-y: scroll;
}
@ -33,12 +32,16 @@ export const Wrapper = styled.div`
gap: 16px;
padding-bottom: 16px;
}
p {
color: ${({ theme }) => theme.schema.backgroundColor.p};
}
}
`;
export const MetaDataLabel = styled((props) => (
<Heading level={4} {...props} />
))`
color: ${theme.lastestVersionItem.metaDataLabel.color};
color: ${({ theme }) => theme.lastestVersionItem.metaDataLabel.color};
width: 110px;
`;

View file

@ -8,12 +8,37 @@ export const DiffWrapper = styled.div`
flex-shrink: 1;
min-height: min-content;
padding-top: 1.5rem !important;
&
.ace_editor
> .ace_scroller
> .ace_content
> .ace_marker-layer
> .codeMarker {
.ace_content {
background-color: ${({ theme }) => theme.default.backgroundColor};
color: ${({ theme }) => theme.default.color.normal};
}
.ace_line {
background-color: ${({ theme }) => theme.default.backgroundColor};
}
.ace_gutter-cell {
background-color: ${({ theme }) =>
theme.ksqlDb.query.editor.cell.backgroundColor};
}
.ace_gutter-layer {
background-color: ${({ theme }) =>
theme.ksqlDb.query.editor.layer.backgroundColor};
color: ${({ theme }) => theme.default.color.normal};
}
.ace_cursor {
color: ${({ theme }) => theme.ksqlDb.query.editor.cursor};
}
.ace_print-margin {
display: none;
}
.ace_variable {
color: ${({ theme }) => theme.ksqlDb.query.editor.variable};
}
.ace_string {
color: ${({ theme }) => theme.ksqlDb.query.editor.aceString};
}
> .codeMarker {
background: ${({ theme }) => theme.icons.warningIcon};
position: absolute;
z-index: 20;

View file

@ -2,7 +2,7 @@ import styled, { css } from 'styled-components';
export const EditWrapper = styled.div`
padding: 16px;
padding-top: 0px;
padding-top: 0;
& > form {
display: flex;
flex-direction: column;
@ -44,6 +44,7 @@ export const EditorContainer = styled.div(
font-size: 16px;
line-height: 24px;
padding-bottom: 16px;
color: ${theme.heading.h4};
}
`
);

View file

@ -4,4 +4,7 @@ export const Wrapper = styled.div`
display: flex;
gap: 5px;
align-items: center;
& > div {
color: ${({ theme }) => theme.select.label};
}
`;

View file

@ -3,7 +3,7 @@ import styled from 'styled-components';
export const Wrapper = styled.div`
margin-top: 16px;
padding: 16px;
border-top: 1px solid ${({ theme }) => theme.dangerZone.borderColor};
border: 1px solid ${({ theme }) => theme.dangerZone.borderColor};
box-sizing: border-box;
width: 768px;

View file

@ -48,18 +48,24 @@ export const SeekTypeSelectorWrapper = styled.div`
export const OffsetSelector = styled(Input)`
border-radius: 0 4px 4px 0 !important;
border-left: none;
&::placeholder {
color: ${({ theme }) => theme.input.color.normal};
}
`;
export const DatePickerInput = styled(DatePicker)`
height: 32px;
border: 1px ${(props) => props.theme.select.borderColor.normal} solid;
border: 1px ${({ theme }) => theme.select.borderColor.normal} solid;
border-left: none;
border-radius: 0 4px 4px 0;
font-size: 14px;
width: 100%;
padding-left: 12px;
color: ${(props) => props.theme.select.color.normal};
background-color: ${({ theme }) => theme.input.backgroundColor.normal};
color: ${({ theme }) => theme.input.color.normal};
&::placeholder {
color: ${({ theme }) => theme.input.color.normal};
}
background-image: url('data:image/svg+xml,%3Csvg width="10" height="6" viewBox="0 0 10 6" fill="none" xmlns="http://www.w3.org/2000/svg"%3E%3Cpath d="M1 1L5 5L9 1" stroke="%23454F54"/%3E%3C/svg%3E%0A') !important;
background-repeat: no-repeat !important;
@ -83,7 +89,10 @@ export const FiltersMetrics = styled.div`
padding-top: 16px;
padding-bottom: 16px;
`;
export const Message = styled.div`
font-size: 14px;
color: ${({ theme }) => theme.metrics.filters.color.normal};
`;
export const Metric = styled.div`
color: ${({ theme }) => theme.metrics.filters.color.normal};
font-size: 12px;
@ -168,6 +177,7 @@ export const FilterTitle = styled.h3`
display: flex;
align-items: center;
justify-content: space-between;
color: ${({ theme }) => theme.modal.color};
&:after {
content: '';
width: calc(100% + 32px);
@ -176,7 +186,7 @@ export const FilterTitle = styled.h3`
top: 40px;
left: -16px;
display: inline-block;
background-color: #f1f2f3;
background-color: ${({ theme }) => theme.modal.border.top};
}
`;
@ -184,8 +194,12 @@ export const CreatedFilter = styled.p`
margin: 25px 0 10px;
font-size: 14px;
line-height: 20px;
color: ${({ theme }) => theme.savedFilter.color};
`;
export const NoSavedFilter = styled.p`
color: ${({ theme }) => theme.savedFilter.color};
`;
export const SavedFiltersContainer = styled.div`
overflow-y: auto;
height: 195px;
@ -196,6 +210,7 @@ export const SavedFiltersContainer = styled.div`
export const SavedFilterName = styled.div`
font-size: 14px;
line-height: 20px;
color: ${({ theme }) => theme.savedFilter.filterName};
`;
export const FilterButtonWrapper = styled.div`
@ -213,7 +228,7 @@ export const FilterButtonWrapper = styled.div`
top: 0;
left: -16px;
display: inline-block;
background-color: #f1f2f3;
background-color: ${({ theme }) => theme.modal.border.bottom};
}
`;
@ -241,7 +256,7 @@ export const FilterOptions = styled.div`
display: none;
width: 50px;
justify-content: space-between;
color: ${({ theme }) => theme.editFilterText.color};
color: ${({ theme }) => theme.editFilter.textColor};
`;
export const SavedFilter = styled.div.attrs({
@ -253,28 +268,28 @@ export const SavedFilter = styled.div.attrs({
height: 32px;
align-items: center;
cursor: pointer;
border-top: 1px solid #f1f2f3;
border-top: 1px solid ${({ theme }) => theme.panelColor.borderTop};
&:hover ${FilterOptions} {
display: flex;
}
&:hover {
background: ${({ theme }) => theme.layout.stuffColor};
}
background: ${(props) =>
props.selected ? props.theme.layout.stuffColor : props.theme.panelColor};
background: ${({ selected, theme }) =>
selected ? theme.layout.stuffColor : theme.modal.backgroundColor};
`;
export const ActiveSmartFilter = styled.div`
border-radius: 4px;
min-width: 115px;
height: 24px;
background: ${({ theme }) => theme.layout.stuffColor};
background: ${({ theme }) => theme.savedFilter.backgroundColor};
font-size: 14px;
line-height: 20px;
display: flex;
align-items: center;
justify-content: space-between;
color: ${({ theme }) => theme.input.label.color};
color: ${({ theme }) => theme.savedFilter.color};
padding: 16px 8px;
`;
@ -293,7 +308,7 @@ export const MessageLoading = styled.div.attrs({
})<MessageLoadingProps>`
color: ${({ theme }) => theme.heading.h3.color};
font-size: ${({ theme }) => theme.heading.h3.fontSize};
display: ${(props) => (props.isLive ? 'flex' : 'none')};
display: ${({ isLive }) => (isLive ? 'flex' : 'none')};
justify-content: space-around;
width: 250px;
`;
@ -305,7 +320,7 @@ export const StopLoading = styled.div`
`;
export const MessageLoadingSpinner = styled.div<MessageLoadingSpinnerProps>`
display: ${(props) => (props.isFetching ? 'block' : 'none')};
display: ${({ isFetching }) => (isFetching ? 'block' : 'none')};
border: 3px solid ${({ theme }) => theme.pageLoader.borderColor};
border-bottom: 3px solid ${({ theme }) => theme.pageLoader.borderBottomColor};
border-radius: 50%;
@ -334,7 +349,7 @@ export const SavedFiltersTextContainer = styled.div.attrs({
const textStyle = css`
font-size: 14px;
color: ${({ theme }) => theme.editFilterText.color};
color: ${({ theme }) => theme.editFilter.textColor};
font-weight: 500;
`;
@ -353,3 +368,9 @@ export const SeekTypeSelect = styled(Select)`
border-bottom-right-radius: 0;
user-select: none;
`;
export const Serdes = styled.div`
display: flex;
gap: 24px;
padding: 8px 0;
`;

View file

@ -517,7 +517,7 @@ const Filters: React.FC<FiltersProps> = ({
<S.ActiveSmartFilterWrapper>
<Search placeholder="Search" disabled={isTailing} />
<Button buttonType="primary" buttonSize="M" onClick={toggle}>
<Button buttonType="secondary" buttonSize="M" onClick={toggle}>
<PlusIcon />
Add Filters
</Button>
@ -542,11 +542,11 @@ const Filters: React.FC<FiltersProps> = ({
/>
)}
<S.FiltersMetrics>
<p style={{ fontSize: 14 }}>
<S.Message>
{seekDirection !== SeekDirection.TAILING &&
isFetching &&
phaseMessage}
</p>
</S.Message>
<S.MessageLoading isLive={isTailing}>
<S.MessageLoadingSpinner isFetching={isFetching} />
Loading messages.

View file

@ -63,7 +63,9 @@ const SavedFilters: FC<Props> = ({
</S.BackToCustomText>
<S.SavedFiltersContainer>
<S.CreatedFilter>Saved filters</S.CreatedFilter>
{filters.length === 0 && <p>No saved filter(s)</p>}
{filters.length === 0 && (
<S.NoSavedFilter>No saved filter(s)</S.NoSavedFilter>
)}
{filters.map((filter, index) => (
<S.SavedFilter
key={Symbol(filter.name).toString()}

View file

@ -2,7 +2,7 @@ import React from 'react';
import { render } from 'lib/testHelpers';
import * as S from 'components/Topics/Topic/Messages/Filters/Filters.styled';
import { screen } from '@testing-library/react';
import theme from 'theme/theme';
import { theme } from 'theme/theme';
describe('Filters Styled components', () => {
describe('MessageLoading component', () => {

View file

@ -22,7 +22,7 @@ export const Section = styled.div`
`;
export const ContentBox = styled.div`
background-color: white;
background-color: ${({ theme }) => theme.topicMetaData.backgroundColor};
padding: 24px;
border-radius: 8px 0 0 8px;
flex-grow: 3;
@ -37,7 +37,7 @@ export const ContentBox = styled.div`
`;
export const MetadataWrapper = styled.div`
background-color: white;
background-color: ${({ theme }) => theme.topicMetaData.backgroundColor};
padding: 24px;
border-radius: 0 8px 8px 0;
flex-grow: 1;
@ -89,7 +89,7 @@ export const Tab = styled.button<{ $active?: boolean }>(
border-radius: 0 4px 4px 0;
}
&:not(:last-child) {
border-right: 0px;
border-right: 0;
}
`
);

View file

@ -8,7 +8,7 @@ import MessageContent, {
import { TopicMessageTimestampTypeEnum } from 'generated-sources';
import userEvent from '@testing-library/user-event';
import { render } from 'lib/testHelpers';
import theme from 'theme/theme';
import { theme } from 'theme/theme';
const setupWrapper = (props?: Partial<MessageContentProps>) => {
return (

View file

@ -33,4 +33,5 @@ export const Field = styled.div`
white-space: nowrap;
overflow: hidden;
margin-right: 5px;
color: ${({ theme }) => theme.modal.color};
`;

View file

@ -2,7 +2,7 @@ import React from 'react';
import { screen } from '@testing-library/react';
import { render, WithRoute } from 'lib/testHelpers';
import Overview from 'components/Topics/Topic/Overview/Overview';
import theme from 'theme/theme';
import { theme } from 'theme/theme';
import { CleanUpPolicy, Topic } from 'generated-sources';
import ClusterContext from 'components/contexts/ClusterContext';
import userEvent from '@testing-library/user-event';

View file

@ -53,7 +53,7 @@ const Metrics: React.FC = () => {
await cancelTopicAnalysis.mutateAsync();
setIsAnalyzing(true);
}}
buttonType="primary"
buttonType="secondary"
buttonSize="M"
permission={{
resource: ResourceType.TOPIC,

View file

@ -13,6 +13,7 @@ export const NameField = styled.div`
export const CustomParamsHeading = styled.h4`
font-weight: 500;
color: ${({ theme }) => theme.heading.h4};
`;
export const Label = styled.div`
@ -29,12 +30,10 @@ export const Label = styled.div`
export const Button = styled.button<{ isActive: boolean }>`
background-color: ${({ theme, ...props }) =>
props.isActive
? theme.button.secondary.invertedColors.normal
: theme.button.secondary.backgroundColor.normal};
? theme.chips.backgroundColor.active
: theme.chips.backgroundColor.normal};
color: ${({ theme, ...props }) =>
props.isActive
? theme.button.secondary.isActiveColor
: theme.button.primary.color};
props.isActive ? theme.chips.color.active : theme.chips.color.normal};
height: 24px;
padding: 0 5px;
min-width: 51px;

View file

@ -97,6 +97,7 @@ const TopicForm: React.FC<Props> = ({
name="name"
placeholder="Topic Name"
defaultValue={topicName}
autoComplete="off"
/>
<FormError>
<ErrorMessage errors={errors} name="name" />
@ -251,7 +252,7 @@ const TopicForm: React.FC<Props> = ({
<S.ButtonWrapper>
<Button
type="button"
buttonType="primary"
buttonType="secondary"
buttonSize="L"
onClick={onCancel}
>

View file

@ -5,7 +5,7 @@ import TimeToRetainBtn, {
Props,
} from 'components/Topics/shared/Form/TimeToRetainBtn';
import { useForm, FormProvider } from 'react-hook-form';
import theme from 'theme/theme';
import { theme } from 'theme/theme';
import userEvent from '@testing-library/user-event';
describe('TimeToRetainBtn', () => {
@ -42,7 +42,7 @@ describe('TimeToRetainBtn', () => {
it('should test the non active state of the button and its styling', () => {
const buttonElement = screen.getByRole('button');
expect(buttonElement).toHaveStyle(
`background-color:${theme.button.secondary.backgroundColor.normal}`
`background-color:${theme.chips.backgroundColor.normal}`
);
expect(buttonElement).toHaveStyle(`border:none`);
});
@ -50,7 +50,7 @@ describe('TimeToRetainBtn', () => {
const buttonElement = screen.getByRole('button');
await userEvent.click(buttonElement);
expect(buttonElement).toHaveStyle(
`background-color:${theme.button.secondary.invertedColors.normal}`
`background-color:${theme.chips.backgroundColor.active}`
);
expect(buttonElement).toHaveStyle(`border:none`);
});

View file

@ -2,7 +2,7 @@ import React from 'react';
import { render } from 'lib/testHelpers';
import * as S from 'components/Topics/shared/Form/TopicForm.styled';
import { screen } from '@testing-library/react';
import theme from 'theme/theme';
import { theme } from 'theme/theme';
describe('TopicForm styled components', () => {
describe('Button', () => {
@ -11,7 +11,7 @@ describe('TopicForm styled components', () => {
const button = screen.getByRole('button');
expect(button).toHaveStyle({
border: `none`,
backgroundColor: theme.button.secondary.invertedColors.normal,
backgroundColor: theme.chips.backgroundColor.active,
});
});
@ -20,7 +20,7 @@ describe('TopicForm styled components', () => {
const button = screen.getByRole('button');
expect(button).toHaveStyle({
border: `none`,
backgroundColor: theme.button.secondary.backgroundColor.normal,
backgroundColor: theme.chips.backgroundColor.normal,
});
});
});

View file

@ -2,22 +2,22 @@ import styled, { css } from 'styled-components';
export const Wrapper = styled.div`
display: flex;
align-items: center;
align-items: baseline;
`;
const textStyle = css`
font-family: Inter, sans-serif;
font-style: normal;
font-weight: normal;
font-size: 12px;
line-height: 16px;
font-size: 14px;
line-height: 20px;
`;
export const CurrentVersion = styled.span(
({ theme }) => css`
${textStyle}
${textStyle};
color: ${theme.version.currentVersion.color};
margin-right: 0.25rem;
margin-left: 0.25rem;
`
);
@ -25,13 +25,13 @@ export const OutdatedWarning = styled.span`
${textStyle}
`;
export const SymbolWrapper = styled.span(
export const CurrentCommitLink = styled.a(
({ theme }) => css`
${textStyle}
color: ${theme.version.symbolWrapper.color};
${textStyle};
color: ${theme.version.commitLink.color};
margin-left: 0.25rem;
&:hover {
color: ${theme.version.commitLink.color};
}
`
);
export const CurrentCommitLink = styled.a`
${textStyle}
`;

View file

@ -27,8 +27,6 @@ const Version: React.FC = () => {
return (
<S.Wrapper>
<S.CurrentVersion>{currentVersion}</S.CurrentVersion>
{!!outdated && (
<S.OutdatedWarning
title={`Your app version is outdated. Current latest version is ${latestTag}`}
@ -38,8 +36,7 @@ const Version: React.FC = () => {
)}
{commit && (
<>
<S.SymbolWrapper>&#40;</S.SymbolWrapper>
<div>
<S.CurrentCommitLink
title="Current commit"
target="__blank"
@ -47,9 +44,9 @@ const Version: React.FC = () => {
>
{commit}
</S.CurrentCommitLink>
<S.SymbolWrapper>&#41;</S.SymbolWrapper>
</>
</div>
)}
<S.CurrentVersion>{currentVersion}</S.CurrentVersion>
</S.Wrapper>
);
};

View file

@ -9,6 +9,8 @@ jest.mock('components/Nav/Nav', () => () => <div>Navigation</div>);
jest.mock('components/Version/Version', () => () => <div>Version</div>);
jest.mock('components/NavBar/NavBar', () => () => <div>NavBar</div>);
jest.mock('lib/hooks/api/roles', () => ({
useGetUserInfo: jest.fn(),
}));
@ -33,4 +35,8 @@ describe('App', () => {
it('Renders navigation', async () => {
expect(screen.getByText('Navigation')).toBeInTheDocument();
});
it('Renders NavBar', async () => {
expect(screen.getByText('NavBar')).toBeInTheDocument();
});
});

View file

@ -16,52 +16,58 @@ const StyledButton = styled.button<ButtonProps>`
border-radius: 4px;
white-space: nowrap;
background: ${(props) =>
props.isInverted
background: ${({ isInverted, buttonType, theme }) =>
isInverted
? 'transparent'
: props.theme.button[props.buttonType].backgroundColor.normal};
color: ${(props) =>
props.isInverted
? props.theme.button[props.buttonType].invertedColors.normal
: props.theme.button[props.buttonType].color};
font-size: ${(props) => props.theme.button.fontSize[props.buttonSize]};
: theme.button[buttonType].backgroundColor.normal};
color: ${({ isInverted, buttonType, theme }) =>
isInverted
? theme.button[buttonType].invertedColors.normal
: theme.button[buttonType].color.normal};
font-size: ${({ theme, buttonSize }) => theme.button.fontSize[buttonSize]};
font-weight: 500;
height: ${(props) => props.theme.button.height[props.buttonSize]};
height: ${({ theme, buttonSize }) => theme.button.height[buttonSize]};
&:hover:enabled {
background: ${(props) =>
props.isInverted
background: ${({ isInverted, buttonType, theme }) =>
isInverted
? 'transparent'
: props.theme.button[props.buttonType].backgroundColor.hover};
color: ${(props) =>
props.isInverted
? props.theme.button[props.buttonType].invertedColors.hover
: props.theme.button[props.buttonType].color};
: theme.button[buttonType].backgroundColor.hover};
color: ${({ isInverted, buttonType, theme }) =>
isInverted
? theme.button[buttonType].invertedColors.hover
: theme.button[buttonType].color};
cursor: pointer;
}
&:active:enabled {
background: ${(props) =>
props.isInverted
background: ${({ isInverted, buttonType, theme }) =>
isInverted
? 'transparent'
: props.theme.button[props.buttonType].backgroundColor.active};
color: ${(props) =>
props.isInverted
? props.theme.button[props.buttonType].invertedColors.active
: props.theme.button[props.buttonType].color};
: theme.button[buttonType].backgroundColor.active};
color: ${({ isInverted, buttonType, theme }) =>
isInverted
? theme.button[buttonType].invertedColors.active
: theme.button[buttonType].color};
}
&:disabled {
opacity: 0.5;
cursor: not-allowed;
background: ${({ buttonType, theme }) =>
theme.button[buttonType].backgroundColor.disabled};
color: ${({ buttonType, theme }) =>
theme.button[buttonType].color.disabled};
}
& a {
color: ${(props) => props.theme.button.primary.color};
color: ${({ theme }) => theme.button.primary.color};
}
& :first-of-type {
svg {
margin-right: 7px;
}
& svg {
margin-right: 7px;
fill: ${({ theme, disabled, buttonType }) =>
disabled
? theme.button[buttonType].color.disabled
: theme.button[buttonType].color.normal};
}
`;

View file

@ -2,7 +2,7 @@ import React from 'react';
import { Button } from 'components/common/Button/Button';
import { screen } from '@testing-library/react';
import { render } from 'lib/testHelpers';
import theme from 'theme/theme';
import { theme } from 'theme/theme';
describe('Button', () => {
it('renders small primary Button', () => {
@ -10,7 +10,7 @@ describe('Button', () => {
expect(screen.getByRole('button')).toBeInTheDocument();
expect(screen.getByRole('button')).toHaveStyleRule(
'color',
theme.button.primary.color
theme.button.primary.color.normal
);
expect(screen.getByRole('button')).toHaveStyleRule(
'font-size',
@ -23,7 +23,7 @@ describe('Button', () => {
expect(screen.getByRole('button')).toBeInTheDocument();
expect(screen.getByRole('button')).toHaveStyleRule(
'color',
theme.button.secondary.color
theme.button.secondary.color.normal
);
expect(screen.getByRole('button')).toHaveStyleRule(
'font-size',
@ -36,7 +36,7 @@ describe('Button', () => {
expect(screen.getByRole('button')).toBeInTheDocument();
expect(screen.getByRole('button')).toHaveStyleRule(
'color',
theme.button.secondary.color
theme.button.secondary.color.normal
);
});

View file

@ -26,14 +26,14 @@ export const Overlay = styled.div(
);
export const Modal = styled.div(
({ theme: { modal } }) => css`
({ theme: { modal, confirmModal } }) => css`
position: absolute;
display: flex;
flex-direction: column;
width: 560px;
border-radius: 8px;
background-color: ${modal.backgroundColor};
background-color: ${confirmModal.backgroundColor};
filter: drop-shadow(0px 4px 16px ${modal.shadow});
`
);
@ -43,6 +43,7 @@ export const Header = styled.div`
text-align: start;
padding: 16px;
width: 100%;
color: ${({ theme }) => theme.modal.color};
`;
export const Content = styled.div(
@ -51,6 +52,7 @@ export const Content = styled.div(
width: 100%;
border-top: 1px solid ${modal.border.top};
border-bottom: 1px solid ${modal.border.bottom};
color: ${modal.contentColor};
`
);

View file

@ -7,10 +7,11 @@ interface Props {
export const ControlPanelWrapper = styled.div<Props>`
display: flex;
align-items: center;
padding: 0px 16px;
margin: 0px 0px 16px;
padding: 0 16px;
margin: 0 0 16px;
width: 100%;
gap: 16px;
color: ${({ theme }) => theme.default.color.normal};
& > *:first-child {
width: ${(props) => (props.hasInput ? '38%' : 'auto')};
}

View file

@ -70,7 +70,7 @@ export const DropdownButton = styled.button`
`;
export const DangerItem = styled.div`
color: ${({ theme: { dropdown } }) => dropdown.item.color.danger};
color: ${({ theme: { dropdown } }) => dropdown.item.color.normal};
`;
export const DropdownItemHint = styled.div`
@ -84,4 +84,5 @@ export const Wrapper = styled.div`
display: inline-flex;
align-items: center;
justify-content: end;
color: ${({ theme: { dropdown } }) => dropdown.item.color.normal};
`;

View file

@ -43,5 +43,37 @@ Editor.displayName = 'Editor';
export default styled(Editor)`
&.ace-tomorrow {
background: transparent;
.ace_gutter {
background-color: ${({ theme }) =>
theme.ksqlDb.query.editor.layer.backgroundColor};
}
.ace_gutter-active-line {
background-color: ${({ theme }) =>
theme.ksqlDb.query.editor.cell.backgroundColor};
color: ${({ theme }) => theme.default.color.normal};
}
.ace_line {
background-color: ${({ theme }) => theme.default.backgroundColor};
color: ${({ theme }) => theme.default.color.normal};
}
.ace_cursor {
color: ${({ theme }) => theme.ksqlDb.query.editor.cursor};
}
.ace_active-line {
background-color: ${({ theme }) =>
theme.ksqlDb.query.editor.cell.backgroundColor};
}
.ace_gutter-cell {
color: ${({ theme }) => theme.default.color.normal};
}
.ace_variable {
color: ${({ theme }) => theme.ksqlDb.query.editor.variable};
}
.ace_string {
color: ${({ theme }) => theme.ksqlDb.query.editor.aceString};
}
.ace_print-margin {
display: none;
}
}
`;

View file

@ -1,6 +1,13 @@
import styled from 'styled-components';
export const Wrapper = styled.div`
background-color: ${({ theme }) => theme.viewer.wrapper};
background-color: ${({ theme }) => theme.viewer.wrapper.backgroundColor};
padding: 8px 16px;
.ace_active-line {
background-color: ${({ theme }) =>
theme.viewer.wrapper.backgroundColor} !important;
}
.ace_line {
color: ${({ theme }) => theme.viewer.wrapper.color} !important;
}
`;

View file

@ -1,15 +1,20 @@
import React from 'react';
import { useTheme } from 'styled-components';
const ArrowDownIcon: React.FC = () => (
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 384 512"
width="10"
height="10"
>
{/* Font Awesome Pro 6.1.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2022 Fonticons, Inc. */}
<path d="M374.6 310.6l-160 160C208.4 476.9 200.2 480 192 480s-16.38-3.125-22.62-9.375l-160-160c-12.5-12.5-12.5-32.75 0-45.25s32.75-12.5 45.25 0L160 370.8V64c0-17.69 14.33-31.1 31.1-31.1S224 46.31 224 64v306.8l105.4-105.4c12.5-12.5 32.75-12.5 45.25 0S387.1 298.1 374.6 310.6z" />
</svg>
);
const ArrowDownIcon: React.FC = () => {
const theme = useTheme();
return (
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 384 512"
width="10"
height="10"
fill={theme.icons.arrowDownIcon}
>
{/* Font Awesome Pro 6.1.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2022 Fonticons, Inc. */}
<path d="M374.6 310.6l-160 160C208.4 476.9 200.2 480 192 480s-16.38-3.125-22.62-9.375l-160-160c-12.5-12.5-12.5-32.75 0-45.25s32.75-12.5 45.25 0L160 370.8V64c0-17.69 14.33-31.1 31.1-31.1S224 46.31 224 64v306.8l105.4-105.4c12.5-12.5 32.75-12.5 45.25 0S387.1 298.1 374.6 310.6z" />
</svg>
);
};
export default ArrowDownIcon;

View file

@ -0,0 +1,28 @@
import React from 'react';
import { useTheme } from 'styled-components';
const AutoIcon: React.FC = () => {
const theme = useTheme();
return (
<svg
width="14"
height="15"
viewBox="0 0 14 15"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M7.92385 8.49072L7.03019 5.8418H6.97336L6.07796 8.49072H7.92385Z"
fill={theme.icons.autoIcon}
/>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M7 14.7422C10.866 14.7422 14 11.6082 14 7.74219C14 3.87619 10.866 0.742188 7 0.742188C3.13401 0.742188 0 3.87619 0 7.74219C0 11.6082 3.13401 14.7422 7 14.7422ZM3.5 11.2422H5.14789L5.68745 9.646H8.3136L8.85211 11.2422H10.5L7.99264 4.24219H6.01091L3.5 11.2422Z"
fill={theme.icons.autoIcon}
/>
</svg>
);
};
export default AutoIcon;

View file

@ -1,6 +1,8 @@
import React from 'react';
import { useTheme } from 'styled-components';
const CancelIcon: React.FC = () => {
const theme = useTheme();
return (
<svg
xmlns="http://www.w3.org/2000/svg"
@ -17,7 +19,7 @@ const CancelIcon: React.FC = () => {
data-name="layer1"
d="M53.122 48.88L36.243 32l16.878-16.878a3 3 0 0 0-4.242-4.242L32 27.758l-16.878-16.88a3 3 0 0 0-4.243 4.243l16.878 16.88-16.88 16.88a3 3 0 0 0 4.243 4.241L32 36.243l16.878 16.88a3 3 0 0 0 4.244-4.243z"
fill="none"
stroke="#202020"
stroke={theme.icons.cancelIcon}
strokeMiterlimit="10"
strokeWidth="2"
strokeLinejoin="round"

View file

@ -16,7 +16,7 @@ const CheckmarkIcon: FC = () => {
<path
data-name="layer1"
fill="none"
stroke="#202020"
stroke="#FFFFFF"
strokeMiterlimit="10"
strokeWidth="2"
d="M2 30l21 22 39-40"

View file

@ -1,15 +1,20 @@
import React from 'react';
import { useTheme } from 'styled-components';
const ClockIcon: React.FC = () => (
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 512 512"
width={10}
height={10}
>
{/* Font Awesome Pro 6.1.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2022 Fonticons, Inc. */}
<path d="M232 120C232 106.7 242.7 96 256 96C269.3 96 280 106.7 280 120V243.2L365.3 300C376.3 307.4 379.3 322.3 371.1 333.3C364.6 344.3 349.7 347.3 338.7 339.1L242.7 275.1C236 271.5 232 264 232 255.1L232 120zM256 0C397.4 0 512 114.6 512 256C512 397.4 397.4 512 256 512C114.6 512 0 397.4 0 256C0 114.6 114.6 0 256 0zM48 256C48 370.9 141.1 464 256 464C370.9 464 464 370.9 464 256C464 141.1 370.9 48 256 48C141.1 48 48 141.1 48 256z" />
</svg>
);
const ClockIcon: React.FC = () => {
const theme = useTheme();
return (
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 512 512"
width={10}
height={10}
fill={theme.icons.clockIcon}
>
{/* Font Awesome Pro 6.1.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2022 Fonticons, Inc. */}
<path d="M232 120C232 106.7 242.7 96 256 96C269.3 96 280 106.7 280 120V243.2L365.3 300C376.3 307.4 379.3 322.3 371.1 333.3C364.6 344.3 349.7 347.3 338.7 339.1L242.7 275.1C236 271.5 232 264 232 255.1L232 120zM256 0C397.4 0 512 114.6 512 256C512 397.4 397.4 512 256 512C114.6 512 0 397.4 0 256C0 114.6 114.6 0 256 0zM48 256C48 370.9 141.1 464 256 464C370.9 464 464 370.9 464 256C464 141.1 370.9 48 256 48C141.1 48 48 141.1 48 256z" />
</svg>
);
};
export default ClockIcon;

View file

@ -7,7 +7,7 @@ const DeleteIcon: React.FC = () => {
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 448 512"
fill={theme.icons.deleteIcon}
fill={theme.editFilter.deleteIconColor}
width="14"
height="14"
>

View file

@ -7,21 +7,21 @@ interface Props {
color?: string;
}
const DropdownArrowIcon: React.FC<Props> = ({ isOpen, style, color }) => {
const DropdownArrowIcon: React.FC<Props> = ({ isOpen }) => {
const theme = useTheme();
return (
<svg
width="24"
height="24"
fill="none"
style={style || { position: 'absolute', right: '5px' }}
width="10"
height="5"
viewBox="0 0 10 5"
fill="currentColor"
stroke="currentColor"
strokeWidth="2"
color={color || theme.icons.dropdownArrowIcon}
xmlns="http://www.w3.org/2000/svg"
color={theme.icons.dropdownArrowIcon}
transform={isOpen ? 'rotate(180)' : ''}
>
<path d="M6 9L12 15 18 9" />
<path d="M0.646447 0.146447C0.841709 -0.0488155 1.15829 -0.0488155 1.35355 0.146447L5 3.79289L8.64645 0.146447C8.84171 -0.0488155 9.15829 -0.0488155 9.35355 0.146447C9.54882 0.341709 9.54882 0.658291 9.35355 0.853553L5.35355 4.85355C5.15829 5.04882 4.84171 5.04882 4.64645 4.85355L0.646447 0.853553C0.451184 0.658291 0.451184 0.341709 0.646447 0.146447Z" />
</svg>
);
};

View file

@ -1,6 +1,8 @@
import React, { FC } from 'react';
import { useTheme } from 'styled-components';
const EditIcon: FC = () => {
const theme = useTheme();
return (
<svg
viewBox="0 0 64 64"
@ -17,7 +19,7 @@ const EditIcon: FC = () => {
d="M54.368 17.674l6.275-6.267-8.026-8.025-6.274 6.267"
strokeWidth="2"
strokeMiterlimit="10"
stroke="#202020"
stroke={theme.icons.editIcon}
fill="none"
data-name="layer2"
strokeLinejoin="round"
@ -27,7 +29,7 @@ const EditIcon: FC = () => {
d="M17.766 54.236l36.602-36.562-8.025-8.025L9.74 46.211 3.357 60.618l14.409-6.382zM9.74 46.211l8.026 8.025"
strokeWidth="2"
strokeMiterlimit="10"
stroke="#202020"
stroke={theme.icons.editIcon}
fill="none"
data-name="layer1"
strokeLinejoin="round"

View file

@ -1,15 +1,20 @@
import React from 'react';
import { useTheme } from 'styled-components';
const FileIcon: React.FC = () => (
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 384 512"
width="10"
height="10"
>
{/* Font Awesome Pro 6.1.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2022 Fonticons, Inc. */}
<path d="M365.3 93.38l-74.63-74.64C278.6 6.742 262.3 0 245.4 0L64-.0001c-35.35 0-64 28.65-64 64l.0065 384c0 35.34 28.65 64 64 64H320c35.2 0 64-28.8 64-64V138.6C384 121.7 377.3 105.4 365.3 93.38zM336 448c0 8.836-7.164 16-16 16H64.02c-8.838 0-16-7.164-16-16L48 64.13c0-8.836 7.164-16 16-16h160L224 128c0 17.67 14.33 32 32 32h79.1V448zM96 280C96 293.3 106.8 304 120 304h144C277.3 304 288 293.3 288 280S277.3 256 264 256h-144C106.8 256 96 266.8 96 280zM264 352h-144C106.8 352 96 362.8 96 376s10.75 24 24 24h144c13.25 0 24-10.75 24-24S277.3 352 264 352z" />
</svg>
);
const FileIcon: React.FC = () => {
const theme = useTheme();
return (
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 384 512"
width="10"
height="10"
fill={theme.icons.fileIcon}
>
{/* Font Awesome Pro 6.1.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2022 Fonticons, Inc. */}
<path d="M365.3 93.38l-74.63-74.64C278.6 6.742 262.3 0 245.4 0L64-.0001c-35.35 0-64 28.65-64 64l.0065 384c0 35.34 28.65 64 64 64H320c35.2 0 64-28.8 64-64V138.6C384 121.7 377.3 105.4 365.3 93.38zM336 448c0 8.836-7.164 16-16 16H64.02c-8.838 0-16-7.164-16-16L48 64.13c0-8.836 7.164-16 16-16h160L224 128c0 17.67 14.33 32 32 32h79.1V448zM96 280C96 293.3 106.8 304 120 304h144C277.3 304 288 293.3 288 280S277.3 256 264 256h-144C106.8 256 96 266.8 96 280zM264 352h-144C106.8 352 96 362.8 96 376s10.75 24 24 24h144c13.25 0 24-10.75 24-24S277.3 352 264 352z" />
</svg>
);
};
export default FileIcon;

View file

@ -1,50 +1,29 @@
import React from 'react';
import { useTheme } from 'styled-components';
const InfoIcon: React.FC = () => {
const theme = useTheme();
return (
<svg
width="14"
height="15"
viewBox="0 0 14 15"
fill="red"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 64 64"
width="12"
height="12"
aria-labelledby="title"
aria-describedby="desc"
role="img"
>
<desc>A line styled icon from Orion Icon Library.</desc>
<circle
data-name="layer2"
cx="32"
cy="32"
r="30"
fill="none"
stroke="#202020"
strokeMiterlimit="10"
strokeWidth="2"
strokeLinejoin="round"
strokeLinecap="round"
<path
fillRule="evenodd"
clipRule="evenodd"
d="M7 1.81911C3.72878 1.81911 1.07692 4.47096 1.07692 7.74219C1.07692 11.0134 3.72878 13.6653 7 13.6653C10.2712 13.6653 12.9231 11.0134 12.9231 7.74219C12.9231 4.47096 10.2712 1.81911 7 1.81911ZM0 7.74219C0 3.87619 3.13401 0.742188 7 0.742188C10.866 0.742188 14 3.87619 14 7.74219C14 11.6082 10.866 14.7422 7 14.7422C3.13401 14.7422 0 11.6082 0 7.74219Z"
fill={theme.icons.infoIcon}
/>
<path
data-name="layer1"
fill="none"
stroke="#202020"
strokeMiterlimit="10"
strokeWidth="2"
d="M28 26h4v22m-4 .008h8"
strokeLinejoin="round"
strokeLinecap="round"
d="M6 4.74219C6 4.1899 6.44772 3.74219 7 3.74219C7.55228 3.74219 8 4.1899 8 4.74219V7.74219C8 8.29447 7.55228 8.74219 7 8.74219C6.44772 8.74219 6 8.29447 6 7.74219V4.74219Z"
fill={theme.icons.infoIcon}
/>
<circle
data-name="layer1"
cx="31"
cy="19"
r="2"
fill="none"
stroke="#202020"
strokeMiterlimit="10"
strokeWidth="2"
strokeLinejoin="round"
strokeLinecap="round"
<path
d="M6 11.2422C6 10.6899 6.44772 10.2422 7 10.2422C7.55228 10.2422 8 10.6899 8 11.2422C8 11.7945 7.55228 12.2422 7 12.2422C6.44772 12.2422 6 11.7945 6 11.2422Z"
fill={theme.icons.infoIcon}
/>
</svg>
);

View file

@ -1,10 +1,19 @@
import styled from 'styled-components';
export const Svg = styled.svg`
type Props = {
isOpen?: boolean;
};
export const Svg = styled.svg<Props>`
& > path {
fill: ${({ theme }) => theme.icons.messageToggleIcon.normal};
fill: ${({ theme, isOpen }) =>
isOpen
? theme.icons.messageToggleIcon.active
: theme.icons.messageToggleIcon.normal};
&:hover {
fill: ${({ theme }) => theme.icons.messageToggleIcon.hover};
}
&:active {
fill: ${({ theme }) => theme.icons.messageToggleIcon.active};
}
}
`;

View file

@ -9,6 +9,7 @@ const MessageToggleIcon: React.FC<Props> = ({ isOpen }) => {
if (isOpen) {
return (
<S.Svg
isOpen={isOpen}
width="16"
height="16"
viewBox="0 0 16 16"

View file

@ -0,0 +1,22 @@
import React from 'react';
import { useTheme } from 'styled-components';
const MoonIcon: React.FC = () => {
const theme = useTheme();
return (
<svg
width="11"
height="11.99"
viewBox="0 0 12 12"
fill={theme.icons.moonIcon}
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M11.6624 9.31544C11.5535 9.32132 11.4438 9.3243 11.3334 9.3243C8.01971 9.3243 5.33342 6.63801 5.33342 3.3243C5.33342 2.09476 5.70325 0.951604 6.33775 0C3.17705 0.170804 0.666748 2.78781 0.666748 5.99113C0.666748 9.30484 3.35304 11.9911 6.66675 11.9911C8.75092 11.9911 10.5869 10.9285 11.6624 9.31544Z"
fill={theme.icons.moonIcon}
/>
</svg>
);
};
export default MoonIcon;

View file

@ -9,7 +9,7 @@ const SavedIcon: FC = () => {
width="18"
height="20"
viewBox="0 0 18 20"
fill="none"
fill={theme.icons.savedIcon}
xmlns="http://www.w3.org/2000/svg"
>
<path

View file

@ -0,0 +1,54 @@
import React from 'react';
import { useTheme } from 'styled-components';
const SunIcon: React.FC = () => {
const theme = useTheme();
return (
<svg
width="14"
height="14"
viewBox="0 0 14 14"
fill={theme.icons.sunIcon}
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M11.4545 7C11.4545 9.46018 9.46018 11.4545 7 11.4545C4.53982 11.4545 2.54545 9.46018 2.54545 7C2.54545 4.53982 4.53982 2.54545 7 2.54545C9.46018 2.54545 11.4545 4.53982 11.4545 7Z"
fill={theme.icons.sunIcon}
/>
<path
d="M7.63636 0.636364C7.63636 0.987818 7.35145 1.27273 7 1.27273C6.64855 1.27273 6.36364 0.987818 6.36364 0.636364C6.36364 0.28491 6.64855 0 7 0C7.35145 0 7.63636 0.28491 7.63636 0.636364Z"
fill={theme.icons.sunIcon}
/>
<path
d="M7.63636 13.3636C7.63636 13.7151 7.35145 14 7 14C6.64855 14 6.36364 13.7151 6.36364 13.3636C6.36364 13.0122 6.64855 12.7273 7 12.7273C7.35145 12.7273 7.63636 13.0122 7.63636 13.3636Z"
fill={theme.icons.sunIcon}
/>
<path
d="M13.3636 7.63636C13.0122 7.63636 12.7273 7.35145 12.7273 7C12.7273 6.64855 13.0122 6.36364 13.3636 6.36364C13.7151 6.36364 14 6.64855 14 7C14 7.35145 13.7151 7.63636 13.3636 7.63636Z"
fill={theme.icons.sunIcon}
/>
<path
d="M0.636364 7.63636C0.28491 7.63636 -1.53625e-08 7.35145 0 7C1.53625e-08 6.64855 0.28491 6.36364 0.636364 6.36364C0.987818 6.36364 1.27273 6.64855 1.27273 7C1.27273 7.35145 0.987818 7.63636 0.636364 7.63636Z"
fill={theme.icons.sunIcon}
/>
<path
d="M11.9497 2.95018C11.7012 3.19869 11.2983 3.19869 11.0498 2.95018C10.8013 2.70166 10.8013 2.29874 11.0498 2.05022C11.2983 1.80171 11.7012 1.80171 11.9497 2.05022C12.1983 2.29874 12.1983 2.70166 11.9497 2.95018Z"
fill={theme.icons.sunIcon}
/>
<path
d="M2.95021 11.9497C2.70169 12.1982 2.29877 12.1982 2.05025 11.9497C1.80174 11.7012 1.80174 11.2983 2.05025 11.0498C2.29877 10.8012 2.70169 10.8012 2.95021 11.0498C3.19872 11.2983 3.19872 11.7012 2.95021 11.9497Z"
fill={theme.icons.sunIcon}
/>
<path
d="M11.0498 11.9497C10.8013 11.7012 10.8013 11.2983 11.0498 11.0498C11.2983 10.8012 11.7012 10.8012 11.9497 11.0498C12.1983 11.2983 12.1983 11.7012 11.9497 11.9497C11.7012 12.1982 11.2983 12.1982 11.0498 11.9497Z"
fill={theme.icons.sunIcon}
/>
<path
d="M2.05025 2.95018C1.80174 2.70166 1.80174 2.29874 2.05025 2.05022C2.29877 1.80171 2.70169 1.80171 2.95021 2.05022C3.19872 2.29874 3.19872 2.70166 2.95021 2.95018C2.70169 3.19869 2.29877 3.19869 2.05025 2.95018Z"
fill={theme.icons.sunIcon}
/>
</svg>
);
};
export default SunIcon;

View file

@ -1,5 +1,5 @@
import React from 'react';
import styled, { useTheme } from 'styled-components';
import styled from 'styled-components';
const WarningIconContainer = styled.span`
align-items: center;
@ -10,27 +10,21 @@ const WarningIconContainer = styled.span`
`;
const WarningIcon: React.FC = () => {
const theme = useTheme();
return (
<WarningIconContainer>
<svg
width="14"
height="13"
viewBox="0 0 14 13"
fill="none"
xmlns="http://www.w3.org/2000/svg"
version="1.0"
width="18px"
height="16px"
viewBox="0 0 1280.000000 1126.000000"
preserveAspectRatio="xMidYMid meet"
>
<g
transform="translate(0.000000,1126.000000) scale(0.100000,-0.100000)"
fill={theme.icons.warningIcon}
stroke="none"
>
<path d="M6201 11240 c-41 -10 -113 -37 -160 -61 -70 -35 -105 -62 -187 -144 -61 -60 -124 -134 -157 -185 -85 -132 -681 -1182 -2962 -5215 -793 -1402 -1714 -3032 -2047 -3620 -333 -589 -617 -1098 -631 -1131 -79 -187 -72 -394 19 -559 15 -28 64 -86 108 -130 91 -90 177 -139 306 -175 l76 -20 5879 2 5880 3 81 27 c363 124 494 499 304 878 -21 43 -899 1580 -1951 3417 -1052 1836 -2308 4029 -2791 4873 -484 844 -909 1580 -946 1635 -118 177 -268 311 -419 373 -125 52 -272 64 -402 32z m1607 -3410 c793 -1383 2019 -3523 2724 -4755 l1283 -2240 -2712 -3 c-1492 -1 -3934 -1 -5427 0 l-2715 3 1666 2945 c3188 5637 3725 6583 3734 6572 4 -4 655 -1139 1447 -2522z" />
<path d="M6290 7874 c-14 -3 -61 -14 -104 -25 -390 -98 -706 -474 -706 -837 0 -46 22 -254 50 -461 27 -207 113 -857 190 -1446 201 -1535 199 -1517 216 -1581 42 -165 141 -297 271 -361 67 -33 86 -38 168 -41 152 -7 246 30 348 136 99 105 144 224 176 464 11 84 61 462 111 841 49 378 131 996 180 1375 50 378 100 756 111 840 24 182 25 305 4 387 -82 323 -360 599 -693 686 -75 20 -266 33 -322 23z" />
<path d="M6322 2739 c-345 -44 -594 -371 -552 -726 20 -166 86 -301 204 -410 114 -107 237 -160 391 -170 187 -11 336 47 475 187 134 134 192 273 193 465 1 116 -13 183 -58 280 -120 261 -379 409 -653 374z" />
</g>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M8.09265 1.06679C7.60703 0.250524 6.39297 0.250524 5.90735 1.06679L0.170916 10.7089C-0.314707 11.5252 0.292322 12.5455 1.26357 12.5455H12.7364C13.7077 12.5455 14.3147 11.5252 13.8291 10.7089L8.09265 1.06679ZM6 5.00006C6 4.44778 6.44772 4.00006 7 4.00006C7.55228 4.00006 8 4.44778 8 5.00006V7.00006C8 7.55235 7.55228 8.00006 7 8.00006C6.44772 8.00006 6 7.55235 6 7.00006V5.00006ZM6 10.0001C6 9.44778 6.44772 9.00006 7 9.00006C7.55228 9.00006 8 9.44778 8 10.0001C8 10.5523 7.55228 11.0001 7 11.0001C6.44772 11.0001 6 10.5523 6 10.0001Z"
fill="#F2C94C"
/>
</svg>
</WarningIconContainer>
);

View file

@ -13,7 +13,11 @@ const INPUT_SIZES = {
export const Wrapper = styled.div`
position: relative;
&:hover {
svg:first-child {
fill: ${({ theme }) => theme.input.icon.hover};
}
}
svg:first-child {
position: absolute;
top: 8px;
@ -29,8 +33,10 @@ export const Wrapper = styled.div`
export const Input = styled.input<InputProps>(
({ theme: { input }, inputSize, search }) => css`
background-color: ${input.backgroundColor.normal};
border: 1px ${input.borderColor.normal} solid;
border-radius: 4px;
color: ${input.color.normal};
height: ${inputSize && INPUT_SIZES[inputSize]
? INPUT_SIZES[inputSize]
: '40px'};
@ -55,6 +61,7 @@ export const Input = styled.input<InputProps>(
&:disabled {
color: ${input.color.disabled};
border-color: ${input.borderColor.disabled};
background-color: ${input.backgroundColor.disabled};
cursor: not-allowed;
}
&:read-only {
@ -79,5 +86,5 @@ export const FormError = styled.p`
export const InputHint = styled.p`
font-size: 0.85rem;
margin-top: 0.25rem;
color: ${({ theme }) => theme.text.secondary};
color: ${({ theme }) => theme.clusterConfigForm.inputHintText.secondary};
`;

View file

@ -10,7 +10,7 @@ export const Wrapper = styled.div`
`;
export const IndicatorWrapper = styled.div`
background-color: ${({ theme }) => theme.metrics.indicator.backgroundColor};
background-color: ${({ theme }) => theme.default.backgroundColor};
height: 68px;
width: fit-content;
min-width: 150px;
@ -21,6 +21,7 @@ export const IndicatorWrapper = styled.div`
padding: 12px 16px;
box-shadow: 3px 3px 3px rgba(0, 0, 0, 0.08);
flex-grow: 1;
color: ${({ theme }) => theme.default.color.normal};
`;
export const IndicatorTitle = styled.div`
@ -39,12 +40,14 @@ export const IndicatorsWrapper = styled.div`
border-radius: 8px;
overflow: auto;
box-shadow: 3px 3px 3px rgba(0, 0, 0, 0.08);
color: ${({ theme }) => theme.metrics.wrapper};
`;
export const SectionTitle = styled.h5`
font-weight: 500;
margin: 0 0 0.5rem 16px;
font-size: 100%;
color: ${({ theme }) => theme.metrics.sectionTitle};
`;
export const LightText = styled.span`

View file

@ -3,7 +3,7 @@ import { Indicator } from 'components/common/Metrics';
import { screen } from '@testing-library/react';
import { render } from 'lib/testHelpers';
import { Props } from 'components/common/Metrics/Indicator';
import theme from 'theme/theme';
import { theme } from 'theme/theme';
const title = 'Test Title';
const label = 'Test Label';

View file

@ -1,6 +1,7 @@
import React from 'react';
import { Section } from 'components/common/Metrics';
import { render, screen } from '@testing-library/react';
import { screen } from '@testing-library/react';
import { render } from 'lib/testHelpers';
const child = 'Child';
const title = 'Test Title';

Some files were not shown because too many files have changed in this diff Show more