diff options
| author | mjkwiatkowski <mati.rewa@gmail.com> | 2026-02-16 15:18:21 +0100 |
|---|---|---|
| committer | mjkwiatkowski <mati.rewa@gmail.com> | 2026-02-16 15:18:21 +0100 |
| commit | 2f16cb0f48eca4453e3e894b3d45a3aa09e6dcc0 (patch) | |
| tree | 672d98baa2ac071f2c30de06d613254d0d8cd105 | |
| parent | 86d35fcec83057e346e4982b5a6908f25342a392 (diff) | |
338 files changed, 936 insertions, 24601 deletions
diff --git a/README b/README deleted file mode 100644 index d3aedcc4..00000000 --- a/README +++ /dev/null @@ -1,29 +0,0 @@ -#Dependencies - -Kafka: - -extra/kafka 4.1.1-1 - -```bash -sudo -u kafka /usr/bin/kafka-storage.sh format -t "$(/usr/bin/kafka-storage.sh random-uuid)" -c /etc/kafka/server.properties --standalone -systemctl enable kafka -systemctl start kafka -``` - -```bash -bin/kafka-topics.sh --create --topic postgres-topic --bootstrap-server localhost:9092 --partitions 1 --replication-factor 1 -bin/kafka-topics.sh --list --bootstrap-server localhost:9092 -``` - - -Postgresql: - -extra/postgresql 18.1-2 - -```bash -initdb -D /var/lib/postgres/data -mkdir /run/postgresql/ -cd /run/postgresql/ -touch .s.PGSQL.5432.lock -chown -R postgres:postgres /run/postgresql -``` diff --git a/README.md b/README.md new file mode 100644 index 00000000..5267b498 --- /dev/null +++ b/README.md @@ -0,0 +1,55 @@ +### Dependencies + +Paths are hardcoded. + +Confluent local (see https://www.confluent.io/installation/): + +```bash +export CONFLUENT_HOME=/opt/confluent +export PATH=/opt/confluent/bin:$PATH +cd /opt/confluent +kafka-storage.sh random-uuid +kafka-storage.sh format -t 2vi2WtHxQAOPyXb1Bj1Jvw -c etc/kafka/server.properties +kafka-server-start.sh etc/kafka/server.properties +kafka-server-start etc/kafka/server.properties +schema-registry-start $CONFLUENT_HOME/etc/schema-registry/schema-registry.properties +connect-standalone $CONFLUENT_HOME/etc/kafka/connect-standalone.properties $CONFLUENT_HOME/share/confluent-common/connectors/sink-jdbc.properties +``` + +Confluent JDBC sink and source (includes Postgres connector) +(see https://www.confluent.io/hub/confluentinc/kafka-connect-jdbc) +Be mindful to configure the right `plugin.path` in `etc/kafka/connect-standalone.properties` +```bash +ln -s $HOME/git/opendc/resources/experiments/sink-jdbc.properties opt/confluent/share/confluent-common/connectors/sink-jdbc.properties +``` + +Protobuf: + +extra/protobuf 33.1-3 + +You need to run this each time you change `schema.proto` +```bash +cd resources/experiments/ +protoc --java_out=/home/matt/git/opendc/opendc-common/src/main/java/ schema.proto +``` + +Postgresql: + +extra/postgresql 18.1-2 + +```bash +initdb -D /var/lib/postgres/data +mkdir /run/postgresql/ +cd /run/postgresql/ +touch .s.PGSQL.5432.lock +chown -R postgres:postgres /run/postgresql +``` + + +Random +```bash +bin/kafka-topics.sh --bootstrap-server localhost:9092 --delete --topic postgres-topic +bin/kafka-topics.sh --create --topic postgres-topic --bootstrap-server localhost:9092 --partitions 1 --replication-factor 1 +bin/kafka-topics.sh --list --bootstrap-server localhost:9092 +bin/kafka-console-consumer.sh --bootstrap-server :9092 --topic postgres-topic --from-beginning +```
\ No newline at end of file diff --git a/build.gradle.kts b/build.gradle.kts index 29d9202b..f8a20895 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -25,4 +25,4 @@ plugins { } group = "org.opendc" -version = "3.0-SNAPSHOT"
\ No newline at end of file +version = "3.0-SNAPSHOT" diff --git a/opendc-common/build.gradle.kts b/opendc-common/build.gradle.kts index 0094730b..4d9e8b54 100644 --- a/opendc-common/build.gradle.kts +++ b/opendc-common/build.gradle.kts @@ -1,3 +1,4 @@ + /* * Copyright (c) 2020 AtLarge Research * @@ -29,6 +30,11 @@ plugins { kotlin("plugin.serialization") version "1.9.22" } +repositories { + maven(url = "https://packages.confluent.io/maven/") +} + + val serializationVersion = "1.6.0" dependencies { @@ -45,4 +51,17 @@ dependencies { api(libs.kotlin.logging) testImplementation(projects.opendcSimulator.opendcSimulatorCore) + + // Source: https://mvnrepository.com/artifact/org.apache.kafka/kafka-clients + implementation("org.apache.kafka:kafka-clients:4.1.1") + implementation(libs.jackson.core) + + // Source: https://mvnrepository.com/artifact/com.google.protobuf/protobuf-java + // @Mateusz crucial this is an _api_ and not _implementation_ + api("com.google.protobuf:protobuf-java:4.33.5") + + // Source: https://mvnrepository.com/artifact/io.confluent/kafka-protobuf-serializer + implementation("io.confluent:kafka-protobuf-serializer:8.1.1") } + + diff --git a/opendc-common/src/main/java/org/opendc/common/ProtobufMetrics.java b/opendc-common/src/main/java/org/opendc/common/ProtobufMetrics.java new file mode 100644 index 00000000..0ec97cd0 --- /dev/null +++ b/opendc-common/src/main/java/org/opendc/common/ProtobufMetrics.java @@ -0,0 +1,638 @@ +// Generated by the protocol buffer compiler. DO NOT EDIT! +// NO CHECKED-IN PROTOBUF GENCODE +// source: schema.proto +// Protobuf Java Version: 4.33.1 + +package org.opendc.common; + +@com.google.protobuf.Generated +public final class ProtobufMetrics extends com.google.protobuf.GeneratedFile { + private ProtobufMetrics() {} + static { + com.google.protobuf.RuntimeVersion.validateProtobufGencodeVersion( + com.google.protobuf.RuntimeVersion.RuntimeDomain.PUBLIC, + /* major= */ 4, + /* minor= */ 33, + /* patch= */ 1, + /* suffix= */ "", + "ProtobufMetrics"); + } + public static void registerAllExtensions( + com.google.protobuf.ExtensionRegistryLite registry) { + } + + public static void registerAllExtensions( + com.google.protobuf.ExtensionRegistry registry) { + registerAllExtensions( + (com.google.protobuf.ExtensionRegistryLite) registry); + } + public interface ProtoExportOrBuilder extends + // @@protoc_insertion_point(interface_extends:proto.ProtoExport) + com.google.protobuf.MessageOrBuilder { + + /** + * <code>required int32 id = 1;</code> + * @return Whether the id field is set. + */ + boolean hasId(); + /** + * <code>required int32 id = 1;</code> + * @return The id. + */ + int getId(); + + /** + * <code>required int32 tasksactive = 2;</code> + * @return Whether the tasksactive field is set. + */ + boolean hasTasksactive(); + /** + * <code>required int32 tasksactive = 2;</code> + * @return The tasksactive. + */ + int getTasksactive(); + } + /** + * Protobuf type {@code proto.ProtoExport} + */ + public static final class ProtoExport extends + com.google.protobuf.GeneratedMessage implements + // @@protoc_insertion_point(message_implements:proto.ProtoExport) + ProtoExportOrBuilder { + private static final long serialVersionUID = 0L; + static { + com.google.protobuf.RuntimeVersion.validateProtobufGencodeVersion( + com.google.protobuf.RuntimeVersion.RuntimeDomain.PUBLIC, + /* major= */ 4, + /* minor= */ 33, + /* patch= */ 1, + /* suffix= */ "", + "ProtoExport"); + } + // Use ProtoExport.newBuilder() to construct. + private ProtoExport(com.google.protobuf.GeneratedMessage.Builder<?> builder) { + super(builder); + } + private ProtoExport() { + } + + public static final com.google.protobuf.Descriptors.Descriptor + getDescriptor() { + return org.opendc.common.ProtobufMetrics.internal_static_proto_ProtoExport_descriptor; + } + + @java.lang.Override + protected com.google.protobuf.GeneratedMessage.FieldAccessorTable + internalGetFieldAccessorTable() { + return org.opendc.common.ProtobufMetrics.internal_static_proto_ProtoExport_fieldAccessorTable + .ensureFieldAccessorsInitialized( + org.opendc.common.ProtobufMetrics.ProtoExport.class, org.opendc.common.ProtobufMetrics.ProtoExport.Builder.class); + } + + private int bitField0_; + public static final int ID_FIELD_NUMBER = 1; + private int id_ = 0; + /** + * <code>required int32 id = 1;</code> + * @return Whether the id field is set. + */ + @java.lang.Override + public boolean hasId() { + return ((bitField0_ & 0x00000001) != 0); + } + /** + * <code>required int32 id = 1;</code> + * @return The id. + */ + @java.lang.Override + public int getId() { + return id_; + } + + public static final int TASKSACTIVE_FIELD_NUMBER = 2; + private int tasksactive_ = 0; + /** + * <code>required int32 tasksactive = 2;</code> + * @return Whether the tasksactive field is set. + */ + @java.lang.Override + public boolean hasTasksactive() { + return ((bitField0_ & 0x00000002) != 0); + } + /** + * <code>required int32 tasksactive = 2;</code> + * @return The tasksactive. + */ + @java.lang.Override + public int getTasksactive() { + return tasksactive_; + } + + private byte memoizedIsInitialized = -1; + @java.lang.Override + public final boolean isInitialized() { + byte isInitialized = memoizedIsInitialized; + if (isInitialized == 1) return true; + if (isInitialized == 0) return false; + + if (!hasId()) { + memoizedIsInitialized = 0; + return false; + } + if (!hasTasksactive()) { + memoizedIsInitialized = 0; + return false; + } + memoizedIsInitialized = 1; + return true; + } + + @java.lang.Override + public void writeTo(com.google.protobuf.CodedOutputStream output) + throws java.io.IOException { + if (((bitField0_ & 0x00000001) != 0)) { + output.writeInt32(1, id_); + } + if (((bitField0_ & 0x00000002) != 0)) { + output.writeInt32(2, tasksactive_); + } + getUnknownFields().writeTo(output); + } + + @java.lang.Override + public int getSerializedSize() { + int size = memoizedSize; + if (size != -1) return size; + + size = 0; + if (((bitField0_ & 0x00000001) != 0)) { + size += com.google.protobuf.CodedOutputStream + .computeInt32Size(1, id_); + } + if (((bitField0_ & 0x00000002) != 0)) { + size += com.google.protobuf.CodedOutputStream + .computeInt32Size(2, tasksactive_); + } + size += getUnknownFields().getSerializedSize(); + memoizedSize = size; + return size; + } + + @java.lang.Override + public boolean equals(final java.lang.Object obj) { + if (obj == this) { + return true; + } + if (!(obj instanceof org.opendc.common.ProtobufMetrics.ProtoExport)) { + return super.equals(obj); + } + org.opendc.common.ProtobufMetrics.ProtoExport other = (org.opendc.common.ProtobufMetrics.ProtoExport) obj; + + if (hasId() != other.hasId()) return false; + if (hasId()) { + if (getId() + != other.getId()) return false; + } + if (hasTasksactive() != other.hasTasksactive()) return false; + if (hasTasksactive()) { + if (getTasksactive() + != other.getTasksactive()) return false; + } + if (!getUnknownFields().equals(other.getUnknownFields())) return false; + return true; + } + + @java.lang.Override + public int hashCode() { + if (memoizedHashCode != 0) { + return memoizedHashCode; + } + int hash = 41; + hash = (19 * hash) + getDescriptor().hashCode(); + if (hasId()) { + hash = (37 * hash) + ID_FIELD_NUMBER; + hash = (53 * hash) + getId(); + } + if (hasTasksactive()) { + hash = (37 * hash) + TASKSACTIVE_FIELD_NUMBER; + hash = (53 * hash) + getTasksactive(); + } + hash = (29 * hash) + getUnknownFields().hashCode(); + memoizedHashCode = hash; + return hash; + } + + public static org.opendc.common.ProtobufMetrics.ProtoExport parseFrom( + java.nio.ByteBuffer data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static org.opendc.common.ProtobufMetrics.ProtoExport parseFrom( + java.nio.ByteBuffer data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static org.opendc.common.ProtobufMetrics.ProtoExport parseFrom( + com.google.protobuf.ByteString data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static org.opendc.common.ProtobufMetrics.ProtoExport parseFrom( + com.google.protobuf.ByteString data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static org.opendc.common.ProtobufMetrics.ProtoExport parseFrom(byte[] data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static org.opendc.common.ProtobufMetrics.ProtoExport parseFrom( + byte[] data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static org.opendc.common.ProtobufMetrics.ProtoExport parseFrom(java.io.InputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessage + .parseWithIOException(PARSER, input); + } + public static org.opendc.common.ProtobufMetrics.ProtoExport parseFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessage + .parseWithIOException(PARSER, input, extensionRegistry); + } + + public static org.opendc.common.ProtobufMetrics.ProtoExport parseDelimitedFrom(java.io.InputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessage + .parseDelimitedWithIOException(PARSER, input); + } + + public static org.opendc.common.ProtobufMetrics.ProtoExport parseDelimitedFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessage + .parseDelimitedWithIOException(PARSER, input, extensionRegistry); + } + public static org.opendc.common.ProtobufMetrics.ProtoExport parseFrom( + com.google.protobuf.CodedInputStream input) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessage + .parseWithIOException(PARSER, input); + } + public static org.opendc.common.ProtobufMetrics.ProtoExport parseFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return com.google.protobuf.GeneratedMessage + .parseWithIOException(PARSER, input, extensionRegistry); + } + + @java.lang.Override + public Builder newBuilderForType() { return newBuilder(); } + public static Builder newBuilder() { + return DEFAULT_INSTANCE.toBuilder(); + } + public static Builder newBuilder(org.opendc.common.ProtobufMetrics.ProtoExport prototype) { + return DEFAULT_INSTANCE.toBuilder().mergeFrom(prototype); + } + @java.lang.Override + public Builder toBuilder() { + return this == DEFAULT_INSTANCE + ? new Builder() : new Builder().mergeFrom(this); + } + + @java.lang.Override + protected Builder newBuilderForType( + com.google.protobuf.GeneratedMessage.BuilderParent parent) { + Builder builder = new Builder(parent); + return builder; + } + /** + * Protobuf type {@code proto.ProtoExport} + */ + public static final class Builder extends + com.google.protobuf.GeneratedMessage.Builder<Builder> implements + // @@protoc_insertion_point(builder_implements:proto.ProtoExport) + org.opendc.common.ProtobufMetrics.ProtoExportOrBuilder { + public static final com.google.protobuf.Descriptors.Descriptor + getDescriptor() { + return org.opendc.common.ProtobufMetrics.internal_static_proto_ProtoExport_descriptor; + } + + @java.lang.Override + protected com.google.protobuf.GeneratedMessage.FieldAccessorTable + internalGetFieldAccessorTable() { + return org.opendc.common.ProtobufMetrics.internal_static_proto_ProtoExport_fieldAccessorTable + .ensureFieldAccessorsInitialized( + org.opendc.common.ProtobufMetrics.ProtoExport.class, org.opendc.common.ProtobufMetrics.ProtoExport.Builder.class); + } + + // Construct using org.opendc.common.ProtobufMetrics.ProtoExport.newBuilder() + private Builder() { + + } + + private Builder( + com.google.protobuf.GeneratedMessage.BuilderParent parent) { + super(parent); + + } + @java.lang.Override + public Builder clear() { + super.clear(); + bitField0_ = 0; + id_ = 0; + tasksactive_ = 0; + return this; + } + + @java.lang.Override + public com.google.protobuf.Descriptors.Descriptor + getDescriptorForType() { + return org.opendc.common.ProtobufMetrics.internal_static_proto_ProtoExport_descriptor; + } + + @java.lang.Override + public org.opendc.common.ProtobufMetrics.ProtoExport getDefaultInstanceForType() { + return org.opendc.common.ProtobufMetrics.ProtoExport.getDefaultInstance(); + } + + @java.lang.Override + public org.opendc.common.ProtobufMetrics.ProtoExport build() { + org.opendc.common.ProtobufMetrics.ProtoExport result = buildPartial(); + if (!result.isInitialized()) { + throw newUninitializedMessageException(result); + } + return result; + } + + @java.lang.Override + public org.opendc.common.ProtobufMetrics.ProtoExport buildPartial() { + org.opendc.common.ProtobufMetrics.ProtoExport result = new org.opendc.common.ProtobufMetrics.ProtoExport(this); + if (bitField0_ != 0) { buildPartial0(result); } + onBuilt(); + return result; + } + + private void buildPartial0(org.opendc.common.ProtobufMetrics.ProtoExport result) { + int from_bitField0_ = bitField0_; + int to_bitField0_ = 0; + if (((from_bitField0_ & 0x00000001) != 0)) { + result.id_ = id_; + to_bitField0_ |= 0x00000001; + } + if (((from_bitField0_ & 0x00000002) != 0)) { + result.tasksactive_ = tasksactive_; + to_bitField0_ |= 0x00000002; + } + result.bitField0_ |= to_bitField0_; + } + + @java.lang.Override + public Builder mergeFrom(com.google.protobuf.Message other) { + if (other instanceof org.opendc.common.ProtobufMetrics.ProtoExport) { + return mergeFrom((org.opendc.common.ProtobufMetrics.ProtoExport)other); + } else { + super.mergeFrom(other); + return this; + } + } + + public Builder mergeFrom(org.opendc.common.ProtobufMetrics.ProtoExport other) { + if (other == org.opendc.common.ProtobufMetrics.ProtoExport.getDefaultInstance()) return this; + if (other.hasId()) { + setId(other.getId()); + } + if (other.hasTasksactive()) { + setTasksactive(other.getTasksactive()); + } + this.mergeUnknownFields(other.getUnknownFields()); + onChanged(); + return this; + } + + @java.lang.Override + public final boolean isInitialized() { + if (!hasId()) { + return false; + } + if (!hasTasksactive()) { + return false; + } + return true; + } + + @java.lang.Override + public Builder mergeFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + if (extensionRegistry == null) { + throw new java.lang.NullPointerException(); + } + try { + boolean done = false; + while (!done) { + int tag = input.readTag(); + switch (tag) { + case 0: + done = true; + break; + case 8: { + id_ = input.readInt32(); + bitField0_ |= 0x00000001; + break; + } // case 8 + case 16: { + tasksactive_ = input.readInt32(); + bitField0_ |= 0x00000002; + break; + } // case 16 + default: { + if (!super.parseUnknownField(input, extensionRegistry, tag)) { + done = true; // was an endgroup tag + } + break; + } // default: + } // switch (tag) + } // while (!done) + } catch (com.google.protobuf.InvalidProtocolBufferException e) { + throw e.unwrapIOException(); + } finally { + onChanged(); + } // finally + return this; + } + private int bitField0_; + + private int id_ ; + /** + * <code>required int32 id = 1;</code> + * @return Whether the id field is set. + */ + @java.lang.Override + public boolean hasId() { + return ((bitField0_ & 0x00000001) != 0); + } + /** + * <code>required int32 id = 1;</code> + * @return The id. + */ + @java.lang.Override + public int getId() { + return id_; + } + /** + * <code>required int32 id = 1;</code> + * @param value The id to set. + * @return This builder for chaining. + */ + public Builder setId(int value) { + + id_ = value; + bitField0_ |= 0x00000001; + onChanged(); + return this; + } + /** + * <code>required int32 id = 1;</code> + * @return This builder for chaining. + */ + public Builder clearId() { + bitField0_ = (bitField0_ & ~0x00000001); + id_ = 0; + onChanged(); + return this; + } + + private int tasksactive_ ; + /** + * <code>required int32 tasksactive = 2;</code> + * @return Whether the tasksactive field is set. + */ + @java.lang.Override + public boolean hasTasksactive() { + return ((bitField0_ & 0x00000002) != 0); + } + /** + * <code>required int32 tasksactive = 2;</code> + * @return The tasksactive. + */ + @java.lang.Override + public int getTasksactive() { + return tasksactive_; + } + /** + * <code>required int32 tasksactive = 2;</code> + * @param value The tasksactive to set. + * @return This builder for chaining. + */ + public Builder setTasksactive(int value) { + + tasksactive_ = value; + bitField0_ |= 0x00000002; + onChanged(); + return this; + } + /** + * <code>required int32 tasksactive = 2;</code> + * @return This builder for chaining. + */ + public Builder clearTasksactive() { + bitField0_ = (bitField0_ & ~0x00000002); + tasksactive_ = 0; + onChanged(); + return this; + } + + // @@protoc_insertion_point(builder_scope:proto.ProtoExport) + } + + // @@protoc_insertion_point(class_scope:proto.ProtoExport) + private static final org.opendc.common.ProtobufMetrics.ProtoExport DEFAULT_INSTANCE; + static { + DEFAULT_INSTANCE = new org.opendc.common.ProtobufMetrics.ProtoExport(); + } + + public static org.opendc.common.ProtobufMetrics.ProtoExport getDefaultInstance() { + return DEFAULT_INSTANCE; + } + + private static final com.google.protobuf.Parser<ProtoExport> + PARSER = new com.google.protobuf.AbstractParser<ProtoExport>() { + @java.lang.Override + public ProtoExport parsePartialFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + Builder builder = newBuilder(); + try { + builder.mergeFrom(input, extensionRegistry); + } catch (com.google.protobuf.InvalidProtocolBufferException e) { + throw e.setUnfinishedMessage(builder.buildPartial()); + } catch (com.google.protobuf.UninitializedMessageException e) { + throw e.asInvalidProtocolBufferException().setUnfinishedMessage(builder.buildPartial()); + } catch (java.io.IOException e) { + throw new com.google.protobuf.InvalidProtocolBufferException(e) + .setUnfinishedMessage(builder.buildPartial()); + } + return builder.buildPartial(); + } + }; + + public static com.google.protobuf.Parser<ProtoExport> parser() { + return PARSER; + } + + @java.lang.Override + public com.google.protobuf.Parser<ProtoExport> getParserForType() { + return PARSER; + } + + @java.lang.Override + public org.opendc.common.ProtobufMetrics.ProtoExport getDefaultInstanceForType() { + return DEFAULT_INSTANCE; + } + + } + + private static final com.google.protobuf.Descriptors.Descriptor + internal_static_proto_ProtoExport_descriptor; + private static final + com.google.protobuf.GeneratedMessage.FieldAccessorTable + internal_static_proto_ProtoExport_fieldAccessorTable; + + public static com.google.protobuf.Descriptors.FileDescriptor + getDescriptor() { + return descriptor; + } + private static com.google.protobuf.Descriptors.FileDescriptor + descriptor; + static { + java.lang.String[] descriptorData = { + "\n\014schema.proto\022\005proto\".\n\013ProtoExport\022\n\n\002" + + "id\030\001 \002(\005\022\023\n\013tasksactive\030\002 \002(\005B$\n\021org.ope" + + "ndc.commonB\017ProtobufMetrics" + }; + descriptor = com.google.protobuf.Descriptors.FileDescriptor + .internalBuildGeneratedFileFrom(descriptorData, + new com.google.protobuf.Descriptors.FileDescriptor[] { + }); + internal_static_proto_ProtoExport_descriptor = + getDescriptor().getMessageType(0); + internal_static_proto_ProtoExport_fieldAccessorTable = new + com.google.protobuf.GeneratedMessage.FieldAccessorTable( + internal_static_proto_ProtoExport_descriptor, + new java.lang.String[] { "Id", "Tasksactive", }); + descriptor.resolveAllFeaturesImmutable(); + } + + // @@protoc_insertion_point(outer_class_scope) +} diff --git a/opendc-common/src/main/kotlin/org/opendc/common/utils/ConfigParser.kt b/opendc-common/src/main/kotlin/org/opendc/common/utils/ConfigParser.kt index cb9623bb..e6f18da5 100644 --- a/opendc-common/src/main/kotlin/org/opendc/common/utils/ConfigParser.kt +++ b/opendc-common/src/main/kotlin/org/opendc/common/utils/ConfigParser.kt @@ -11,32 +11,40 @@ import java.io.InputStream import java.io.OutputStream import java.net.Socket import java.sql.Connection -import java.sql.DriverManager -import java.sql.SQLException /** + * @author Mateusz * @property name * @property backlog the amount of connections to accept - * @property databasePath * @property address IPv4 address * @property port + * @property postgresql Postgresql port + * @property username Postgresql user + * @property password Postgresql password + * @property database Postgresql database + * @property topic Kafka topic + * @property kafka Kafka port */ @Serializable public data class Config( val name: String = "", var backlog: Int = 0, val address: String = "", - val port: Int = 8080, - val postgresql: Int = 5342, + val port: Int = 0, + val postgresql: Int = 0, val username : String = "", val password : String = "", - val database: String = "" + val database: String = "", + val topic : String = "", + val kafka: Int = 0, ){ public companion object{ public var input: InputStream? = null public var output: OutputStream? = null public var connection : Connection? = null + public var kafka : Kafka? = null + public var database : PostgresqlDB? = null public var socket: Socket? = null @@ -57,9 +65,26 @@ public data class Config( public fun getConfigWriter() : OutputStream? { return output } + + public fun setKafkaInstance(kafka : Kafka) { + this.kafka = kafka + } + + public fun getKafkaInstance() : Kafka? { + return this.kafka + } + + public fun setDB(db : PostgresqlDB){ + this.database = db + } + + public fun getDB() : PostgresqlDB?{ + return this.database + } } } /** + * @author Mateusz * Reads `config.json` into Config data class. */ public class ConfigReader { diff --git a/opendc-common/src/main/kotlin/org/opendc/common/utils/Kafka.kt b/opendc-common/src/main/kotlin/org/opendc/common/utils/Kafka.kt new file mode 100644 index 00000000..48590d9f --- /dev/null +++ b/opendc-common/src/main/kotlin/org/opendc/common/utils/Kafka.kt @@ -0,0 +1,41 @@ +package org.opendc.common.utils +import org.apache.kafka.clients.producer.KafkaProducer +import org.apache.kafka.clients.producer.Producer +import org.apache.kafka.clients.producer.ProducerRecord +import org.opendc.common.ProtobufMetrics + +import java.util.* + +public class Kafka ( + private val topic : String, + address : String, + port : Int, +) { + private val servers : String = "$address:$port" + private var properties: Properties? = null + private var producer: Producer<String, ProtobufMetrics.ProtoExport>? = null + + init { + properties = Properties() + properties?.put("bootstrap.servers", servers) + properties?.put("key.serializer", "org.apache.kafka.common.serialization.StringSerializer") + properties?.put("value.serializer", "io.confluent.kafka.serializers.protobuf.KafkaProtobufSerializer") + properties?.put("schema.registry.url", "http://localhost:8081") + properties?.put("auto.register.schemas", "true") + + try { + producer = KafkaProducer(properties) + } catch (e: Exception){ + println("${e.message}") + } + } + + public fun getProducer() : Producer<String, ProtobufMetrics.ProtoExport>? { + return this.producer + } + + public fun send(value : ProtobufMetrics.ProtoExport){ + producer?.send(ProducerRecord(this.topic, value)) + } + +}
\ No newline at end of file diff --git a/opendc-common/src/main/kotlin/org/opendc/common/utils/PostgresqlDB.kt b/opendc-common/src/main/kotlin/org/opendc/common/utils/PostgresqlDB.kt index 3dc7a0e4..69314ef3 100644 --- a/opendc-common/src/main/kotlin/org/opendc/common/utils/PostgresqlDB.kt +++ b/opendc-common/src/main/kotlin/org/opendc/common/utils/PostgresqlDB.kt @@ -4,25 +4,40 @@ import java.sql.Connection import java.sql.DriverManager import java.sql.SQLException -public class PostgresqlDB { - public var connection : Connection? = null - public var dbUrl : String = "" - public var user : String = "" - public var password : String = "" +/** + * Represents the Postgresql database. + * On setup cleans the entire database and creates empty tables. + * + * @author Mateusz + * + * @param address ipv4 address + * @param port postgres post + * @param dbName database name + * @param user + * @param password + */ +public class PostgresqlDB( + address : String, + port : Int, + dbName : String, + private var user : String, + private var password : String, +) { + private var connection : Connection? = null + private var dbUrl : String = "" - public fun setupDatabase(address : String, port : Int, dbUser : String, dbPassword : String, db : String){ - dbUrl = "jdbc:postgresql://$address:$port/$db" - user = dbUser - password = dbPassword + init { + dbUrl = "jdbc:postgresql://$address:$port/$dbName" println(dbUrl) try { - connection = DriverManager.getConnection(dbUrl, dbUser, dbPassword) + connection = DriverManager.getConnection(dbUrl, user, password) + clear() + setup() } catch (e: SQLException) { print("${e.message}") } } - - public fun create(){ + public fun setup(){ val CREATE_TABLE = """ CREATE TABLE metrics ( id SERIAL PRIMARY KEY, diff --git a/opendc-compute/opendc-compute-simulator/src/main/kotlin/org/opendc/compute/simulator/telemetry/KafkaComputeMonitor.kt b/opendc-compute/opendc-compute-simulator/src/main/kotlin/org/opendc/compute/simulator/telemetry/KafkaComputeMonitor.kt new file mode 100644 index 00000000..d0be10db --- /dev/null +++ b/opendc-compute/opendc-compute-simulator/src/main/kotlin/org/opendc/compute/simulator/telemetry/KafkaComputeMonitor.kt @@ -0,0 +1,50 @@ +package org.opendc.compute.simulator.telemetry + + +import com.fasterxml.jackson.databind.ObjectMapper +import org.opendc.common.utils.Config +import org.opendc.common.utils.Kafka +import org.opendc.common.ProtobufMetrics + +import org.opendc.compute.simulator.telemetry.table.host.HostTableReader + +/** + * @author Mateusz + * This class logs data from the simulator into Kafka. + */ +public class KafkaComputeMonitor: ComputeMonitor { + private val metrics : MonitoringMetrics = MonitoringMetrics() + private val kafka : Kafka? = Config.getKafkaInstance() + + @Override + override fun record(reader: HostTableReader) { + metrics.id += 1 + metrics.timestamp = reader.timestamp.toEpochMilli() + metrics.tasksActive = reader.tasksActive + metrics.clusterName = reader.hostInfo.clusterName + + try{ + val packet = ProtobufMetrics.ProtoExport.newBuilder() + .setId(metrics.id) + .setTasksactive(metrics.tasksActive) + .build() + kafka?.send(packet) + } + + catch(e: Exception){ + println("${e.message}") + } + } + +} + +/** + * @author Mateusz + * This serves as editable data class for ObjectMapper(). + */ +public class MonitoringMetrics { + public var id: Int = 0 + public var timestamp: Long = 0 + public var tasksActive : Int = 0 + public var clusterName: String = "" +} diff --git a/opendc-demo/build.gradle.kts b/opendc-demo/build.gradle.kts index 02972f44..d5a84120 100644 --- a/opendc-demo/build.gradle.kts +++ b/opendc-demo/build.gradle.kts @@ -28,9 +28,5 @@ plugins { } dependencies { - // Source: https://mvnrepository.com/artifact/org.apache.kafka/kafka-clients - implementation("org.apache.kafka:kafka-clients:4.1.1") - implementation(libs.jackson.core) implementation(project(mapOf("path" to "::opendc-compute:opendc-compute-simulator"))) - } diff --git a/opendc-demo/src/main/kotlin/org/opendc/demo/DemoComputeMonitor.kt b/opendc-demo/src/main/kotlin/org/opendc/demo/DemoComputeMonitor.kt deleted file mode 100644 index f59f90a1..00000000 --- a/opendc-demo/src/main/kotlin/org/opendc/demo/DemoComputeMonitor.kt +++ /dev/null @@ -1,42 +0,0 @@ -package org.opendc.demo - -import com.fasterxml.jackson.databind.ObjectMapper -import org.apache.kafka.clients.producer.ProducerRecord -import org.opendc.compute.simulator.telemetry.ComputeMonitor -import org.opendc.compute.simulator.telemetry.table.host.HostTableReader -import java.time.Instant - -public class DemoComputeMonitor: ComputeMonitor { - public val metrics : MonitoringMetrics = MonitoringMetrics() - - @Override - override fun record(reader: HostTableReader) { - metrics.timestamp = reader.timestamp.toEpochMilli() - metrics.tasksActive = reader.tasksActive - metrics.clusterName = reader.hostInfo.clusterName - - try{ - val objectMapper = ObjectMapper() - val jsonBytes = objectMapper.writeValueAsBytes(metrics) - println(metrics.clusterName) - } - - catch(e: Exception){ - println("${e.message}") - } - } - -} -public class MonitoringMetrics { - public var timestamp: Long = 0 - public var tasksActive : Int = 0 - public var cpuUsage : Double = 0.0 - public var cpuUtilisation: Double = 0.0 - public var cpuActiveTime : Long = 0 - public var cpuIdleTime: Long = 0 - public var cpuLostTime: Long = 0 - public var energyUsage: Double = 0.0 - public var uptime: Long = 0 - public var powerDraw: Double = 0.0 - public var clusterName: String = "" -} diff --git a/opendc-demo/src/main/kotlin/org/opendc/demo/RunRequest.kt b/opendc-demo/src/main/kotlin/org/opendc/demo/RunRequest.kt index e24a0af5..c4dfb7ca 100644 --- a/opendc-demo/src/main/kotlin/org/opendc/demo/RunRequest.kt +++ b/opendc-demo/src/main/kotlin/org/opendc/demo/RunRequest.kt @@ -23,7 +23,5 @@ package org.opendc.demo public fun runRequest(request: String) { - // https://github.com/am-i-helpful/opendc/blob/master/opendc-oda/opendc-oda-experiments/src/main/kotlin/org/opendc/oda/experimentrunner/ODAComputeMonitor.kt - // Do this here println("The request is $request\n") } diff --git a/opendc-experiments/opendc-experiments-base/build.gradle.kts b/opendc-experiments/opendc-experiments-base/build.gradle.kts index bac04854..76de6a3c 100644 --- a/opendc-experiments/opendc-experiments-base/build.gradle.kts +++ b/opendc-experiments/opendc-experiments-base/build.gradle.kts @@ -85,4 +85,6 @@ distributions { } repositories { mavenCentral() + // @Mateusz crucial to include this for kafka proto + maven(url = "https://packages.confluent.io/maven/") } diff --git a/opendc-experiments/opendc-experiments-base/src/main/kotlin/org/opendc/experiments/base/runner/ExperimentCli.kt b/opendc-experiments/opendc-experiments-base/src/main/kotlin/org/opendc/experiments/base/runner/ExperimentCli.kt index 874f5654..96071833 100644 --- a/opendc-experiments/opendc-experiments-base/src/main/kotlin/org/opendc/experiments/base/runner/ExperimentCli.kt +++ b/opendc-experiments/opendc-experiments-base/src/main/kotlin/org/opendc/experiments/base/runner/ExperimentCli.kt @@ -30,6 +30,7 @@ import com.github.ajalt.clikt.parameters.options.option import com.github.ajalt.clikt.parameters.types.file import org.opendc.common.utils.Config import org.opendc.common.utils.ConfigReader +import org.opendc.common.utils.Kafka import org.opendc.common.utils.PostgresqlDB import org.opendc.experiments.base.experiment.getExperiment import java.io.File @@ -49,6 +50,7 @@ public fun main(args: Array<String>) { /** + * @author Mateusz * Opens a client socket from `config`, but otherwise works as before. */ internal class ExperimentCommand : CliktCommand(name = "experiment") { @@ -68,6 +70,7 @@ internal class ExperimentCommand : CliktCommand(name = "experiment") { try { clientSocket = Socket(config.address, config.port) Config.setConfigSocket(clientSocket) + Config.setKafkaInstance(Kafka(config.topic, config.address, config.kafka)) val experiment = getExperiment(experimentPath) runExperiment(experiment) @@ -81,6 +84,7 @@ internal class ExperimentCommand : CliktCommand(name = "experiment") { } /** + * @author Mateusz * Creates a server socket and database connection from `config`. */ internal class ExperimentListener: CliktCommand(name = "listener") { @@ -92,14 +96,11 @@ internal class ExperimentListener: CliktCommand(name = "listener") { val configReader = ConfigReader() var serverSocket: ServerSocket? = null val config = configReader.read(configPath) - val database = PostgresqlDB() - val inetAddress = InetAddress.getByName(config.address) + Config.setDB(PostgresqlDB(config.address, config.postgresql, config.database, config.username, config.password)) try { + val inetAddress = InetAddress.getByName(config.address) serverSocket = ServerSocket(config.port, config.backlog, inetAddress) - database.setupDatabase(config.address, config.postgresql, config.username, config.password, config.database) - database.clear() - database.create() runListener(serverSocket) } catch (e: IOException) { println("${e.message}") @@ -107,9 +108,4 @@ internal class ExperimentListener: CliktCommand(name = "listener") { serverSocket?.close() } } -} - - - - - +}
\ No newline at end of file diff --git a/opendc-experiments/opendc-experiments-base/src/main/kotlin/org/opendc/experiments/base/runner/ExperimentRunner.kt b/opendc-experiments/opendc-experiments-base/src/main/kotlin/org/opendc/experiments/base/runner/ExperimentRunner.kt index 9d1f7374..9fee6cf9 100644 --- a/opendc-experiments/opendc-experiments-base/src/main/kotlin/org/opendc/experiments/base/runner/ExperimentRunner.kt +++ b/opendc-experiments/opendc-experiments-base/src/main/kotlin/org/opendc/experiments/base/runner/ExperimentRunner.kt @@ -65,6 +65,7 @@ public fun runExperiment(experiment: List<Scenario>) { } /** + * @author Mateusz * Accepts a (single) connection and listens for requests. * @param socket The socket to listen to. */ @@ -72,9 +73,6 @@ public fun runListener(socket: ServerSocket) { var client : Socket? = null try { client = socket.accept() - // here you should create another thread listening for sever-Kafka-client - // communication, and to store the incoming results into Postgresql - Config.setConfigSocket(client) val request = ByteArray(1024) diff --git a/opendc-experiments/opendc-experiments-base/src/main/kotlin/org/opendc/experiments/base/runner/ScenarioRunner.kt b/opendc-experiments/opendc-experiments-base/src/main/kotlin/org/opendc/experiments/base/runner/ScenarioRunner.kt index f33b6da8..ab25ef25 100644 --- a/opendc-experiments/opendc-experiments-base/src/main/kotlin/org/opendc/experiments/base/runner/ScenarioRunner.kt +++ b/opendc-experiments/opendc-experiments-base/src/main/kotlin/org/opendc/experiments/base/runner/ScenarioRunner.kt @@ -30,11 +30,8 @@ import org.opendc.compute.simulator.provisioner.setupComputeService import org.opendc.compute.simulator.provisioner.setupHosts import org.opendc.compute.simulator.scheduler.ComputeScheduler import org.opendc.compute.simulator.service.ComputeService -import org.opendc.compute.simulator.telemetry.parquet.ComputeExportConfig -import org.opendc.compute.simulator.telemetry.parquet.ParquetComputeMonitor -import org.opendc.compute.simulator.telemetry.parquet.withGpuColumns import org.opendc.compute.topology.clusterTopology -import org.opendc.demo.DemoComputeMonitor +import org.opendc.compute.simulator.telemetry.KafkaComputeMonitor import org.opendc.experiments.base.experiment.Scenario import org.opendc.experiments.base.experiment.specs.allocation.TimeShiftAllocationPolicySpec import org.opendc.experiments.base.experiment.specs.allocation.createComputeScheduler @@ -194,15 +191,15 @@ public fun addExportModel( ) { /* - * @Mateusz - * Here is the entry point to DemoComputeMonitor(). + * @author Mateusz + * Here is the entry point to KafkaComputeMonitor(). * With this setting, the simulator no longer writes to parquet files. * To get back the original code, refer to https://github.com/atlarge-research/opendc * */ provisioner.runStep( registerComputeMonitor( serviceDomain, - DemoComputeMonitor(), + KafkaComputeMonitor(), Duration.ofSeconds(scenario.exportModelSpec.exportInterval), startTime, scenario.exportModelSpec.filesToExportDict, diff --git a/opendc-web/build.gradle.kts b/opendc-web/build.gradle.kts deleted file mode 100644 index 47b19763..00000000 --- a/opendc-web/build.gradle.kts +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -description = "Interactive web interface for OpenDC" - -subprojects { - group = "org.opendc.web" -} diff --git a/opendc-web/opendc-web-client/build.gradle.kts b/opendc-web/opendc-web-client/build.gradle.kts deleted file mode 100644 index 55228ef9..00000000 --- a/opendc-web/opendc-web-client/build.gradle.kts +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright (c) 2022 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -description = "Client for the OpenDC web API" - -// Build configuration -plugins { - `kotlin-library-conventions` -} - -dependencies { - api(projects.opendcWeb.opendcWebProto) - implementation(libs.jackson.module.kotlin) - implementation(libs.jackson.datatype.jsr310) - implementation(libs.jakarta.validation) -} diff --git a/opendc-web/opendc-web-client/src/main/kotlin/org/opendc/web/client/OpenDCClient.kt b/opendc-web/opendc-web-client/src/main/kotlin/org/opendc/web/client/OpenDCClient.kt deleted file mode 100644 index a34c7864..00000000 --- a/opendc-web/opendc-web-client/src/main/kotlin/org/opendc/web/client/OpenDCClient.kt +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Copyright (c) 2022 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.web.client - -import org.opendc.web.client.auth.AuthController -import org.opendc.web.client.transport.HttpTransportClient -import org.opendc.web.client.transport.TransportClient -import java.net.URI - -/** - * Client implementation for the user-facing OpenDC REST API (version 2). - * - * @param client Low-level client for managing the underlying transport. - */ -public class OpenDCClient(client: TransportClient) { - /** - * Construct a new [OpenDCClient]. - * - * @param baseUrl The base url of the API. - * @param auth Helper class for managing authentication. - */ - public constructor(baseUrl: URI, auth: AuthController? = null) : this(HttpTransportClient(baseUrl, auth)) - - /** - * A resource for the available projects. - */ - public val projects: ProjectResource = ProjectResource(client) - - /** - * A resource for the topologies available to the user. - */ - public val topologies: TopologyResource = TopologyResource(client) - - /** - * A resource for the portfolios available to the user. - */ - public val portfolios: PortfolioResource = PortfolioResource(client) - - /** - * A resource for the scenarios available to the user. - */ - public val scenarios: ScenarioResource = ScenarioResource(client) - - /** - * A resource for the available schedulers. - */ - public val schedulers: SchedulerResource = SchedulerResource(client) - - /** - * A resource for the available workload traces. - */ - public val traces: TraceResource = TraceResource(client) -} diff --git a/opendc-web/opendc-web-client/src/main/kotlin/org/opendc/web/client/PortfolioResource.kt b/opendc-web/opendc-web-client/src/main/kotlin/org/opendc/web/client/PortfolioResource.kt deleted file mode 100644 index f0e49973..00000000 --- a/opendc-web/opendc-web-client/src/main/kotlin/org/opendc/web/client/PortfolioResource.kt +++ /dev/null @@ -1,67 +0,0 @@ -/* - * Copyright (c) 2022 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.web.client - -import org.opendc.web.client.internal.delete -import org.opendc.web.client.internal.get -import org.opendc.web.client.internal.post -import org.opendc.web.client.transport.TransportClient -import org.opendc.web.proto.user.Portfolio - -/** - * A resource representing the portfolios available to the user. - */ -public class PortfolioResource internal constructor(private val client: TransportClient) { - /** - * List all portfolios that belong to the specified [project]. - */ - public fun getAll(project: Long): List<Portfolio> = client.get("projects/$project/portfolios") ?: emptyList() - - /** - * Obtain the portfolio for [project] with [number]. - */ - public fun get( - project: Long, - number: Int, - ): Portfolio? = client.get("projects/$project/portfolios/$number") - - /** - * Create a new portfolio for [project] with the specified [request]. - */ - public fun create( - project: Long, - request: Portfolio.Create, - ): Portfolio { - return checkNotNull(client.post("projects/$project/portfolios", request)) - } - - /** - * Delete the portfolio for [project] with [index]. - */ - public fun delete( - project: Long, - index: Int, - ): Portfolio { - return requireNotNull(client.delete("projects/$project/portfolios/$index")) { "Unknown portfolio $index" } - } -} diff --git a/opendc-web/opendc-web-client/src/main/kotlin/org/opendc/web/client/ProjectResource.kt b/opendc-web/opendc-web-client/src/main/kotlin/org/opendc/web/client/ProjectResource.kt deleted file mode 100644 index 579d0d66..00000000 --- a/opendc-web/opendc-web-client/src/main/kotlin/org/opendc/web/client/ProjectResource.kt +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright (c) 2022 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.web.client - -import org.opendc.web.client.internal.delete -import org.opendc.web.client.internal.get -import org.opendc.web.client.internal.post -import org.opendc.web.client.transport.TransportClient -import org.opendc.web.proto.user.Project - -/** - * A resource representing the projects available to the user. - */ -public class ProjectResource internal constructor(private val client: TransportClient) { - /** - * List all projects available to the user. - */ - public fun getAll(): List<Project> = client.get("projects") ?: emptyList() - - /** - * Obtain the project with [id]. - */ - public fun get(id: Long): Project? = client.get("projects/$id") - - /** - * Create a new project. - */ - public fun create(name: String): Project = checkNotNull(client.post("projects", Project.Create(name))) - - /** - * Delete the project with the specified [id]. - */ - public fun delete(id: Long): Project = requireNotNull(client.delete("projects/$id")) { "Unknown project $id" } -} diff --git a/opendc-web/opendc-web-client/src/main/kotlin/org/opendc/web/client/ScenarioResource.kt b/opendc-web/opendc-web-client/src/main/kotlin/org/opendc/web/client/ScenarioResource.kt deleted file mode 100644 index d43515a9..00000000 --- a/opendc-web/opendc-web-client/src/main/kotlin/org/opendc/web/client/ScenarioResource.kt +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Copyright (c) 2022 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.web.client - -import org.opendc.web.client.internal.delete -import org.opendc.web.client.internal.get -import org.opendc.web.client.internal.post -import org.opendc.web.client.transport.TransportClient -import org.opendc.web.proto.user.Scenario - -/** - * A resource representing the scenarios available to the user. - */ -public class ScenarioResource internal constructor(private val client: TransportClient) { - /** - * List all scenarios that belong to the specified [project]. - */ - public fun getAll(project: Long): List<Scenario> = client.get("projects/$project/scenarios") ?: emptyList() - - /** - * List all scenarios that belong to the specified [portfolioNumber]. - */ - public fun getAll( - project: Long, - portfolioNumber: Int, - ): List<Scenario> = client.get("projects/$project/portfolios/$portfolioNumber/scenarios") ?: emptyList() - - /** - * Obtain the scenario for [project] with [index]. - */ - public fun get( - project: Long, - index: Int, - ): Scenario? = client.get("projects/$project/scenarios/$index") - - /** - * Create a new scenario for [portfolio][portfolioNumber] with the specified [request]. - */ - public fun create( - project: Long, - portfolioNumber: Int, - request: Scenario.Create, - ): Scenario { - return checkNotNull(client.post("projects/$project/portfolios/$portfolioNumber", request)) - } - - /** - * Delete the scenario for [project] with [index]. - */ - public fun delete( - project: Long, - index: Int, - ): Scenario { - return requireNotNull(client.delete("projects/$project/scenarios/$index")) { "Unknown scenario $index" } - } -} diff --git a/opendc-web/opendc-web-client/src/main/kotlin/org/opendc/web/client/SchedulerResource.kt b/opendc-web/opendc-web-client/src/main/kotlin/org/opendc/web/client/SchedulerResource.kt deleted file mode 100644 index 43b72d88..00000000 --- a/opendc-web/opendc-web-client/src/main/kotlin/org/opendc/web/client/SchedulerResource.kt +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright (c) 2022 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.web.client - -import org.opendc.web.client.internal.get -import org.opendc.web.client.transport.TransportClient - -/** - * A resource representing the schedulers available in the OpenDC instance. - */ -public class SchedulerResource internal constructor(private val client: TransportClient) { - /** - * List all schedulers available. - */ - public fun getAll(): List<String> = client.get("schedulers") ?: emptyList() -} diff --git a/opendc-web/opendc-web-client/src/main/kotlin/org/opendc/web/client/TopologyResource.kt b/opendc-web/opendc-web-client/src/main/kotlin/org/opendc/web/client/TopologyResource.kt deleted file mode 100644 index 34f5ea1b..00000000 --- a/opendc-web/opendc-web-client/src/main/kotlin/org/opendc/web/client/TopologyResource.kt +++ /dev/null @@ -1,79 +0,0 @@ -/* - * Copyright (c) 2022 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.web.client - -import org.opendc.web.client.internal.delete -import org.opendc.web.client.internal.get -import org.opendc.web.client.internal.post -import org.opendc.web.client.internal.put -import org.opendc.web.client.transport.TransportClient -import org.opendc.web.proto.user.Topology - -/** - * A resource representing the topologies available to the user. - */ -public class TopologyResource internal constructor(private val client: TransportClient) { - /** - * List all topologies that belong to the specified [project]. - */ - public fun getAll(project: Long): List<Topology> = client.get("projects/$project/topologies") ?: emptyList() - - /** - * Obtain the topology for [project] with [index]. - */ - public fun get( - project: Long, - index: Int, - ): Topology? = client.get("projects/$project/topologies/$index") - - /** - * Create a new topology for [project] with [request]. - */ - public fun create( - project: Long, - request: Topology.Create, - ): Topology { - return checkNotNull(client.post("projects/$project/topologies", request)) - } - - /** - * Update the topology with [index] for [project] using the specified [request]. - */ - public fun update( - project: Long, - index: Int, - request: Topology.Update, - ): Topology? { - return client.put("projects/$project/topologies/$index", request) - } - - /** - * Delete the topology for [project] with [index]. - */ - public fun delete( - project: Long, - index: Long, - ): Topology { - return requireNotNull(client.delete("projects/$project/topologies/$index")) { "Unknown topology $index" } - } -} diff --git a/opendc-web/opendc-web-client/src/main/kotlin/org/opendc/web/client/TraceResource.kt b/opendc-web/opendc-web-client/src/main/kotlin/org/opendc/web/client/TraceResource.kt deleted file mode 100644 index b4a8c089..00000000 --- a/opendc-web/opendc-web-client/src/main/kotlin/org/opendc/web/client/TraceResource.kt +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright (c) 2022 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.web.client - -import org.opendc.web.client.internal.get -import org.opendc.web.client.transport.TransportClient -import org.opendc.web.proto.Trace - -/** - * A resource representing the workload traces available in the OpenDC instance. - */ -public class TraceResource internal constructor(private val client: TransportClient) { - /** - * List all workload traces available. - */ - public fun getAll(): List<Trace> = client.get("traces") ?: emptyList() - - /** - * Obtain the workload trace with the specified [id]. - */ - public fun get(id: Long): Trace? = client.get("traces/$id") -} diff --git a/opendc-web/opendc-web-client/src/main/kotlin/org/opendc/web/client/auth/AuthController.kt b/opendc-web/opendc-web-client/src/main/kotlin/org/opendc/web/client/auth/AuthController.kt deleted file mode 100644 index a4c66f55..00000000 --- a/opendc-web/opendc-web-client/src/main/kotlin/org/opendc/web/client/auth/AuthController.kt +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright (c) 2022 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.web.client.auth - -import java.net.http.HttpRequest - -/** - * Helper interface for managing API authentication. - */ -public interface AuthController { - /** - * Inject the authorization token into the specified [request]. - */ - public fun injectToken(request: HttpRequest.Builder) - - /** - * Refresh the current auth token. - */ - public fun refreshToken() -} diff --git a/opendc-web/opendc-web-client/src/main/kotlin/org/opendc/web/client/auth/OpenIdAuthController.kt b/opendc-web/opendc-web-client/src/main/kotlin/org/opendc/web/client/auth/OpenIdAuthController.kt deleted file mode 100644 index 707dc138..00000000 --- a/opendc-web/opendc-web-client/src/main/kotlin/org/opendc/web/client/auth/OpenIdAuthController.kt +++ /dev/null @@ -1,147 +0,0 @@ -/* - * Copyright (c) 2022 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.web.client.auth - -import com.fasterxml.jackson.databind.DeserializationFeature -import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper -import com.fasterxml.jackson.module.kotlin.readValue -import org.opendc.web.client.internal.OAuthTokenRequest -import org.opendc.web.client.internal.OAuthTokenResponse -import org.opendc.web.client.internal.OpenIdConfiguration -import java.net.URI -import java.net.http.HttpClient -import java.net.http.HttpRequest -import java.net.http.HttpResponse - -/** - * An [AuthController] for OpenID Connect protected APIs. - */ -public class OpenIdAuthController( - private val domain: String, - private val clientId: String, - private val clientSecret: String, - private val audience: String = "https://api.opendc.org/v2/", - private val client: HttpClient = HttpClient.newHttpClient(), -) : AuthController { - /** - * The Jackson object mapper to convert messages from/to JSON. - */ - private val mapper = - jacksonObjectMapper() - .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false) - - /** - * The cached [OpenIdConfiguration]. - */ - private val openidConfig: OpenIdConfiguration - get() { - var openidConfig = localOpenidConfig - if (openidConfig == null) { - openidConfig = requestConfig() - localOpenidConfig = openidConfig - } - - return openidConfig - } - private var localOpenidConfig: OpenIdConfiguration? = null - - /** - * The cached OAuth token. - */ - private var localToken: OAuthTokenResponse? = null - - override fun injectToken(request: HttpRequest.Builder) { - var token = localToken - if (token == null) { - token = requestToken() - localToken = token - } - - request.header("Authorization", "Bearer ${token.accessToken}") - } - - /** - * Refresh the current access token. - */ - override fun refreshToken() { - val refreshToken = localToken?.refreshToken - if (refreshToken == null) { - requestToken() - return - } - - localToken = refreshToken(openidConfig, refreshToken) - } - - /** - * Request the OpenID configuration from the chosen auth domain - */ - private fun requestConfig(): OpenIdConfiguration { - val request = - HttpRequest.newBuilder(URI("https://$domain/.well-known/openid-configuration")) - .GET() - .build() - val response = client.send(request, HttpResponse.BodyHandlers.ofInputStream()) - return mapper.readValue(response.body()) - } - - /** - * Request the auth token from the server. - */ - private fun requestToken(openidConfig: OpenIdConfiguration): OAuthTokenResponse { - val body = OAuthTokenRequest.ClientCredentials(audience, clientId, clientSecret) - val request = - HttpRequest.newBuilder(openidConfig.tokenEndpoint) - .header("Content-Type", "application/json") - .POST(HttpRequest.BodyPublishers.ofByteArray(mapper.writeValueAsBytes(body))) - .build() - val response = client.send(request, HttpResponse.BodyHandlers.ofInputStream()) - return mapper.readValue(response.body()) - } - - /** - * Helper method to refresh the auth token. - */ - private fun refreshToken( - openidConfig: OpenIdConfiguration, - refreshToken: String, - ): OAuthTokenResponse { - val body = OAuthTokenRequest.RefreshToken(refreshToken, clientId, clientSecret) - val request = - HttpRequest.newBuilder(openidConfig.tokenEndpoint) - .header("Content-Type", "application/json") - .POST(HttpRequest.BodyPublishers.ofByteArray(mapper.writeValueAsBytes(body))) - .build() - val response = client.send(request, HttpResponse.BodyHandlers.ofInputStream()) - return mapper.readValue(response.body()) - } - - /** - * Fetch a new access token. - */ - private fun requestToken(): OAuthTokenResponse { - val token = requestToken(openidConfig) - localToken = token - return token - } -} diff --git a/opendc-web/opendc-web-client/src/main/kotlin/org/opendc/web/client/internal/ClientUtils.kt b/opendc-web/opendc-web-client/src/main/kotlin/org/opendc/web/client/internal/ClientUtils.kt deleted file mode 100644 index 1ffaa602..00000000 --- a/opendc-web/opendc-web-client/src/main/kotlin/org/opendc/web/client/internal/ClientUtils.kt +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Copyright (c) 2022 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.web.client.internal - -import com.fasterxml.jackson.core.type.TypeReference -import org.opendc.web.client.transport.TransportClient - -/** - * Perform a GET request for resource at [path] and convert to type [T]. - */ -internal inline fun <reified T> TransportClient.get(path: String): T? { - return get(path, object : TypeReference<T>() {}) -} - -/** - * Perform a POST request for resource at [path] and convert to type [T]. - */ -internal inline fun <B, reified T> TransportClient.post( - path: String, - body: B, -): T? { - return post(path, body, object : TypeReference<T>() {}) -} - -/** - * Perform a PUT request for resource at [path] and convert to type [T]. - */ -internal inline fun <B, reified T> TransportClient.put( - path: String, - body: B, -): T? { - return put(path, body, object : TypeReference<T>() {}) -} - -/** - * Perform a DELETE request for resource at [path] and convert to type [T]. - */ -internal inline fun <reified T> TransportClient.delete(path: String): T? { - return delete(path, object : TypeReference<T>() {}) -} diff --git a/opendc-web/opendc-web-client/src/main/kotlin/org/opendc/web/client/internal/OAuthTokenRequest.kt b/opendc-web/opendc-web-client/src/main/kotlin/org/opendc/web/client/internal/OAuthTokenRequest.kt deleted file mode 100644 index 1bb06c8f..00000000 --- a/opendc-web/opendc-web-client/src/main/kotlin/org/opendc/web/client/internal/OAuthTokenRequest.kt +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Copyright (c) 2022 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.web.client.internal - -import com.fasterxml.jackson.annotation.JsonProperty -import com.fasterxml.jackson.annotation.JsonSubTypes -import com.fasterxml.jackson.annotation.JsonTypeInfo - -/** - * Token request sent to the OAuth server. - */ -@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "grant_type") -@JsonSubTypes( - value = [ - JsonSubTypes.Type(value = OAuthTokenRequest.ClientCredentials::class, name = "client_credentials"), - JsonSubTypes.Type(value = OAuthTokenRequest.RefreshToken::class, name = "refresh_token"), - ], -) -internal sealed class OAuthTokenRequest { - /** - * Client credentials grant for OAuth2 - */ - data class ClientCredentials( - val audience: String, - @JsonProperty("client_id") - val clientId: String, - @JsonProperty("client_secret") - val clientSecret: String, - ) : OAuthTokenRequest() - - /** - * Refresh token grant for OAuth2. - */ - data class RefreshToken( - @JsonProperty("refresh_token") - val refreshToken: String, - @JsonProperty("client_id") - val clientId: String, - @JsonProperty("client_secret") - val clientSecret: String, - ) : OAuthTokenRequest() -} diff --git a/opendc-web/opendc-web-client/src/main/kotlin/org/opendc/web/client/internal/OAuthTokenResponse.kt b/opendc-web/opendc-web-client/src/main/kotlin/org/opendc/web/client/internal/OAuthTokenResponse.kt deleted file mode 100644 index 76fe007c..00000000 --- a/opendc-web/opendc-web-client/src/main/kotlin/org/opendc/web/client/internal/OAuthTokenResponse.kt +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright (c) 2022 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.web.client.internal - -import com.fasterxml.jackson.annotation.JsonProperty - -/** - * Token response from the OAuth server. - */ -internal data class OAuthTokenResponse( - @JsonProperty("access_token") - val accessToken: String, - @JsonProperty("refresh_token") - val refreshToken: String? = null, - @JsonProperty("token_type") - val tokenType: String, - val scope: String = "", - @JsonProperty("expires_in") - val expiresIn: Long, -) diff --git a/opendc-web/opendc-web-client/src/main/kotlin/org/opendc/web/client/internal/OpenIdConfiguration.kt b/opendc-web/opendc-web-client/src/main/kotlin/org/opendc/web/client/internal/OpenIdConfiguration.kt deleted file mode 100644 index eac1607e..00000000 --- a/opendc-web/opendc-web-client/src/main/kotlin/org/opendc/web/client/internal/OpenIdConfiguration.kt +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright (c) 2022 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.web.client.internal - -import com.fasterxml.jackson.annotation.JsonProperty -import java.net.URI - -/** - * OpenID configuration exposed by the auth server. - */ -internal data class OpenIdConfiguration( - val issuer: String, - @JsonProperty("authorization_endpoint") - val authorizationEndpoint: URI, - @JsonProperty("token_endpoint") - val tokenEndpoint: URI, - @JsonProperty("userinfo_endpoint") - val userInfoEndpoint: URI, - @JsonProperty("jwks_uri") - val jwksUri: URI, - @JsonProperty("scopes_supported") - val scopesSupported: Set<String>, -) diff --git a/opendc-web/opendc-web-client/src/main/kotlin/org/opendc/web/client/runner/JobResource.kt b/opendc-web/opendc-web-client/src/main/kotlin/org/opendc/web/client/runner/JobResource.kt deleted file mode 100644 index e72f703c..00000000 --- a/opendc-web/opendc-web-client/src/main/kotlin/org/opendc/web/client/runner/JobResource.kt +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright (c) 2022 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.web.client.runner - -import org.opendc.web.client.internal.get -import org.opendc.web.client.internal.post -import org.opendc.web.client.transport.TransportClient -import org.opendc.web.proto.runner.Job - -/** - * A resource representing the available simulation jobs for the runner. - */ -public class JobResource internal constructor(private val client: TransportClient) { - /** - * Query the pending jobs. - */ - public fun queryPending(): List<Job> = client.get("jobs") ?: emptyList() - - /** - * Obtain the job with [id]. - */ - public fun get(id: Long): Job? = client.get("jobs/$id") - - /** - * Update the job with [id]. - */ - public fun update( - id: Long, - update: Job.Update, - ): Job? = client.post("jobs/$id", update) -} diff --git a/opendc-web/opendc-web-client/src/main/kotlin/org/opendc/web/client/runner/OpenDCRunnerClient.kt b/opendc-web/opendc-web-client/src/main/kotlin/org/opendc/web/client/runner/OpenDCRunnerClient.kt deleted file mode 100644 index 98785a55..00000000 --- a/opendc-web/opendc-web-client/src/main/kotlin/org/opendc/web/client/runner/OpenDCRunnerClient.kt +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Copyright (c) 2022 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.web.client.runner - -import org.opendc.web.client.SchedulerResource -import org.opendc.web.client.TraceResource -import org.opendc.web.client.auth.AuthController -import org.opendc.web.client.transport.HttpTransportClient -import org.opendc.web.client.transport.TransportClient -import java.net.URI - -/** - * Client implementation for the runner-facing OpenDC REST API (version 2). - * - * @param client Low-level client for managing the underlying transport. - */ -public class OpenDCRunnerClient(client: TransportClient) { - /** - * Construct a new [OpenDCRunnerClient]. - * - * @param baseUrl The base url of the API. - * @param auth Helper class for managing authentication. - */ - public constructor(baseUrl: URI, auth: AuthController? = null) : this(HttpTransportClient(baseUrl, auth)) - - /** - * A resource for the available simulation jobs. - */ - public val jobs: JobResource = JobResource(client) - - /** - * A resource for the available schedulers. - */ - public val schedulers: SchedulerResource = SchedulerResource(client) - - /** - * A resource for the available workload traces. - */ - public val traces: TraceResource = TraceResource(client) -} diff --git a/opendc-web/opendc-web-client/src/main/kotlin/org/opendc/web/client/transport/HttpTransportClient.kt b/opendc-web/opendc-web-client/src/main/kotlin/org/opendc/web/client/transport/HttpTransportClient.kt deleted file mode 100644 index f6dca4d1..00000000 --- a/opendc-web/opendc-web-client/src/main/kotlin/org/opendc/web/client/transport/HttpTransportClient.kt +++ /dev/null @@ -1,184 +0,0 @@ -/* - * Copyright (c) 2022 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.web.client.transport - -import com.fasterxml.jackson.core.type.TypeReference -import com.fasterxml.jackson.databind.DeserializationFeature -import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule -import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper -import org.opendc.web.client.auth.AuthController -import java.net.URI -import java.net.http.HttpClient -import java.net.http.HttpRequest -import java.net.http.HttpResponse -import java.nio.file.Paths - -/** - * A [TransportClient] that accesses the OpenDC API over HTTP. - * - * @param baseUrl The base url of the API. - * @param auth Helper class for managing authentication. - * @param client The HTTP client to use. - */ -public class HttpTransportClient( - private val baseUrl: URI, - private val auth: AuthController?, - private val client: HttpClient = HttpClient.newHttpClient(), -) : TransportClient { - /** - * The Jackson object mapper to convert messages from/to JSON. - */ - private val mapper = - jacksonObjectMapper() - .registerModule(JavaTimeModule()) - .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false) - - /** - * Obtain a resource at [path] of [targetType]. - */ - override fun <T> get( - path: String, - targetType: TypeReference<T>, - ): T? { - val request = - HttpRequest.newBuilder(buildUri(path)) - .GET() - .also { auth?.injectToken(it) } - .build() - val response = client.send(request, HttpResponse.BodyHandlers.ofInputStream()) - - return when (val code = response.statusCode()) { - in 200..299 -> mapper.readValue(response.body(), targetType) - 401 -> { - val auth = auth - if (auth != null) { - auth.refreshToken() - get(path, targetType) - } else { - throw IllegalStateException("Authorization required") - } - } - 404 -> null - else -> throw IllegalStateException("Invalid response $code") - } - } - - /** - * Update a resource at [path] of [targetType]. - */ - override fun <B, T> post( - path: String, - body: B, - targetType: TypeReference<T>, - ): T? { - val request = - HttpRequest.newBuilder(buildUri(path)) - .POST(HttpRequest.BodyPublishers.ofByteArray(mapper.writeValueAsBytes(body))) - .header("Content-Type", "application/json") - .also { auth?.injectToken(it) } - .build() - val response = client.send(request, HttpResponse.BodyHandlers.ofInputStream()) - - return when (val code = response.statusCode()) { - in 200..299 -> mapper.readValue(response.body(), targetType) - 401 -> { - val auth = auth - if (auth != null) { - auth.refreshToken() - post(path, body, targetType) - } else { - throw IllegalStateException("Authorization required") - } - } - 404 -> null - else -> throw IllegalStateException("Invalid response $code") - } - } - - /** - * Replace a resource at [path] of [targetType]. - */ - override fun <B, T> put( - path: String, - body: B, - targetType: TypeReference<T>, - ): T? { - val request = - HttpRequest.newBuilder(buildUri(path)) - .PUT(HttpRequest.BodyPublishers.ofByteArray(mapper.writeValueAsBytes(body))) - .header("Content-Type", "application/json") - .also { auth?.injectToken(it) } - .build() - val response = client.send(request, HttpResponse.BodyHandlers.ofInputStream()) - - return when (val code = response.statusCode()) { - in 200..299 -> mapper.readValue(response.body(), targetType) - 401 -> { - val auth = auth - if (auth != null) { - auth.refreshToken() - put(path, body, targetType) - } else { - throw IllegalStateException("Authorization required") - } - } - 404 -> null - else -> throw IllegalStateException("Invalid response $code") - } - } - - /** - * Delete a resource at [path] of [targetType]. - */ - override fun <T> delete( - path: String, - targetType: TypeReference<T>, - ): T? { - val request = - HttpRequest.newBuilder(buildUri(path)) - .DELETE() - .also { auth?.injectToken(it) } - .build() - val response = client.send(request, HttpResponse.BodyHandlers.ofInputStream()) - - return when (val code = response.statusCode()) { - in 200..299 -> mapper.readValue(response.body(), targetType) - 401 -> { - val auth = auth - if (auth != null) { - auth.refreshToken() - delete(path, targetType) - } else { - throw IllegalStateException("Authorization required") - } - } - 404 -> null - else -> throw IllegalStateException("Invalid response $code") - } - } - - /** - * Build the absolute [URI] to which the request should be sent. - */ - private fun buildUri(path: String): URI = baseUrl.resolve(Paths.get(baseUrl.path, path).toString()) -} diff --git a/opendc-web/opendc-web-client/src/main/kotlin/org/opendc/web/client/transport/TransportClient.kt b/opendc-web/opendc-web-client/src/main/kotlin/org/opendc/web/client/transport/TransportClient.kt deleted file mode 100644 index ebf3402f..00000000 --- a/opendc-web/opendc-web-client/src/main/kotlin/org/opendc/web/client/transport/TransportClient.kt +++ /dev/null @@ -1,64 +0,0 @@ -/* - * Copyright (c) 2022 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.web.client.transport - -import com.fasterxml.jackson.core.type.TypeReference - -/** - * Low-level interface for dealing with the transport layer of the API. - */ -public interface TransportClient { - /** - * Obtain a resource at [path] of [targetType]. - */ - public fun <T> get( - path: String, - targetType: TypeReference<T>, - ): T? - - /** - * Update a resource at [path] of [targetType]. - */ - public fun <B, T> post( - path: String, - body: B, - targetType: TypeReference<T>, - ): T? - - /** - * Replace a resource at [path] of [targetType]. - */ - public fun <B, T> put( - path: String, - body: B, - targetType: TypeReference<T>, - ): T? - - /** - * Delete a resource at [path] of [targetType]. - */ - public fun <T> delete( - path: String, - targetType: TypeReference<T>, - ): T? -} diff --git a/opendc-web/opendc-web-proto/build.gradle.kts b/opendc-web/opendc-web-proto/build.gradle.kts deleted file mode 100644 index 9b307655..00000000 --- a/opendc-web/opendc-web-proto/build.gradle.kts +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright (c) 2020 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -description = "Web communication protocol for OpenDC" - -// Build configuration -plugins { - `kotlin-library-conventions` - id("org.kordamp.gradle.jandex") // Necessary for Quarkus to process annotations -} - -dependencies { - implementation(libs.jackson.annotations) - implementation(libs.jakarta.validation) - implementation(libs.microprofile.openapi.api) -} - -tasks.withType<org.jetbrains.kotlin.gradle.tasks.KotlinCompile> { - kotlinOptions.javaParameters = true -} diff --git a/opendc-web/opendc-web-proto/src/main/kotlin/org/opendc/web/proto/JobState.kt b/opendc-web/opendc-web-proto/src/main/kotlin/org/opendc/web/proto/JobState.kt deleted file mode 100644 index a8e67ec5..00000000 --- a/opendc-web/opendc-web-proto/src/main/kotlin/org/opendc/web/proto/JobState.kt +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright (c) 2022 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.web.proto - -/** - * State of a scenario for the simulator runner. - */ -public enum class JobState { - /** - * The job is pending to be claimed by a runner. - */ - PENDING, - - /** - * The job is claimed by a runner. - */ - CLAIMED, - - /** - * The job is currently running. - */ - RUNNING, - - /** - * The job has finished. - */ - FINISHED, - - /** - * The job has failed. - */ - FAILED, -} diff --git a/opendc-web/opendc-web-proto/src/main/kotlin/org/opendc/web/proto/Machine.kt b/opendc-web/opendc-web-proto/src/main/kotlin/org/opendc/web/proto/Machine.kt deleted file mode 100644 index 72163f51..00000000 --- a/opendc-web/opendc-web-proto/src/main/kotlin/org/opendc/web/proto/Machine.kt +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright (c) 2022 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.web.proto - -import com.fasterxml.jackson.annotation.JsonProperty - -/** - * A machine in a rack. - */ -public data class Machine( - val id: String, - val position: Int, - val cpus: List<ProcessingUnit> = emptyList(), - val gpus: List<ProcessingUnit> = emptyList(), - @JsonProperty("memories") - val memory: List<MemoryUnit> = emptyList(), - @JsonProperty("storages") - val storage: List<MemoryUnit> = emptyList(), - val rackId: String? = null, -) diff --git a/opendc-web/opendc-web-proto/src/main/kotlin/org/opendc/web/proto/MemoryUnit.kt b/opendc-web/opendc-web-proto/src/main/kotlin/org/opendc/web/proto/MemoryUnit.kt deleted file mode 100644 index 00560ad6..00000000 --- a/opendc-web/opendc-web-proto/src/main/kotlin/org/opendc/web/proto/MemoryUnit.kt +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.web.proto - -/** - * A memory unit in a system. - */ -public data class MemoryUnit( - val id: String, - val name: String, - val speedMbPerS: Double, - val sizeMb: Double, - val energyConsumptionW: Double, -) diff --git a/opendc-web/opendc-web-proto/src/main/kotlin/org/opendc/web/proto/OperationalPhenomena.kt b/opendc-web/opendc-web-proto/src/main/kotlin/org/opendc/web/proto/OperationalPhenomena.kt deleted file mode 100644 index 28006d27..00000000 --- a/opendc-web/opendc-web-proto/src/main/kotlin/org/opendc/web/proto/OperationalPhenomena.kt +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.web.proto - -/** - * Object describing the enabled operational phenomena for a scenario. - */ -public data class OperationalPhenomena( - val failures: Boolean, - val interference: Boolean, -) diff --git a/opendc-web/opendc-web-proto/src/main/kotlin/org/opendc/web/proto/ProcessingUnit.kt b/opendc-web/opendc-web-proto/src/main/kotlin/org/opendc/web/proto/ProcessingUnit.kt deleted file mode 100644 index 86f40516..00000000 --- a/opendc-web/opendc-web-proto/src/main/kotlin/org/opendc/web/proto/ProcessingUnit.kt +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.web.proto - -/** - * A CPU model. - */ -public data class ProcessingUnit( - val id: String, - val name: String, - val clockRateMhz: Double, - val numberOfCores: Int, - val energyConsumptionW: Double, -) diff --git a/opendc-web/opendc-web-proto/src/main/kotlin/org/opendc/web/proto/ProtocolError.kt b/opendc-web/opendc-web-proto/src/main/kotlin/org/opendc/web/proto/ProtocolError.kt deleted file mode 100644 index e7fe2702..00000000 --- a/opendc-web/opendc-web-proto/src/main/kotlin/org/opendc/web/proto/ProtocolError.kt +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright (c) 2022 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.web.proto - -/** - * Container for reporting errors. - */ -public data class ProtocolError(val code: Int, val message: String) diff --git a/opendc-web/opendc-web-proto/src/main/kotlin/org/opendc/web/proto/Rack.kt b/opendc-web/opendc-web-proto/src/main/kotlin/org/opendc/web/proto/Rack.kt deleted file mode 100644 index c997e814..00000000 --- a/opendc-web/opendc-web-proto/src/main/kotlin/org/opendc/web/proto/Rack.kt +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright (c) 2022 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.web.proto - -/** - * A rack in a datacenter. - */ -public data class Rack( - val id: String, - val name: String, - val capacity: Int, - val powerCapacityW: Double, - val machines: List<Machine>, -) diff --git a/opendc-web/opendc-web-proto/src/main/kotlin/org/opendc/web/proto/Room.kt b/opendc-web/opendc-web-proto/src/main/kotlin/org/opendc/web/proto/Room.kt deleted file mode 100644 index 5b305168..00000000 --- a/opendc-web/opendc-web-proto/src/main/kotlin/org/opendc/web/proto/Room.kt +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright (c) 2022 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.web.proto - -/** - * A room in a datacenter. - */ -public data class Room( - val id: String, - val name: String, - val tiles: Set<RoomTile>, - val topologyId: String? = null, -) diff --git a/opendc-web/opendc-web-proto/src/main/kotlin/org/opendc/web/proto/RoomTile.kt b/opendc-web/opendc-web-proto/src/main/kotlin/org/opendc/web/proto/RoomTile.kt deleted file mode 100644 index 666d66ee..00000000 --- a/opendc-web/opendc-web-proto/src/main/kotlin/org/opendc/web/proto/RoomTile.kt +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright (c) 2022 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.web.proto - -/** - * A room tile. - */ -public data class RoomTile( - val id: String, - val positionX: Double, - val positionY: Double, - val rack: Rack? = null, - val roomId: String? = null, -) diff --git a/opendc-web/opendc-web-proto/src/main/kotlin/org/opendc/web/proto/Targets.kt b/opendc-web/opendc-web-proto/src/main/kotlin/org/opendc/web/proto/Targets.kt deleted file mode 100644 index 25516ff0..00000000 --- a/opendc-web/opendc-web-proto/src/main/kotlin/org/opendc/web/proto/Targets.kt +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright (c) 2022 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.web.proto - -import jakarta.validation.constraints.Min - -/** - * The targets of a portfolio. - * - * @param metrics The selected metrics to track during simulation. - * @param repeats The number of repetitions per scenario. - */ -public data class Targets( - val metrics: Set<String>, - @field:Min(1) - val repeats: Int = 1, -) diff --git a/opendc-web/opendc-web-proto/src/main/kotlin/org/opendc/web/proto/Trace.kt b/opendc-web/opendc-web-proto/src/main/kotlin/org/opendc/web/proto/Trace.kt deleted file mode 100644 index 2952a273..00000000 --- a/opendc-web/opendc-web-proto/src/main/kotlin/org/opendc/web/proto/Trace.kt +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright (c) 2022 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.web.proto - -/** - * A workload trace available for simulation. - * - * @param id The unique identifier of the trace. - * @param name The name of the trace. - * @param type The type of trace. - */ -public data class Trace( - val id: String, - val name: String, - val type: String, -) diff --git a/opendc-web/opendc-web-proto/src/main/kotlin/org/opendc/web/proto/Workload.kt b/opendc-web/opendc-web-proto/src/main/kotlin/org/opendc/web/proto/Workload.kt deleted file mode 100644 index 58daf817..00000000 --- a/opendc-web/opendc-web-proto/src/main/kotlin/org/opendc/web/proto/Workload.kt +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.web.proto - -import jakarta.validation.constraints.DecimalMax -import jakarta.validation.constraints.DecimalMin - -/** - * The workload to simulate for a scenario. - */ -public data class Workload(val trace: Trace, val samplingFraction: Double) { - /** - * Specification for a workload. - * - * @param trace The unique identifier of the trace. - * @param samplingFraction The fraction of the workload to sample. - */ - public data class Spec( - val trace: String, - @DecimalMin(value = "0.001", message = "Sampling fraction must be non-zero") - @DecimalMax(value = "1", message = "Sampling fraction cannot exceed one") - val samplingFraction: Double, - ) -} diff --git a/opendc-web/opendc-web-proto/src/main/kotlin/org/opendc/web/proto/runner/Job.kt b/opendc-web/opendc-web-proto/src/main/kotlin/org/opendc/web/proto/runner/Job.kt deleted file mode 100644 index 34642436..00000000 --- a/opendc-web/opendc-web-proto/src/main/kotlin/org/opendc/web/proto/runner/Job.kt +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright (c) 2022 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.web.proto.runner - -import org.eclipse.microprofile.openapi.annotations.media.Schema -import org.opendc.web.proto.JobState -import java.time.Instant - -/** - * A simulation job to be simulated by a runner. - */ -@Schema(name = "Runner.Job") -public data class Job( - val id: Long, - val scenario: Scenario, - val state: JobState, - val createdAt: Instant, - val updatedAt: Instant, - val runtime: Int, - val results: Map<String, Any>? = null, -) { - /** - * A request to update the state of a job. - * - * @property state The next state of the job. - * @property runtime The runtime of the job (in seconds). - * @property results The results of the job. - */ - @Schema(name = "Runner.Job.Update") - public data class Update(val state: JobState, val runtime: Int, val results: Map<String, Any>? = null) -} diff --git a/opendc-web/opendc-web-proto/src/main/kotlin/org/opendc/web/proto/runner/Portfolio.kt b/opendc-web/opendc-web-proto/src/main/kotlin/org/opendc/web/proto/runner/Portfolio.kt deleted file mode 100644 index 916d8cf0..00000000 --- a/opendc-web/opendc-web-proto/src/main/kotlin/org/opendc/web/proto/runner/Portfolio.kt +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright (c) 2022 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.web.proto.runner - -import org.eclipse.microprofile.openapi.annotations.media.Schema -import org.opendc.web.proto.Targets -import org.opendc.web.proto.user.Portfolio - -/** - * A [Portfolio] as seen from the runner's perspective. - * - * @param id The unique identifier of the portfolio. - * @param number The number of the portfolio for the project. - * @param name The name of the portfolio. - * @param targets The targets of the portfolio. - */ -@Schema(name = "Runner.Portfolio") -public data class Portfolio( - val id: Long, - val number: Int, - val name: String, - val targets: Targets, -) diff --git a/opendc-web/opendc-web-proto/src/main/kotlin/org/opendc/web/proto/runner/Scenario.kt b/opendc-web/opendc-web-proto/src/main/kotlin/org/opendc/web/proto/runner/Scenario.kt deleted file mode 100644 index ebc10bb0..00000000 --- a/opendc-web/opendc-web-proto/src/main/kotlin/org/opendc/web/proto/runner/Scenario.kt +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright (c) 2022 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.web.proto.runner - -import org.eclipse.microprofile.openapi.annotations.media.Schema -import org.opendc.web.proto.OperationalPhenomena -import org.opendc.web.proto.Workload - -/** - * A [Scenario] that is exposed to an OpenDC runner. - */ -@Schema(name = "Runner.Scenario") -public data class Scenario( - val id: Long, - val number: Int, - val portfolio: Portfolio, - val name: String, - val workload: Workload, - val topology: Topology, - val phenomena: OperationalPhenomena, - val schedulerName: String, -) diff --git a/opendc-web/opendc-web-proto/src/main/kotlin/org/opendc/web/proto/runner/Topology.kt b/opendc-web/opendc-web-proto/src/main/kotlin/org/opendc/web/proto/runner/Topology.kt deleted file mode 100644 index 4bffdee9..00000000 --- a/opendc-web/opendc-web-proto/src/main/kotlin/org/opendc/web/proto/runner/Topology.kt +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright (c) 2022 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.web.proto.runner - -import org.eclipse.microprofile.openapi.annotations.media.Schema -import org.opendc.web.proto.Room -import java.time.Instant - -/** - * A [Topology] that is exposed to an OpenDC runner. - */ -@Schema(name = "Runner.Topology") -public data class Topology( - val id: Long, - val number: Int, - val name: String, - val rooms: List<Room>, - val createdAt: Instant, - val updatedAt: Instant, -) diff --git a/opendc-web/opendc-web-proto/src/main/kotlin/org/opendc/web/proto/user/Job.kt b/opendc-web/opendc-web-proto/src/main/kotlin/org/opendc/web/proto/user/Job.kt deleted file mode 100644 index dd2f209e..00000000 --- a/opendc-web/opendc-web-proto/src/main/kotlin/org/opendc/web/proto/user/Job.kt +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright (c) 2022 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.web.proto.user - -import org.opendc.web.proto.JobState -import org.opendc.web.proto.runner.Job -import java.time.Instant - -/** - * A simulation job that is associated with a [Scenario]. - * - * This entity is exposed in the runner-facing API via [Job]. - */ -public data class Job( - val id: Long, - val state: JobState, - val createdAt: Instant, - val updatedAt: Instant, - val results: Map<String, Any>? = null, -) diff --git a/opendc-web/opendc-web-proto/src/main/kotlin/org/opendc/web/proto/user/Portfolio.kt b/opendc-web/opendc-web-proto/src/main/kotlin/org/opendc/web/proto/user/Portfolio.kt deleted file mode 100644 index 6f433a04..00000000 --- a/opendc-web/opendc-web-proto/src/main/kotlin/org/opendc/web/proto/user/Portfolio.kt +++ /dev/null @@ -1,72 +0,0 @@ -/* - * Copyright (c) 2022 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.web.proto.user - -import jakarta.validation.constraints.NotBlank -import org.eclipse.microprofile.openapi.annotations.media.Schema -import org.opendc.web.proto.Targets - -/** - * A portfolio is the composition of multiple scenarios. - * - * @param id The unique identifier of the portfolio. - * @param number The number of the portfolio with respect to the project. - * @param project The project to which the portfolio belongs. - * @param name The name of the portfolio. - * @param targets The targets of the portfolio. - * @param scenarios The scenarios in the portfolio. - */ -public data class Portfolio( - val id: Long, - val number: Int, - val project: Project, - val name: String, - val targets: Targets, - val scenarios: List<Scenario.Summary>, -) { - /** - * A request to create a new portfolio. - */ - @Schema(name = "Portfolio.Update") - public data class Create( - @field:NotBlank(message = "Name must not be empty") - val name: String, - val targets: Targets, - ) - - /** - * A summary view of a [Portfolio] provided for nested relations. - * - * @param id The unique identifier of the portfolio. - * @param number The number of the portfolio for the project. - * @param name The name of the portfolio. - * @param targets The targets of the portfolio. - */ - @Schema(name = "Portfolio.Summary") - public data class Summary( - val id: Long, - val number: Int, - val name: String, - val targets: Targets, - ) -} diff --git a/opendc-web/opendc-web-proto/src/main/kotlin/org/opendc/web/proto/user/Project.kt b/opendc-web/opendc-web-proto/src/main/kotlin/org/opendc/web/proto/user/Project.kt deleted file mode 100644 index 635552a9..00000000 --- a/opendc-web/opendc-web-proto/src/main/kotlin/org/opendc/web/proto/user/Project.kt +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright (c) 2022 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.web.proto.user - -import jakarta.validation.constraints.NotBlank -import org.eclipse.microprofile.openapi.annotations.media.Schema -import java.time.Instant - -/** - * A project in OpenDC encapsulates all the datacenter designs and simulation runs for a set of users. - */ -public data class Project( - val id: Long, - val name: String, - val createdAt: Instant, - val updatedAt: Instant, - val role: ProjectRole, -) { - /** - * A request to create a new project. - */ - @Schema(name = "Project.Create") - public data class Create( - @field:NotBlank(message = "Name must not be empty") val name: String, - ) -} diff --git a/opendc-web/opendc-web-proto/src/main/kotlin/org/opendc/web/proto/user/ProjectRole.kt b/opendc-web/opendc-web-proto/src/main/kotlin/org/opendc/web/proto/user/ProjectRole.kt deleted file mode 100644 index 0f6de1fc..00000000 --- a/opendc-web/opendc-web-proto/src/main/kotlin/org/opendc/web/proto/user/ProjectRole.kt +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright (c) 2022 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.web.proto.user - -/** - * The role of a user in a project. - */ -public enum class ProjectRole { - /** - * The user is allowed to view the project. - */ - VIEWER, - - /** - * The user is allowed to edit the project. - */ - EDITOR, - - /** - * The user owns the project (so he can delete it). - */ - OWNER, -} diff --git a/opendc-web/opendc-web-proto/src/main/kotlin/org/opendc/web/proto/user/Scenario.kt b/opendc-web/opendc-web-proto/src/main/kotlin/org/opendc/web/proto/user/Scenario.kt deleted file mode 100644 index e0c790f5..00000000 --- a/opendc-web/opendc-web-proto/src/main/kotlin/org/opendc/web/proto/user/Scenario.kt +++ /dev/null @@ -1,86 +0,0 @@ -/* - * Copyright (c) 2022 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.web.proto.user - -import jakarta.validation.constraints.NotBlank -import org.eclipse.microprofile.openapi.annotations.media.Schema -import org.opendc.web.proto.OperationalPhenomena -import org.opendc.web.proto.Workload - -/** - * A single scenario to be explored by the simulator. - */ -public data class Scenario( - val id: Long, - val number: Int, - val project: Project, - val portfolio: Portfolio.Summary, - val name: String, - val workload: Workload, - val topology: Topology.Summary, - val phenomena: OperationalPhenomena, - val schedulerName: String, - val jobs: List<Job>, -) { - /** - * Create a new scenario. - * - * @param name The name of the scenario. - * @param workload The workload specification to use for the scenario. - * @param topology The number of the topology to use. - * @param phenomena The phenomena to model during simulation. - * @param schedulerName The name of the scheduler. - */ - @Schema(name = "Scenario.Create") - public data class Create( - @field:NotBlank(message = "Name must not be empty") - val name: String, - val workload: Workload.Spec, - val topology: Long, - val phenomena: OperationalPhenomena, - val schedulerName: String, - ) - - /** - * A summary view of a [Scenario] provided for nested relations. - * - * @param id The unique identifier of the scenario. - * @param number The number of the scenario for the project. - * @param name The name of the scenario. - * @param workload The workload to be modeled by the scenario. - * @param phenomena The phenomena simulated for this scenario. - * @param schedulerName The scheduler name to use for the experiment. - * @param job The simulation job associated with the scenario. - */ - @Schema(name = "Scenario.Summary") - public data class Summary( - val id: Long, - val number: Int, - val name: String, - val workload: Workload, - val topology: Topology.Summary, - val phenomena: OperationalPhenomena, - val schedulerName: String, - val jobs: List<Job>, - ) -} diff --git a/opendc-web/opendc-web-proto/src/main/kotlin/org/opendc/web/proto/user/Topology.kt b/opendc-web/opendc-web-proto/src/main/kotlin/org/opendc/web/proto/user/Topology.kt deleted file mode 100644 index 0943eaf8..00000000 --- a/opendc-web/opendc-web-proto/src/main/kotlin/org/opendc/web/proto/user/Topology.kt +++ /dev/null @@ -1,75 +0,0 @@ -/* - * Copyright (c) 2022 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.web.proto.user - -import jakarta.validation.constraints.NotBlank -import org.eclipse.microprofile.openapi.annotations.media.Schema -import org.opendc.web.proto.Room -import java.time.Instant - -/** - * Model for an OpenDC topology. - */ -public data class Topology( - val id: Long, - val number: Int, - val project: Project, - val name: String, - val rooms: List<Room>, - val createdAt: Instant, - val updatedAt: Instant, -) { - /** - * Create a new topology for a project. - */ - @Schema(name = "Topology.Create") - public data class Create( - @field:NotBlank(message = "Name must not be empty") - val name: String, - val rooms: List<Room>, - ) - - /** - * Update an existing topology. - */ - @Schema(name = "Topology.Update") - public data class Update(val rooms: List<Room>) - - /** - * A summary view of a [Topology] provided for nested relations. - * - * @param id The unique identifier of the topology. - * @param number The number of the topology for the project. - * @param name The name of the topology. - * @param createdAt The instant at which the topology was created. - * @param updatedAt The instant at which the topology was updated. - */ - @Schema(name = "Topology.Summary") - public data class Summary( - val id: Long, - val number: Int, - val name: String, - val createdAt: Instant, - val updatedAt: Instant, - ) -} diff --git a/opendc-web/opendc-web-proto/src/main/kotlin/org/opendc/web/proto/user/User.kt b/opendc-web/opendc-web-proto/src/main/kotlin/org/opendc/web/proto/user/User.kt deleted file mode 100644 index 33dad4ff..00000000 --- a/opendc-web/opendc-web-proto/src/main/kotlin/org/opendc/web/proto/user/User.kt +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright (c) 2022 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.web.proto.user - -/** - * A user of OpenDC. - */ -public data class User( - val userId: String, - val accounting: UserAccounting, -) diff --git a/opendc-web/opendc-web-proto/src/main/kotlin/org/opendc/web/proto/user/UserAccounting.kt b/opendc-web/opendc-web-proto/src/main/kotlin/org/opendc/web/proto/user/UserAccounting.kt deleted file mode 100644 index 970721eb..00000000 --- a/opendc-web/opendc-web-proto/src/main/kotlin/org/opendc/web/proto/user/UserAccounting.kt +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright (c) 2022 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.web.proto.user - -import java.time.LocalDate - -/** - * Accounting data for a user. - */ -public data class UserAccounting( - val periodEnd: LocalDate, - val simulationTime: Int, - val simulationTimeBudget: Int, -) diff --git a/opendc-web/opendc-web-runner-quarkus-deployment/build.gradle.kts b/opendc-web/opendc-web-runner-quarkus-deployment/build.gradle.kts deleted file mode 100644 index 589337f4..00000000 --- a/opendc-web/opendc-web-runner-quarkus-deployment/build.gradle.kts +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright (c) 2022 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -description = "Quarkus extension for the OpenDC experiment runner" - -// Build configuration -plugins { - `java-library-conventions` -} - -dependencies { - implementation(projects.opendcWeb.opendcWebRunnerQuarkus) - implementation(projects.opendcTrace.opendcTraceApi) - - implementation(platform(libs.quarkus.bom)) - implementation(libs.quarkus.core.deployment) - implementation(libs.quarkus.arc.deployment) -} diff --git a/opendc-web/opendc-web-runner-quarkus-deployment/src/main/java/org/opendc/web/runner/deployment/OpenDCRunnerBuildItem.java b/opendc-web/opendc-web-runner-quarkus-deployment/src/main/java/org/opendc/web/runner/deployment/OpenDCRunnerBuildItem.java deleted file mode 100644 index 3be15ee3..00000000 --- a/opendc-web/opendc-web-runner-quarkus-deployment/src/main/java/org/opendc/web/runner/deployment/OpenDCRunnerBuildItem.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright (c) 2022 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.web.runner.deployment; - -import io.quarkus.builder.item.SimpleBuildItem; -import io.quarkus.runtime.RuntimeValue; -import org.opendc.web.runner.OpenDCRunner; - -/** - * A {@link SimpleBuildItem} that produces an {@link OpenDCRunner} instance. - */ -public final class OpenDCRunnerBuildItem extends SimpleBuildItem { - private final RuntimeValue<OpenDCRunner> runner; - - public OpenDCRunnerBuildItem(RuntimeValue<OpenDCRunner> runner) { - this.runner = runner; - } - - public RuntimeValue<OpenDCRunner> getRunner() { - return runner; - } -} diff --git a/opendc-web/opendc-web-runner-quarkus-deployment/src/main/java/org/opendc/web/runner/deployment/OpenDCRunnerConfig.java b/opendc-web/opendc-web-runner-quarkus-deployment/src/main/java/org/opendc/web/runner/deployment/OpenDCRunnerConfig.java deleted file mode 100644 index ccbc5e19..00000000 --- a/opendc-web/opendc-web-runner-quarkus-deployment/src/main/java/org/opendc/web/runner/deployment/OpenDCRunnerConfig.java +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright (c) 2022 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.web.runner.deployment; - -import io.quarkus.runtime.annotations.ConfigItem; -import io.quarkus.runtime.annotations.ConfigRoot; - -/** - * Build-time configuration for the OpenDC web runner extension. - */ -@ConfigRoot(name = "opendc-runner") -public class OpenDCRunnerConfig { - /** - * A flag to include the OpenDC web runner extension into the build. - */ - @ConfigItem(defaultValue = "true") - boolean include; -} diff --git a/opendc-web/opendc-web-runner-quarkus-deployment/src/main/java/org/opendc/web/runner/deployment/OpenDCRunnerProcessor.java b/opendc-web/opendc-web-runner-quarkus-deployment/src/main/java/org/opendc/web/runner/deployment/OpenDCRunnerProcessor.java deleted file mode 100644 index 85a973e7..00000000 --- a/opendc-web/opendc-web-runner-quarkus-deployment/src/main/java/org/opendc/web/runner/deployment/OpenDCRunnerProcessor.java +++ /dev/null @@ -1,120 +0,0 @@ -/* - * Copyright (c) 2022 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.web.runner.deployment; - -import static io.quarkus.deployment.annotations.ExecutionTime.RUNTIME_INIT; - -import io.quarkus.arc.deployment.UnremovableBeanBuildItem; -import io.quarkus.deployment.annotations.BuildProducer; -import io.quarkus.deployment.annotations.BuildStep; -import io.quarkus.deployment.annotations.Record; -import io.quarkus.deployment.builditem.*; -import io.quarkus.deployment.builditem.nativeimage.ServiceProviderBuildItem; -import io.quarkus.deployment.util.ServiceUtil; -import io.quarkus.runtime.RuntimeValue; -import java.io.IOException; -import java.util.Set; -import java.util.function.BooleanSupplier; -import org.opendc.trace.spi.TraceFormat; -import org.opendc.web.runner.JobManager; -import org.opendc.web.runner.OpenDCRunner; -import org.opendc.web.runner.runtime.OpenDCRunnerRecorder; -import org.opendc.web.runner.runtime.OpenDCRunnerRuntimeConfig; - -/** - * Build processor for the OpenDC web runner Quarkus extension. - */ -public class OpenDCRunnerProcessor { - - private static final String FEATURE = "opendc-runner"; - - /** - * Provide the {@link FeatureBuildItem} for this Quarkus extension. - */ - @BuildStep(onlyIf = IsIncluded.class) - public FeatureBuildItem feature() { - return new FeatureBuildItem(FEATURE); - } - - /** - * Build step to register the trace formats used by OpenDC. - */ - @BuildStep - void registerTraceFormats(BuildProducer<ServiceProviderBuildItem> services) throws IOException { - String service = "META-INF/services/" + TraceFormat.class.getName(); - - Set<String> implementations = - ServiceUtil.classNamesNamedIn(Thread.currentThread().getContextClassLoader(), service); - - services.produce( - new ServiceProviderBuildItem(TraceFormat.class.getName(), implementations.toArray(new String[0]))); - } - - /** - * Mark {@link JobManager} as unremoveable, since we look up this service dynamically in {@link OpenDCRunnerRecorder}. - */ - @BuildStep - UnremovableBeanBuildItem unremovableBeans() { - return UnremovableBeanBuildItem.beanTypes(JobManager.class); - } - - /** - * Build step to create the runner service. - */ - @BuildStep(onlyIf = IsIncluded.class) - @Record(RUNTIME_INIT) - ServiceStartBuildItem createRunnerService( - OpenDCRunnerRecorder recorder, - OpenDCRunnerRuntimeConfig config, - BuildProducer<OpenDCRunnerBuildItem> runnerBuildItem) { - RuntimeValue<OpenDCRunner> runner = recorder.createRunner(config); - runnerBuildItem.produce(new OpenDCRunnerBuildItem(runner)); - return new ServiceStartBuildItem("OpenDCRunnerService"); - } - - /** - * Build step to start the runner service. - */ - @BuildStep(onlyIf = IsIncluded.class) - @Record(RUNTIME_INIT) - void startRunnerService( - ApplicationStartBuildItem start, - OpenDCRunnerBuildItem runnerBuildItem, - OpenDCRunnerRecorder recorder, - OpenDCRunnerRuntimeConfig config, - ShutdownContextBuildItem shutdownContextBuildItem) { - recorder.startRunner(runnerBuildItem.getRunner(), config, shutdownContextBuildItem); - } - - /** - * A {@link BooleanSupplier} to determine if the OpenDC web runner extension should be included. - */ - private static class IsIncluded implements BooleanSupplier { - OpenDCRunnerConfig config; - - @Override - public boolean getAsBoolean() { - return config.include; - } - } -} diff --git a/opendc-web/opendc-web-runner-quarkus/build.gradle.kts b/opendc-web/opendc-web-runner-quarkus/build.gradle.kts deleted file mode 100644 index 8e4e08d5..00000000 --- a/opendc-web/opendc-web-runner-quarkus/build.gradle.kts +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright (c) 2022 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -description = "Quarkus extension for the OpenDC experiment runner" - -plugins { - `java-library-conventions` - id("io.quarkus.extension") -} - -quarkusExtension { - deploymentModule.set("opendc-web-runner-quarkus-deployment") -} - -dependencies { - modules { - module("javax.annotation:javax.annotation-api") { - replacedBy("jakarta.annotation:jakarta.annotation-api", "javax has been replaced by Jakarta") - } - } - - api(projects.opendcWeb.opendcWebRunner) - - implementation(platform(libs.quarkus.bom)) - implementation(libs.quarkus.core.runtime) -} - -evaluationDependsOn(projects.opendcWeb.opendcWebRunnerQuarkusDeployment.dependencyProject.path) diff --git a/opendc-web/opendc-web-runner-quarkus/src/main/java/org/opendc/web/runner/runtime/OpenDCRunnerRecorder.java b/opendc-web/opendc-web-runner-quarkus/src/main/java/org/opendc/web/runner/runtime/OpenDCRunnerRecorder.java deleted file mode 100644 index d5d524f1..00000000 --- a/opendc-web/opendc-web-runner-quarkus/src/main/java/org/opendc/web/runner/runtime/OpenDCRunnerRecorder.java +++ /dev/null @@ -1,79 +0,0 @@ -/* - * Copyright (c) 2022 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.web.runner.runtime; - -import io.quarkus.runtime.RuntimeValue; -import io.quarkus.runtime.ShutdownContext; -import io.quarkus.runtime.annotations.Recorder; -import jakarta.enterprise.inject.spi.CDI; -import java.io.File; -import org.jboss.logging.Logger; -import org.opendc.web.runner.JobManager; -import org.opendc.web.runner.OpenDCRunner; - -/** - * Helper class for starting the OpenDC web runner. - */ -@Recorder -public class OpenDCRunnerRecorder { - private static final Logger LOGGER = Logger.getLogger(OpenDCRunnerRecorder.class.getName()); - - /** - * Helper method to create an {@link OpenDCRunner} instance. - */ - public RuntimeValue<OpenDCRunner> createRunner(OpenDCRunnerRuntimeConfig config) { - int parallelism = config.parallelism; - if (parallelism < 0) { - throw new IllegalArgumentException("Parallelism must be non-negative"); - } else if (parallelism == 0) { - parallelism = Math.min(1, Runtime.getRuntime().availableProcessors() - 1); - } - - JobManager manager = CDI.current().select(JobManager.class).get(); - OpenDCRunner runner = new OpenDCRunner( - manager, - new File(config.tracePath), - parallelism, - config.jobTimeout, - config.pollInterval, - config.heartbeatInterval); - - return new RuntimeValue<>(runner); - } - - /** - * Helper method to start the OpenDC runner service. - */ - public void startRunner( - RuntimeValue<OpenDCRunner> runner, OpenDCRunnerRuntimeConfig config, ShutdownContext shutdownContext) { - if (config.enable) { - LOGGER.info("Starting OpenDC Runner in background (polling every " + config.pollInterval + ")"); - - Thread thread = new Thread(runner.getValue()); - thread.setName("opendc-runner"); - thread.start(); - - shutdownContext.addShutdownTask(thread::interrupt); - } - } -} diff --git a/opendc-web/opendc-web-runner-quarkus/src/main/java/org/opendc/web/runner/runtime/OpenDCRunnerRuntimeConfig.java b/opendc-web/opendc-web-runner-quarkus/src/main/java/org/opendc/web/runner/runtime/OpenDCRunnerRuntimeConfig.java deleted file mode 100644 index 61c07e48..00000000 --- a/opendc-web/opendc-web-runner-quarkus/src/main/java/org/opendc/web/runner/runtime/OpenDCRunnerRuntimeConfig.java +++ /dev/null @@ -1,70 +0,0 @@ -/* - * Copyright (c) 2022 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.web.runner.runtime; - -import io.quarkus.runtime.annotations.ConfigItem; -import io.quarkus.runtime.annotations.ConfigPhase; -import io.quarkus.runtime.annotations.ConfigRoot; -import java.time.Duration; - -/** - * Configuration for the OpenDC web runner. - */ -@ConfigRoot(phase = ConfigPhase.RUN_TIME, name = "opendc-runner") -public class OpenDCRunnerRuntimeConfig { - /** - * Flag to indicate whether the runner should be enabled. - */ - @ConfigItem(defaultValue = "true") - public boolean enable; - - /** - * The path where the workload traces are located. - */ - @ConfigItem(defaultValue = "traces") - public String tracePath; - - /** - * The number of concurrent simulations - */ - @ConfigItem(defaultValue = "1") - public int parallelism; - - /** - * The maximum duration of a job. - */ - @ConfigItem(defaultValue = "10m") - public Duration jobTimeout; - - /** - * The interval between successive polls to the API. - */ - @ConfigItem(defaultValue = "30s") - public Duration pollInterval; - - /** - * The interval between successive heartbeats to the API. - */ - @ConfigItem(defaultValue = "1m") - public Duration heartbeatInterval; -} diff --git a/opendc-web/opendc-web-runner-quarkus/src/main/resources/META-INF/quarkus-extension.yaml b/opendc-web/opendc-web-runner-quarkus/src/main/resources/META-INF/quarkus-extension.yaml deleted file mode 100644 index b93b467a..00000000 --- a/opendc-web/opendc-web-runner-quarkus/src/main/resources/META-INF/quarkus-extension.yaml +++ /dev/null @@ -1,5 +0,0 @@ ---- -name: "OpenDC Web Runner" -metadata: - status: "preview" - unlisted: true diff --git a/opendc-web/opendc-web-runner/Dockerfile b/opendc-web/opendc-web-runner/Dockerfile deleted file mode 100644 index 9ec184ee..00000000 --- a/opendc-web/opendc-web-runner/Dockerfile +++ /dev/null @@ -1,27 +0,0 @@ -FROM eclipse-temurin:21-jdk-jammy -MAINTAINER OpenDC Maintainers <opendc@atlarge-research.com> - -# Obtain (cache) Gradle wrapper -COPY gradlew /app/ -COPY gradle /app/gradle -WORKDIR /app -RUN ./gradlew --version - -# Build project -COPY ./ /app/ -RUN ./gradlew --no-daemon :opendc-web:opendc-web-runner:installDist - -FROM eclipse-temurin:21-jdk-jammy -COPY --from=0 /app/opendc-web/opendc-web-runner/build/install /opt/ -COPY --from=0 /app/opendc-experiments/opendc-experiments-base/src/test/resources/workloadTraces \ - /opt/opendc/traces -WORKDIR /opt/opendc -CMD bin/opendc-web-runner - -LABEL org.opencontainers.image.authors="OpenDC Maintainers <opendc@atlarge-research.com>" -LABEL org.opencontainers.image.url="https://opendc.org" -LABEL org.opencontainers.image.documentation="https://opendc.org" -LABEL org.opencontainers.image.source="https://github.com/atlarge-research/opendc" -LABEL org.opencontainers.image.title="OpenDC Web Runner UI" -LABEL org.opencontainers.image.description="OpenDC Web Runner Docker Image" -LABEL org.opencontainers.image.vendor="AtLarge Research" diff --git a/opendc-web/opendc-web-runner/build.gradle.kts b/opendc-web/opendc-web-runner/build.gradle.kts deleted file mode 100644 index 97328324..00000000 --- a/opendc-web/opendc-web-runner/build.gradle.kts +++ /dev/null @@ -1,91 +0,0 @@ -/* - * Copyright (c) 2022 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -description = "Experiment runner for OpenDC" - -// Build configuration -plugins { - `kotlin-library-conventions` - distribution -} - -val cli: SourceSet by sourceSets.creating { - compileClasspath += sourceSets["main"].output - runtimeClasspath += sourceSets["main"].output -} - -val cliImplementation: Configuration by configurations.getting { - extendsFrom(configurations["implementation"]) -} -val cliRuntimeOnly: Configuration by configurations.getting -val cliRuntimeClasspath: Configuration by configurations.getting { - extendsFrom(configurations["runtimeClasspath"]) -} - -val cliJar by tasks.creating(Jar::class) { - from(cli.output) - - archiveBaseName.set("${project.name}-cli") -} - -dependencies { - api(projects.opendcWeb.opendcWebClient) - implementation(projects.opendcSimulator.opendcSimulatorCore) - implementation(projects.opendcTrace.opendcTraceApi) - - implementation(libs.kotlin.logging) - implementation(project(mapOf("path" to ":opendc-compute:opendc-compute-workload"))) - implementation(project(mapOf("path" to ":opendc-compute:opendc-compute-carbon"))) - implementation(project(mapOf("path" to ":opendc-experiments:opendc-experiments-base"))) - implementation(project(mapOf("path" to ":opendc-compute:opendc-compute-topology"))) - implementation(project(mapOf("path" to ":opendc-compute:opendc-compute-failure"))) - - cliImplementation(libs.clikt) - - cliRuntimeOnly(libs.log4j.core) - cliRuntimeOnly(libs.log4j.slf4j) - cliRuntimeOnly(libs.sentry.log4j2) -} - -val createCli by tasks.creating(CreateStartScripts::class) { - dependsOn(cliJar) - - applicationName = "opendc-runner" - mainClass.set("org.opendc.web.runner.cli.MainKt") - classpath = cliJar.outputs.files + cliRuntimeClasspath - outputDir = project.layout.buildDirectory.get().asFile.resolve("scripts") -} - -distributions { - main { - contents { - into("bin") { - from(createCli) - } - - into("lib") { - from(cliJar) - from(cliRuntimeClasspath) // Also includes main classpath - } - } - } -} diff --git a/opendc-web/opendc-web-runner/src/cli/kotlin/org/opendc/web/runner/Main.kt b/opendc-web/opendc-web-runner/src/cli/kotlin/org/opendc/web/runner/Main.kt deleted file mode 100644 index 5d35fd98..00000000 --- a/opendc-web/opendc-web-runner/src/cli/kotlin/org/opendc/web/runner/Main.kt +++ /dev/null @@ -1,132 +0,0 @@ -/* - * Copyright (c) 2022 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.web.runner - -import com.github.ajalt.clikt.core.CliktCommand -import com.github.ajalt.clikt.parameters.options.convert -import com.github.ajalt.clikt.parameters.options.default -import com.github.ajalt.clikt.parameters.options.defaultLazy -import com.github.ajalt.clikt.parameters.options.option -import com.github.ajalt.clikt.parameters.options.required -import com.github.ajalt.clikt.parameters.types.file -import com.github.ajalt.clikt.parameters.types.int -import mu.KotlinLogging -import org.opendc.web.client.auth.OpenIdAuthController -import org.opendc.web.client.runner.OpenDCRunnerClient -import java.io.File -import java.net.URI - -private val logger = KotlinLogging.logger {} - -/** - * Represents the CLI command for starting the OpenDC web runner. - */ -class RunnerCli : CliktCommand(name = "opendc-runner") { - /** - * The URL to the OpenDC API. - */ - private val apiUrl by option( - "--api-url", - help = "url to the OpenDC API", - envvar = "OPENDC_API_URL", - ) - .convert { URI(it) } - .default(URI("https://api.opendc.org/v2")) - - /** - * The auth domain to use. - */ - private val authDomain by option( - "--auth-domain", - help = "auth domain of the OpenDC API", - envvar = "AUTH0_DOMAIN", - ) - .required() - - /** - * The auth domain to use. - */ - private val authAudience by option( - "--auth-audience", - help = "auth audience of the OpenDC API", - envvar = "AUTH0_AUDIENCE", - ) - .required() - - /** - * The auth client ID to use. - */ - private val authClientId by option( - "--auth-id", - help = "auth client id of the OpenDC API", - envvar = "AUTH0_CLIENT_ID", - ) - .required() - - /** - * The auth client secret to use. - */ - private val authClientSecret by option( - "--auth-secret", - help = "auth client secret of the OpenDC API", - envvar = "AUTH0_CLIENT_SECRET", - ) - .required() - - /** - * The path to the traces directory. - */ - private val tracePath by option( - "--traces", - help = "path to the directory containing the traces", - envvar = "OPENDC_TRACES", - ) - .file(canBeFile = false) - .defaultLazy { File("traces/") } - - /** - * The number of threads used for simulations.. - */ - private val parallelism by option( - "--parallelism", - help = "maximum number of threads for simulations", - ) - .int() - .default(Runtime.getRuntime().availableProcessors() - 1) - - override fun run() { - logger.info { "Starting OpenDC web runner" } - - val client = OpenDCRunnerClient(baseUrl = apiUrl, OpenIdAuthController(authDomain, authClientId, authClientSecret, authAudience)) - val manager = JobManager(client) - val runner = OpenDCRunner(manager, tracePath, parallelism = parallelism) - - logger.info { "Watching for queued scenarios" } - runner.run() - } -} - -/** - * Main entry point of the runner. - */ -fun main(args: Array<String>): Unit = RunnerCli().main(args) diff --git a/opendc-web/opendc-web-runner/src/main/kotlin/org/opendc/web/runner/JobManager.kt b/opendc-web/opendc-web-runner/src/main/kotlin/org/opendc/web/runner/JobManager.kt deleted file mode 100644 index a517f3b4..00000000 --- a/opendc-web/opendc-web-runner/src/main/kotlin/org/opendc/web/runner/JobManager.kt +++ /dev/null @@ -1,82 +0,0 @@ -/* - * Copyright (c) 2022 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.web.runner - -import org.opendc.web.client.runner.OpenDCRunnerClient -import org.opendc.web.proto.runner.Job -import org.opendc.web.runner.internal.JobManagerImpl - -/** - * Interface used by the [OpenDCRunner] to manage the available jobs to be processed. - */ -public interface JobManager { - /** - * Find the next job that the simulator needs to process. - */ - public fun findNext(): Job? - - /** - * Claim the simulation job with the specified id. - */ - public fun claim(id: Long): Boolean - - /** - * Update the heartbeat of the specified job. - * - * @param id The identifier of the job. - * @param runtime The total runtime of the job. - * @return `true` if the job can continue, `false` if the job has been cancelled. - */ - public fun heartbeat( - id: Long, - runtime: Int, - ): Boolean - - /** - * Mark the job as failed. - */ - public fun fail( - id: Long, - runtime: Int, - ) - - /** - * Persist the specified results for the specified job. - */ - public fun finish( - id: Long, - runtime: Int, - results: Map<String, Any>, - ) - - public companion object { - /** - * Create a [JobManager] given the specified [client]. - */ - @JvmStatic - @JvmName("create") - public operator fun invoke(client: OpenDCRunnerClient): JobManager { - return JobManagerImpl(client) - } - } -} diff --git a/opendc-web/opendc-web-runner/src/main/kotlin/org/opendc/web/runner/OpenDCRunner.kt b/opendc-web/opendc-web-runner/src/main/kotlin/org/opendc/web/runner/OpenDCRunner.kt deleted file mode 100644 index 83583eab..00000000 --- a/opendc-web/opendc-web-runner/src/main/kotlin/org/opendc/web/runner/OpenDCRunner.kt +++ /dev/null @@ -1,385 +0,0 @@ -/* - * Copyright (c) 2022 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.web.runner - -import mu.KotlinLogging -import org.opendc.compute.failure.prefab.FailurePrefab -import org.opendc.compute.failure.prefab.createFailureModelPrefab -import org.opendc.compute.simulator.provisioner.Provisioner -import org.opendc.compute.simulator.provisioner.registerComputeMonitor -import org.opendc.compute.simulator.provisioner.setupComputeService -import org.opendc.compute.simulator.provisioner.setupHosts -import org.opendc.compute.simulator.scheduler.createPrefabComputeScheduler -import org.opendc.compute.simulator.service.ComputeService -import org.opendc.compute.topology.specs.ClusterSpec -import org.opendc.compute.topology.specs.HostSpec -import org.opendc.compute.topology.specs.PowerSourceSpec -import org.opendc.compute.workload.ComputeWorkloadLoader -import org.opendc.experiments.base.runner.replay -import org.opendc.simulator.compute.models.CpuModel -import org.opendc.simulator.compute.models.MachineModel -import org.opendc.simulator.compute.models.MemoryUnit -import org.opendc.simulator.compute.power.PowerModels -import org.opendc.simulator.kotlin.runSimulation -import org.opendc.web.proto.runner.Job -import org.opendc.web.proto.runner.Scenario -import org.opendc.web.proto.runner.Topology -import org.opendc.web.runner.internal.WebComputeMonitor -import java.io.File -import java.time.Duration -import java.time.Instant -import java.time.temporal.ChronoUnit -import java.util.Random -import java.util.concurrent.Executors -import java.util.concurrent.ForkJoinPool -import java.util.concurrent.ForkJoinPool.ForkJoinWorkerThreadFactory -import java.util.concurrent.ForkJoinWorkerThread -import java.util.concurrent.RecursiveAction -import java.util.concurrent.RecursiveTask -import java.util.concurrent.ScheduledExecutorService -import java.util.concurrent.TimeUnit - -/** - * Class to execute the pending jobs via the OpenDC web API. - * - * @param manager The underlying [JobManager] to manage the available jobs. - * @param tracePath The directory where the traces are located. - * @param jobTimeout The maximum duration of a simulation job. - * @param pollInterval The interval to poll the API with. - * @param heartbeatInterval The interval to send a heartbeat to the API server. - */ -public class OpenDCRunner( - private val manager: JobManager, - private val tracePath: File, - parallelism: Int = Runtime.getRuntime().availableProcessors(), - private val jobTimeout: Duration = Duration.ofMinutes(10), - private val pollInterval: Duration = Duration.ofSeconds(30), - private val heartbeatInterval: Duration = Duration.ofMinutes(1), -) : Runnable { - /** - * Logging instance for this runner. - */ - private val logger = KotlinLogging.logger {} - - /** - * Helper class to load the workloads. - */ - private val workloadLoader = ComputeWorkloadLoader(tracePath) - - /** - * The [ForkJoinPool] that is used to execute the simulation jobs. - */ - private val pool = - ForkJoinPool(parallelism, RunnerThreadFactory(Thread.currentThread().contextClassLoader), null, false) - - /** - * A [ScheduledExecutorService] to manage the heartbeat of simulation jobs as well as tracking the deadline of - * individual simulations. - */ - private val scheduler = Executors.newSingleThreadScheduledExecutor() - - /** - * Start the runner process. - * - * This method will block until interrupted and poll the OpenDC API for new jobs to execute. - */ - override fun run() { - try { - while (true) { - val job = manager.findNext() - if (job == null) { - Thread.sleep(pollInterval.toMillis()) - continue - } - - val id = job.id - - logger.info { "Found queued job $id: attempting to claim" } - - if (!manager.claim(id)) { - logger.info { "Failed to claim scenario" } - continue - } - - pool.submit(JobAction(job)) - } - } catch (_: InterruptedException) { - // Gracefully exit when the thread is interrupted - } finally { - workloadLoader.reset() - - pool.shutdown() - scheduler.shutdown() - - pool.awaitTermination(5, TimeUnit.SECONDS) - } - } - - /** - * A [RecursiveAction] that runs a simulation job (consisting of possible multiple simulations). - * - * @param job The job to simulate. - */ - private inner class JobAction(private val job: Job) : RecursiveAction() { - override fun compute() { - val id = job.id - val scenario = job.scenario - val startTime = Instant.now() - val currentThread = Thread.currentThread() - - val heartbeat = - scheduler.scheduleWithFixedDelay( - { - if (!manager.heartbeat(id, startTime.secondsSince())) { - currentThread.interrupt() - } - }, - 0, - heartbeatInterval.toMillis(), - TimeUnit.MILLISECONDS, - ) - - try { - val topology = convertTopology(scenario.topology) - val jobs = - (0 until scenario.portfolio.targets.repeats).map { repeat -> - SimulationTask( - scenario, - repeat, - topology, - ) - } - val results = invokeAll(jobs).map { it.rawResult } - - heartbeat.cancel(true) - - val duration = startTime.secondsSince() - logger.info { "Finished simulation for job $id (in $duration seconds)" } - - manager.finish( - id, - duration, - mapOf( - "total_requested_burst" to results.map { it.totalActiveTime + it.totalIdleTime }, - "total_granted_burst" to results.map { it.totalActiveTime }, - "total_overcommitted_burst" to results.map { it.totalStealTime }, - "total_interfered_burst" to results.map { it.totalLostTime }, - "mean_cpu_usage" to results.map { it.meanCpuUsage }, - "mean_cpu_demand" to results.map { it.meanCpuDemand }, - "mean_num_deployed_images" to results.map { it.meanNumDeployedImages }, - "max_num_deployed_images" to results.map { it.maxNumDeployedImages }, - "total_power_draw" to results.map { it.totalPowerDraw }, - "total_failure_slices" to results.map { it.totalFailureSlices }, - "total_failure_vm_slices" to results.map { it.totalFailureVmSlices }, - "total_vms_submitted" to results.map { it.totalVmsSubmitted }, - "total_vms_queued" to results.map { it.totalVmsQueued }, - "total_vms_finished" to results.map { it.totalVmsFinished }, - "total_vms_failed" to results.map { it.totalVmsFailed }, - ), - ) - } catch (e: Exception) { - // Check whether the job failed due to exceeding its time budget - if (Thread.interrupted()) { - logger.info { "Simulation job $id exceeded time limit (${startTime.secondsSince()} seconds)" } - } else { - logger.info(e) { "Simulation job $id failed" } - } - - try { - heartbeat.cancel(true) - manager.fail(id, startTime.secondsSince()) - } catch (e: Throwable) { - logger.error(e) { "Failed to update job" } - } - } - } - - /** - * Calculate the seconds since the specified instant. - */ - private fun Instant.secondsSince(): Int { - return ChronoUnit.SECONDS.between(this, Instant.now()).toInt() - } - } - - /** - * A [RecursiveTask] that simulates a single scenario. - * - * @param scenario The scenario to simulate. - * @param repeat The repeat number used to seed the simulation. - * @param topologyHosts The topology to simulate. - */ - private inner class SimulationTask( - private val scenario: Scenario, - private val repeat: Int, - private val topologyHosts: List<HostSpec>, - ) : RecursiveTask<WebComputeMonitor.Results>() { - override fun compute(): WebComputeMonitor.Results { - val monitor = WebComputeMonitor() - - // Schedule task that interrupts the simulation if it runs for too long. - val currentThread = Thread.currentThread() - val interruptTask = - scheduler.schedule({ currentThread.interrupt() }, jobTimeout.toMillis(), TimeUnit.MILLISECONDS) - - try { - runSimulation(monitor) - } finally { - interruptTask.cancel(false) - } - - return monitor.collectResults() - } - - /** - * Run a single simulation of the scenario. - */ - private fun runSimulation(monitor: WebComputeMonitor) = - runSimulation { - val serviceDomain = "compute.opendc.org" - val seed = repeat.toLong() - - val scenario = scenario - - val powerSourceSpec = - PowerSourceSpec( - totalPower = Long.MAX_VALUE, - ) - val topology = listOf(ClusterSpec("cluster", topologyHosts, powerSourceSpec)) - - Provisioner(dispatcher, seed).use { provisioner -> - -// val workload = -// trace(scenario.workload.trace.id).sampleByLoad(scenario.workload.samplingFraction) -// val vms = workload.resolve(workloadLoader, Random(seed)) - - val vms = workloadLoader.sampleByLoad(scenario.workload.samplingFraction) - val startTime = vms.minOf { it.submittedAt } - - provisioner.runSteps( - setupComputeService( - serviceDomain, - { createPrefabComputeScheduler(scenario.schedulerName, Random(it.seeder.nextLong()), timeSource) }, - ), - registerComputeMonitor(serviceDomain, monitor), - setupHosts(serviceDomain, topology, startTime), - ) - - val service = provisioner.registry.resolve(serviceDomain, ComputeService::class.java)!! - - val phenomena = scenario.phenomena - val failureModel = - if (phenomena.failures) { - createFailureModelPrefab(coroutineContext, timeSource, service, Random(seed), FailurePrefab.G5k06Exp) - } else { - null - } - - // Run workload trace - service.replay(timeSource, vms, seed = seed) - - val serviceMetrics = service.getSchedulerStats() - logger.debug { - "Scheduler " + - "Success=${serviceMetrics.attemptsSuccess} " + - "Failure=${serviceMetrics.attemptsFailure} " + - "Pending=${serviceMetrics.tasksPending} " + - "Active=${serviceMetrics.tasksActive}" - } - } - } - } - - /** - * Convert the specified [topology] into an [Topology] understood by OpenDC. - */ - private fun convertTopology(topology: Topology): List<HostSpec> { - val res = mutableListOf<HostSpec>() - val random = Random(0) - - val machines = - topology.rooms.asSequence() - .flatMap { room -> - room.tiles.flatMap { tile -> - val rack = tile.rack - rack?.machines?.map { machine -> rack to machine } ?: emptyList() - } - } - - for ((rack, machine) in machines) { - val clusterId = rack.id - val position = machine.position - - val processors = - machine.cpus.map { cpu -> - CpuModel( - 0, - cpu.numberOfCores, - cpu.clockRateMhz, - "Intel", - "amd64", - cpu.name, - ) - } - - val memoryUnits = - machine.memory.map { memory -> - MemoryUnit( - "Samsung", - memory.name, - memory.speedMbPerS, - memory.sizeMb.toLong(), - ) - } - - val energyConsumptionW = machine.cpus.sumOf { it.energyConsumptionW } - val cpuPowerModel = PowerModels.linear(2 * energyConsumptionW, energyConsumptionW * 0.5) - - val spec = - HostSpec( - "node-$clusterId-$position", - "node-$clusterId", - clusterId, - MachineModel(processors, memoryUnits[0]), - cpuPowerModel, - null, - ) - - res += spec - } - - return res - } - - /** - * A custom [ForkJoinWorkerThreadFactory] that uses the [ClassLoader] of specified by the runner. - */ - private class RunnerThreadFactory(private val classLoader: ClassLoader) : ForkJoinWorkerThreadFactory { - override fun newThread(pool: ForkJoinPool): ForkJoinWorkerThread = - object : ForkJoinWorkerThread(pool) { - init { - contextClassLoader = classLoader - } - } - } -} diff --git a/opendc-web/opendc-web-runner/src/main/kotlin/org/opendc/web/runner/internal/JobManagerImpl.kt b/opendc-web/opendc-web-runner/src/main/kotlin/org/opendc/web/runner/internal/JobManagerImpl.kt deleted file mode 100644 index 7081041c..00000000 --- a/opendc-web/opendc-web-runner/src/main/kotlin/org/opendc/web/runner/internal/JobManagerImpl.kt +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Copyright (c) 2022 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.web.runner.internal - -import org.opendc.web.client.runner.OpenDCRunnerClient -import org.opendc.web.proto.JobState -import org.opendc.web.proto.runner.Job -import org.opendc.web.runner.JobManager - -/** - * Default implementation of [JobManager] that uses the OpenDC client to receive jobs. - */ -internal class JobManagerImpl(private val client: OpenDCRunnerClient) : JobManager { - override fun findNext(): Job? { - return client.jobs.queryPending().firstOrNull() - } - - override fun claim(id: Long): Boolean { - return try { - client.jobs.update(id, Job.Update(JobState.CLAIMED, 0)) - true - } catch (e: IllegalStateException) { - false - } - } - - override fun heartbeat( - id: Long, - runtime: Int, - ): Boolean { - val res = client.jobs.update(id, Job.Update(JobState.RUNNING, runtime)) - return res?.state != JobState.FAILED - } - - override fun fail( - id: Long, - runtime: Int, - ) { - client.jobs.update(id, Job.Update(JobState.FAILED, runtime)) - } - - override fun finish( - id: Long, - runtime: Int, - results: Map<String, Any>, - ) { - client.jobs.update(id, Job.Update(JobState.FINISHED, runtime)) - } -} diff --git a/opendc-web/opendc-web-runner/src/main/kotlin/org/opendc/web/runner/internal/WebComputeMonitor.kt b/opendc-web/opendc-web-runner/src/main/kotlin/org/opendc/web/runner/internal/WebComputeMonitor.kt deleted file mode 100644 index 9288b403..00000000 --- a/opendc-web/opendc-web-runner/src/main/kotlin/org/opendc/web/runner/internal/WebComputeMonitor.kt +++ /dev/null @@ -1,134 +0,0 @@ -/* - * Copyright (c) 2022 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.web.runner.internal - -import org.opendc.compute.simulator.telemetry.ComputeMonitor -import org.opendc.compute.simulator.telemetry.table.host.HostTableReader -import org.opendc.compute.simulator.telemetry.table.service.ServiceData -import org.opendc.compute.simulator.telemetry.table.service.ServiceTableReader -import org.opendc.compute.simulator.telemetry.table.service.toServiceData -import kotlin.math.roundToLong - -/** - * A [ComputeMonitor] that tracks the aggregate metrics for each repeat. - */ -internal class WebComputeMonitor : ComputeMonitor { - override fun record(reader: HostTableReader) { - val slices = reader.downtime / sliceLength - - hostAggregateMetrics = - AggregateHostMetrics( - hostAggregateMetrics.totalActiveTime + reader.cpuActiveTime, - hostAggregateMetrics.totalIdleTime + reader.cpuIdleTime, - hostAggregateMetrics.totalStealTime + reader.cpuStealTime, - hostAggregateMetrics.totalLostTime + reader.cpuLostTime, - hostAggregateMetrics.totalPowerDraw + reader.energyUsage, - hostAggregateMetrics.totalFailureSlices + slices, - hostAggregateMetrics.totalFailureVmSlices + reader.tasksActive * slices, - ) - - hostMetrics.compute(reader.hostInfo.name) { _, prev -> - HostMetrics( - reader.cpuUsage + (prev?.cpuUsage ?: 0.0), - reader.cpuDemand + (prev?.cpuDemand ?: 0.0), - reader.tasksActive + (prev?.instanceCount ?: 0), - 1 + (prev?.count ?: 0), - ) - } - } - - private var hostAggregateMetrics: AggregateHostMetrics = AggregateHostMetrics() - private val hostMetrics: MutableMap<String, HostMetrics> = mutableMapOf() - private val sliceLength: Long = 5 * 60L - - private data class AggregateHostMetrics( - val totalActiveTime: Long = 0L, - val totalIdleTime: Long = 0L, - val totalStealTime: Long = 0L, - val totalLostTime: Long = 0L, - val totalPowerDraw: Double = 0.0, - val totalFailureSlices: Double = 0.0, - val totalFailureVmSlices: Double = 0.0, - ) - - private data class HostMetrics( - val cpuUsage: Double, - val cpuDemand: Double, - val instanceCount: Long, - val count: Long, - ) - - private lateinit var serviceData: ServiceData - - override fun record(reader: ServiceTableReader) { - serviceData = reader.toServiceData() - } - - /** - * Collect the results of the simulation. - */ - fun collectResults(): Results { - val hostAggregateMetrics = hostAggregateMetrics - val hostMetrics = hostMetrics - val serviceData = serviceData - - return Results( - hostAggregateMetrics.totalActiveTime, - hostAggregateMetrics.totalIdleTime, - hostAggregateMetrics.totalStealTime, - hostAggregateMetrics.totalLostTime, - hostMetrics.map { it.value.cpuUsage / it.value.count }.average().let { if (it.isNaN()) 0.0 else it }, - hostMetrics.map { it.value.cpuDemand / it.value.count }.average().let { if (it.isNaN()) 0.0 else it }, - hostMetrics.map { it.value.instanceCount.toDouble() / it.value.count }.average().let { if (it.isNaN()) 0.0 else it }, - hostMetrics.map { it.value.instanceCount.toDouble() / it.value.count }.maxOrNull() ?: 0.0, - hostAggregateMetrics.totalPowerDraw, - hostAggregateMetrics.totalFailureSlices.roundToLong(), - hostAggregateMetrics.totalFailureVmSlices.roundToLong(), - serviceData.tasksTotal, - serviceData.tasksPending, - serviceData.tasksTotal - serviceData.tasksPending - serviceData.tasksActive, - serviceData.attemptsTerminated, - ) - } - - /** - * Structure of the results of a single simulation. - */ - data class Results( - val totalActiveTime: Long, - val totalIdleTime: Long, - val totalStealTime: Long, - val totalLostTime: Long, - val meanCpuUsage: Double, - val meanCpuDemand: Double, - val meanNumDeployedImages: Double, - val maxNumDeployedImages: Double, - val totalPowerDraw: Double, - val totalFailureSlices: Long, - val totalFailureVmSlices: Long, - val totalVmsSubmitted: Int, - val totalVmsQueued: Int, - val totalVmsFinished: Int, - val totalVmsFailed: Int, - ) -} diff --git a/opendc-web/opendc-web-runner/src/main/resources/log4j2.xml b/opendc-web/opendc-web-runner/src/main/resources/log4j2.xml deleted file mode 100644 index ad99cc00..00000000 --- a/opendc-web/opendc-web-runner/src/main/resources/log4j2.xml +++ /dev/null @@ -1,48 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- - ~ MIT License - ~ - ~ Copyright (c) 2020 atlarge-research - ~ - ~ Permission is hereby granted, free of charge, to any person obtaining a copy - ~ of this software and associated documentation files (the "Software"), to deal - ~ in the Software without restriction, including without limitation the rights - ~ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - ~ copies of the Software, and to permit persons to whom the Software is - ~ furnished to do so, subject to the following conditions: - ~ - ~ The above copyright notice and this permission notice shall be included in all - ~ copies or substantial portions of the Software. - ~ - ~ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - ~ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - ~ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - ~ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - ~ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - ~ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - ~ SOFTWARE. - --> - -<Configuration status="WARN" packages="org.apache.logging.log4j.core,io.sentry.log4j2"> - <Appenders> - <Console name="Console" target="SYSTEM_OUT"> - <PatternLayout pattern="%d{HH:mm:ss.SSS} [%highlight{%-5level}] %logger{36} - %msg%n" disableAnsi="false"/> - </Console> - - <Sentry name="Sentry" /> - </Appenders> - <Loggers> - <Logger name="org.opendc" level="warn" additivity="false"> - <AppenderRef ref="Console"/> - <AppenderRef ref="Sentry"/> - </Logger> - <Logger name="org.opendc.web.runner" level="info" additivity="false"> - <AppenderRef ref="Console"/> - <AppenderRef ref="Sentry"/> - </Logger> - <Root level="info"> - <AppenderRef level="error" ref="Console"/> - <AppenderRef ref="Sentry"/> - </Root> - </Loggers> -</Configuration> diff --git a/opendc-web/opendc-web-server/Dockerfile b/opendc-web/opendc-web-server/Dockerfile deleted file mode 100644 index 2aee7ddf..00000000 --- a/opendc-web/opendc-web-server/Dockerfile +++ /dev/null @@ -1,33 +0,0 @@ -FROM eclipse-temurin:21-jdk-jammy -MAINTAINER OpenDC Maintainers <opendc@atlarge-research.com> - -# Obtain (cache) Gradle wrapper -COPY gradlew /app/ -COPY gradle /app/gradle -WORKDIR /app -RUN ./gradlew --version - -# Build project -ARG OPENDC_AUTH0_DOMAIN -ARG OPENDC_AUTH0_AUDIENCE -ARG OPENDC_AUTH0_DOCS_CLIENT_ID - -ENV OPENDC_AUTH0_DOMAIN=$OPENDC_AUTH0_DOMAIN -ENV OPENDC_AUTH0_AUDIENCE=$OPENDC_AUTH0_AUDIENCE -ENV OPENDC_AUTH0_DOCS_CLIENT_ID=$OPENDC_AUTH0_DOCS_CLIENT_ID - -COPY ./ /app/ -RUN ./gradlew --no-daemon :opendc-web:opendc-web-server:quarkusBuild -Dquarkus.profile=docker - -FROM eclipse-temurin:21-jdk-jammy -COPY --from=0 /app/opendc-web/opendc-web-server/build/quarkus-app /opt/opendc -WORKDIR /opt/opendc -CMD java -jar quarkus-run.jar - -LABEL org.opencontainers.image.authors="OpenDC Maintainers <opendc@atlarge-research.com>" -LABEL org.opencontainers.image.url="https://opendc.org" -LABEL org.opencontainers.image.documentation="https://opendc.org" -LABEL org.opencontainers.image.source="https://github.com/atlarge-research/opendc" -LABEL org.opencontainers.image.title="OpenDC" -LABEL org.opencontainers.image.description="OpenDC Docker Image" -LABEL org.opencontainers.image.vendor="AtLarge Research" diff --git a/opendc-web/opendc-web-server/build.gradle.kts b/opendc-web/opendc-web-server/build.gradle.kts deleted file mode 100644 index 484e98c0..00000000 --- a/opendc-web/opendc-web-server/build.gradle.kts +++ /dev/null @@ -1,96 +0,0 @@ -/* - * Copyright (c) 2020 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -description = "Web server of OpenDC" - -// Build configuration -plugins { - `quarkus-conventions` - distribution -} - -dependencies { - implementation(enforcedPlatform(libs.quarkus.bom)) - - implementation(projects.opendcWeb.opendcWebProto) - testImplementation("junit:junit:4.13.1") - testImplementation("junit:junit:4.13.1") - compileOnly(projects.opendcWeb.opendcWebUiQuarkusDeployment) // Temporary fix for Quarkus/Gradle issues - compileOnly(projects.opendcWeb.opendcWebRunnerQuarkusDeployment) - implementation(projects.opendcWeb.opendcWebUiQuarkus) - implementation(projects.opendcWeb.opendcWebRunnerQuarkus) - - implementation(libs.quarkus.kotlin) - implementation(libs.quarkus.resteasy.core) - implementation(libs.quarkus.resteasy.jackson) - implementation(libs.jackson.module.kotlin) - implementation(libs.quarkus.smallrye.openapi) - - implementation(libs.quarkus.security) - implementation(libs.quarkus.oidc) - - implementation(libs.quarkus.hibernate.orm.core) - implementation(libs.quarkus.hibernate.orm.panache) - implementation(libs.quarkus.hibernate.validator) - implementation(libs.quarkus.flyway) - implementation(libs.quarkus.jdbc.postgresql) - implementation(libs.quarkus.jdbc.h2) - implementation(libs.hypersistence.utils.hibernate) - - testImplementation(libs.quarkus.junit5.core) - testImplementation(libs.quarkus.jacoco) - testImplementation(libs.quarkus.panache.mock) - testImplementation(libs.restassured.core) - testImplementation(libs.quarkus.test.security) - testImplementation(libs.quarkus.jdbc.h2) -} - -val createStartScripts by tasks.creating(CreateStartScripts::class) { - applicationName = "opendc-server" - mainClass.set("io.quarkus.bootstrap.runner.QuarkusEntryPoint") - classpath = files("lib/quarkus-run.jar") - outputDir = project.layout.buildDirectory.get().asFile.resolve("scripts") -} - -distributions { - main { - distributionBaseName.set("opendc") - - contents { - from("../../LICENSE.txt") - from("config") { - into("config") - } - - from(createStartScripts) { - into("bin") - } - from(tasks.quarkusBuild) { - into("lib") - } - - from("../../traces") { - into("traces") - } - } - } -} diff --git a/opendc-web/opendc-web-server/config/application.properties b/opendc-web/opendc-web-server/config/application.properties deleted file mode 100644 index 30eaaef9..00000000 --- a/opendc-web/opendc-web-server/config/application.properties +++ /dev/null @@ -1 +0,0 @@ -# Custom server properties diff --git a/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/model/Job.java b/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/model/Job.java deleted file mode 100644 index a0ac390f..00000000 --- a/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/model/Job.java +++ /dev/null @@ -1,166 +0,0 @@ -/* - * Copyright (c) 2022 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.web.server.model; - -import io.hypersistence.utils.hibernate.type.json.JsonType; -import io.quarkus.hibernate.orm.panache.Panache; -import io.quarkus.hibernate.orm.panache.PanacheEntityBase; -import io.quarkus.hibernate.orm.panache.PanacheQuery; -import io.quarkus.panache.common.Parameters; -import jakarta.persistence.*; -import java.time.Instant; -import java.util.Map; -import org.hibernate.annotations.Type; -import org.opendc.web.proto.JobState; - -/** - * A simulation job to be run by the simulator. - */ -@Entity -@Table -@NamedQueries({ - @NamedQuery( - name = "Job.updateOne", - query = - """ - UPDATE Job j - SET j.state = :newState, j.updatedAt = :updatedAt, j.runtime = :runtime, j.results = :results - WHERE j.id = :id AND j.state = :oldState - """) -}) -public class Job extends PanacheEntityBase { - /** - * The main ID of a project. - * The value starts at 6 to account for the other 5 projects already made by the loading script. - */ - @Id - @SequenceGenerator(name = "jobSeq", sequenceName = "job_id_seq", allocationSize = 1, initialValue = 3) - @GeneratedValue(generator = "jobSeq") - public Long id; - - @ManyToOne(optional = false, fetch = FetchType.EAGER) - @JoinColumn(name = "scenario_id", foreignKey = @ForeignKey(name = "fk_jobs_scenario"), nullable = false) - public Scenario scenario; - - @Column(name = "created_by", nullable = false, updatable = false) - public String createdBy; - - @Column(name = "created_at", nullable = false, updatable = false) - public Instant createdAt; - - /** - * The number of simulation runs to perform. - */ - @Column(nullable = false, updatable = false) - public int repeats; - - /** - * The instant at which the job was updated. - */ - @Column(name = "updated_at", nullable = false) - public Instant updatedAt; - - /** - * The state of the job. - */ - @Enumerated(EnumType.STRING) - @Column(nullable = false) - public JobState state = JobState.PENDING; - - /** - * The runtime of the job (in seconds). - */ - @Column(nullable = false) - public int runtime = 0; - - /** - * Experiment results in JSON - */ - @Column(columnDefinition = "jsonb") - @Type(JsonType.class) - public Map<String, ?> results = null; - - /** - * Construct a {@link Job} instance. - */ - public Job(Scenario scenario, String createdBy, Instant createdAt, int repeats) { - this.createdBy = createdBy; - this.scenario = scenario; - this.createdAt = createdAt; - this.updatedAt = createdAt; - this.repeats = repeats; - } - - /** - * JPA constructor - */ - protected Job() {} - - /** - * Find {@link Job}s in the specified {@link JobState}. - * - * @param state The state of the jobs to find. - * @return A query for jobs that are in the specified state. - */ - public static PanacheQuery<Job> findByState(JobState state) { - return find("state", state); - } - - /** - * Atomically update this job. - * - * @param newState The new state to enter into. - * @param time The time at which the update occurs. - * @param results The results to possible set. - * @return <code>true</code> when the update succeeded`, <code>false</code> when there was a conflict. - */ - public boolean updateAtomically(JobState newState, Instant time, int runtime, Map<String, ?> results) { - long count = update( - "#Job.updateOne", - Parameters.with("id", id) - .and("oldState", state) - .and("newState", newState) - .and("updatedAt", time) - .and("runtime", runtime) - .and("results", results)); - Panache.getEntityManager().refresh(this); - return count > 0; - } - - /** - * Determine whether the job is allowed to transition to <code>newState</code>. - * - * @param newState The new state to transition to. - * @return <code>true</code> if the transition to the new state is legal, <code>false</code> otherwise. - */ - public boolean canTransitionTo(JobState newState) { - // Note that we always allow transitions from the state - return newState == this.state - || switch (this.state) { - case PENDING -> newState == JobState.CLAIMED; - case CLAIMED -> newState == JobState.RUNNING || newState == JobState.FAILED; - case RUNNING -> newState == JobState.FINISHED || newState == JobState.FAILED; - case FINISHED, FAILED -> false; - }; - } -} diff --git a/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/model/Portfolio.java b/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/model/Portfolio.java deleted file mode 100644 index c2695192..00000000 --- a/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/model/Portfolio.java +++ /dev/null @@ -1,151 +0,0 @@ -/* - * Copyright (c) 2023 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.web.server.model; - -import io.hypersistence.utils.hibernate.type.json.JsonType; -import io.quarkus.hibernate.orm.panache.PanacheEntityBase; -import io.quarkus.hibernate.orm.panache.PanacheQuery; -import io.quarkus.panache.common.Parameters; -import jakarta.persistence.CascadeType; -import jakarta.persistence.Column; -import jakarta.persistence.Entity; -import jakarta.persistence.GeneratedValue; -import jakarta.persistence.Id; -import jakarta.persistence.Index; -import jakarta.persistence.JoinColumn; -import jakarta.persistence.ManyToOne; -import jakarta.persistence.NamedQueries; -import jakarta.persistence.NamedQuery; -import jakarta.persistence.OneToMany; -import jakarta.persistence.OrderBy; -import jakarta.persistence.SequenceGenerator; -import jakarta.persistence.Table; -import jakarta.persistence.UniqueConstraint; -import java.util.HashSet; -import java.util.Set; -import org.hibernate.annotations.Type; -import org.opendc.web.proto.Targets; - -/** - * A portfolio is the composition of multiple scenarios. - */ -@Entity -@Table( - uniqueConstraints = { - @UniqueConstraint( - name = "uk_portfolios_number", - columnNames = {"project_id", "number"}) - }, - indexes = {@Index(name = "ux_portfolios_number", columnList = "project_id, number")}) -@NamedQueries({ - @NamedQuery(name = "Portfolio.findByProject", query = "SELECT p FROM Portfolio p WHERE p.project.id = :projectId"), - @NamedQuery( - name = "Portfolio.findOneByProject", - query = "SELECT p FROM Portfolio p WHERE p.project.id = :projectId AND p.number = :number") -}) -public class Portfolio extends PanacheEntityBase { - - /** - * The main ID of a project. - * The value starts at 6 to account for the other 5 projects already made by the loading script. - */ - @Id - @SequenceGenerator(name = "portfolioSeq", sequenceName = "portfolio_id_seq", allocationSize = 1, initialValue = 4) - @GeneratedValue(generator = "portfolioSeq") - public Long id; - - /** - * The {@link Project} this portfolio belongs to. - */ - @ManyToOne(optional = false) - @JoinColumn(name = "project_id", nullable = false) - public Project project; - - /** - * Unique number of the portfolio for the project. - */ - @Column(nullable = false) - public int number; - - /** - * The name of this portfolio. - */ - @Column(nullable = false) - public String name; - - /** - * The portfolio targets (metrics, repetitions). - */ - @Column(columnDefinition = "jsonb", nullable = false, updatable = false) - @Type(JsonType.class) - public Targets targets; - - /** - * The scenarios in this portfolio. - */ - @OneToMany( - cascade = {CascadeType.ALL}, - mappedBy = "portfolio", - orphanRemoval = true) - @OrderBy("id ASC") - public Set<Scenario> scenarios = new HashSet<>(); - - /** - * Construct a {@link Portfolio} object. - */ - public Portfolio(Project project, int number, String name, Targets targets) { - this.project = project; - this.number = number; - this.name = name; - this.targets = targets; - } - - /** - * JPA constructor - */ - protected Portfolio() {} - - /** - * Find all {@link Portfolio}s that belong to the specified project - * - * @param projectId The unique identifier of the project. - * @return The query of portfolios that belong to the specified project. - */ - public static PanacheQuery<Portfolio> findByProject(long projectId) { - return find("#Portfolio.findByProject", Parameters.with("projectId", projectId)); - } - - /** - * Find the {@link Portfolio} with the specified <code>number</code> belonging to the specified project. - * - * @param projectId The unique identifier of the project. - * @param number The number of the scenario. - * @return The portfolio or <code>null</code> if it does not exist. - */ - public static Portfolio findByProject(long projectId, int number) { - return find( - "#Portfolio.findOneByProject", - Parameters.with("projectId", projectId).and("number", number)) - .firstResult(); - } -} diff --git a/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/model/Project.java b/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/model/Project.java deleted file mode 100644 index f4e5305d..00000000 --- a/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/model/Project.java +++ /dev/null @@ -1,237 +0,0 @@ -/* - * Copyright (c) 2022 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.web.server.model; - -import io.quarkus.hibernate.orm.panache.Panache; -import io.quarkus.hibernate.orm.panache.PanacheEntityBase; -import io.quarkus.panache.common.Parameters; -import jakarta.persistence.CascadeType; -import jakarta.persistence.Column; -import jakarta.persistence.Entity; -import jakarta.persistence.GeneratedValue; -import jakarta.persistence.Id; -import jakarta.persistence.NamedQueries; -import jakarta.persistence.NamedQuery; -import jakarta.persistence.OneToMany; -import jakarta.persistence.OrderBy; -import jakarta.persistence.SequenceGenerator; -import jakarta.persistence.Table; -import java.time.Instant; -import java.util.HashSet; -import java.util.Set; - -/** - * A project in OpenDC encapsulates all the datacenter designs and simulation runs for a set of users. - */ -@Entity -@Table -@NamedQueries({ - @NamedQuery( - name = "Project.findByUser", - query = - """ - SELECT a - FROM ProjectAuthorization a - WHERE a.key.userName = :userName - """), - @NamedQuery( - name = "Project.allocatePortfolio", - query = - """ - UPDATE Project p - SET p.portfoliosCreated = :oldState + 1, p.updatedAt = :now - WHERE p.id = :id AND p.portfoliosCreated = :oldState - """), - @NamedQuery( - name = "Project.allocateTopology", - query = - """ - UPDATE Project p - SET p.topologiesCreated = :oldState + 1, p.updatedAt = :now - WHERE p.id = :id AND p.topologiesCreated = :oldState - """), - @NamedQuery( - name = "Project.allocateScenario", - query = - """ - UPDATE Project p - SET p.scenariosCreated = :oldState + 1, p.updatedAt = :now - WHERE p.id = :id AND p.scenariosCreated = :oldState - """) -}) -public class Project extends PanacheEntityBase { - - /** - * The main ID of a project. - * The value starts at 6 to account for the other 5 projects already made by the loading script. - */ - @Id - @SequenceGenerator(name = "projectSeq", sequenceName = "project_id_seq", allocationSize = 1, initialValue = 7) - @GeneratedValue(generator = "projectSeq") - public Long id; - - /** - * The name of the project. - */ - @Column(nullable = false) - public String name; - - /** - * The instant at which the project was created. - */ - @Column(name = "created_at", nullable = false, updatable = false) - public Instant createdAt; - - /** - * The instant at which the project was updated. - */ - @Column(name = "updated_at", nullable = false) - public Instant updatedAt; - - /** - * The portfolios belonging to this project. - */ - @OneToMany( - cascade = {CascadeType.ALL}, - mappedBy = "project", - orphanRemoval = true) - @OrderBy("id ASC") - public Set<Portfolio> portfolios = new HashSet<>(); - - /** - * The number of portfolios created for this project (including deleted portfolios). - */ - @Column(name = "portfolios_created", nullable = false) - public int portfoliosCreated = 0; - - /** - * The topologies belonging to this project. - */ - @OneToMany( - cascade = {CascadeType.ALL}, - mappedBy = "project", - orphanRemoval = true) - @OrderBy("id ASC") - public Set<Topology> topologies = new HashSet<>(); - - /** - * The number of topologies created for this project (including deleted topologies). - */ - @Column(name = "topologies_created", nullable = false) - public int topologiesCreated = 0; - - /** - * The scenarios belonging to this project. - */ - @OneToMany(mappedBy = "project", orphanRemoval = true) - public Set<Scenario> scenarios = new HashSet<>(); - - /** - * The number of scenarios created for this project (including deleted scenarios). - */ - @Column(name = "scenarios_created", nullable = false) - public int scenariosCreated = 0; - - /** - * The users authorized to access the project. - */ - @OneToMany( - cascade = {CascadeType.ALL}, - mappedBy = "project", - orphanRemoval = true) - public Set<ProjectAuthorization> authorizations = new HashSet<>(); - - /** - * Construct a {@link Project} object. - */ - public Project(String name, Instant createdAt) { - this.name = name; - this.createdAt = createdAt; - this.updatedAt = createdAt; - } - - /** - * JPA constructor - */ - protected Project() {} - - /** - * Allocate the next portfolio number for the specified [project]. - * - * @param time The time at which the new portfolio is created. - */ - public int allocatePortfolio(Instant time) { - for (int i = 0; i < 4; i++) { - long count = update( - "#Project.allocatePortfolio", - Parameters.with("id", id).and("oldState", portfoliosCreated).and("now", time)); - if (count > 0) { - return portfoliosCreated + 1; - } else { - Panache.getEntityManager().refresh(this); - } - } - - throw new IllegalStateException("Failed to allocate next portfolio"); - } - - /** - * Allocate the next topology number for the specified [project]. - * - * @param time The time at which the new topology is created. - */ - public int allocateTopology(Instant time) { - for (int i = 0; i < 4; i++) { - long count = update( - "#Project.allocateTopology", - Parameters.with("id", id).and("oldState", topologiesCreated).and("now", time)); - if (count > 0) { - return topologiesCreated + 1; - } else { - Panache.getEntityManager().refresh(this); - } - } - - throw new IllegalStateException("Failed to allocate next topology"); - } - - /** - * Allocate the next scenario number for the specified [project]. - * - * @param time The time at which the new scenario is created. - */ - public int allocateScenario(Instant time) { - for (int i = 0; i < 4; i++) { - long count = update( - "#Project.allocateScenario", - Parameters.with("id", id).and("oldState", scenariosCreated).and("now", time)); - if (count > 0) { - return scenariosCreated + 1; - } else { - Panache.getEntityManager().refresh(this); - } - } - - throw new IllegalStateException("Failed to allocate next scenario"); - } -} diff --git a/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/model/ProjectAuthorization.java b/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/model/ProjectAuthorization.java deleted file mode 100644 index 3776ae12..00000000 --- a/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/model/ProjectAuthorization.java +++ /dev/null @@ -1,172 +0,0 @@ -/* - * Copyright (c) 2022 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.web.server.model; - -import io.quarkus.hibernate.orm.panache.PanacheEntityBase; -import io.quarkus.hibernate.orm.panache.PanacheQuery; -import io.quarkus.panache.common.Parameters; -import jakarta.persistence.Column; -import jakarta.persistence.Embeddable; -import jakarta.persistence.EmbeddedId; -import jakarta.persistence.Entity; -import jakarta.persistence.EnumType; -import jakarta.persistence.Enumerated; -import jakarta.persistence.FetchType; -import jakarta.persistence.ForeignKey; -import jakarta.persistence.JoinColumn; -import jakarta.persistence.ManyToOne; -import jakarta.persistence.MapsId; -import jakarta.persistence.NamedQueries; -import jakarta.persistence.NamedQuery; -import jakarta.persistence.Table; -import java.io.Serializable; -import java.util.Objects; -import org.opendc.web.proto.user.ProjectRole; - -/** - * An authorization for some user to participate in a project. - */ -@Entity -@Table -@NamedQueries({ - @NamedQuery( - name = "ProjectAuthorization.findByUser", - query = - """ - SELECT a - FROM ProjectAuthorization a - WHERE a.key.userName = :userName - """), -}) -public class ProjectAuthorization extends PanacheEntityBase { - /** - * The user identifier of the authorization. - */ - @EmbeddedId - public ProjectAuthorization.Key key; - - /** - * The project that the user is authorized to participate in. - */ - @ManyToOne(optional = false, fetch = FetchType.LAZY) - @MapsId("projectId") - @JoinColumn( - name = "project_id", - updatable = false, - insertable = false, - nullable = false, - foreignKey = @ForeignKey(name = "fk_project_authorizations")) - public Project project; - - /** - * The role of the user in the project. - */ - @Column(nullable = false) - @Enumerated(EnumType.STRING) - public ProjectRole role; - - /** - * Construct a {@link ProjectAuthorization} object. - */ - public ProjectAuthorization(Project project, String userName, ProjectRole role) { - this.key = new ProjectAuthorization.Key(project.id, userName); - this.project = project; - this.role = role; - } - - /** - * JPA constructor - */ - protected ProjectAuthorization() {} - - /** - * List all projects for the user with the specified <code>userName</code>. - * - * @param userName The identifier of the user that is requesting the list of projects. - * @return A query returning projects that the user has received authorization for. - */ - public static PanacheQuery<ProjectAuthorization> findByUser(String userName) { - return find("#ProjectAuthorization.findByUser", Parameters.with("userName", userName)); - } - - /** - * Find the project with <code>id</code> for the user with the specified <code>userName</code>. - * - * @param userName The identifier of the user that is requesting the list of projects. - * @param project_id The unique identifier of the project. - * @return The project with the specified identifier or <code>null</code> if it does not exist or is not accessible - * to the user with the specified identifier. - */ - public static ProjectAuthorization findByUser(String userName, long project_id) { - return findById(new ProjectAuthorization.Key(project_id, userName)); - } - - /** - * Determine whether the authorization allows the user to edit the project. - */ - public boolean canEdit() { - return switch (role) { - case OWNER, EDITOR -> true; - case VIEWER -> false; - }; - } - - /** - * Determine whether the authorization allows the user to delete the project. - */ - public boolean canDelete() { - return role == ProjectRole.OWNER; - } - - /** - * Key for representing a {@link ProjectAuthorization} object. - */ - @Embeddable - public static class Key implements Serializable { - @Column(name = "project_id", nullable = false) - public long projectId; - - @Column(name = "user_name", nullable = false) - public String userName; - - public Key(long projectId, String userName) { - this.projectId = projectId; - this.userName = userName; - } - - protected Key() {} - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - Key key = (Key) o; - return projectId == key.projectId && userName.equals(key.userName); - } - - @Override - public int hashCode() { - return Objects.hash(projectId, userName); - } - } -} diff --git a/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/model/Scenario.java b/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/model/Scenario.java deleted file mode 100644 index c79ef5bb..00000000 --- a/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/model/Scenario.java +++ /dev/null @@ -1,197 +0,0 @@ -/* - * Copyright (c) 2023 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.web.server.model; - -import io.hypersistence.utils.hibernate.type.json.JsonType; -import io.quarkus.hibernate.orm.panache.PanacheEntityBase; -import io.quarkus.hibernate.orm.panache.PanacheQuery; -import io.quarkus.panache.common.Parameters; -import jakarta.persistence.*; -import java.util.ArrayList; -import java.util.List; -import org.hibernate.annotations.Type; -import org.opendc.web.proto.OperationalPhenomena; - -/** - * A single scenario to be explored by the simulator. - */ -@Entity -@Table( - uniqueConstraints = { - @UniqueConstraint( - name = "uk_scenarios_number", - columnNames = {"project_id", "number"}) - }, - indexes = {@Index(name = "ux_scenarios_number", columnList = "project_id, number")}) -@NamedQueries({ - @NamedQuery(name = "Scenario.findByProject", query = "SELECT s FROM Scenario s WHERE s.project.id = :projectId"), - @NamedQuery( - name = "Scenario.findByPortfolio", - query = - """ - SELECT s - FROM Scenario s - JOIN Portfolio p ON p.id = s.portfolio.id AND p.number = :number - WHERE s.project.id = :projectId - """), - @NamedQuery( - name = "Scenario.findOneByProject", - query = "SELECT s FROM Scenario s WHERE s.project.id = :projectId AND s.number = :number") -}) -public class Scenario extends PanacheEntityBase { - /** - * The main ID of a Scenario. - * The value starts at 3 to account for the other 2 scenarios already made by the loading script. - */ - @Id - @SequenceGenerator(name = "scenarioSeq", sequenceName = "scenario_id_seq", allocationSize = 1, initialValue = 3) - @GeneratedValue(generator = "scenarioSeq") - public Long id; - - /** - * The {@link Project} to which this scenario belongs. - */ - @ManyToOne(optional = false) - @JoinColumn(name = "project_id", nullable = false, foreignKey = @ForeignKey(name = "fk_scenarios_project")) - public Project project; - - /** - * The {@link Portfolio} to which this scenario belongs. - */ - @ManyToOne(optional = false) - @JoinColumn(name = "portfolio_id", nullable = false, foreignKey = @ForeignKey(name = "fk_scenarios_portfolio")) - public Portfolio portfolio; - - /** - * Unique number of the scenario for the project. - */ - @Column(nullable = false) - public int number; - - /** - * The name of the scenario. - */ - @Column(nullable = false, updatable = false) - public String name; - - /** - * Workload details of the scenario. - */ - @Embedded - public Workload workload; - - /** - * Topology details of the scenario. - */ - @ManyToOne(optional = false) - @JoinColumn(name = "topology_id", nullable = false, foreignKey = @ForeignKey(name = "fk_scenarios_topology")) - public Topology topology; - - /** - * Operational phenomena activated in the scenario. - * @Column(columnDefinition = "jsonb", nullable = false, updatable = false) - * @Type(JsonType.class) - */ - @Column(columnDefinition = "jsonb", nullable = false, updatable = false) - @Type(JsonType.class) - public OperationalPhenomena phenomena; - - /** - * The name of the VM scheduler used in the scenario. - */ - @Column(name = "scheduler_name", nullable = false, updatable = false) - public String schedulerName; - - /** - * The {@link Job} associated with the scenario. - */ - @OneToMany( - cascade = {CascadeType.ALL}, - mappedBy = "scenario", - fetch = FetchType.LAZY) - public List<Job> jobs = new ArrayList<>(); - - /** - * Construct a {@link Scenario} object. - */ - public Scenario( - Project project, - Portfolio portfolio, - int number, - String name, - Workload workload, - Topology topology, - OperationalPhenomena phenomena, - String schedulerName) { - this.project = project; - this.portfolio = portfolio; - this.number = number; - this.name = name; - this.workload = workload; - this.topology = topology; - this.phenomena = phenomena; - this.schedulerName = schedulerName; - } - - /** - * JPA constructor - */ - protected Scenario() {} - - /** - * Find all {@link Scenario}s that belong to the specified project - * - * @param projectId The unique identifier of the project. - * @return The query of scenarios that belong to the specified project. - */ - public static PanacheQuery<Scenario> findByProject(long projectId) { - return find("#Scenario.findByProject", Parameters.with("projectId", projectId)); - } - - /** - * Find all {@link Scenario}s that belong to the specified portfolio. - * - * @param projectId The unique identifier of the project. - * @param number The number of the portfolio. - * @return The query of scenarios that belong to the specified project and portfolio.. - */ - public static PanacheQuery<Scenario> findByPortfolio(long projectId, int number) { - return find( - "#Scenario.findByPortfolio", - Parameters.with("projectId", projectId).and("number", number)); - } - - /** - * Find the {@link Scenario} with the specified <code>number</code> belonging to the specified project. - * - * @param projectId The unique identifier of the project. - * @param number The number of the scenario. - * @return The scenario or <code>null</code> if it does not exist. - */ - public static Scenario findByProject(long projectId, int number) { - return find( - "#Scenario.findOneByProject", - Parameters.with("projectId", projectId).and("number", number)) - .firstResult(); - } -} diff --git a/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/model/Topology.java b/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/model/Topology.java deleted file mode 100644 index 8a4e2ae2..00000000 --- a/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/model/Topology.java +++ /dev/null @@ -1,153 +0,0 @@ -/* - * Copyright (c) 2022 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.web.server.model; - -import io.hypersistence.utils.hibernate.type.json.JsonType; -import io.quarkus.hibernate.orm.panache.PanacheEntityBase; -import io.quarkus.hibernate.orm.panache.PanacheQuery; -import io.quarkus.panache.common.Parameters; -import jakarta.persistence.Column; -import jakarta.persistence.Entity; -import jakarta.persistence.GeneratedValue; -import jakarta.persistence.Id; -import jakarta.persistence.Index; -import jakarta.persistence.JoinColumn; -import jakarta.persistence.ManyToOne; -import jakarta.persistence.NamedQueries; -import jakarta.persistence.NamedQuery; -import jakarta.persistence.SequenceGenerator; -import jakarta.persistence.Table; -import jakarta.persistence.UniqueConstraint; -import java.time.Instant; -import java.util.List; -import org.hibernate.annotations.Type; -import org.opendc.web.proto.Room; - -/** - * A datacenter design in OpenDC. - */ -@Entity -@Table( - uniqueConstraints = { - @UniqueConstraint( - name = "uk_topologies_number", - columnNames = {"project_id", "number"}) - }, - indexes = {@Index(name = "ux_topologies_number", columnList = "project_id, number")}) -@NamedQueries({ - @NamedQuery(name = "Topology.findByProject", query = "SELECT t FROM Topology t WHERE t.project.id = :projectId"), - @NamedQuery( - name = "Topology.findOneByProject", - query = "SELECT t FROM Topology t WHERE t.project.id = :projectId AND t.number = :number") -}) -public class Topology extends PanacheEntityBase { - /** - * The main ID of a project. - * The value starts at 6 to account for the other 5 projects already made by the loading script. - */ - @Id - @SequenceGenerator(name = "topologySeq", sequenceName = "topology_id_seq", allocationSize = 1, initialValue = 5) - @GeneratedValue(generator = "topologySeq") - public Long id; - - /** - * The {@link Project} to which the topology belongs. - */ - @ManyToOne(optional = false) - @JoinColumn(name = "project_id", nullable = false) - public Project project; - - /** - * Unique number of the topology for the project. - */ - @Column(nullable = false) - public int number; - - /** - * The name of the topology. - */ - @Column(nullable = false) - public String name; - - /** - * The instant at which the topology was created. - */ - @Column(name = "created_at", nullable = false, updatable = false) - public Instant createdAt; - - /** - * The instant at which the topology was updated. - */ - @Column(name = "updated_at", nullable = false) - public Instant updatedAt; - - /** - * Datacenter design in JSON - * @Column(columnDefinition = "jsonb", nullable = false) - * @Type(JsonType.class) - */ - @Column(columnDefinition = "jsonb", nullable = false) - @Type(JsonType.class) - public List<Room> rooms; - - /** - * Construct a {@link Topology} object. - */ - public Topology(Project project, int number, String name, Instant createdAt, List<Room> rooms) { - this.project = project; - this.number = number; - this.name = name; - this.createdAt = createdAt; - this.updatedAt = createdAt; - this.rooms = rooms; - } - - /** - * JPA constructor - */ - protected Topology() {} - - /** - * Find all [Topology]s that belong to [project][projectId]. - * - * @param projectId The unique identifier of the project. - * @return The query of topologies that belong to the specified project. - */ - public static PanacheQuery<Topology> findByProject(long projectId) { - return find("#Topology.findByProject", Parameters.with("projectId", projectId)); - } - - /** - * Find the [Topology] with the specified [number] belonging to [project][projectId]. - * - * @param projectId The unique identifier of the project. - * @param number The number of the topology. - * @return The topology or `null` if it does not exist. - */ - public static Topology findByProject(long projectId, int number) { - return find( - "#Topology.findOneByProject", - Parameters.with("projectId", projectId).and("number", number)) - .firstResult(); - } -} diff --git a/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/model/Trace.java b/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/model/Trace.java deleted file mode 100644 index 71c647bc..00000000 --- a/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/model/Trace.java +++ /dev/null @@ -1,72 +0,0 @@ -/* - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.web.server.model; - -import io.quarkus.hibernate.orm.panache.PanacheEntityBase; -import jakarta.persistence.Column; -import jakarta.persistence.Entity; -import jakarta.persistence.Id; -import jakarta.persistence.Table; - -/** - * A workload trace available for simulation. - */ -@Entity -@Table -public class Trace extends PanacheEntityBase { - /** - * The unique identifier of the trace. - */ - @Id - public String id; - - /** - * The name of the trace. - */ - @Column(nullable = false, updatable = false) - public String name; - - /** - * The type of trace. - */ - @Column(nullable = false, updatable = false) - public String type; - - /** - * Construct a {@link Trace}. - * - * @param id The unique identifier of the trace. - * @param name The name of the trace. - * @param type The type of trace. - */ - public Trace(String id, String name, String type) { - this.id = id; - this.name = name; - this.type = type; - } - - /** - * JPA constructor. - */ - protected Trace() {} -} diff --git a/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/model/UserAccounting.java b/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/model/UserAccounting.java deleted file mode 100644 index 10a10ef9..00000000 --- a/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/model/UserAccounting.java +++ /dev/null @@ -1,167 +0,0 @@ -/* - * Copyright (c) 2023 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.web.server.model; - -import io.quarkus.hibernate.orm.panache.PanacheEntityBase; -import io.quarkus.panache.common.Parameters; -import jakarta.persistence.Column; -import jakarta.persistence.Entity; -import jakarta.persistence.Id; -import jakarta.persistence.NamedQueries; -import jakarta.persistence.NamedQuery; -import jakarta.persistence.Table; -import java.time.LocalDate; - -/** - * Entity to track the number of simulation minutes used by a user. - */ -@Entity -@Table -@NamedQueries({ - @NamedQuery( - name = "UserAccounting.consumeBudget", - query = - """ - UPDATE UserAccounting a - SET a.simulationTime = a.simulationTime + :seconds - WHERE a.userId = :userId AND a.periodEnd = :periodEnd - """), - @NamedQuery( - name = "UserAccounting.resetBudget", - query = - """ - UPDATE UserAccounting a - SET a.periodEnd = :periodEnd, a.simulationTime = :seconds - WHERE a.userId = :userId AND a.periodEnd = :oldPeriodEnd - """) -}) -public class UserAccounting extends PanacheEntityBase { - /** - * User to which this object belongs. - */ - @Id - @Column(name = "user_id", nullable = false) - public String userId; - - /** - * The end of the accounting period. - */ - @Column(name = "period_end", nullable = false) - public LocalDate periodEnd; - - /** - * The number of simulation seconds to be used per accounting period. - */ - @Column(name = "simulation_time_budget", nullable = false) - public int simulationTimeBudget; - - /** - * The number of simulation seconds used in this period. This number should reset once the accounting period has - * been reached. - */ - @Column(name = "simulation_time", nullable = false) - public int simulationTime = 0; - - /** - * Construct a new {@link UserAccounting} object. - * - * @param userId The identifier of the user that this object belongs to. - * @param periodEnd The end of the accounting period. - * @param simulationTimeBudget The number of simulation seconds available per accounting period. - */ - public UserAccounting(String userId, LocalDate periodEnd, int simulationTimeBudget) { - this.userId = userId; - this.periodEnd = periodEnd; - this.simulationTimeBudget = simulationTimeBudget; - } - - /** - * JPA constructor. - */ - protected UserAccounting() {} - - /** - * Return the {@link UserAccounting} object associated with the specified user id. - */ - public static UserAccounting findByUser(String userId) { - return findById(userId); - } - - /** - * Create a new {@link UserAccounting} object and persist it to the database. - * - * @param userId The identifier of the user that this object belongs to. - * @param periodEnd The end of the accounting period. - * @param simulationTimeBudget The number of simulation seconds available per accounting period. - * @param simulationTime The initial simulation time that has been consumed. - */ - public static UserAccounting create( - String userId, LocalDate periodEnd, int simulationTimeBudget, int simulationTime) { - UserAccounting newAccounting = new UserAccounting(userId, periodEnd, simulationTimeBudget); - newAccounting.simulationTime = simulationTime; - newAccounting.persistAndFlush(); - return newAccounting; - } - - /** - * Atomically consume the budget for this {@link UserAccounting} object. - * - * @param seconds The number of seconds to consume from the user. - * @return <code>true</code> when the update succeeded, <code>false</code> when there was a conflict. - */ - public boolean consumeBudget(int seconds) { - long count = update( - "#UserAccounting.consumeBudget", - Parameters.with("userId", userId).and("periodEnd", periodEnd).and("seconds", seconds)); - return count > 0; - } - - /** - * Atomically reset the budget for this {@link UserAccounting} object. - * - * @param periodEnd The new end period for the budget. - * @param seconds The number of seconds that have already been consumed. - * @return <code>true</code> when the update succeeded`, <code>false</code> when there was a conflict. - */ - public boolean resetBudget(LocalDate periodEnd, int seconds) { - long count = update( - "#UserAccounting.resetBudget", - Parameters.with("userId", userId) - .and("oldPeriodEnd", this.periodEnd) - .and("periodEnd", periodEnd) - .and("seconds", seconds)); - return count > 0; - } - - /** - * Determine whether the user has any remaining simulation budget. - * - * @return <code>true</code> when the user still has budget left, <code>false</code> otherwise. - */ - public boolean hasSimulationBudget() { - var today = LocalDate.now(); - - // The accounting period must be over or there must be budget remaining. - return !today.isBefore(periodEnd) || simulationTimeBudget > simulationTime; - } -} diff --git a/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/model/Workload.java b/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/model/Workload.java deleted file mode 100644 index fd7010d2..00000000 --- a/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/model/Workload.java +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright (c) 2023 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.web.server.model; - -import jakarta.persistence.Column; -import jakarta.persistence.Embeddable; -import jakarta.persistence.ManyToOne; - -/** - * Specification of the workload for a {@link Scenario} - */ -@Embeddable -public class Workload { - /** - * The {@link Trace} that the workload runs. - */ - @ManyToOne(optional = false) - public Trace trace; - - /** - * The percentage of the trace that should be sampled. - */ - @Column(name = "sampling_fraction", nullable = false, updatable = false) - public double samplingFraction; - - /** - * Construct a {@link Workload} object. - * - * @param trace The {@link Trace} to run as workload. - * @param samplingFraction The percentage of the workload to sample. - */ - public Workload(Trace trace, double samplingFraction) { - this.trace = trace; - this.samplingFraction = samplingFraction; - } - - /** - * JPA constructor. - */ - protected Workload() {} -} diff --git a/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/rest/BaseProtocol.java b/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/rest/BaseProtocol.java deleted file mode 100644 index 44d2d569..00000000 --- a/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/rest/BaseProtocol.java +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright (c) 2023 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.web.server.rest; - -import org.opendc.web.server.model.Trace; -import org.opendc.web.server.model.Workload; - -/** - * DTO-conversions for the base protocol. - */ -public final class BaseProtocol { - /** - * Private constructor to prevent instantiation of class. - */ - private BaseProtocol() {} - - /** - * Convert a {@link Workload} entity into a DTO. - */ - public static org.opendc.web.proto.Workload toDto(Workload workload) { - return new org.opendc.web.proto.Workload(toDto(workload.trace), workload.samplingFraction); - } - - /** - * Convert a {@link Trace] entity into a {@link org.opendc.web.proto.Trace} DTO. - */ - public static org.opendc.web.proto.Trace toDto(Trace trace) { - return new org.opendc.web.proto.Trace(trace.id, trace.name, trace.type); - } -} diff --git a/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/rest/SchedulerResource.java b/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/rest/SchedulerResource.java deleted file mode 100644 index 3e839040..00000000 --- a/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/rest/SchedulerResource.java +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Copyright (c) 2023 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.web.server.rest; - -import jakarta.ws.rs.GET; -import jakarta.ws.rs.Path; -import jakarta.ws.rs.Produces; -import java.util.List; - -/** - * A resource representing the available schedulers that can be used during experiments. - */ -@Produces("application/json") -@Path("/schedulers") -public final class SchedulerResource { - /** - * Obtain all available schedulers. - */ - @GET - public List<String> getAll() { - return List.of( - "mem", - "mem-inv", - "core-mem", - "core-mem-inv", - "active-tasks", - "active-tasks-inv", - "provisioned-cores", - "provisioned-cores-inv", - "random"); - } -} diff --git a/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/rest/TraceResource.java b/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/rest/TraceResource.java deleted file mode 100644 index daec01cd..00000000 --- a/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/rest/TraceResource.java +++ /dev/null @@ -1,70 +0,0 @@ -/* - * Copyright (c) 2023 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.web.server.rest; - -import jakarta.ws.rs.GET; -import jakarta.ws.rs.Path; -import jakarta.ws.rs.PathParam; -import jakarta.ws.rs.Produces; -import jakarta.ws.rs.WebApplicationException; -import java.util.List; -import java.util.stream.Stream; -import org.opendc.web.server.model.Trace; - -/** - * A resource representing the workload traces available in the OpenDC instance. - */ -@Produces("application/json") -@Path("/traces") -public final class TraceResource { - /** - * Obtain all available traces. - */ - @GET - public List<org.opendc.web.proto.Trace> getAll() { - Stream<Trace> entities = Trace.streamAll(); - return entities.map(TraceResource::toDto).toList(); - } - - /** - * Obtain trace information by identifier. - */ - @GET - @Path("{id}") - public org.opendc.web.proto.Trace get(@PathParam("id") String id) { - Trace trace = Trace.findById(id); - - if (trace == null) { - throw new WebApplicationException("Trace not found", 404); - } - - return toDto(trace); - } - - /** - * Convert a {@link Trace] entity into a {@link org.opendc.web.proto.Trace} DTO. - */ - public static org.opendc.web.proto.Trace toDto(Trace trace) { - return new org.opendc.web.proto.Trace(trace.id, trace.name, trace.type); - } -} diff --git a/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/rest/error/MissingKotlinParameterExceptionMapper.java b/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/rest/error/MissingKotlinParameterExceptionMapper.java deleted file mode 100644 index 345acdfe..00000000 --- a/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/rest/error/MissingKotlinParameterExceptionMapper.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright (c) 2023 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.web.server.rest.error; - -import com.fasterxml.jackson.module.kotlin.MissingKotlinParameterException; -import jakarta.ws.rs.core.MediaType; -import jakarta.ws.rs.core.Response; -import jakarta.ws.rs.ext.ExceptionMapper; -import jakarta.ws.rs.ext.Provider; -import org.opendc.web.proto.ProtocolError; - -/** - * An [ExceptionMapper] for [MissingKotlinParameterException] thrown by Jackson. - */ -@Provider -public final class MissingKotlinParameterExceptionMapper implements ExceptionMapper<MissingKotlinParameterException> { - @Override - public Response toResponse(MissingKotlinParameterException exception) { - return Response.status(Response.Status.BAD_REQUEST) - .entity(new ProtocolError( - Response.Status.BAD_REQUEST.getStatusCode(), - "Field " + exception.getParameter().getName() + " is missing from body.")) - .type(MediaType.APPLICATION_JSON) - .build(); - } -} diff --git a/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/rest/error/WebApplicationExceptionMapper.java b/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/rest/error/WebApplicationExceptionMapper.java deleted file mode 100644 index e027e559..00000000 --- a/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/rest/error/WebApplicationExceptionMapper.java +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright (c) 2023 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.web.server.rest.error; - -import jakarta.ws.rs.WebApplicationException; -import jakarta.ws.rs.core.MediaType; -import jakarta.ws.rs.core.Response; -import jakarta.ws.rs.ext.ExceptionMapper; -import jakarta.ws.rs.ext.Provider; -import org.opendc.web.proto.ProtocolError; - -/** - * Helper class to transform a {@link WebApplicationException} into an JSON error response. - */ -@Provider -public final class WebApplicationExceptionMapper implements ExceptionMapper<WebApplicationException> { - @Override - public Response toResponse(WebApplicationException exception) { - int code = exception.getResponse().getStatus(); - - String message = exception.getMessage(); - if (message == null) { - message = "Unknown error"; - } - - return Response.status(code) - .entity(new ProtocolError(code, message)) - .type(MediaType.APPLICATION_JSON) - .build(); - } -} diff --git a/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/rest/runner/JobResource.java b/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/rest/runner/JobResource.java deleted file mode 100644 index 4dde8654..00000000 --- a/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/rest/runner/JobResource.java +++ /dev/null @@ -1,110 +0,0 @@ -/* - * Copyright (c) 2023 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.web.server.rest.runner; - -import jakarta.annotation.security.RolesAllowed; -import jakarta.transaction.Transactional; -import jakarta.validation.Valid; -import jakarta.ws.rs.Consumes; -import jakarta.ws.rs.GET; -import jakarta.ws.rs.POST; -import jakarta.ws.rs.Path; -import jakarta.ws.rs.PathParam; -import jakarta.ws.rs.Produces; -import jakarta.ws.rs.WebApplicationException; -import java.util.List; -import org.opendc.web.proto.JobState; -import org.opendc.web.server.model.Job; -import org.opendc.web.server.service.JobService; - -/** - * A resource representing the available simulation jobs. - */ -@Produces("application/json") -@Path("/jobs") -@RolesAllowed("runner") -public final class JobResource { - /** - * The {@link JobService} for helping manage the job lifecycle. - */ - private final JobService jobService; - - /** - * Construct a {@link JobResource} instance. - * - * @param jobService The {@link JobService} for managing the job lifecycle. - */ - public JobResource(JobService jobService) { - this.jobService = jobService; - } - - /** - * Obtain all pending simulation jobs. - */ - @GET - public List<org.opendc.web.proto.runner.Job> queryPending() { - return Job.findByState(JobState.PENDING).list().stream() - .map(RunnerProtocol::toDto) - .toList(); - } - - /** - * Get a job by identifier. - */ - @GET - @Path("{job}") - public org.opendc.web.proto.runner.Job get(@PathParam("job") long id) { - Job job = Job.findById(id); - - if (job == null) { - throw new WebApplicationException("Job not found", 404); - } - - return RunnerProtocol.toDto(job); - } - - /** - * Atomically update the state of a job. - */ - @POST - @Path("{job}") - @Consumes("application/json") - @Transactional - public org.opendc.web.proto.runner.Job update( - @PathParam("job") long id, @Valid org.opendc.web.proto.runner.Job.Update update) { - Job job = Job.findById(id); - if (job == null) { - throw new WebApplicationException("Job not found", 404); - } - - try { - jobService.updateJob(job, update.getState(), update.getRuntime(), update.getResults()); - } catch (IllegalArgumentException e) { - throw new WebApplicationException(e, 400); - } catch (IllegalStateException e) { - throw new WebApplicationException(e, 409); - } - - return RunnerProtocol.toDto(job); - } -} diff --git a/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/rest/runner/RunnerProtocol.java b/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/rest/runner/RunnerProtocol.java deleted file mode 100644 index 6bf65d97..00000000 --- a/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/rest/runner/RunnerProtocol.java +++ /dev/null @@ -1,78 +0,0 @@ -/* - * Copyright (c) 2023 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.web.server.rest.runner; - -import org.opendc.web.server.model.Job; -import org.opendc.web.server.model.Portfolio; -import org.opendc.web.server.model.Scenario; -import org.opendc.web.server.model.Topology; -import org.opendc.web.server.rest.BaseProtocol; - -/** - * DTO-conversions for the runner protocol. - */ -public final class RunnerProtocol { - /** - * Private constructor to prevent instantiation of class. - */ - private RunnerProtocol() {} - - /** - * Convert a {@link Job} into a runner-facing DTO. - */ - public static org.opendc.web.proto.runner.Job toDto(Job job) { - return new org.opendc.web.proto.runner.Job( - job.id, toDto(job.scenario), job.state, job.createdAt, job.updatedAt, job.runtime, job.results); - } - - /** - * Convert a {@link Scenario} into a runner-facing DTO. - */ - public static org.opendc.web.proto.runner.Scenario toDto(Scenario scenario) { - return new org.opendc.web.proto.runner.Scenario( - scenario.id, - scenario.number, - toDto(scenario.portfolio), - scenario.name, - BaseProtocol.toDto(scenario.workload), - toDto(scenario.topology), - scenario.phenomena, - scenario.schedulerName); - } - - /** - * Convert a {@link Portfolio} into a runner-facing DTO. - */ - public static org.opendc.web.proto.runner.Portfolio toDto(Portfolio portfolio) { - return new org.opendc.web.proto.runner.Portfolio( - portfolio.id, portfolio.number, portfolio.name, portfolio.targets); - } - - /** - * Convert a {@link Topology} into a runner-facing DTO. - */ - public static org.opendc.web.proto.runner.Topology toDto(Topology topology) { - return new org.opendc.web.proto.runner.Topology( - topology.id, topology.number, topology.name, topology.rooms, topology.createdAt, topology.updatedAt); - } -} diff --git a/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/rest/user/PortfolioResource.java b/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/rest/user/PortfolioResource.java deleted file mode 100644 index 2a3a40f4..00000000 --- a/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/rest/user/PortfolioResource.java +++ /dev/null @@ -1,161 +0,0 @@ -/* - * Copyright (c) 2023 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.web.server.rest.user; - -import io.quarkus.security.identity.SecurityIdentity; -import jakarta.annotation.security.RolesAllowed; -import jakarta.transaction.Transactional; -import jakarta.validation.Valid; -import jakarta.ws.rs.Consumes; -import jakarta.ws.rs.DELETE; -import jakarta.ws.rs.GET; -import jakarta.ws.rs.POST; -import jakarta.ws.rs.Path; -import jakarta.ws.rs.PathParam; -import jakarta.ws.rs.Produces; -import jakarta.ws.rs.WebApplicationException; -import java.time.Instant; -import java.util.List; -import org.opendc.web.server.model.Portfolio; -import org.opendc.web.server.model.ProjectAuthorization; - -/** - * A resource representing the portfolios of a project. - */ -@Produces("application/json") -@Path("/projects/{project}/portfolios") -@RolesAllowed("openid") -public final class PortfolioResource { - /** - * The identity of the current user. - */ - private final SecurityIdentity identity; - - /** - * Construct a {@link PortfolioResource}. - * - * @param identity The {@link SecurityIdentity} of the current user. - */ - public PortfolioResource(SecurityIdentity identity) { - this.identity = identity; - } - - /** - * Get all portfolios that belong to the specified project. - */ - @GET - public List<org.opendc.web.proto.user.Portfolio> getAll(@PathParam("project") long projectId) { - // User must have access to project - ProjectAuthorization auth = - ProjectAuthorization.findByUser(identity.getPrincipal().getName(), projectId); - - if (auth == null) { - return List.of(); - } - - return Portfolio.findByProject(projectId).list().stream() - .map((p) -> UserProtocol.toDto(p, auth)) - .toList(); - } - - /** - * Create a portfolio for this project. - */ - @POST - @Transactional - @Consumes("application/json") - public org.opendc.web.proto.user.Portfolio create( - @PathParam("project") long projectId, @Valid org.opendc.web.proto.user.Portfolio.Create request) { - // User must have access to project - ProjectAuthorization auth = - ProjectAuthorization.findByUser(identity.getPrincipal().getName(), projectId); - - if (auth == null) { - throw new WebApplicationException("Project not found", 404); - } else if (!auth.canEdit()) { - throw new WebApplicationException("Not permitted to edit project", 403); - } - - var now = Instant.now(); - var project = auth.project; - int number = project.allocatePortfolio(now); - - Portfolio portfolio = new Portfolio(project, number, request.getName(), request.getTargets()); - - project.portfolios.add(portfolio); - portfolio.persist(); - - return UserProtocol.toDto(portfolio, auth); - } - - /** - * Obtain a portfolio by its identifier. - */ - @GET - @Path("{portfolio}") - public org.opendc.web.proto.user.Portfolio get( - @PathParam("project") long projectId, @PathParam("portfolio") int number) { - // User must have access to project - ProjectAuthorization auth = - ProjectAuthorization.findByUser(identity.getPrincipal().getName(), projectId); - - if (auth == null) { - throw new WebApplicationException("Portfolio not found", 404); - } - - Portfolio portfolio = Portfolio.findByProject(projectId, number); - - if (portfolio == null) { - throw new WebApplicationException("Portfolio not found", 404); - } - - return UserProtocol.toDto(portfolio, auth); - } - - /** - * Delete a portfolio. - */ - @DELETE - @Path("{portfolio}") - @Transactional - public org.opendc.web.proto.user.Portfolio delete( - @PathParam("project") long projectId, @PathParam("portfolio") int number) { - // User must have access to project - ProjectAuthorization auth = - ProjectAuthorization.findByUser(identity.getPrincipal().getName(), projectId); - - if (auth == null) { - throw new WebApplicationException("Portfolio not found", 404); - } else if (!auth.canEdit()) { - throw new WebApplicationException("Not permitted to edit project", 403); - } - - Portfolio entity = Portfolio.findByProject(projectId, number); - if (entity == null) { - throw new WebApplicationException("Portfolio not found", 404); - } - - entity.delete(); - return UserProtocol.toDto(entity, auth); - } -} diff --git a/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/rest/user/PortfolioScenarioResource.java b/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/rest/user/PortfolioScenarioResource.java deleted file mode 100644 index 789808c8..00000000 --- a/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/rest/user/PortfolioScenarioResource.java +++ /dev/null @@ -1,159 +0,0 @@ -/* - * Copyright (c) 2023 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.web.server.rest.user; - -import io.quarkus.security.identity.SecurityIdentity; -import jakarta.annotation.security.RolesAllowed; -import jakarta.transaction.Transactional; -import jakarta.validation.Valid; -import jakarta.ws.rs.Consumes; -import jakarta.ws.rs.GET; -import jakarta.ws.rs.POST; -import jakarta.ws.rs.Path; -import jakarta.ws.rs.PathParam; -import jakarta.ws.rs.Produces; -import jakarta.ws.rs.WebApplicationException; -import java.time.Instant; -import java.util.List; -import org.opendc.web.proto.JobState; -import org.opendc.web.server.model.Job; -import org.opendc.web.server.model.Portfolio; -import org.opendc.web.server.model.ProjectAuthorization; -import org.opendc.web.server.model.Scenario; -import org.opendc.web.server.model.Topology; -import org.opendc.web.server.model.Trace; -import org.opendc.web.server.model.Workload; -import org.opendc.web.server.service.UserAccountingService; - -/** - * A resource representing the scenarios of a portfolio. - */ -@Path("/projects/{project}/portfolios/{portfolio}/scenarios") -@RolesAllowed("openid") -@Produces("application/json") -public final class PortfolioScenarioResource { - /** - * The service for managing the user accounting. - */ - private final UserAccountingService accountingService; - - /** - * The identity of the current user. - */ - private final SecurityIdentity identity; - - /** - * Construct a {@link PortfolioScenarioResource}. - * - * @param accountingService The {@link UserAccountingService} instance to use. - * @param identity The {@link SecurityIdentity} of the current user. - */ - public PortfolioScenarioResource(UserAccountingService accountingService, SecurityIdentity identity) { - this.accountingService = accountingService; - this.identity = identity; - } - - /** - * Get all scenarios that belong to the specified portfolio. - */ - @GET - public List<org.opendc.web.proto.user.Scenario> get( - @PathParam("project") long projectId, @PathParam("portfolio") int portfolioNumber) { - // User must have access to project - ProjectAuthorization auth = - ProjectAuthorization.findByUser(identity.getPrincipal().getName(), projectId); - - if (auth == null) { - return List.of(); - } - - return org.opendc.web.server.model.Scenario.findByPortfolio(projectId, portfolioNumber).list().stream() - .map((s) -> UserProtocol.toDto(s, auth)) - .toList(); - } - - /** - * Create a scenario for this portfolio. - */ - @POST - @Transactional - @Consumes("application/json") - public org.opendc.web.proto.user.Scenario create( - @PathParam("project") long projectId, - @PathParam("portfolio") int portfolioNumber, - @Valid org.opendc.web.proto.user.Scenario.Create request) { - // User must have access to project - String userId = identity.getPrincipal().getName(); - ProjectAuthorization auth = ProjectAuthorization.findByUser(userId, projectId); - - if (auth == null) { - throw new WebApplicationException("Portfolio not found", 404); - } else if (!auth.canEdit()) { - throw new WebApplicationException("Not permitted to edit project", 403); - } - - Portfolio portfolio = Portfolio.findByProject(projectId, portfolioNumber); - - if (portfolio == null) { - throw new WebApplicationException("Portfolio not found", 404); - } - - Topology topology = Topology.findByProject(projectId, (int) request.getTopology()); - if (topology == null) { - throw new WebApplicationException("Referred topology does not exist", 400); - } - - Trace trace = Trace.findById(request.getWorkload().getTrace()); - if (trace == null) { - throw new WebApplicationException("Referred trace does not exist", 400); - } - - var now = Instant.now(); - var project = auth.project; - int number = project.allocateScenario(now); - - Scenario scenario = new Scenario( - project, - portfolio, - number, - request.getName(), - new Workload(trace, request.getWorkload().getSamplingFraction()), - topology, - request.getPhenomena(), - request.getSchedulerName()); - scenario.persist(); - - Job job = new Job(scenario, userId, now, portfolio.targets.getRepeats()); - job.persist(); - - // Fail the job if there is not enough budget for the simulation - if (!accountingService.hasSimulationBudget(userId)) { - job.state = JobState.FAILED; - } - - scenario.jobs.add(job); - portfolio.scenarios.add(scenario); - - return UserProtocol.toDto(scenario, auth); - } -} diff --git a/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/rest/user/ProjectResource.java b/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/rest/user/ProjectResource.java deleted file mode 100644 index ae1c959e..00000000 --- a/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/rest/user/ProjectResource.java +++ /dev/null @@ -1,131 +0,0 @@ -/* - * Copyright (c) 2023 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.web.server.rest.user; - -import io.quarkus.security.identity.SecurityIdentity; -import jakarta.annotation.security.RolesAllowed; -import jakarta.transaction.Transactional; -import jakarta.validation.Valid; -import jakarta.ws.rs.Consumes; -import jakarta.ws.rs.DELETE; -import jakarta.ws.rs.GET; -import jakarta.ws.rs.POST; -import jakarta.ws.rs.Path; -import jakarta.ws.rs.PathParam; -import jakarta.ws.rs.Produces; -import jakarta.ws.rs.WebApplicationException; -import java.time.Instant; -import java.util.List; -import org.opendc.web.proto.user.ProjectRole; -import org.opendc.web.server.model.Project; -import org.opendc.web.server.model.ProjectAuthorization; - -/** - * A resource representing the created projects. - */ -@Produces("application/json") -@Path("/projects") -@RolesAllowed("openid") -public final class ProjectResource { - /** - * The identity of the current user. - */ - private final SecurityIdentity identity; - - /** - * Construct a {@link ProjectResource}. - * - * @param identity The {@link SecurityIdentity} of the current user. - */ - public ProjectResource(SecurityIdentity identity) { - this.identity = identity; - } - - /** - * Obtain all the projects of the current user. - */ - @GET - public List<org.opendc.web.proto.user.Project> getAll() { - return ProjectAuthorization.findByUser(identity.getPrincipal().getName()).list().stream() - .map(UserProtocol::toDto) - .toList(); - } - - /** - * Create a new project for the current user. - */ - @POST - @Transactional - @Consumes("application/json") - public org.opendc.web.proto.user.Project create(@Valid org.opendc.web.proto.user.Project.Create request) { - Instant now = Instant.now(); - Project entity = new Project(request.getName(), now); - entity.persist(); - - ProjectAuthorization authorization = - new ProjectAuthorization(entity, identity.getPrincipal().getName(), ProjectRole.OWNER); - - entity.authorizations.add(authorization); - authorization.persist(); - - return UserProtocol.toDto(authorization); - } - - /** - * Obtain a single project by its identifier. - */ - @GET - @Path("{project}") - public org.opendc.web.proto.user.Project get(@PathParam("project") long project_id) { - ProjectAuthorization auth = - ProjectAuthorization.findByUser(identity.getPrincipal().getName(), project_id); - - if (auth == null) { - throw new WebApplicationException("Project not found", 404); - } - - return UserProtocol.toDto(auth); - } - - /** - * Delete a project. - */ - @DELETE - @Path("{project}") - @Transactional - public org.opendc.web.proto.user.Project delete(@PathParam("project") long id) { - ProjectAuthorization auth = - ProjectAuthorization.findByUser(identity.getPrincipal().getName(), id); - - if (auth == null) { - throw new WebApplicationException("Project not found", 404); - } else if (!auth.canDelete()) { - throw new WebApplicationException("Not allowed to delete project", 403); - } - - auth.project.updatedAt = Instant.now(); - org.opendc.web.proto.user.Project project = UserProtocol.toDto(auth); - auth.project.delete(); - return project; - } -} diff --git a/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/rest/user/ScenarioResource.java b/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/rest/user/ScenarioResource.java deleted file mode 100644 index bb3eb89b..00000000 --- a/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/rest/user/ScenarioResource.java +++ /dev/null @@ -1,127 +0,0 @@ -/* - * Copyright (c) 2023 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.web.server.rest.user; - -import io.quarkus.security.identity.SecurityIdentity; -import jakarta.annotation.security.RolesAllowed; -import jakarta.transaction.Transactional; -import jakarta.ws.rs.DELETE; -import jakarta.ws.rs.GET; -import jakarta.ws.rs.Path; -import jakarta.ws.rs.PathParam; -import jakarta.ws.rs.Produces; -import jakarta.ws.rs.WebApplicationException; -import java.util.List; -import org.opendc.web.server.model.ProjectAuthorization; -import org.opendc.web.server.model.Scenario; - -/** - * A resource representing the scenarios of a portfolio. - */ -@Produces("application/json") -@Path("/projects/{project}/scenarios") -@RolesAllowed("openid") -public final class ScenarioResource { - /** - * The identity of the current user. - */ - private final SecurityIdentity identity; - - /** - * Construct a {@link ScenarioResource}. - * - * @param identity The {@link SecurityIdentity} of the current user. - */ - public ScenarioResource(SecurityIdentity identity) { - this.identity = identity; - } - - /** - * Obtain the scenarios belonging to a project. - */ - @GET - public List<org.opendc.web.proto.user.Scenario> getAll(@PathParam("project") long projectId) { - // User must have access to project - ProjectAuthorization auth = - ProjectAuthorization.findByUser(identity.getPrincipal().getName(), projectId); - - if (auth == null) { - throw new WebApplicationException("Project not found", 404); - } - - return Scenario.findByProject(projectId).list().stream() - .map((s) -> UserProtocol.toDto(s, auth)) - .toList(); - } - - /** - * Obtain a scenario by its identifier. - */ - @GET - @Path("{scenario}") - public org.opendc.web.proto.user.Scenario get( - @PathParam("project") long projectId, @PathParam("scenario") int number) { - // User must have access to project - ProjectAuthorization auth = - ProjectAuthorization.findByUser(identity.getPrincipal().getName(), projectId); - - if (auth == null) { - throw new WebApplicationException("Project not found", 404); - } - - Scenario scenario = Scenario.findByProject(projectId, number); - - if (scenario == null) { - throw new WebApplicationException("Scenario not found", 404); - } - - return UserProtocol.toDto(scenario, auth); - } - - /** - * Delete a scenario. - */ - @DELETE - @Path("{scenario}") - @Transactional - public org.opendc.web.proto.user.Scenario delete( - @PathParam("project") long projectId, @PathParam("scenario") int number) { - // User must have access to project - ProjectAuthorization auth = - ProjectAuthorization.findByUser(identity.getPrincipal().getName(), projectId); - - if (auth == null) { - throw new WebApplicationException("Project not found", 404); - } else if (!auth.canEdit()) { - throw new WebApplicationException("Not permitted to edit project", 403); - } - - Scenario entity = Scenario.findByProject(projectId, number); - if (entity == null) { - throw new WebApplicationException("Scenario not found", 404); - } - - entity.delete(); - return UserProtocol.toDto(entity, auth); - } -} diff --git a/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/rest/user/TopologyResource.java b/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/rest/user/TopologyResource.java deleted file mode 100644 index b8c542d3..00000000 --- a/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/rest/user/TopologyResource.java +++ /dev/null @@ -1,208 +0,0 @@ -/* - * Copyright (c) 2023 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.web.server.rest.user; - -import io.quarkus.hibernate.orm.panache.Panache; -import io.quarkus.security.identity.SecurityIdentity; -import jakarta.annotation.security.RolesAllowed; -import jakarta.persistence.PersistenceException; -import jakarta.transaction.Transactional; -import jakarta.validation.Valid; -import jakarta.ws.rs.Consumes; -import jakarta.ws.rs.DELETE; -import jakarta.ws.rs.GET; -import jakarta.ws.rs.POST; -import jakarta.ws.rs.PUT; -import jakarta.ws.rs.Path; -import jakarta.ws.rs.PathParam; -import jakarta.ws.rs.Produces; -import jakarta.ws.rs.WebApplicationException; -import java.time.Instant; -import java.util.List; -import org.opendc.web.server.model.Project; -import org.opendc.web.server.model.ProjectAuthorization; -import org.opendc.web.server.model.Topology; - -/** - * A resource representing the constructed datacenter topologies. - */ -@Produces("application/json") -@Path("/projects/{project}/topologies") -@RolesAllowed("openid") -public final class TopologyResource { - /** - * The identity of the current user. - */ - private final SecurityIdentity identity; - - /** - * Construct a {@link TopologyResource}. - * - * @param identity The {@link SecurityIdentity} of the current user. - */ - public TopologyResource(SecurityIdentity identity) { - this.identity = identity; - } - - /** - * Get all topologies that belong to the specified project. - */ - @GET - public List<org.opendc.web.proto.user.Topology> getAll(@PathParam("project") long projectId) { - // User must have access to project - ProjectAuthorization auth = - ProjectAuthorization.findByUser(identity.getPrincipal().getName(), projectId); - - if (auth == null) { - return List.of(); - } - - return Topology.findByProject(projectId).list().stream() - .map((t) -> UserProtocol.toDto(t, auth)) - .toList(); - } - - /** - * Create a topology for this project. - */ - @POST - @Consumes("application/json") - @Transactional - public org.opendc.web.proto.user.Topology create( - @PathParam("project") long projectId, @Valid org.opendc.web.proto.user.Topology.Create request) { - // User must have access to project - ProjectAuthorization auth = - ProjectAuthorization.findByUser(identity.getPrincipal().getName(), projectId); - - if (auth == null) { - throw new WebApplicationException("Topology not found", 404); - } else if (!auth.canEdit()) { - throw new WebApplicationException("Not permitted to edit project", 403); - } - - Instant now = Instant.now(); - Project project = auth.project; - int number = project.allocateTopology(now); - - Topology topology = new Topology(project, number, request.getName(), now, request.getRooms()); - - project.topologies.add(topology); - topology.persist(); - - return UserProtocol.toDto(topology, auth); - } - - /** - * Obtain a topology by its number. - */ - @GET - @Path("{topology}") - public org.opendc.web.proto.user.Topology get( - @PathParam("project") long projectId, @PathParam("topology") int number) { - // User must have access to project - ProjectAuthorization auth = - ProjectAuthorization.findByUser(identity.getPrincipal().getName(), projectId); - - if (auth == null) { - throw new WebApplicationException("Topology not found", 404); - } - - Topology topology = Topology.findByProject(projectId, number); - - if (topology == null) { - throw new WebApplicationException("Topology not found", 404); - } - - return UserProtocol.toDto(topology, auth); - } - - /** - * Update the specified topology by its number. - */ - @PUT - @Path("{topology}") - @Consumes("application/json") - @Transactional - public org.opendc.web.proto.user.Topology update( - @PathParam("project") long projectId, - @PathParam("topology") int number, - @Valid org.opendc.web.proto.user.Topology.Update request) { - // User must have access to project - ProjectAuthorization auth = - ProjectAuthorization.findByUser(identity.getPrincipal().getName(), projectId); - - if (auth == null) { - throw new WebApplicationException("Topology not found", 404); - } else if (!auth.canEdit()) { - throw new WebApplicationException("Not permitted to edit project", 403); - } - - Topology entity = Topology.findByProject(projectId, number); - - if (entity == null) { - throw new WebApplicationException("Topology not found", 404); - } - - entity.updatedAt = Instant.now(); - entity.rooms = request.getRooms(); - - return UserProtocol.toDto(entity, auth); - } - - /** - * Delete the specified topology. - */ - @Path("{topology}") - @DELETE - @Transactional - public org.opendc.web.proto.user.Topology delete( - @PathParam("project") long projectId, @PathParam("topology") int number) { - // User must have access to project - ProjectAuthorization auth = - ProjectAuthorization.findByUser(identity.getPrincipal().getName(), projectId); - - if (auth == null) { - throw new WebApplicationException("Topology not found", 404); - } else if (!auth.canEdit()) { - throw new WebApplicationException("Not permitted to edit project", 403); - } - - Topology entity = Topology.findByProject(projectId, number); - - if (entity == null) { - throw new WebApplicationException("Topology not found", 404); - } - - entity.updatedAt = Instant.now(); - entity.delete(); - - try { - // Flush the results, so we can check whether the constraints are not violated - Panache.flush(); - } catch (PersistenceException e) { - throw new WebApplicationException("Topology is still in use", 403); - } - - return UserProtocol.toDto(entity, auth); - } -} diff --git a/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/rest/user/UserProtocol.java b/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/rest/user/UserProtocol.java deleted file mode 100644 index 8196a9d6..00000000 --- a/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/rest/user/UserProtocol.java +++ /dev/null @@ -1,132 +0,0 @@ -/* - * Copyright (c) 2023 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.web.server.rest.user; - -import org.opendc.web.server.model.Job; -import org.opendc.web.server.model.Portfolio; -import org.opendc.web.server.model.Project; -import org.opendc.web.server.model.ProjectAuthorization; -import org.opendc.web.server.model.Scenario; -import org.opendc.web.server.model.Topology; -import org.opendc.web.server.rest.BaseProtocol; - -/** - * DTO-conversions for the user protocol. - */ -public final class UserProtocol { - /** - * Private constructor to prevent instantiation of class. - */ - private UserProtocol() {} - - /** - * Convert a {@link ProjectAuthorization} entity into a {@link Project} DTO. - */ - public static org.opendc.web.proto.user.Project toDto(ProjectAuthorization auth) { - Project project = auth.project; - return new org.opendc.web.proto.user.Project( - project.id, project.name, project.createdAt, project.updatedAt, auth.role); - } - - /** - * Convert a {@link Portfolio} entity into a {@link org.opendc.web.proto.user.Portfolio} DTO. - */ - public static org.opendc.web.proto.user.Portfolio toDto(Portfolio portfolio, ProjectAuthorization auth) { - return new org.opendc.web.proto.user.Portfolio( - portfolio.id, - portfolio.number, - toDto(auth), - portfolio.name, - portfolio.targets, - portfolio.scenarios.stream().map(UserProtocol::toSummaryDto).toList()); - } - - /** - * Convert a {@link Portfolio} entity into a {@link org.opendc.web.proto.user.Portfolio.Summary} DTO. - */ - public static org.opendc.web.proto.user.Portfolio.Summary toSummaryDto(Portfolio portfolio) { - return new org.opendc.web.proto.user.Portfolio.Summary( - portfolio.id, portfolio.number, portfolio.name, portfolio.targets); - } - - /** - * Convert a {@link Topology} entity into a {@link org.opendc.web.proto.user.Topology} DTO. - */ - public static org.opendc.web.proto.user.Topology toDto(Topology topology, ProjectAuthorization auth) { - return new org.opendc.web.proto.user.Topology( - topology.id, - topology.number, - toDto(auth), - topology.name, - topology.rooms, - topology.createdAt, - topology.updatedAt); - } - - /** - * Convert a {@link Topology} entity into a {@link org.opendc.web.proto.user.Topology.Summary} DTO. - */ - public static org.opendc.web.proto.user.Topology.Summary toSummaryDto(Topology topology) { - return new org.opendc.web.proto.user.Topology.Summary( - topology.id, topology.number, topology.name, topology.createdAt, topology.updatedAt); - } - - /** - * Convert a {@link Scenario} entity into a {@link org.opendc.web.proto.user.Scenario} DTO. - */ - public static org.opendc.web.proto.user.Scenario toDto(Scenario scenario, ProjectAuthorization auth) { - return new org.opendc.web.proto.user.Scenario( - scenario.id, - scenario.number, - toDto(auth), - toSummaryDto(scenario.portfolio), - scenario.name, - BaseProtocol.toDto(scenario.workload), - toSummaryDto(scenario.topology), - scenario.phenomena, - scenario.schedulerName, - scenario.jobs.stream().map(UserProtocol::toDto).toList()); - } - - /** - * Convert a {@link Scenario} entity into a {@link org.opendc.web.proto.user.Scenario.Summary} DTO. - */ - public static org.opendc.web.proto.user.Scenario.Summary toSummaryDto(Scenario scenario) { - return new org.opendc.web.proto.user.Scenario.Summary( - scenario.id, - scenario.number, - scenario.name, - BaseProtocol.toDto(scenario.workload), - toSummaryDto(scenario.topology), - scenario.phenomena, - scenario.schedulerName, - scenario.jobs.stream().map(UserProtocol::toDto).toList()); - } - - /** - * Convert a {@link Job} entity into a {@link org.opendc.web.proto.user.Job} DTO. - */ - public static org.opendc.web.proto.user.Job toDto(Job job) { - return new org.opendc.web.proto.user.Job(job.id, job.state, job.createdAt, job.updatedAt, job.results); - } -} diff --git a/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/rest/user/UserResource.java b/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/rest/user/UserResource.java deleted file mode 100644 index c8cda2b7..00000000 --- a/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/rest/user/UserResource.java +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Copyright (c) 2023 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.web.server.rest.user; - -import io.quarkus.security.identity.SecurityIdentity; -import jakarta.annotation.security.RolesAllowed; -import jakarta.ws.rs.GET; -import jakarta.ws.rs.Path; -import jakarta.ws.rs.Produces; -import org.opendc.web.proto.user.User; -import org.opendc.web.proto.user.UserAccounting; -import org.opendc.web.server.service.UserAccountingService; - -/** - * A resource representing the active user. - */ -@Produces("application/json") -@Path("/users") -@RolesAllowed("openid") -public final class UserResource { - /** - * The service for managing the user accounting. - */ - private final UserAccountingService accountingService; - - /** - * The identity of the current user. - */ - private final SecurityIdentity identity; - - /** - * Construct a {@link UserResource}. - * - * @param accountingService The {@link UserAccountingService} instance to use. - * @param identity The {@link SecurityIdentity} of the current user. - */ - public UserResource(UserAccountingService accountingService, SecurityIdentity identity) { - this.accountingService = accountingService; - this.identity = identity; - } - - /** - * Get the current active user data. - */ - @GET - @Path("me") - public User get() { - String userId = identity.getPrincipal().getName(); - UserAccounting accounting = accountingService.getAccounting(userId); - - return new User(userId, accounting); - } -} diff --git a/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/service/JobService.java b/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/service/JobService.java deleted file mode 100644 index 70933520..00000000 --- a/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/service/JobService.java +++ /dev/null @@ -1,81 +0,0 @@ -/* - * Copyright (c) 2023 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.web.server.service; - -import jakarta.enterprise.context.ApplicationScoped; -import java.time.Instant; -import java.util.Map; -import org.opendc.web.proto.JobState; -import org.opendc.web.server.model.Job; - -/** - * A service for managing the lifecycle of a job and ensuring that the user does not consume - * too much simulation resources. - */ -@ApplicationScoped -public final class JobService { - /** - * The {@link UserAccountingService} responsible for accounting the simulation time of users. - */ - private final UserAccountingService accountingService; - - /** - * Construct a {@link JobService} instance. - * - * @param accountingService The {@link UserAccountingService} for accounting the simulation time of users. - */ - public JobService(UserAccountingService accountingService) { - this.accountingService = accountingService; - } - - /** - * Update the job state. - * - * @param job The {@link Job} to update. - * @param newState The new state to transition the job to. - * @param runtime The runtime (in seconds) consumed by the simulation jbo so far. - * @param results The results to attach to the job. - * @throws IllegalArgumentException if the state transition is invalid. - * @throws IllegalStateException if someone tries to update the job concurrently. - */ - public void updateJob(Job job, JobState newState, int runtime, Map<String, ?> results) { - JobState state = job.state; - - if (!job.canTransitionTo(newState)) { - throw new IllegalArgumentException("Invalid transition from %s to %s".formatted(state, newState)); - } - - Instant now = Instant.now(); - JobState nextState = newState; - int consumedBudget = Math.min(1, runtime - job.runtime); - - // Check whether the user still has any simulation budget left - if (accountingService.consumeSimulationBudget(job.createdBy, consumedBudget) && nextState == JobState.RUNNING) { - nextState = JobState.FAILED; // User has consumed all their budget; cancel the job - } - - if (!job.updateAtomically(nextState, now, runtime, results)) { - throw new IllegalStateException("Conflicting update"); - } - } -} diff --git a/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/service/UserAccountingService.java b/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/service/UserAccountingService.java deleted file mode 100644 index 73fa2a3e..00000000 --- a/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/service/UserAccountingService.java +++ /dev/null @@ -1,136 +0,0 @@ -/* - * Copyright (c) 2023 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.web.server.service; - -import jakarta.enterprise.context.ApplicationScoped; -import jakarta.persistence.EntityExistsException; -import java.time.Duration; -import java.time.LocalDate; -import java.time.temporal.TemporalAdjusters; -import org.eclipse.microprofile.config.inject.ConfigProperty; -import org.opendc.web.server.model.UserAccounting; - -/** - * Service to track the simulation budget of users. - */ -@ApplicationScoped -public final class UserAccountingService { - /** - * The default simulation budget for new users. - */ - private final Duration simulationBudget; - - /** - * Construct a {@link UserAccountingService} instance. - * - * @param simulationBudget The default simulation budget for new users. - */ - public UserAccountingService( - @ConfigProperty(name = "opendc.accounting.simulation-budget", defaultValue = "2000m") - Duration simulationBudget) { - this.simulationBudget = simulationBudget; - } - - /** - * Return the {@link org.opendc.web.proto.user.UserAccounting} object for the user with the - * specified <code>userId</code>. If the object does not exist in the database, a default value is constructed. - */ - public org.opendc.web.proto.user.UserAccounting getAccounting(String userId) { - UserAccounting accounting = UserAccounting.findByUser(userId); - if (accounting != null) { - return new org.opendc.web.proto.user.UserAccounting( - accounting.periodEnd, accounting.simulationTime, accounting.simulationTimeBudget); - } - - return new org.opendc.web.proto.user.UserAccounting( - getNextAccountingPeriod(LocalDate.now()), 0, (int) simulationBudget.toSeconds()); - } - - /** - * Determine whether the user with <code>userId</code> has any remaining simulation budget. - * - * @param userId The unique identifier of the user. - * @return <code>true</code> when the user still has budget left, <code>false</code> otherwise. - */ - public boolean hasSimulationBudget(String userId) { - UserAccounting accounting = UserAccounting.findByUser(userId); - if (accounting == null) { - return true; - } - return accounting.hasSimulationBudget(); - } - - /** - * Consume <code>seconds</code> from the simulation budget of the user with <code>userId</code>. - * - * @param userId The unique identifier of the user. - * @param seconds The seconds to consume from the simulation budget. - * @return <code>true</code> if the user has consumed his full budget or <code>false</code> if there is still budget - * remaining. - */ - public boolean consumeSimulationBudget(String userId, int seconds) { - LocalDate today = LocalDate.now(); - LocalDate nextAccountingPeriod = getNextAccountingPeriod(today); - - // We need to be careful to prevent conflicts in case of concurrency - // 1. First, we try to create the accounting object if it does not exist yet. This may fail if another instance - // creates the object concurrently. - // 2. Second, we check if the budget needs to be reset and try this atomically. - // 3. Finally, we atomically consume the budget from the object - // This is repeated three times in case there is a conflict - for (int i = 0; i < 3; i++) { - UserAccounting accounting = UserAccounting.findByUser(userId); - - if (accounting == null) { - try { - UserAccounting newAccounting = UserAccounting.create( - userId, nextAccountingPeriod, (int) simulationBudget.toSeconds(), seconds); - return !newAccounting.hasSimulationBudget(); - } catch (EntityExistsException e) { - // Conflict due to concurrency; retry - } - } else { - boolean success; - - if (!today.isBefore(accounting.periodEnd)) { - success = accounting.resetBudget(nextAccountingPeriod, seconds); - } else { - success = accounting.consumeBudget(seconds); - } - - if (success) { - return !accounting.hasSimulationBudget(); - } - } - } - - throw new IllegalStateException("Failed to allocate consume budget due to conflict"); - } - - /** - * Helper method to find next accounting period. - */ - private static LocalDate getNextAccountingPeriod(LocalDate today) { - return today.with(TemporalAdjusters.firstDayOfNextMonth()); - } -} diff --git a/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/util/DevSecurityOverrideFilter.java b/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/util/DevSecurityOverrideFilter.java deleted file mode 100644 index 103f868d..00000000 --- a/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/util/DevSecurityOverrideFilter.java +++ /dev/null @@ -1,64 +0,0 @@ -/* - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.web.server.util; - -import io.quarkus.arc.properties.IfBuildProperty; -import jakarta.ws.rs.container.ContainerRequestContext; -import jakarta.ws.rs.container.ContainerRequestFilter; -import jakarta.ws.rs.container.PreMatching; -import jakarta.ws.rs.core.SecurityContext; -import jakarta.ws.rs.ext.Provider; -import java.security.Principal; - -/** - * Helper class to disable security for the OpenDC web API when in development mode. - */ -@Provider -@PreMatching -@IfBuildProperty(name = "opendc.security.enabled", stringValue = "false") -public class DevSecurityOverrideFilter implements ContainerRequestFilter { - @Override - public void filter(ContainerRequestContext requestContext) { - requestContext.setSecurityContext(new SecurityContext() { - @Override - public Principal getUserPrincipal() { - return () -> "anon"; - } - - @Override - public boolean isUserInRole(String role) { - return true; - } - - @Override - public boolean isSecure() { - return false; - } - - @Override - public String getAuthenticationScheme() { - return "basic"; - } - }); - } -} diff --git a/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/util/KotlinModuleCustomizer.java b/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/util/KotlinModuleCustomizer.java deleted file mode 100644 index ff3ba1cd..00000000 --- a/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/util/KotlinModuleCustomizer.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright (c) 2023 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.web.server.util; - -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.module.kotlin.KotlinModule; -import io.quarkus.jackson.ObjectMapperCustomizer; -import jakarta.inject.Singleton; - -/** - * Helper class to register the Kotlin Jackson module. - */ -@Singleton -public final class KotlinModuleCustomizer implements ObjectMapperCustomizer { - @Override - public void customize(ObjectMapper objectMapper) { - objectMapper.registerModule(new KotlinModule.Builder().build()); - } -} diff --git a/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/util/QuarkusObjectMapperSupplier.java b/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/util/QuarkusObjectMapperSupplier.java deleted file mode 100644 index 60ca77e5..00000000 --- a/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/util/QuarkusObjectMapperSupplier.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright (c) 2023 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.web.server.util; - -import com.fasterxml.jackson.databind.ObjectMapper; -import io.hypersistence.utils.hibernate.type.util.ObjectMapperSupplier; -import io.quarkus.runtime.annotations.RegisterForReflection; -import jakarta.enterprise.inject.spi.CDI; - -/** - * A supplier for an {@link ObjectMapper} used by the Hypersistence utilities. - */ -@RegisterForReflection -public class QuarkusObjectMapperSupplier implements ObjectMapperSupplier { - @Override - public ObjectMapper get() { - return CDI.current().select(ObjectMapper.class).get(); - } -} diff --git a/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/util/runner/QuarkusJobManager.java b/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/util/runner/QuarkusJobManager.java deleted file mode 100644 index 47d397f3..00000000 --- a/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/util/runner/QuarkusJobManager.java +++ /dev/null @@ -1,114 +0,0 @@ -/* - * Copyright (c) 2022 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.web.server.util.runner; - -import jakarta.enterprise.context.ApplicationScoped; -import jakarta.transaction.Transactional; -import java.util.Map; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; -import org.opendc.web.proto.JobState; -import org.opendc.web.runner.JobManager; -import org.opendc.web.server.model.Job; -import org.opendc.web.server.rest.runner.RunnerProtocol; -import org.opendc.web.server.service.JobService; - -/** - * Implementation of {@link JobManager} that interfaces directly with the database without overhead of the REST API. - */ -@ApplicationScoped -public class QuarkusJobManager implements JobManager { - /** - * The {@link JobService} used to manage the job's lifecycle. - */ - private final JobService jobService; - - /** - * Construct a {@link QuarkusJobManager}. - * - * @param jobService The {@link JobService} for managing the job's lifecycle. - */ - public QuarkusJobManager(JobService jobService) { - this.jobService = jobService; - } - - @Transactional - @Nullable - @Override - public org.opendc.web.proto.runner.Job findNext() { - var job = Job.findByState(JobState.PENDING).firstResult(); - if (job == null) { - return null; - } - - return RunnerProtocol.toDto(job); - } - - @Transactional - @Override - public boolean claim(long id) { - return updateState(id, JobState.CLAIMED, 0, null); - } - - @Transactional - @Override - public boolean heartbeat(long id, int runtime) { - return updateState(id, JobState.RUNNING, runtime, null); - } - - @Transactional - @Override - public void fail(long id, int runtime) { - updateState(id, JobState.FAILED, runtime, null); - } - - @Transactional - @Override - public void finish(long id, int runtime, @NotNull Map<String, ?> results) { - updateState(id, JobState.FINISHED, runtime, results); - } - - /** - * Helper method to update the state of a job. - * - * @param id The unique id of the job. - * @param newState The new state to transition to. - * @param runtime The runtime of the job. - * @param results The results of the job. - * @return <code>true</code> if the operation succeeded, <code>false</code> otherwise. - */ - private boolean updateState(long id, JobState newState, int runtime, Map<String, ?> results) { - Job job = Job.findById(id); - - if (job == null) { - return false; - } - - try { - jobService.updateJob(job, newState, runtime, results); - return true; - } catch (IllegalArgumentException | IllegalStateException e) { - return false; - } - } -} diff --git a/opendc-web/opendc-web-server/src/main/resources/META-INF/branding/logo.png b/opendc-web/opendc-web-server/src/main/resources/META-INF/branding/logo.png Binary files differdeleted file mode 100644 index d743038b..00000000 --- a/opendc-web/opendc-web-server/src/main/resources/META-INF/branding/logo.png +++ /dev/null diff --git a/opendc-web/opendc-web-server/src/main/resources/application-dev.properties b/opendc-web/opendc-web-server/src/main/resources/application-dev.properties deleted file mode 100644 index 5fbc4c04..00000000 --- a/opendc-web/opendc-web-server/src/main/resources/application-dev.properties +++ /dev/null @@ -1,36 +0,0 @@ -# Copyright (c) 2022 AtLarge Research -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in all -# copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -# SOFTWARE. - -# Datasource (H2) -quarkus.datasource.db-kind=h2 -quarkus.datasource.jdbc.url=jdbc:h2:mem:default;DB_CLOSE_DELAY=-1;INIT=CREATE TYPE IF NOT EXISTS "JSONB" AS json; - -# Hibernate -quarkus.hibernate-orm.dialect=org.hibernate.dialect.H2Dialect -quarkus.flyway.clean-at-start=true - -# Disable authentication -opendc.security.enabled=false - -# Mount web UI at root and API at "/api" -quarkus.resteasy.path=/api - -# Swagger UI -quarkus.smallrye-openapi.servers=http://localhost:8080 diff --git a/opendc-web/opendc-web-server/src/main/resources/application-docker.properties b/opendc-web/opendc-web-server/src/main/resources/application-docker.properties deleted file mode 100644 index eae9ee1e..00000000 --- a/opendc-web/opendc-web-server/src/main/resources/application-docker.properties +++ /dev/null @@ -1,49 +0,0 @@ -# Copyright (c) 2022 AtLarge Research -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in all -# copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -# SOFTWARE. - -# Configuration for standalone Docker server distribution without web UI. - -# Datasource -quarkus.datasource.db-kind=postgresql -quarkus.datasource.username=${OPENDC_DB_USERNAME} -quarkus.datasource.password=${OPENDC_DB_PASSWORD} -quarkus.datasource.jdbc.url=${OPENDC_DB_URL} - -# Hibernate -quarkus.hibernate-orm.dialect=org.hibernate.dialect.PostgreSQL95Dialect - -# Disable OpenDC web UI -quarkus.opendc-ui.include=false - -# Security -opendc.security.enabled=true -quarkus.oidc.auth-server-url=https://${OPENDC_AUTH0_DOMAIN} -quarkus.oidc.client-id=${OPENDC_AUTH0_AUDIENCE} -quarkus.oidc.token.audience=${quarkus.oidc.client-id} -quarkus.oidc.roles.role-claim-path=scope - -# Swagger UI -quarkus.swagger-ui.oauth-client-id=${OPENDC_AUTH0_DOCS_CLIENT_ID:} -quarkus.swagger-ui.oauth-additional-query-string-params={"audience":"${OPENDC_AUTH0_AUDIENCE:https://api.opendc.org/v2/}"} - -quarkus.smallrye-openapi.security-scheme=oidc -quarkus.smallrye-openapi.security-scheme-name=Auth0 -quarkus.smallrye-openapi.oidc-open-id-connect-url=https://${OPENDC_AUTH0_DOMAIN:opendc.eu.auth0.com}/.well-known/openid-configuration -quarkus.smallrye-openapi.servers=https://api.opendc.org diff --git a/opendc-web/opendc-web-server/src/main/resources/application-prod.properties b/opendc-web/opendc-web-server/src/main/resources/application-prod.properties deleted file mode 100644 index fe997fc0..00000000 --- a/opendc-web/opendc-web-server/src/main/resources/application-prod.properties +++ /dev/null @@ -1,36 +0,0 @@ -# Copyright (c) 2022 AtLarge Research -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in all -# copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -# SOFTWARE. - -# Datasource (H2) -quarkus.datasource.db-kind=h2 -quarkus.datasource.jdbc.url=jdbc:h2:file:./data/opendc;DB_CLOSE_DELAY=-1;INIT=CREATE TYPE IF NOT EXISTS "JSONB" AS json; - -# Hibernate -quarkus.hibernate-orm.dialect=org.hibernate.dialect.H2Dialect - -# Disable authentication -opendc.security.enabled=false -quarkus.oidc.enabled=${opendc.security.enabled} - -# Mount web UI at root and API at "/api" -quarkus.resteasy.path=/api - -# Swagger UI -quarkus.smallrye-openapi.servers=http://localhost:8080 diff --git a/opendc-web/opendc-web-server/src/main/resources/application-test.properties b/opendc-web/opendc-web-server/src/main/resources/application-test.properties deleted file mode 100644 index 4e3063e4..00000000 --- a/opendc-web/opendc-web-server/src/main/resources/application-test.properties +++ /dev/null @@ -1,43 +0,0 @@ -# Copyright (c) 2022 AtLarge Research -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in all -# copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -# SOFTWARE. - -# Datasource configuration -quarkus.datasource.db-kind = h2 -quarkus.datasource.jdbc.url=jdbc:h2:mem:default;DB_CLOSE_DELAY=-1;INIT=CREATE TYPE IF NOT EXISTS "JSONB" AS json; - -quarkus.hibernate-orm.dialect=org.hibernate.dialect.H2Dialect -quarkus.hibernate-orm.log.sql=true -quarkus.flyway.clean-at-start=true -quarkus.flyway.locations=db/migration,db/testing - -# Disable security -quarkus.oidc.enabled=false - -# Disable OpenAPI/Swagger -quarkus.smallrye-openapi.enable=false -quarkus.swagger-ui.enable=false - -# Disable OpenDC web UI and runner -quarkus.opendc-ui.include=false -quarkus.opendc-runner.include=false - -# Create new tables and fill them -quarkus.hibernate-orm.database.generation=drop-and-create -quarkus.hibernate-orm.sql-load-script=load_data.sql diff --git a/opendc-web/opendc-web-server/src/main/resources/application.properties b/opendc-web/opendc-web-server/src/main/resources/application.properties deleted file mode 100644 index 0f47db30..00000000 --- a/opendc-web/opendc-web-server/src/main/resources/application.properties +++ /dev/null @@ -1,49 +0,0 @@ -# Copyright (c) 2022 AtLarge Research -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in all -# copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -# SOFTWARE. - -# Enable CORS -quarkus.http.cors=true -quarkus.http.cors.origins=http://localhost:3000,https://opendc.org - -# Security -quarkus.oidc.enabled=${opendc.security.enabled} - -# Runner logging -quarkus.log.category."org.opendc".level=ERROR -quarkus.log.category."org.opendc.web".level=INFO -quarkus.log.category."org.apache".level=WARN - -# OpenAPI and Swagger -quarkus.smallrye-openapi.info-title=OpenDC REST API -%dev.quarkus.smallrye-openapi.info-title=OpenDC REST API (development) -quarkus.smallrye-openapi.info-version=2.1-rc1 -quarkus.smallrye-openapi.info-description=OpenDC is an open-source datacenter simulator for education, featuring real-time online collaboration, diverse simulation models, and detailed performance feedback statistics. -quarkus.smallrye-openapi.info-contact-email=opendc@atlarge-research.com -quarkus.smallrye-openapi.info-contact-name=OpenDC Support -quarkus.smallrye-openapi.info-contact-url=https://opendc.org -quarkus.smallrye-openapi.info-license-name=MIT -quarkus.smallrye-openapi.info-license-url=https://github.com/atlarge-research/opendc/blob/master/LICENSE.txt - -quarkus.swagger-ui.path=docs -quarkus.swagger-ui.always-include=true - -# Flyway database migrations -quarkus.flyway.baseline-on-migrate=true -quarkus.flyway.migrate-at-start=true diff --git a/opendc-web/opendc-web-server/src/main/resources/hypersistence-utils.properties b/opendc-web/opendc-web-server/src/main/resources/hypersistence-utils.properties deleted file mode 100644 index 451ce2d8..00000000 --- a/opendc-web/opendc-web-server/src/main/resources/hypersistence-utils.properties +++ /dev/null @@ -1 +0,0 @@ -hypersistence.utils.jackson.object.mapper=org.opendc.web.server.util.QuarkusObjectMapperSupplier diff --git a/opendc-web/opendc-web-server/src/main/resources/load_data.sql b/opendc-web/opendc-web-server/src/main/resources/load_data.sql deleted file mode 100644 index 72396cef..00000000 --- a/opendc-web/opendc-web-server/src/main/resources/load_data.sql +++ /dev/null @@ -1,124 +0,0 @@ - --- Insert data - -INSERT INTO PROJECT (created_at, name, portfolios_created, scenarios_created, topologies_created, updated_at, id) - VALUES ('2024-03-01T15:31:41.579969Z', 'Test Project 1', 0, 0, 0, '2024-03-01T15:31:41.579969Z', 1); - -INSERT INTO PROJECTAUTHORIZATION (role, project_id, user_name) -VALUES ('OWNER', 1, 'test_user_1'); - --- Add test user 2 as a viewer for project 1 - -INSERT INTO PROJECTAUTHORIZATION (role, project_id, user_name) -VALUES ('VIEWER', 1, 'test_user_2'); - --- Add test user 3 as an editor for project 1 - -INSERT INTO PROJECTAUTHORIZATION (role, project_id, user_name) -VALUES ('EDITOR', 1, 'test_user_3'); - --- Create a project for test user 2 - -INSERT INTO PROJECT (created_at, name, portfolios_created, scenarios_created, topologies_created, updated_at, id) -VALUES ('2024-03-01T15:31:41.579969Z', 'Test Project 2', 0, 0, 0, '2024-03-01T15:31:41.579969Z', 2); - -INSERT INTO PROJECTAUTHORIZATION (role, project_id, user_name) -VALUES ('OWNER', 2, 'test_user_2'); - --- Create three projects for test user 3. User 3 has multiple projects to test getAll - -INSERT INTO PROJECT (created_at, name, portfolios_created, scenarios_created, topologies_created, updated_at, id) -VALUES ('2024-03-01T15:31:41.579969Z', 'Test Project 3', 0, 0, 0, '2024-03-01T15:31:41.579969Z', 3); - -INSERT INTO PROJECTAUTHORIZATION (role, project_id, user_name) -VALUES ('OWNER', 3, 'test_user_3'); - -INSERT INTO PROJECT (created_at, name, portfolios_created, scenarios_created, topologies_created, updated_at, id) -VALUES ('2024-03-01T15:31:41.579969Z', 'Test Project 4', 0, 0, 0, '2024-03-01T15:31:41.579969Z', 4); - -INSERT INTO PROJECTAUTHORIZATION (role, project_id, user_name) -VALUES ('OWNER', 4, 'test_user_3'); - -INSERT INTO PROJECT (created_at, name, portfolios_created, scenarios_created, topologies_created, updated_at, id) -VALUES ('2024-03-01T15:31:41.579969Z', 'Test Project 5', 0, 0, 0, '2024-03-01T15:31:41.579969Z', 5); - -INSERT INTO PROJECTAUTHORIZATION (role, project_id, user_name) -VALUES ('OWNER', 5, 'test_user_3'); - --- Project to delete - -INSERT INTO PROJECT (created_at, name, portfolios_created, scenarios_created, topologies_created, updated_at, id) -VALUES ('2024-03-01T15:31:41.579969Z', 'Test Project Delete', 0, 0, 0, '2024-03-01T15:31:41.579969Z', 6); - -INSERT INTO PROJECTAUTHORIZATION (role, project_id, user_name) -VALUES ('OWNER', 6, 'test_user_1'); - --- -------------------------------------------------------------------------------- --- PortFolios --- -------------------------------------------------------------------------------- - --- Add Portfolio to project 1 -INSERT INTO PORTFOLIO (name, number, project_id, targets, id) -VALUES ('Test PortFolio Base', 1, 1, '{"metrics": [], "repeats":1}' FORMAT JSON, 1); - -INSERT INTO PORTFOLIO (name, number, project_id, targets, id) -VALUES ('Test PortFolio Delete', 2, 1, '{"metrics": [], "repeats":1}' FORMAT JSON, 2); - -INSERT INTO PORTFOLIO (name, number, project_id, targets, id) -VALUES ('Test PortFolio DeleteEditor', 3, 1, '{"metrics": [], "repeats":1}' FORMAT JSON, 3); - -UPDATE Project p -SET p.portfolios_created = 3, p.updated_at = '2024-03-01T15:31:41.579969Z' -WHERE p.id = 1; - --- -------------------------------------------------------------------------------- --- Topologies --- -------------------------------------------------------------------------------- - -INSERT INTO TOPOLOGY (created_at, name, number, project_id, rooms, updated_at, id) -VALUES ('2024-03-01T15:31:41.579969Z', 'Test Topology testUpdate', 1, 1, '[]' FORMAT JSON, '2024-03-01T15:31:41.579969Z', 1); - -INSERT INTO TOPOLOGY (created_at, name, number, project_id, rooms, updated_at, id) -VALUES ('2024-03-01T15:31:41.579969Z', 'Test Topology testDeleteAsEditor', 2, 1, '[]' FORMAT JSON, '2024-03-01T15:31:41.579969Z', 2); - -INSERT INTO TOPOLOGY (created_at, name, number, project_id, rooms, updated_at, id) -VALUES ('2024-03-01T15:31:41.579969Z', 'Test Topology testDelete', 3, 1, '[]' FORMAT JSON, '2024-03-01T15:31:41.579969Z', 3); - -INSERT INTO TOPOLOGY (created_at, name, number, project_id, rooms, updated_at, id) -VALUES ('2024-03-01T15:31:41.579969Z', 'Test Topology testDeleteUsed', 4, 1, '[]' FORMAT JSON, '2024-03-01T15:31:41.579969Z', 4); - -UPDATE Project p -SET p.topologies_created = 4, p.updated_at = '2024-03-01T15:31:41.579969Z' -WHERE p.id = 1; - --- -------------------------------------------------------------------------------- --- Traces --- -------------------------------------------------------------------------------- - -INSERT INTO TRACE (id, name, type) -VALUES ('bitbrains-small', 'Bitbrains Small', 'small'); - --- -------------------------------------------------------------------------------- --- Scenario --- -------------------------------------------------------------------------------- - -INSERT INTO SCENARIO (name, number, phenomena, portfolio_id, project_id, scheduler_name, topology_id, sampling_fraction, trace_id, id) -VALUES ('Test Scenario testDelete', 1, '{"failures": false, "interference": false}' FORMAT JSON, 1, 1, 'test', 1, 1.0, 'bitbrains-small', 1); - -INSERT INTO SCENARIO (name, number, phenomena, portfolio_id, project_id, scheduler_name, topology_id, sampling_fraction, trace_id, id) -VALUES ('Test Scenario testDeleteUsed', 2, '{"failures": false, "interference": false}' FORMAT JSON, 1, 1, 'test', 4, 1.0, 'bitbrains-small', 2); - - -UPDATE Project p -SET p.scenarios_created = 2, p.updated_at = '2024-03-01T15:31:41.579969Z' -WHERE p.id = 1; - --- -------------------------------------------------------------------------------- --- Job --- -------------------------------------------------------------------------------- - -INSERT INTO JOB (scenario_id, created_by, created_at, repeats, updated_at, state, runtime, results, id) -VALUES (1, 'test_user_1', '2024-03-01T15:31:41.579969Z', 1, '2024-03-01T15:31:41.579969Z', 'PENDING', 1, '{}' FORMAT JSON, 1); - -INSERT INTO JOB (scenario_id, created_by, created_at, repeats, updated_at, state, runtime, results, id) -VALUES (1, 'test_user_1', '2024-03-01T15:31:41.579969Z', 1, '2024-03-01T15:31:41.579969Z', 'PENDING', 1, '{}' FORMAT JSON, 2); diff --git a/opendc-web/opendc-web-server/src/test/java/org/opendc/web/server/rest/SchedulerResourceTest.java b/opendc-web/opendc-web-server/src/test/java/org/opendc/web/server/rest/SchedulerResourceTest.java deleted file mode 100644 index f52ede3a..00000000 --- a/opendc-web/opendc-web-server/src/test/java/org/opendc/web/server/rest/SchedulerResourceTest.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright (c) 2023 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.web.server.rest; - -import static io.restassured.RestAssured.given; - -import io.quarkus.test.common.http.TestHTTPEndpoint; -import io.quarkus.test.junit.QuarkusTest; -import org.junit.jupiter.api.Test; - -/** - * Test suite for {@link SchedulerResource}. - */ -@QuarkusTest -@TestHTTPEndpoint(SchedulerResource.class) -public final class SchedulerResourceTest { - /** - * Test to verify whether we can obtain all schedulers. - */ - @Test - public void testGetSchedulers() { - given().get().then().statusCode(200); - } -} diff --git a/opendc-web/opendc-web-server/src/test/java/org/opendc/web/server/rest/TraceResourceTest.java b/opendc-web/opendc-web-server/src/test/java/org/opendc/web/server/rest/TraceResourceTest.java deleted file mode 100644 index 9da26059..00000000 --- a/opendc-web/opendc-web-server/src/test/java/org/opendc/web/server/rest/TraceResourceTest.java +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Copyright (c) 2023 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.web.server.rest; - -import static io.restassured.RestAssured.when; -import static org.hamcrest.Matchers.equalTo; - -import io.quarkus.test.common.http.TestHTTPEndpoint; -import io.quarkus.test.junit.QuarkusTest; -import io.restassured.http.ContentType; -import org.junit.jupiter.api.Test; - -/** - * Test suite for {@link TraceResource}. - */ -@QuarkusTest -@TestHTTPEndpoint(TraceResource.class) -public final class TraceResourceTest { - /** - * Test that tries to obtain all traces. - */ - @Test - public void testGetAllEmpty() { - when().get().then().statusCode(200); - } - - /** - * Test that tries to obtain a non-existent trace. - */ - @Test - public void testGetNonExisting() { - when().get("/unknown").then().statusCode(404); - } - - /** - * Test that tries to obtain an existing trace. - */ - @Test - public void testGetExisting() { - when().get("/bitbrains-small") - .then() - .statusCode(200) - .contentType(ContentType.JSON) - .body("name", equalTo("Bitbrains Small")); - } -} diff --git a/opendc-web/opendc-web-server/src/test/java/org/opendc/web/server/rest/runner/JobResourceTest.java b/opendc-web/opendc-web-server/src/test/java/org/opendc/web/server/rest/runner/JobResourceTest.java deleted file mode 100644 index 09f60c0a..00000000 --- a/opendc-web/opendc-web-server/src/test/java/org/opendc/web/server/rest/runner/JobResourceTest.java +++ /dev/null @@ -1,144 +0,0 @@ -/* - * Copyright (c) 2023 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.web.server.rest.runner; - -import static io.restassured.RestAssured.given; -import static org.hamcrest.Matchers.equalTo; - -import io.quarkus.test.common.http.TestHTTPEndpoint; -import io.quarkus.test.junit.QuarkusTest; -import io.quarkus.test.security.TestSecurity; -import io.restassured.http.ContentType; -import org.junit.jupiter.api.Test; -import org.opendc.web.proto.JobState; - -/** - * Test suite for {@link JobResource}. - */ -@QuarkusTest -@TestHTTPEndpoint(JobResource.class) -public final class JobResourceTest { - /** - * Test that tries to query the pending jobs without token. - */ - @Test - public void testQueryWithoutToken() { - given().get().then().statusCode(401); - } - - /** - * Test that tries to query the pending jobs for a user. - */ - @Test - @TestSecurity( - user = "test_user_1", - roles = {"openid"}) - public void testQueryInvalidScope() { - given().get().then().statusCode(403); - } - - /** - * Test that tries to query the pending jobs for a runner. - */ - @Test - @TestSecurity( - user = "test_user_1", - roles = {"runner"}) - public void testQuery() { - given().get().then().statusCode(200).contentType(ContentType.JSON).body("get(0).state", equalTo("PENDING")); - } - - /** - * Test that tries to obtain a non-existent job. - */ - @Test - @TestSecurity( - user = "test_user_1", - roles = {"runner"}) - public void testGetNonExisting() { - given().get("/0").then().statusCode(404); - } - - /** - * Test that tries to obtain a job. - */ - @Test - @TestSecurity( - user = "test_user_1", - roles = {"runner"}) - public void testGetExisting() { - given().get("/1").then().statusCode(200).contentType(ContentType.JSON).body("id", equalTo(1)); - } - - /** - * Test that tries to update a non-existent job. - */ - @Test - @TestSecurity( - user = "test_user_1", - roles = {"runner"}) - public void testUpdateNonExistent() { - given().body(new org.opendc.web.proto.runner.Job.Update(JobState.PENDING, 0, null)) - .contentType(ContentType.JSON) - .when() - .post("/0") - .then() - .statusCode(404) - .contentType(ContentType.JSON); - } - - /** - * Test that tries to update a job. - */ - @Test - @TestSecurity( - user = "test_user_1", - roles = {"runner"}) - public void testUpdateState() { - given().body(new org.opendc.web.proto.runner.Job.Update(JobState.CLAIMED, 0, null)) - .contentType(ContentType.JSON) - .when() - .post("/2") - .then() - .statusCode(200) - .contentType(ContentType.JSON) - .body("state", equalTo(JobState.CLAIMED.toString())); - } - - /** - * Test that tries to update a job with invalid input. - */ - @Test - @TestSecurity( - user = "test_user_1", - roles = {"runner"}) - public void testUpdateInvalidInput() { - given().body("{ \"test\": \"test\" }") - .contentType(ContentType.JSON) - .when() - .post("/1") - .then() - .statusCode(400) - .contentType(ContentType.JSON); - } -} diff --git a/opendc-web/opendc-web-server/src/test/java/org/opendc/web/server/rest/user/PortfolioResourceTest.java b/opendc-web/opendc-web-server/src/test/java/org/opendc/web/server/rest/user/PortfolioResourceTest.java deleted file mode 100644 index f23b4fc4..00000000 --- a/opendc-web/opendc-web-server/src/test/java/org/opendc/web/server/rest/user/PortfolioResourceTest.java +++ /dev/null @@ -1,284 +0,0 @@ -/* - * Copyright (c) 2023 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.web.server.rest.user; - -import static io.restassured.RestAssured.given; -import static org.hamcrest.Matchers.equalTo; - -import io.quarkus.test.common.http.TestHTTPEndpoint; -import io.quarkus.test.junit.QuarkusTest; -import io.quarkus.test.security.TestSecurity; -import io.restassured.http.ContentType; -import java.util.Set; -import org.junit.jupiter.api.Test; -import org.opendc.web.proto.Targets; - -/** - * Test suite for {@link PortfolioResource}. - */ -@QuarkusTest -@TestHTTPEndpoint(PortfolioResource.class) -public final class PortfolioResourceTest { - /** - * Test that tries to obtain the list of all portfolios belonging to a project. - */ - @Test - @TestSecurity( - user = "test_user_1", - roles = {"openid"}) - public void testGetAllForProject() { - given().pathParam("project", 1).when().get().then().statusCode(200); - } - - /** - * Test that tries to obtain the list of all portfolios belonging to a project - * without authorization. - * - * TODO: Why is this an empty list, and not a 403 message? - */ - @Test - @TestSecurity( - user = "test_user_1", - roles = {"openid"}) - public void testGetAllForProjectNoAuthorization() { - given().pathParam("project", 1).when().get().then().statusCode(200); - } - - /** - * Test that tries to create a portfolio for a project that exists and user has permission. - */ - @Test - @TestSecurity( - user = "test_user_1", - roles = {"openid"}) - public void testCreate() { - given().pathParam("project", "1") - .body(new org.opendc.web.proto.user.Portfolio.Create("Test Portfolio New", new Targets(Set.of(), 1))) - .contentType(ContentType.JSON) - .when() - .post() - .then() - .statusCode(200) - .contentType(ContentType.JSON) - .body("name", equalTo("Test Portfolio New")); - } - - /** - * Test that tries to create a topology for a project that does not exist. - */ - @Test - @TestSecurity( - user = "test_user_1", - roles = {"openid"}) - public void testCreateNonExistent() { - given().pathParam("project", "0") - .body(new org.opendc.web.proto.user.Portfolio.Create("test", new Targets(Set.of(), 1))) - .contentType(ContentType.JSON) - .when() - .post() - .then() - .statusCode(404); - } - - /** - * Test that tries to create a portfolio for a project that does exist but the user does not have permission. - */ - @Test - @TestSecurity( - user = "test_user_2", - roles = {"openid"}) - public void testCreateViewer() { - given().pathParam("project", "1") - .body(new org.opendc.web.proto.user.Portfolio.Create("test", new Targets(Set.of(), 1))) - .contentType(ContentType.JSON) - .when() - .post() - .then() - .statusCode(403); - } - - /** - * Test that tries to create a portfolio for a project that does exist but the user does not have permission. - * TODO: This should return 403 but does not because there is no user class - */ - @Test - @TestSecurity( - user = "test_user_1", - roles = {"openid"}) - public void testCreateNotPermitted() { - given().pathParam("project", "3") - .body(new org.opendc.web.proto.user.Portfolio.Create("test", new Targets(Set.of(), 1))) - .contentType(ContentType.JSON) - .when() - .post() - .then() - .statusCode(404); - } - - /** - * Test to create a portfolio with an empty body. - */ - @Test - @TestSecurity( - user = "test_user_1", - roles = {"openid"}) - public void testCreateEmpty() { - given().pathParam("project", "1") - .body("{}") - .contentType(ContentType.JSON) - .when() - .post() - .then() - .statusCode(400); - } - - /** - * Test to create a portfolio with a blank name. - */ - @Test - @TestSecurity( - user = "test_user_1", - roles = {"openid"}) - public void testCreateBlankName() { - given().pathParam("project", "1") - .body(new org.opendc.web.proto.user.Portfolio.Create("", new Targets(Set.of(), 1))) - .contentType(ContentType.JSON) - .when() - .post() - .then() - .statusCode(400); - } - - /** - * Test that tries to obtain a portfolio without token. - */ - @Test - public void testGetWithoutToken() { - given().pathParam("project", "1").when().get("/1").then().statusCode(401); - } - - /** - * Test that tries to obtain a portfolio with an invalid scope. - */ - @Test - @TestSecurity( - user = "test_user_1", - roles = {"runner"}) - public void testGetInvalidToken() { - given().pathParam("project", "1").when().get("/1").then().statusCode(403); - } - - /** - * Test that tries to obtain a non-existent portfolio. - */ - @Test - @TestSecurity( - user = "test_user_1", - roles = {"openid"}) - public void testGetNonExisting() { - given().pathParam("project", "1").when().get("/0").then().statusCode(404); - } - - /** - * Test that tries to obtain a portfolio for a non-existent project. - */ - @Test - @TestSecurity( - user = "test_user_1", - roles = {"openid"}) - public void testGetNonExistingProject() { - given().pathParam("project", "0").when().get("/1").then().statusCode(404); - } - - /** - * Test that tries to obtain a portfolio. - */ - @Test - @TestSecurity( - user = "test_user_1", - roles = {"openid"}) - public void testGetExisting() { - given().pathParam("project", "1") - .when() - .get("/1") - .then() - .statusCode(200) - .contentType(ContentType.JSON) - .body("id", equalTo(1)); - } - - /** - * Test to delete a non-existent portfolio. - */ - @Test - @TestSecurity( - user = "test_user_1", - roles = {"openid"}) - public void testDeleteNonExistent() { - given().pathParam("project", "1").when().delete("/0").then().statusCode(404); - } - - /** - * Test to delete a portfolio on a non-existent project. - */ - @Test - @TestSecurity( - user = "test_user_1", - roles = {"openid"}) - public void testDeleteNonExistentProject() { - given().pathParam("project", "0").when().delete("/1").then().statusCode(404); - } - - /** - * Test to delete a portfolio. - */ - @Test - @TestSecurity( - user = "test_user_1", - roles = {"openid"}) - public void testDelete() { - given().pathParam("project", "1").when().delete("/2").then().statusCode(200); - } - - /** - * Test to delete a portfolio as an editor. - */ - @Test - @TestSecurity( - user = "test_user_3", - roles = {"openid"}) - public void testDeleteEditor() { - given().pathParam("project", "1").when().delete("/3").then().statusCode(200); - } - - /** - * Test to delete a portfolio as a viewer. - */ - @Test - @TestSecurity( - user = "test_user_2", - roles = {"openid"}) - public void testDeleteAsViewer() { - given().pathParam("project", "1").when().delete("/1").then().statusCode(403); - } -} diff --git a/opendc-web/opendc-web-server/src/test/java/org/opendc/web/server/rest/user/PortfolioScenarioResourceTest.java b/opendc-web/opendc-web-server/src/test/java/org/opendc/web/server/rest/user/PortfolioScenarioResourceTest.java deleted file mode 100644 index 270dbae9..00000000 --- a/opendc-web/opendc-web-server/src/test/java/org/opendc/web/server/rest/user/PortfolioScenarioResourceTest.java +++ /dev/null @@ -1,273 +0,0 @@ -/* - * Copyright (c) 2023 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.web.server.rest.user; - -import static io.restassured.RestAssured.given; -import static org.hamcrest.Matchers.equalTo; - -import io.quarkus.test.common.http.TestHTTPEndpoint; -import io.quarkus.test.junit.QuarkusTest; -import io.quarkus.test.security.TestSecurity; -import io.restassured.http.ContentType; -import org.junit.jupiter.api.Test; -import org.opendc.web.proto.OperationalPhenomena; -import org.opendc.web.proto.Workload; -import org.opendc.web.proto.user.Scenario; - -/** - * Test suite for {@link PortfolioScenarioResource}. - */ -@QuarkusTest -@TestHTTPEndpoint(PortfolioScenarioResource.class) -public final class PortfolioScenarioResourceTest { - /** - * Test that tries to obtain a portfolio without token. - */ - @Test - public void testGetWithoutToken() { - given().pathParam("project", "1") - .pathParam("portfolio", "1") - .when() - .get() - .then() - .statusCode(401); - } - - /** - * Test that tries to obtain a portfolio with an invalid scope. - */ - @Test - @TestSecurity( - user = "test_user_1", - roles = {"runner"}) - public void testGetInvalidToken() { - given().pathParam("project", "1") - .pathParam("portfolio", "1") - .when() - .get() - .then() - .statusCode(403); - } - - /** - * Test that tries to obtain a scenario without authorization. - */ - @Test - @TestSecurity( - user = "test_user_1", - roles = {"openid"}) - public void testGetUnauthorized() { - given().pathParam("project", "2") - .pathParam("portfolio", "1") - .when() - .get() - .then() - .statusCode(200) - .contentType(ContentType.JSON); - } - - /** - * Test that tries to obtain a scenario. - * TODO: shouldn't this be all scenarios? - */ - @Test - @TestSecurity( - user = "test_user_1", - roles = {"openid"}) - public void testGet() { - given().pathParam("project", "1") - .pathParam("portfolio", "1") - .when() - .get() - .then() - .statusCode(200); - } - - /** - * Test that tries to create a scenario for a portfolio that does not exist in a project that can be accessed. - */ - @Test - @TestSecurity( - user = "test_user_1", - roles = {"openid"}) - public void testCreateNonExistent() { - given().pathParam("project", "1") - .pathParam("portfolio", "0") - .body(new Scenario.Create( - "test", new Workload.Spec("test", 1.0), 1, new OperationalPhenomena(false, false), "test")) - .contentType(ContentType.JSON) - .when() - .post() - .then() - .statusCode(404) - .contentType(ContentType.JSON); - } - - /** - * Test that tries to create a scenario for a portfolio without authorization. - */ - @Test - @TestSecurity( - user = "test_user_1", - roles = {"openid"}) - public void testCreateUnauthorized() { - given().pathParam("project", "2") - .pathParam("portfolio", "1") - .body(new Scenario.Create( - "test", new Workload.Spec("test", 1.0), 1, new OperationalPhenomena(false, false), "test")) - .contentType(ContentType.JSON) - .when() - .post() - .then() - .statusCode(404) - .contentType(ContentType.JSON); - } - - /** - * Test that tries to create a scenario for a portfolio as a viewer. - */ - @Test - @TestSecurity( - user = "test_user_2", - roles = {"openid"}) - public void testCreateAsViewer() { - given().pathParam("project", "1") - .pathParam("portfolio", "1") - .body(new Scenario.Create( - "test", new Workload.Spec("test", 1.0), 1, new OperationalPhenomena(false, false), "test")) - .contentType(ContentType.JSON) - .when() - .post() - .then() - .statusCode(403) - .contentType(ContentType.JSON); - } - - /** - * Test that tries to create a scenario for a portfolio. - */ - @Test - @TestSecurity( - user = "test_user_1", - roles = {"openid"}) - public void testCreate() { - given().pathParam("project", "1") - .pathParam("portfolio", "1") - .body(new Scenario.Create( - "Test Scenario New", - new Workload.Spec("bitbrains-small", 1.0), - 1, - new OperationalPhenomena(false, false), - "test")) - .contentType(ContentType.JSON) - .when() - .post() - .then() - .statusCode(200) - .contentType(ContentType.JSON) - .body("name", equalTo("Test Scenario New")); - } - - /** - * Test to create a project with an empty body. - */ - @Test - @TestSecurity( - user = "test_user_1", - roles = {"openid"}) - public void testCreateEmpty() { - given().pathParam("project", "1") - .pathParam("portfolio", "1") - .body("{}") - .contentType(ContentType.JSON) - .when() - .post() - .then() - .statusCode(400) - .contentType(ContentType.JSON); - } - - /** - * Test to create a project with a blank name. - */ - @Test - @TestSecurity( - user = "test_user_1", - roles = {"openid"}) - public void testCreateBlankName() { - given().pathParam("project", "1") - .pathParam("portfolio", "1") - .body(new Scenario.Create( - "", new Workload.Spec("test", 1.0), 1, new OperationalPhenomena(false, false), "test")) - .contentType(ContentType.JSON) - .when() - .post() - .then() - .statusCode(400) - .contentType(ContentType.JSON); - } - - /** - * Test that tries to create a scenario for a portfolio with an unknown Topology. - */ - @Test - @TestSecurity( - user = "test_user_1", - roles = {"openid"}) - public void testCreateUnknownTopology() { - given().pathParam("project", "1") - .pathParam("portfolio", "1") - .body(new Scenario.Create( - "test", - new Workload.Spec("bitbrains-small", 1.0), - -1, - new OperationalPhenomena(false, false), - "test")) - .contentType(ContentType.JSON) - .when() - .post() - .then() - .statusCode(400) - .contentType(ContentType.JSON); - } - - /** - * Test that tries to create a scenario for a portfolio with an unknown Trace. - */ - @Test - @TestSecurity( - user = "test_user_1", - roles = {"openid"}) - public void testCreateUnknownTrace() { - given().pathParam("project", "1") - .pathParam("portfolio", "1") - .body(new Scenario.Create( - "test", new Workload.Spec("unknown", 1.0), 1, new OperationalPhenomena(false, false), "test")) - .contentType(ContentType.JSON) - .when() - .post() - .then() - .statusCode(400) - .contentType(ContentType.JSON); - } -} diff --git a/opendc-web/opendc-web-server/src/test/java/org/opendc/web/server/rest/user/ProjectResourceTest.java b/opendc-web/opendc-web-server/src/test/java/org/opendc/web/server/rest/user/ProjectResourceTest.java deleted file mode 100644 index 450c0c0c..00000000 --- a/opendc-web/opendc-web-server/src/test/java/org/opendc/web/server/rest/user/ProjectResourceTest.java +++ /dev/null @@ -1,196 +0,0 @@ -/* - * Copyright (c) 2023 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.web.server.rest.user; - -import static io.restassured.RestAssured.given; -import static io.restassured.RestAssured.when; -import static org.hamcrest.Matchers.*; - -import io.quarkus.test.common.http.TestHTTPEndpoint; -import io.quarkus.test.junit.QuarkusTest; -import io.quarkus.test.security.TestSecurity; -import io.restassured.http.ContentType; -import org.junit.jupiter.api.Test; - -/** - * Test suite for [ProjectResource]. - */ -@QuarkusTest -@TestHTTPEndpoint(ProjectResource.class) -public final class ProjectResourceTest { - /** - * Test that tries to obtain all projects without token. - */ - @Test - public void testGetAllWithoutToken() { - when().get().then().statusCode(401); - } - - /** - * Test that tries to obtain all projects with an invalid scope. - */ - @Test - @TestSecurity( - user = "test_user_1", - roles = {"runner"}) - public void testGetAllWithInvalidScope() { - when().get().then().statusCode(403); - } - - /** - * Test that tries to obtain when no projects have yet been made. - */ - @Test - @TestSecurity( - user = "test_user_4", - roles = {"openid"}) - public void testGetAllWithNoAvailableProjects() { - when().get().then().statusCode(200).contentType(ContentType.JSON).body("", empty()); - } - - /** - * Test that tries to obtain all project for a user. - */ - @Test - @TestSecurity( - user = "test_user_3", - roles = {"openid"}) - public void testGetAll() { - given().get().then().statusCode(200).contentType(ContentType.JSON).body("", hasSize(4)); - } - - /** - * Test that tries to obtain a non-existent project. - */ - @Test - @TestSecurity( - user = "test_user_1", - roles = {"openid"}) - public void testGetNonExisting() { - when().get("/0").then().statusCode(404).contentType(ContentType.JSON); - } - - /** - * Test that tries to obtain a project. - */ - @Test - @TestSecurity( - user = "test_user_1", - roles = {"openid"}) - public void testGetExisting() { - // Try to get the project - given().get("/1").then().statusCode(200).contentType(ContentType.JSON).body("id", equalTo(1)); - } - - /** - * Test that tries to create a project. - */ - @Test - @TestSecurity( - user = "test_user_1", - roles = {"openid"}) - public void testCreate() { - given().body(new org.opendc.web.proto.user.Project.Create("Test Project New")) - .contentType(ContentType.JSON) - .when() - .post() - .then() - .statusCode(200) - .contentType(ContentType.JSON) - .body("name", equalTo("Test Project New")); - } - - /** - * Test to create a project with an empty body. - */ - @Test - @TestSecurity( - user = "test_user_1", - roles = {"openid"}) - public void testCreateEmpty() { - given().body("{}").contentType(ContentType.JSON).when().post().then().statusCode(400); - } - - /** - * Test to create a project with a blank name. - */ - @Test - @TestSecurity( - user = "test_user_1", - roles = {"openid"}) - public void testCreateBlankName() { - given().body(new org.opendc.web.proto.user.Project.Create("")) - .contentType(ContentType.JSON) - .when() - .post() - .then() - .statusCode(400); - } - - /** - * Test to delete a project that is owned by the user. - */ - @Test - @TestSecurity( - user = "test_user_1", - roles = {"openid"}) - public void testDelete() { - given().delete("/6").then().statusCode(200); - } - - /** - * Test to delete a non-existent project. - */ - @Test - @TestSecurity( - user = "test_user_1", - roles = {"openid"}) - public void testDeleteNonExistent() { - when().delete("/0").then().statusCode(404); - } - - /** - * Test to delete a project which is not connected to the user. - * test_user_3 is not connected to project 1. - */ - @Test - @TestSecurity( - user = "test_user_3", - roles = {"openid"}) - public void testDeleteNotConnected() { - when().delete("/1").then().statusCode(403); - } - - /** - * Test to delete a project which the user does not own. - * project 1 is owned by test_user_1, test_user_2 is a viewer - * should not be able to delete it - */ - @Test - @TestSecurity( - user = "test_user_2", - roles = {"openid"}) - public void testDeleteNonOwner() { - when().delete("/1").then().statusCode(403); - } -} diff --git a/opendc-web/opendc-web-server/src/test/java/org/opendc/web/server/rest/user/ScenarioResourceTest.java b/opendc-web/opendc-web-server/src/test/java/org/opendc/web/server/rest/user/ScenarioResourceTest.java deleted file mode 100644 index d81f9655..00000000 --- a/opendc-web/opendc-web-server/src/test/java/org/opendc/web/server/rest/user/ScenarioResourceTest.java +++ /dev/null @@ -1,178 +0,0 @@ -/* - * Copyright (c) 2023 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.web.server.rest.user; - -import static io.restassured.RestAssured.given; -import static org.hamcrest.Matchers.equalTo; - -import io.quarkus.test.common.http.TestHTTPEndpoint; -import io.quarkus.test.junit.QuarkusTest; -import io.quarkus.test.security.TestSecurity; -import io.restassured.http.ContentType; -import org.junit.jupiter.api.Test; - -/** - * Test suite for {@link ScenarioResource}. - */ -@QuarkusTest -@TestHTTPEndpoint(ScenarioResource.class) -public final class ScenarioResourceTest { - /** - * Test that tries to obtain all scenarios belonging to a project without authorization. - */ - @Test - @TestSecurity( - user = "test_user_1", - roles = {"openid"}) - public void testGetAllUnauthorized() { - given().pathParam("project", "2").when().get().then().statusCode(404); - } - - /** - * Test that tries to obtain all scenarios belonging to a project. - */ - @Test - @TestSecurity( - user = "test_user_1", - roles = {"openid"}) - public void testGetAll() { - given().pathParam("project", "1").when().get().then().statusCode(200); - } - - /** - * Test that tries to obtain a scenario without token. - */ - @Test - public void testGetWithoutToken() { - given().pathParam("project", "1").when().get("/1").then().statusCode(401); - } - - /** - * Test that tries to obtain a scenario with an invalid scope. - */ - @Test - @TestSecurity( - user = "test_user_1", - roles = {"runner"}) - public void testGetInvalidToken() { - given().pathParam("project", "1").when().get("/1").then().statusCode(403); - } - - /** - * Test that tries to obtain a non-existent scenario. - */ - @Test - @TestSecurity( - user = "test_user_1", - roles = {"openid"}) - public void testGetNonExisting() { - given().pathParam("project", "1") - .when() - .get("/0") - .then() - .statusCode(404) - .contentType(ContentType.JSON); - } - - /** - * Test that tries to obtain a scenario when it does not have authority to get to the project. - */ - @Test - @TestSecurity( - user = "test_user_1", - roles = {"openid"}) - public void testGetExistingUnauthorized() { - given().pathParam("project", "2") - .when() - .get("/1") - .then() - .statusCode(404) - .contentType(ContentType.JSON); - } - - /** - * Test that tries to obtain a scenario. - */ - @Test - @TestSecurity( - user = "test_user_1", - roles = {"openid"}) - public void testGetExisting() { - given().pathParam("project", "1") - .when() - .get("/1") - .then() - .statusCode(200) - .contentType(ContentType.JSON) - .body("id", equalTo(1)); - } - - /** - * Test to delete a non-existent scenario. - */ - @Test - @TestSecurity( - user = "test_user_1", - roles = {"openid"}) - public void testDeleteNonExistent() { - given().pathParam("project", "1").when().delete("/0").then().statusCode(404); - } - - /** - * Test to delete a scenario without authorization. - */ - @Test - @TestSecurity( - user = "test_user_1", - roles = {"openid"}) - public void testDeleteUnauthorized() { - given().pathParam("project", "2").when().delete("/1").then().statusCode(404); - } - - /** - * Test to delete a scenario as a viewer. - */ - @Test - @TestSecurity( - user = "test_user_2", - roles = {"openid"}) - public void testDeleteAsViewer() { - given().pathParam("project", "1").when().delete("/1").then().statusCode(403); - } - - /** - * Test to delete a scenario. - */ - @Test - @TestSecurity( - user = "test_user_1", - roles = {"openid"}) - public void testDelete() { - given().pathParam("project", "1") - .when() - .delete("/1") - .then() - .statusCode(200) - .contentType(ContentType.JSON); - } -} diff --git a/opendc-web/opendc-web-server/src/test/java/org/opendc/web/server/rest/user/TopologyResourceTest.java b/opendc-web/opendc-web-server/src/test/java/org/opendc/web/server/rest/user/TopologyResourceTest.java deleted file mode 100644 index 277376e5..00000000 --- a/opendc-web/opendc-web-server/src/test/java/org/opendc/web/server/rest/user/TopologyResourceTest.java +++ /dev/null @@ -1,358 +0,0 @@ -/* - * Copyright (c) 2023 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.web.server.rest.user; - -import static io.restassured.RestAssured.given; -import static org.hamcrest.Matchers.equalTo; - -import io.quarkus.test.common.http.TestHTTPEndpoint; -import io.quarkus.test.junit.QuarkusTest; -import io.quarkus.test.security.TestSecurity; -import io.restassured.http.ContentType; -import java.util.List; -import org.junit.jupiter.api.Test; -import org.opendc.web.proto.user.Topology; - -/** - * Test suite for {@link TopologyResource}. - */ -@QuarkusTest -@TestHTTPEndpoint(TopologyResource.class) -public final class TopologyResourceTest { - /** - * Test that tries to obtain the list of topologies of a project without proper authorization. - */ - @Test - @TestSecurity( - user = "test_user_4", - roles = {"openid"}) - public void testGetAllWithoutAuth() { - given().pathParam("project", "1") - .when() - .get() - .then() - .statusCode(200) - .contentType(ContentType.JSON) - .body(equalTo("[]")); - } - - /** - * Test that tries to obtain the list of topologies belonging to a project. - * TODO: check if any topology comes back - */ - @Test - @TestSecurity( - user = "test_user_1", - roles = {"openid"}) - public void testGetAll() { - given().pathParam("project", "1").when().get().then().statusCode(200); - } - - /** - * Test that tries to create a topology for a project that does not exist. - */ - @Test - @TestSecurity( - user = "test_user_1", - roles = {"openid"}) - public void testCreateNonExistent() { - given().pathParam("project", "0") - .body(new Topology.Create("test", List.of())) - .contentType(ContentType.JSON) - .when() - .post() - .then() - .statusCode(404); - } - - /** - * Test that tries to create a topology for a project while not authorized. - * TODO: should probably return 403, but this does not work in the current system - */ - @Test - @TestSecurity( - user = "test_user_1", - roles = {"openid"}) - public void testCreateUnauthorized() { - given().pathParam("project", "2") - .body(new Topology.Create("test", List.of())) - .contentType(ContentType.JSON) - .when() - .post() - .then() - .statusCode(404); - } - - /** - * Test that tries to create a topology for a project. - */ - @Test - @TestSecurity( - user = "test_user_1", - roles = {"openid"}) - public void testCreate() { - given().pathParam("project", "1") - .body(new Topology.Create("Test Topology New", List.of())) - .contentType(ContentType.JSON) - .when() - .post() - .then() - .statusCode(200) - .contentType(ContentType.JSON) - .body("name", equalTo("Test Topology New")); - } - - /** - * Test to create a topology with an empty body. - */ - @Test - @TestSecurity( - user = "test_user_1", - roles = {"openid"}) - public void testCreateEmpty() { - given().pathParam("project", "1") - .body("{}") - .contentType(ContentType.JSON) - .when() - .post() - .then() - .statusCode(400); - } - - /** - * Test to create a topology with a blank name. - */ - @Test - @TestSecurity( - user = "test_user_1", - roles = {"openid"}) - public void testCreateBlankName() { - given().pathParam("project", "1") - .body(new Topology.Create("", List.of())) - .contentType(ContentType.JSON) - .when() - .post() - .then() - .statusCode(400); - } - - /** - * Test that tries to obtain a topology without token. - */ - @Test - public void testGetWithoutToken() { - given().pathParam("project", "1").when().get("/1").then().statusCode(401); - } - - /** - * Test that tries to obtain a topology with an invalid scope. - */ - @Test - @TestSecurity( - user = "test_user_1", - roles = {"runner"}) - public void testGetInvalidToken() { - given().pathParam("project", "1").when().get("/1").then().statusCode(403); - } - - /** - * Test that tries to obtain a non-existent topology. - */ - @Test - @TestSecurity( - user = "test_user_1", - roles = {"openid"}) - public void testGetNonExisting() { - given().pathParam("project", "1").when().get("/0").then().statusCode(404); - } - - /** - * Test that tries to obtain a topology without authorization. - */ - @Test - @TestSecurity( - user = "test_user_1", - roles = {"openid"}) - public void testGetUnauthorized() { - given().pathParam("project", "2").when().get("/1").then().statusCode(404); - } - - /** - * Test that tries to obtain a topology. - */ - @Test - @TestSecurity( - user = "test_user_1", - roles = {"openid"}) - public void testGetExisting() { - given().pathParam("project", "1") - .when() - .get("/1") - .then() - .statusCode(200) - .contentType(ContentType.JSON) - .body("id", equalTo(1)); - } - - /** - * Test to delete a non-existent topology. - */ - @Test - @TestSecurity( - user = "test_user_1", - roles = {"openid"}) - public void testUpdateNonExistent() { - given().pathParam("project", "1") - .body(new Topology.Update(List.of())) - .contentType(ContentType.JSON) - .when() - .put("/0") - .then() - .statusCode(404); - } - - /** - * Test to delete a topology without authorization. - */ - @Test - @TestSecurity( - user = "test_user_1", - roles = {"openid"}) - public void testUpdateUnauthorized() { - given().pathParam("project", "2") - .body(new Topology.Update(List.of())) - .contentType(ContentType.JSON) - .when() - .put("/1") - .then() - .statusCode(404); - } - - /** - * Test to update a topology as a viewer. - * TODO: should return 403, but currently returns 404 - */ - @Test - @TestSecurity( - user = "test_user_2", - roles = {"openid"}) - public void testUpdateAsViewer() { - given().pathParam("project", "1") - .body(new Topology.Update(List.of())) - .contentType(ContentType.JSON) - .when() - .put("/1") - .then() - .statusCode(403) - .contentType(ContentType.JSON); - } - - /** - * Test to update a topology. - */ - @Test - @TestSecurity( - user = "test_user_1", - roles = {"openid"}) - public void testUpdate() { - given().pathParam("project", "1") - .body(new Topology.Update(List.of())) - .contentType(ContentType.JSON) - .when() - .put("/1") - .then() - .statusCode(200); - } - - /** - * Test to delete a non-existent topology. - */ - @Test - @TestSecurity( - user = "test_user_1", - roles = {"openid"}) - public void testDeleteNonExistent() { - given().pathParam("project", "1").when().delete("/0").then().statusCode(404); - } - - /** - * Test to delete a topology without authorization. - */ - @Test - @TestSecurity( - user = "test_user_1", - roles = {"openid"}) - public void testDeleteUnauthorized() { - given().pathParam("project", "2").when().delete("/1").then().statusCode(404); - } - - /** - * Test to delete a topology as a viewer. - */ - @Test - @TestSecurity( - user = "test_user_2", - roles = {"openid"}) - public void testDeleteAsViewer() { - given().pathParam("project", "1").when().delete("/1").then().statusCode(403); - } - - /** - * Test to delete a topology as a viewer. - */ - @Test - @TestSecurity( - user = "test_user_3", - roles = {"openid"}) - public void testDeleteAsEditor() { - given().pathParam("project", "1").when().delete("/2").then().statusCode(200); - } - - /** - * Test to delete a topology. - */ - @Test - @TestSecurity( - user = "test_user_1", - roles = {"openid"}) - public void testDelete() { - given().pathParam("project", "1").when().delete("/3").then().statusCode(200); - } - - /** - * Test to delete a topology that is still being used by a scenario. - * TODO: fix later - */ - @Test - @TestSecurity( - user = "test_user_1", - roles = {"openid"}) - public void testDeleteUsed() { - given().pathParam("project", "1") - .when() - .delete("/4") // Topology 1 is still used by scenario 1 and 2 - .then() - .statusCode(403) - .contentType(ContentType.JSON); - } -} diff --git a/opendc-web/opendc-web-server/src/test/java/org/opendc/web/server/rest/user/UserResourceTest.java b/opendc-web/opendc-web-server/src/test/java/org/opendc/web/server/rest/user/UserResourceTest.java deleted file mode 100644 index 6dcb3b4d..00000000 --- a/opendc-web/opendc-web-server/src/test/java/org/opendc/web/server/rest/user/UserResourceTest.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright (c) 2023 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.web.server.rest.user; - -import static io.restassured.RestAssured.when; -import static org.hamcrest.Matchers.equalTo; -import static org.hamcrest.Matchers.greaterThan; - -import io.quarkus.test.common.http.TestHTTPEndpoint; -import io.quarkus.test.junit.QuarkusTest; -import io.quarkus.test.security.TestSecurity; -import io.restassured.http.ContentType; -import org.junit.jupiter.api.Test; - -/** - * Test suite for [UserResource]. - */ -@QuarkusTest -@TestHTTPEndpoint(UserResource.class) -public final class UserResourceTest { - /** - * Test that tries to obtain the profile of the active user. - */ - @Test - @TestSecurity( - user = "testUser", - roles = {"openid"}) - public void testMe() { - when().get("me") - .then() - .statusCode(200) - .contentType(ContentType.JSON) - .body("userId", equalTo("testUser")) - .body("accounting.simulationTime", equalTo(0)) - .body("accounting.simulationTimeBudget", greaterThan(0)); - } - - /** - * Test that tries to obtain the profile of the active user without authorization. - */ - @Test - public void testMeUnauthorized() { - when().get("me").then().statusCode(401); - } -} diff --git a/opendc-web/opendc-web-server/src/test/java/org/opendc/web/server/service/JobServiceTest.java b/opendc-web/opendc-web-server/src/test/java/org/opendc/web/server/service/JobServiceTest.java deleted file mode 100644 index f6d871c0..00000000 --- a/opendc-web/opendc-web-server/src/test/java/org/opendc/web/server/service/JobServiceTest.java +++ /dev/null @@ -1,124 +0,0 @@ -/* - * Copyright (c) 2023 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.web.server.service; - -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyInt; -import static org.mockito.ArgumentMatchers.eq; - -import io.quarkus.test.junit.QuarkusTest; -import java.time.Instant; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.mockito.Mockito; -import org.opendc.web.proto.JobState; -import org.opendc.web.server.model.Job; - -/** - * Test suite for the {@link JobService}. - */ -@QuarkusTest -public class JobServiceTest { - /** - * The {@link JobService} instance under test. - */ - private JobService service; - - /** - * The mock {@link UserAccountingService}. - */ - private UserAccountingService mockAccountingService; - - @BeforeEach - public void setUp() { - mockAccountingService = Mockito.mock(UserAccountingService.class); - service = new JobService(mockAccountingService); - } - - @Test - public void testUpdateInvalidTransition() { - Job job = new Job(null, "test", Instant.now(), 1); - job.state = JobState.RUNNING; - - assertThrows(IllegalArgumentException.class, () -> service.updateJob(job, JobState.CLAIMED, 0, null)); - - Mockito.verifyNoInteractions(mockAccountingService); - } - - @Test - public void testUpdateNoBudget() { - Job job = Mockito.spy(new Job(null, "test", Instant.now(), 1)); - job.state = JobState.RUNNING; - - Mockito.when(mockAccountingService.consumeSimulationBudget(any(), anyInt())) - .thenReturn(true); - Mockito.doReturn(true).when(job).updateAtomically(any(), any(), anyInt(), any()); - - service.updateJob(job, JobState.RUNNING, 0, null); - - Mockito.verify(job).updateAtomically(eq(JobState.FAILED), any(), anyInt(), any()); - } - - @Test - public void testUpdateNoBudgetWhenFinishing() { - Job job = Mockito.spy(new Job(null, "test", Instant.now(), 1)); - job.state = JobState.RUNNING; - - Mockito.when(mockAccountingService.consumeSimulationBudget(any(), anyInt())) - .thenReturn(true); - Mockito.doReturn(true).when(job).updateAtomically(any(), any(), anyInt(), any()); - - service.updateJob(job, JobState.FINISHED, 0, null); - - Mockito.verify(job).updateAtomically(eq(JobState.FINISHED), any(), anyInt(), any()); - } - - @Test - public void testUpdateSuccess() { - Job job = Mockito.spy(new Job(null, "test", Instant.now(), 1)); - job.state = JobState.RUNNING; - - Mockito.when(mockAccountingService.consumeSimulationBudget(any(), anyInt())) - .thenReturn(false); - Mockito.doReturn(true).when(job).updateAtomically(any(), any(), anyInt(), any()); - - service.updateJob(job, JobState.FINISHED, 0, null); - - Mockito.verify(job).updateAtomically(eq(JobState.FINISHED), any(), anyInt(), any()); - } - - @Test - public void testUpdateConflict() { - Job job = Mockito.spy(new Job(null, "test", Instant.now(), 1)); - job.state = JobState.RUNNING; - - Mockito.when(mockAccountingService.consumeSimulationBudget(any(), anyInt())) - .thenReturn(false); - Mockito.doReturn(false).when(job).updateAtomically(any(), any(), anyInt(), any()); - - assertThrows(IllegalStateException.class, () -> service.updateJob(job, JobState.FINISHED, 0, null)); - - Mockito.verify(job).updateAtomically(eq(JobState.FINISHED), any(), anyInt(), any()); - } -} diff --git a/opendc-web/opendc-web-server/src/test/java/org/opendc/web/server/service/UserAccountingServiceTest.java b/opendc-web/opendc-web-server/src/test/java/org/opendc/web/server/service/UserAccountingServiceTest.java deleted file mode 100644 index 91e3eb66..00000000 --- a/opendc-web/opendc-web-server/src/test/java/org/opendc/web/server/service/UserAccountingServiceTest.java +++ /dev/null @@ -1,213 +0,0 @@ -/* - * Copyright (c) 2023 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.web.server.service; - -import static org.junit.jupiter.api.Assertions.assertAll; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyInt; -import static org.mockito.ArgumentMatchers.anyString; - -import io.quarkus.panache.mock.PanacheMock; -import io.quarkus.test.junit.QuarkusTest; -import jakarta.persistence.EntityExistsException; -import java.time.Duration; -import java.time.LocalDate; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.mockito.Mockito; -import org.opendc.web.server.model.UserAccounting; - -/** - * Test suite for the {@link UserAccountingService}. - */ -@QuarkusTest -public class UserAccountingServiceTest { - /** - * The {@link UserAccountingService} instance under test. - */ - private UserAccountingService service; - - /** - * The user id to test with - */ - private final String userId = "test"; - - @BeforeEach - public void setUp() { - PanacheMock.mock(UserAccounting.class); - service = new UserAccountingService(Duration.ofHours(1)); - } - - @Test - public void testGetUserDoesNotExist() { - Mockito.when(UserAccounting.findByUser(userId)).thenReturn(null); - - var accounting = service.getAccounting(userId); - - assertTrue(accounting.getPeriodEnd().isAfter(LocalDate.now())); - assertEquals(0, accounting.getSimulationTime()); - } - - @Test - public void testGetUserDoesExist() { - var now = LocalDate.now(); - var periodEnd = now.plusMonths(1); - - var mockAccounting = new UserAccounting(userId, periodEnd, 3600); - mockAccounting.simulationTime = 32; - - Mockito.when(UserAccounting.findByUser(userId)).thenReturn(mockAccounting); - - var accounting = service.getAccounting(userId); - - assertAll( - () -> assertEquals(periodEnd, accounting.getPeriodEnd()), - () -> assertEquals(32, accounting.getSimulationTime()), - () -> assertEquals(3600, accounting.getSimulationTimeBudget())); - } - - @Test - public void testHasBudgetUserDoesNotExist() { - Mockito.when(UserAccounting.findByUser(userId)).thenReturn(null); - - assertTrue(service.hasSimulationBudget(userId)); - } - - @Test - public void testHasBudget() { - var periodEnd = LocalDate.now().plusMonths(2); - - var mockAccounting = new UserAccounting(userId, periodEnd, 3600); - Mockito.when(UserAccounting.findByUser(userId)).thenReturn(mockAccounting); - - assertTrue(service.hasSimulationBudget(userId)); - } - - @Test - public void testHasBudgetExceededButPeriodExpired() { - var periodEnd = LocalDate.now().minusMonths(2); - - var mockAccounting = new UserAccounting(userId, periodEnd, 3600); - mockAccounting.simulationTime = 3900; - Mockito.when(UserAccounting.findByUser(userId)).thenReturn(mockAccounting); - - assertTrue(service.hasSimulationBudget(userId)); - } - - @Test - public void testHasBudgetPeriodExpired() { - var periodEnd = LocalDate.now().minusMonths(2); - - var mockAccounting = new UserAccounting(userId, periodEnd, 3600); - Mockito.when(UserAccounting.findByUser(userId)).thenReturn(mockAccounting); - - assertTrue(service.hasSimulationBudget(userId)); - } - - @Test - public void testHasBudgetExceeded() { - var periodEnd = LocalDate.now().plusMonths(1); - - var mockAccounting = new UserAccounting(userId, periodEnd, 3600); - mockAccounting.simulationTime = 3900; - Mockito.when(UserAccounting.findByUser(userId)).thenReturn(mockAccounting); - - assertFalse(service.hasSimulationBudget(userId)); - } - - @Test - public void testConsumeBudgetNewUser() { - Mockito.when(UserAccounting.findByUser(userId)).thenReturn(null); - Mockito.when(UserAccounting.create(anyString(), any(), anyInt(), anyInt())) - .thenAnswer((i) -> { - var accounting = new UserAccounting(i.getArgument(0), i.getArgument(1), i.getArgument(2)); - accounting.simulationTime = i.getArgument(3); - return accounting; - }); - - assertFalse(service.consumeSimulationBudget(userId, 10)); - } - - @Test - public void testConsumeBudgetNewUserExceeded() { - Mockito.when(UserAccounting.findByUser(userId)).thenReturn(null); - Mockito.when(UserAccounting.create(anyString(), any(), anyInt(), anyInt())) - .thenAnswer((i) -> { - var accounting = new UserAccounting(i.getArgument(0), i.getArgument(1), i.getArgument(2)); - accounting.simulationTime = i.getArgument(3); - return accounting; - }); - - assertTrue(service.consumeSimulationBudget(userId, 4000)); - } - - @Test - public void testConsumeBudgetNewUserConflict() { - var periodEnd = LocalDate.now().plusMonths(1); - var accountingMock = Mockito.spy(new UserAccounting(userId, periodEnd, 3600)); - - Mockito.when(UserAccounting.findByUser(userId)).thenReturn(null).thenReturn(accountingMock); - Mockito.when(UserAccounting.create(anyString(), any(), anyInt(), anyInt())) - .thenThrow(new EntityExistsException()); - Mockito.when(accountingMock.consumeBudget(anyInt())).thenAnswer((i) -> { - accountingMock.simulationTime += i.<Integer>getArgument(0); - return true; - }); - - assertFalse(service.consumeSimulationBudget(userId, 10)); - } - - @Test - public void testConsumeBudgetResetSuccess() { - var periodEnd = LocalDate.now().minusMonths(2); - var accountingMock = Mockito.spy(new UserAccounting(userId, periodEnd, 3600)); - accountingMock.simulationTime = 3900; - - Mockito.when(UserAccounting.findByUser(userId)).thenReturn(accountingMock); - Mockito.when(accountingMock.resetBudget(any(), anyInt())).thenAnswer((i) -> { - accountingMock.periodEnd = i.getArgument(0); - accountingMock.simulationTime += i.<Integer>getArgument(1); - return true; - }); - - assertTrue(service.consumeSimulationBudget(userId, 4000)); - } - - @Test - public void testInfiniteConflict() { - var periodEnd = LocalDate.now().plusMonths(1); - var accountingMock = Mockito.spy(new UserAccounting(userId, periodEnd, 3600)); - - Mockito.when(UserAccounting.findByUser(userId)).thenReturn(accountingMock); - Mockito.when(accountingMock.consumeBudget(anyInt())).thenAnswer((i) -> { - accountingMock.simulationTime += i.<Integer>getArgument(0); - return false; - }); - - assertThrows(IllegalStateException.class, () -> service.consumeSimulationBudget(userId, 10)); - } -} diff --git a/opendc-web/opendc-web-ui-quarkus-deployment/build.gradle.kts b/opendc-web/opendc-web-ui-quarkus-deployment/build.gradle.kts deleted file mode 100644 index 6be49e22..00000000 --- a/opendc-web/opendc-web-ui-quarkus-deployment/build.gradle.kts +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright (c) 2022 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -description = "Quarkus extension for serving OpenDC web interface" - -// Build configuration -plugins { - `java-library-conventions` -} - -dependencies { - implementation(platform(libs.quarkus.bom)) - - implementation(projects.opendcWeb.opendcWebUi) - implementation(projects.opendcWeb.opendcWebUiQuarkus) - - implementation(libs.quarkus.core.deployment) - implementation(libs.quarkus.vertx.http.deployment) - implementation(libs.quarkus.arc.deployment) -} diff --git a/opendc-web/opendc-web-ui-quarkus-deployment/src/main/java/org/opendc/web/ui/deployment/AuthConfiguration.java b/opendc-web/opendc-web-ui-quarkus-deployment/src/main/java/org/opendc/web/ui/deployment/AuthConfiguration.java deleted file mode 100644 index 82e02549..00000000 --- a/opendc-web/opendc-web-ui-quarkus-deployment/src/main/java/org/opendc/web/ui/deployment/AuthConfiguration.java +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright (c) 2022 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.web.ui.deployment; - -import io.quarkus.runtime.annotations.ConfigGroup; -import io.quarkus.runtime.annotations.ConfigItem; -import java.util.Optional; - -/** - * Auth configuration for the OpenDC UI extension. - */ -@ConfigGroup -public class AuthConfiguration { - /** - * The authentication domain. - */ - @ConfigItem - Optional<String> domain; - - /** - * The client identifier used by the OpenDC web ui. - */ - @ConfigItem - Optional<String> clientId; - - /** - * The audience of the OpenDC API. - */ - @ConfigItem - Optional<String> audience; -} diff --git a/opendc-web/opendc-web-ui-quarkus-deployment/src/main/java/org/opendc/web/ui/deployment/OpenDCUiConfig.java b/opendc-web/opendc-web-ui-quarkus-deployment/src/main/java/org/opendc/web/ui/deployment/OpenDCUiConfig.java deleted file mode 100644 index 091e60ab..00000000 --- a/opendc-web/opendc-web-ui-quarkus-deployment/src/main/java/org/opendc/web/ui/deployment/OpenDCUiConfig.java +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright (c) 2022 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.web.ui.deployment; - -import io.quarkus.runtime.annotations.ConfigItem; -import io.quarkus.runtime.annotations.ConfigRoot; -import java.util.Optional; - -/** - * Build-time configuration for the OpenDC UI extension. - */ -@ConfigRoot(name = "opendc-ui") -public class OpenDCUiConfig { - /** - * A flag to include the OpenDC UI extension into the build. - */ - @ConfigItem(defaultValue = "true") - boolean include; - - /** - * The base URL of the OpenDC API. - */ - @ConfigItem(defaultValue = "/api") - String apiBaseUrl; - - /** - * Configuration properties for web UI authentication. - */ - AuthConfiguration auth; - - /** - * Sentry DSN. - */ - @ConfigItem - Optional<String> sentryDsn; -} diff --git a/opendc-web/opendc-web-ui-quarkus-deployment/src/main/java/org/opendc/web/ui/deployment/OpenDCUiProcessor.java b/opendc-web/opendc-web-ui-quarkus-deployment/src/main/java/org/opendc/web/ui/deployment/OpenDCUiProcessor.java deleted file mode 100644 index 5733e0db..00000000 --- a/opendc-web/opendc-web-ui-quarkus-deployment/src/main/java/org/opendc/web/ui/deployment/OpenDCUiProcessor.java +++ /dev/null @@ -1,301 +0,0 @@ -/* - * Copyright (c) 2022 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.web.ui.deployment; - -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.ObjectMapper; -import io.quarkus.deployment.annotations.BuildProducer; -import io.quarkus.deployment.annotations.BuildStep; -import io.quarkus.deployment.annotations.ExecutionTime; -import io.quarkus.deployment.annotations.Record; -import io.quarkus.deployment.builditem.FeatureBuildItem; -import io.quarkus.deployment.builditem.ShutdownContextBuildItem; -import io.quarkus.deployment.pkg.builditem.CurateOutcomeBuildItem; -import io.quarkus.maven.dependency.GACT; -import io.quarkus.maven.dependency.ResolvedDependency; -import io.quarkus.paths.PathVisit; -import io.quarkus.vertx.http.deployment.HttpRootPathBuildItem; -import io.quarkus.vertx.http.deployment.RouteBuildItem; -import io.quarkus.vertx.http.deployment.webjar.WebJarBuildItem; -import io.quarkus.vertx.http.deployment.webjar.WebJarResourcesFilter; -import io.quarkus.vertx.http.deployment.webjar.WebJarResultsBuildItem; -import io.vertx.core.Handler; -import io.vertx.ext.web.RoutingContext; -import java.io.*; -import java.util.ArrayList; -import java.util.Iterator; -import java.util.function.BooleanSupplier; -import java.util.function.Function; -import java.util.regex.Matcher; -import java.util.regex.Pattern; -import org.opendc.web.ui.runtime.OpenDCUiRecorder; -import org.opendc.web.ui.runtime.OpenDCUiRuntimeConfig; - -/** - * Build processor for the OpenDC web UI Quarkus extension. - */ -public class OpenDCUiProcessor { - - private static final String FEATURE = "opendc-ui"; - private static final GACT OPENDC_UI_WEBJAR_ARTIFACT_KEY = new GACT("org.opendc.web", "opendc-web-ui", null, "jar"); - private static final String OPENDC_UI_WEBJAR_STATIC_RESOURCES_PATH = "META-INF/resources/opendc-web-ui"; - private static final Pattern PATH_PARAM_PATTERN = Pattern.compile("\\[(\\w+)]"); - - private final ObjectMapper objectMapper = new ObjectMapper(); - - /** - * Provide the {@link FeatureBuildItem} for this Quarkus extension. - */ - @BuildStep(onlyIf = IsIncluded.class) - public FeatureBuildItem feature() { - return new FeatureBuildItem(FEATURE); - } - - /** - * Build the WebJar that is used to serve the Next.js resources. - */ - @BuildStep(onlyIf = IsIncluded.class) - public WebJarBuildItem buildWebJar(OpenDCUiConfig config) { - return WebJarBuildItem.builder() - .artifactKey(OPENDC_UI_WEBJAR_ARTIFACT_KEY) - .root(OPENDC_UI_WEBJAR_STATIC_RESOURCES_PATH) - .onlyCopyNonArtifactFiles(false) - .useDefaultQuarkusBranding(false) - .filter(new InsertVariablesResourcesFilter(config)) - .build(); - } - - /** - * Build the Next.js routes based on the route manifest generated by it. - */ - @BuildStep(onlyIf = IsIncluded.class) - public OpenDCUiRoutingBuildItem buildRoutes(CurateOutcomeBuildItem curateOutcomeBuildItem) throws IOException { - ResolvedDependency dependency = getAppArtifact(curateOutcomeBuildItem, OPENDC_UI_WEBJAR_ARTIFACT_KEY); - PathVisit visit = dependency - .getContentTree() - .apply(OPENDC_UI_WEBJAR_STATIC_RESOURCES_PATH + "/routes-manifest.json", v -> v); - - if (visit == null) { - throw new FileNotFoundException("Cannot find routes-manifest.json"); - } - - JsonNode routeManifest = objectMapper.readTree(visit.getUrl()); - - var pages = new ArrayList<OpenDCUiRoutingBuildItem.Page>(); - for (Iterator<JsonNode> it = routeManifest.get("staticRoutes").elements(); it.hasNext(); ) { - JsonNode route = it.next(); - - String page = route.get("page").asText(); - - // Static routes do not have any path parameters - pages.add(new OpenDCUiRoutingBuildItem.Page(page, page)); - } - - for (Iterator<JsonNode> it = routeManifest.get("dynamicRoutes").elements(); it.hasNext(); ) { - JsonNode route = it.next(); - - String page = route.get("page").asText(); - String path = PATH_PARAM_PATTERN.matcher(page).replaceAll(r -> ":" + r.group(1)); - - pages.add(new OpenDCUiRoutingBuildItem.Page(path, page)); - } - - var redirects = new ArrayList<OpenDCUiRoutingBuildItem.Redirect>(); - for (Iterator<JsonNode> it = routeManifest.get("redirects").elements(); it.hasNext(); ) { - JsonNode redirect = it.next(); - if (redirect.has("internal")) { - continue; - } - - int statusCode = redirect.get("statusCode").asInt(); - String path = redirect.get("source").asText(); - String destination = redirect.get("destination").asText(); - - if (path.isEmpty()) { - path = "/"; - } - - redirects.add(new OpenDCUiRoutingBuildItem.Redirect(path, destination, statusCode)); - } - - var custom404 = routeManifest.get("pages404").asBoolean(); - return new OpenDCUiRoutingBuildItem(pages, redirects, custom404); - } - - /** - * Register the HTTP handles for the OpenDC web UI. - */ - @BuildStep(onlyIf = IsIncluded.class) - @Record(ExecutionTime.RUNTIME_INIT) - public void registerOpenDCUiHandler( - OpenDCUiRecorder recorder, - BuildProducer<RouteBuildItem> routes, - HttpRootPathBuildItem httpRootPathBuildItem, - WebJarResultsBuildItem webJarResultsBuildItem, - OpenDCUiRoutingBuildItem openDCUiBuildItem, - OpenDCUiRuntimeConfig runtimeConfig, - ShutdownContextBuildItem shutdownContext) { - - WebJarResultsBuildItem.WebJarResult result = - webJarResultsBuildItem.byArtifactKey(OPENDC_UI_WEBJAR_ARTIFACT_KEY); - if (result == null) { - return; - } - - String basePath = httpRootPathBuildItem.getRootPath(); - String finalDestination = result.getFinalDestination(); - - /* Construct dynamic routes */ - for (var redirect : openDCUiBuildItem.getRedirects()) { - String destination = - basePath.equals("/") ? redirect.getDestination() : basePath + redirect.getDestination(); - - routes.produce(httpRootPathBuildItem - .routeBuilder() - .route(basePath + redirect.getPath()) - .handler(recorder.redirectHandler(destination, redirect.getStatusCode(), runtimeConfig)) - .build()); - } - - for (var page : openDCUiBuildItem.getPages()) { - routes.produce(httpRootPathBuildItem - .routeBuilder() - .route(basePath + page.getPath()) - .handler(recorder.pageHandler(finalDestination, page.getName(), runtimeConfig)) - .build()); - } - - /* Construct static routes */ - Handler<RoutingContext> staticHandler = recorder.staticHandler( - finalDestination, basePath, result.getWebRootConfigurations(), runtimeConfig, shutdownContext); - - routes.produce(httpRootPathBuildItem - .routeBuilder() - .route("*") - .handler(staticHandler) - .build()); - } - - /** - * A {@link WebJarResourcesFilter} that instantiates the variables in the web jar resource. - */ - private static class InsertVariablesResourcesFilter implements WebJarResourcesFilter { - - private static final String HTML = ".html"; - private static final String CSS = ".css"; - private static final String JS = ".js"; - - private final OpenDCUiConfig config; - - public InsertVariablesResourcesFilter(OpenDCUiConfig config) { - this.config = config; - } - - @Override - public FilterResult apply(String fileName, InputStream stream) throws IOException { - if (stream == null) { - return new FilterResult(null, false); - } - - // Allow replacement of variables in HTML, CSS, and JavaScript files - if (fileName.endsWith(HTML) || fileName.endsWith(CSS) || fileName.endsWith(JS)) { - byte[] oldContentBytes = stream.readAllBytes(); - String oldContents = new String(oldContentBytes); - String contents = substituteVariables(oldContents, this::substitute); - - boolean changed = contents.length() != oldContents.length() || !contents.equals(oldContents); - if (changed) { - return new FilterResult(new ByteArrayInputStream(contents.getBytes()), true); - } else { - return new FilterResult(new ByteArrayInputStream(oldContentBytes), false); - } - } - - return new FilterResult(stream, false); - } - - private String substitute(String var) { - switch (var) { - case "NEXT_PUBLIC_API_BASE_URL": - return config.apiBaseUrl; - case "NEXT_PUBLIC_SENTRY_DSN": - return config.sentryDsn.orElse(""); - case "NEXT_PUBLIC_AUTH0_DOMAIN": - return config.auth.domain.orElse(""); - case "NEXT_PUBLIC_AUTH0_CLIENT_ID": - return config.auth.clientId.orElse(""); - case "NEXT_PUBLIC_AUTH0_AUDIENCE": - return config.auth.audience.orElse(""); - default: - return null; - } - } - - /** - * Pattern to match variables in the OpenDC web UI sources specified using the following format: "%%VAR_NAME%%". - * <p> - * Be aware that to properly handle Next.js base path, we need to also match a possible forward slash in front - * of the variable. - */ - private static final Pattern PATTERN = Pattern.compile("%%(\\w+)%%"); - - /** - * Helper method to substitute variables in the OpenDC web UI. - */ - private static String substituteVariables(String contents, Function<String, String> substitute) { - Matcher m = PATTERN.matcher(contents); - StringBuilder sb = new StringBuilder(); - - while (m.find()) { - String group = m.group(1); - String val = substitute.apply(group); - m.appendReplacement(sb, val != null ? val : group); - } - - m.appendTail(sb); - return sb.toString(); - } - } - - /** - * A {@link BooleanSupplier} to determine if the OpenDC web UI extension should be included. - */ - private static class IsIncluded implements BooleanSupplier { - OpenDCUiConfig config; - - @Override - public boolean getAsBoolean() { - return config.include; - } - } - - private static ResolvedDependency getAppArtifact(CurateOutcomeBuildItem curateOutcomeBuildItem, GACT artifactKey) { - for (ResolvedDependency dep : - curateOutcomeBuildItem.getApplicationModel().getDependencies()) { - if (dep.getKey().equals(artifactKey)) { - return dep; - } - } - throw new RuntimeException("Could not find artifact " + artifactKey + " among the application dependencies"); - } -} diff --git a/opendc-web/opendc-web-ui-quarkus-deployment/src/main/java/org/opendc/web/ui/deployment/OpenDCUiRoutingBuildItem.java b/opendc-web/opendc-web-ui-quarkus-deployment/src/main/java/org/opendc/web/ui/deployment/OpenDCUiRoutingBuildItem.java deleted file mode 100644 index 6cf44893..00000000 --- a/opendc-web/opendc-web-ui-quarkus-deployment/src/main/java/org/opendc/web/ui/deployment/OpenDCUiRoutingBuildItem.java +++ /dev/null @@ -1,118 +0,0 @@ -/* - * Copyright (c) 2022 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.web.ui.deployment; - -import io.quarkus.builder.item.SimpleBuildItem; -import java.util.List; - -/** - * Build item containing the routes for the OpenDC web UI. - */ -public final class OpenDCUiRoutingBuildItem extends SimpleBuildItem { - - private final boolean custom404; - private final List<Page> pages; - private final List<Redirect> redirects; - - /** - * Construct a {@link OpenDCUiRoutingBuildItem} instance. - * - * @param routes The routes defined by Next.js. - * @param redirects The redirects that have been defined by Next.js. - * @param custom404 A flag to indicate that custom 404 pages are enabled. - */ - public OpenDCUiRoutingBuildItem(List<Page> routes, List<Redirect> redirects, boolean custom404) { - this.custom404 = custom404; - this.pages = routes; - this.redirects = redirects; - } - - public List<Page> getPages() { - return pages; - } - - public List<Redirect> getRedirects() { - return redirects; - } - - public boolean hasCustom404() { - return this.custom404; - } - - /** - * A redirect defined by the Next.js routes manifest. - */ - public static final class Redirect { - - private final String path; - private final String destination; - private final int statusCode; - - /** - * Construct a {@link Redirect} route. - * - * @param path The path that should result in a redirect. - * @param destination The destination of the redirect. - * @param statusCode The status code of the redirect. - */ - public Redirect(String path, String destination, int statusCode) { - this.statusCode = statusCode; - this.path = path; - this.destination = destination; - } - - public String getPath() { - return path; - } - - public String getDestination() { - return destination; - } - - public int getStatusCode() { - return statusCode; - } - } - - /** - * A page defined by the Next.js routes manifest. - */ - public static final class Page { - - private final String path; - private final String name; - - public Page(String path, String page) { - this.path = path; - this.name = page; - } - - public String getPath() { - return path; - } - - public String getName() { - return name; - } - } -} diff --git a/opendc-web/opendc-web-ui-quarkus/build.gradle.kts b/opendc-web/opendc-web-ui-quarkus/build.gradle.kts deleted file mode 100644 index 3f8887f6..00000000 --- a/opendc-web/opendc-web-ui-quarkus/build.gradle.kts +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright (c) 2022 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -description = "Quarkus extension for serving OpenDC web interface" - -plugins { - `java-library-conventions` - id("io.quarkus.extension") -} - -quarkusExtension { - deploymentModule.set("opendc-web-ui-quarkus-deployment") -} - -dependencies { - implementation(platform(libs.quarkus.bom)) - - implementation(libs.quarkus.core.runtime) - implementation(libs.quarkus.vertx.http.runtime) - implementation(libs.quarkus.arc.runtime) -} - -evaluationDependsOn(projects.opendcWeb.opendcWebUiQuarkusDeployment.dependencyProject.path) diff --git a/opendc-web/opendc-web-ui-quarkus/src/main/java/org/opendc/web/ui/runtime/OpenDCUiRecorder.java b/opendc-web/opendc-web-ui-quarkus/src/main/java/org/opendc/web/ui/runtime/OpenDCUiRecorder.java deleted file mode 100644 index 03fedd4f..00000000 --- a/opendc-web/opendc-web-ui-quarkus/src/main/java/org/opendc/web/ui/runtime/OpenDCUiRecorder.java +++ /dev/null @@ -1,98 +0,0 @@ -/* - * Copyright (c) 2022 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.web.ui.runtime; - -import io.quarkus.runtime.ShutdownContext; -import io.quarkus.runtime.annotations.Recorder; -import io.quarkus.vertx.http.runtime.devmode.FileSystemStaticHandler; -import io.quarkus.vertx.http.runtime.webjar.WebJarNotFoundHandler; -import io.quarkus.vertx.http.runtime.webjar.WebJarStaticHandler; -import io.vertx.core.Handler; -import io.vertx.ext.web.RoutingContext; -import java.util.List; -import java.util.stream.Collectors; - -/** - * Helper class for serving the OpenDC web interface. - */ -@Recorder -public class OpenDCUiRecorder { - /** - * Construct a {@link Handler} for serving a page of the OpenDC web interface. - */ - public Handler<RoutingContext> pageHandler( - String finalDestination, String page, OpenDCUiRuntimeConfig runtimeConfig) { - if (runtimeConfig.enable) { - String pageDirectory = finalDestination + "/pages"; - return (event) -> { - event.response().setStatusCode(200).sendFile(pageDirectory + page + ".html"); - }; - } - - return new WebJarNotFoundHandler(); - } - - /** - * Construct a {@link Handler} for handling redirects in the OpenDC web interface. - */ - public Handler<RoutingContext> redirectHandler( - String destination, int statusCode, OpenDCUiRuntimeConfig runtimeConfig) { - if (runtimeConfig.enable) { - return (event) -> { - String query = event.request().query(); - String fullDestination = query != null ? destination + "?" + query : destination; - - event.response() - .setStatusCode(statusCode) - .putHeader("Location", fullDestination) - .end(); - }; - } - - return new WebJarNotFoundHandler(); - } - - /** - * Construct a {@link Handler} for serving the static files of the OpenDC web interface. - */ - public Handler<RoutingContext> staticHandler( - String finalDestination, - String path, - List<FileSystemStaticHandler.StaticWebRootConfiguration> webRootConfigurations, - OpenDCUiRuntimeConfig runtimeConfig, - ShutdownContext shutdownContext) { - if (runtimeConfig.enable) { - var augmentedWebRootConfigurations = webRootConfigurations.stream() - .map(c -> new FileSystemStaticHandler.StaticWebRootConfiguration( - c.getFileSystem(), c.getWebRoot().isEmpty() ? "static" : c.getWebRoot() + "/static")) - .collect(Collectors.toList()); - - WebJarStaticHandler handler = - new WebJarStaticHandler(finalDestination + "/static", path, augmentedWebRootConfigurations); - shutdownContext.addShutdownTask(new ShutdownContext.CloseRunnable(handler)); - return handler; - } - - return new WebJarNotFoundHandler(); - } -} diff --git a/opendc-web/opendc-web-ui-quarkus/src/main/java/org/opendc/web/ui/runtime/OpenDCUiRuntimeConfig.java b/opendc-web/opendc-web-ui-quarkus/src/main/java/org/opendc/web/ui/runtime/OpenDCUiRuntimeConfig.java deleted file mode 100644 index 8ae3b6a2..00000000 --- a/opendc-web/opendc-web-ui-quarkus/src/main/java/org/opendc/web/ui/runtime/OpenDCUiRuntimeConfig.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright (c) 2022 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -package org.opendc.web.ui.runtime; - -import io.quarkus.runtime.annotations.ConfigItem; -import io.quarkus.runtime.annotations.ConfigPhase; -import io.quarkus.runtime.annotations.ConfigRoot; - -/** - * Configuration for the OpenDC web UI. - */ -@ConfigRoot(phase = ConfigPhase.RUN_TIME, name = "opendc-ui") -public class OpenDCUiRuntimeConfig { - /** - * Flag to indicate whether the web interface should be served by the OpenDC API server. - */ - @ConfigItem(defaultValue = "true") - public boolean enable; -} diff --git a/opendc-web/opendc-web-ui-quarkus/src/main/resources/META-INF/quarkus-extension.yaml b/opendc-web/opendc-web-ui-quarkus/src/main/resources/META-INF/quarkus-extension.yaml deleted file mode 100644 index 581a1779..00000000 --- a/opendc-web/opendc-web-ui-quarkus/src/main/resources/META-INF/quarkus-extension.yaml +++ /dev/null @@ -1,5 +0,0 @@ ---- -name: "OpenDC Web UI" -metadata: - status: "preview" - unlisted: true diff --git a/opendc-web/opendc-web-ui/.dockerignore b/opendc-web/opendc-web-ui/.dockerignore deleted file mode 100644 index b91894f6..00000000 --- a/opendc-web/opendc-web-ui/.dockerignore +++ /dev/null @@ -1,9 +0,0 @@ -Dockerfile - -.idea/ -**/out -*.iml -.idea_modules/ - -node_modules -build diff --git a/opendc-web/opendc-web-ui/.eslintrc b/opendc-web/opendc-web-ui/.eslintrc deleted file mode 100644 index 1446fa02..00000000 --- a/opendc-web/opendc-web-ui/.eslintrc +++ /dev/null @@ -1,16 +0,0 @@ -{ - "extends": ["next", "eslint:recommended"], - "env": { - "browser": true, - "node": true, - "es6": true - }, - "overrides": [ - { - "files": ["src/**/*.test.js"], - "env": { - "jest": true - } - } - ] -} diff --git a/opendc-web/opendc-web-ui/.gitignore b/opendc-web/opendc-web-ui/.gitignore deleted file mode 100644 index 54eada4c..00000000 --- a/opendc-web/opendc-web-ui/.gitignore +++ /dev/null @@ -1,27 +0,0 @@ -# Dependencies -/node_modules - -# Testing -/coverage - -# Production -/build - -# Misc -.DS_Store -.env.local -.env.development.local -.env.test.local -.env.production.local - -npm-debug.log* -yarn-debug.log* -yarn-error.log* - -# IntelliJ IDEA -/.idea - -# Environment variables -.env.local - -/.next diff --git a/opendc-web/opendc-web-ui/.prettierrc.yaml b/opendc-web/opendc-web-ui/.prettierrc.yaml deleted file mode 100644 index 9a2b9a95..00000000 --- a/opendc-web/opendc-web-ui/.prettierrc.yaml +++ /dev/null @@ -1,5 +0,0 @@ -trailingComma: "es5" -tabWidth: 4 -semi: false -singleQuote: true -printWidth: 120 diff --git a/opendc-web/opendc-web-ui/Dockerfile b/opendc-web/opendc-web-ui/Dockerfile deleted file mode 100644 index 24ca2b3e..00000000 --- a/opendc-web/opendc-web-ui/Dockerfile +++ /dev/null @@ -1,36 +0,0 @@ -FROM node:18-slim AS staging -MAINTAINER OpenDC Maintainers <opendc@atlarge-research.com> - -# Copy package details -COPY ./package.json ./package-lock.json /opendc/ -RUN cd /opendc && npm ci - -# Build frontend -FROM node:18-slim AS build - -COPY ./ /opendc -COPY --from=staging /opendc/node_modules /opendc/node_modules -RUN cd /opendc/ \ - # Environmental variables that will be substituted during image runtime - && export NEXT_PUBLIC_API_BASE_URL="%%NEXT_PUBLIC_API_BASE_URL%%" \ - NEXT_PUBLIC_SENTRY_DSN="%%NEXT_PUBLIC_SENTRY_DSN%%" \ - NEXT_PUBLIC_AUTH0_DOMAIN="%%NEXT_PUBLIC_AUTH0_DOMAIN%%" \ - NEXT_PUBLIC_AUTH0_CLIENT_ID="%%NEXT_PUBLIC_AUTH0_CLIENT_ID%%" \ - NEXT_PUBLIC_AUTH0_AUDIENCE="%%NEXT_PUBLIC_AUTH0_AUDIENCE%%" \ - && npm run build \ - && npm cache clean --force \ - && mv build/next build/next.template - - -FROM node:18-slim -COPY --from=build /opendc /opendc -WORKDIR /opendc -CMD ./scripts/envsubst.sh; npm run start - -LABEL org.opencontainers.image.authors="OpenDC Maintainers <opendc@atlarge-research.com>" -LABEL org.opencontainers.image.url="https://opendc.org" -LABEL org.opencontainers.image.documentation="https://opendc.org" -LABEL org.opencontainers.image.source="https://github.com/atlarge-research/opendc" -LABEL org.opencontainers.image.title="OpenDC Web UI" -LABEL org.opencontainers.image.description="OpenDC Web UI Docker Image" -LABEL org.opencontainers.image.vendor="AtLarge Research" diff --git a/opendc-web/opendc-web-ui/README.md b/opendc-web/opendc-web-ui/README.md deleted file mode 100644 index a1133475..00000000 --- a/opendc-web/opendc-web-ui/README.md +++ /dev/null @@ -1,106 +0,0 @@ -# OpenDC Web UI - -The user-facing component of the OpenDC stack, allowing users to build and interact with their own (virtual) -datacenters. Built in *React.js* and *Redux*, with the help of [Next.js](https://nextjs.org/). - -## Architecture - -The codebase follows a standard React.js structure, with static assets being contained in the `public` folder, while -dynamic components and their styles are contained in `src`. - -### Pages - -All pages are represented by a component in the `src/pages` directory, following -the [Next.js conventions](https://nextjs.org/docs/routing/introduction) for routing. There are components for the -following pages: - -1. **index.js** - Entry page (`/`) -2. **projects/index.js** - Overview of projects of the user (`/projects`) -3. **projects/[project]/index.js** - Main application, with datacenter construction and simulation UI (`/projects/:projectId` -and `/projects/:projectId/portfolios/:portfolioId`) -4. **profile.js** - Profile of the current user (`/profile`) -5. **404.js** - 404 page to appear when the route is invalid (`/*`) - -### Components & Containers - -The building blocks of the UI are divided into so-called *components* and *containers* -([as encouraged](https://medium.com/@dan_abramov/smart-and-dumb-components-7ca2f9a7c7d0) by the author of Redux). -*Components* are considered 'pure', rendered as a function of input properties. *Containers*, on the other hand, -are wrappers around *components*, injecting state through the properties of the components they wrap. - -Even the canvas (the main component of the app) is built using React components, with the help of the `react-konva` -module. To illustrate: A rectangular object on the canvas is defined in a way that is not very different from how we -define a standard `div` element on the splash page. - -### API Interaction - -The web-app needs to pull data in from the API of a backend running on a server. The functions that call routes are -located in `src/api`. The actual logic responsible for calling these functions is contained in `src/data`. - -### State Management - -State for the topology editor is managed via a Redux store. State is kept there in an immutable form, only to be modified through -actions being dispatched. These actions are contained in the `src/actions` folder, and the reducers (managing how state -is updated according to dispatched actions) are located in `src/reducers`. If you're not familiar with the Redux -approach to state management, have a look at their [official documentation](https://redux.js.org/). - -## Running the development server - -Before we can start the development server, create a file called `.env` in this directory and specify the base URL of -the API that the React frontend will communicate with. For instance, if you run the OpenDC development server: - -``` -NEXT_PUBLIC_API_BASE_URL=http://localhost:8080/api -``` - -Now, you're ready to start the Next.js development server. Run the following command in the root of the repository -(that is, two levels up where the `gradlew` file is located): - -```bash -./gradlew :opendc-web:opendc-web-ui:nextDev -``` - -This will start a development server running on [`localhost:3000`](http://localhost:3000), watching for changes you make -to the code and rebuilding automatically when you save changes. - -To compile everything for camera-ready deployment, use the following command: - -```bash -./gradlew :opendc-web:opendc-web-ui:build -``` - -You can then run the production server using Next.js as follows: - -```bash -./gradlew :opendc-web:opendc-web-ui:nextStart -``` - -## Tests - -Files containing tests can be recognized by the `.test.js` suffix. They are usually located right next to the source -code they are testing, to make discovery easier. - -### Running all tests - -The following command runs all tests in the codebase using [Jest](https://jest.io). On top of this, it also watches the -code for changes and reruns the tests whenever any file is saved. - -```bash -./gradlew :opendc-web:opendc-web-ui:test -``` - -## Code Quality - -We use [Prettier](https://prettier.io) to ensure the formatting of the JavaScript codebase remains consistent. To format -the files of the codebase according to the preferred coding style, run the following command: - -```bash -./gradlew :opendc-web:opendc-web-ui:prettierFormat -``` - -Furthermore, we also employ [ESLint](https://eslint.org/) (via Next) to detect issues and problematic code in our -codebase. To check for potential issues, run the following command: - -```bash -./gradlew :opendc-web:opendc-web-ui:nextLint -``` diff --git a/opendc-web/opendc-web-ui/build.gradle.kts b/opendc-web/opendc-web-ui/build.gradle.kts deleted file mode 100644 index 777098d4..00000000 --- a/opendc-web/opendc-web-ui/build.gradle.kts +++ /dev/null @@ -1,144 +0,0 @@ -/* - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -import com.github.gradle.node.npm.task.NpmTask - -description = "Web interface for OpenDC" - -plugins { - `java-conventions` - id("com.github.node-gradle.node") -} - -sourceSets { - main { - java.srcDir("src") - } - test { - java.srcDir("test") - } -} - -node { - download.set(true) - version.set(libs.versions.node.get()) -} - -val formatTask = - tasks.register<NpmTask>("prettierFormat") { - group = "formatting" - description = "Use Prettier to format the JavaScript codebase" - - args.set(listOf("run", "format")) - dependsOn(tasks.npmInstall) - inputs.dir("src") - inputs.files("package.json", "next.config.js", ".prettierrc.yaml") - outputs.upToDateWhen { true } - } - -val lintTask = - tasks.register<NpmTask>("nextLint") { - group = "verification" - description = "Use ESLint to check for problems" - - args.set(listOf("run", "lint")) - dependsOn(tasks.npmInstall) - inputs.dir("src") - inputs.files("package.json", "next.config.js", ".eslintrc") - outputs.upToDateWhen { true } - } - -tasks.register<NpmTask>("nextDev") { - group = "build" - description = "Run the Next.js project in development mode" - - args.set(listOf("run", "dev")) - dependsOn(tasks.npmInstall) - inputs.dir(project.fileTree("src")) - inputs.dir("node_modules") - inputs.files("package.json", "next.config.js") - outputs.upToDateWhen { true } -} - -val buildTask = - tasks.register<NpmTask>("nextBuild") { - group = "build" - description = "Build the Next.js project" - - args.set(listOf("run", "build")) - - val env = - listOf( - "NEXT_PUBLIC_API_BASE_URL", - "NEXT_PUBLIC_SENTRY_DSN", - "NEXT_PUBLIC_AUTH0_DOMAIN", - "NEXT_PUBLIC_AUTH0_CLIENT_ID", - "NEXT_PUBLIC_AUTH0_AUDIENCE", - ) - for (envvar in env) { - environment.put(envvar, "%%$envvar%%") - } - - dependsOn(tasks.npmInstall) - inputs.dir(project.fileTree("src")) - inputs.dir("node_modules") - inputs.files("package.json", "next.config.js") - outputs.dir(layout.buildDirectory.dir("next")) - } - -tasks.register<NpmTask>("nextStart") { - group = "build" - description = "Build the Next.js project" - - args.set(listOf("run", "start")) - - dependsOn(buildTask) - dependsOn(tasks.npmInstall) - - inputs.dir(project.fileTree("src")) - inputs.dir("node_modules") - inputs.files("package.json", "next.config.js") - outputs.upToDateWhen { true } -} - -tasks.processResources { - dependsOn(buildTask) - inputs.dir(project.fileTree("public")) - - from(layout.buildDirectory.dir("next")) { - include("routes-manifest.json") - into("META-INF/resources/${project.name}") - } - - from(layout.buildDirectory.dir("next/static")) { - into("META-INF/resources/${project.name}/static/_next/static") - } - - from(layout.buildDirectory.dir("next/server/pages")) { - include("**/*.html") - into("META-INF/resources/${project.name}/pages") - } - - from(project.fileTree("public")) { - into("META-INF/resources/${project.name}/static") - } -} diff --git a/opendc-web/opendc-web-ui/next.config.js b/opendc-web/opendc-web-ui/next.config.js deleted file mode 100644 index f761ba74..00000000 --- a/opendc-web/opendc-web-ui/next.config.js +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -// PatternFly 4 uses global CSS imports in its distribution files. Therefore, -// we need to transpile the modules before we can use them. -const { withGlobalCss } = require('next-global-css') -const { PHASE_DEVELOPMENT_SERVER } = require("next/constants"); -const withConfig = withGlobalCss() - -module.exports = (phase) => withConfig({ - basePath: process.env.NEXT_BASE_PATH && '/' + process.env.NEXT_BASE_PATH, - reactStrictMode: true, - distDir: phase === PHASE_DEVELOPMENT_SERVER ? 'build/next-dev' : 'build/next', - async redirects() { - return [ - { - source: '/', - destination: '/projects', - permanent: false - } - ] - }, - images: { unoptimized: true } -}) diff --git a/opendc-web/opendc-web-ui/package-lock.json b/opendc-web/opendc-web-ui/package-lock.json deleted file mode 100644 index 5d78081e..00000000 --- a/opendc-web/opendc-web-ui/package-lock.json +++ /dev/null @@ -1,4665 +0,0 @@ -{ - "name": "opendc-frontend", - "version": "3.0.0", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "opendc-frontend", - "version": "3.0.0", - "license": "MIT", - "dependencies": { - "@auth0/auth0-react": "^1.12.1", - "@patternfly/react-charts": "^6.94.18", - "@patternfly/react-core": "^4.276.6", - "@patternfly/react-icons": "^4.93.6", - "@patternfly/react-table": "^4.112.39", - "@sentry/react": "^7.45.0", - "@sentry/tracing": "^7.45.0", - "clsx": "^1.2.1", - "immer": "^9.0.21", - "konva": "^8.4.3", - "mathjs": "^11.7.0", - "next": "^13.5.4", - "next-global-css": "^1.3.1", - "normalizr": "^3.6.2", - "prettier": "^2.8.7", - "prop-types": "^15.8.1", - "react": "^18.2.0", - "react-dom": "^18.2.0", - "react-hotkeys-hook": "^4.3.8", - "react-konva": "^18.2.5", - "react-query": "^3.39.3", - "react-redux": "^8.0.5", - "redux": "^4.2.1", - "redux-logger": "^3.0.6", - "redux-saga": "^1.2.3", - "redux-thunk": "^2.4.2", - "svgsaver": "^0.9.0", - "use-resize-observer": "^9.1.0", - "uuid": "^9.0.0", - "victory-errorbar": "^36.6.8" - }, - "devDependencies": { - "eslint": "^8.36.0", - "eslint-config-next": "^13.2.4" - } - }, - "node_modules/@auth0/auth0-react": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/@auth0/auth0-react/-/auth0-react-1.12.1.tgz", - "integrity": "sha512-8+ecK/4rE0AGsxLW2IDcr1oPbT55tuE6cQEzEIOkQjB6QGQxxWMzQy0D4nMKw3JUAc7nYcFVOABNFNbc471n9Q==", - "dependencies": { - "@auth0/auth0-spa-js": "^1.22.6" - }, - "peerDependencies": { - "react": "^16.11.0 || ^17 || ^18", - "react-dom": "^16.11.0 || ^17 || ^18" - } - }, - "node_modules/@auth0/auth0-spa-js": { - "version": "1.22.6", - "resolved": "https://registry.npmjs.org/@auth0/auth0-spa-js/-/auth0-spa-js-1.22.6.tgz", - "integrity": "sha512-iL3O0vWanfKFVgy1J2ZHDPlAUK6EVHWEHWS6mUXwHEuPiK39tjlQtyUKQIJI1F5YsZB75ijGgRWMTawSDXlwCA==", - "dependencies": { - "abortcontroller-polyfill": "^1.7.3", - "browser-tabs-lock": "^1.2.15", - "core-js": "^3.25.4", - "es-cookie": "~1.3.2", - "fast-text-encoding": "^1.0.6", - "promise-polyfill": "^8.2.3", - "unfetch": "^4.2.0" - } - }, - "node_modules/@babel/runtime": { - "version": "7.21.0", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.21.0.tgz", - "integrity": "sha512-xwII0//EObnq89Ji5AKYQaRYiW/nZ3llSv29d49IuxPhKbtJoLP+9QUUZ4nVragQVtaVGeZrpB+ZtG/Pdy/POw==", - "dependencies": { - "regenerator-runtime": "^0.13.11" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/runtime-corejs3": { - "version": "7.19.6", - "resolved": "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.19.6.tgz", - "integrity": "sha512-oWNn1ZlGde7b4i/3tnixpH9qI0bOAACiUs+KEES4UUCnsPjVWFlWdLV/iwJuPC2qp3EowbAqsm+0XqNwnwYhxA==", - "dev": true, - "dependencies": { - "core-js-pure": "^3.25.1", - "regenerator-runtime": "^0.13.4" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@eslint-community/eslint-utils": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", - "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==", - "dev": true, - "dependencies": { - "eslint-visitor-keys": "^3.3.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "peerDependencies": { - "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" - } - }, - "node_modules/@eslint-community/regexpp": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.4.1.tgz", - "integrity": "sha512-BISJ6ZE4xQsuL/FmsyRaiffpq977bMlsKfGHTQrOGFErfByxIe6iZTxPf/00Zon9b9a7iUykfQwejN3s2ZW/Bw==", - "dev": true, - "engines": { - "node": "^12.0.0 || ^14.0.0 || >=16.0.0" - } - }, - "node_modules/@eslint/eslintrc": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.0.1.tgz", - "integrity": "sha512-eFRmABvW2E5Ho6f5fHLqgena46rOj7r7OKHYfLElqcBfGFHHpjBhivyi5+jOEQuSpdc/1phIZJlbC2te+tZNIw==", - "dev": true, - "dependencies": { - "ajv": "^6.12.4", - "debug": "^4.3.2", - "espree": "^9.5.0", - "globals": "^13.19.0", - "ignore": "^5.2.0", - "import-fresh": "^3.2.1", - "js-yaml": "^4.1.0", - "minimatch": "^3.1.2", - "strip-json-comments": "^3.1.1" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/@eslint/js": { - "version": "8.36.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.36.0.tgz", - "integrity": "sha512-lxJ9R5ygVm8ZWgYdUweoq5ownDlJ4upvoWmO4eLxBYHdMo+vZ/Rx0EN6MbKWDJOSUGrqJy2Gt+Dyv/VKml0fjg==", - "dev": true, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - } - }, - "node_modules/@humanwhocodes/config-array": { - "version": "0.11.8", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.8.tgz", - "integrity": "sha512-UybHIJzJnR5Qc/MsD9Kr+RpO2h+/P1GhOwdiLPXK5TWk5sgTdu88bTD9UP+CKbPPh5Rni1u0GjAdYQLemG8g+g==", - "dev": true, - "dependencies": { - "@humanwhocodes/object-schema": "^1.2.1", - "debug": "^4.1.1", - "minimatch": "^3.0.5" - }, - "engines": { - "node": ">=10.10.0" - } - }, - "node_modules/@humanwhocodes/module-importer": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", - "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", - "dev": true, - "engines": { - "node": ">=12.22" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/nzakas" - } - }, - "node_modules/@humanwhocodes/object-schema": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", - "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", - "dev": true - }, - "node_modules/@juggle/resize-observer": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/@juggle/resize-observer/-/resize-observer-3.4.0.tgz", - "integrity": "sha512-dfLbk+PwWvFzSxwk3n5ySL0hfBog779o8h68wK/7/APo/7cgyWp5jcXockbxdk5kFRkbeXWm4Fbi9FrdN381sA==" - }, - "node_modules/@next/env": { - "version": "13.5.4", - "resolved": "https://registry.npmjs.org/@next/env/-/env-13.5.4.tgz", - "integrity": "sha512-LGegJkMvRNw90WWphGJ3RMHMVplYcOfRWf2Be3td3sUa+1AaxmsYyANsA+znrGCBjXJNi4XAQlSoEfUxs/4kIQ==" - }, - "node_modules/@next/eslint-plugin-next": { - "version": "13.2.4", - "resolved": "https://registry.npmjs.org/@next/eslint-plugin-next/-/eslint-plugin-next-13.2.4.tgz", - "integrity": "sha512-ck1lI+7r1mMJpqLNa3LJ5pxCfOB1lfJncKmRJeJxcJqcngaFwylreLP7da6Rrjr6u2gVRTfmnkSkjc80IiQCwQ==", - "dev": true, - "dependencies": { - "glob": "7.1.7" - } - }, - "node_modules/@next/swc-darwin-arm64": { - "version": "13.5.4", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-13.5.4.tgz", - "integrity": "sha512-Df8SHuXgF1p+aonBMcDPEsaahNo2TCwuie7VXED4FVyECvdXfRT9unapm54NssV9tF3OQFKBFOdlje4T43VO0w==", - "cpu": [ - "arm64" - ], - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@next/swc-darwin-x64": { - "version": "13.5.4", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-13.5.4.tgz", - "integrity": "sha512-siPuUwO45PnNRMeZnSa8n/Lye5ZX93IJom9wQRB5DEOdFrw0JjOMu1GINB8jAEdwa7Vdyn1oJ2xGNaQpdQQ9Pw==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@next/swc-linux-arm64-gnu": { - "version": "13.5.4", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-13.5.4.tgz", - "integrity": "sha512-l/k/fvRP/zmB2jkFMfefmFkyZbDkYW0mRM/LB+tH5u9pB98WsHXC0WvDHlGCYp3CH/jlkJPL7gN8nkTQVrQ/2w==", - "cpu": [ - "arm64" - ], - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@next/swc-linux-arm64-musl": { - "version": "13.5.4", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-13.5.4.tgz", - "integrity": "sha512-YYGb7SlLkI+XqfQa8VPErljb7k9nUnhhRrVaOdfJNCaQnHBcvbT7cx/UjDQLdleJcfyg1Hkn5YSSIeVfjgmkTg==", - "cpu": [ - "arm64" - ], - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@next/swc-linux-x64-gnu": { - "version": "13.5.4", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-13.5.4.tgz", - "integrity": "sha512-uE61vyUSClnCH18YHjA8tE1prr/PBFlBFhxBZis4XBRJoR+txAky5d7gGNUIbQ8sZZ7LVkSVgm/5Fc7mwXmRAg==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@next/swc-linux-x64-musl": { - "version": "13.5.4", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-13.5.4.tgz", - "integrity": "sha512-qVEKFYML/GvJSy9CfYqAdUexA6M5AklYcQCW+8JECmkQHGoPxCf04iMh7CPR7wkHyWWK+XLt4Ja7hhsPJtSnhg==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@next/swc-win32-arm64-msvc": { - "version": "13.5.4", - "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-13.5.4.tgz", - "integrity": "sha512-mDSQfqxAlfpeZOLPxLymZkX0hYF3juN57W6vFHTvwKlnHfmh12Pt7hPIRLYIShk8uYRsKPtMTth/EzpwRI+u8w==", - "cpu": [ - "arm64" - ], - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@next/swc-win32-ia32-msvc": { - "version": "13.5.4", - "resolved": "https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-13.5.4.tgz", - "integrity": "sha512-aoqAT2XIekIWoriwzOmGFAvTtVY5O7JjV21giozBTP5c6uZhpvTWRbmHXbmsjZqY4HnEZQRXWkSAppsIBweKqw==", - "cpu": [ - "ia32" - ], - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@next/swc-win32-x64-msvc": { - "version": "13.5.4", - "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-13.5.4.tgz", - "integrity": "sha512-cyRvlAxwlddlqeB9xtPSfNSCRy8BOa4wtMo0IuI9P7Y0XT2qpDrpFKRyZ7kUngZis59mPVla5k8X1oOJ8RxDYg==", - "cpu": [ - "x64" - ], - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@nodelib/fs.scandir": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", - "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", - "dev": true, - "dependencies": { - "@nodelib/fs.stat": "2.0.5", - "run-parallel": "^1.1.9" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@nodelib/fs.stat": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", - "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", - "dev": true, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@nodelib/fs.walk": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", - "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", - "dev": true, - "dependencies": { - "@nodelib/fs.scandir": "2.1.5", - "fastq": "^1.6.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@patternfly/react-charts": { - "version": "6.94.18", - "resolved": "https://registry.npmjs.org/@patternfly/react-charts/-/react-charts-6.94.18.tgz", - "integrity": "sha512-56WxnZYC3blRX41mW67JaPxJ3YhXViLvwGpEsZrYCccla/rTV8JgKK0gjHnqtzPQiVBfpn+3ewOyNCOR5uRoSw==", - "dependencies": { - "@patternfly/react-styles": "^4.92.6", - "@patternfly/react-tokens": "^4.94.6", - "hoist-non-react-statics": "^3.3.0", - "lodash": "^4.17.19", - "tslib": "^2.0.0", - "victory-area": "^36.6.7", - "victory-axis": "^36.6.7", - "victory-bar": "^36.6.7", - "victory-chart": "^36.6.7", - "victory-core": "^36.6.7", - "victory-create-container": "^36.6.7", - "victory-cursor-container": "^36.6.7", - "victory-group": "^36.6.7", - "victory-legend": "^36.6.7", - "victory-line": "^36.6.7", - "victory-pie": "^36.6.7", - "victory-scatter": "^36.6.7", - "victory-stack": "^36.6.7", - "victory-tooltip": "^36.6.7", - "victory-voronoi-container": "^36.6.7", - "victory-zoom-container": "^36.6.7" - }, - "peerDependencies": { - "react": "^16.8 || ^17 || ^18", - "react-dom": "^16.8 || ^17 || ^18" - } - }, - "node_modules/@patternfly/react-core": { - "version": "4.276.6", - "resolved": "https://registry.npmjs.org/@patternfly/react-core/-/react-core-4.276.6.tgz", - "integrity": "sha512-G0K+378jf9jw9g+hCAoKnsAe/UGTRspqPeuAYypF2FgNr+dC7dUpc7/VkNhZBVqSJzUWVEK8NyXcqkfi0IemIg==", - "dependencies": { - "@patternfly/react-icons": "^4.93.6", - "@patternfly/react-styles": "^4.92.6", - "@patternfly/react-tokens": "^4.94.6", - "focus-trap": "6.9.2", - "react-dropzone": "9.0.0", - "tippy.js": "5.1.2", - "tslib": "^2.0.0" - }, - "peerDependencies": { - "react": "^16.8 || ^17 || ^18", - "react-dom": "^16.8 || ^17 || ^18" - } - }, - "node_modules/@patternfly/react-icons": { - "version": "4.93.6", - "resolved": "https://registry.npmjs.org/@patternfly/react-icons/-/react-icons-4.93.6.tgz", - "integrity": "sha512-ZrXegc/81oiuTIeWvoHb3nG/eZODbB4rYmekBEsrbiysyO7m/sUFoi/RLvgFINrRoh6YCJqL5fj06Jg6d7jX1g==", - "peerDependencies": { - "react": "^16.8 || ^17 || ^18", - "react-dom": "^16.8 || ^17 || ^18" - } - }, - "node_modules/@patternfly/react-styles": { - "version": "4.92.6", - "resolved": "https://registry.npmjs.org/@patternfly/react-styles/-/react-styles-4.92.6.tgz", - "integrity": "sha512-b8uQdEReMyeoMzjpMri845QxqtupY/tIFFYfVeKoB2neno8gkcW1RvDdDe62LF88q45OktCwAe/8A99ker10Iw==" - }, - "node_modules/@patternfly/react-table": { - "version": "4.112.39", - "resolved": "https://registry.npmjs.org/@patternfly/react-table/-/react-table-4.112.39.tgz", - "integrity": "sha512-U+hOMgYlbghGH4M5MX+qt0GkVi/ocrGnxDnm11YiS3CtEGsd6Rr0NeqMmk0uoR46Od4Pr5tKuXxZhPP32sCL/w==", - "dependencies": { - "@patternfly/react-core": "^4.276.6", - "@patternfly/react-icons": "^4.93.6", - "@patternfly/react-styles": "^4.92.6", - "@patternfly/react-tokens": "^4.94.6", - "lodash": "^4.17.19", - "tslib": "^2.0.0" - }, - "peerDependencies": { - "react": "^16.8 || ^17 || ^18", - "react-dom": "^16.8 || ^17 || ^18" - } - }, - "node_modules/@patternfly/react-tokens": { - "version": "4.94.6", - "resolved": "https://registry.npmjs.org/@patternfly/react-tokens/-/react-tokens-4.94.6.tgz", - "integrity": "sha512-tm7C6nat+uKMr1hrapis7hS3rN9cr41tpcCKhx6cod6FLU8KwF2Yt5KUxakhIOCEcE/M/EhXhAw/qejp8w0r7Q==" - }, - "node_modules/@pkgr/utils": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/@pkgr/utils/-/utils-2.3.1.tgz", - "integrity": "sha512-wfzX8kc1PMyUILA+1Z/EqoE4UCXGy0iRGMhPwdfae1+f0OXlLqCk+By+aMzgJBzR9AzS4CDizioG6Ss1gvAFJw==", - "dev": true, - "dependencies": { - "cross-spawn": "^7.0.3", - "is-glob": "^4.0.3", - "open": "^8.4.0", - "picocolors": "^1.0.0", - "tiny-glob": "^0.2.9", - "tslib": "^2.4.0" - }, - "engines": { - "node": "^12.20.0 || ^14.18.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/unts" - } - }, - "node_modules/@redux-saga/core": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/@redux-saga/core/-/core-1.2.3.tgz", - "integrity": "sha512-U1JO6ncFBAklFTwoQ3mjAeQZ6QGutsJzwNBjgVLSWDpZTRhobUzuVDS1qH3SKGJD8fvqoaYOjp6XJ3gCmeZWgA==", - "dependencies": { - "@babel/runtime": "^7.6.3", - "@redux-saga/deferred": "^1.2.1", - "@redux-saga/delay-p": "^1.2.1", - "@redux-saga/is": "^1.1.3", - "@redux-saga/symbols": "^1.1.3", - "@redux-saga/types": "^1.2.1", - "redux": "^4.0.4", - "typescript-tuple": "^2.2.1" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/redux-saga" - } - }, - "node_modules/@redux-saga/deferred": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@redux-saga/deferred/-/deferred-1.2.1.tgz", - "integrity": "sha512-cmin3IuuzMdfQjA0lG4B+jX+9HdTgHZZ+6u3jRAOwGUxy77GSlTi4Qp2d6PM1PUoTmQUR5aijlA39scWWPF31g==" - }, - "node_modules/@redux-saga/delay-p": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@redux-saga/delay-p/-/delay-p-1.2.1.tgz", - "integrity": "sha512-MdiDxZdvb1m+Y0s4/hgdcAXntpUytr9g0hpcOO1XFVyyzkrDu3SKPgBFOtHn7lhu7n24ZKIAT1qtKyQjHqRd+w==", - "dependencies": { - "@redux-saga/symbols": "^1.1.3" - } - }, - "node_modules/@redux-saga/is": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@redux-saga/is/-/is-1.1.3.tgz", - "integrity": "sha512-naXrkETG1jLRfVfhOx/ZdLj0EyAzHYbgJWkXbB3qFliPcHKiWbv/ULQryOAEKyjrhiclmr6AMdgsXFyx7/yE6Q==", - "dependencies": { - "@redux-saga/symbols": "^1.1.3", - "@redux-saga/types": "^1.2.1" - } - }, - "node_modules/@redux-saga/symbols": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@redux-saga/symbols/-/symbols-1.1.3.tgz", - "integrity": "sha512-hCx6ZvU4QAEUojETnX8EVg4ubNLBFl1Lps4j2tX7o45x/2qg37m3c6v+kSp8xjDJY+2tJw4QB3j8o8dsl1FDXg==" - }, - "node_modules/@redux-saga/types": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@redux-saga/types/-/types-1.2.1.tgz", - "integrity": "sha512-1dgmkh+3so0+LlBWRhGA33ua4MYr7tUOj+a9Si28vUi0IUFNbff1T3sgpeDJI/LaC75bBYnQ0A3wXjn0OrRNBA==" - }, - "node_modules/@rushstack/eslint-patch": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@rushstack/eslint-patch/-/eslint-patch-1.2.0.tgz", - "integrity": "sha512-sXo/qW2/pAcmT43VoRKOJbDOfV3cYpq3szSVfIThQXNt+E4DfKj361vaAt3c88U5tPUxzEswam7GW48PJqtKAg==", - "dev": true - }, - "node_modules/@sentry-internal/tracing": { - "version": "7.45.0", - "resolved": "https://registry.npmjs.org/@sentry-internal/tracing/-/tracing-7.45.0.tgz", - "integrity": "sha512-0aIDY2OvUX7k2XHaimOlWkboXoQvJ9dEKvfpu0Wh0YxfUTGPa+wplUdg3WVdkk018sq1L11MKmj4MPZyYUvXhw==", - "dependencies": { - "@sentry/core": "7.45.0", - "@sentry/types": "7.45.0", - "@sentry/utils": "7.45.0", - "tslib": "^1.9.3" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@sentry-internal/tracing/node_modules/tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" - }, - "node_modules/@sentry/browser": { - "version": "7.45.0", - "resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-7.45.0.tgz", - "integrity": "sha512-/dUrUwnI34voMj+jSJT7b5Jun+xy1utVyzzwTq3Oc22N+SB17ZOX9svZ4jl1Lu6tVJPVjPyvL6zlcbrbMwqFjg==", - "dependencies": { - "@sentry-internal/tracing": "7.45.0", - "@sentry/core": "7.45.0", - "@sentry/replay": "7.45.0", - "@sentry/types": "7.45.0", - "@sentry/utils": "7.45.0", - "tslib": "^1.9.3" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@sentry/browser/node_modules/tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" - }, - "node_modules/@sentry/core": { - "version": "7.45.0", - "resolved": "https://registry.npmjs.org/@sentry/core/-/core-7.45.0.tgz", - "integrity": "sha512-xJfdTS4lRmHvZI/A5MazdnKhBJFkisKu6G9EGNLlZLre+6W4PH5sb7QX4+xoBdqG7v10Jvdia112vi762ojO2w==", - "dependencies": { - "@sentry/types": "7.45.0", - "@sentry/utils": "7.45.0", - "tslib": "^1.9.3" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@sentry/core/node_modules/tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" - }, - "node_modules/@sentry/react": { - "version": "7.45.0", - "resolved": "https://registry.npmjs.org/@sentry/react/-/react-7.45.0.tgz", - "integrity": "sha512-Dbz85nfvMUikbLHUuIt6fBNPmTvThFn+rWB5KS1NIOJifyWAdpIU3X7yCUJE5xhsUObNLiHlNJlqhaQI4nR1bQ==", - "dependencies": { - "@sentry/browser": "7.45.0", - "@sentry/types": "7.45.0", - "@sentry/utils": "7.45.0", - "hoist-non-react-statics": "^3.3.2", - "tslib": "^1.9.3" - }, - "engines": { - "node": ">=8" - }, - "peerDependencies": { - "react": "15.x || 16.x || 17.x || 18.x" - } - }, - "node_modules/@sentry/react/node_modules/tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" - }, - "node_modules/@sentry/replay": { - "version": "7.45.0", - "resolved": "https://registry.npmjs.org/@sentry/replay/-/replay-7.45.0.tgz", - "integrity": "sha512-smM7FIcFIyKu30BqCl8BzLo1gH/z9WwXdGX6V0fNvHab9fJZ09+xjFn+LmIyo6N8H8jjwsup0+yQ12kiF/ZsEw==", - "dependencies": { - "@sentry/core": "7.45.0", - "@sentry/types": "7.45.0", - "@sentry/utils": "7.45.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/@sentry/tracing": { - "version": "7.45.0", - "resolved": "https://registry.npmjs.org/@sentry/tracing/-/tracing-7.45.0.tgz", - "integrity": "sha512-FsoFmZPzTBGvWeJH73NxSF1ot61Zw3aIZo5XolengiKnRmcrQOFxebtMKBiZ61QBRYGqsm5uT7QB7zITU6Ikgg==", - "dependencies": { - "@sentry-internal/tracing": "7.45.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@sentry/types": { - "version": "7.45.0", - "resolved": "https://registry.npmjs.org/@sentry/types/-/types-7.45.0.tgz", - "integrity": "sha512-iFt7msfUK8LCodFF3RKUyaxy9tJv/gpWhzxUFyNxtuVwlpmd+q6mtsFGn8Af3pbpm8A+MKyz1ebMwXj0PQqknw==", - "engines": { - "node": ">=8" - } - }, - "node_modules/@sentry/utils": { - "version": "7.45.0", - "resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-7.45.0.tgz", - "integrity": "sha512-aTY7qqtNUudd09SH5DVSKMm3iQ6ZeWufduc0I9bPZe6UMM09BDc4KmjmrzRkdQ+VaOmHo7+v+HZKQk5f+AbuTQ==", - "dependencies": { - "@sentry/types": "7.45.0", - "tslib": "^1.9.3" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@sentry/utils/node_modules/tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" - }, - "node_modules/@swc/helpers": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.2.tgz", - "integrity": "sha512-E4KcWTpoLHqwPHLxidpOqQbcrZVgi0rsmmZXUle1jXmJfuIf/UWpczUJ7MZZ5tlxytgJXyp0w4PGkkeLiuIdZw==", - "dependencies": { - "tslib": "^2.4.0" - } - }, - "node_modules/@types/d3-array": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.0.3.tgz", - "integrity": "sha512-Reoy+pKnvsksN0lQUlcH6dOGjRZ/3WRwXR//m+/8lt1BXeI4xyaUZoqULNjyXXRuh0Mj4LNpkCvhUpQlY3X5xQ==" - }, - "node_modules/@types/d3-color": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-3.1.0.tgz", - "integrity": "sha512-HKuicPHJuvPgCD+np6Se9MQvS6OCbJmOjGvylzMJRlDwUXjKTTXs6Pwgk79O09Vj/ho3u1ofXnhFOaEWWPrlwA==" - }, - "node_modules/@types/d3-ease": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@types/d3-ease/-/d3-ease-3.0.0.tgz", - "integrity": "sha512-aMo4eaAOijJjA6uU+GIeW018dvy9+oH5Y2VPPzjjfxevvGQ/oRDs+tfYC9b50Q4BygRR8yE2QCLsrT0WtAVseA==" - }, - "node_modules/@types/d3-interpolate": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-3.0.1.tgz", - "integrity": "sha512-jx5leotSeac3jr0RePOH1KdR9rISG91QIE4Q2PYTu4OymLTZfA3SrnURSLzKH48HmXVUru50b8nje4E79oQSQw==", - "dependencies": { - "@types/d3-color": "*" - } - }, - "node_modules/@types/d3-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@types/d3-path/-/d3-path-3.0.0.tgz", - "integrity": "sha512-0g/A+mZXgFkQxN3HniRDbXMN79K3CdTpLsevj+PXiTcb2hVyvkZUBg37StmgCQkaD84cUJ4uaDAWq7UJOQy2Tg==" - }, - "node_modules/@types/d3-scale": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-4.0.2.tgz", - "integrity": "sha512-Yk4htunhPAwN0XGlIwArRomOjdoBFXC3+kCxK2Ubg7I9shQlVSJy/pG/Ht5ASN+gdMIalpk8TJ5xV74jFsetLA==", - "dependencies": { - "@types/d3-time": "*" - } - }, - "node_modules/@types/d3-shape": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-3.1.0.tgz", - "integrity": "sha512-jYIYxFFA9vrJ8Hd4Se83YI6XF+gzDL1aC5DCsldai4XYYiVNdhtpGbA/GM6iyQ8ayhSp3a148LY34hy7A4TxZA==", - "dependencies": { - "@types/d3-path": "*" - } - }, - "node_modules/@types/d3-time": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-3.0.0.tgz", - "integrity": "sha512-sZLCdHvBUcNby1cB6Fd3ZBrABbjz3v1Vm90nysCQ6Vt7vd6e/h9Lt7SiJUoEX0l4Dzc7P5llKyhqSi1ycSf1Hg==" - }, - "node_modules/@types/d3-timer": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@types/d3-timer/-/d3-timer-3.0.0.tgz", - "integrity": "sha512-HNB/9GHqu7Fo8AQiugyJbv6ZxYz58wef0esl4Mv828w1ZKpAshw/uFWVDUcIB9KKFeFKoxS3cHY07FFgtTRZ1g==" - }, - "node_modules/@types/hoist-non-react-statics": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz", - "integrity": "sha512-iMIqiko6ooLrTh1joXodJK5X9xeEALT1kM5G3ZLhD3hszxBdIEd5C75U834D9mLcINgD4OyZf5uQXjkuYydWvA==", - "dependencies": { - "@types/react": "*", - "hoist-non-react-statics": "^3.3.0" - } - }, - "node_modules/@types/json5": { - "version": "0.0.29", - "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", - "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", - "dev": true - }, - "node_modules/@types/prop-types": { - "version": "15.7.5", - "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.5.tgz", - "integrity": "sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==" - }, - "node_modules/@types/react": { - "version": "18.0.23", - "resolved": "https://registry.npmjs.org/@types/react/-/react-18.0.23.tgz", - "integrity": "sha512-R1wTULtCiJkudAN2DJGoYYySbGtOdzZyUWAACYinKdiQC8auxso4kLDUhQ7AJ2kh3F6A6z4v69U6tNY39hihVQ==", - "dependencies": { - "@types/prop-types": "*", - "@types/scheduler": "*", - "csstype": "^3.0.2" - } - }, - "node_modules/@types/react-reconciler": { - "version": "0.28.2", - "resolved": "https://registry.npmjs.org/@types/react-reconciler/-/react-reconciler-0.28.2.tgz", - "integrity": "sha512-8tu6lHzEgYPlfDf/J6GOQdIc+gs+S2yAqlby3zTsB3SP2svlqTYe5fwZNtZyfactP74ShooP2vvi1BOp9ZemWw==", - "dependencies": { - "@types/react": "*" - } - }, - "node_modules/@types/scheduler": { - "version": "0.16.2", - "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.2.tgz", - "integrity": "sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew==" - }, - "node_modules/@types/use-sync-external-store": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.3.tgz", - "integrity": "sha512-EwmlvuaxPNej9+T4v5AuBPJa2x2UOJVdjCtDHgcDqitUeOtjnJKJ+apYjVcAoBEMjKW1VVFGZLUb5+qqa09XFA==" - }, - "node_modules/@typescript-eslint/parser": { - "version": "5.42.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.42.1.tgz", - "integrity": "sha512-kAV+NiNBWVQDY9gDJDToTE/NO8BHi4f6b7zTsVAJoTkmB/zlfOpiEVBzHOKtlgTndCKe8vj9F/PuolemZSh50Q==", - "dev": true, - "dependencies": { - "@typescript-eslint/scope-manager": "5.42.1", - "@typescript-eslint/types": "5.42.1", - "@typescript-eslint/typescript-estree": "5.42.1", - "debug": "^4.3.4" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@typescript-eslint/scope-manager": { - "version": "5.42.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.42.1.tgz", - "integrity": "sha512-QAZY/CBP1Emx4rzxurgqj3rUinfsh/6mvuKbLNMfJMMKYLRBfweus8brgXF8f64ABkIZ3zdj2/rYYtF8eiuksQ==", - "dev": true, - "dependencies": { - "@typescript-eslint/types": "5.42.1", - "@typescript-eslint/visitor-keys": "5.42.1" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/types": { - "version": "5.42.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.42.1.tgz", - "integrity": "sha512-Qrco9dsFF5lhalz+lLFtxs3ui1/YfC6NdXu+RAGBa8uSfn01cjO7ssCsjIsUs484vny9Xm699FSKwpkCcqwWwA==", - "dev": true, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/typescript-estree": { - "version": "5.42.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.42.1.tgz", - "integrity": "sha512-qElc0bDOuO0B8wDhhW4mYVgi/LZL+igPwXtV87n69/kYC/7NG3MES0jHxJNCr4EP7kY1XVsRy8C/u3DYeTKQmw==", - "dev": true, - "dependencies": { - "@typescript-eslint/types": "5.42.1", - "@typescript-eslint/visitor-keys": "5.42.1", - "debug": "^4.3.4", - "globby": "^11.1.0", - "is-glob": "^4.0.3", - "semver": "^7.3.7", - "tsutils": "^3.21.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@typescript-eslint/visitor-keys": { - "version": "5.42.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.42.1.tgz", - "integrity": "sha512-LOQtSF4z+hejmpUvitPlc4hA7ERGoj2BVkesOcG91HCn8edLGUXbTrErmutmPbl8Bo9HjAvOO/zBKQHExXNA2A==", - "dev": true, - "dependencies": { - "@typescript-eslint/types": "5.42.1", - "eslint-visitor-keys": "^3.3.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/abortcontroller-polyfill": { - "version": "1.7.5", - "resolved": "https://registry.npmjs.org/abortcontroller-polyfill/-/abortcontroller-polyfill-1.7.5.tgz", - "integrity": "sha512-JMJ5soJWP18htbbxJjG7bG6yuI6pRhgJ0scHHTfkUjf6wjP912xZWvM+A4sJK3gqd9E8fcPbDnOefbA9Th/FIQ==" - }, - "node_modules/acorn": { - "version": "8.8.2", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz", - "integrity": "sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==", - "dev": true, - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/acorn-jsx": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", - "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", - "dev": true, - "peerDependencies": { - "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" - } - }, - "node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dev": true, - "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true - }, - "node_modules/aria-query": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-4.2.2.tgz", - "integrity": "sha512-o/HelwhuKpTj/frsOsbNLNgnNGVIFsVP/SW2BSF14gVl7kAfMOJ6/8wUAUvG1R1NHKrfG+2sHZTu0yauT1qBrA==", - "dev": true, - "dependencies": { - "@babel/runtime": "^7.10.2", - "@babel/runtime-corejs3": "^7.10.2" - }, - "engines": { - "node": ">=6.0" - } - }, - "node_modules/array-includes": { - "version": "3.1.6", - "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.6.tgz", - "integrity": "sha512-sgTbLvL6cNnw24FnbaDyjmvddQ2ML8arZsgaJhoABMoplz/4QRhtrYS+alr1BUM1Bwp6dhx8vVCBSLG+StwOFw==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.20.4", - "get-intrinsic": "^1.1.3", - "is-string": "^1.0.7" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/array-union": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", - "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/array.prototype.flat": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.1.tgz", - "integrity": "sha512-roTU0KWIOmJ4DRLmwKd19Otg0/mT3qPNt0Qb3GWW8iObuZXxrjB/pzn0R3hqpRSWg4HCwqx+0vwOnWnvlOyeIA==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.20.4", - "es-shim-unscopables": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/array.prototype.flatmap": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.1.tgz", - "integrity": "sha512-8UGn9O1FDVvMNB0UlLv4voxRMze7+FpHyF5mSMRjWHUMlpoDViniy05870VlxhfgTnLbpuwTzvD76MTtWxB/mQ==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.20.4", - "es-shim-unscopables": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/ast-types-flow": { - "version": "0.0.7", - "resolved": "https://registry.npmjs.org/ast-types-flow/-/ast-types-flow-0.0.7.tgz", - "integrity": "sha512-eBvWn1lvIApYMhzQMsu9ciLfkBY499mFZlNqG+/9WR7PVlroQw0vG30cOQQbaKz3sCEc44TAOu2ykzqXSNnwag==", - "dev": true - }, - "node_modules/attr-accept": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/attr-accept/-/attr-accept-1.1.3.tgz", - "integrity": "sha512-iT40nudw8zmCweivz6j58g+RT33I4KbaIvRUhjNmDwO2WmsQUxFEZZYZ5w3vXe5x5MX9D7mfvA/XaLOZYFR9EQ==", - "dependencies": { - "core-js": "^2.5.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/attr-accept/node_modules/core-js": { - "version": "2.6.12", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.12.tgz", - "integrity": "sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ==", - "deprecated": "core-js@<3.23.3 is no longer maintained and not recommended for usage due to the number of issues. Because of the V8 engine whims, feature detection in old core-js versions could cause a slowdown up to 100x even if nothing is polyfilled. Some versions have web compatibility issues. Please, upgrade your dependencies to the actual version of core-js.", - "hasInstallScript": true - }, - "node_modules/axe-core": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.5.0.tgz", - "integrity": "sha512-4+rr8eQ7+XXS5nZrKcMO/AikHL0hVqy+lHWAnE3xdHl+aguag8SOQ6eEqLexwLNWgXIMfunGuD3ON1/6Kyet0A==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/axobject-query": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-2.2.0.tgz", - "integrity": "sha512-Td525n+iPOOyUQIeBfcASuG6uJsDOITl7Mds5gFyerkWiX7qhUTdYUBlSgNMyVqtSJqwpt1kXGLdUt6SykLMRA==", - "dev": true - }, - "node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" - }, - "node_modules/big-integer": { - "version": "1.6.51", - "resolved": "https://registry.npmjs.org/big-integer/-/big-integer-1.6.51.tgz", - "integrity": "sha512-GPEid2Y9QU1Exl1rpO9B2IPJGHPSupF5GnVIP0blYvNOMer2bTvSWs1jGOUg04hTmu67nmLsQ9TBo1puaotBHg==", - "engines": { - "node": ">=0.6" - } - }, - "node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", - "dev": true, - "dependencies": { - "fill-range": "^7.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/broadcast-channel": { - "version": "3.7.0", - "resolved": "https://registry.npmjs.org/broadcast-channel/-/broadcast-channel-3.7.0.tgz", - "integrity": "sha512-cIAKJXAxGJceNZGTZSBzMxzyOn72cVgPnKx4dc6LRjQgbaJUQqhy5rzL3zbMxkMWsGKkv2hSFkPRMEXfoMZ2Mg==", - "dependencies": { - "@babel/runtime": "^7.7.2", - "detect-node": "^2.1.0", - "js-sha3": "0.8.0", - "microseconds": "0.2.0", - "nano-time": "1.0.0", - "oblivious-set": "1.0.0", - "rimraf": "3.0.2", - "unload": "2.2.0" - } - }, - "node_modules/browser-tabs-lock": { - "version": "1.2.15", - "resolved": "https://registry.npmjs.org/browser-tabs-lock/-/browser-tabs-lock-1.2.15.tgz", - "integrity": "sha512-J8K9vdivK0Di+b8SBdE7EZxDr88TnATing7XoLw6+nFkXMQ6sVBh92K3NQvZlZU91AIkFRi0w3sztk5Z+vsswA==", - "hasInstallScript": true, - "dependencies": { - "lodash": ">=4.17.21" - } - }, - "node_modules/busboy": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz", - "integrity": "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==", - "dependencies": { - "streamsearch": "^1.1.0" - }, - "engines": { - "node": ">=10.16.0" - } - }, - "node_modules/call-bind": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", - "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", - "dev": true, - "dependencies": { - "function-bind": "^1.1.1", - "get-intrinsic": "^1.0.2" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/callsites": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/caniuse-lite": { - "version": "1.0.30001426", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001426.tgz", - "integrity": "sha512-n7cosrHLl8AWt0wwZw/PJZgUg3lV0gk9LMI7ikGJwhyhgsd2Nb65vKvmSexCqq/J7rbH3mFG6yZZiPR5dLPW5A==", - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/caniuse-lite" - } - ] - }, - "node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/client-only": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz", - "integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==" - }, - "node_modules/clsx": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/clsx/-/clsx-1.2.1.tgz", - "integrity": "sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg==", - "engines": { - "node": ">=6" - } - }, - "node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/complex.js": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/complex.js/-/complex.js-2.1.1.tgz", - "integrity": "sha512-8njCHOTtFFLtegk6zQo0kkVX1rngygb/KQI6z1qZxlFI3scluC+LVTCFbrkWjBv4vvLlbQ9t88IPMC6k95VTTg==", - "engines": { - "node": "*" - }, - "funding": { - "type": "patreon", - "url": "https://www.patreon.com/infusion" - } - }, - "node_modules/computed-styles": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/computed-styles/-/computed-styles-1.1.2.tgz", - "integrity": "sha512-CGbti1B791SKg6goVX0cSI++hFBSzY9+7+lhX8lqXDI5FHexluglI1cPtvIifS4mEcWxPJ+HKYPr2t6nqz7PxA==" - }, - "node_modules/concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" - }, - "node_modules/core-js": { - "version": "3.29.1", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.29.1.tgz", - "integrity": "sha512-+jwgnhg6cQxKYIIjGtAHq2nwUOolo9eoFZ4sHfUH09BLXBgxnH4gA0zEd+t+BO2cNB8idaBtZFcFTRjQJRJmAw==", - "hasInstallScript": true, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/core-js" - } - }, - "node_modules/core-js-pure": { - "version": "3.26.0", - "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.26.0.tgz", - "integrity": "sha512-LiN6fylpVBVwT8twhhluD9TzXmZQQsr2I2eIKtWNbZI1XMfBT7CV18itaN6RA7EtQd/SDdRx/wzvAShX2HvhQA==", - "dev": true, - "hasInstallScript": true, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/core-js" - } - }, - "node_modules/cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", - "dev": true, - "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/csstype": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.1.tgz", - "integrity": "sha512-DJR/VvkAvSZW9bTouZue2sSxDwdTN92uHjqeKVm+0dAqdfNykRzQ95tay8aXMBAAPpUiq4Qcug2L7neoRh2Egw==" - }, - "node_modules/d3-array": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.0.tgz", - "integrity": "sha512-3yXFQo0oG3QCxbF06rMPFyGRMGJNS7NvsV1+2joOjbBE+9xvWQ8+GcMJAjRCzw06zQ3/arXeJgbPYcjUCuC+3g==", - "dependencies": { - "internmap": "1 - 2" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-color": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz", - "integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==", - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-ease": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz", - "integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==", - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-format": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.0.tgz", - "integrity": "sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA==", - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-interpolate": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz", - "integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==", - "dependencies": { - "d3-color": "1 - 3" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-path": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-3.0.1.tgz", - "integrity": "sha512-gq6gZom9AFZby0YLduxT1qmrp4xpBA1YZr19OI717WIdKE2OM5ETq5qrHLb301IgxhLwcuxvGZVLeeWc/k1I6w==", - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-scale": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz", - "integrity": "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==", - "dependencies": { - "d3-array": "2.10.0 - 3", - "d3-format": "1 - 3", - "d3-interpolate": "1.2.0 - 3", - "d3-time": "2.1.1 - 3", - "d3-time-format": "2 - 4" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-shape": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-3.1.0.tgz", - "integrity": "sha512-tGDh1Muf8kWjEDT/LswZJ8WF85yDZLvVJpYU9Nq+8+yW1Z5enxrmXOhTArlkaElU+CTn0OTVNli+/i+HP45QEQ==", - "dependencies": { - "d3-path": "1 - 3" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-time": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-3.0.0.tgz", - "integrity": "sha512-zmV3lRnlaLI08y9IMRXSDshQb5Nj77smnfpnd2LrBa/2K281Jijactokeak14QacHs/kKq0AQ121nidNYlarbQ==", - "dependencies": { - "d3-array": "2 - 3" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-time-format": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-4.1.0.tgz", - "integrity": "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==", - "dependencies": { - "d3-time": "1 - 3" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-timer": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz", - "integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==", - "engines": { - "node": ">=12" - } - }, - "node_modules/damerau-levenshtein": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz", - "integrity": "sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==", - "dev": true - }, - "node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dev": true, - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/decimal.js": { - "version": "10.4.3", - "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.4.3.tgz", - "integrity": "sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA==" - }, - "node_modules/deep-diff": { - "version": "0.3.8", - "resolved": "https://registry.npmjs.org/deep-diff/-/deep-diff-0.3.8.tgz", - "integrity": "sha512-yVn6RZmHiGnxRKR9sJb3iVV2XTF1Ghh2DiWRZ3dMnGc43yUdWWF/kX6lQyk3+P84iprfWKU/8zFTrlkvtFm1ug==" - }, - "node_modules/deep-is": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", - "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", - "dev": true - }, - "node_modules/define-lazy-prop": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz", - "integrity": "sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/define-properties": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.4.tgz", - "integrity": "sha512-uckOqKcfaVvtBdsVkdPv3XjveQJsNQqmhXgRi8uhvWWuPYZCNlzT8qAyblUgNoXdHdjMTzAqeGjAoli8f+bzPA==", - "dev": true, - "dependencies": { - "has-property-descriptors": "^1.0.0", - "object-keys": "^1.1.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/delaunator": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/delaunator/-/delaunator-4.0.1.tgz", - "integrity": "sha512-WNPWi1IRKZfCt/qIDMfERkDp93+iZEmOxN2yy4Jg+Xhv8SLk2UTqqbe1sfiipn0and9QrE914/ihdx82Y/Giag==" - }, - "node_modules/delaunay-find": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/delaunay-find/-/delaunay-find-0.0.6.tgz", - "integrity": "sha512-1+almjfrnR7ZamBk0q3Nhg6lqSe6Le4vL0WJDSMx4IDbQwTpUTXPjxC00lqLBT8MYsJpPCbI16sIkw9cPsbi7Q==", - "dependencies": { - "delaunator": "^4.0.0" - } - }, - "node_modules/detect-node": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.1.0.tgz", - "integrity": "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==" - }, - "node_modules/dir-glob": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", - "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", - "dev": true, - "dependencies": { - "path-type": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/doctrine": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", - "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", - "dev": true, - "dependencies": { - "esutils": "^2.0.2" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/emoji-regex": { - "version": "9.2.2", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", - "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", - "dev": true - }, - "node_modules/enhanced-resolve": { - "version": "5.12.0", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.12.0.tgz", - "integrity": "sha512-QHTXI/sZQmko1cbDoNAa3mJ5qhWUUNAq3vR0/YiD379fWQrcfuoX1+HW2S0MTt7XmoPLapdaDKUtelUSPic7hQ==", - "dev": true, - "dependencies": { - "graceful-fs": "^4.2.4", - "tapable": "^2.2.0" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/es-abstract": { - "version": "1.20.4", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.20.4.tgz", - "integrity": "sha512-0UtvRN79eMe2L+UNEF1BwRe364sj/DXhQ/k5FmivgoSdpM90b8Jc0mDzKMGo7QS0BVbOP/bTwBKNnDc9rNzaPA==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "es-to-primitive": "^1.2.1", - "function-bind": "^1.1.1", - "function.prototype.name": "^1.1.5", - "get-intrinsic": "^1.1.3", - "get-symbol-description": "^1.0.0", - "has": "^1.0.3", - "has-property-descriptors": "^1.0.0", - "has-symbols": "^1.0.3", - "internal-slot": "^1.0.3", - "is-callable": "^1.2.7", - "is-negative-zero": "^2.0.2", - "is-regex": "^1.1.4", - "is-shared-array-buffer": "^1.0.2", - "is-string": "^1.0.7", - "is-weakref": "^1.0.2", - "object-inspect": "^1.12.2", - "object-keys": "^1.1.1", - "object.assign": "^4.1.4", - "regexp.prototype.flags": "^1.4.3", - "safe-regex-test": "^1.0.0", - "string.prototype.trimend": "^1.0.5", - "string.prototype.trimstart": "^1.0.5", - "unbox-primitive": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/es-cookie": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/es-cookie/-/es-cookie-1.3.2.tgz", - "integrity": "sha512-UTlYYhXGLOy05P/vKVT2Ui7WtC7NiRzGtJyAKKn32g5Gvcjn7KAClLPWlipCtxIus934dFg9o9jXiBL0nP+t9Q==" - }, - "node_modules/es-shim-unscopables": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.0.0.tgz", - "integrity": "sha512-Jm6GPcCdC30eMLbZ2x8z2WuRwAws3zTBBKuusffYVUrNj/GVSUAZ+xKMaUpfNDR5IbyNA5LJbaecoUVbmUcB1w==", - "dev": true, - "dependencies": { - "has": "^1.0.3" - } - }, - "node_modules/es-to-primitive": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", - "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", - "dev": true, - "dependencies": { - "is-callable": "^1.1.4", - "is-date-object": "^1.0.1", - "is-symbol": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/escape-latex": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/escape-latex/-/escape-latex-1.2.0.tgz", - "integrity": "sha512-nV5aVWW1K0wEiUIEdZ4erkGGH8mDxGyxSeqPzRNtWP7ataw+/olFObw7hujFWlVjNsaDFw5VZ5NzVSIqRgfTiw==" - }, - "node_modules/escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/eslint": { - "version": "8.36.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.36.0.tgz", - "integrity": "sha512-Y956lmS7vDqomxlaaQAHVmeb4tNMp2FWIvU/RnU5BD3IKMD/MJPr76xdyr68P8tV1iNMvN2mRK0yy3c+UjL+bw==", - "dev": true, - "dependencies": { - "@eslint-community/eslint-utils": "^4.2.0", - "@eslint-community/regexpp": "^4.4.0", - "@eslint/eslintrc": "^2.0.1", - "@eslint/js": "8.36.0", - "@humanwhocodes/config-array": "^0.11.8", - "@humanwhocodes/module-importer": "^1.0.1", - "@nodelib/fs.walk": "^1.2.8", - "ajv": "^6.10.0", - "chalk": "^4.0.0", - "cross-spawn": "^7.0.2", - "debug": "^4.3.2", - "doctrine": "^3.0.0", - "escape-string-regexp": "^4.0.0", - "eslint-scope": "^7.1.1", - "eslint-visitor-keys": "^3.3.0", - "espree": "^9.5.0", - "esquery": "^1.4.2", - "esutils": "^2.0.2", - "fast-deep-equal": "^3.1.3", - "file-entry-cache": "^6.0.1", - "find-up": "^5.0.0", - "glob-parent": "^6.0.2", - "globals": "^13.19.0", - "grapheme-splitter": "^1.0.4", - "ignore": "^5.2.0", - "import-fresh": "^3.0.0", - "imurmurhash": "^0.1.4", - "is-glob": "^4.0.0", - "is-path-inside": "^3.0.3", - "js-sdsl": "^4.1.4", - "js-yaml": "^4.1.0", - "json-stable-stringify-without-jsonify": "^1.0.1", - "levn": "^0.4.1", - "lodash.merge": "^4.6.2", - "minimatch": "^3.1.2", - "natural-compare": "^1.4.0", - "optionator": "^0.9.1", - "strip-ansi": "^6.0.1", - "strip-json-comments": "^3.1.0", - "text-table": "^0.2.0" - }, - "bin": { - "eslint": "bin/eslint.js" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/eslint-config-next": { - "version": "13.2.4", - "resolved": "https://registry.npmjs.org/eslint-config-next/-/eslint-config-next-13.2.4.tgz", - "integrity": "sha512-lunIBhsoeqw6/Lfkd6zPt25w1bn0znLA/JCL+au1HoEpSb4/PpsOYsYtgV/q+YPsoKIOzFyU5xnb04iZnXjUvg==", - "dev": true, - "dependencies": { - "@next/eslint-plugin-next": "13.2.4", - "@rushstack/eslint-patch": "^1.1.3", - "@typescript-eslint/parser": "^5.42.0", - "eslint-import-resolver-node": "^0.3.6", - "eslint-import-resolver-typescript": "^3.5.2", - "eslint-plugin-import": "^2.26.0", - "eslint-plugin-jsx-a11y": "^6.5.1", - "eslint-plugin-react": "^7.31.7", - "eslint-plugin-react-hooks": "^4.5.0" - }, - "peerDependencies": { - "eslint": "^7.23.0 || ^8.0.0", - "typescript": ">=3.3.1" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/eslint-import-resolver-node": { - "version": "0.3.7", - "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.7.tgz", - "integrity": "sha512-gozW2blMLJCeFpBwugLTGyvVjNoeo1knonXAcatC6bjPBZitotxdWf7Gimr25N4c0AAOo4eOUfaG82IJPDpqCA==", - "dev": true, - "dependencies": { - "debug": "^3.2.7", - "is-core-module": "^2.11.0", - "resolve": "^1.22.1" - } - }, - "node_modules/eslint-import-resolver-node/node_modules/debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "dev": true, - "dependencies": { - "ms": "^2.1.1" - } - }, - "node_modules/eslint-import-resolver-typescript": { - "version": "3.5.3", - "resolved": "https://registry.npmjs.org/eslint-import-resolver-typescript/-/eslint-import-resolver-typescript-3.5.3.tgz", - "integrity": "sha512-njRcKYBc3isE42LaTcJNVANR3R99H9bAxBDMNDr2W7yq5gYPxbU3MkdhsQukxZ/Xg9C2vcyLlDsbKfRDg0QvCQ==", - "dev": true, - "dependencies": { - "debug": "^4.3.4", - "enhanced-resolve": "^5.10.0", - "get-tsconfig": "^4.2.0", - "globby": "^13.1.2", - "is-core-module": "^2.10.0", - "is-glob": "^4.0.3", - "synckit": "^0.8.4" - }, - "engines": { - "node": "^14.18.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/unts/projects/eslint-import-resolver-ts" - }, - "peerDependencies": { - "eslint": "*", - "eslint-plugin-import": "*" - } - }, - "node_modules/eslint-import-resolver-typescript/node_modules/globby": { - "version": "13.1.3", - "resolved": "https://registry.npmjs.org/globby/-/globby-13.1.3.tgz", - "integrity": "sha512-8krCNHXvlCgHDpegPzleMq07yMYTO2sXKASmZmquEYWEmCx6J5UTRbp5RwMJkTJGtcQ44YpiUYUiN0b9mzy8Bw==", - "dev": true, - "dependencies": { - "dir-glob": "^3.0.1", - "fast-glob": "^3.2.11", - "ignore": "^5.2.0", - "merge2": "^1.4.1", - "slash": "^4.0.0" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/eslint-import-resolver-typescript/node_modules/slash": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-4.0.0.tgz", - "integrity": "sha512-3dOsAHXXUkQTpOYcoAxLIorMTp4gIQr5IW3iVb7A7lFIp0VHhnynm9izx6TssdrIcVIESAlVjtnO2K8bg+Coew==", - "dev": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/eslint-module-utils": { - "version": "2.7.4", - "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.7.4.tgz", - "integrity": "sha512-j4GT+rqzCoRKHwURX7pddtIPGySnX9Si/cgMI5ztrcqOPtk5dDEeZ34CQVPphnqkJytlc97Vuk05Um2mJ3gEQA==", - "dev": true, - "dependencies": { - "debug": "^3.2.7" - }, - "engines": { - "node": ">=4" - }, - "peerDependenciesMeta": { - "eslint": { - "optional": true - } - } - }, - "node_modules/eslint-module-utils/node_modules/debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "dev": true, - "dependencies": { - "ms": "^2.1.1" - } - }, - "node_modules/eslint-plugin-import": { - "version": "2.27.5", - "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.27.5.tgz", - "integrity": "sha512-LmEt3GVofgiGuiE+ORpnvP+kAm3h6MLZJ4Q5HCyHADofsb4VzXFsRiWj3c0OFiV+3DWFh0qg3v9gcPlfc3zRow==", - "dev": true, - "dependencies": { - "array-includes": "^3.1.6", - "array.prototype.flat": "^1.3.1", - "array.prototype.flatmap": "^1.3.1", - "debug": "^3.2.7", - "doctrine": "^2.1.0", - "eslint-import-resolver-node": "^0.3.7", - "eslint-module-utils": "^2.7.4", - "has": "^1.0.3", - "is-core-module": "^2.11.0", - "is-glob": "^4.0.3", - "minimatch": "^3.1.2", - "object.values": "^1.1.6", - "resolve": "^1.22.1", - "semver": "^6.3.0", - "tsconfig-paths": "^3.14.1" - }, - "engines": { - "node": ">=4" - }, - "peerDependencies": { - "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8" - } - }, - "node_modules/eslint-plugin-import/node_modules/debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "dev": true, - "dependencies": { - "ms": "^2.1.1" - } - }, - "node_modules/eslint-plugin-import/node_modules/doctrine": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", - "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", - "dev": true, - "dependencies": { - "esutils": "^2.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/eslint-plugin-import/node_modules/semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true, - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/eslint-plugin-jsx-a11y": { - "version": "6.6.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.6.1.tgz", - "integrity": "sha512-sXgFVNHiWffBq23uiS/JaP6eVR622DqwB4yTzKvGZGcPq6/yZ3WmOZfuBks/vHWo9GaFOqC2ZK4i6+C35knx7Q==", - "dev": true, - "dependencies": { - "@babel/runtime": "^7.18.9", - "aria-query": "^4.2.2", - "array-includes": "^3.1.5", - "ast-types-flow": "^0.0.7", - "axe-core": "^4.4.3", - "axobject-query": "^2.2.0", - "damerau-levenshtein": "^1.0.8", - "emoji-regex": "^9.2.2", - "has": "^1.0.3", - "jsx-ast-utils": "^3.3.2", - "language-tags": "^1.0.5", - "minimatch": "^3.1.2", - "semver": "^6.3.0" - }, - "engines": { - "node": ">=4.0" - }, - "peerDependencies": { - "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8" - } - }, - "node_modules/eslint-plugin-jsx-a11y/node_modules/semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true, - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/eslint-plugin-react": { - "version": "7.31.10", - "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.31.10.tgz", - "integrity": "sha512-e4N/nc6AAlg4UKW/mXeYWd3R++qUano5/o+t+wnWxIf+bLsOaH3a4q74kX3nDjYym3VBN4HyO9nEn1GcAqgQOA==", - "dev": true, - "dependencies": { - "array-includes": "^3.1.5", - "array.prototype.flatmap": "^1.3.0", - "doctrine": "^2.1.0", - "estraverse": "^5.3.0", - "jsx-ast-utils": "^2.4.1 || ^3.0.0", - "minimatch": "^3.1.2", - "object.entries": "^1.1.5", - "object.fromentries": "^2.0.5", - "object.hasown": "^1.1.1", - "object.values": "^1.1.5", - "prop-types": "^15.8.1", - "resolve": "^2.0.0-next.3", - "semver": "^6.3.0", - "string.prototype.matchall": "^4.0.7" - }, - "engines": { - "node": ">=4" - }, - "peerDependencies": { - "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8" - } - }, - "node_modules/eslint-plugin-react-hooks": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.6.0.tgz", - "integrity": "sha512-oFc7Itz9Qxh2x4gNHStv3BqJq54ExXmfC+a1NjAta66IAN87Wu0R/QArgIS9qKzX3dXKPI9H5crl9QchNMY9+g==", - "dev": true, - "engines": { - "node": ">=10" - }, - "peerDependencies": { - "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0" - } - }, - "node_modules/eslint-plugin-react/node_modules/doctrine": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", - "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", - "dev": true, - "dependencies": { - "esutils": "^2.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/eslint-plugin-react/node_modules/resolve": { - "version": "2.0.0-next.4", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.4.tgz", - "integrity": "sha512-iMDbmAWtfU+MHpxt/I5iWI7cY6YVEZUQ3MBgPQ++XD1PELuJHIl82xBmObyP2KyQmkNB2dsqF7seoQQiAn5yDQ==", - "dev": true, - "dependencies": { - "is-core-module": "^2.9.0", - "path-parse": "^1.0.7", - "supports-preserve-symlinks-flag": "^1.0.0" - }, - "bin": { - "resolve": "bin/resolve" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/eslint-plugin-react/node_modules/semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true, - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/eslint-scope": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.1.1.tgz", - "integrity": "sha512-QKQM/UXpIiHcLqJ5AOyIW7XZmzjkzQXYE54n1++wb0u9V/abW3l9uQnxX8Z5Xd18xyKIMTUAyQ0k1e8pz6LUrw==", - "dev": true, - "dependencies": { - "esrecurse": "^4.3.0", - "estraverse": "^5.2.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - } - }, - "node_modules/eslint-visitor-keys": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz", - "integrity": "sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==", - "dev": true, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - } - }, - "node_modules/espree": { - "version": "9.5.0", - "resolved": "https://registry.npmjs.org/espree/-/espree-9.5.0.tgz", - "integrity": "sha512-JPbJGhKc47++oo4JkEoTe2wjy4fmMwvFpgJT9cQzmfXKp22Dr6Hf1tdCteLz1h0P3t+mGvWZ+4Uankvh8+c6zw==", - "dev": true, - "dependencies": { - "acorn": "^8.8.0", - "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^3.3.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/esquery": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz", - "integrity": "sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==", - "dev": true, - "dependencies": { - "estraverse": "^5.1.0" - }, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/esrecurse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", - "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", - "dev": true, - "dependencies": { - "estraverse": "^5.2.0" - }, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/esutils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true - }, - "node_modules/fast-glob": { - "version": "3.2.12", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.12.tgz", - "integrity": "sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==", - "dev": true, - "dependencies": { - "@nodelib/fs.stat": "^2.0.2", - "@nodelib/fs.walk": "^1.2.3", - "glob-parent": "^5.1.2", - "merge2": "^1.3.0", - "micromatch": "^4.0.4" - }, - "engines": { - "node": ">=8.6.0" - } - }, - "node_modules/fast-glob/node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/fast-json-stable-stringify": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "dev": true - }, - "node_modules/fast-levenshtein": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", - "dev": true - }, - "node_modules/fast-text-encoding": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/fast-text-encoding/-/fast-text-encoding-1.0.6.tgz", - "integrity": "sha512-VhXlQgj9ioXCqGstD37E/HBeqEGV/qOD/kmbVG8h5xKBYvM1L3lR1Zn4555cQ8GkYbJa8aJSipLPndE1k6zK2w==" - }, - "node_modules/fastq": { - "version": "1.13.0", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.13.0.tgz", - "integrity": "sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw==", - "dev": true, - "dependencies": { - "reusify": "^1.0.4" - } - }, - "node_modules/file-entry-cache": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", - "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", - "dev": true, - "dependencies": { - "flat-cache": "^3.0.4" - }, - "engines": { - "node": "^10.12.0 || >=12.0.0" - } - }, - "node_modules/file-saver": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/file-saver/-/file-saver-1.3.8.tgz", - "integrity": "sha512-spKHSBQIxxS81N/O21WmuXA2F6wppUCsutpzenOeZzOCCJ5gEfcbqJP983IrpLXzYmXnMUa6J03SubcNPdKrlg==" - }, - "node_modules/file-selector": { - "version": "0.1.19", - "resolved": "https://registry.npmjs.org/file-selector/-/file-selector-0.1.19.tgz", - "integrity": "sha512-kCWw3+Aai8Uox+5tHCNgMFaUdgidxvMnLWO6fM5sZ0hA2wlHP5/DHGF0ECe84BiB95qdJbKNEJhWKVDvMN+JDQ==", - "dependencies": { - "tslib": "^2.0.1" - }, - "engines": { - "node": ">= 10" - } - }, - "node_modules/fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", - "dev": true, - "dependencies": { - "to-regex-range": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/find-up": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", - "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", - "dev": true, - "dependencies": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/flat-cache": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", - "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", - "dev": true, - "dependencies": { - "flatted": "^3.1.0", - "rimraf": "^3.0.2" - }, - "engines": { - "node": "^10.12.0 || >=12.0.0" - } - }, - "node_modules/flatted": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.7.tgz", - "integrity": "sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==", - "dev": true - }, - "node_modules/focus-trap": { - "version": "6.9.2", - "resolved": "https://registry.npmjs.org/focus-trap/-/focus-trap-6.9.2.tgz", - "integrity": "sha512-gBEuXOPNOKPrLdZpMFUSTyIo1eT2NSZRrwZ9r/0Jqw5tmT3Yvxfmu8KBHw8xW2XQkw6E/JoG+OlEq7UDtSUNgw==", - "dependencies": { - "tabbable": "^5.3.2" - } - }, - "node_modules/fraction.js": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.2.0.tgz", - "integrity": "sha512-MhLuK+2gUcnZe8ZHlaaINnQLl0xRIGRfcGk2yl8xoQAfHrSsL3rYu6FCmBdkdbhc9EPlwyGHewaRsvwRMJtAlA==", - "engines": { - "node": "*" - }, - "funding": { - "type": "patreon", - "url": "https://www.patreon.com/infusion" - } - }, - "node_modules/fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" - }, - "node_modules/function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", - "dev": true - }, - "node_modules/function.prototype.name": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.5.tgz", - "integrity": "sha512-uN7m/BzVKQnCUF/iW8jYea67v++2u7m5UgENbHRtdDVclOUP+FMPlCNdmk0h/ysGyo2tavMJEDqJAkJdRa1vMA==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3", - "es-abstract": "^1.19.0", - "functions-have-names": "^1.2.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/functions-have-names": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", - "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", - "dev": true, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/get-intrinsic": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.3.tgz", - "integrity": "sha512-QJVz1Tj7MS099PevUG5jvnt9tSkXN8K14dxQlikJuPt4uD9hHAHjLyLBiLR5zELelBdD9QNRAXZzsJx0WaDL9A==", - "dev": true, - "dependencies": { - "function-bind": "^1.1.1", - "has": "^1.0.3", - "has-symbols": "^1.0.3" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/get-symbol-description": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.0.tgz", - "integrity": "sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.1.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/get-tsconfig": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.4.0.tgz", - "integrity": "sha512-0Gdjo/9+FzsYhXCEFueo2aY1z1tpXrxWZzP7k8ul9qt1U5o8rYJwTJYmaeHdrVosYIVYkOy2iwCJ9FdpocJhPQ==", - "dev": true, - "funding": { - "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" - } - }, - "node_modules/glob": { - "version": "7.1.7", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.7.tgz", - "integrity": "sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ==", - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/glob-parent": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", - "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", - "dev": true, - "dependencies": { - "is-glob": "^4.0.3" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/glob-to-regexp": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", - "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==" - }, - "node_modules/globals": { - "version": "13.20.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.20.0.tgz", - "integrity": "sha512-Qg5QtVkCy/kv3FUSlu4ukeZDVf9ee0iXLAUYX13gbR17bnejFTzr4iS9bY7kwCf1NztRNm1t91fjOiyx4CSwPQ==", - "dev": true, - "dependencies": { - "type-fest": "^0.20.2" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/globalyzer": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/globalyzer/-/globalyzer-0.1.0.tgz", - "integrity": "sha512-40oNTM9UfG6aBmuKxk/giHn5nQ8RVz/SS4Ir6zgzOv9/qC3kKZ9v4etGTcJbEl/NyVQH7FGU7d+X1egr57Md2Q==", - "dev": true - }, - "node_modules/globby": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", - "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", - "dev": true, - "dependencies": { - "array-union": "^2.1.0", - "dir-glob": "^3.0.1", - "fast-glob": "^3.2.9", - "ignore": "^5.2.0", - "merge2": "^1.4.1", - "slash": "^3.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/globrex": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/globrex/-/globrex-0.1.2.tgz", - "integrity": "sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg==", - "dev": true - }, - "node_modules/graceful-fs": { - "version": "4.2.11", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", - "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==" - }, - "node_modules/grapheme-splitter": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz", - "integrity": "sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==", - "dev": true - }, - "node_modules/has": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", - "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", - "dev": true, - "dependencies": { - "function-bind": "^1.1.1" - }, - "engines": { - "node": ">= 0.4.0" - } - }, - "node_modules/has-bigints": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz", - "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==", - "dev": true, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/has-property-descriptors": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz", - "integrity": "sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==", - "dev": true, - "dependencies": { - "get-intrinsic": "^1.1.1" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-symbols": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", - "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", - "dev": true, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-tostringtag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz", - "integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==", - "dev": true, - "dependencies": { - "has-symbols": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/hoist-non-react-statics": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", - "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", - "dependencies": { - "react-is": "^16.7.0" - } - }, - "node_modules/ignore": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.0.tgz", - "integrity": "sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ==", - "dev": true, - "engines": { - "node": ">= 4" - } - }, - "node_modules/immer": { - "version": "9.0.21", - "resolved": "https://registry.npmjs.org/immer/-/immer-9.0.21.tgz", - "integrity": "sha512-bc4NBHqOqSfRW7POMkHd51LvClaeMXpm8dx0e8oE2GORbq5aRK7Bxl4FyzVLdGtLmvLKL7BTDBG5ACQm4HWjTA==", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/immer" - } - }, - "node_modules/import-fresh": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", - "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", - "dev": true, - "dependencies": { - "parent-module": "^1.0.0", - "resolve-from": "^4.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", - "dev": true, - "engines": { - "node": ">=0.8.19" - } - }, - "node_modules/inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", - "dependencies": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" - }, - "node_modules/internal-slot": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.3.tgz", - "integrity": "sha512-O0DB1JC/sPyZl7cIo78n5dR7eUSwwpYPiXRhTzNxZVAMUuB8vlnRFyLxdrVToks6XPLVnFfbzaVd5WLjhgg+vA==", - "dev": true, - "dependencies": { - "get-intrinsic": "^1.1.0", - "has": "^1.0.3", - "side-channel": "^1.0.4" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/internmap": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz", - "integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==", - "engines": { - "node": ">=12" - } - }, - "node_modules/is-bigint": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", - "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==", - "dev": true, - "dependencies": { - "has-bigints": "^1.0.1" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-boolean-object": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz", - "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "has-tostringtag": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-callable": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", - "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", - "dev": true, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-core-module": { - "version": "2.11.0", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.11.0.tgz", - "integrity": "sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw==", - "dev": true, - "dependencies": { - "has": "^1.0.3" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-date-object": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz", - "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==", - "dev": true, - "dependencies": { - "has-tostringtag": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-docker": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", - "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", - "dev": true, - "bin": { - "is-docker": "cli.js" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-glob": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dev": true, - "dependencies": { - "is-extglob": "^2.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-negative-zero": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.2.tgz", - "integrity": "sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==", - "dev": true, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true, - "engines": { - "node": ">=0.12.0" - } - }, - "node_modules/is-number-object": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz", - "integrity": "sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==", - "dev": true, - "dependencies": { - "has-tostringtag": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-path-inside": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", - "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/is-regex": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", - "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "has-tostringtag": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-shared-array-buffer": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz", - "integrity": "sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-string": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", - "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", - "dev": true, - "dependencies": { - "has-tostringtag": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-symbol": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz", - "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", - "dev": true, - "dependencies": { - "has-symbols": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-weakref": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz", - "integrity": "sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-wsl": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", - "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", - "dev": true, - "dependencies": { - "is-docker": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true - }, - "node_modules/its-fine": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/its-fine/-/its-fine-1.0.6.tgz", - "integrity": "sha512-VZJZPwVT2kxe5KQv+TxCjojfLiUIut8zXDNLTxcM7gJ/xQ/bSPk5M0neZ+j3myy45KKkltY1mm1jyJgx3Fxsdg==", - "dependencies": { - "@types/react-reconciler": "^0.28.0" - }, - "peerDependencies": { - "react": ">=18.0" - } - }, - "node_modules/javascript-natural-sort": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/javascript-natural-sort/-/javascript-natural-sort-0.7.1.tgz", - "integrity": "sha512-nO6jcEfZWQXDhOiBtG2KvKyEptz7RVbpGP4vTD2hLBdmNQSsCiicO2Ioinv6UI4y9ukqnBpy+XZ9H6uLNgJTlw==" - }, - "node_modules/js-sdsl": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/js-sdsl/-/js-sdsl-4.1.5.tgz", - "integrity": "sha512-08bOAKweV2NUC1wqTtf3qZlnpOX/R2DU9ikpjOHs0H+ibQv3zpncVQg6um4uYtRtrwIX8M4Nh3ytK4HGlYAq7Q==", - "dev": true - }, - "node_modules/js-sha3": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/js-sha3/-/js-sha3-0.8.0.tgz", - "integrity": "sha512-gF1cRrHhIzNfToc802P800N8PpXS+evLLXfsVpowqmAFR9uwbi89WvXg2QspOmXL8QL86J4T1EpFu+yUkwJY3Q==" - }, - "node_modules/js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" - }, - "node_modules/js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "dev": true, - "dependencies": { - "argparse": "^2.0.1" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true - }, - "node_modules/json-stable-stringify-without-jsonify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", - "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", - "dev": true - }, - "node_modules/json-stringify-safe": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", - "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==" - }, - "node_modules/json5": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", - "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", - "dev": true, - "dependencies": { - "minimist": "^1.2.0" - }, - "bin": { - "json5": "lib/cli.js" - } - }, - "node_modules/jsx-ast-utils": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.3.tgz", - "integrity": "sha512-fYQHZTZ8jSfmWZ0iyzfwiU4WDX4HpHbMCZ3gPlWYiCl3BoeOTsqKBqnTVfH2rYT7eP5c3sVbeSPHnnJOaTrWiw==", - "dev": true, - "dependencies": { - "array-includes": "^3.1.5", - "object.assign": "^4.1.3" - }, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/konva": { - "version": "8.4.3", - "resolved": "https://registry.npmjs.org/konva/-/konva-8.4.3.tgz", - "integrity": "sha512-ARqdgAbdNIougRlOKvkQwHlGhXPRBV4KvhCP+qoPpGoVQwwiJe4Hkdu4HHdRPb9rGUp04jDTAxBzEwBsE272pg==", - "funding": [ - { - "type": "patreon", - "url": "https://www.patreon.com/lavrton" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/konva" - }, - { - "type": "github", - "url": "https://github.com/sponsors/lavrton" - } - ] - }, - "node_modules/language-subtag-registry": { - "version": "0.3.22", - "resolved": "https://registry.npmjs.org/language-subtag-registry/-/language-subtag-registry-0.3.22.tgz", - "integrity": "sha512-tN0MCzyWnoz/4nHS6uxdlFWoUZT7ABptwKPQ52Ea7URk6vll88bWBVhodtnlfEuCcKWNGoc+uGbw1cwa9IKh/w==", - "dev": true - }, - "node_modules/language-tags": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/language-tags/-/language-tags-1.0.5.tgz", - "integrity": "sha512-qJhlO9cGXi6hBGKoxEG/sKZDAHD5Hnu9Hs4WbOY3pCWXDhw0N8x1NenNzm2EnNLkLkk7J2SdxAkDSbb6ftT+UQ==", - "dev": true, - "dependencies": { - "language-subtag-registry": "~0.3.2" - } - }, - "node_modules/levn": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", - "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", - "dev": true, - "dependencies": { - "prelude-ls": "^1.2.1", - "type-check": "~0.4.0" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/locate-path": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", - "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", - "dev": true, - "dependencies": { - "p-locate": "^5.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" - }, - "node_modules/lodash.merge": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", - "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", - "dev": true - }, - "node_modules/loose-envify": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", - "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", - "dependencies": { - "js-tokens": "^3.0.0 || ^4.0.0" - }, - "bin": { - "loose-envify": "cli.js" - } - }, - "node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/match-sorter": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/match-sorter/-/match-sorter-6.3.1.tgz", - "integrity": "sha512-mxybbo3pPNuA+ZuCUhm5bwNkXrJTbsk5VWbR5wiwz/GC6LIiegBGn2w3O08UG/jdbYLinw51fSQ5xNU1U3MgBw==", - "dependencies": { - "@babel/runtime": "^7.12.5", - "remove-accents": "0.4.2" - } - }, - "node_modules/mathjs": { - "version": "11.7.0", - "resolved": "https://registry.npmjs.org/mathjs/-/mathjs-11.7.0.tgz", - "integrity": "sha512-RCXtrP5xGIbl9PUc5+7QL81rBCUjzoIZ0ugNqKsarOUxg+x7deY0BzfNai+bGfUL/T+1uYq1xs5w2xVdL3lp0g==", - "dependencies": { - "@babel/runtime": "^7.21.0", - "complex.js": "^2.1.1", - "decimal.js": "^10.4.3", - "escape-latex": "^1.2.0", - "fraction.js": "^4.2.0", - "javascript-natural-sort": "^0.7.1", - "seedrandom": "^3.0.5", - "tiny-emitter": "^2.1.0", - "typed-function": "^4.1.0" - }, - "bin": { - "mathjs": "bin/cli.js" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/merge2": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", - "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", - "dev": true, - "engines": { - "node": ">= 8" - } - }, - "node_modules/micromatch": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", - "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", - "dev": true, - "dependencies": { - "braces": "^3.0.2", - "picomatch": "^2.3.1" - }, - "engines": { - "node": ">=8.6" - } - }, - "node_modules/microseconds": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/microseconds/-/microseconds-0.2.0.tgz", - "integrity": "sha512-n7DHHMjR1avBbSpsTBj6fmMGh2AGrifVV4e+WYc3Q9lO+xnSZ3NyhcBND3vzzatt05LFhoKFRxrIyklmLlUtyA==" - }, - "node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/minimist": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", - "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", - "dev": true, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - }, - "node_modules/nano-time": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/nano-time/-/nano-time-1.0.0.tgz", - "integrity": "sha512-flnngywOoQ0lLQOTRNexn2gGSNuM9bKj9RZAWSzhQ+UJYaAFG9bac4DW9VHjUAzrOaIcajHybCTHe/bkvozQqA==", - "dependencies": { - "big-integer": "^1.6.16" - } - }, - "node_modules/nanoid": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.6.tgz", - "integrity": "sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "bin": { - "nanoid": "bin/nanoid.cjs" - }, - "engines": { - "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" - } - }, - "node_modules/natural-compare": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", - "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", - "dev": true - }, - "node_modules/next": { - "version": "13.5.4", - "resolved": "https://registry.npmjs.org/next/-/next-13.5.4.tgz", - "integrity": "sha512-+93un5S779gho8y9ASQhb/bTkQF17FNQOtXLKAj3lsNgltEcF0C5PMLLncDmH+8X1EnJH1kbqAERa29nRXqhjA==", - "dependencies": { - "@next/env": "13.5.4", - "@swc/helpers": "0.5.2", - "busboy": "1.6.0", - "caniuse-lite": "^1.0.30001406", - "postcss": "8.4.31", - "styled-jsx": "5.1.1", - "watchpack": "2.4.0" - }, - "bin": { - "next": "dist/bin/next" - }, - "engines": { - "node": ">=16.14.0" - }, - "optionalDependencies": { - "@next/swc-darwin-arm64": "13.5.4", - "@next/swc-darwin-x64": "13.5.4", - "@next/swc-linux-arm64-gnu": "13.5.4", - "@next/swc-linux-arm64-musl": "13.5.4", - "@next/swc-linux-x64-gnu": "13.5.4", - "@next/swc-linux-x64-musl": "13.5.4", - "@next/swc-win32-arm64-msvc": "13.5.4", - "@next/swc-win32-ia32-msvc": "13.5.4", - "@next/swc-win32-x64-msvc": "13.5.4" - }, - "peerDependencies": { - "@opentelemetry/api": "^1.1.0", - "react": "^18.2.0", - "react-dom": "^18.2.0", - "sass": "^1.3.0" - }, - "peerDependenciesMeta": { - "@opentelemetry/api": { - "optional": true - }, - "sass": { - "optional": true - } - } - }, - "node_modules/next-global-css": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/next-global-css/-/next-global-css-1.3.1.tgz", - "integrity": "sha512-+OnTwQKmv1lDP7r4R3T94oq6372R9UGVivchBQu49j7ZjzvSXHCnv93yAuhgMkvUgAbGifTs8sQ5YL9wjyAxfA==" - }, - "node_modules/normalizr": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/normalizr/-/normalizr-3.6.2.tgz", - "integrity": "sha512-30qCybsBaCBciotorvuOZTCGEg2AXrJfADMT2Kk/lvpIAcipHdK0zc33nNtwKzyfQAqIJXAcqET6YgflYUgsoQ==" - }, - "node_modules/object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object-inspect": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.2.tgz", - "integrity": "sha512-z+cPxW0QGUp0mcqcsgQyLVRDoXFQbXOwBaqyF7VIgI4TWNQsDHrBpUQslRmIfAoYWdYzs6UlKJtB2XJpTaNSpQ==", - "dev": true, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/object-keys": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", - "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", - "dev": true, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/object.assign": { - "version": "4.1.4", - "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.4.tgz", - "integrity": "sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "has-symbols": "^1.0.3", - "object-keys": "^1.1.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/object.entries": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.5.tgz", - "integrity": "sha512-TyxmjUoZggd4OrrU1W66FMDG6CuqJxsFvymeyXI51+vQLN67zYfZseptRge703kKQdo4uccgAKebXFcRCzk4+g==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3", - "es-abstract": "^1.19.1" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/object.fromentries": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.5.tgz", - "integrity": "sha512-CAyG5mWQRRiBU57Re4FKoTBjXfDoNwdFVH2Y1tS9PqCsfUTymAohOkEMSG3aRNKmv4lV3O7p1et7c187q6bynw==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3", - "es-abstract": "^1.19.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/object.hasown": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/object.hasown/-/object.hasown-1.1.1.tgz", - "integrity": "sha512-LYLe4tivNQzq4JdaWW6WO3HMZZJWzkkH8fnI6EebWl0VZth2wL2Lovm74ep2/gZzlaTdV62JZHEqHQ2yVn8Q/A==", - "dev": true, - "dependencies": { - "define-properties": "^1.1.4", - "es-abstract": "^1.19.5" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/object.values": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.6.tgz", - "integrity": "sha512-FVVTkD1vENCsAcwNs9k6jea2uHC/X0+JcjG8YA60FN5CMaJmG95wT9jek/xX9nornqGRrBkKtzuAu2wuHpKqvw==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.20.4" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/oblivious-set": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/oblivious-set/-/oblivious-set-1.0.0.tgz", - "integrity": "sha512-z+pI07qxo4c2CulUHCDf9lcqDlMSo72N/4rLUpRXf6fu+q8vjt8y0xS+Tlf8NTJDdTXHbdeO1n3MlbctwEoXZw==" - }, - "node_modules/once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "dependencies": { - "wrappy": "1" - } - }, - "node_modules/open": { - "version": "8.4.2", - "resolved": "https://registry.npmjs.org/open/-/open-8.4.2.tgz", - "integrity": "sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ==", - "dev": true, - "dependencies": { - "define-lazy-prop": "^2.0.0", - "is-docker": "^2.1.1", - "is-wsl": "^2.2.0" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/optionator": { - "version": "0.9.1", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", - "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", - "dev": true, - "dependencies": { - "deep-is": "^0.1.3", - "fast-levenshtein": "^2.0.6", - "levn": "^0.4.1", - "prelude-ls": "^1.2.1", - "type-check": "^0.4.0", - "word-wrap": "^1.2.3" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/p-limit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "dev": true, - "dependencies": { - "yocto-queue": "^0.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-locate": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", - "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", - "dev": true, - "dependencies": { - "p-limit": "^3.0.2" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/parent-module": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", - "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", - "dev": true, - "dependencies": { - "callsites": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/path-parse": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", - "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", - "dev": true - }, - "node_modules/path-type": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", - "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/picocolors": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", - "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==" - }, - "node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true, - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/popper.js": { - "version": "1.16.1", - "resolved": "https://registry.npmjs.org/popper.js/-/popper.js-1.16.1.tgz", - "integrity": "sha512-Wb4p1J4zyFTbM+u6WuO4XstYx4Ky9Cewe4DWrel7B0w6VVICvPwdOpotjzcf6eD8TsckVnIMNONQyPIUFOUbCQ==", - "deprecated": "You can find the new Popper v2 at @popperjs/core, this package is dedicated to the legacy v1", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/popperjs" - } - }, - "node_modules/postcss": { - "version": "8.4.31", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz", - "integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==", - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/postcss" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "dependencies": { - "nanoid": "^3.3.6", - "picocolors": "^1.0.0", - "source-map-js": "^1.0.2" - }, - "engines": { - "node": "^10 || ^12 || >=14" - } - }, - "node_modules/prelude-ls": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", - "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", - "dev": true, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/prettier": { - "version": "2.8.7", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.7.tgz", - "integrity": "sha512-yPngTo3aXUUmyuTjeTUT75txrf+aMh9FiD7q9ZE/i6r0bPb22g4FsE6Y338PQX1bmfy08i9QQCB7/rcUAVntfw==", - "bin": { - "prettier": "bin-prettier.js" - }, - "engines": { - "node": ">=10.13.0" - }, - "funding": { - "url": "https://github.com/prettier/prettier?sponsor=1" - } - }, - "node_modules/promise-polyfill": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/promise-polyfill/-/promise-polyfill-8.3.0.tgz", - "integrity": "sha512-H5oELycFml5yto/atYqmjyigJoAo3+OXwolYiH7OfQuYlAqhxNvTfiNMbV9hsC6Yp83yE5r2KTVmtrG6R9i6Pg==" - }, - "node_modules/prop-types": { - "version": "15.8.1", - "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", - "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", - "dependencies": { - "loose-envify": "^1.4.0", - "object-assign": "^4.1.1", - "react-is": "^16.13.1" - } - }, - "node_modules/prop-types-extra": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/prop-types-extra/-/prop-types-extra-1.1.1.tgz", - "integrity": "sha512-59+AHNnHYCdiC+vMwY52WmvP5dM3QLeoumYuEyceQDi9aEhtwN9zIQ2ZNo25sMyXnbh32h+P1ezDsUpUH3JAew==", - "dependencies": { - "react-is": "^16.3.2", - "warning": "^4.0.0" - }, - "peerDependencies": { - "react": ">=0.14.0" - } - }, - "node_modules/punycode": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz", - "integrity": "sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/queue-microtask": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", - "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "node_modules/react": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz", - "integrity": "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==", - "dependencies": { - "loose-envify": "^1.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/react-dom": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz", - "integrity": "sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==", - "dependencies": { - "loose-envify": "^1.1.0", - "scheduler": "^0.23.0" - }, - "peerDependencies": { - "react": "^18.2.0" - } - }, - "node_modules/react-dropzone": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/react-dropzone/-/react-dropzone-9.0.0.tgz", - "integrity": "sha512-wZ2o9B2qkdE3RumWhfyZT9swgJYJPeU5qHEcMU8weYpmLex1eeWX0CC32/Y0VutB+BBi2D+iePV/YZIiB4kZGw==", - "dependencies": { - "attr-accept": "^1.1.3", - "file-selector": "^0.1.8", - "prop-types": "^15.6.2", - "prop-types-extra": "^1.1.0" - }, - "engines": { - "node": ">= 6" - }, - "peerDependencies": { - "react": ">=0.14.0" - } - }, - "node_modules/react-fast-compare": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-3.2.0.tgz", - "integrity": "sha512-rtGImPZ0YyLrscKI9xTpV8psd6I8VAtjKCzQDlzyDvqJA8XOW78TXYQwNRNd8g8JZnDu8q9Fu/1v4HPAVwVdHA==" - }, - "node_modules/react-hotkeys-hook": { - "version": "4.3.8", - "resolved": "https://registry.npmjs.org/react-hotkeys-hook/-/react-hotkeys-hook-4.3.8.tgz", - "integrity": "sha512-RmrIQ3M259c84MnYVEAQsmHkD6s7XUgLG0rW6S7qjt1Lh7q+SPIz5b6obVU8OJw1Utsj1mUCj6twtBPaK/ytww==", - "peerDependencies": { - "react": ">=16.8.1", - "react-dom": ">=16.8.1" - } - }, - "node_modules/react-is": { - "version": "16.13.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", - "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" - }, - "node_modules/react-konva": { - "version": "18.2.5", - "resolved": "https://registry.npmjs.org/react-konva/-/react-konva-18.2.5.tgz", - "integrity": "sha512-lTqJStcHnpGSXB9RlV7p5at3MpRML/TujzbuUDZRIInsLocJ/I4Nhhg3w6yJm9UV05kcwr88OY6LO+2zRyzXog==", - "funding": [ - { - "type": "patreon", - "url": "https://www.patreon.com/lavrton" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/konva" - }, - { - "type": "github", - "url": "https://github.com/sponsors/lavrton" - } - ], - "dependencies": { - "@types/react-reconciler": "^0.28.2", - "its-fine": "^1.0.6", - "react-reconciler": "~0.29.0", - "scheduler": "^0.23.0" - }, - "peerDependencies": { - "konva": "^8.0.1 || ^7.2.5", - "react": ">=18.0.0", - "react-dom": ">=18.0.0" - } - }, - "node_modules/react-query": { - "version": "3.39.3", - "resolved": "https://registry.npmjs.org/react-query/-/react-query-3.39.3.tgz", - "integrity": "sha512-nLfLz7GiohKTJDuT4us4X3h/8unOh+00MLb2yJoGTPjxKs2bc1iDhkNx2bd5MKklXnOD3NrVZ+J2UXujA5In4g==", - "dependencies": { - "@babel/runtime": "^7.5.5", - "broadcast-channel": "^3.4.1", - "match-sorter": "^6.0.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/tannerlinsley" - }, - "peerDependencies": { - "react": "^16.8.0 || ^17.0.0 || ^18.0.0" - }, - "peerDependenciesMeta": { - "react-dom": { - "optional": true - }, - "react-native": { - "optional": true - } - } - }, - "node_modules/react-reconciler": { - "version": "0.29.0", - "resolved": "https://registry.npmjs.org/react-reconciler/-/react-reconciler-0.29.0.tgz", - "integrity": "sha512-wa0fGj7Zht1EYMRhKWwoo1H9GApxYLBuhoAuXN0TlltESAjDssB+Apf0T/DngVqaMyPypDmabL37vw/2aRM98Q==", - "dependencies": { - "loose-envify": "^1.1.0", - "scheduler": "^0.23.0" - }, - "engines": { - "node": ">=0.10.0" - }, - "peerDependencies": { - "react": "^18.2.0" - } - }, - "node_modules/react-redux": { - "version": "8.0.5", - "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-8.0.5.tgz", - "integrity": "sha512-Q2f6fCKxPFpkXt1qNRZdEDLlScsDWyrgSj0mliK59qU6W5gvBiKkdMEG2lJzhd1rCctf0hb6EtePPLZ2e0m1uw==", - "dependencies": { - "@babel/runtime": "^7.12.1", - "@types/hoist-non-react-statics": "^3.3.1", - "@types/use-sync-external-store": "^0.0.3", - "hoist-non-react-statics": "^3.3.2", - "react-is": "^18.0.0", - "use-sync-external-store": "^1.0.0" - }, - "peerDependencies": { - "@types/react": "^16.8 || ^17.0 || ^18.0", - "@types/react-dom": "^16.8 || ^17.0 || ^18.0", - "react": "^16.8 || ^17.0 || ^18.0", - "react-dom": "^16.8 || ^17.0 || ^18.0", - "react-native": ">=0.59", - "redux": "^4" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - }, - "react-dom": { - "optional": true - }, - "react-native": { - "optional": true - }, - "redux": { - "optional": true - } - } - }, - "node_modules/react-redux/node_modules/react-is": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", - "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==" - }, - "node_modules/redux": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/redux/-/redux-4.2.1.tgz", - "integrity": "sha512-LAUYz4lc+Do8/g7aeRa8JkyDErK6ekstQaqWQrNRW//MY1TvCEpMtpTWvlQ+FPbWCx+Xixu/6SHt5N0HR+SB4w==", - "dependencies": { - "@babel/runtime": "^7.9.2" - } - }, - "node_modules/redux-logger": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/redux-logger/-/redux-logger-3.0.6.tgz", - "integrity": "sha512-JoCIok7bg/XpqA1JqCqXFypuqBbQzGQySrhFzewB7ThcnysTO30l4VCst86AuB9T9tuT03MAA56Jw2PNhRSNCg==", - "dependencies": { - "deep-diff": "^0.3.5" - } - }, - "node_modules/redux-saga": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/redux-saga/-/redux-saga-1.2.3.tgz", - "integrity": "sha512-HDe0wTR5nhd8Xr5xjGzoyTbdAw6rjy1GDplFt3JKtKN8/MnkQSRqK/n6aQQhpw5NI4ekDVOaW+w4sdxPBaCoTQ==", - "dependencies": { - "@redux-saga/core": "^1.2.3" - } - }, - "node_modules/redux-thunk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-2.4.2.tgz", - "integrity": "sha512-+P3TjtnP0k/FEjcBL5FZpoovtvrTNT/UXd4/sluaSyrURlSlhLSzEdfsTBW7WsKB6yPvgd7q/iZPICFjW4o57Q==", - "peerDependencies": { - "redux": "^4" - } - }, - "node_modules/regenerator-runtime": { - "version": "0.13.11", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", - "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==" - }, - "node_modules/regexp.prototype.flags": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.4.3.tgz", - "integrity": "sha512-fjggEOO3slI6Wvgjwflkc4NFRCTZAu5CnNfBd5qOMYhWdn67nJBBu34/TkD++eeFmd8C9r9jfXJ27+nSiRkSUA==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3", - "functions-have-names": "^1.2.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/remove-accents": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/remove-accents/-/remove-accents-0.4.2.tgz", - "integrity": "sha512-7pXIJqJOq5tFgG1A2Zxti3Ht8jJF337m4sowbuHsW30ZnkQFnDzy9qBNhgzX8ZLW4+UBcXiiR7SwR6pokHsxiA==" - }, - "node_modules/resolve": { - "version": "1.22.1", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz", - "integrity": "sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==", - "dev": true, - "dependencies": { - "is-core-module": "^2.9.0", - "path-parse": "^1.0.7", - "supports-preserve-symlinks-flag": "^1.0.0" - }, - "bin": { - "resolve": "bin/resolve" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/resolve-from": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/reusify": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", - "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", - "dev": true, - "engines": { - "iojs": ">=1.0.0", - "node": ">=0.10.0" - } - }, - "node_modules/rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/run-parallel": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", - "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "dependencies": { - "queue-microtask": "^1.2.2" - } - }, - "node_modules/safe-regex-test": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.0.tgz", - "integrity": "sha512-JBUUzyOgEwXQY1NuPtvcj/qcBDbDmEvWufhlnXZIm75DEHp+afM1r1ujJpJsV/gSM4t59tpDyPi1sd6ZaPFfsA==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.1.3", - "is-regex": "^1.1.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/scheduler": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.0.tgz", - "integrity": "sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==", - "dependencies": { - "loose-envify": "^1.1.0" - } - }, - "node_modules/seedrandom": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/seedrandom/-/seedrandom-3.0.5.tgz", - "integrity": "sha512-8OwmbklUNzwezjGInmZ+2clQmExQPvomqjL7LFqOYqtmuxRgQYqOD3mHaU+MvZn5FLUeVxVfQjwLZW/n/JFuqg==" - }, - "node_modules/semver": { - "version": "7.3.8", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", - "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", - "dev": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, - "dependencies": { - "shebang-regex": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/side-channel": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", - "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.0", - "get-intrinsic": "^1.0.2", - "object-inspect": "^1.9.0" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/source-map-js": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", - "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/streamsearch": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz", - "integrity": "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==", - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/string.prototype.matchall": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.7.tgz", - "integrity": "sha512-f48okCX7JiwVi1NXCVWcFnZgADDC/n2vePlQ/KUCNqCikLLilQvwjMO8+BHVKvgzH0JB0J9LEPgxOGT02RoETg==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3", - "es-abstract": "^1.19.1", - "get-intrinsic": "^1.1.1", - "has-symbols": "^1.0.3", - "internal-slot": "^1.0.3", - "regexp.prototype.flags": "^1.4.1", - "side-channel": "^1.0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/string.prototype.trimend": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.5.tgz", - "integrity": "sha512-I7RGvmjV4pJ7O3kdf+LXFpVfdNOxtCW/2C8f6jNiW4+PQchwxkCDzlk1/7p+Wl4bqFIZeF47qAHXLuHHWKAxog==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.19.5" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/string.prototype.trimstart": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.5.tgz", - "integrity": "sha512-THx16TJCGlsN0o6dl2o6ncWUsdgnLRSA23rRE5pyGBw/mLr3Ej/R2LaqCtgP8VNMGZsvMWnf9ooZPyY2bHvUFg==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.19.5" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-bom": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", - "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/strip-json-comments": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", - "dev": true, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/styled-jsx": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.1.1.tgz", - "integrity": "sha512-pW7uC1l4mBZ8ugbiZrcIsiIvVx1UmTfw7UkC3Um2tmfUq9Bhk8IiyEIPl6F8agHgjzku6j0xQEZbfA5uSgSaCw==", - "dependencies": { - "client-only": "0.0.1" - }, - "engines": { - "node": ">= 12.0.0" - }, - "peerDependencies": { - "react": ">= 16.8.0 || 17.x.x || ^18.0.0-0" - }, - "peerDependenciesMeta": { - "@babel/core": { - "optional": true - }, - "babel-plugin-macros": { - "optional": true - } - } - }, - "node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/supports-preserve-symlinks-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", - "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", - "dev": true, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/svgsaver": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/svgsaver/-/svgsaver-0.9.0.tgz", - "integrity": "sha512-m94bg62CjuKyhcyJV50ljIPDo5FxEwmdOn60IvHEPlGKhC8gNMnyxbjlYmGi9QW9rIi93DjvfjBuafFfn3+m0w==", - "dependencies": { - "computed-styles": "^1.1.2", - "file-saver": "^1.3.3" - } - }, - "node_modules/synckit": { - "version": "0.8.5", - "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.8.5.tgz", - "integrity": "sha512-L1dapNV6vu2s/4Sputv8xGsCdAVlb5nRDMFU/E27D44l5U6cw1g0dGd45uLc+OXjNMmF4ntiMdCimzcjFKQI8Q==", - "dev": true, - "dependencies": { - "@pkgr/utils": "^2.3.1", - "tslib": "^2.5.0" - }, - "engines": { - "node": "^14.18.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/unts" - } - }, - "node_modules/tabbable": { - "version": "5.3.3", - "resolved": "https://registry.npmjs.org/tabbable/-/tabbable-5.3.3.tgz", - "integrity": "sha512-QD9qKY3StfbZqWOPLp0++pOrAVb/HbUi5xCc8cUo4XjP19808oaMiDzn0leBY5mCespIBM0CIZePzZjgzR83kA==" - }, - "node_modules/tapable": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", - "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/text-table": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", - "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", - "dev": true - }, - "node_modules/tiny-emitter": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/tiny-emitter/-/tiny-emitter-2.1.0.tgz", - "integrity": "sha512-NB6Dk1A9xgQPMoGqC5CVXn123gWyte215ONT5Pp5a0yt4nlEoO1ZWeCwpncaekPHXO60i47ihFnZPiRPjRMq4Q==" - }, - "node_modules/tiny-glob": { - "version": "0.2.9", - "resolved": "https://registry.npmjs.org/tiny-glob/-/tiny-glob-0.2.9.tgz", - "integrity": "sha512-g/55ssRPUjShh+xkfx9UPDXqhckHEsHr4Vd9zX55oSdGZc/MD0m3sferOkwWtp98bv+kcVfEHtRJgBVJzelrzg==", - "dev": true, - "dependencies": { - "globalyzer": "0.1.0", - "globrex": "^0.1.2" - } - }, - "node_modules/tippy.js": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/tippy.js/-/tippy.js-5.1.2.tgz", - "integrity": "sha512-Qtrv2wqbRbaKMUb6bWWBQWPayvcDKNrGlvihxtsyowhT7RLGEh1STWuy6EMXC6QLkfKPB2MLnf8W2mzql9VDAw==", - "dependencies": { - "popper.js": "^1.16.0" - } - }, - "node_modules/to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, - "dependencies": { - "is-number": "^7.0.0" - }, - "engines": { - "node": ">=8.0" - } - }, - "node_modules/tsconfig-paths": { - "version": "3.14.2", - "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.14.2.tgz", - "integrity": "sha512-o/9iXgCYc5L/JxCHPe3Hvh8Q/2xm5Z+p18PESBU6Ff33695QnCHBEjcytY2q19ua7Mbl/DavtBOLq+oG0RCL+g==", - "dev": true, - "dependencies": { - "@types/json5": "^0.0.29", - "json5": "^1.0.2", - "minimist": "^1.2.6", - "strip-bom": "^3.0.0" - } - }, - "node_modules/tslib": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", - "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==" - }, - "node_modules/tsutils": { - "version": "3.21.0", - "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", - "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", - "dev": true, - "dependencies": { - "tslib": "^1.8.1" - }, - "engines": { - "node": ">= 6" - }, - "peerDependencies": { - "typescript": ">=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta" - } - }, - "node_modules/tsutils/node_modules/tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", - "dev": true - }, - "node_modules/type-check": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", - "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", - "dev": true, - "dependencies": { - "prelude-ls": "^1.2.1" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/type-fest": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", - "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/typed-function": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/typed-function/-/typed-function-4.1.0.tgz", - "integrity": "sha512-DGwUl6cioBW5gw2L+6SMupGwH/kZOqivy17E4nsh1JI9fKF87orMmlQx3KISQPmg3sfnOUGlwVkroosvgddrlg==", - "engines": { - "node": ">= 14" - } - }, - "node_modules/typescript": { - "version": "5.4.5", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.5.tgz", - "integrity": "sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ==", - "dev": true, - "peer": true, - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=14.17" - } - }, - "node_modules/typescript-compare": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/typescript-compare/-/typescript-compare-0.0.2.tgz", - "integrity": "sha512-8ja4j7pMHkfLJQO2/8tut7ub+J3Lw2S3061eJLFQcvs3tsmJKp8KG5NtpLn7KcY2w08edF74BSVN7qJS0U6oHA==", - "dependencies": { - "typescript-logic": "^0.0.0" - } - }, - "node_modules/typescript-logic": { - "version": "0.0.0", - "resolved": "https://registry.npmjs.org/typescript-logic/-/typescript-logic-0.0.0.tgz", - "integrity": "sha512-zXFars5LUkI3zP492ls0VskH3TtdeHCqu0i7/duGt60i5IGPIpAHE/DWo5FqJ6EjQ15YKXrt+AETjv60Dat34Q==" - }, - "node_modules/typescript-tuple": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/typescript-tuple/-/typescript-tuple-2.2.1.tgz", - "integrity": "sha512-Zcr0lbt8z5ZdEzERHAMAniTiIKerFCMgd7yjq1fPnDJ43et/k9twIFQMUYff9k5oXcsQ0WpvFcgzK2ZKASoW6Q==", - "dependencies": { - "typescript-compare": "^0.0.2" - } - }, - "node_modules/unbox-primitive": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz", - "integrity": "sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "has-bigints": "^1.0.2", - "has-symbols": "^1.0.3", - "which-boxed-primitive": "^1.0.2" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/unfetch": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/unfetch/-/unfetch-4.2.0.tgz", - "integrity": "sha512-F9p7yYCn6cIW9El1zi0HI6vqpeIvBsr3dSuRO6Xuppb1u5rXpCPmMvLSyECLhybr9isec8Ohl0hPekMVrEinDA==" - }, - "node_modules/unload": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/unload/-/unload-2.2.0.tgz", - "integrity": "sha512-B60uB5TNBLtN6/LsgAf3udH9saB5p7gqJwcFfbOEZ8BcBHnGwCf6G/TGiEqkRAxX7zAFIUtzdrXQSdL3Q/wqNA==", - "dependencies": { - "@babel/runtime": "^7.6.2", - "detect-node": "^2.0.4" - } - }, - "node_modules/uri-js": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", - "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", - "dev": true, - "dependencies": { - "punycode": "^2.1.0" - } - }, - "node_modules/use-resize-observer": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/use-resize-observer/-/use-resize-observer-9.1.0.tgz", - "integrity": "sha512-R25VqO9Wb3asSD4eqtcxk8sJalvIOYBqS8MNZlpDSQ4l4xMQxC/J7Id9HoTqPq8FwULIn0PVW+OAqF2dyYbjow==", - "dependencies": { - "@juggle/resize-observer": "^3.3.1" - }, - "peerDependencies": { - "react": "16.8.0 - 18", - "react-dom": "16.8.0 - 18" - } - }, - "node_modules/use-sync-external-store": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz", - "integrity": "sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==", - "peerDependencies": { - "react": "^16.8.0 || ^17.0.0 || ^18.0.0" - } - }, - "node_modules/uuid": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.0.tgz", - "integrity": "sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg==", - "bin": { - "uuid": "dist/bin/uuid" - } - }, - "node_modules/victory-area": { - "version": "36.6.8", - "resolved": "https://registry.npmjs.org/victory-area/-/victory-area-36.6.8.tgz", - "integrity": "sha512-aIyMuzUqiDcpTCB7FUOYDJvqiDPiluEXLOw6Lh1vrUYmV7CNqMDOIBtTau2vI41Ao0o0YJdCAcyzBib9e3UYbw==", - "dependencies": { - "lodash": "^4.17.19", - "prop-types": "^15.8.1", - "victory-core": "^36.6.8", - "victory-vendor": "^36.6.8" - }, - "peerDependencies": { - "react": ">=16.6.0" - } - }, - "node_modules/victory-axis": { - "version": "36.6.8", - "resolved": "https://registry.npmjs.org/victory-axis/-/victory-axis-36.6.8.tgz", - "integrity": "sha512-tClVJEay1YOJAh9rRyyLx8pei7Sr1/xTz04bJmfzFoAxFoPBtvgfFwXhfZ1YjGIl7m5Wh2CiYMY3figueLzYtg==", - "dependencies": { - "lodash": "^4.17.19", - "prop-types": "^15.8.1", - "victory-core": "^36.6.8" - }, - "peerDependencies": { - "react": ">=16.6.0" - } - }, - "node_modules/victory-bar": { - "version": "36.6.8", - "resolved": "https://registry.npmjs.org/victory-bar/-/victory-bar-36.6.8.tgz", - "integrity": "sha512-jLLPm3IW8/2uSLPvQD9bxzXnTraUYBIDTkbZPZy7oHP01OVzP1sj+MMHcINCWcUbyUyLZDL3u8CvViXjS273JQ==", - "dependencies": { - "lodash": "^4.17.19", - "prop-types": "^15.8.1", - "victory-core": "^36.6.8", - "victory-vendor": "^36.6.8" - }, - "peerDependencies": { - "react": ">=16.6.0" - } - }, - "node_modules/victory-brush-container": { - "version": "36.6.8", - "resolved": "https://registry.npmjs.org/victory-brush-container/-/victory-brush-container-36.6.8.tgz", - "integrity": "sha512-PN5zQ6kjVwZca1qV41WlV6J2IEyQh+2hykRe6c/wERDotVVbSrX3sJVAzUbN+7x2rrK0CL6a/XUI8jDsWTMM2A==", - "dependencies": { - "lodash": "^4.17.19", - "prop-types": "^15.8.1", - "react-fast-compare": "^3.2.0", - "victory-core": "^36.6.8" - }, - "peerDependencies": { - "react": ">=16.6.0" - } - }, - "node_modules/victory-chart": { - "version": "36.6.8", - "resolved": "https://registry.npmjs.org/victory-chart/-/victory-chart-36.6.8.tgz", - "integrity": "sha512-kC1jL63PAmqUrvZNOfwAXNuaIwz4nvXYUuEPu59WRBCOIGDGRgv2wJ1O7O0xYXqDkI57EtAYf9KUK+miEn/Btg==", - "dependencies": { - "lodash": "^4.17.19", - "prop-types": "^15.8.1", - "react-fast-compare": "^3.2.0", - "victory-axis": "^36.6.8", - "victory-core": "^36.6.8", - "victory-polar-axis": "^36.6.8", - "victory-shared-events": "^36.6.8" - }, - "peerDependencies": { - "react": ">=16.6.0" - } - }, - "node_modules/victory-core": { - "version": "36.6.8", - "resolved": "https://registry.npmjs.org/victory-core/-/victory-core-36.6.8.tgz", - "integrity": "sha512-SkyEszZKGyxjqfptfFWYdI22CvCuE9LhkaDpikzIhT2gcE3SuOBO5fk/740XMYE2ZUsJ4Fu/Vy4+8jZi17y44A==", - "dependencies": { - "lodash": "^4.17.21", - "prop-types": "^15.8.1", - "react-fast-compare": "^3.2.0", - "victory-vendor": "^36.6.8" - }, - "peerDependencies": { - "react": ">=16.6.0" - } - }, - "node_modules/victory-create-container": { - "version": "36.6.8", - "resolved": "https://registry.npmjs.org/victory-create-container/-/victory-create-container-36.6.8.tgz", - "integrity": "sha512-H2BsdTbJ/RxxcEg5lzk3TDlihtOs7I/5KaIBP3yosPs702i40mL2qndkRkj08QeiZhkaKfQ2GOUvyP+t7DSdmg==", - "dependencies": { - "lodash": "^4.17.19", - "victory-brush-container": "^36.6.8", - "victory-core": "^36.6.8", - "victory-cursor-container": "^36.6.8", - "victory-selection-container": "^36.6.8", - "victory-voronoi-container": "^36.6.8", - "victory-zoom-container": "^36.6.8" - }, - "peerDependencies": { - "react": ">=16.6.0" - } - }, - "node_modules/victory-cursor-container": { - "version": "36.6.8", - "resolved": "https://registry.npmjs.org/victory-cursor-container/-/victory-cursor-container-36.6.8.tgz", - "integrity": "sha512-3WIBRl+7jnZok6syLfW8RK23nliDcoD/JUTN0YZo6bKBqHeFc4+ur3mlwCfghH7sGoxJRYuOJxTd9x2MwM5HQQ==", - "dependencies": { - "lodash": "^4.17.19", - "prop-types": "^15.8.1", - "victory-core": "^36.6.8" - }, - "peerDependencies": { - "react": ">=16.6.0" - } - }, - "node_modules/victory-errorbar": { - "version": "36.6.8", - "resolved": "https://registry.npmjs.org/victory-errorbar/-/victory-errorbar-36.6.8.tgz", - "integrity": "sha512-N4JdBy5wV+KU6pus7FBx+5on31oXanO+qVmtRH8u4W7CMWH5EwHortyu2wVYD9K2QoluXemIxZd7kfn14hmqfQ==", - "dependencies": { - "lodash": "^4.17.19", - "prop-types": "^15.8.1", - "victory-core": "^36.6.8" - }, - "peerDependencies": { - "react": ">=16.6.0" - } - }, - "node_modules/victory-group": { - "version": "36.6.8", - "resolved": "https://registry.npmjs.org/victory-group/-/victory-group-36.6.8.tgz", - "integrity": "sha512-CiupDIGPPWVgwif3ayd8glSlR41mVbuT0Nl0ay9q42w2fiM32syiJAoifIw47X4tL8ow/DXH+/5Pd8eEyA2trA==", - "dependencies": { - "lodash": "^4.17.19", - "prop-types": "^15.8.1", - "react-fast-compare": "^3.2.0", - "victory-core": "^36.6.8", - "victory-shared-events": "^36.6.8" - }, - "peerDependencies": { - "react": ">=16.6.0" - } - }, - "node_modules/victory-legend": { - "version": "36.6.8", - "resolved": "https://registry.npmjs.org/victory-legend/-/victory-legend-36.6.8.tgz", - "integrity": "sha512-OnkzB82Mvt5/1LYNsrfZQoXaVvgfp1rCsFRI3imq257Sh/UPy0/eZehCMQs/SVbU0z0EuIpXokhZb3BBdoJgpw==", - "dependencies": { - "lodash": "^4.17.19", - "prop-types": "^15.8.1", - "victory-core": "^36.6.8" - }, - "peerDependencies": { - "react": ">=16.6.0" - } - }, - "node_modules/victory-line": { - "version": "36.6.8", - "resolved": "https://registry.npmjs.org/victory-line/-/victory-line-36.6.8.tgz", - "integrity": "sha512-MozOejQRZPdzFaru5zUfqVB4TEff6nZjtQhOs+F5yyhXjLgM89zGX30r3jK5cjVdAPbTu4KPUrwktvlw+AkPRA==", - "dependencies": { - "lodash": "^4.17.19", - "prop-types": "^15.8.1", - "victory-core": "^36.6.8", - "victory-vendor": "^36.6.8" - }, - "peerDependencies": { - "react": ">=16.6.0" - } - }, - "node_modules/victory-pie": { - "version": "36.6.8", - "resolved": "https://registry.npmjs.org/victory-pie/-/victory-pie-36.6.8.tgz", - "integrity": "sha512-dUHWiiKd60dlt7OjFa+YYwanHAkP/T0abzy6O3SFxGre52oeqd8px1EoVhlLKpn4ao8L35koG9mvz6/pGyr8Dw==", - "dependencies": { - "lodash": "^4.17.19", - "prop-types": "^15.8.1", - "victory-core": "^36.6.8", - "victory-vendor": "^36.6.8" - }, - "peerDependencies": { - "react": ">=16.6.0" - } - }, - "node_modules/victory-polar-axis": { - "version": "36.6.8", - "resolved": "https://registry.npmjs.org/victory-polar-axis/-/victory-polar-axis-36.6.8.tgz", - "integrity": "sha512-aU+Wp5six21POhI9oXeREnZHljpqcmwFHHnliVGrwgRsuc7TAjfXPWVOX9guEFfh6zQW6IZWWWTTLAN/PIEm9w==", - "dependencies": { - "lodash": "^4.17.19", - "prop-types": "^15.8.1", - "victory-core": "^36.6.8" - }, - "peerDependencies": { - "react": ">=16.6.0" - } - }, - "node_modules/victory-scatter": { - "version": "36.6.8", - "resolved": "https://registry.npmjs.org/victory-scatter/-/victory-scatter-36.6.8.tgz", - "integrity": "sha512-GKSNneBxIWLsF3eBSTW5IwT5S4YdsfFl4PVCP3/wTa2myfS5DIS9FufEnJp/FEZGalEXYWxeR47rlWqABxAj5A==", - "dependencies": { - "lodash": "^4.17.19", - "prop-types": "^15.8.1", - "victory-core": "^36.6.8" - }, - "peerDependencies": { - "react": ">=16.6.0" - } - }, - "node_modules/victory-selection-container": { - "version": "36.6.8", - "resolved": "https://registry.npmjs.org/victory-selection-container/-/victory-selection-container-36.6.8.tgz", - "integrity": "sha512-kudYbSX+o7fr64oeN7+EG/c+lqO22aypxVdCwa6BagAGoqqLR4jXxTqqIdp8tvxCgfCCXxopnTKYr46nubypGw==", - "dependencies": { - "lodash": "^4.17.19", - "prop-types": "^15.8.1", - "victory-core": "^36.6.8" - }, - "peerDependencies": { - "react": ">=16.6.0" - } - }, - "node_modules/victory-shared-events": { - "version": "36.6.8", - "resolved": "https://registry.npmjs.org/victory-shared-events/-/victory-shared-events-36.6.8.tgz", - "integrity": "sha512-hWPOVqMD3Sv6Rl1iyO6ibQrwYF9/eLCnRo0T59/Hsid6On0AJJjL9gv0oEIM5fqz7R7zx9PJmMk877IctEOemw==", - "dependencies": { - "json-stringify-safe": "^5.0.1", - "lodash": "^4.17.19", - "prop-types": "^15.8.1", - "react-fast-compare": "^3.2.0", - "victory-core": "^36.6.8" - }, - "peerDependencies": { - "react": ">=16.6.0" - } - }, - "node_modules/victory-stack": { - "version": "36.6.8", - "resolved": "https://registry.npmjs.org/victory-stack/-/victory-stack-36.6.8.tgz", - "integrity": "sha512-Pkux46IqAealOi0KvqQpaJKKKpHCfZ/sh5IeUKYFy+QKWAjiQjG6hFZeHgr2YaS7OfdbvHhoAdvp03KntWzpbw==", - "dependencies": { - "lodash": "^4.17.19", - "prop-types": "^15.8.1", - "react-fast-compare": "^3.2.0", - "victory-core": "^36.6.8", - "victory-shared-events": "^36.6.8" - }, - "peerDependencies": { - "react": ">=16.6.0" - } - }, - "node_modules/victory-tooltip": { - "version": "36.6.8", - "resolved": "https://registry.npmjs.org/victory-tooltip/-/victory-tooltip-36.6.8.tgz", - "integrity": "sha512-9P+QeAGyDpP0trJnQ1NtnbDhpoJB0Ghc2boYEehvL12p0OzolY9/Nq5SDP0tu5i1BBujwFXtnoCDqt+mOH25fA==", - "dependencies": { - "lodash": "^4.17.19", - "prop-types": "^15.8.1", - "victory-core": "^36.6.8" - }, - "peerDependencies": { - "react": ">=16.6.0" - } - }, - "node_modules/victory-vendor": { - "version": "36.6.8", - "resolved": "https://registry.npmjs.org/victory-vendor/-/victory-vendor-36.6.8.tgz", - "integrity": "sha512-H3kyQ+2zgjMPvbPqAl7Vwm2FD5dU7/4bCTQakFQnpIsfDljeOMDojRsrmJfwh4oAlNnWhpAf+mbAoLh8u7dwyQ==", - "dependencies": { - "@types/d3-array": "^3.0.3", - "@types/d3-ease": "^3.0.0", - "@types/d3-interpolate": "^3.0.1", - "@types/d3-scale": "^4.0.2", - "@types/d3-shape": "^3.1.0", - "@types/d3-time": "^3.0.0", - "@types/d3-timer": "^3.0.0", - "d3-array": "^3.1.6", - "d3-ease": "^3.0.1", - "d3-interpolate": "^3.0.1", - "d3-scale": "^4.0.2", - "d3-shape": "^3.1.0", - "d3-time": "^3.0.0", - "d3-timer": "^3.0.1" - } - }, - "node_modules/victory-voronoi-container": { - "version": "36.6.8", - "resolved": "https://registry.npmjs.org/victory-voronoi-container/-/victory-voronoi-container-36.6.8.tgz", - "integrity": "sha512-x9/OOZdMm4dh38jNhSfBYT0nG6ribsINU0/WNzIn3QcDXFBInsJ7jRySxYmdmk45OdXfbDRwDMqVHk72sWQyUw==", - "dependencies": { - "delaunay-find": "0.0.6", - "lodash": "^4.17.19", - "prop-types": "^15.8.1", - "react-fast-compare": "^3.2.0", - "victory-core": "^36.6.8", - "victory-tooltip": "^36.6.8" - }, - "peerDependencies": { - "react": ">=16.6.0" - } - }, - "node_modules/victory-zoom-container": { - "version": "36.6.8", - "resolved": "https://registry.npmjs.org/victory-zoom-container/-/victory-zoom-container-36.6.8.tgz", - "integrity": "sha512-gxX5iJUaxrFFZ2IGS0sQnUI+3Mhj6bVLqtOlQd3Krld+9f/ieuUbxl+P+eIyhQU/VyHSlirIZeOGOXJeYcU9jQ==", - "dependencies": { - "lodash": "^4.17.19", - "prop-types": "^15.8.1", - "victory-core": "^36.6.8" - }, - "peerDependencies": { - "react": ">=16.6.0" - } - }, - "node_modules/warning": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz", - "integrity": "sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==", - "dependencies": { - "loose-envify": "^1.0.0" - } - }, - "node_modules/watchpack": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.0.tgz", - "integrity": "sha512-Lcvm7MGST/4fup+ifyKi2hjyIAwcdI4HRgtvTpIUxBRhB+RFtUh8XtDOxUfctVCnhVi+QQj49i91OyvzkJl6cg==", - "dependencies": { - "glob-to-regexp": "^0.4.1", - "graceful-fs": "^4.1.2" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/which-boxed-primitive": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz", - "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", - "dev": true, - "dependencies": { - "is-bigint": "^1.0.1", - "is-boolean-object": "^1.1.0", - "is-number-object": "^1.0.4", - "is-string": "^1.0.5", - "is-symbol": "^1.0.3" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/word-wrap": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.4.tgz", - "integrity": "sha512-2V81OA4ugVo5pRo46hAoD2ivUJx8jXmWXfUkY4KFNw0hEptvN0QfH3K4nHiwzGeKl5rFKedV48QVoqYavy4YpA==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" - }, - "node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - }, - "node_modules/yocto-queue": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", - "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - } - } -} diff --git a/opendc-web/opendc-web-ui/package.json b/opendc-web/opendc-web-ui/package.json deleted file mode 100644 index 18840db6..00000000 --- a/opendc-web/opendc-web-ui/package.json +++ /dev/null @@ -1,76 +0,0 @@ -{ - "name": "opendc-frontend", - "version": "3.0.0", - "description": "The user-facing component of the OpenDC stack, allowing users to build and interact with their own (virtual) datacenters.", - "keywords": [ - "opendc", - "simulation", - "datacenter", - "frontend" - ], - "homepage": "http://opendc.org", - "bugs": { - "url": "https://github.com/atlarge-research/opendc/issues", - "email": "opendc@atlarge-research.com" - }, - "author": "OpenDC Maintainers <opendc@atlarge-research.com>", - "license": "MIT", - "private": true, - "dependencies": { - "@auth0/auth0-react": "^1.12.1", - "@patternfly/react-charts": "^6.94.18", - "@patternfly/react-core": "^4.276.6", - "@patternfly/react-icons": "^4.93.6", - "@patternfly/react-table": "^4.112.39", - "@sentry/react": "^7.45.0", - "@sentry/tracing": "^7.45.0", - "clsx": "^1.2.1", - "immer": "^9.0.21", - "konva": "^8.4.3", - "mathjs": "^11.7.0", - "next": "^13.5.4", - "next-global-css": "^1.3.1", - "normalizr": "^3.6.2", - "prettier": "^2.8.7", - "prop-types": "^15.8.1", - "react": "^18.2.0", - "react-dom": "^18.2.0", - "react-hotkeys-hook": "^4.3.8", - "react-konva": "^18.2.5", - "react-query": "^3.39.3", - "react-redux": "^8.0.5", - "redux": "^4.2.1", - "redux-logger": "^3.0.6", - "redux-saga": "^1.2.3", - "redux-thunk": "^2.4.2", - "svgsaver": "^0.9.0", - "use-resize-observer": "^9.1.0", - "uuid": "^9.0.0", - "victory-errorbar": "^36.6.8" - }, - "devDependencies": { - "eslint": "^8.36.0", - "eslint-config-next": "^13.2.4" - }, - "scripts": { - "format": "prettier --write src", - "precommit": "lint-staged", - "dev": "next dev", - "lint": "next lint", - "build": "next build", - "start": "next start", - "export": "next export -o build/export" - }, - "browserslist": { - "production": [ - ">0.2%", - "not dead", - "not op_mini all" - ], - "development": [ - "last 1 chrome version", - "last 1 firefox version", - "last 1 safari version" - ] - } -} diff --git a/opendc-web/opendc-web-ui/public/favicon.ico b/opendc-web/opendc-web-ui/public/favicon.ico Binary files differdeleted file mode 100644 index c2f40a0d..00000000 --- a/opendc-web/opendc-web-ui/public/favicon.ico +++ /dev/null diff --git a/opendc-web/opendc-web-ui/public/humans.txt b/opendc-web/opendc-web-ui/public/humans.txt deleted file mode 100644 index dadcd530..00000000 --- a/opendc-web/opendc-web-ui/public/humans.txt +++ /dev/null @@ -1,35 +0,0 @@ -/* TEAM */ -Benevolent Dictator for Life: Alexandru Iosup. -Site: http://www.ds.ewi.tudelft.nl/~iosup/ -Twitter: aiosup. -Location: Delft, Netherlands. - -Full-Stack Engineer: Georgios Andreadis. -Site: https://github.com/gandreadis -Location: Delft, Netherlands. - -Simulation Engineer: Fabian Mastenbroek. -Site: https://github.com/fabianishere -Location: Delft, Netherlands. - -Simulation Engineer: Jacob Burley. -Site: https://github.com/jc0b -Location: Amsterdam, Netherlands. - -Backend Engineer: Leon Overweel. -Site: http://leonoverweel.com/ -Twitter: layon_overwhale. -Location: Delft, Netherlands. - -Simulation Engineer: Matthijs Bijman. -Site: https://github.com/MDBijman -Location: Delft, Netherlands. - -/* THANKS */ -Executive Producer: Vincent van Beek. -Executive Producer: Tim Hegeman. - -/* SITE */ -Standards: HTML5, Sass, ES6 -Components: React.js, Redux, create-react-app, react-konva -Software: WebStorm, Vim, Visual Studio diff --git a/opendc-web/opendc-web-ui/public/img/avatar.svg b/opendc-web/opendc-web-ui/public/img/avatar.svg deleted file mode 100644 index 73726f9b..00000000 --- a/opendc-web/opendc-web-ui/public/img/avatar.svg +++ /dev/null @@ -1,18 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- Generator: Adobe Illustrator 24.0.2, SVG Export Plug-In . SVG Version: 6.00 Build 0) --> -<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" - viewBox="0 0 36 36" style="enable-background:new 0 0 36 36;" xml:space="preserve"> -<style type="text/css"> - .st0{fill-rule:evenodd;clip-rule:evenodd;fill:#F0F0F0;} - .st1{fill-rule:evenodd;clip-rule:evenodd;fill:#D2D2D2;} - .st2{fill:#B8BBBE;} - .st3{fill:#D2D2D2;} -</style> -<rect class="st0" width="36" height="36"/> -<path class="st1" d="M17.7,20.1c-3.5,0-6.4-2.9-6.4-6.4s2.9-6.4,6.4-6.4s6.4,2.9,6.4,6.4S21.3,20.1,17.7,20.1z"/> -<path class="st2" d="M13.3,36l0-6.7c-2,0.4-2.9,1.4-3.1,3.5L10.1,36H13.3z"/> -<path class="st3" d="M10.1,36l0.1-3.2c0.2-2.1,1.1-3.1,3.1-3.5l0,6.7h9.4l0-6.7c2,0.4,2.9,1.4,3.1,3.5l0.1,3.2h4.7 - c-0.4-3.9-1.3-9-2.9-11c-1.1-1.4-2.3-2.2-3.5-2.6s-1.8-0.6-6.3-0.6s-6.1,0.7-6.1,0.7c-1.2,0.4-2.4,1.2-3.4,2.6 - C6.7,27,5.8,32.2,5.4,36H10.1z"/> -<path class="st2" d="M25.9,36l-0.1-3.2c-0.2-2.1-1.1-3.1-3.1-3.5l0,6.7H25.9z"/> -</svg> diff --git a/opendc-web/opendc-web-ui/public/img/datacenter-drawing.png b/opendc-web/opendc-web-ui/public/img/datacenter-drawing.png Binary files differdeleted file mode 100644 index ec2b7398..00000000 --- a/opendc-web/opendc-web-ui/public/img/datacenter-drawing.png +++ /dev/null diff --git a/opendc-web/opendc-web-ui/public/img/logo.png b/opendc-web/opendc-web-ui/public/img/logo.png Binary files differdeleted file mode 100644 index d743038b..00000000 --- a/opendc-web/opendc-web-ui/public/img/logo.png +++ /dev/null diff --git a/opendc-web/opendc-web-ui/public/img/logo.svg b/opendc-web/opendc-web-ui/public/img/logo.svg deleted file mode 100644 index 5283a034..00000000 --- a/opendc-web/opendc-web-ui/public/img/logo.svg +++ /dev/null @@ -1,191 +0,0 @@ -<?xml version="1.0" encoding="UTF-8" standalone="no"?> -<!-- Created with Inkscape (http://www.inkscape.org/) --> - -<svg - xmlns:dc="http://purl.org/dc/elements/1.1/" - xmlns:cc="http://creativecommons.org/ns#" - xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" - xmlns:svg="http://www.w3.org/2000/svg" - xmlns="http://www.w3.org/2000/svg" - xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" - xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" - width="4.3656249mm" - height="5.4239674mm" - viewBox="0 0 4.3656249 5.4239674" - version="1.1" - id="svg4738" - inkscape:version="0.92.1 r15371" - sodipodi:docname="opendc.svg"> - <defs - id="defs4732" /> - <sodipodi:namedview - id="base" - pagecolor="#ffffff" - bordercolor="#666666" - borderopacity="1.0" - inkscape:pageopacity="0.0" - inkscape:pageshadow="2" - inkscape:zoom="22.4" - inkscape:cx="-5.0874286" - inkscape:cy="6.401307" - inkscape:document-units="mm" - inkscape:current-layer="layer1" - showgrid="false" - inkscape:window-width="1920" - inkscape:window-height="1027" - inkscape:window-x="-8" - inkscape:window-y="-8" - inkscape:window-maximized="1" /> - <metadata - id="metadata4735"> - <rdf:RDF> - <cc:Work - rdf:about=""> - <dc:format>image/svg+xml</dc:format> - <dc:type - rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> - <dc:title></dc:title> - </cc:Work> - </rdf:RDF> - </metadata> - <g - inkscape:label="Layer 1" - inkscape:groupmode="layer" - id="layer1" - transform="translate(-16.538532,-146.52225)"> - <g - transform="matrix(0.26458333,0,0,0.26458333,-15.13354,-112.7517)" - id="g4333" - inkscape:export-filename="h:\Desktop\logo.png" - inkscape:export-xdpi="561.54816" - inkscape:export-ydpi="561.54816"> - <path - inkscape:export-ydpi="1307.2168" - inkscape:export-xdpi="1307.2168" - inkscape:export-filename="h:\Desktop\stackv2.png" - style="fill:#48a1cd;fill-opacity:1;stroke:none" - d="m 119.95547,988.18306 8,4 v -4 l -8,-4 z" - id="path4052-1-2-8" - inkscape:connector-curvature="0" /> - <path - inkscape:export-ydpi="1307.2168" - inkscape:export-xdpi="1307.2168" - inkscape:export-filename="h:\Desktop\stackv2.png" - style="fill:#82d0e7;fill-opacity:1;stroke:none" - d="m 127.95547,988.18306 v 4 l 8.00001,-4 v -4 z" - id="path4054-4-9-2" - inkscape:connector-curvature="0" /> - <path - inkscape:export-ydpi="1307.2168" - inkscape:export-xdpi="1307.2168" - inkscape:export-filename="h:\Desktop\stackv2.png" - style="fill:#82d0e7;fill-opacity:1;stroke:none" - d="m 119.95547,984.18306 8,-4 8.00001,4 -8.00001,4 z" - id="path4056-2-1-5" - inkscape:connector-curvature="0" /> - <path - inkscape:export-ydpi="1307.2168" - inkscape:export-xdpi="1307.2168" - inkscape:export-filename="h:\Desktop\comparison.png" - inkscape:connector-curvature="0" - id="path4048-1-9" - d="m 119.95547,992.18306 8,4 v -4 l -8,-4 z" - style="fill:#d1af2e;fill-opacity:1;stroke:none" /> - <path - inkscape:export-ydpi="1307.2168" - inkscape:export-xdpi="1307.2168" - inkscape:export-filename="h:\Desktop\comparison.png" - inkscape:connector-curvature="0" - id="path4050-7-2" - d="m 127.95546,992.18306 v 4 l 8.00001,-4 v -4 z" - style="fill:#edd667;fill-opacity:1;stroke:none" /> - <path - inkscape:export-ydpi="1307.2168" - inkscape:export-xdpi="1307.2168" - inkscape:export-filename="h:\Desktop\comparison.png" - style="fill:#df6f20;fill-opacity:1;stroke:none" - d="m 119.95547,996.18306 8,4.00004 v -4.00004 l -8,-4 z" - id="path3883-9-9-8" - inkscape:connector-curvature="0" /> - <path - inkscape:export-ydpi="1307.2168" - inkscape:export-xdpi="1307.2168" - inkscape:export-filename="h:\Desktop\comparison.png" - style="fill:#ed9c67;fill-opacity:1;stroke:none" - d="m 127.95547,996.18306 v 4.00004 l 8,-4.00004 v -4 z" - id="path3885-8-9-1" - inkscape:connector-curvature="0" /> - <path - inkscape:export-ydpi="1307.2168" - inkscape:export-xdpi="1307.2168" - inkscape:export-filename="h:\Desktop\comparison.png" - style="fill:none;stroke:#000000;stroke-width:0.5;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1" - d="m 119.95547,984.18306 8,-4 8,4 -8,4 z" - id="path3893-6-0-8" - inkscape:connector-curvature="0" /> - <path - inkscape:export-ydpi="1307.2168" - inkscape:export-xdpi="1307.2168" - inkscape:export-filename="h:\Desktop\comparison.png" - style="fill:none;stroke:#000000;stroke-width:0.5;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" - d="m 119.95547,984.18306 v 4 l 8,4 8,-4 v -4" - id="path3895-2-6-1" - inkscape:connector-curvature="0" /> - <path - inkscape:export-ydpi="1307.2168" - inkscape:export-xdpi="1307.2168" - inkscape:export-filename="h:\Desktop\comparison.png" - style="fill:none;stroke:#000000;stroke-width:0.5;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" - d="m 127.95547,988.18306 v 12.00004" - id="path3897-3-2-1" - inkscape:connector-curvature="0" - sodipodi:nodetypes="cc" /> - <path - inkscape:export-ydpi="1307.2168" - inkscape:export-xdpi="1307.2168" - inkscape:export-filename="h:\Desktop\comparison.png" - inkscape:connector-curvature="0" - id="path3899-8-1-7" - d="m 119.95547,988.18306 v 4 l 8,4 8,-4 v -4" - style="fill:none;stroke:#000000;stroke-width:0.5;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" /> - <path - inkscape:export-ydpi="1307.2168" - inkscape:export-xdpi="1307.2168" - inkscape:export-filename="h:\Desktop\comparison.png" - style="fill:none;stroke:#000000;stroke-width:0.5;stroke-linecap:butt;stroke-linejoin:round;stroke-opacity:1" - d="m 119.95547,992.18306 v 4 l 8,4.00004 8,-4.00004 v -4" - id="path3901-5-6-1" - inkscape:connector-curvature="0" /> - <circle - inkscape:export-ydpi="1307.2168" - inkscape:export-xdpi="1307.2168" - inkscape:export-filename="h:\Desktop\stackv2.png" - transform="matrix(1.0403949,-0.44307824,0.3060712,0.9369482,-235.62413,33.696703)" - id="path3906-6-2-4-1-2" - style="fill:#000000;fill-opacity:1;stroke:none" - cx="43.5" - cy="1044.8622" - r="0.5" /> - <circle - inkscape:export-ydpi="1307.2168" - inkscape:export-xdpi="1307.2168" - inkscape:export-filename="h:\Desktop\stackv2.png" - transform="matrix(1.0403949,-0.44307824,0.3060712,0.9369482,-235.62413,37.696663)" - id="path3910-3-0-1-9-1" - style="fill:#000000;fill-opacity:1;stroke:none" - cx="43.5" - cy="1044.8622" - r="0.5" /> - <circle - style="fill:#000000;fill-opacity:1;stroke:none" - id="path4108-7-3-6" - transform="matrix(1.0403949,-0.44307824,0.3060712,0.9369482,-235.62413,29.696659)" - inkscape:export-filename="h:\Desktop\stackv2.png" - inkscape:export-xdpi="1307.2168" - inkscape:export-ydpi="1307.2168" - cx="43.5" - cy="1044.8622" - r="0.5" /> - </g> - </g> -</svg> diff --git a/opendc-web/opendc-web-ui/public/img/opendc-architecture.png b/opendc-web/opendc-web-ui/public/img/opendc-architecture.png Binary files differdeleted file mode 100644 index e0bf8e9b..00000000 --- a/opendc-web/opendc-web-ui/public/img/opendc-architecture.png +++ /dev/null diff --git a/opendc-web/opendc-web-ui/public/img/opendc-timeline-v2.png b/opendc-web/opendc-web-ui/public/img/opendc-timeline-v2.png Binary files differdeleted file mode 100644 index 0b2821c4..00000000 --- a/opendc-web/opendc-web-ui/public/img/opendc-timeline-v2.png +++ /dev/null diff --git a/opendc-web/opendc-web-ui/public/img/portraits/aiosup.png b/opendc-web/opendc-web-ui/public/img/portraits/aiosup.png Binary files differdeleted file mode 100644 index d2019b4d..00000000 --- a/opendc-web/opendc-web-ui/public/img/portraits/aiosup.png +++ /dev/null diff --git a/opendc-web/opendc-web-ui/public/img/portraits/evaneyk.png b/opendc-web/opendc-web-ui/public/img/portraits/evaneyk.png Binary files differdeleted file mode 100644 index 011c1627..00000000 --- a/opendc-web/opendc-web-ui/public/img/portraits/evaneyk.png +++ /dev/null diff --git a/opendc-web/opendc-web-ui/public/img/portraits/fmastenbroek.png b/opendc-web/opendc-web-ui/public/img/portraits/fmastenbroek.png Binary files differdeleted file mode 100644 index 218b1a6f..00000000 --- a/opendc-web/opendc-web-ui/public/img/portraits/fmastenbroek.png +++ /dev/null diff --git a/opendc-web/opendc-web-ui/public/img/portraits/gandreadis.png b/opendc-web/opendc-web-ui/public/img/portraits/gandreadis.png Binary files differdeleted file mode 100644 index 96a3abda..00000000 --- a/opendc-web/opendc-web-ui/public/img/portraits/gandreadis.png +++ /dev/null diff --git a/opendc-web/opendc-web-ui/public/img/portraits/hhe.png b/opendc-web/opendc-web-ui/public/img/portraits/hhe.png Binary files differdeleted file mode 100644 index 4891c7f5..00000000 --- a/opendc-web/opendc-web-ui/public/img/portraits/hhe.png +++ /dev/null diff --git a/opendc-web/opendc-web-ui/public/img/portraits/jbosch.png b/opendc-web/opendc-web-ui/public/img/portraits/jbosch.png Binary files differdeleted file mode 100644 index c76e1fab..00000000 --- a/opendc-web/opendc-web-ui/public/img/portraits/jbosch.png +++ /dev/null diff --git a/opendc-web/opendc-web-ui/public/img/portraits/jburley.png b/opendc-web/opendc-web-ui/public/img/portraits/jburley.png Binary files differdeleted file mode 100644 index d2691659..00000000 --- a/opendc-web/opendc-web-ui/public/img/portraits/jburley.png +++ /dev/null diff --git a/opendc-web/opendc-web-ui/public/img/portraits/lfdversluis.png b/opendc-web/opendc-web-ui/public/img/portraits/lfdversluis.png Binary files differdeleted file mode 100644 index 6fbc8472..00000000 --- a/opendc-web/opendc-web-ui/public/img/portraits/lfdversluis.png +++ /dev/null diff --git a/opendc-web/opendc-web-ui/public/img/portraits/loverweel.png b/opendc-web/opendc-web-ui/public/img/portraits/loverweel.png Binary files differdeleted file mode 100644 index 85865977..00000000 --- a/opendc-web/opendc-web-ui/public/img/portraits/loverweel.png +++ /dev/null diff --git a/opendc-web/opendc-web-ui/public/img/portraits/sjounaid.png b/opendc-web/opendc-web-ui/public/img/portraits/sjounaid.png Binary files differdeleted file mode 100644 index 41878161..00000000 --- a/opendc-web/opendc-web-ui/public/img/portraits/sjounaid.png +++ /dev/null diff --git a/opendc-web/opendc-web-ui/public/img/portraits/vvanbeek.png b/opendc-web/opendc-web-ui/public/img/portraits/vvanbeek.png Binary files differdeleted file mode 100644 index 4c8b3311..00000000 --- a/opendc-web/opendc-web-ui/public/img/portraits/vvanbeek.png +++ /dev/null diff --git a/opendc-web/opendc-web-ui/public/img/portraits/wlai.png b/opendc-web/opendc-web-ui/public/img/portraits/wlai.png Binary files differdeleted file mode 100644 index c758846d..00000000 --- a/opendc-web/opendc-web-ui/public/img/portraits/wlai.png +++ /dev/null diff --git a/opendc-web/opendc-web-ui/public/img/screenshot-construction.png b/opendc-web/opendc-web-ui/public/img/screenshot-construction.png Binary files differdeleted file mode 100644 index ea20a7c4..00000000 --- a/opendc-web/opendc-web-ui/public/img/screenshot-construction.png +++ /dev/null diff --git a/opendc-web/opendc-web-ui/public/img/screenshot-simulation.png b/opendc-web/opendc-web-ui/public/img/screenshot-simulation.png Binary files differdeleted file mode 100644 index 1bd989c7..00000000 --- a/opendc-web/opendc-web-ui/public/img/screenshot-simulation.png +++ /dev/null diff --git a/opendc-web/opendc-web-ui/public/img/stakeholders/Developer.png b/opendc-web/opendc-web-ui/public/img/stakeholders/Developer.png Binary files differdeleted file mode 100644 index d2638e6c..00000000 --- a/opendc-web/opendc-web-ui/public/img/stakeholders/Developer.png +++ /dev/null diff --git a/opendc-web/opendc-web-ui/public/img/stakeholders/Manager.png b/opendc-web/opendc-web-ui/public/img/stakeholders/Manager.png Binary files differdeleted file mode 100644 index 92db7459..00000000 --- a/opendc-web/opendc-web-ui/public/img/stakeholders/Manager.png +++ /dev/null diff --git a/opendc-web/opendc-web-ui/public/img/stakeholders/Researcher.png b/opendc-web/opendc-web-ui/public/img/stakeholders/Researcher.png Binary files differdeleted file mode 100644 index d87edd39..00000000 --- a/opendc-web/opendc-web-ui/public/img/stakeholders/Researcher.png +++ /dev/null diff --git a/opendc-web/opendc-web-ui/public/img/stakeholders/Sales.png b/opendc-web/opendc-web-ui/public/img/stakeholders/Sales.png Binary files differdeleted file mode 100644 index 5b7c3a72..00000000 --- a/opendc-web/opendc-web-ui/public/img/stakeholders/Sales.png +++ /dev/null diff --git a/opendc-web/opendc-web-ui/public/img/stakeholders/Student.png b/opendc-web/opendc-web-ui/public/img/stakeholders/Student.png Binary files differdeleted file mode 100644 index a4900303..00000000 --- a/opendc-web/opendc-web-ui/public/img/stakeholders/Student.png +++ /dev/null diff --git a/opendc-web/opendc-web-ui/public/img/topology/cpu-icon.png b/opendc-web/opendc-web-ui/public/img/topology/cpu-icon.png Binary files differdeleted file mode 100644 index 07cfbd31..00000000 --- a/opendc-web/opendc-web-ui/public/img/topology/cpu-icon.png +++ /dev/null diff --git a/opendc-web/opendc-web-ui/public/img/topology/gpu-icon.png b/opendc-web/opendc-web-ui/public/img/topology/gpu-icon.png Binary files differdeleted file mode 100644 index 55d4fb05..00000000 --- a/opendc-web/opendc-web-ui/public/img/topology/gpu-icon.png +++ /dev/null diff --git a/opendc-web/opendc-web-ui/public/img/topology/memory-icon.png b/opendc-web/opendc-web-ui/public/img/topology/memory-icon.png Binary files differdeleted file mode 100644 index 36e8a44e..00000000 --- a/opendc-web/opendc-web-ui/public/img/topology/memory-icon.png +++ /dev/null diff --git a/opendc-web/opendc-web-ui/public/img/topology/rack-energy-icon.png b/opendc-web/opendc-web-ui/public/img/topology/rack-energy-icon.png Binary files differdeleted file mode 100644 index 1088c61b..00000000 --- a/opendc-web/opendc-web-ui/public/img/topology/rack-energy-icon.png +++ /dev/null diff --git a/opendc-web/opendc-web-ui/public/img/topology/rack-space-icon.png b/opendc-web/opendc-web-ui/public/img/topology/rack-space-icon.png Binary files differdeleted file mode 100644 index 387d7ea6..00000000 --- a/opendc-web/opendc-web-ui/public/img/topology/rack-space-icon.png +++ /dev/null diff --git a/opendc-web/opendc-web-ui/public/img/topology/storage-icon.png b/opendc-web/opendc-web-ui/public/img/topology/storage-icon.png Binary files differdeleted file mode 100644 index 7a39cb6f..00000000 --- a/opendc-web/opendc-web-ui/public/img/topology/storage-icon.png +++ /dev/null diff --git a/opendc-web/opendc-web-ui/public/img/tudelft-icon.png b/opendc-web/opendc-web-ui/public/img/tudelft-icon.png Binary files differdeleted file mode 100644 index a7a2d56a..00000000 --- a/opendc-web/opendc-web-ui/public/img/tudelft-icon.png +++ /dev/null diff --git a/opendc-web/opendc-web-ui/public/manifest.json b/opendc-web/opendc-web-ui/public/manifest.json deleted file mode 100644 index adb82218..00000000 --- a/opendc-web/opendc-web-ui/public/manifest.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "short_name": "OpenDC", - "name": "OpenDC", - "icons": [ - { - "src": "favicon.ico", - "sizes": "16x16", - "type": "image/png" - } - ], - "start_url": "./index.html", - "display": "standalone", - "theme_color": "#00A6D6", - "background_color": "#eeeeee" -} diff --git a/opendc-web/opendc-web-ui/public/robots.txt b/opendc-web/opendc-web-ui/public/robots.txt deleted file mode 100644 index 1c6094ce..00000000 --- a/opendc-web/opendc-web-ui/public/robots.txt +++ /dev/null @@ -1,3 +0,0 @@ -User-agent: * -Disallow: /projects/ -Disallow: /profile/ diff --git a/opendc-web/opendc-web-ui/scripts/envsubst.sh b/opendc-web/opendc-web-ui/scripts/envsubst.sh deleted file mode 100755 index afc976ed..00000000 --- a/opendc-web/opendc-web-ui/scripts/envsubst.sh +++ /dev/null @@ -1,14 +0,0 @@ -#!/bin/sh - -set -e - -auto_envsubst() { - input_path="build/next.template" - output_path="build/next" - - cp -r "$input_path" "$output_path" - find "$output_path" -type f -name '*.js' -exec perl -pi -e 's/%%(NEXT_PUBLIC_[_A-Z0-9]+)%%/$ENV{$1}/g' {} \; -} - -auto_envsubst -exit 0 diff --git a/opendc-web/opendc-web-ui/src/api/index.js b/opendc-web/opendc-web-ui/src/api/index.js deleted file mode 100644 index 3411b96e..00000000 --- a/opendc-web/opendc-web-ui/src/api/index.js +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -import { apiUrl } from '../config' - -/** - * Send the specified request to the OpenDC API. - * - * @param auth The authentication context. - * @param path Relative path for the API. - * @param method The method to use for the request. - * @param body The body of the request. - */ -export async function request(auth, path, method = 'GET', body) { - const headers = { - 'Content-Type': 'application/json', - } - - const { getAccessTokenSilently } = auth - if (getAccessTokenSilently) { - const token = await getAccessTokenSilently() - headers['Authorization'] = `Bearer ${token}` - } - - const response = await fetch(`${apiUrl}/${path}`, { - method: method, - headers: headers, - body: body && JSON.stringify(body), - }) - const json = await response.json() - - if (!response.ok) { - throw json.message - } - - return json -} diff --git a/opendc-web/opendc-web-ui/src/api/portfolios.js b/opendc-web/opendc-web-ui/src/api/portfolios.js deleted file mode 100644 index d818876f..00000000 --- a/opendc-web/opendc-web-ui/src/api/portfolios.js +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -import { request } from './index' - -export function fetchPortfolio(auth, projectId, number) { - return request(auth, `projects/${projectId}/portfolios/${number}`) -} - -export function fetchPortfolios(auth, projectId) { - return request(auth, `projects/${projectId}/portfolios`) -} - -export function addPortfolio(auth, projectId, portfolio) { - return request(auth, `projects/${projectId}/portfolios`, 'POST', portfolio) -} - -export function deletePortfolio(auth, projectId, number) { - return request(auth, `projects/${projectId}/portfolios/${number}`, 'DELETE') -} diff --git a/opendc-web/opendc-web-ui/src/api/projects.js b/opendc-web/opendc-web-ui/src/api/projects.js deleted file mode 100644 index e7e095da..00000000 --- a/opendc-web/opendc-web-ui/src/api/projects.js +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -import { request } from './index' - -export function fetchProjects(auth) { - return request(auth, `projects/`) -} - -export function fetchProject(auth, projectId) { - return request(auth, `projects/${projectId}`) -} - -export function addProject(auth, project) { - return request(auth, 'projects/', 'POST', project) -} - -export function deleteProject(auth, projectId) { - return request(auth, `projects/${projectId}`, 'DELETE') -} diff --git a/opendc-web/opendc-web-ui/src/api/scenarios.js b/opendc-web/opendc-web-ui/src/api/scenarios.js deleted file mode 100644 index 7eeb8f28..00000000 --- a/opendc-web/opendc-web-ui/src/api/scenarios.js +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -import { request } from './index' - -export function fetchScenario(auth, projectId, scenarioId) { - return request(auth, `projects/${projectId}/scenarios/${scenarioId}`) -} - -export function fetchScenariosOfPortfolio(auth, projectId, portfolioId) { - return request(auth, `projects/${projectId}/portfolios/${portfolioId}/scenarios`) -} - -export function addScenario(auth, projectId, portfolioId, scenario) { - return request(auth, `projects/${projectId}/portfolios/${portfolioId}/scenarios`, 'POST', scenario) -} - -export function deleteScenario(auth, projectId, scenarioId) { - return request(auth, `projects/${projectId}/scenarios/${scenarioId}`, 'DELETE') -} diff --git a/opendc-web/opendc-web-ui/src/api/schedulers.js b/opendc-web/opendc-web-ui/src/api/schedulers.js deleted file mode 100644 index 0b8b8153..00000000 --- a/opendc-web/opendc-web-ui/src/api/schedulers.js +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -import { request } from './index' - -export function fetchSchedulers(auth) { - return request(auth, 'schedulers/') -} diff --git a/opendc-web/opendc-web-ui/src/api/topologies.js b/opendc-web/opendc-web-ui/src/api/topologies.js deleted file mode 100644 index 0509c6d0..00000000 --- a/opendc-web/opendc-web-ui/src/api/topologies.js +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -import { request } from './index' - -export function fetchTopology(auth, projectId, number) { - return request(auth, `projects/${projectId}/topologies/${number}`) -} - -export function fetchTopologies(auth, projectId) { - return request(auth, `projects/${projectId}/topologies`) -} - -export function addTopology(auth, projectId, topology) { - return request(auth, `projects/${projectId}/topologies`, 'POST', topology) -} - -export function updateTopology(auth, topology) { - const { project, number, rooms } = topology - return request(auth, `projects/${project.id}/topologies/${number}`, 'PUT', { rooms }) -} - -export function deleteTopology(auth, projectId, number) { - return request(auth, `projects/${projectId}/topologies/${number}`, 'DELETE') -} diff --git a/opendc-web/opendc-web-ui/src/api/traces.js b/opendc-web/opendc-web-ui/src/api/traces.js deleted file mode 100644 index fd637ac3..00000000 --- a/opendc-web/opendc-web-ui/src/api/traces.js +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -import { request } from './index' - -export function fetchTraces(auth) { - return request(auth, 'traces/') -} diff --git a/opendc-web/opendc-web-ui/src/api/users.js b/opendc-web/opendc-web-ui/src/api/users.js deleted file mode 100644 index 12a9be05..00000000 --- a/opendc-web/opendc-web-ui/src/api/users.js +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -import { request } from './index' - -/** - * Fetch information about the user from the web server. - * - * @param auth The authentication object. - */ -export function fetchUser(auth) { - return request(auth, `users/me`) -} diff --git a/opendc-web/opendc-web-ui/src/auth.js b/opendc-web/opendc-web-ui/src/auth.js deleted file mode 100644 index 8c88f526..00000000 --- a/opendc-web/opendc-web-ui/src/auth.js +++ /dev/null @@ -1,97 +0,0 @@ -/* - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -import PropTypes from 'prop-types' -import { Auth0Provider, useAuth0 } from '@auth0/auth0-react' -import { useEffect } from 'react' -import { auth } from './config' - -/** - * Helper function to provide the authentication context in case Auth0 is not - * configured and the user is anonymous. - */ -function useAnonymousAuth() { - return { - isAnonymous: true, - isAuthenticated: false, - isLoading: false, - logout: () => {}, - loginWithRedirect: () => {}, - } -} - -/** - * Determine whether the auth domain is anonymous. - */ -function isAnonymousDomain(config) { - return !config.domain || config.domain === '%%NEXT_PUBLIC_AUTH0_DOMAIN%%' -} - -/** - * Force the user to be authenticated or redirect to the homepage. - */ -function useRequireAuth0() { - const auth = useAuth0() - const { loginWithRedirect, isLoading, isAuthenticated } = auth - - useEffect(() => { - if (!isLoading && !isAuthenticated) { - loginWithRedirect() - } - }, [loginWithRedirect, isLoading, isAuthenticated]) -} - -/** - * Obtain the authentication context. - */ -export const useAuth = isAnonymousDomain(auth) ? useAnonymousAuth : useAuth0 - -/** - * Force the user to be authenticated or redirect to the homepage. - */ -export const useRequireAuth = isAnonymousDomain(auth) ? () => {} : useRequireAuth0 - -/** - * AuthProvider which provides an authentication context. - */ -export function AuthProvider({ children }) { - const authConfig = auth - - if (!isAnonymousDomain(authConfig)) { - return ( - <Auth0Provider - domain={authConfig.domain} - clientId={authConfig.clientId} - redirectUri={authConfig.redirectUri} - audience={authConfig.audience} - > - {children} - </Auth0Provider> - ) - } - - return children -} - -AuthProvider.propTypes = { - children: PropTypes.node, -} diff --git a/opendc-web/opendc-web-ui/src/components/AppHeader.js b/opendc-web/opendc-web-ui/src/components/AppHeader.js deleted file mode 100644 index 514dce2a..00000000 --- a/opendc-web/opendc-web-ui/src/components/AppHeader.js +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -import Image from 'next/image' -import PropTypes from 'prop-types' -import React from 'react' -import { - Masthead, - MastheadMain, - MastheadBrand, - MastheadContent, - Toolbar, - ToolbarContent, - ToolbarItem, -} from '@patternfly/react-core' -import Link from 'next/link' -import AppHeaderTools from './AppHeaderTools' -import AppHeaderUser from './AppHeaderUser' -import ProjectSelector from './context/ProjectSelector' - -import styles from './AppHeader.module.css' - -export default function AppHeader({ nav }) { - return ( - <Masthead id="app-header" className={styles.header}> - <MastheadMain> - <MastheadBrand className={styles.logo} component={(props) => <Link href="/projects" {...props} />}> - <Image src="/img/logo.svg" alt="OpenDC logo" width={25} height={25} /> - <span>OpenDC</span> - </MastheadBrand> - </MastheadMain> - <MastheadContent> - <Toolbar id="toolbar" isFullHeight isStatic> - <ToolbarContent> - <ToolbarItem> - <ProjectSelector /> - </ToolbarItem> - {nav && <ToolbarItem>{nav}</ToolbarItem>} - <AppHeaderTools /> - <AppHeaderUser /> - </ToolbarContent> - </Toolbar> - </MastheadContent> - </Masthead> - ) -} - -AppHeader.propTypes = { - nav: PropTypes.node, -} diff --git a/opendc-web/opendc-web-ui/src/components/AppHeader.module.css b/opendc-web/opendc-web-ui/src/components/AppHeader.module.css deleted file mode 100644 index 9d5dbed1..00000000 --- a/opendc-web/opendc-web-ui/src/components/AppHeader.module.css +++ /dev/null @@ -1,42 +0,0 @@ -/*! - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -.header.header { - /* Increase precedence */ - --pf-c-masthead--m-display-inline__content--MinHeight: 3rem; - --pf-c-masthead--m-display-inline__main--MinHeight: 3rem; - - --pf-c-masthead--c-context-selector--Width: 200px; -} - -.logo > span { - margin-left: 8px; - color: #fff; - align-self: center; - font-weight: 600; - font-size: 0.9rem; -} - -.logo:hover, -.logo:focus > span { - --pf-global--link--TextDecoration: none; -} diff --git a/opendc-web/opendc-web-ui/src/components/AppHeaderTools.js b/opendc-web/opendc-web-ui/src/components/AppHeaderTools.js deleted file mode 100644 index 499bceef..00000000 --- a/opendc-web/opendc-web-ui/src/components/AppHeaderTools.js +++ /dev/null @@ -1,93 +0,0 @@ -/* - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -import { - Button, - ButtonVariant, - Dropdown, - DropdownItem, - KebabToggle, - ToolbarGroup, - ToolbarItem, -} from '@patternfly/react-core' -import { useReducer } from 'react' -import { GithubIcon, HelpIcon } from '@patternfly/react-icons' - -function AppHeaderTools() { - const [isKebabDropdownOpen, toggleKebabDropdown] = useReducer((t) => !t, false) - const kebabDropdownItems = [ - <DropdownItem - key={0} - component={ - <a href="https://opendc.org" target="_blank" rel="noreferrer"> - <HelpIcon /> Help - </a> - } - />, - ] - - return ( - <ToolbarGroup - variant="icon-button-group" - alignment={{ default: 'alignRight' }} - spacer={{ default: 'spacerNone', md: 'spacerMd' }} - > - <ToolbarGroup variant="icon-button-group" visibility={{ default: 'hidden', lg: 'visible' }}> - <ToolbarItem> - <Button - component="a" - href="https://github.com/atlarge-research/opendc" - target="_blank" - aria-label="Source code" - variant={ButtonVariant.plain} - > - <GithubIcon /> - </Button> - </ToolbarItem> - <ToolbarItem> - <Button - component="a" - href="https://opendc.org/" - target="_blank" - aria-label="Help actions" - variant={ButtonVariant.plain} - > - <HelpIcon /> - </Button> - </ToolbarItem> - </ToolbarGroup> - <ToolbarItem visibility={{ lg: 'hidden' }}> - <Dropdown - isPlain - position="right" - toggle={<KebabToggle onToggle={toggleKebabDropdown} />} - isOpen={isKebabDropdownOpen} - dropdownItems={kebabDropdownItems} - /> - </ToolbarItem> - </ToolbarGroup> - ) -} - -AppHeaderTools.propTypes = {} - -export default AppHeaderTools diff --git a/opendc-web/opendc-web-ui/src/components/AppHeaderUser.js b/opendc-web/opendc-web-ui/src/components/AppHeaderUser.js deleted file mode 100644 index 3a73d9ba..00000000 --- a/opendc-web/opendc-web-ui/src/components/AppHeaderUser.js +++ /dev/null @@ -1,99 +0,0 @@ -/* - * Copyright (c) 2022 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -import { - Dropdown, - DropdownToggle, - Skeleton, - ToolbarItem, - DropdownItem, - DropdownGroup, - Avatar, - Progress, - ProgressSize, - DropdownSeparator, -} from '@patternfly/react-core' -import { useReducer } from 'react' -import { useAuth } from '../auth' -import useUser from '../data/user' - -export default function AppHeaderUser() { - const { logout, user, isAuthenticated, isLoading } = useAuth() - const username = isAuthenticated || isLoading ? user?.name : 'Anonymous' - const avatar = isAuthenticated || isLoading ? user?.picture : '/img/avatar.svg' - - const { data } = useUser() - const simulationBudget = data?.accounting?.simulationTimeBudget ?? 3600 - const simulationTime = data?.accounting?.simulationTime | 0 - - const [isDropdownOpen, toggleDropdown] = useReducer((t) => !t, false) - const userDropdownItems = [ - <DropdownGroup key="budget" label="Monthly Simulation Budget"> - <DropdownItem isDisabled> - <Progress - min={0} - max={simulationBudget} - value={simulationTime} - title={`${Math.ceil(simulationTime / 60)} of ${Math.ceil(simulationBudget / 60)} minutes`} - size={ProgressSize.sm} - /> - </DropdownItem> - </DropdownGroup>, - <DropdownSeparator key="separator" />, - <DropdownItem - key="group 2 logout" - isDisabled={!isAuthenticated} - onClick={() => logout({ returnTo: window.location.origin })} - > - Logout - </DropdownItem>, - ] - - const avatarComponent = avatar ? ( - <Avatar src={avatar} alt="Avatar image" size="sm" /> - ) : ( - <Skeleton className="pf-c-avatar" shape="circle" width="24px" screenreaderText="Loading avatar" /> - ) - - return ( - <ToolbarItem visibility={{ default: 'hidden', md: 'visible' }}> - <Dropdown - isFullHeight - position="right" - isOpen={isDropdownOpen} - toggle={ - <DropdownToggle onToggle={toggleDropdown} icon={avatarComponent}> - {username ?? ( - <Skeleton - fontSize="xs" - width="150px" - className="pf-u-display-inline-flex" - screenreaderText="Loading username" - /> - )} - </DropdownToggle> - } - dropdownItems={userDropdownItems} - /> - </ToolbarItem> - ) -} diff --git a/opendc-web/opendc-web-ui/src/components/AppPage.js b/opendc-web/opendc-web-ui/src/components/AppPage.js deleted file mode 100644 index 2893146e..00000000 --- a/opendc-web/opendc-web-ui/src/components/AppPage.js +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -import PropTypes from 'prop-types' -import AppHeader from './AppHeader' -import React from 'react' -import { Page, PageGroup, PageBreadcrumb } from '@patternfly/react-core' - -export function AppPage({ children, breadcrumb, contextSelectors }) { - return ( - <Page header={<AppHeader />}> - <PageGroup> - {contextSelectors} - {breadcrumb && <PageBreadcrumb>{breadcrumb}</PageBreadcrumb>} - </PageGroup> - {children} - </Page> - ) -} - -AppPage.propTypes = { - breadcrumb: PropTypes.node, - contextSelectors: PropTypes.node, - children: PropTypes.node, -} diff --git a/opendc-web/opendc-web-ui/src/components/context/ContextSelectionSection.js b/opendc-web/opendc-web-ui/src/components/context/ContextSelectionSection.js deleted file mode 100644 index f3c25b79..00000000 --- a/opendc-web/opendc-web-ui/src/components/context/ContextSelectionSection.js +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -import PropTypes from 'prop-types' -import { contextSelectionSection } from './ContextSelectionSection.module.css' - -function ContextSelectionSection({ children }) { - return <section className={contextSelectionSection}>{children}</section> -} - -ContextSelectionSection.propTypes = { - children: PropTypes.node, -} - -export default ContextSelectionSection diff --git a/opendc-web/opendc-web-ui/src/components/context/ContextSelectionSection.module.css b/opendc-web/opendc-web-ui/src/components/context/ContextSelectionSection.module.css deleted file mode 100644 index 0e902af0..00000000 --- a/opendc-web/opendc-web-ui/src/components/context/ContextSelectionSection.module.css +++ /dev/null @@ -1,28 +0,0 @@ -/*! - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -.contextSelectionSection { - padding-left: var(--pf-c-page__main-breadcrumb--PaddingLeft); - flex-shrink: 0; - border-bottom: var(--pf-global--BorderWidth--sm) solid var(--pf-global--BorderColor--100); - background-color: var(--pf-c-page__main-breadcrumb--BackgroundColor); -} diff --git a/opendc-web/opendc-web-ui/src/components/context/ContextSelector.js b/opendc-web/opendc-web-ui/src/components/context/ContextSelector.js deleted file mode 100644 index d2601008..00000000 --- a/opendc-web/opendc-web-ui/src/components/context/ContextSelector.js +++ /dev/null @@ -1,79 +0,0 @@ -/* - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -import PropTypes from 'prop-types' -import { ContextSelector as PFContextSelector, ContextSelectorItem } from '@patternfly/react-core' -import { useMemo, useState } from 'react' - -import styles from './ContextSelector.module.css' - -function ContextSelector({ id, type = 'page', toggleText, items, onSelect, onToggle, isOpen, isFullHeight }) { - const [searchValue, setSearchValue] = useState('') - const filteredItems = useMemo( - () => items.filter(({ name }) => name.toLowerCase().indexOf(searchValue.toLowerCase()) !== -1) || items, - [items, searchValue] - ) - - return ( - <PFContextSelector - id={id} - className={type === 'page' && styles.pageSelector} - toggleText={toggleText} - onSearchInputChange={(value) => setSearchValue(value)} - searchInputValue={searchValue} - onToggle={(_, isOpen) => onToggle(isOpen)} - onSelect={(event) => { - const targetId = +event.target.value - const target = items.find((item) => item.id === targetId) - - onSelect(target) - onToggle(!isOpen) - }} - isOpen={isOpen} - isFullHeight={isFullHeight} - > - {filteredItems.map((item) => ( - <ContextSelectorItem key={item.id} value={item.id}> - {item.name} - </ContextSelectorItem> - ))} - </PFContextSelector> - ) -} - -const Item = PropTypes.shape({ - id: PropTypes.any.isRequired, - name: PropTypes.string.isRequired, -}) - -ContextSelector.propTypes = { - id: PropTypes.string, - type: PropTypes.oneOf(['app', 'page']), - items: PropTypes.arrayOf(Item).isRequired, - toggleText: PropTypes.string, - onSelect: PropTypes.func.isRequired, - onToggle: PropTypes.func.isRequired, - isOpen: PropTypes.bool, - isFullHeight: PropTypes.bool, -} - -export default ContextSelector diff --git a/opendc-web/opendc-web-ui/src/components/context/ContextSelector.module.css b/opendc-web/opendc-web-ui/src/components/context/ContextSelector.module.css deleted file mode 100644 index 7662d00c..00000000 --- a/opendc-web/opendc-web-ui/src/components/context/ContextSelector.module.css +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -.pageSelector.pageSelector { - /* Ensure this selector has precedence over the default one */ - margin-right: 20px; - - --pf-c-context-selector__menu--ZIndex: var(--pf-global--ZIndex--lg); - --pf-c-context-selector__toggle--PaddingTop: var(--pf-global--spacer--sm); - --pf-c-context-selector__toggle--PaddingRight: 0; - --pf-c-context-selector__toggle--PaddingBottom: var(--pf-global--spacer--sm); - --pf-c-context-selector__toggle--PaddingLeft: 0; - --pf-c-context-selector__toggle--BorderWidth: 0; - --pf-c-context-selector__toggle-text--FontSize: var(--pf-global--FontSize--sm); -} - -.pageSelector.pageSelector :global(.pf-c-context-selector__toggle):active, -.pageSelector.pageSelector :global(.pf-c-context-selector__toggle):focus-within, -.pageSelector.pageSelector :global(.pf-c-context-selector__toggle):global(.pf-m-active) { - --pf-c-context-selector__toggle--after--BorderBottomWidth: 0; -} - -.pageSelector.pageSelector:global(.pf-m-expanded) > :global(.pf-c-context-selector__toggle) { - --pf-c-context-selector__toggle--after--BorderBottomWidth: 0; -} diff --git a/opendc-web/opendc-web-ui/src/components/context/PortfolioSelector.js b/opendc-web/opendc-web-ui/src/components/context/PortfolioSelector.js deleted file mode 100644 index e401e6fc..00000000 --- a/opendc-web/opendc-web-ui/src/components/context/PortfolioSelector.js +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -import { useRouter } from 'next/router' -import { useState } from 'react' -import { usePortfolios } from '../../data/project' -import { Portfolio } from '../../shapes' -import ContextSelector from './ContextSelector' - -function PortfolioSelector({ activePortfolio }) { - const router = useRouter() - - const [isOpen, setOpen] = useState(false) - const { data: portfolios = [] } = usePortfolios(activePortfolio?.project?.id, { enabled: isOpen }) - - return ( - <ContextSelector - id="portfolio" - toggleText={activePortfolio ? `Portfolio: ${activePortfolio.name}` : 'Select portfolio'} - activeItem={activePortfolio} - items={portfolios} - onSelect={(portfolio) => router.push(`/projects/${portfolio.project.id}/portfolios/${portfolio.number}`)} - onToggle={setOpen} - isOpen={isOpen} - /> - ) -} - -PortfolioSelector.propTypes = { - activePortfolio: Portfolio, -} - -export default PortfolioSelector diff --git a/opendc-web/opendc-web-ui/src/components/context/ProjectSelector.js b/opendc-web/opendc-web-ui/src/components/context/ProjectSelector.js deleted file mode 100644 index f2791b38..00000000 --- a/opendc-web/opendc-web-ui/src/components/context/ProjectSelector.js +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -import { useRouter } from 'next/router' -import { useState } from 'react' -import { useProjects, useProject } from '../../data/project' -import { Project } from '../../shapes' -import ContextSelector from './ContextSelector' - -function ProjectSelector() { - const router = useRouter() - const projectId = +router.query['project'] - - const [isOpen, setOpen] = useState(false) - const { data: activeProject } = useProject(+projectId) - const { data: projects = [] } = useProjects({ enabled: isOpen }) - - return ( - <ContextSelector - id="project" - type="app" - toggleText={activeProject ? activeProject.name : 'Select project'} - items={projects} - onSelect={(project) => router.push(`/projects/${project.id}`)} - onToggle={setOpen} - isOpen={isOpen} - isFullHeight - /> - ) -} - -ProjectSelector.propTypes = { - activeProject: Project, -} - -export default ProjectSelector diff --git a/opendc-web/opendc-web-ui/src/components/context/TopologySelector.js b/opendc-web/opendc-web-ui/src/components/context/TopologySelector.js deleted file mode 100644 index 355d9f4b..00000000 --- a/opendc-web/opendc-web-ui/src/components/context/TopologySelector.js +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -import { useRouter } from 'next/router' -import { useState } from 'react' -import { useTopologies } from '../../data/topology' -import { Topology } from '../../shapes' -import ContextSelector from './ContextSelector' - -function TopologySelector({ activeTopology }) { - const router = useRouter() - - const [isOpen, setOpen] = useState(false) - const { data: topologies = [] } = useTopologies(activeTopology?.project?.id, { enabled: isOpen }) - - return ( - <ContextSelector - id="topology" - toggleText={activeTopology ? `Topology: ${activeTopology.name}` : 'Select topology'} - activeItem={activeTopology} - items={topologies} - onSelect={(topology) => router.push(`/projects/${topology.project.id}/topologies/${topology.number}`)} - onToggle={setOpen} - isOpen={isOpen} - /> - ) -} - -TopologySelector.propTypes = { - activeTopology: Topology, -} - -export default TopologySelector diff --git a/opendc-web/opendc-web-ui/src/components/portfolios/NewScenario.js b/opendc-web/opendc-web-ui/src/components/portfolios/NewScenario.js deleted file mode 100644 index fd9a72d2..00000000 --- a/opendc-web/opendc-web-ui/src/components/portfolios/NewScenario.js +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -import PropTypes from 'prop-types' -import { PlusIcon } from '@patternfly/react-icons' -import { Button } from '@patternfly/react-core' -import { useState } from 'react' -import { useNewScenario } from '../../data/project' -import NewScenarioModal from './NewScenarioModal' - -function NewScenario({ projectId, portfolioId }) { - const [isVisible, setVisible] = useState(false) - const { mutate: addScenario } = useNewScenario() - - const onSubmit = (projectId, portfolioNumber, data) => { - addScenario({ projectId, portfolioNumber, data }) - setVisible(false) - } - - return ( - <> - <Button icon={<PlusIcon />} isSmall onClick={() => setVisible(true)}> - New Scenario - </Button> - <NewScenarioModal - projectId={projectId} - portfolioId={portfolioId} - isOpen={isVisible} - onSubmit={onSubmit} - onCancel={() => setVisible(false)} - /> - </> - ) -} - -NewScenario.propTypes = { - projectId: PropTypes.number, - portfolioId: PropTypes.number, -} - -export default NewScenario diff --git a/opendc-web/opendc-web-ui/src/components/portfolios/NewScenarioModal.js b/opendc-web/opendc-web-ui/src/components/portfolios/NewScenarioModal.js deleted file mode 100644 index ed35c163..00000000 --- a/opendc-web/opendc-web-ui/src/components/portfolios/NewScenarioModal.js +++ /dev/null @@ -1,157 +0,0 @@ -import PropTypes from 'prop-types' -import React, { useRef, useState } from 'react' -import Modal from '../util/modals/Modal' -import { - Checkbox, - Form, - FormGroup, - FormSection, - FormSelect, - FormSelectOption, - NumberInput, - TextInput, -} from '@patternfly/react-core' -import { useSchedulers, useTraces } from '../../data/experiments' -import { useTopologies } from '../../data/topology' -import { usePortfolio } from '../../data/project' - -function NewScenarioModal({ projectId, portfolioId, isOpen, onSubmit: onSubmitUpstream, onCancel: onCancelUpstream }) { - const { data: portfolio } = usePortfolio(projectId, portfolioId) - const { data: topologies = [] } = useTopologies(projectId, { enabled: isOpen }) - const { data: traces = [] } = useTraces({ enabled: isOpen }) - const { data: schedulers = [] } = useSchedulers({ enabled: isOpen }) - - // eslint-disable-next-line no-unused-vars - const [isSubmitted, setSubmitted] = useState(false) - const [traceLoad, setTraceLoad] = useState(100) - const [trace, setTrace] = useState(undefined) - const [topology, setTopology] = useState(undefined) - const [scheduler, setScheduler] = useState(undefined) - const [failuresEnabled, setFailuresEnabled] = useState(false) - const [opPhenEnabled, setOpPhenEnabled] = useState(false) - const nameInput = useRef(null) - - const resetState = () => { - setSubmitted(false) - setTraceLoad(100) - setTrace(undefined) - setTopology(undefined) - setScheduler(undefined) - setFailuresEnabled(false) - setOpPhenEnabled(false) - nameInput.current.value = '' - } - - const onSubmit = (event) => { - setSubmitted(true) - - if (event) { - event.preventDefault() - } - - const name = nameInput.current.value - - onSubmitUpstream(portfolio.project.id, portfolio.number, { - name, - workload: { - trace: trace || traces[0].id, - samplingFraction: traceLoad / 100, - }, - topology: topology || topologies[0].number, - phenomena: { - failures: failuresEnabled, - interference: opPhenEnabled, - }, - schedulerName: scheduler || schedulers[0], - }) - - resetState() - return true - } - const onCancel = () => { - onCancelUpstream() - resetState() - } - - return ( - <Modal title="New Scenario" isOpen={isOpen} onSubmit={onSubmit} onCancel={onCancel}> - <Form onSubmit={onSubmit}> - <FormGroup label="Name" fieldId="name" isRequired> - <TextInput - id="name" - name="name" - type="text" - isDisabled={portfolio?.scenarios?.length === 0} - defaultValue={portfolio?.scenarios?.length === 0 ? 'Base scenario' : ''} - ref={nameInput} - /> - </FormGroup> - <FormSection title="Workload"> - <FormGroup label="Trace" fieldId="trace" isRequired> - <FormSelect id="trace" name="trace" value={trace} onChange={setTrace}> - {traces.map((trace) => ( - <FormSelectOption value={trace.id} key={trace.id} label={trace.name} /> - ))} - </FormSelect> - </FormGroup> - <FormGroup label="Load Sampling Fraction" fieldId="trace-load" isRequired> - <NumberInput - name="trace-load" - type="number" - min={0} - max={100} - value={traceLoad} - onMinus={() => setTraceLoad((load) => load - 1)} - onPlus={() => setTraceLoad((load) => load + 1)} - onChange={(e) => setTraceLoad(Number(e.target.value))} - unit="%" - /> - </FormGroup> - </FormSection> - <FormSection title="Topology"> - <FormGroup label="Topology" fieldId="topology" isRequired> - <FormSelect id="topology" name="topology" value={topology} onChange={setTopology}> - {topologies.map((topology) => ( - <FormSelectOption value={topology.number} key={topology.number} label={topology.name} /> - ))} - </FormSelect> - </FormGroup> - - <FormGroup label="Scheduler" fieldId="scheduler" isRequired> - <FormSelect id="scheduler" name="scheduler" value={scheduler} onChange={setScheduler}> - {schedulers.map((scheduler) => ( - <FormSelectOption value={scheduler} key={scheduler} label={scheduler} /> - ))} - </FormSelect> - </FormGroup> - </FormSection> - <FormSection title="Operational Phenomena"> - <Checkbox - label="Failures" - id="failures" - name="failures" - isChecked={failuresEnabled} - onChange={() => setFailuresEnabled((e) => !e)} - /> - <Checkbox - label="Performance Interference" - id="perf-interference" - name="perf-interference" - isChecked={opPhenEnabled} - onChange={() => setOpPhenEnabled((e) => !e)} - /> - </FormSection> - </Form> - </Modal> - ) -} - -NewScenarioModal.propTypes = { - projectId: PropTypes.number, - portfolioId: PropTypes.number, - isOpen: PropTypes.bool.isRequired, - onSubmit: PropTypes.func.isRequired, - onCancel: PropTypes.func.isRequired, -} - -export default NewScenarioModal diff --git a/opendc-web/opendc-web-ui/src/components/portfolios/PortfolioOverview.js b/opendc-web/opendc-web-ui/src/components/portfolios/PortfolioOverview.js deleted file mode 100644 index e561b655..00000000 --- a/opendc-web/opendc-web-ui/src/components/portfolios/PortfolioOverview.js +++ /dev/null @@ -1,120 +0,0 @@ -/* - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -import PropTypes from 'prop-types' -import { - Card, - CardActions, - CardBody, - CardHeader, - CardTitle, - Chip, - ChipGroup, - DescriptionList, - DescriptionListDescription, - DescriptionListGroup, - DescriptionListTerm, - Grid, - GridItem, - Skeleton, -} from '@patternfly/react-core' -import React from 'react' -import { usePortfolio } from '../../data/project' -import { METRIC_NAMES } from '../../util/available-metrics' -import NewScenario from './NewScenario' -import ScenarioTable from './ScenarioTable' - -function PortfolioOverview({ projectId, portfolioId }) { - const { status, data: portfolio } = usePortfolio(projectId, portfolioId) - - return ( - <Grid hasGutter> - <GridItem md={2}> - <Card> - <CardTitle>Details</CardTitle> - <CardBody> - <DescriptionList> - <DescriptionListGroup> - <DescriptionListTerm>Name</DescriptionListTerm> - <DescriptionListDescription> - {portfolio?.name ?? <Skeleton screenreaderText="Loading portfolio" />} - </DescriptionListDescription> - </DescriptionListGroup> - <DescriptionListGroup> - <DescriptionListTerm>Scenarios</DescriptionListTerm> - <DescriptionListDescription> - {portfolio?.scenarios?.length ?? <Skeleton screenreaderText="Loading portfolio" />} - </DescriptionListDescription> - </DescriptionListGroup> - <DescriptionListGroup> - <DescriptionListTerm>Metrics</DescriptionListTerm> - <DescriptionListDescription> - {portfolio ? ( - portfolio.targets.metrics.length > 0 ? ( - <ChipGroup> - {portfolio.targets.metrics.map((metric) => ( - <Chip isReadOnly key={metric}> - {METRIC_NAMES[metric]} - </Chip> - ))} - </ChipGroup> - ) : ( - 'No metrics enabled' - ) - ) : ( - <Skeleton screenreaderText="Loading portfolio" /> - )} - </DescriptionListDescription> - </DescriptionListGroup> - <DescriptionListGroup> - <DescriptionListTerm>Repeats per Scenario</DescriptionListTerm> - <DescriptionListDescription> - {portfolio?.targets?.repeats ?? <Skeleton screenreaderText="Loading portfolio" />} - </DescriptionListDescription> - </DescriptionListGroup> - </DescriptionList> - </CardBody> - </Card> - </GridItem> - <GridItem md={6}> - <Card> - <CardHeader> - <CardActions> - <NewScenario projectId={projectId} portfolioId={portfolioId} /> - </CardActions> - <CardTitle>Scenarios</CardTitle> - </CardHeader> - <CardBody> - <ScenarioTable portfolio={portfolio} status={status} /> - </CardBody> - </Card> - </GridItem> - </Grid> - ) -} - -PortfolioOverview.propTypes = { - projectId: PropTypes.number, - portfolioId: PropTypes.number, -} - -export default PortfolioOverview diff --git a/opendc-web/opendc-web-ui/src/components/portfolios/PortfolioResultInfo.js b/opendc-web/opendc-web-ui/src/components/portfolios/PortfolioResultInfo.js deleted file mode 100644 index dbfa928f..00000000 --- a/opendc-web/opendc-web-ui/src/components/portfolios/PortfolioResultInfo.js +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -import PropTypes from 'prop-types' -import { Tooltip } from '@patternfly/react-core' -import { OutlinedQuestionCircleIcon } from '@patternfly/react-icons' -import { METRIC_DESCRIPTIONS } from '../../util/available-metrics' - -function PortfolioResultInfo({ metric }) { - return ( - <Tooltip position="top" content={<div>{METRIC_DESCRIPTIONS[metric]}</div>}> - <OutlinedQuestionCircleIcon title="Metric information" /> - </Tooltip> - ) -} - -PortfolioResultInfo.propTypes = { - metric: PropTypes.string.isRequired, -} - -export default PortfolioResultInfo diff --git a/opendc-web/opendc-web-ui/src/components/portfolios/PortfolioResults.js b/opendc-web/opendc-web-ui/src/components/portfolios/PortfolioResults.js deleted file mode 100644 index 62150fa7..00000000 --- a/opendc-web/opendc-web-ui/src/components/portfolios/PortfolioResults.js +++ /dev/null @@ -1,180 +0,0 @@ -/* - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -import { mean, std } from 'mathjs' -import React, { useMemo } from 'react' -import PropTypes from 'prop-types' -import { VictoryErrorBar } from 'victory-errorbar' -import { METRIC_NAMES, METRIC_UNITS, AVAILABLE_METRICS } from '../../util/available-metrics' -import { - Bullseye, - Card, - CardActions, - CardBody, - CardHeader, - CardTitle, - EmptyState, - EmptyStateBody, - EmptyStateIcon, - Grid, - GridItem, - Spinner, - Title, -} from '@patternfly/react-core' -import { Chart, ChartAxis, ChartBar, ChartTooltip } from '@patternfly/react-charts' -import { ErrorCircleOIcon, CubesIcon } from '@patternfly/react-icons' -import { usePortfolio } from '../../data/project' -import PortfolioResultInfo from './PortfolioResultInfo' -import NewScenario from './NewScenario' - -function PortfolioResults({ projectId, portfolioId }) { - const { status, data: portfolio } = usePortfolio(projectId, portfolioId) - const scenarios = useMemo(() => portfolio?.scenarios ?? [], [portfolio]) - - const label = ({ datum }) => - `${datum.x}: ${datum.y.toLocaleString()} ± ${datum.errorY.toLocaleString()} ${METRIC_UNITS[datum.metric]}` - const selectedMetrics = new Set(portfolio?.targets?.metrics ?? []) - const dataPerMetric = useMemo(() => { - const dataPerMetric = {} - AVAILABLE_METRICS.forEach((metric) => { - dataPerMetric[metric] = scenarios - .filter((scenario) => scenario.jobs && scenario.jobs[scenario.jobs.length - 1].results) - .map((scenario) => { - const job = scenario.jobs[scenario.jobs.length - 1] - return { - metric, - x: scenario.name, - y: mean(job.results[metric]), - errorY: std(job.results[metric]), - label, - } - }) - }) - return dataPerMetric - }, [scenarios]) - - const categories = useMemo(() => ({ x: scenarios.map((s) => s.name).reverse() }), [scenarios]) - - if (status === 'loading') { - return ( - <Bullseye> - <EmptyState> - <EmptyStateIcon variant="container" component={Spinner} /> - <Title size="lg" headingLevel="h4"> - Loading Results - </Title> - </EmptyState> - </Bullseye> - ) - } else if (status === 'error') { - return ( - <Bullseye> - <EmptyState> - <EmptyStateIcon variant="container" component={ErrorCircleOIcon} /> - <Title size="lg" headingLevel="h4"> - Unable to connect - </Title> - <EmptyStateBody> - There was an error retrieving data. Check your connection and try again. - </EmptyStateBody> - </EmptyState> - </Bullseye> - ) - } else if (scenarios.length === 0) { - return ( - <Bullseye> - <EmptyState> - <EmptyStateIcon variant="container" component={CubesIcon} /> - <Title size="lg" headingLevel="h4"> - No results - </Title> - <EmptyStateBody> - No results are currently available for this portfolio. Run a scenario to obtain simulation - results. - </EmptyStateBody> - <NewScenario projectId={projectId} portfolioId={portfolioId} /> - </EmptyState> - </Bullseye> - ) - } - - return ( - <Grid hasGutter> - {AVAILABLE_METRICS.map( - (metric) => - selectedMetrics.has(metric) && ( - <GridItem xl={6} lg={12} key={metric}> - <Card> - <CardHeader> - <CardActions> - <PortfolioResultInfo metric={metric} /> - </CardActions> - <CardTitle>{METRIC_NAMES[metric]}</CardTitle> - </CardHeader> - <CardBody> - <Chart - width={650} - height={250} - padding={{ - top: 10, - bottom: 60, - left: 130, - }} - domainPadding={25} - > - <ChartAxis /> - <ChartAxis - dependentAxis - showGrid - label={METRIC_UNITS[metric]} - fixLabelOverlap - /> - <ChartBar - categories={categories} - data={dataPerMetric[metric]} - labelComponent={<ChartTooltip constrainToVisibleArea />} - barWidth={25} - horizontal - /> - <VictoryErrorBar - categories={categories} - data={dataPerMetric[metric]} - errorY={(d) => d.errorY} - labelComponent={<></>} - horizontal - /> - </Chart> - </CardBody> - </Card> - </GridItem> - ) - )} - </Grid> - ) -} - -PortfolioResults.propTypes = { - projectId: PropTypes.number, - portfolioId: PropTypes.number, -} - -export default PortfolioResults diff --git a/opendc-web/opendc-web-ui/src/components/portfolios/ScenarioState.js b/opendc-web/opendc-web-ui/src/components/portfolios/ScenarioState.js deleted file mode 100644 index 99d83f64..00000000 --- a/opendc-web/opendc-web-ui/src/components/portfolios/ScenarioState.js +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -import { ClockIcon, CheckCircleIcon, ErrorCircleOIcon } from '@patternfly/react-icons' -import { JobState } from '../../shapes' - -function ScenarioState({ state }) { - switch (state) { - case 'PENDING': - case 'CLAIMED': - return ( - <span> - <ClockIcon color="blue" /> Queued - </span> - ) - case 'RUNNING': - return ( - <span> - <ClockIcon color="green" /> Running - </span> - ) - case 'FINISHED': - return ( - <span> - <CheckCircleIcon color="green" /> Finished - </span> - ) - case 'FAILED': - return ( - <span> - <ErrorCircleOIcon color="red" /> Failed - </span> - ) - } - - return 'Unknown' -} - -ScenarioState.propTypes = { - state: JobState.isRequired, -} - -export default ScenarioState diff --git a/opendc-web/opendc-web-ui/src/components/portfolios/ScenarioTable.js b/opendc-web/opendc-web-ui/src/components/portfolios/ScenarioTable.js deleted file mode 100644 index b068d045..00000000 --- a/opendc-web/opendc-web-ui/src/components/portfolios/ScenarioTable.js +++ /dev/null @@ -1,103 +0,0 @@ -/* - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -import { Bullseye } from '@patternfly/react-core' -import Link from 'next/link' -import { TableComposable, Thead, Tr, Th, Tbody, Td, ActionsColumn } from '@patternfly/react-table' -import React from 'react' -import { Portfolio, Status } from '../../shapes' -import TableEmptyState from '../util/TableEmptyState' -import ScenarioState from './ScenarioState' -import { useDeleteScenario } from '../../data/project' - -function ScenarioTable({ portfolio, status }) { - const { mutate: deleteScenario } = useDeleteScenario() - const projectId = portfolio?.project?.id - const scenarios = portfolio?.scenarios ?? [] - - const actions = ({ number }) => [ - { - title: 'Delete Scenario', - onClick: () => deleteScenario({ projectId: projectId, number }), - isDisabled: number === 0, - }, - ] - - return ( - <TableComposable aria-label="Scenario List" variant="compact"> - <Thead> - <Tr> - <Th>Name</Th> - <Th>Topology</Th> - <Th>Trace</Th> - <Th>State</Th> - </Tr> - </Thead> - <Tbody> - {scenarios.map((scenario) => ( - <Tr key={scenario.id}> - <Td dataLabel="Name">{scenario.name}</Td> - <Td dataLabel="Topology"> - {scenario.topology ? ( - <Link href={`/projects/${projectId}/topologies/${scenario.topology.number}`}> - {scenario.topology.name} - </Link> - ) : ( - 'Unknown Topology' - )} - </Td> - <Td dataLabel="Workload">{`${scenario.workload.trace.name} (${ - scenario.workload.samplingFraction * 100 - }%)`}</Td> - <Td dataLabel="State"> - <ScenarioState state={scenario.jobs[scenario.jobs.length - 1].state} /> - </Td> - <Td isActionCell> - <ActionsColumn items={actions(scenario)} /> - </Td> - </Tr> - ))} - {scenarios.length === 0 && ( - <Tr> - <Td colSpan={4}> - <Bullseye> - <TableEmptyState - status={status} - loadingTitle="Loading Scenarios" - emptyTitle="No scenarios" - emptyText="You have not created any scenario for this portfolio yet. Click the New Scenario button to create one." - /> - </Bullseye> - </Td> - </Tr> - )} - </Tbody> - </TableComposable> - ) -} - -ScenarioTable.propTypes = { - portfolio: Portfolio, - status: Status.isRequired, -} - -export default ScenarioTable diff --git a/opendc-web/opendc-web-ui/src/components/projects/FilterPanel.js b/opendc-web/opendc-web-ui/src/components/projects/FilterPanel.js deleted file mode 100644 index 5aaa56ac..00000000 --- a/opendc-web/opendc-web-ui/src/components/projects/FilterPanel.js +++ /dev/null @@ -1,26 +0,0 @@ -import React from 'react' -import PropTypes from 'prop-types' -import { ToggleGroup, ToggleGroupItem } from '@patternfly/react-core' -import { filterPanel } from './FilterPanel.module.css' - -export const FILTERS = { SHOW_ALL: 'All Projects', SHOW_OWN: 'My Projects', SHOW_SHARED: 'Shared with me' } - -const FilterPanel = ({ onSelect, activeFilter = 'SHOW_ALL' }) => ( - <ToggleGroup className={`${filterPanel} pf-u-mb-sm`}> - {Object.keys(FILTERS).map((filter) => ( - <ToggleGroupItem - key={filter} - onChange={() => activeFilter === filter || onSelect(filter)} - isSelected={activeFilter === filter} - text={FILTERS[filter]} - /> - ))} - </ToggleGroup> -) - -FilterPanel.propTypes = { - onSelect: PropTypes.func.isRequired, - activeFilter: PropTypes.string, -} - -export default FilterPanel diff --git a/opendc-web/opendc-web-ui/src/components/projects/FilterPanel.module.css b/opendc-web/opendc-web-ui/src/components/projects/FilterPanel.module.css deleted file mode 100644 index 15c36821..00000000 --- a/opendc-web/opendc-web-ui/src/components/projects/FilterPanel.module.css +++ /dev/null @@ -1,7 +0,0 @@ -.filterPanel { - display: flex; -} - -.filterPanel > button { - flex: 1 !important; -} diff --git a/opendc-web/opendc-web-ui/src/components/projects/NewPortfolio.js b/opendc-web/opendc-web-ui/src/components/projects/NewPortfolio.js deleted file mode 100644 index aebcc3c9..00000000 --- a/opendc-web/opendc-web-ui/src/components/projects/NewPortfolio.js +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -import PropTypes from 'prop-types' -import { PlusIcon } from '@patternfly/react-icons' -import { Button } from '@patternfly/react-core' -import { useState } from 'react' -import { useNewPortfolio } from '../../data/project' -import NewPortfolioModal from './NewPortfolioModal' - -function NewPortfolio({ projectId }) { - const [isVisible, setVisible] = useState(false) - const { mutate: addPortfolio } = useNewPortfolio() - - const onSubmit = (name, targets) => { - addPortfolio({ projectId, name, targets }) - setVisible(false) - } - - return ( - <> - <Button icon={<PlusIcon />} isSmall onClick={() => setVisible(true)}> - New Portfolio - </Button> - <NewPortfolioModal isOpen={isVisible} onSubmit={onSubmit} onCancel={() => setVisible(false)} /> - </> - ) -} - -NewPortfolio.propTypes = { - projectId: PropTypes.number, -} - -export default NewPortfolio diff --git a/opendc-web/opendc-web-ui/src/components/projects/NewPortfolioModal.js b/opendc-web/opendc-web-ui/src/components/projects/NewPortfolioModal.js deleted file mode 100644 index ba4bc819..00000000 --- a/opendc-web/opendc-web-ui/src/components/projects/NewPortfolioModal.js +++ /dev/null @@ -1,161 +0,0 @@ -/* - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -import PropTypes from 'prop-types' -import React, { useRef, useState } from 'react' -import { - Form, - FormGroup, - FormSection, - NumberInput, - Select, - SelectGroup, - SelectOption, - SelectVariant, - TextInput, -} from '@patternfly/react-core' -import Modal from '../util/modals/Modal' -import { METRIC_GROUPS, METRIC_NAMES } from '../../util/available-metrics' - -const NewPortfolioModal = ({ isOpen, onSubmit: onSubmitUpstream, onCancel: onUpstreamCancel }) => { - const nameInput = useRef(null) - const [repeats, setRepeats] = useState(1) - const [isSelectOpen, setSelectOpen] = useState(false) - const [selectedMetrics, setSelectedMetrics] = useState([]) - - const [isSubmitted, setSubmitted] = useState(false) - const [errors, setErrors] = useState({}) - - const clearState = () => { - setSubmitted(false) - setErrors({}) - nameInput.current.value = '' - setRepeats(1) - setSelectOpen(false) - setSelectedMetrics([]) - } - - const onSubmit = (event) => { - setSubmitted(true) - - if (event) { - event.preventDefault() - } - - const name = nameInput.current.value - - if (!name) { - setErrors({ name: true }) - return false - } else { - onSubmitUpstream(name, { metrics: selectedMetrics, repeats }) - } - - clearState() - return false - } - const onCancel = () => { - onUpstreamCancel() - clearState() - } - - const onSelect = (event, selection) => { - if (selectedMetrics.includes(selection)) { - setSelectedMetrics((metrics) => metrics.filter((item) => item !== selection)) - } else { - setSelectedMetrics((metrics) => [...metrics, selection]) - } - } - - return ( - <Modal title="New Portfolio" isOpen={isOpen} onSubmit={onSubmit} onCancel={onCancel}> - <Form onSubmit={onSubmit}> - <FormSection> - <FormGroup - label="Name" - fieldId="name" - isRequired - validated={isSubmitted && errors.name ? 'error' : 'default'} - helperTextInvalid="This field cannot be empty" - > - <TextInput - name="name" - id="name" - type="text" - isRequired - ref={nameInput} - placeholder="My Portfolio" - /> - </FormGroup> - </FormSection> - <FormSection title="Targets" titleElement="h4"> - <FormGroup label="Metrics" fieldId="metrics"> - <Select - variant={SelectVariant.typeaheadMulti} - typeAheadAriaLabel="Select a metric" - onToggle={() => setSelectOpen(!isSelectOpen)} - onSelect={onSelect} - onClear={() => setSelectedMetrics([])} - selections={selectedMetrics} - isOpen={isSelectOpen} - placeholderText="Select a metric" - menuAppendTo="parent" - maxHeight="300px" - chipGroupProps={{ numChips: 1 }} - isGrouped - > - {Object.entries(METRIC_GROUPS).map(([group, metrics]) => ( - <SelectGroup label={group} key={group}> - {metrics.map((metric) => ( - <SelectOption key={metric} value={metric}> - {METRIC_NAMES[metric]} - </SelectOption> - ))} - </SelectGroup> - ))} - </Select> - </FormGroup> - <FormGroup label="Repeats per Scenario" fieldId="repeats"> - <NumberInput - id="repeats" - inputName="repeats" - type="number" - value={repeats} - onChange={(e) => setRepeats(Number(e.target.value))} - onPlus={() => setRepeats((r) => r + 1)} - onMinus={() => setRepeats((r) => r - 1)} - min={1} - /> - </FormGroup> - </FormSection> - </Form> - </Modal> - ) -} - -NewPortfolioModal.propTypes = { - isOpen: PropTypes.bool.isRequired, - onSubmit: PropTypes.func.isRequired, - onCancel: PropTypes.func.isRequired, -} - -export default NewPortfolioModal diff --git a/opendc-web/opendc-web-ui/src/components/projects/NewTopology.js b/opendc-web/opendc-web-ui/src/components/projects/NewTopology.js deleted file mode 100644 index 4c569c56..00000000 --- a/opendc-web/opendc-web-ui/src/components/projects/NewTopology.js +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -import PropTypes from 'prop-types' -import { PlusIcon } from '@patternfly/react-icons' -import { Button } from '@patternfly/react-core' -import { useState } from 'react' -import { useNewTopology } from '../../data/topology' -import NewTopologyModal from './NewTopologyModal' - -function NewTopology({ projectId }) { - const [isVisible, setVisible] = useState(false) - const { mutate: addTopology } = useNewTopology() - - const onSubmit = (topology) => { - addTopology(topology) - setVisible(false) - } - return ( - <> - <Button icon={<PlusIcon />} isSmall onClick={() => setVisible(true)}> - New Topology - </Button> - <NewTopologyModal - projectId={projectId} - isOpen={isVisible} - onSubmit={onSubmit} - onCancel={() => setVisible(false)} - /> - </> - ) -} - -NewTopology.propTypes = { - projectId: PropTypes.number, -} - -export default NewTopology diff --git a/opendc-web/opendc-web-ui/src/components/projects/NewTopologyModal.js b/opendc-web/opendc-web-ui/src/components/projects/NewTopologyModal.js deleted file mode 100644 index 780ec034..00000000 --- a/opendc-web/opendc-web-ui/src/components/projects/NewTopologyModal.js +++ /dev/null @@ -1,115 +0,0 @@ -/* - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -import produce from 'immer' -import PropTypes from 'prop-types' -import React, { useRef, useState } from 'react' -import { Form, FormGroup, FormSelect, FormSelectOption, TextInput } from '@patternfly/react-core' -import { useTopologies } from '../../data/topology' -import Modal from '../util/modals/Modal' - -const NewTopologyModal = ({ projectId, isOpen, onSubmit: onSubmitUpstream, onCancel: onCancelUpstream }) => { - const nameInput = useRef(null) - const [isSubmitted, setSubmitted] = useState(false) - const [originTopology, setOriginTopology] = useState(-1) - const [errors, setErrors] = useState({}) - - const { data: topologies = [] } = useTopologies(projectId, { enabled: isOpen }) - - const clearState = () => { - if (nameInput.current) { - nameInput.current.value = '' - } - setSubmitted(false) - setOriginTopology(-1) - setErrors({}) - } - - const onSubmit = (event) => { - setSubmitted(true) - - if (event) { - event.preventDefault() - } - - const name = nameInput.current.value - - if (!name) { - setErrors({ name: true }) - return false - } else { - const candidate = topologies.find((topology) => topology.id === originTopology) || { rooms: [] } - const topology = produce(candidate, (draft) => { - delete draft.project - draft.projectId = projectId - draft.name = name - }) - onSubmitUpstream(topology) - } - - clearState() - return true - } - - const onCancel = () => { - onCancelUpstream() - clearState() - } - - return ( - <Modal title="New Topology" isOpen={isOpen} onSubmit={onSubmit} onCancel={onCancel}> - <Form onSubmit={onSubmit}> - <FormGroup - label="Name" - fieldId="name" - isRequired - validated={isSubmitted && errors.name ? 'error' : 'default'} - helperTextInvalid="This field cannot be empty" - > - <TextInput id="name" name="name" type="text" isRequired ref={nameInput} /> - </FormGroup> - <FormGroup label="Topology to duplicate" fieldId="origin" isRequired> - <FormSelect - id="origin" - name="origin" - value={originTopology} - onChange={(v) => setOriginTopology(+v)} - > - <FormSelectOption value={-1} key={-1} label="None - start from scratch" /> - {topologies.map((topology) => ( - <FormSelectOption value={topology.id} key={topology.id} label={topology.name} /> - ))} - </FormSelect> - </FormGroup> - </Form> - </Modal> - ) -} - -NewTopologyModal.propTypes = { - projectId: PropTypes.number, - isOpen: PropTypes.bool.isRequired, - onSubmit: PropTypes.func.isRequired, - onCancel: PropTypes.func.isRequired, -} - -export default NewTopologyModal diff --git a/opendc-web/opendc-web-ui/src/components/projects/PortfolioTable.js b/opendc-web/opendc-web-ui/src/components/projects/PortfolioTable.js deleted file mode 100644 index 0afeaeaf..00000000 --- a/opendc-web/opendc-web-ui/src/components/projects/PortfolioTable.js +++ /dev/null @@ -1,99 +0,0 @@ -/* - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -import { Bullseye } from '@patternfly/react-core' -import PropTypes from 'prop-types' -import Link from 'next/link' -import { TableComposable, Thead, Tbody, Tr, Th, Td, ActionsColumn } from '@patternfly/react-table' -import React from 'react' -import TableEmptyState from '../util/TableEmptyState' -import { usePortfolios, useDeletePortfolio } from '../../data/project' - -function PortfolioTable({ projectId }) { - const { status, data: portfolios = [] } = usePortfolios(projectId) - const { mutate: deletePortfolio } = useDeletePortfolio() - - const actions = (portfolio) => [ - { - title: 'Delete Portfolio', - onClick: () => deletePortfolio({ projectId, number: portfolio.number }), - }, - ] - - return ( - <TableComposable aria-label="Portfolio List" variant="compact"> - <Thead> - <Tr> - <Th>Name</Th> - <Th>Scenarios</Th> - <Th>Metrics</Th> - <Th>Repeats</Th> - </Tr> - </Thead> - <Tbody> - {portfolios.map((portfolio) => ( - <Tr key={portfolio.id}> - <Td dataLabel="Name"> - <Link href={`/projects/${projectId}/portfolios/${portfolio.number}`}>{portfolio.name}</Link> - </Td> - <Td dataLabel="Scenarios"> - {portfolio.scenarios.length === 1 - ? '1 scenario' - : `${portfolio.scenarios.length} scenarios`} - </Td> - <Td dataLabel="Metrics"> - {portfolio.targets.metrics.length === 1 - ? '1 metric' - : `${portfolio.targets.metrics.length} metrics`} - </Td> - <Td dataLabel="Repeats"> - {portfolio.targets.repeats === 1 ? '1 repeat' : `${portfolio.targets.repeats} repeats`} - </Td> - <Td isActionCell> - <ActionsColumn items={actions(portfolio)} /> - </Td> - </Tr> - ))} - {portfolios.length === 0 && ( - <Tr> - <Td colSpan={4}> - <Bullseye> - <TableEmptyState - status={status} - loadingTitle="Loading portfolios" - emptyTitle="No portfolios" - emptyText="You have not created any portfolio for this project yet. Click the New Portfolio button to create one." - /> - </Bullseye> - </Td> - </Tr> - )} - </Tbody> - </TableComposable> - ) -} - -PortfolioTable.propTypes = { - projectId: PropTypes.number, -} - -export default PortfolioTable diff --git a/opendc-web/opendc-web-ui/src/components/projects/ProjectCollection.js b/opendc-web/opendc-web-ui/src/components/projects/ProjectCollection.js deleted file mode 100644 index a26fed46..00000000 --- a/opendc-web/opendc-web-ui/src/components/projects/ProjectCollection.js +++ /dev/null @@ -1,137 +0,0 @@ -import Link from 'next/link' -import { - Gallery, - Bullseye, - EmptyState, - EmptyStateIcon, - Card, - CardTitle, - CardActions, - DropdownItem, - CardHeader, - Dropdown, - KebabToggle, - CardBody, - CardHeaderMain, - TextVariants, - Text, - TextContent, - Tooltip, - Button, - Label, -} from '@patternfly/react-core' -import { PlusIcon, FolderIcon, TrashIcon } from '@patternfly/react-icons' -import PropTypes from 'prop-types' -import React, { useReducer, useMemo } from 'react' -import { Project, Status } from '../../shapes' -import { parseAndFormatDateTime } from '../../util/date-time' -import { AUTH_DESCRIPTION_MAP, AUTH_ICON_MAP, AUTH_NAME_MAP } from '../../util/authorizations' -import TableEmptyState from '../util/TableEmptyState' - -function ProjectCard({ project, onDelete }) { - const [isKebabOpen, toggleKebab] = useReducer((t) => !t, false) - const { id, role, name, updatedAt } = project - const Icon = AUTH_ICON_MAP[role] - - return ( - <Card - isCompact - isRounded - isFlat - className="pf-u-min-height" - style={{ '--pf-u-min-height--MinHeight': '175px' }} - > - <CardHeader className="pf-u-flex-grow-1"> - <CardHeaderMain className="pf-u-align-self-flex-start"> - <FolderIcon /> - </CardHeaderMain> - <CardActions> - <Tooltip content={AUTH_DESCRIPTION_MAP[role]}> - <Label icon={<Icon />}>{AUTH_NAME_MAP[role]}</Label> - </Tooltip> - <Dropdown - isPlain - position="right" - toggle={<KebabToggle className="pf-u-px-0" onToggle={toggleKebab} />} - isOpen={isKebabOpen} - dropdownItems={[ - <DropdownItem - key="trash" - onClick={() => { - onDelete() - toggleKebab() - }} - position="right" - icon={<TrashIcon />} - > - Delete - </DropdownItem>, - ]} - /> - </CardActions> - </CardHeader> - <CardTitle component={Link} className="pf-u-pb-0" href={`/projects/${id}`}> - {name} - </CardTitle> - <CardBody isFilled={false}> - <TextContent> - <Text component={TextVariants.small}>Last modified {parseAndFormatDateTime(updatedAt)}</Text> - </TextContent> - </CardBody> - </Card> - ) -} - -function ProjectCollection({ status, projects, onDelete, onCreate, isFiltering }) { - const sortedProjects = useMemo(() => { - const res = [...projects] - res.sort((a, b) => (new Date(a.updatedAt) < new Date(b.updatedAt) ? 1 : -1)) - return res - }, [projects]) - - if (sortedProjects.length === 0) { - return ( - <TableEmptyState - status={status} - isFiltering={isFiltering} - loadingTitle="Loading Projects" - emptyTitle="No projects" - emptyText="You have not created any projects yet. Create a new project to get started quickly." - emptyAction={ - <Button icon={<PlusIcon />} onClick={onCreate}> - Create Project - </Button> - } - /> - ) - } - - return ( - <Gallery hasGutter aria-label="Available projects"> - {sortedProjects.map((project) => ( - <ProjectCard key={project.id} project={project} onDelete={() => onDelete(project)} /> - ))} - <Card isCompact isFlat isRounded style={{ borderStyle: 'dotted' }}> - <Bullseye> - <EmptyState> - <Button isBlock variant="link" onClick={onCreate}> - <EmptyStateIcon icon={PlusIcon} /> - <br /> - Create Project - </Button> - </EmptyState> - </Bullseye> - </Card> - </Gallery> - ) -} - -ProjectCollection.propTypes = { - status: Status.isRequired, - isFiltering: PropTypes.bool, - projects: PropTypes.arrayOf(Project).isRequired, - onDelete: PropTypes.func, - onCreate: PropTypes.func, -} - -export default ProjectCollection diff --git a/opendc-web/opendc-web-ui/src/components/projects/ProjectOverview.js b/opendc-web/opendc-web-ui/src/components/projects/ProjectOverview.js deleted file mode 100644 index 3e1656f6..00000000 --- a/opendc-web/opendc-web-ui/src/components/projects/ProjectOverview.js +++ /dev/null @@ -1,98 +0,0 @@ -/* - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -import PropTypes from 'prop-types' -import { - Card, - CardActions, - CardBody, - CardHeader, - CardTitle, - DescriptionList, - DescriptionListDescription, - DescriptionListGroup, - DescriptionListTerm, - Grid, - GridItem, - Skeleton, -} from '@patternfly/react-core' -import NewTopology from './NewTopology' -import TopologyTable from './TopologyTable' -import NewPortfolio from './NewPortfolio' -import PortfolioTable from './PortfolioTable' -import { useProject } from '../../data/project' - -function ProjectOverview({ projectId }) { - const { data: project } = useProject(projectId) - - return ( - <Grid hasGutter> - <GridItem md={2}> - <Card> - <CardTitle>Details</CardTitle> - <CardBody> - <DescriptionList> - <DescriptionListGroup> - <DescriptionListTerm>Name</DescriptionListTerm> - <DescriptionListDescription> - {project?.name ?? <Skeleton screenreaderText="Loading project" />} - </DescriptionListDescription> - </DescriptionListGroup> - </DescriptionList> - </CardBody> - </Card> - </GridItem> - <GridItem md={5}> - <Card> - <CardHeader> - <CardActions> - <NewTopology projectId={projectId} /> - </CardActions> - <CardTitle>Topologies</CardTitle> - </CardHeader> - <CardBody> - <TopologyTable projectId={projectId} /> - </CardBody> - </Card> - </GridItem> - <GridItem md={5}> - <Card> - <CardHeader> - <CardActions> - <NewPortfolio projectId={projectId} /> - </CardActions> - <CardTitle>Portfolios</CardTitle> - </CardHeader> - <CardBody> - <PortfolioTable projectId={projectId} /> - </CardBody> - </Card> - </GridItem> - </Grid> - ) -} - -ProjectOverview.propTypes = { - projectId: PropTypes.number, -} - -export default ProjectOverview diff --git a/opendc-web/opendc-web-ui/src/components/projects/TopologyTable.js b/opendc-web/opendc-web-ui/src/components/projects/TopologyTable.js deleted file mode 100644 index 1c2c4f04..00000000 --- a/opendc-web/opendc-web-ui/src/components/projects/TopologyTable.js +++ /dev/null @@ -1,115 +0,0 @@ -/* - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -import { Bullseye, AlertGroup, Alert, AlertVariant, AlertActionCloseButton } from '@patternfly/react-core' -import PropTypes from 'prop-types' -import Link from 'next/link' -import { Tr, Th, Thead, Td, ActionsColumn, Tbody, TableComposable } from '@patternfly/react-table' -import React, { useState } from 'react' -import TableEmptyState from '../util/TableEmptyState' -import { parseAndFormatDateTime } from '../../util/date-time' -import { useTopologies, useDeleteTopology } from '../../data/topology' - -function TopologyTable({ projectId }) { - const [error, setError] = useState('') - - const { status, data: topologies = [] } = useTopologies(projectId) - const { mutate: deleteTopology } = useDeleteTopology({ - onError: (error) => setError(error), - }) - - const actions = ({ number }) => [ - { - title: 'Delete Topology', - onClick: () => deleteTopology({ projectId, number }), - isDisabled: number === 0, - }, - ] - - return ( - <> - <AlertGroup isToast> - {error && ( - <Alert - isLiveRegion - variant={AlertVariant.danger} - title={error} - actionClose={ - <AlertActionCloseButton - title={error} - variantLabel="danger alert" - onClose={() => setError(null)} - /> - } - /> - )} - </AlertGroup> - <TableComposable aria-label="Topology List" variant="compact"> - <Thead> - <Tr> - <Th>Name</Th> - <Th>Rooms</Th> - <Th>Last Edited</Th> - </Tr> - </Thead> - <Tbody> - {topologies.map((topology) => ( - <Tr key={topology.id}> - <Td dataLabel="Name"> - <Link href={`/projects/${projectId}/topologies/${topology.number}`}> - {topology.name} - </Link> - </Td> - <Td dataLabel="Rooms"> - {topology.rooms.length === 1 ? '1 room' : `${topology.rooms.length} rooms`} - </Td> - <Td dataLabel="Last Edited">{parseAndFormatDateTime(topology.updatedAt)}</Td> - <Td isActionCell> - <ActionsColumn items={actions(topology)} /> - </Td> - </Tr> - ))} - {topologies.length === 0 && ( - <Tr> - <Td colSpan={3}> - <Bullseye> - <TableEmptyState - status={status} - loadingTitle="Loading topologies" - emptyTitle="No topologies" - emptyText="You have not created any topology for this project yet. Click the New Topology button to create one." - /> - </Bullseye> - </Td> - </Tr> - )} - </Tbody> - </TableComposable> - </> - ) -} - -TopologyTable.propTypes = { - projectId: PropTypes.number, -} - -export default TopologyTable diff --git a/opendc-web/opendc-web-ui/src/components/topologies/RoomTable.js b/opendc-web/opendc-web-ui/src/components/topologies/RoomTable.js deleted file mode 100644 index 7f7b4171..00000000 --- a/opendc-web/opendc-web-ui/src/components/topologies/RoomTable.js +++ /dev/null @@ -1,74 +0,0 @@ -import { Button, Bullseye } from '@patternfly/react-core' -import PropTypes from 'prop-types' -import React from 'react' -import { useDispatch } from 'react-redux' -import { useTopology } from '../../data/topology' -import { Tr, Th, Thead, TableComposable, Td, ActionsColumn, Tbody } from '@patternfly/react-table' -import { deleteRoom } from '../../redux/actions/topology/room' -import TableEmptyState from '../util/TableEmptyState' - -function RoomTable({ projectId, topologyId, onSelect }) { - const dispatch = useDispatch() - const { status, data: topology } = useTopology(projectId, topologyId) - const onDelete = (room) => dispatch(deleteRoom(room.id)) - const actions = (room) => [ - { - title: 'Delete room', - onClick: () => onDelete(room), - }, - ] - - return ( - <TableComposable aria-label="Room list" variant="compact"> - <Thead> - <Tr> - <Th>Name</Th> - <Th>Tiles</Th> - <Th>Racks</Th> - </Tr> - </Thead> - <Tbody> - {topology?.rooms.map((room) => { - const tileCount = room.tiles.length - const rackCount = room.tiles.filter((tile) => tile.rack).length - return ( - <Tr key={room.id}> - <Td dataLabel="Name"> - <Button variant="link" isInline onClick={() => onSelect(room)}> - {room.name} - </Button> - </Td> - <Td dataLabel="Tiles">{tileCount === 1 ? '1 tile' : `${tileCount} tiles`}</Td> - <Td dataLabel="Racks">{rackCount === 1 ? '1 rack' : `${rackCount} racks`}</Td> - <Td isActionCell> - <ActionsColumn items={actions(room)} /> - </Td> - </Tr> - ) - })} - {topology?.rooms.length === 0 && ( - <Tr> - <Td colSpan={4}> - <Bullseye> - <TableEmptyState - status={status} - loadingTitle="Loading Rooms" - emptyTitle="No rooms" - emptyText="There are currently no rooms in this topology. Open the Floor Plan to create a room" - /> - </Bullseye> - </Td> - </Tr> - )} - </Tbody> - </TableComposable> - ) -} - -RoomTable.propTypes = { - projectId: PropTypes.number, - topologyId: PropTypes.number, - onSelect: PropTypes.func, -} - -export default RoomTable diff --git a/opendc-web/opendc-web-ui/src/components/topologies/TopologyMap.js b/opendc-web/opendc-web-ui/src/components/topologies/TopologyMap.js deleted file mode 100644 index ff583750..00000000 --- a/opendc-web/opendc-web-ui/src/components/topologies/TopologyMap.js +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -import React, { useState, useRef } from 'react' -import { - Bullseye, - Drawer, - DrawerContent, - DrawerContentBody, - EmptyState, - EmptyStateIcon, - Spinner, - Title, -} from '@patternfly/react-core' -import MapStage from './map/MapStage' -import Collapse from './map/controls/Collapse' -import { useSelector } from 'react-redux' -import TopologySidebar from './sidebar/TopologySidebar' - -function TopologyMap() { - const topologyIsLoading = useSelector((state) => !state.topology.root) - const interactionLevel = useSelector((state) => state.interactionLevel) - - const [isExpanded, setExpanded] = useState(true) - const panelContent = <TopologySidebar interactionLevel={interactionLevel} onClose={() => setExpanded(false)} /> - - const hotkeysRef = useRef() - - return topologyIsLoading ? ( - <Bullseye> - <EmptyState> - <EmptyStateIcon variant="container" component={Spinner} /> - <Title size="lg" headingLevel="h4"> - Loading Topology - </Title> - </EmptyState> - </Bullseye> - ) : ( - <Drawer isExpanded={isExpanded}> - <DrawerContent panelContent={panelContent}> - <DrawerContentBody style={{ position: 'relative' }}> - <MapStage hotkeysRef={hotkeysRef} /> - <Collapse onClick={() => setExpanded(true)} /> - </DrawerContentBody> - </DrawerContent> - </Drawer> - ) -} - -export default TopologyMap diff --git a/opendc-web/opendc-web-ui/src/components/topologies/TopologyOverview.js b/opendc-web/opendc-web-ui/src/components/topologies/TopologyOverview.js deleted file mode 100644 index f8ee4990..00000000 --- a/opendc-web/opendc-web-ui/src/components/topologies/TopologyOverview.js +++ /dev/null @@ -1,92 +0,0 @@ -/* - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -import PropTypes from 'prop-types' -import { - Card, - CardBody, - CardTitle, - DescriptionList, - DescriptionListDescription, - DescriptionListGroup, - DescriptionListTerm, - Grid, - GridItem, - Skeleton, -} from '@patternfly/react-core' -import React from 'react' -import { useTopology } from '../../data/topology' -import { parseAndFormatDateTime } from '../../util/date-time' -import RoomTable from './RoomTable' - -function TopologyOverview({ projectId, topologyNumber, onSelect }) { - const { data: topology } = useTopology(projectId, topologyNumber) - return ( - <Grid hasGutter> - <GridItem md={2}> - <Card> - <CardTitle>Details</CardTitle> - <CardBody> - <DescriptionList> - <DescriptionListGroup> - <DescriptionListTerm>Name</DescriptionListTerm> - <DescriptionListDescription> - {topology?.name ?? <Skeleton screenreaderText="Loading topology" />} - </DescriptionListDescription> - </DescriptionListGroup> - <DescriptionListGroup> - <DescriptionListTerm>Last edited</DescriptionListTerm> - <DescriptionListDescription> - {topology ? ( - parseAndFormatDateTime(topology.updatedAt) - ) : ( - <Skeleton screenreaderText="Loading topology" /> - )} - </DescriptionListDescription> - </DescriptionListGroup> - </DescriptionList> - </CardBody> - </Card> - </GridItem> - <GridItem md={5}> - <Card> - <CardTitle>Rooms</CardTitle> - <CardBody> - <RoomTable - projectId={projectId} - topologyId={topologyNumber} - onSelect={(room) => onSelect('room', room)} - /> - </CardBody> - </Card> - </GridItem> - </Grid> - ) -} - -TopologyOverview.propTypes = { - projectId: PropTypes.number, - topologyNumber: PropTypes.number, - onSelect: PropTypes.func, -} - -export default TopologyOverview diff --git a/opendc-web/opendc-web-ui/src/components/topologies/map/GrayContainer.js b/opendc-web/opendc-web-ui/src/components/topologies/map/GrayContainer.js deleted file mode 100644 index ccf637e5..00000000 --- a/opendc-web/opendc-web-ui/src/components/topologies/map/GrayContainer.js +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -import React from 'react' -import { useDispatch } from 'react-redux' -import { goDownOneInteractionLevel } from '../../../redux/actions/interaction-level' -import GrayLayer from './elements/GrayLayer' - -function GrayContainer() { - const dispatch = useDispatch() - const onClick = () => dispatch(goDownOneInteractionLevel()) - return <GrayLayer onClick={onClick} /> -} - -export default GrayContainer diff --git a/opendc-web/opendc-web-ui/src/components/topologies/map/MapConstants.js b/opendc-web/opendc-web-ui/src/components/topologies/map/MapConstants.js deleted file mode 100644 index 4c3b2757..00000000 --- a/opendc-web/opendc-web-ui/src/components/topologies/map/MapConstants.js +++ /dev/null @@ -1,25 +0,0 @@ -export const MAP_SIZE = 50 -export const TILE_SIZE_IN_PIXELS = 100 -export const TILE_SIZE_IN_METERS = 0.5 -export const MAP_SIZE_IN_PIXELS = MAP_SIZE * TILE_SIZE_IN_PIXELS - -export const OBJECT_MARGIN_IN_PIXELS = TILE_SIZE_IN_PIXELS / 5 -export const TILE_PLUS_MARGIN_IN_PIXELS = TILE_SIZE_IN_PIXELS / 3 -export const OBJECT_SIZE_IN_PIXELS = TILE_SIZE_IN_PIXELS - OBJECT_MARGIN_IN_PIXELS * 2 - -export const GRID_LINE_WIDTH_IN_PIXELS = 2 -export const WALL_WIDTH_IN_PIXELS = TILE_SIZE_IN_PIXELS / 16 -export const OBJECT_BORDER_WIDTH_IN_PIXELS = TILE_SIZE_IN_PIXELS / 16 -export const TILE_PLUS_WIDTH_IN_PIXELS = TILE_SIZE_IN_PIXELS / 10 - -export const RACK_FILL_ICON_WIDTH = OBJECT_SIZE_IN_PIXELS / 3 -export const RACK_FILL_ICON_OPACITY = 0.8 - -export const MAP_MOVE_PIXELS_PER_EVENT = 20 -export const MAP_SCALE_PER_EVENT = 1.1 -export const MAP_MIN_SCALE = 0.5 -export const MAP_MAX_SCALE = 1.5 - -export const MAX_NUM_UNITS_PER_MACHINE = 6 -export const DEFAULT_RACK_SLOT_CAPACITY = 42 -export const DEFAULT_RACK_POWER_CAPACITY = 10000 diff --git a/opendc-web/opendc-web-ui/src/components/topologies/map/MapStage.js b/opendc-web/opendc-web-ui/src/components/topologies/map/MapStage.js deleted file mode 100644 index e2b626ec..00000000 --- a/opendc-web/opendc-web-ui/src/components/topologies/map/MapStage.js +++ /dev/null @@ -1,83 +0,0 @@ -import React, { useRef, useState } from 'react' -import PropTypes from 'prop-types' -import { useHotkeys } from 'react-hotkeys-hook' -import { Stage } from 'react-konva' -import { MAP_MAX_SCALE, MAP_MIN_SCALE, MAP_MOVE_PIXELS_PER_EVENT, MAP_SCALE_PER_EVENT } from './MapConstants' -import useResizeObserver from 'use-resize-observer' -import { mapContainer } from './MapStage.module.css' -import MapLayer from './layers/MapLayer' -import RoomHoverLayer from './layers/RoomHoverLayer' -import ObjectHoverLayer from './layers/ObjectHoverLayer' -import ScaleIndicator from './controls/ScaleIndicator' -import Toolbar from './controls/Toolbar' - -function MapStage({ hotkeysRef }) { - const stageRef = useRef(null) - const { width = 500, height = 500 } = useResizeObserver({ ref: stageRef.current?.attrs?.container }) - const [[x, y], setPos] = useState([0, 0]) - const [scale, setScale] = useState(1) - - const clampScale = (target) => Math.min(Math.max(target, MAP_MIN_SCALE), MAP_MAX_SCALE) - const moveWithDelta = (deltaX, deltaY) => setPos(([x, y]) => [x + deltaX, y + deltaY]) - - const onZoom = (e) => { - e.evt.preventDefault() - - const stage = stageRef.current.getStage() - const oldScale = scale - - const pointer = stage.getPointerPosition() - const mousePointTo = { - x: (pointer.x - x) / oldScale, - y: (pointer.y - y) / oldScale, - } - - const newScale = clampScale(e.evt.deltaY > 0 ? oldScale * MAP_SCALE_PER_EVENT : oldScale / MAP_SCALE_PER_EVENT) - - setScale(newScale) - setPos([pointer.x - mousePointTo.x * newScale, pointer.y - mousePointTo.y * newScale]) - } - const onZoomButton = (zoomIn) => - setScale((scale) => clampScale(zoomIn ? scale * MAP_SCALE_PER_EVENT : scale / MAP_SCALE_PER_EVENT)) - const onDragEnd = (e) => setPos([e.target.x(), e.target.y()]) - const onExport = () => { - const download = document.createElement('a') - download.href = stageRef.current.getStage().toDataURL() - download.download = 'opendc-canvas-export-' + Date.now() + '.png' - download.click() - } - - useHotkeys('left, a', () => moveWithDelta(MAP_MOVE_PIXELS_PER_EVENT, 0), { element: hotkeysRef.current }) - useHotkeys('right, d', () => moveWithDelta(-MAP_MOVE_PIXELS_PER_EVENT, 0), { element: hotkeysRef.current }) - useHotkeys('up, w', () => moveWithDelta(0, MAP_MOVE_PIXELS_PER_EVENT), { element: hotkeysRef.current }) - useHotkeys('down, s', () => moveWithDelta(0, -MAP_MOVE_PIXELS_PER_EVENT), { element: hotkeysRef.current }) - - return ( - <> - <Stage - className={mapContainer} - ref={stageRef} - onWheel={onZoom} - onDragEnd={onDragEnd} - draggable - width={width} - height={height} - scale={{ x: scale, y: scale }} - x={x} - y={y} - > - <MapLayer /> - <RoomHoverLayer /> - <ObjectHoverLayer /> - </Stage> - <ScaleIndicator scale={scale} /> - <Toolbar onZoom={onZoomButton} onExport={onExport} /> - </> - ) -} - -MapStage.propTypes = { - hotkeysRef: PropTypes.object.isRequired, -} - -export default MapStage diff --git a/opendc-web/opendc-web-ui/src/components/topologies/map/MapStage.module.css b/opendc-web/opendc-web-ui/src/components/topologies/map/MapStage.module.css deleted file mode 100644 index 47c3dde2..00000000 --- a/opendc-web/opendc-web-ui/src/components/topologies/map/MapStage.module.css +++ /dev/null @@ -1,29 +0,0 @@ -/*! - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -.mapContainer { - background-color: var(--pf-global--Color--light-200); - position: relative; - display: flex; - width: 100%; - height: 100%; -} diff --git a/opendc-web/opendc-web-ui/src/components/topologies/map/RackContainer.js b/opendc-web/opendc-web-ui/src/components/topologies/map/RackContainer.js deleted file mode 100644 index 14449a91..00000000 --- a/opendc-web/opendc-web-ui/src/components/topologies/map/RackContainer.js +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -import React from 'react' -import { useSelector } from 'react-redux' -import { Tile } from '../../../shapes' -import RackGroup from './groups/RackGroup' - -function RackContainer({ tile }) { - const interactionLevel = useSelector((state) => state.interactionLevel) - return <RackGroup interactionLevel={interactionLevel} tile={tile} /> -} - -RackContainer.propTypes = { - tile: Tile, -} - -export default RackContainer diff --git a/opendc-web/opendc-web-ui/src/components/topologies/map/RackEnergyFillContainer.js b/opendc-web/opendc-web-ui/src/components/topologies/map/RackEnergyFillContainer.js deleted file mode 100644 index a1ca7426..00000000 --- a/opendc-web/opendc-web-ui/src/components/topologies/map/RackEnergyFillContainer.js +++ /dev/null @@ -1,36 +0,0 @@ -import React from 'react' -import PropTypes from 'prop-types' -import { useSelector } from 'react-redux' -import RackFillBar from './elements/RackFillBar' - -function RackSpaceFillContainer({ rackId, ...props }) { - const fillFraction = useSelector((state) => { - const rack = state.topology.racks[rackId] - if (!rack) { - return 0 - } - - const { machines, cpus, gpus, memories, storages } = state.topology - let energyConsumptionTotal = 0 - - for (const machineId of rack.machines) { - if (!machineId) { - continue - } - const machine = machines[machineId] - machine.cpus.forEach((id) => (energyConsumptionTotal += cpus[id].energyConsumptionW)) - machine.gpus.forEach((id) => (energyConsumptionTotal += gpus[id].energyConsumptionW)) - machine.memories.forEach((id) => (energyConsumptionTotal += memories[id].energyConsumptionW)) - machine.storages.forEach((id) => (energyConsumptionTotal += storages[id].energyConsumptionW)) - } - - return Math.min(1, energyConsumptionTotal / rack.powerCapacityW) - }) - return <RackFillBar {...props} type="energy" fillFraction={fillFraction} /> -} - -RackSpaceFillContainer.propTypes = { - rackId: PropTypes.string.isRequired, -} - -export default RackSpaceFillContainer diff --git a/opendc-web/opendc-web-ui/src/components/topologies/map/RackSpaceFillContainer.js b/opendc-web/opendc-web-ui/src/components/topologies/map/RackSpaceFillContainer.js deleted file mode 100644 index 2039a9d3..00000000 --- a/opendc-web/opendc-web-ui/src/components/topologies/map/RackSpaceFillContainer.js +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -import React from 'react' -import PropTypes from 'prop-types' -import { useSelector } from 'react-redux' -import RackFillBar from './elements/RackFillBar' - -function RackSpaceFillContainer({ rackId, ...props }) { - const rack = useSelector((state) => state.topology.racks[rackId]) - - if (!rack) { - return null - } - - return <RackFillBar {...props} type="space" fillFraction={rack.machines.length / rack.capacity} /> -} - -RackSpaceFillContainer.propTypes = { - rackId: PropTypes.string.isRequired, -} - -export default RackSpaceFillContainer diff --git a/opendc-web/opendc-web-ui/src/components/topologies/map/RoomContainer.js b/opendc-web/opendc-web-ui/src/components/topologies/map/RoomContainer.js deleted file mode 100644 index 76785bea..00000000 --- a/opendc-web/opendc-web-ui/src/components/topologies/map/RoomContainer.js +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -import PropTypes from 'prop-types' -import React from 'react' -import { useDispatch, useSelector } from 'react-redux' -import { goFromBuildingToRoom } from '../../../redux/actions/interaction-level' -import RoomGroup from './groups/RoomGroup' - -function RoomContainer({ roomId, ...props }) { - const interactionLevel = useSelector((state) => state.interactionLevel) - const currentRoomInConstruction = useSelector((state) => state.construction.currentRoomInConstruction) - const room = useSelector((state) => state.topology.rooms[roomId]) - const dispatch = useDispatch() - - if (!room) { - return null - } - - return ( - <RoomGroup - {...props} - interactionLevel={interactionLevel} - currentRoomInConstruction={currentRoomInConstruction} - room={room} - onClick={() => dispatch(goFromBuildingToRoom(roomId))} - /> - ) -} - -RoomContainer.propTypes = { - roomId: PropTypes.string, -} - -export default RoomContainer diff --git a/opendc-web/opendc-web-ui/src/components/topologies/map/TileContainer.js b/opendc-web/opendc-web-ui/src/components/topologies/map/TileContainer.js deleted file mode 100644 index 0788b894..00000000 --- a/opendc-web/opendc-web-ui/src/components/topologies/map/TileContainer.js +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -import React from 'react' -import PropTypes from 'prop-types' -import { useDispatch, useSelector } from 'react-redux' -import { goFromRoomToRack } from '../../../redux/actions/interaction-level' -import TileGroup from './groups/TileGroup' - -function TileContainer({ tileId, ...props }) { - const interactionLevel = useSelector((state) => state.interactionLevel) - const dispatch = useDispatch() - const tile = useSelector((state) => state.topology.tiles[tileId]) - - if (!tile) { - return null - } - - const onClick = (tile) => { - if (tile.rack) { - dispatch(goFromRoomToRack(tile.id)) - } - } - return <TileGroup {...props} onClick={onClick} tile={tile} interactionLevel={interactionLevel} /> -} - -TileContainer.propTypes = { - tileId: PropTypes.string.isRequired, -} - -export default TileContainer diff --git a/opendc-web/opendc-web-ui/src/components/topologies/map/TopologyContainer.js b/opendc-web/opendc-web-ui/src/components/topologies/map/TopologyContainer.js deleted file mode 100644 index cc0d46b3..00000000 --- a/opendc-web/opendc-web-ui/src/components/topologies/map/TopologyContainer.js +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -import React from 'react' -import { useSelector } from 'react-redux' -import TopologyGroup from './groups/TopologyGroup' - -function TopologyContainer() { - const topology = useSelector((state) => state.topology.root) - const interactionLevel = useSelector((state) => state.interactionLevel) - - return <TopologyGroup topology={topology} interactionLevel={interactionLevel} /> -} - -export default TopologyContainer diff --git a/opendc-web/opendc-web-ui/src/components/topologies/map/WallContainer.js b/opendc-web/opendc-web-ui/src/components/topologies/map/WallContainer.js deleted file mode 100644 index 106d8d3d..00000000 --- a/opendc-web/opendc-web-ui/src/components/topologies/map/WallContainer.js +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -import React from 'react' -import PropTypes from 'prop-types' -import { useSelector } from 'react-redux' -import WallGroup from './groups/WallGroup' - -function WallContainer({ roomId, ...props }) { - const tiles = useSelector((state) => { - return state.topology.rooms[roomId]?.tiles.map((tileId) => state.topology.tiles[tileId]) ?? [] - }) - return <WallGroup {...props} tiles={tiles} /> -} - -WallContainer.propTypes = { - roomId: PropTypes.string.isRequired, -} - -export default WallContainer diff --git a/opendc-web/opendc-web-ui/src/components/topologies/map/controls/Collapse.js b/opendc-web/opendc-web-ui/src/components/topologies/map/controls/Collapse.js deleted file mode 100644 index 931ded94..00000000 --- a/opendc-web/opendc-web-ui/src/components/topologies/map/controls/Collapse.js +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -import PropTypes from 'prop-types' -import { ChevronLeftIcon } from '@patternfly/react-icons' -import { collapseContainer } from './Collapse.module.css' -import { Button } from '@patternfly/react-core' - -function Collapse({ onClick }) { - return ( - <div className={collapseContainer}> - <Button variant="tertiary" onClick={onClick}> - <ChevronLeftIcon /> - </Button> - </div> - ) -} - -Collapse.propTypes = { - onClick: PropTypes.func, -} - -export default Collapse diff --git a/opendc-web/opendc-web-ui/src/components/topologies/map/controls/Collapse.module.css b/opendc-web/opendc-web-ui/src/components/topologies/map/controls/Collapse.module.css deleted file mode 100644 index 70fd465f..00000000 --- a/opendc-web/opendc-web-ui/src/components/topologies/map/controls/Collapse.module.css +++ /dev/null @@ -1,55 +0,0 @@ -/*! - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -.collapseContainer { - position: absolute; - right: var(--pf-global--spacer--xs); - top: 0; - bottom: 10%; - margin: auto 0; - height: 50px; -} - -.collapseContainer > button:global(.pf-m-tertiary) { - height: 100%; - padding: 2px; - - margin-right: var(--pf-global--spacer--xs); - margin-top: var(--pf-global--spacer--xs); - background-color: var(--pf-global--BackgroundColor--100); - border: none; - border-radius: var(--pf-global--BorderRadius--sm); - box-shadow: var(--pf-global--BoxShadow--sm); -} - -.collapseContainer > button:global(.pf-m-tertiary):not(:global(.pf-m-disabled)) { - background-color: var(--pf-global--BackgroundColor--100); -} - -.collapseContainer > button:global(.pf-m-tertiary):after { - display: none; -} - -.collapseContainer > button:global(.pf-m-tertiary):hover { - border: none; - box-shadow: var(--pf-global--BoxShadow--md); -} diff --git a/opendc-web/opendc-web-ui/src/components/topologies/map/controls/ScaleIndicator.js b/opendc-web/opendc-web-ui/src/components/topologies/map/controls/ScaleIndicator.js deleted file mode 100644 index 3ec893fb..00000000 --- a/opendc-web/opendc-web-ui/src/components/topologies/map/controls/ScaleIndicator.js +++ /dev/null @@ -1,18 +0,0 @@ -import PropTypes from 'prop-types' -import React from 'react' -import { TILE_SIZE_IN_METERS, TILE_SIZE_IN_PIXELS } from '../MapConstants' -import { scaleIndicator } from './ScaleIndicator.module.css' - -function ScaleIndicator({ scale }) { - return ( - <div className={scaleIndicator} style={{ width: TILE_SIZE_IN_PIXELS * scale }}> - {TILE_SIZE_IN_METERS}m - </div> - ) -} - -ScaleIndicator.propTypes = { - scale: PropTypes.number.isRequired, -} - -export default ScaleIndicator diff --git a/opendc-web/opendc-web-ui/src/components/topologies/map/controls/ScaleIndicator.module.css b/opendc-web/opendc-web-ui/src/components/topologies/map/controls/ScaleIndicator.module.css deleted file mode 100644 index f19e0ff2..00000000 --- a/opendc-web/opendc-web-ui/src/components/topologies/map/controls/ScaleIndicator.module.css +++ /dev/null @@ -1,10 +0,0 @@ -.scaleIndicator { - position: absolute; - right: 10px; - bottom: 10px; - z-index: 50; - - border: solid 2px #212529; - border-top: none; - border-left: none; -} diff --git a/opendc-web/opendc-web-ui/src/components/topologies/map/controls/Toolbar.js b/opendc-web/opendc-web-ui/src/components/topologies/map/controls/Toolbar.js deleted file mode 100644 index 00aaf3e1..00000000 --- a/opendc-web/opendc-web-ui/src/components/topologies/map/controls/Toolbar.js +++ /dev/null @@ -1,33 +0,0 @@ -import PropTypes from 'prop-types' -import React from 'react' -import { control, toolBar } from './Toolbar.module.css' -import { Button } from '@patternfly/react-core' -import { SearchPlusIcon, SearchMinusIcon, CameraIcon } from '@patternfly/react-icons' - -function Toolbar({ onZoom, onExport }) { - return ( - <div className={toolBar}> - <Button variant="tertiary" title="Zoom in" onClick={() => onZoom(true)} className={control}> - <SearchPlusIcon /> - </Button> - <Button variant="tertiary" title="Zoom out" onClick={() => onZoom(false)} className={control}> - <SearchMinusIcon /> - </Button> - <Button - variant="tertiary" - title="Export Canvas to PNG Image" - onClick={() => onExport()} - className={control} - > - <CameraIcon /> - </Button> - </div> - ) -} - -Toolbar.propTypes = { - onZoom: PropTypes.func, - onExport: PropTypes.func, -} - -export default Toolbar diff --git a/opendc-web/opendc-web-ui/src/components/topologies/map/controls/Toolbar.module.css b/opendc-web/opendc-web-ui/src/components/topologies/map/controls/Toolbar.module.css deleted file mode 100644 index 007389da..00000000 --- a/opendc-web/opendc-web-ui/src/components/topologies/map/controls/Toolbar.module.css +++ /dev/null @@ -1,27 +0,0 @@ -.toolBar { - position: absolute; - bottom: var(--pf-global--spacer--md); - left: var(--pf-global--spacer--xl); -} - -.control:global(.pf-m-tertiary) { - margin-right: var(--pf-global--spacer--xs); - margin-top: var(--pf-global--spacer--xs); - background-color: var(--pf-global--BackgroundColor--100); - border: none; - border-radius: var(--pf-global--BorderRadius--sm); - box-shadow: var(--pf-global--BoxShadow--sm); -} - -.control:global(.pf-m-tertiary):not(:global(.pf-m-disabled)) { - background-color: var(--pf-global--BackgroundColor--100); -} - -.control:global(.pf-m-tertiary):after { - display: none; -} - -.control:global(.pf-m-tertiary):hover { - border: none; - box-shadow: var(--pf-global--BoxShadow--md); -} diff --git a/opendc-web/opendc-web-ui/src/components/topologies/map/elements/Backdrop.js b/opendc-web/opendc-web-ui/src/components/topologies/map/elements/Backdrop.js deleted file mode 100644 index 93037b51..00000000 --- a/opendc-web/opendc-web-ui/src/components/topologies/map/elements/Backdrop.js +++ /dev/null @@ -1,10 +0,0 @@ -import React from 'react' -import { Rect } from 'react-konva' -import { BACKDROP_COLOR } from '../../../../util/colors' -import { MAP_SIZE_IN_PIXELS } from '../MapConstants' - -function Backdrop() { - return <Rect x={0} y={0} width={MAP_SIZE_IN_PIXELS} height={MAP_SIZE_IN_PIXELS} fill={BACKDROP_COLOR} /> -} - -export default Backdrop diff --git a/opendc-web/opendc-web-ui/src/components/topologies/map/elements/GrayLayer.js b/opendc-web/opendc-web-ui/src/components/topologies/map/elements/GrayLayer.js deleted file mode 100644 index 08c687f6..00000000 --- a/opendc-web/opendc-web-ui/src/components/topologies/map/elements/GrayLayer.js +++ /dev/null @@ -1,24 +0,0 @@ -import PropTypes from 'prop-types' -import React from 'react' -import { Rect } from 'react-konva' -import { GRAYED_OUT_AREA_COLOR } from '../../../../util/colors' -import { MAP_SIZE_IN_PIXELS } from '../MapConstants' - -function GrayLayer({ onClick }) { - return ( - <Rect - x={0} - y={0} - width={MAP_SIZE_IN_PIXELS} - height={MAP_SIZE_IN_PIXELS} - fill={GRAYED_OUT_AREA_COLOR} - onClick={onClick} - /> - ) -} - -GrayLayer.propTypes = { - onClick: PropTypes.func, -} - -export default GrayLayer diff --git a/opendc-web/opendc-web-ui/src/components/topologies/map/elements/HoverTile.js b/opendc-web/opendc-web-ui/src/components/topologies/map/elements/HoverTile.js deleted file mode 100644 index 20c2c6d1..00000000 --- a/opendc-web/opendc-web-ui/src/components/topologies/map/elements/HoverTile.js +++ /dev/null @@ -1,30 +0,0 @@ -import PropTypes from 'prop-types' -import React from 'react' -import { Rect } from 'react-konva' -import { ROOM_HOVER_INVALID_COLOR, ROOM_HOVER_VALID_COLOR } from '../../../../util/colors' -import { TILE_SIZE_IN_PIXELS } from '../MapConstants' - -function HoverTile({ x, y, isValid, scale = 1, onClick }) { - return ( - <Rect - x={x} - y={y} - scaleX={scale} - scaleY={scale} - width={TILE_SIZE_IN_PIXELS} - height={TILE_SIZE_IN_PIXELS} - fill={isValid ? ROOM_HOVER_VALID_COLOR : ROOM_HOVER_INVALID_COLOR} - onClick={onClick} - /> - ) -} - -HoverTile.propTypes = { - x: PropTypes.number.isRequired, - y: PropTypes.number.isRequired, - isValid: PropTypes.bool.isRequired, - scale: PropTypes.number, - onClick: PropTypes.func.isRequired, -} - -export default HoverTile diff --git a/opendc-web/opendc-web-ui/src/components/topologies/map/elements/ImageComponent.js b/opendc-web/opendc-web-ui/src/components/topologies/map/elements/ImageComponent.js deleted file mode 100644 index fdae53f2..00000000 --- a/opendc-web/opendc-web-ui/src/components/topologies/map/elements/ImageComponent.js +++ /dev/null @@ -1,37 +0,0 @@ -import PropTypes from 'prop-types' -import React, { useEffect, useState } from 'react' -import { Image } from 'react-konva' - -const imageCaches = {} - -function ImageComponent({ src, x, y, width, height, opacity }) { - const [image, setImage] = useState(null) - - useEffect(() => { - if (imageCaches[src]) { - setImage(imageCaches[src]) - return - } - - const image = new window.Image() - image.src = src - image.onload = () => { - setImage(image) - imageCaches[src] = image - } - }, [src]) - - // eslint-disable-next-line jsx-a11y/alt-text - return <Image image={image} x={x} y={y} width={width} height={height} opacity={opacity} /> -} - -ImageComponent.propTypes = { - src: PropTypes.string.isRequired, - x: PropTypes.number.isRequired, - y: PropTypes.number.isRequired, - width: PropTypes.number.isRequired, - height: PropTypes.number.isRequired, - opacity: PropTypes.number.isRequired, -} - -export default ImageComponent diff --git a/opendc-web/opendc-web-ui/src/components/topologies/map/elements/RackFillBar.js b/opendc-web/opendc-web-ui/src/components/topologies/map/elements/RackFillBar.js deleted file mode 100644 index aa284944..00000000 --- a/opendc-web/opendc-web-ui/src/components/topologies/map/elements/RackFillBar.js +++ /dev/null @@ -1,68 +0,0 @@ -import PropTypes from 'prop-types' -import React from 'react' -import { Group, Rect } from 'react-konva' -import { - RACK_ENERGY_BAR_BACKGROUND_COLOR, - RACK_ENERGY_BAR_FILL_COLOR, - RACK_SPACE_BAR_BACKGROUND_COLOR, - RACK_SPACE_BAR_FILL_COLOR, -} from '../../../../util/colors' -import { - OBJECT_BORDER_WIDTH_IN_PIXELS, - OBJECT_MARGIN_IN_PIXELS, - RACK_FILL_ICON_OPACITY, - RACK_FILL_ICON_WIDTH, - TILE_SIZE_IN_PIXELS, -} from '../MapConstants' -import ImageComponent from './ImageComponent' - -function RackFillBar({ positionX, positionY, type, fillFraction }) { - const halfOfObjectBorderWidth = OBJECT_BORDER_WIDTH_IN_PIXELS / 2 - const x = - positionX * TILE_SIZE_IN_PIXELS + - OBJECT_MARGIN_IN_PIXELS + - (type === 'space' ? halfOfObjectBorderWidth : 0.5 * (TILE_SIZE_IN_PIXELS - 2 * OBJECT_MARGIN_IN_PIXELS)) - const startY = positionY * TILE_SIZE_IN_PIXELS + OBJECT_MARGIN_IN_PIXELS + halfOfObjectBorderWidth - const width = 0.5 * (TILE_SIZE_IN_PIXELS - OBJECT_MARGIN_IN_PIXELS * 2) - halfOfObjectBorderWidth - const fullHeight = TILE_SIZE_IN_PIXELS - OBJECT_MARGIN_IN_PIXELS * 2 - OBJECT_BORDER_WIDTH_IN_PIXELS - - const fractionHeight = fillFraction * fullHeight - const fractionY = - (positionY + 1) * TILE_SIZE_IN_PIXELS - OBJECT_MARGIN_IN_PIXELS - halfOfObjectBorderWidth - fractionHeight - - return ( - <Group> - <Rect - x={x} - y={startY} - width={width} - height={fullHeight} - fill={type === 'space' ? RACK_SPACE_BAR_BACKGROUND_COLOR : RACK_ENERGY_BAR_BACKGROUND_COLOR} - /> - <Rect - x={x} - y={fractionY} - width={width} - height={fractionHeight} - fill={type === 'space' ? RACK_SPACE_BAR_FILL_COLOR : RACK_ENERGY_BAR_FILL_COLOR} - /> - <ImageComponent - src={'/img/topology/rack-' + type + '-icon.png'} - x={x + width * 0.5 - RACK_FILL_ICON_WIDTH * 0.5} - y={startY + fullHeight * 0.5 - RACK_FILL_ICON_WIDTH * 0.5} - width={RACK_FILL_ICON_WIDTH} - height={RACK_FILL_ICON_WIDTH} - opacity={RACK_FILL_ICON_OPACITY} - /> - </Group> - ) -} - -RackFillBar.propTypes = { - positionX: PropTypes.number.isRequired, - positionY: PropTypes.number.isRequired, - type: PropTypes.string.isRequired, - fillFraction: PropTypes.number.isRequired, -} - -export default RackFillBar diff --git a/opendc-web/opendc-web-ui/src/components/topologies/map/elements/RoomTile.js b/opendc-web/opendc-web-ui/src/components/topologies/map/elements/RoomTile.js deleted file mode 100644 index e7329dc0..00000000 --- a/opendc-web/opendc-web-ui/src/components/topologies/map/elements/RoomTile.js +++ /dev/null @@ -1,24 +0,0 @@ -import PropTypes from 'prop-types' -import React from 'react' -import { Rect } from 'react-konva' -import { Tile } from '../../../../shapes' -import { TILE_SIZE_IN_PIXELS } from '../MapConstants' - -function RoomTile({ tile, color }) { - return ( - <Rect - x={tile.positionX * TILE_SIZE_IN_PIXELS} - y={tile.positionY * TILE_SIZE_IN_PIXELS} - width={TILE_SIZE_IN_PIXELS} - height={TILE_SIZE_IN_PIXELS} - fill={color} - /> - ) -} - -RoomTile.propTypes = { - tile: Tile, - color: PropTypes.string, -} - -export default RoomTile diff --git a/opendc-web/opendc-web-ui/src/components/topologies/map/elements/TileObject.js b/opendc-web/opendc-web-ui/src/components/topologies/map/elements/TileObject.js deleted file mode 100644 index 3211f187..00000000 --- a/opendc-web/opendc-web-ui/src/components/topologies/map/elements/TileObject.js +++ /dev/null @@ -1,27 +0,0 @@ -import PropTypes from 'prop-types' -import React from 'react' -import { Rect } from 'react-konva' -import { OBJECT_BORDER_COLOR } from '../../../../util/colors' -import { OBJECT_BORDER_WIDTH_IN_PIXELS, OBJECT_MARGIN_IN_PIXELS, TILE_SIZE_IN_PIXELS } from '../MapConstants' - -function TileObject({ positionX, positionY, color }) { - return ( - <Rect - x={positionX * TILE_SIZE_IN_PIXELS + OBJECT_MARGIN_IN_PIXELS} - y={positionY * TILE_SIZE_IN_PIXELS + OBJECT_MARGIN_IN_PIXELS} - width={TILE_SIZE_IN_PIXELS - OBJECT_MARGIN_IN_PIXELS * 2} - height={TILE_SIZE_IN_PIXELS - OBJECT_MARGIN_IN_PIXELS * 2} - fill={color} - stroke={OBJECT_BORDER_COLOR} - strokeWidth={OBJECT_BORDER_WIDTH_IN_PIXELS} - /> - ) -} - -TileObject.propTypes = { - positionX: PropTypes.number.isRequired, - positionY: PropTypes.number.isRequired, - color: PropTypes.string.isRequired, -} - -export default TileObject diff --git a/opendc-web/opendc-web-ui/src/components/topologies/map/elements/TilePlusIcon.js b/opendc-web/opendc-web-ui/src/components/topologies/map/elements/TilePlusIcon.js deleted file mode 100644 index 186c2b3a..00000000 --- a/opendc-web/opendc-web-ui/src/components/topologies/map/elements/TilePlusIcon.js +++ /dev/null @@ -1,44 +0,0 @@ -import PropTypes from 'prop-types' -import React from 'react' -import { Group, Line } from 'react-konva' -import { TILE_PLUS_COLOR } from '../../../../util/colors' -import { TILE_PLUS_MARGIN_IN_PIXELS, TILE_PLUS_WIDTH_IN_PIXELS, TILE_SIZE_IN_PIXELS } from '../MapConstants' - -function TilePlusIcon({ x, y, scale = 1 }) { - const linePoints = [ - [ - x + 0.5 * TILE_SIZE_IN_PIXELS * scale, - y + TILE_PLUS_MARGIN_IN_PIXELS * scale, - x + 0.5 * TILE_SIZE_IN_PIXELS * scale, - y + TILE_SIZE_IN_PIXELS * scale - TILE_PLUS_MARGIN_IN_PIXELS * scale, - ], - [ - x + TILE_PLUS_MARGIN_IN_PIXELS * scale, - y + 0.5 * TILE_SIZE_IN_PIXELS * scale, - x + TILE_SIZE_IN_PIXELS * scale - TILE_PLUS_MARGIN_IN_PIXELS * scale, - y + 0.5 * TILE_SIZE_IN_PIXELS * scale, - ], - ] - return ( - <Group> - {linePoints.map((points, index) => ( - <Line - key={index} - points={points} - lineCap="round" - stroke={TILE_PLUS_COLOR} - strokeWidth={TILE_PLUS_WIDTH_IN_PIXELS * scale} - listening={false} - /> - ))} - </Group> - ) -} - -TilePlusIcon.propTypes = { - x: PropTypes.number, - y: PropTypes.number, - scale: PropTypes.number, -} - -export default TilePlusIcon diff --git a/opendc-web/opendc-web-ui/src/components/topologies/map/elements/WallSegment.js b/opendc-web/opendc-web-ui/src/components/topologies/map/elements/WallSegment.js deleted file mode 100644 index 4f18813e..00000000 --- a/opendc-web/opendc-web-ui/src/components/topologies/map/elements/WallSegment.js +++ /dev/null @@ -1,32 +0,0 @@ -import React from 'react' -import { Line } from 'react-konva' -import { WallSegment as WallSegmentShape } from '../../../../shapes' -import { WALL_COLOR } from '../../../../util/colors' -import { TILE_SIZE_IN_PIXELS, WALL_WIDTH_IN_PIXELS } from '../MapConstants' - -function WallSegment({ wallSegment }) { - let points - if (wallSegment.isHorizontal) { - points = [ - wallSegment.startPosX * TILE_SIZE_IN_PIXELS, - wallSegment.startPosY * TILE_SIZE_IN_PIXELS, - (wallSegment.startPosX + wallSegment.length) * TILE_SIZE_IN_PIXELS, - wallSegment.startPosY * TILE_SIZE_IN_PIXELS, - ] - } else { - points = [ - wallSegment.startPosX * TILE_SIZE_IN_PIXELS, - wallSegment.startPosY * TILE_SIZE_IN_PIXELS, - wallSegment.startPosX * TILE_SIZE_IN_PIXELS, - (wallSegment.startPosY + wallSegment.length) * TILE_SIZE_IN_PIXELS, - ] - } - - return <Line points={points} lineCap="round" stroke={WALL_COLOR} strokeWidth={WALL_WIDTH_IN_PIXELS} /> -} - -WallSegment.propTypes = { - wallSegment: WallSegmentShape, -} - -export default WallSegment diff --git a/opendc-web/opendc-web-ui/src/components/topologies/map/groups/GridGroup.js b/opendc-web/opendc-web-ui/src/components/topologies/map/groups/GridGroup.js deleted file mode 100644 index d66a18de..00000000 --- a/opendc-web/opendc-web-ui/src/components/topologies/map/groups/GridGroup.js +++ /dev/null @@ -1,36 +0,0 @@ -import React from 'react' -import { Group, Line } from 'react-konva' -import { GRID_COLOR } from '../../../../util/colors' -import { GRID_LINE_WIDTH_IN_PIXELS, MAP_SIZE, MAP_SIZE_IN_PIXELS, TILE_SIZE_IN_PIXELS } from '../MapConstants' - -const MAP_COORDINATE_ENTRIES = Array.from(new Array(MAP_SIZE), (x, i) => i) -const HORIZONTAL_POINT_PAIRS = MAP_COORDINATE_ENTRIES.map((index) => [ - 0, - index * TILE_SIZE_IN_PIXELS, - MAP_SIZE_IN_PIXELS, - index * TILE_SIZE_IN_PIXELS, -]) -const VERTICAL_POINT_PAIRS = MAP_COORDINATE_ENTRIES.map((index) => [ - index * TILE_SIZE_IN_PIXELS, - 0, - index * TILE_SIZE_IN_PIXELS, - MAP_SIZE_IN_PIXELS, -]) - -function GridGroup() { - return ( - <Group> - {HORIZONTAL_POINT_PAIRS.concat(VERTICAL_POINT_PAIRS).map((points, index) => ( - <Line - key={index} - points={points} - stroke={GRID_COLOR} - strokeWidth={GRID_LINE_WIDTH_IN_PIXELS} - listening={false} - /> - ))} - </Group> - ) -} - -export default GridGroup diff --git a/opendc-web/opendc-web-ui/src/components/topologies/map/groups/RackGroup.js b/opendc-web/opendc-web-ui/src/components/topologies/map/groups/RackGroup.js deleted file mode 100644 index ed942661..00000000 --- a/opendc-web/opendc-web-ui/src/components/topologies/map/groups/RackGroup.js +++ /dev/null @@ -1,25 +0,0 @@ -import React from 'react' -import { Group } from 'react-konva' -import { Tile } from '../../../../shapes' -import { RACK_BACKGROUND_COLOR } from '../../../../util/colors' -import TileObject from '../elements/TileObject' -import RackSpaceFillContainer from '../RackSpaceFillContainer' -import RackEnergyFillContainer from '../RackEnergyFillContainer' - -function RackGroup({ tile }) { - return ( - <Group> - <TileObject positionX={tile.positionX} positionY={tile.positionY} color={RACK_BACKGROUND_COLOR} /> - <Group> - <RackSpaceFillContainer rackId={tile.rack} positionX={tile.positionX} positionY={tile.positionY} /> - <RackEnergyFillContainer rackId={tile.rack} positionX={tile.positionX} positionY={tile.positionY} /> - </Group> - </Group> - ) -} - -RackGroup.propTypes = { - tile: Tile, -} - -export default RackGroup diff --git a/opendc-web/opendc-web-ui/src/components/topologies/map/groups/RoomGroup.js b/opendc-web/opendc-web-ui/src/components/topologies/map/groups/RoomGroup.js deleted file mode 100644 index 3f8b3089..00000000 --- a/opendc-web/opendc-web-ui/src/components/topologies/map/groups/RoomGroup.js +++ /dev/null @@ -1,52 +0,0 @@ -import PropTypes from 'prop-types' -import React from 'react' -import { Group } from 'react-konva' -import { InteractionLevel, Room } from '../../../../shapes' -import GrayContainer from '../GrayContainer' -import TileContainer from '../TileContainer' -import WallContainer from '../WallContainer' - -function RoomGroup({ room, interactionLevel, currentRoomInConstruction, onClick }) { - if (currentRoomInConstruction === room.id) { - return ( - <Group onClick={onClick}> - {room.tiles.map((tileId) => ( - <TileContainer key={tileId} tileId={tileId} newTile={true} /> - ))} - </Group> - ) - } - - return ( - <Group onClick={onClick}> - {(() => { - if ( - (interactionLevel.mode === 'RACK' || interactionLevel.mode === 'MACHINE') && - interactionLevel.roomId === room.id - ) { - return [ - room.tiles - .filter((tileId) => tileId !== interactionLevel.tileId) - .map((tileId) => <TileContainer key={tileId} tileId={tileId} />), - <GrayContainer key={-1} />, - room.tiles - .filter((tileId) => tileId === interactionLevel.tileId) - .map((tileId) => <TileContainer key={tileId} tileId={tileId} />), - ] - } else { - return room.tiles.map((tileId) => <TileContainer key={tileId} tileId={tileId} />) - } - })()} - <WallContainer roomId={room.id} /> - </Group> - ) -} - -RoomGroup.propTypes = { - room: Room, - interactionLevel: InteractionLevel, - currentRoomInConstruction: PropTypes.string, - onClick: PropTypes.func, -} - -export default RoomGroup diff --git a/opendc-web/opendc-web-ui/src/components/topologies/map/groups/TileGroup.js b/opendc-web/opendc-web-ui/src/components/topologies/map/groups/TileGroup.js deleted file mode 100644 index f2084017..00000000 --- a/opendc-web/opendc-web-ui/src/components/topologies/map/groups/TileGroup.js +++ /dev/null @@ -1,36 +0,0 @@ -import PropTypes from 'prop-types' -import React from 'react' -import { Group } from 'react-konva' -import { Tile } from '../../../../shapes' -import { ROOM_DEFAULT_COLOR, ROOM_IN_CONSTRUCTION_COLOR } from '../../../../util/colors' -import RoomTile from '../elements/RoomTile' -import RackContainer from '../RackContainer' - -function TileGroup({ tile, newTile, onClick }) { - let tileObject - if (tile.rack) { - tileObject = <RackContainer tile={tile} /> - } else { - tileObject = null - } - - let color = ROOM_DEFAULT_COLOR - if (newTile) { - color = ROOM_IN_CONSTRUCTION_COLOR - } - - return ( - <Group onClick={() => onClick(tile)}> - <RoomTile tile={tile} color={color} /> - {tileObject} - </Group> - ) -} - -TileGroup.propTypes = { - tile: Tile, - newTile: PropTypes.bool, - onClick: PropTypes.func, -} - -export default TileGroup diff --git a/opendc-web/opendc-web-ui/src/components/topologies/map/groups/TopologyGroup.js b/opendc-web/opendc-web-ui/src/components/topologies/map/groups/TopologyGroup.js deleted file mode 100644 index 011dcf34..00000000 --- a/opendc-web/opendc-web-ui/src/components/topologies/map/groups/TopologyGroup.js +++ /dev/null @@ -1,44 +0,0 @@ -import React from 'react' -import { Group } from 'react-konva' -import { InteractionLevel, Topology } from '../../../../shapes' -import RoomContainer from '../RoomContainer' -import GrayContainer from '../GrayContainer' - -function TopologyGroup({ topology, interactionLevel }) { - if (!topology) { - return <Group /> - } - - if (interactionLevel.mode === 'BUILDING') { - return ( - <Group> - {topology.rooms.map((roomId) => ( - <RoomContainer key={roomId} roomId={roomId} /> - ))} - </Group> - ) - } - - return ( - <Group> - {topology.rooms - .filter((roomId) => roomId !== interactionLevel.roomId) - .map((roomId) => ( - <RoomContainer key={roomId} roomId={roomId} /> - ))} - {interactionLevel.mode === 'ROOM' ? <GrayContainer /> : null} - {topology.rooms - .filter((roomId) => roomId === interactionLevel.roomId) - .map((roomId) => ( - <RoomContainer key={roomId} roomId={roomId} /> - ))} - </Group> - ) -} - -TopologyGroup.propTypes = { - topology: Topology, - interactionLevel: InteractionLevel, -} - -export default TopologyGroup diff --git a/opendc-web/opendc-web-ui/src/components/topologies/map/groups/WallGroup.js b/opendc-web/opendc-web-ui/src/components/topologies/map/groups/WallGroup.js deleted file mode 100644 index 6cbd1cd0..00000000 --- a/opendc-web/opendc-web-ui/src/components/topologies/map/groups/WallGroup.js +++ /dev/null @@ -1,22 +0,0 @@ -import PropTypes from 'prop-types' -import React from 'react' -import { Group } from 'react-konva' -import { Tile } from '../../../../shapes' -import { deriveWallLocations } from '../../../../util/tile-calculations' -import WallSegment from '../elements/WallSegment' - -function WallGroup({ tiles }) { - return ( - <Group> - {deriveWallLocations(tiles).map((wallSegment, index) => ( - <WallSegment key={index} wallSegment={wallSegment} /> - ))} - </Group> - ) -} - -WallGroup.propTypes = { - tiles: PropTypes.arrayOf(Tile).isRequired, -} - -export default WallGroup diff --git a/opendc-web/opendc-web-ui/src/components/topologies/map/layers/HoverLayerComponent.js b/opendc-web/opendc-web-ui/src/components/topologies/map/layers/HoverLayerComponent.js deleted file mode 100644 index d7e0c56a..00000000 --- a/opendc-web/opendc-web-ui/src/components/topologies/map/layers/HoverLayerComponent.js +++ /dev/null @@ -1,55 +0,0 @@ -import PropTypes from 'prop-types' -import React, { useMemo, useState } from 'react' -import { Layer } from 'react-konva/lib/ReactKonva' -import HoverTile from '../elements/HoverTile' -import { TILE_SIZE_IN_PIXELS } from '../MapConstants' -import { useEffectRef } from '../../../../util/effect-ref' - -function HoverLayerComponent({ isEnabled, isValid, onClick, children }) { - const [[mouseWorldX, mouseWorldY], setPos] = useState([0, 0]) - - const layerRef = useEffectRef((layer) => { - if (!layer) { - return - } - - const stage = layer.getStage() - - stage.on('mousemove.hover', () => { - // Transform used to convert mouse coordinates to world coordinates - const transform = stage.getAbsoluteTransform().copy() - transform.invert() - - const { x, y } = transform.point(stage.getPointerPosition()) - setPos([x, y]) - }) - return () => stage.off('mousemove.hover') - }) - - const gridX = Math.floor(mouseWorldX / TILE_SIZE_IN_PIXELS) - const gridY = Math.floor(mouseWorldY / TILE_SIZE_IN_PIXELS) - const valid = useMemo(() => isEnabled && isValid(gridX, gridY), [isEnabled, isValid, gridX, gridY]) - - if (!isEnabled) { - return <Layer /> - } - - const x = gridX * TILE_SIZE_IN_PIXELS - const y = gridY * TILE_SIZE_IN_PIXELS - - return ( - <Layer opacity={0.2} ref={layerRef}> - <HoverTile x={x} y={y} isValid={valid} onClick={() => (valid ? onClick(gridX, gridY) : undefined)} /> - {children ? React.cloneElement(children, { x, y, scale: 1 }) : undefined} - </Layer> - ) -} - -HoverLayerComponent.propTypes = { - isEnabled: PropTypes.bool.isRequired, - isValid: PropTypes.func.isRequired, - onClick: PropTypes.func.isRequired, - children: PropTypes.node, -} - -export default HoverLayerComponent diff --git a/opendc-web/opendc-web-ui/src/components/topologies/map/layers/MapLayer.js b/opendc-web/opendc-web-ui/src/components/topologies/map/layers/MapLayer.js deleted file mode 100644 index c902532b..00000000 --- a/opendc-web/opendc-web-ui/src/components/topologies/map/layers/MapLayer.js +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -import React from 'react' -import { Group, Layer } from 'react-konva' -import Backdrop from '../elements/Backdrop' -import TopologyContainer from '../TopologyContainer' -import GridGroup from '../groups/GridGroup' - -function MapLayer() { - return ( - <Layer> - <Group> - <Backdrop /> - <TopologyContainer /> - <GridGroup /> - </Group> - </Layer> - ) -} - -export default MapLayer diff --git a/opendc-web/opendc-web-ui/src/components/topologies/map/layers/ObjectHoverLayer.js b/opendc-web/opendc-web-ui/src/components/topologies/map/layers/ObjectHoverLayer.js deleted file mode 100644 index 5e741a3b..00000000 --- a/opendc-web/opendc-web-ui/src/components/topologies/map/layers/ObjectHoverLayer.js +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -import React from 'react' -import { useDispatch, useSelector } from 'react-redux' -import { addRackToTile } from '../../../../redux/actions/topology/room' -import { findTileWithPosition } from '../../../../util/tile-calculations' -import HoverLayerComponent from './HoverLayerComponent' -import TilePlusIcon from '../elements/TilePlusIcon' - -export default function ObjectHoverLayer() { - const isEnabled = useSelector((state) => state.construction.inRackConstructionMode) - const isValid = useSelector((state) => (x, y) => { - if (state.interactionLevel.mode !== 'ROOM') { - return false - } - - const currentRoom = state.topology.rooms[state.interactionLevel.roomId] - const tiles = currentRoom.tiles.map((tileId) => state.topology.tiles[tileId]) - const tile = findTileWithPosition(tiles, x, y) - - return !(tile === null || tile.rack) - }) - - const dispatch = useDispatch() - const onClick = (x, y) => dispatch(addRackToTile(x, y)) - return ( - <HoverLayerComponent onClick={onClick} isEnabled={isEnabled} isValid={isValid}> - <TilePlusIcon /> - </HoverLayerComponent> - ) -} diff --git a/opendc-web/opendc-web-ui/src/components/topologies/map/layers/RoomHoverLayer.js b/opendc-web/opendc-web-ui/src/components/topologies/map/layers/RoomHoverLayer.js deleted file mode 100644 index b9cfcaf4..00000000 --- a/opendc-web/opendc-web-ui/src/components/topologies/map/layers/RoomHoverLayer.js +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -import React from 'react' -import { useDispatch, useSelector } from 'react-redux' -import { toggleTileAtLocation } from '../../../../redux/actions/topology/building' -import { - deriveValidNextTilePositions, - findPositionInPositions, - findPositionInRooms, -} from '../../../../util/tile-calculations' -import HoverLayerComponent from './HoverLayerComponent' - -export default function RoomHoverLayer() { - const dispatch = useDispatch() - const onClick = (x, y) => dispatch(toggleTileAtLocation(x, y)) - const isEnabled = useSelector((state) => state.construction.currentRoomInConstruction !== '-1') - const isValid = useSelector((state) => (x, y) => { - const newRoom = { ...state.topology.rooms[state.construction.currentRoomInConstruction] } - const oldRooms = Object.keys(state.topology.rooms) - .map((id) => ({ ...state.topology.rooms[id] })) - .filter( - (room) => - state.topology.root.rooms.indexOf(room.id) !== -1 && - room.id !== state.construction.currentRoomInConstruction - ) - - ;[...oldRooms, newRoom].forEach((room) => { - room.tiles = room.tiles.map((tileId) => state.topology.tiles[tileId]) - }) - if (newRoom.tiles.length === 0) { - return findPositionInRooms(oldRooms, x, y) === -1 - } - - const validNextPositions = deriveValidNextTilePositions(oldRooms, newRoom.tiles) - return findPositionInPositions(validNextPositions, x, y) !== -1 - }) - - return <HoverLayerComponent onClick={onClick} isEnabled={isEnabled} isValid={isValid} /> -} diff --git a/opendc-web/opendc-web-ui/src/components/topologies/sidebar/NameComponent.js b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/NameComponent.js deleted file mode 100644 index ececd07b..00000000 --- a/opendc-web/opendc-web-ui/src/components/topologies/sidebar/NameComponent.js +++ /dev/null @@ -1,69 +0,0 @@ -import PropTypes from 'prop-types' -import React, { useRef, useState } from 'react' -import { Button, TextInput } from '@patternfly/react-core' -import { PencilAltIcon, CheckIcon, TimesIcon } from '@patternfly/react-icons' - -function NameComponent({ name, onEdit }) { - const [isEditing, setEditing] = useState(false) - const nameInput = useRef(null) - - const onCancel = () => { - nameInput.current.value = name - setEditing(false) - } - - const onSubmit = (event) => { - if (event) { - event.preventDefault() - } - - const name = nameInput.current.value - if (name) { - onEdit(name) - } - - setEditing(false) - } - - return ( - <form - className={`pf-c-inline-edit ${isEditing ? 'pf-m-inline-editable' : ''} pf-u-display-inline-block`} - onSubmit={onSubmit} - > - <div className="pf-c-inline-edit__group"> - <div className="pf-c-inline-edit__value" id="single-inline-edit-example-label"> - {name} - </div> - <div className="pf-c-inline-edit__action pf-m-enable-editable"> - <Button className="pf-u-py-0" variant="plain" aria-label="Edit" onClick={() => setEditing(true)}> - <PencilAltIcon /> - </Button> - </div> - </div> - <div className="pf-c-inline-edit__group"> - <div className="pf-c-inline-edit__input"> - <TextInput type="text" defaultValue={name} ref={nameInput} aria-label="Editable text input" /> - </div> - <div className="pf-c-inline-edit__group pf-m-action-group pf-m-icon-group"> - <div className="pf-c-inline-edit__action pf-m-valid"> - <Button className="pf-u-py-0" variant="plain" aria-label="Save edits" onClick={onSubmit}> - <CheckIcon /> - </Button> - </div> - <div className="pf-c-inline-edit__action"> - <Button className="pf-u-py-0" variant="plain" aria-label="Cancel edits" onClick={onCancel}> - <TimesIcon /> - </Button> - </div> - </div> - </div> - </form> - ) -} - -NameComponent.propTypes = { - name: PropTypes.string, - onEdit: PropTypes.func, -} - -export default NameComponent diff --git a/opendc-web/opendc-web-ui/src/components/topologies/sidebar/TopologySidebar.js b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/TopologySidebar.js deleted file mode 100644 index 5aaa7834..00000000 --- a/opendc-web/opendc-web-ui/src/components/topologies/sidebar/TopologySidebar.js +++ /dev/null @@ -1,83 +0,0 @@ -import PropTypes from 'prop-types' -import React from 'react' -import { InteractionLevel } from '../../../shapes' -import BuildingSidebar from './building/BuildingSidebar' -import { - Button, - DrawerActions, - DrawerCloseButton, - DrawerHead, - DrawerPanelBody, - DrawerPanelContent, - Flex, - Title, -} from '@patternfly/react-core' -import { AngleLeftIcon } from '@patternfly/react-icons' -import { useDispatch } from 'react-redux' -import { backButton } from './TopologySidebar.module.css' -import RoomSidebar from './room/RoomSidebar' -import RackSidebar from './rack/RackSidebar' -import MachineSidebar from './machine/MachineSidebar' -import { goDownOneInteractionLevel } from '../../../redux/actions/interaction-level' - -const name = { - BUILDING: 'Building', - ROOM: 'Room', - RACK: 'Rack', - MACHINE: 'Machine', -} - -function TopologySidebar({ interactionLevel, onClose }) { - let sidebarContent - - switch (interactionLevel.mode) { - case 'BUILDING': - sidebarContent = <BuildingSidebar /> - break - case 'ROOM': - sidebarContent = <RoomSidebar roomId={interactionLevel.roomId} /> - break - case 'RACK': - sidebarContent = <RackSidebar tileId={interactionLevel.tileId} /> - break - case 'MACHINE': - sidebarContent = <MachineSidebar tileId={interactionLevel.tileId} position={interactionLevel.position} /> - break - default: - sidebarContent = 'Missing Content' - } - - const dispatch = useDispatch() - const onClick = () => dispatch(goDownOneInteractionLevel()) - - return ( - <DrawerPanelContent isResizable defaultSize="450px" minSize="400px"> - <DrawerHead> - <Flex> - <Button - variant="tertiary" - isSmall - className={backButton} - onClick={interactionLevel.mode === 'BUILDING' ? onClose : onClick} - > - <AngleLeftIcon /> - </Button> - <Title className="pf-u-align-self-center" headingLevel="h1"> - {name[interactionLevel.mode]} - </Title> - </Flex> - <DrawerActions> - <DrawerCloseButton onClose={onClose} /> - </DrawerActions> - </DrawerHead> - <DrawerPanelBody>{sidebarContent}</DrawerPanelBody> - </DrawerPanelContent> - ) -} - -TopologySidebar.propTypes = { - interactionLevel: InteractionLevel, - onClose: PropTypes.func, -} - -export default TopologySidebar diff --git a/opendc-web/opendc-web-ui/src/components/topologies/sidebar/TopologySidebar.module.css b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/TopologySidebar.module.css deleted file mode 100644 index 3853c625..00000000 --- a/opendc-web/opendc-web-ui/src/components/topologies/sidebar/TopologySidebar.module.css +++ /dev/null @@ -1,35 +0,0 @@ -/*! - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -.backButton:global(.pf-c-button) { - align-self: center; - --pf-c-button--after--BorderColor: var(--pf-global--BorderColor--light-100); - color: var(--pf-global--Color--400); - - --pf-c-button--PaddingRight: var(--pf-global--spacer--sm); - --pf-c-button--PaddingLeft: var(--pf-global--spacer--sm); -} - -.backButton:hover, -.backButton:global(.pf-c-button):focus { - --pf-c-button--after--BorderColor: var(--pf-global--BorderColor--100); -} diff --git a/opendc-web/opendc-web-ui/src/components/topologies/sidebar/building/BuildingSidebar.js b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/building/BuildingSidebar.js deleted file mode 100644 index 5fcd46be..00000000 --- a/opendc-web/opendc-web-ui/src/components/topologies/sidebar/building/BuildingSidebar.js +++ /dev/null @@ -1,8 +0,0 @@ -import React from 'react' -import NewRoomConstructionContainer from './NewRoomConstructionContainer' - -function BuildingSidebar() { - return <NewRoomConstructionContainer /> -} - -export default BuildingSidebar diff --git a/opendc-web/opendc-web-ui/src/components/topologies/sidebar/building/NewRoomConstructionComponent.js b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/building/NewRoomConstructionComponent.js deleted file mode 100644 index 9fc85d0c..00000000 --- a/opendc-web/opendc-web-ui/src/components/topologies/sidebar/building/NewRoomConstructionComponent.js +++ /dev/null @@ -1,46 +0,0 @@ -import PropTypes from 'prop-types' -import React from 'react' -import { Button, Toolbar, ToolbarContent, ToolbarGroup, ToolbarItem } from '@patternfly/react-core' -import PlusIcon from '@patternfly/react-icons/dist/js/icons/plus-icon' -import CheckIcon from '@patternfly/react-icons/dist/js/icons/check-icon' - -function NewRoomConstructionComponent({ onStart, onFinish, onCancel, currentRoomInConstruction }) { - if (currentRoomInConstruction === '-1') { - return ( - <Button isBlock icon={<PlusIcon />} onClick={onStart}> - Construct a new room - </Button> - ) - } - return ( - <Toolbar - inset={{ - default: 'insetNone', - }} - > - <ToolbarContent> - <ToolbarGroup> - <ToolbarItem> - <Button icon={<CheckIcon />} onClick={onFinish}> - Finalize new room - </Button> - </ToolbarItem> - <ToolbarItem widths={{ default: '100%' }}> - <Button isBlock variant="secondary" onClick={onCancel}> - Cancel - </Button> - </ToolbarItem> - </ToolbarGroup> - </ToolbarContent> - </Toolbar> - ) -} - -NewRoomConstructionComponent.propTypes = { - onStart: PropTypes.func, - onFinish: PropTypes.func, - onCancel: PropTypes.func, - currentRoomInConstruction: PropTypes.string, -} - -export default NewRoomConstructionComponent diff --git a/opendc-web/opendc-web-ui/src/components/topologies/sidebar/building/NewRoomConstructionContainer.js b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/building/NewRoomConstructionContainer.js deleted file mode 100644 index c149b224..00000000 --- a/opendc-web/opendc-web-ui/src/components/topologies/sidebar/building/NewRoomConstructionContainer.js +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -import React from 'react' -import { useDispatch, useSelector } from 'react-redux' -import { - cancelNewRoomConstruction, - finishNewRoomConstruction, - startNewRoomConstruction, -} from '../../../../redux/actions/topology/building' -import NewRoomConstructionComponent from './NewRoomConstructionComponent' - -function NewRoomConstructionButton() { - const currentRoomInConstruction = useSelector((state) => state.construction.currentRoomInConstruction) - const dispatch = useDispatch() - - return ( - <NewRoomConstructionComponent - onStart={() => dispatch(startNewRoomConstruction())} - onFinish={() => dispatch(finishNewRoomConstruction())} - onCancel={() => dispatch(cancelNewRoomConstruction())} - currentRoomInConstruction={currentRoomInConstruction} - /> - ) -} - -export default NewRoomConstructionButton diff --git a/opendc-web/opendc-web-ui/src/components/topologies/sidebar/machine/DeleteMachine.js b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/machine/DeleteMachine.js deleted file mode 100644 index a4b9457b..00000000 --- a/opendc-web/opendc-web-ui/src/components/topologies/sidebar/machine/DeleteMachine.js +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -import PropTypes from 'prop-types' -import React, { useState } from 'react' -import { useDispatch } from 'react-redux' -import { Button } from '@patternfly/react-core' -import { TrashIcon } from '@patternfly/react-icons' -import ConfirmationModal from '../../../util/modals/ConfirmationModal' -import { deleteMachine } from '../../../../redux/actions/topology/machine' - -function DeleteMachine({ machineId }) { - const dispatch = useDispatch() - const [isVisible, setVisible] = useState(false) - const callback = (isConfirmed) => { - if (isConfirmed) { - dispatch(deleteMachine(machineId)) - } - setVisible(false) - } - return ( - <> - <Button variant="danger" icon={<TrashIcon />} isBlock onClick={() => setVisible(true)}> - Delete this machine - </Button> - <ConfirmationModal - title="Delete this machine" - message="Are you sure you want to delete this machine?" - isOpen={isVisible} - callback={callback} - /> - </> - ) -} - -DeleteMachine.propTypes = { - machineId: PropTypes.string.isRequired, -} - -export default DeleteMachine diff --git a/opendc-web/opendc-web-ui/src/components/topologies/sidebar/machine/MachineSidebar.js b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/machine/MachineSidebar.js deleted file mode 100644 index 8a4c33dc..00000000 --- a/opendc-web/opendc-web-ui/src/components/topologies/sidebar/machine/MachineSidebar.js +++ /dev/null @@ -1,55 +0,0 @@ -import PropTypes from 'prop-types' -import React from 'react' -import UnitTabsComponent from './UnitTabsComponent' -import DeleteMachine from './DeleteMachine' -import { - TextContent, - TextList, - TextListItem, - TextListItemVariants, - TextListVariants, - Title, -} from '@patternfly/react-core' -import { useSelector } from 'react-redux' - -function MachineSidebar({ tileId, position }) { - const machine = useSelector(({ topology }) => { - const rack = topology.racks[topology.tiles[tileId].rack] - - for (const machineId of rack.machines) { - const machine = topology.machines[machineId] - if (machine.position === position) { - return machine - } - } - }) - const machineId = machine.id - return ( - <div> - <TextContent> - <Title headingLevel="h2">Details</Title> - <TextList component={TextListVariants.dl}> - <TextListItem component={TextListItemVariants.dt}>Name</TextListItem> - <TextListItem component={TextListItemVariants.dd}> - Machine at position {machine.position} - </TextListItem> - </TextList> - - <Title headingLevel="h2">Actions</Title> - <DeleteMachine machineId={machineId} /> - - <Title headingLevel="h2">Units</Title> - </TextContent> - <div className="pf-u-h-100"> - <UnitTabsComponent machineId={machineId} /> - </div> - </div> - ) -} - -MachineSidebar.propTypes = { - tileId: PropTypes.string.isRequired, - position: PropTypes.number.isRequired, -} - -export default MachineSidebar diff --git a/opendc-web/opendc-web-ui/src/components/topologies/sidebar/machine/UnitAddComponent.js b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/machine/UnitAddComponent.js deleted file mode 100644 index 18cba23a..00000000 --- a/opendc-web/opendc-web-ui/src/components/topologies/sidebar/machine/UnitAddComponent.js +++ /dev/null @@ -1,42 +0,0 @@ -import PropTypes from 'prop-types' -import React, { useState } from 'react' -import { Button, InputGroup, Select, SelectOption, SelectVariant } from '@patternfly/react-core' -import PlusIcon from '@patternfly/react-icons/dist/js/icons/plus-icon' - -function UnitAddComponent({ units, onAdd }) { - const [isOpen, setOpen] = useState(false) - const [selected, setSelected] = useState(null) - - return ( - <InputGroup> - <Select - variant={SelectVariant.single} - placeholderText="Select a unit" - aria-label="Select Unit" - onToggle={() => setOpen(!isOpen)} - isOpen={isOpen} - onSelect={(_, selection) => { - setSelected(selection) - setOpen(false) - }} - selections={selected} - > - {units.map((unit) => ( - <SelectOption value={unit.id} key={unit.id}> - {unit.name} - </SelectOption> - ))} - </Select> - <Button icon={<PlusIcon />} variant="control" onClick={() => onAdd(selected)} isDisabled={!selected}> - Add - </Button> - </InputGroup> - ) -} - -UnitAddComponent.propTypes = { - units: PropTypes.array.isRequired, - onAdd: PropTypes.func.isRequired, -} - -export default UnitAddComponent diff --git a/opendc-web/opendc-web-ui/src/components/topologies/sidebar/machine/UnitAddContainer.js b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/machine/UnitAddContainer.js deleted file mode 100644 index a0054ef6..00000000 --- a/opendc-web/opendc-web-ui/src/components/topologies/sidebar/machine/UnitAddContainer.js +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -import PropTypes from 'prop-types' -import React from 'react' -import { useDispatch, useSelector } from 'react-redux' -import UnitAddComponent from './UnitAddComponent' -import { addUnit } from '../../../../redux/actions/topology/machine' -import UnitType from './UnitType' - -function UnitAddContainer({ machineId, unitType }) { - const units = useSelector((state) => Object.values(state.topology[unitType])) - const dispatch = useDispatch() - - const onAdd = (id) => dispatch(addUnit(machineId, unitType, id)) - - return <UnitAddComponent onAdd={onAdd} units={units} /> -} - -UnitAddContainer.propTypes = { - machineId: PropTypes.string.isRequired, - unitType: UnitType.isRequired, -} - -export default UnitAddContainer diff --git a/opendc-web/opendc-web-ui/src/components/topologies/sidebar/machine/UnitListComponent.js b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/machine/UnitListComponent.js deleted file mode 100644 index 75ab0ad7..00000000 --- a/opendc-web/opendc-web-ui/src/components/topologies/sidebar/machine/UnitListComponent.js +++ /dev/null @@ -1,113 +0,0 @@ -import PropTypes from 'prop-types' -import React from 'react' -import { - Button, - DataList, - DataListAction, - DataListCell, - DataListItem, - DataListItemCells, - DataListItemRow, - DescriptionList, - DescriptionListDescription, - DescriptionListGroup, - DescriptionListTerm, - EmptyState, - EmptyStateBody, - EmptyStateIcon, - Popover, - Title, -} from '@patternfly/react-core' -import { CubesIcon, InfoIcon, TrashIcon } from '@patternfly/react-icons' -import { ProcessingUnit, StorageUnit } from '../../../../shapes' -import UnitType from './UnitType' - -function UnitInfo({ unit, unitType }) { - if (unitType === 'cpus' || unitType === 'gpus') { - return ( - <DescriptionList> - <DescriptionListGroup> - <DescriptionListTerm>Clock Frequency</DescriptionListTerm> - <DescriptionListDescription>{unit.clockRateMhz} MHz</DescriptionListDescription> - </DescriptionListGroup> - <DescriptionListGroup> - <DescriptionListTerm>Number of Cores</DescriptionListTerm> - <DescriptionListDescription>{unit.numberOfCores}</DescriptionListDescription> - </DescriptionListGroup> - <DescriptionListGroup> - <DescriptionListTerm>Energy Consumption</DescriptionListTerm> - <DescriptionListDescription>{unit.energyConsumptionW} W</DescriptionListDescription> - </DescriptionListGroup> - </DescriptionList> - ) - } - - return ( - <DescriptionList> - <DescriptionListGroup> - <DescriptionListTerm>Speed</DescriptionListTerm> - <DescriptionListDescription>{unit.speedMbPerS} Mb/s</DescriptionListDescription> - </DescriptionListGroup> - <DescriptionListGroup> - <DescriptionListTerm>Capacity</DescriptionListTerm> - <DescriptionListDescription>{unit.sizeMb} MB</DescriptionListDescription> - </DescriptionListGroup> - <DescriptionListGroup> - <DescriptionListTerm>Energy Consumption</DescriptionListTerm> - <DescriptionListDescription>{unit.energyConsumptionW} W</DescriptionListDescription> - </DescriptionListGroup> - </DescriptionList> - ) -} - -UnitInfo.propTypes = { - unitType: UnitType.isRequired, - unit: PropTypes.oneOfType([ProcessingUnit, StorageUnit]).isRequired, -} - -function UnitListComponent({ unitType, units, onDelete }) { - if (units.length === 0) { - return ( - <EmptyState> - <EmptyStateIcon icon={CubesIcon} /> - <Title headingLevel="h5" size="lg"> - No units found - </Title> - <EmptyStateBody>You have not configured any units yet. Add some with the menu above!</EmptyStateBody> - </EmptyState> - ) - } - - return ( - <DataList aria-label="Machine Units" isCompact> - {units.map((unit, index) => ( - <DataListItem key={index}> - <DataListItemRow> - <DataListItemCells dataListCells={[<DataListCell key="unit">{unit.name}</DataListCell>]} /> - <DataListAction id="goto" aria-label="Goto Machine" aria-labelledby="goto"> - <Popover - headerContent="Unit Information" - bodyContent={<UnitInfo unitType={unitType} unit={unit} />} - > - <Button isSmall variant="plain" className="pf-u-p-0"> - <InfoIcon /> - </Button> - </Popover> - <Button isSmall variant="plain" className="pf-u-p-0" onClick={() => onDelete(units[index])}> - <TrashIcon /> - </Button> - </DataListAction> - </DataListItemRow> - </DataListItem> - ))} - </DataList> - ) -} - -UnitListComponent.propTypes = { - unitType: UnitType.isRequired, - units: PropTypes.arrayOf(PropTypes.oneOfType([ProcessingUnit, StorageUnit])).isRequired, - onDelete: PropTypes.func, -} - -export default UnitListComponent diff --git a/opendc-web/opendc-web-ui/src/components/topologies/sidebar/machine/UnitListContainer.js b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/machine/UnitListContainer.js deleted file mode 100644 index bcd4bdcc..00000000 --- a/opendc-web/opendc-web-ui/src/components/topologies/sidebar/machine/UnitListContainer.js +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -import PropTypes from 'prop-types' -import React from 'react' -import { useDispatch, useSelector } from 'react-redux' -import UnitListComponent from './UnitListComponent' -import { deleteUnit } from '../../../../redux/actions/topology/machine' -import UnitType from './UnitType' - -function UnitListContainer({ machineId, unitType }) { - const dispatch = useDispatch() - const units = useSelector((state) => { - const machine = state.topology.machines[machineId] - return machine[unitType].map((id) => state.topology[unitType][id]) - }) - - const onDelete = (unit) => dispatch(deleteUnit(machineId, unitType, unit.id)) - - return <UnitListComponent units={units} unitType={unitType} onDelete={onDelete} /> -} - -UnitListContainer.propTypes = { - machineId: PropTypes.string.isRequired, - unitType: UnitType.isRequired, -} - -export default UnitListContainer diff --git a/opendc-web/opendc-web-ui/src/components/topologies/sidebar/machine/UnitTabsComponent.js b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/machine/UnitTabsComponent.js deleted file mode 100644 index 4032d607..00000000 --- a/opendc-web/opendc-web-ui/src/components/topologies/sidebar/machine/UnitTabsComponent.js +++ /dev/null @@ -1,36 +0,0 @@ -import PropTypes from 'prop-types' -import React, { useState } from 'react' -import { Tab, Tabs, TabTitleText } from '@patternfly/react-core' -import UnitAddContainer from './UnitAddContainer' -import UnitListContainer from './UnitListContainer' - -function UnitTabsComponent({ machineId }) { - const [activeTab, setActiveTab] = useState('cpuModel-units') - - return ( - <Tabs activeKey={activeTab} onSelect={(_, tab) => setActiveTab(tab)}> - <Tab eventKey="cpuModel-units" title={<TabTitleText>CPU</TabTitleText>}> - <UnitAddContainer machineId={machineId} unitType="cpus" /> - <UnitListContainer machineId={machineId} unitType="cpus" /> - </Tab> - <Tab eventKey="gpu-units" title={<TabTitleText>GPU</TabTitleText>}> - <UnitAddContainer machineId={machineId} unitType="gpus" /> - <UnitListContainer machineId={machineId} unitType="gpus" /> - </Tab> - <Tab eventKey="memory-units" title={<TabTitleText>Memory</TabTitleText>}> - <UnitAddContainer machineId={machineId} unitType="memories" /> - <UnitListContainer machineId={machineId} unitType="memories" /> - </Tab> - <Tab eventKey="storage-units" title={<TabTitleText>Storage</TabTitleText>}> - <UnitAddContainer machineId={machineId} unitType="storages" /> - <UnitListContainer machineId={machineId} unitType="storages" /> - </Tab> - </Tabs> - ) -} - -UnitTabsComponent.propTypes = { - machineId: PropTypes.string.isRequired, -} - -export default UnitTabsComponent diff --git a/opendc-web/opendc-web-ui/src/components/topologies/sidebar/machine/UnitType.js b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/machine/UnitType.js deleted file mode 100644 index b6d7bf8b..00000000 --- a/opendc-web/opendc-web-ui/src/components/topologies/sidebar/machine/UnitType.js +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (c) 2022 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -import PropTypes from 'prop-types' - -export default PropTypes.oneOf(['cpus', 'gpus', 'memories', 'storages']) diff --git a/opendc-web/opendc-web-ui/src/components/topologies/sidebar/rack/AddPrefab.js b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/rack/AddPrefab.js deleted file mode 100644 index 6a0c3ff3..00000000 --- a/opendc-web/opendc-web-ui/src/components/topologies/sidebar/rack/AddPrefab.js +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -import PropTypes from 'prop-types' -import React from 'react' -import { Button } from '@patternfly/react-core' -import { SaveIcon } from '@patternfly/react-icons' - -function AddPrefab() { - const onClick = () => {} // TODO - return ( - <Button variant="primary" icon={<SaveIcon />} isBlock onClick={onClick} className="pf-u-mb-sm"> - Save this rack to a prefab - </Button> - ) -} - -AddPrefab.propTypes = { - tileId: PropTypes.string.isRequired, -} - -export default AddPrefab diff --git a/opendc-web/opendc-web-ui/src/components/topologies/sidebar/rack/DeleteRackContainer.js b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/rack/DeleteRackContainer.js deleted file mode 100644 index 0583a7a4..00000000 --- a/opendc-web/opendc-web-ui/src/components/topologies/sidebar/rack/DeleteRackContainer.js +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -import PropTypes from 'prop-types' -import React, { useState } from 'react' -import { useDispatch, useSelector } from 'react-redux' -import TrashIcon from '@patternfly/react-icons/dist/js/icons/trash-icon' -import { Button } from '@patternfly/react-core' -import ConfirmationModal from '../../../util/modals/ConfirmationModal' -import { deleteRack } from '../../../../redux/actions/topology/rack' - -function DeleteRackContainer({ tileId }) { - const dispatch = useDispatch() - const [isVisible, setVisible] = useState(false) - const rackId = useSelector((state) => state.topology.tiles[tileId].rack) - const callback = (isConfirmed) => { - if (isConfirmed) { - dispatch(deleteRack(tileId, rackId)) - } - setVisible(false) - } - return ( - <> - <Button variant="danger" icon={<TrashIcon />} isBlock onClick={() => setVisible(true)}> - Delete this rack - </Button> - <ConfirmationModal - title="Delete this rack" - message="Are you sure you want to delete this rack?" - isOpen={isVisible} - callback={callback} - /> - </> - ) -} - -DeleteRackContainer.propTypes = { - tileId: PropTypes.string.isRequired, -} - -export default DeleteRackContainer diff --git a/opendc-web/opendc-web-ui/src/components/topologies/sidebar/rack/MachineComponent.js b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/rack/MachineComponent.js deleted file mode 100644 index b0a96a9f..00000000 --- a/opendc-web/opendc-web-ui/src/components/topologies/sidebar/rack/MachineComponent.js +++ /dev/null @@ -1,40 +0,0 @@ -import PropTypes from 'prop-types' -import React from 'react' -import { Flex, Label } from '@patternfly/react-core' -import { Machine } from '../../../../shapes' - -const UnitIcon = ({ id, type }) => ( - // eslint-disable-next-line @next/next/no-img-element - <img src={'/img/topology/' + id + '-icon.png'} alt={'Machine contains ' + type + ' units'} height={24} width={24} /> -) - -UnitIcon.propTypes = { - id: PropTypes.string, - type: PropTypes.string, -} - -function MachineComponent({ machine, onClick }) { - const hasNoUnits = - machine.cpus.length + machine.gpus.length + machine.memories.length + machine.storages.length === 0 - - return ( - <Flex onClick={() => onClick()}> - {machine.cpus.length > 0 ? <UnitIcon id="cpuModel" type="CPU" /> : undefined} - {machine.gpus.length > 0 ? <UnitIcon id="gpu" type="GPU" /> : undefined} - {machine.memories.length > 0 ? <UnitIcon id="memory" type="memory" /> : undefined} - {machine.storages.length > 0 ? <UnitIcon id="storage" type="storage" /> : undefined} - {hasNoUnits ? ( - <Label variant="outline" color="orange"> - Machine with no units - </Label> - ) : undefined} - </Flex> - ) -} - -MachineComponent.propTypes = { - machine: Machine.isRequired, - onClick: PropTypes.func, -} - -export default MachineComponent diff --git a/opendc-web/opendc-web-ui/src/components/topologies/sidebar/rack/MachineListComponent.js b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/rack/MachineListComponent.js deleted file mode 100644 index 02c97730..00000000 --- a/opendc-web/opendc-web-ui/src/components/topologies/sidebar/rack/MachineListComponent.js +++ /dev/null @@ -1,80 +0,0 @@ -import PropTypes from 'prop-types' -import React from 'react' -import MachineComponent from './MachineComponent' -import { - Badge, - Button, - DataList, - DataListAction, - DataListCell, - DataListItem, - DataListItemCells, - DataListItemRow, -} from '@patternfly/react-core' -import { AngleRightIcon, PlusIcon } from '@patternfly/react-icons' -import { Machine } from '../../../../shapes' - -function MachineListComponent({ machines = [], onSelect, onAdd }) { - return ( - <DataList aria-label="Rack Units"> - {machines - .map((machine, index) => - machine ? ( - <DataListItem key={index} onClick={() => onSelect(index + 1)}> - <DataListItemRow> - <DataListItemCells - dataListCells={[ - <DataListCell isIcon key="icon"> - <Badge isRead>{index + 1}U</Badge> - </DataListCell>, - <DataListCell key="primary content"> - <MachineComponent onClick={() => onSelect(index + 1)} machine={machine} /> - </DataListCell>, - ]} - /> - <DataListAction id="goto" aria-label="Goto Machine" aria-labelledby="goto"> - <Button isSmall variant="plain" className="pf-u-p-0"> - <AngleRightIcon /> - </Button> - </DataListAction> - </DataListItemRow> - </DataListItem> - ) : ( - <DataListItem key={index}> - <DataListItemRow> - <DataListItemCells - dataListCells={[ - <DataListCell isIcon key="icon"> - <Badge isRead>{index + 1}U</Badge> - </DataListCell>, - <DataListCell key="add" className="text-secondary"> - Empty Slot - </DataListCell>, - ]} - /> - <DataListAction id="add" aria-label="Add Machine" aria-labelledby="add"> - <Button - isSmall - variant="plain" - className="pf-u-p-0" - onClick={() => onAdd(index + 1)} - > - <PlusIcon /> - </Button> - </DataListAction> - </DataListItemRow> - </DataListItem> - ) - ) - .reverse()} - </DataList> - ) -} - -MachineListComponent.propTypes = { - machines: PropTypes.arrayOf(Machine), - onSelect: PropTypes.func.isRequired, - onAdd: PropTypes.func.isRequired, -} - -export default MachineListComponent diff --git a/opendc-web/opendc-web-ui/src/components/topologies/sidebar/rack/MachineListContainer.js b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/rack/MachineListContainer.js deleted file mode 100644 index e1914730..00000000 --- a/opendc-web/opendc-web-ui/src/components/topologies/sidebar/rack/MachineListContainer.js +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -import PropTypes from 'prop-types' -import React, { useMemo } from 'react' -import { useDispatch, useSelector } from 'react-redux' -import MachineListComponent from './MachineListComponent' -import { goFromRackToMachine } from '../../../../redux/actions/interaction-level' -import { addMachine } from '../../../../redux/actions/topology/rack' - -function MachineListContainer({ tileId, ...props }) { - const rack = useSelector((state) => state.topology.racks[state.topology.tiles[tileId].rack]) - const machines = useSelector((state) => rack.machines.map((id) => state.topology.machines[id])) - const machinesNull = useMemo(() => { - const res = Array(rack.capacity).fill(null) - for (const machine of machines) { - res[machine.position - 1] = machine - } - return res - }, [rack, machines]) - const dispatch = useDispatch() - - return ( - <MachineListComponent - {...props} - machines={machinesNull} - onAdd={(index) => dispatch(addMachine(rack.id, index))} - onSelect={(index) => dispatch(goFromRackToMachine(index))} - /> - ) -} - -MachineListContainer.propTypes = { - tileId: PropTypes.string.isRequired, -} - -export default MachineListContainer diff --git a/opendc-web/opendc-web-ui/src/components/topologies/sidebar/rack/RackNameContainer.js b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/rack/RackNameContainer.js deleted file mode 100644 index c3422318..00000000 --- a/opendc-web/opendc-web-ui/src/components/topologies/sidebar/rack/RackNameContainer.js +++ /dev/null @@ -1,22 +0,0 @@ -import PropTypes from 'prop-types' -import React from 'react' -import { useDispatch, useSelector } from 'react-redux' -import NameComponent from '../NameComponent' -import { editRackName } from '../../../../redux/actions/topology/rack' - -const RackNameContainer = ({ tileId }) => { - const { name: rackName, id } = useSelector((state) => state.topology.racks[state.topology.tiles[tileId].rack]) - const dispatch = useDispatch() - const callback = (name) => { - if (name) { - dispatch(editRackName(id, name)) - } - } - return <NameComponent name={rackName} onEdit={callback} /> -} - -RackNameContainer.propTypes = { - tileId: PropTypes.string.isRequired, -} - -export default RackNameContainer diff --git a/opendc-web/opendc-web-ui/src/components/topologies/sidebar/rack/RackSidebar.js b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/rack/RackSidebar.js deleted file mode 100644 index cb7d3b68..00000000 --- a/opendc-web/opendc-web-ui/src/components/topologies/sidebar/rack/RackSidebar.js +++ /dev/null @@ -1,58 +0,0 @@ -import PropTypes from 'prop-types' -import React from 'react' -import { machineListContainer, sidebarContainer } from './RackSidebar.module.css' -import RackNameContainer from './RackNameContainer' -import AddPrefab from './AddPrefab' -import DeleteRackContainer from './DeleteRackContainer' -import MachineListContainer from './MachineListContainer' -import { - Skeleton, - TextContent, - TextList, - TextListItem, - TextListItemVariants, - TextListVariants, - Title, -} from '@patternfly/react-core' -import { useSelector } from 'react-redux' - -function RackSidebar({ tileId }) { - const rack = useSelector((state) => state.topology.racks[state.topology.tiles[tileId].rack]) - - return ( - <div className={sidebarContainer}> - <TextContent> - <Title headingLevel="h2">Details</Title> - <TextList component={TextListVariants.dl}> - <TextListItem - component={TextListItemVariants.dt} - className="pf-u-display-inline-flex pf-u-align-items-center" - > - Name - </TextListItem> - <TextListItem component={TextListItemVariants.dd}> - <RackNameContainer tileId={tileId} /> - </TextListItem> - <TextListItem component={TextListItemVariants.dt}>Capacity</TextListItem> - <TextListItem component={TextListItemVariants.dd}> - {rack?.capacity ?? <Skeleton screenreaderText="Loading rack" />} - </TextListItem> - </TextList> - <Title headingLevel="h2">Actions</Title> - <AddPrefab tileId={tileId} /> - <DeleteRackContainer tileId={tileId} /> - - <Title headingLevel="h2">Slots</Title> - </TextContent> - <div className={machineListContainer}> - <MachineListContainer tileId={tileId} /> - </div> - </div> - ) -} - -RackSidebar.propTypes = { - tileId: PropTypes.string.isRequired, -} - -export default RackSidebar diff --git a/opendc-web/opendc-web-ui/src/components/topologies/sidebar/rack/RackSidebar.module.css b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/rack/RackSidebar.module.css deleted file mode 100644 index f4c8829f..00000000 --- a/opendc-web/opendc-web-ui/src/components/topologies/sidebar/rack/RackSidebar.module.css +++ /dev/null @@ -1,14 +0,0 @@ -.sidebarContainer { - display: flex; - flex-direction: column; - - height: 100%; -} - -.machineListContainer { - overflow-y: auto; - - flex: 1 0 300px; - - margin-top: 10px; -} diff --git a/opendc-web/opendc-web-ui/src/components/topologies/sidebar/room/DeleteRoomContainer.js b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/room/DeleteRoomContainer.js deleted file mode 100644 index 29b8f78a..00000000 --- a/opendc-web/opendc-web-ui/src/components/topologies/sidebar/room/DeleteRoomContainer.js +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -import PropTypes from 'prop-types' -import React, { useState } from 'react' -import { useDispatch } from 'react-redux' -import ConfirmationModal from '../../../util/modals/ConfirmationModal' -import { deleteRoom } from '../../../../redux/actions/topology/room' -import TrashIcon from '@patternfly/react-icons/dist/js/icons/trash-icon' -import { Button } from '@patternfly/react-core' - -function DeleteRoomContainer({ roomId }) { - const dispatch = useDispatch() - const [isVisible, setVisible] = useState(false) - const callback = (isConfirmed) => { - if (isConfirmed) { - dispatch(deleteRoom(roomId)) - } - setVisible(false) - } - return ( - <> - <Button variant="danger" icon={<TrashIcon />} isBlock onClick={() => setVisible(true)}> - Delete this room - </Button> - <ConfirmationModal - title="Delete this room" - message="Are you sure you want to delete this room?" - isOpen={isVisible} - callback={callback} - /> - </> - ) -} - -DeleteRoomContainer.propTypes = { - roomId: PropTypes.string.isRequired, -} - -export default DeleteRoomContainer diff --git a/opendc-web/opendc-web-ui/src/components/topologies/sidebar/room/EditRoomContainer.js b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/room/EditRoomContainer.js deleted file mode 100644 index 7a278cd6..00000000 --- a/opendc-web/opendc-web-ui/src/components/topologies/sidebar/room/EditRoomContainer.js +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -import PropTypes from 'prop-types' -import React from 'react' -import { useDispatch, useSelector } from 'react-redux' -import { finishRoomEdit, startRoomEdit } from '../../../../redux/actions/topology/building' -import CheckIcon from '@patternfly/react-icons/dist/js/icons/check-icon' -import PencilAltIcon from '@patternfly/react-icons/dist/js/icons/pencil-alt-icon' -import { Button } from '@patternfly/react-core' - -function EditRoomContainer({ roomId }) { - const isEditing = useSelector((state) => state.construction.currentRoomInConstruction !== '-1') - const isInRackConstructionMode = useSelector((state) => state.construction.inRackConstructionMode) - - const dispatch = useDispatch() - const onEdit = () => dispatch(startRoomEdit(roomId)) - const onFinish = () => dispatch(finishRoomEdit()) - - return isEditing ? ( - <Button variant="tertiary" icon={<CheckIcon />} isBlock onClick={onFinish} className="pf-u-mb-sm"> - Finish editing room - </Button> - ) : ( - <Button - variant="tertiary" - icon={<PencilAltIcon />} - isBlock - disabled={isInRackConstructionMode} - onClick={() => (isInRackConstructionMode ? undefined : onEdit())} - className="pf-u-mb-sm" - > - Edit the tiles of this room - </Button> - ) -} - -EditRoomContainer.propTypes = { - roomId: PropTypes.string.isRequired, -} - -export default EditRoomContainer diff --git a/opendc-web/opendc-web-ui/src/components/topologies/sidebar/room/RackConstructionComponent.js b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/room/RackConstructionComponent.js deleted file mode 100644 index a384d5d5..00000000 --- a/opendc-web/opendc-web-ui/src/components/topologies/sidebar/room/RackConstructionComponent.js +++ /dev/null @@ -1,35 +0,0 @@ -import PropTypes from 'prop-types' -import React from 'react' -import { Button } from '@patternfly/react-core' -import { PlusIcon, TimesIcon } from '@patternfly/react-icons' - -const RackConstructionComponent = ({ onStart, onStop, inRackConstructionMode, isEditingRoom }) => { - if (inRackConstructionMode) { - return ( - <Button isBlock={true} icon={<TimesIcon />} onClick={onStop} className="pf-u-mb-sm"> - Stop rack construction - </Button> - ) - } - - return ( - <Button - icon={<PlusIcon />} - isBlock - isDisabled={isEditingRoom} - onClick={() => (isEditingRoom ? undefined : onStart())} - className="pf-u-mb-sm" - > - Start rack construction - </Button> - ) -} - -RackConstructionComponent.propTypes = { - onStart: PropTypes.func, - onStop: PropTypes.func, - inRackConstructionMode: PropTypes.bool, - isEditingRoom: PropTypes.bool, -} - -export default RackConstructionComponent diff --git a/opendc-web/opendc-web-ui/src/components/topologies/sidebar/room/RackConstructionContainer.js b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/room/RackConstructionContainer.js deleted file mode 100644 index e04287a5..00000000 --- a/opendc-web/opendc-web-ui/src/components/topologies/sidebar/room/RackConstructionContainer.js +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -import React from 'react' -import { useDispatch, useSelector } from 'react-redux' -import { startRackConstruction, stopRackConstruction } from '../../../../redux/actions/topology/room' -import RackConstructionComponent from './RackConstructionComponent' - -function RackConstructionContainer(props) { - const isRackConstructionMode = useSelector((state) => state.construction.inRackConstructionMode) - const isEditingRoom = useSelector((state) => state.construction.currentRoomInConstruction !== '-1') - - const dispatch = useDispatch() - const onStart = () => dispatch(startRackConstruction()) - const onStop = () => dispatch(stopRackConstruction()) - return ( - <RackConstructionComponent - {...props} - inRackConstructionMode={isRackConstructionMode} - isEditingRoom={isEditingRoom} - onStart={onStart} - onStop={onStop} - /> - ) -} - -export default RackConstructionContainer diff --git a/opendc-web/opendc-web-ui/src/components/topologies/sidebar/room/RoomName.js b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/room/RoomName.js deleted file mode 100644 index 72d45bea..00000000 --- a/opendc-web/opendc-web-ui/src/components/topologies/sidebar/room/RoomName.js +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -import PropTypes from 'prop-types' -import React from 'react' -import { useDispatch, useSelector } from 'react-redux' -import NameComponent from '../NameComponent' -import { editRoomName } from '../../../../redux/actions/topology/room' - -function RoomName({ roomId }) { - const { name: roomName, id } = useSelector((state) => state.topology.rooms[roomId]) - const dispatch = useDispatch() - const callback = (name) => { - if (name) { - dispatch(editRoomName(id, name)) - } - } - return <NameComponent name={roomName} onEdit={callback} /> -} - -RoomName.propTypes = { - roomId: PropTypes.string.isRequired, -} - -export default RoomName diff --git a/opendc-web/opendc-web-ui/src/components/topologies/sidebar/room/RoomSidebar.js b/opendc-web/opendc-web-ui/src/components/topologies/sidebar/room/RoomSidebar.js deleted file mode 100644 index 6ad489e0..00000000 --- a/opendc-web/opendc-web-ui/src/components/topologies/sidebar/room/RoomSidebar.js +++ /dev/null @@ -1,43 +0,0 @@ -import PropTypes from 'prop-types' -import React from 'react' -import RoomName from './RoomName' -import RackConstructionContainer from './RackConstructionContainer' -import EditRoomContainer from './EditRoomContainer' -import DeleteRoomContainer from './DeleteRoomContainer' -import { - TextContent, - TextList, - TextListItem, - TextListItemVariants, - TextListVariants, - Title, -} from '@patternfly/react-core' - -const RoomSidebar = ({ roomId }) => { - return ( - <TextContent> - <Title headingLevel="h2">Details</Title> - <TextList component={TextListVariants.dl}> - <TextListItem - component={TextListItemVariants.dt} - className="pf-u-display-inline-flex pf-u-align-items-center" - > - Name - </TextListItem> - <TextListItem component={TextListItemVariants.dd}> - <RoomName roomId={roomId} /> - </TextListItem> - </TextList> - <Title headingLevel="h2">Construction</Title> - <RackConstructionContainer /> - <EditRoomContainer roomId={roomId} /> - <DeleteRoomContainer roomId={roomId} /> - </TextContent> - ) -} - -RoomSidebar.propTypes = { - roomId: PropTypes.string.isRequired, -} - -export default RoomSidebar diff --git a/opendc-web/opendc-web-ui/src/components/util/TableEmptyState.js b/opendc-web/opendc-web-ui/src/components/util/TableEmptyState.js deleted file mode 100644 index 9d16ffbb..00000000 --- a/opendc-web/opendc-web-ui/src/components/util/TableEmptyState.js +++ /dev/null @@ -1,103 +0,0 @@ -/* - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -import PropTypes from 'prop-types' -import { Bullseye, EmptyState, EmptyStateBody, EmptyStateIcon, Spinner, Title } from '@patternfly/react-core' -import { SearchIcon, CubesIcon } from '@patternfly/react-icons' -import { Status } from '../../shapes' - -function TableEmptyState({ - status, - isFiltering, - loadingTitle = 'Loading', - emptyTitle = 'No results found', - emptyText = 'No results found of this type.', - emptyAction = '', -}) { - if (status === 'loading') { - return ( - <Bullseye> - <EmptyState variant="xs"> - <EmptyStateIcon variant="container" component={Spinner} /> - <Title headingLevel="h4" size="md"> - {loadingTitle} - </Title> - </EmptyState> - </Bullseye> - ) - } else if (status === 'error') { - return ( - <EmptyState variant="xs"> - <Title headingLevel="h4" size="md"> - Unable to connect - </Title> - <EmptyStateBody> - There was an error retrieving data. Check your connection and try again. - </EmptyStateBody> - </EmptyState> - ) - } else if (status === 'idle') { - return ( - <EmptyState variant="xs"> - <EmptyStateIcon icon={CubesIcon} /> - <Title headingLevel="h4" size="md"> - {emptyTitle} - </Title> - <EmptyStateBody>No results available at this moment.</EmptyStateBody> - </EmptyState> - ) - } else if (isFiltering) { - return ( - <EmptyState variant="xs"> - <EmptyStateIcon icon={SearchIcon} /> - <Title headingLevel="h4" size="md"> - No results found - </Title> - <EmptyStateBody> - No results match this filter criteria. Remove all filters or clear all filters to show results. - </EmptyStateBody> - </EmptyState> - ) - } - - return ( - <EmptyState variant="xs"> - <EmptyStateIcon icon={CubesIcon} /> - <Title headingLevel="h4" size="md"> - {emptyTitle} - </Title> - <EmptyStateBody>{emptyText}</EmptyStateBody> - {emptyAction} - </EmptyState> - ) -} - -TableEmptyState.propTypes = { - status: Status.isRequired, - isFiltering: PropTypes.bool, - loadingTitle: PropTypes.string, - emptyTitle: PropTypes.string, - emptyText: PropTypes.string, - emptyAction: PropTypes.node, -} - -export default TableEmptyState diff --git a/opendc-web/opendc-web-ui/src/components/util/modals/ConfirmationModal.js b/opendc-web/opendc-web-ui/src/components/util/modals/ConfirmationModal.js deleted file mode 100644 index f6e1c98b..00000000 --- a/opendc-web/opendc-web-ui/src/components/util/modals/ConfirmationModal.js +++ /dev/null @@ -1,27 +0,0 @@ -import PropTypes from 'prop-types' -import React from 'react' -import Modal from './Modal' - -function ConfirmationModal({ title, message, isOpen, callback }) { - return ( - <Modal - title={title} - isOpen={isOpen} - onSubmit={() => callback(true)} - onCancel={() => callback(false)} - submitButtonType="danger" - submitButtonText="Confirm" - > - {message} - </Modal> - ) -} - -ConfirmationModal.propTypes = { - title: PropTypes.string.isRequired, - message: PropTypes.string.isRequired, - isOpen: PropTypes.bool.isRequired, - callback: PropTypes.func.isRequired, -} - -export default ConfirmationModal diff --git a/opendc-web/opendc-web-ui/src/components/util/modals/Modal.js b/opendc-web/opendc-web-ui/src/components/util/modals/Modal.js deleted file mode 100644 index d4577062..00000000 --- a/opendc-web/opendc-web-ui/src/components/util/modals/Modal.js +++ /dev/null @@ -1,38 +0,0 @@ -import React from 'react' -import PropTypes from 'prop-types' -import { Button, Modal as PModal, ModalVariant } from '@patternfly/react-core' - -function Modal({ children, title, isOpen, onSubmit, onCancel, submitButtonType, submitButtonText }) { - const actions = [ - <Button variant={submitButtonType} onClick={onSubmit} key="confirm"> - {submitButtonText} - </Button>, - <Button variant="link" onClick={onCancel} key="cancel"> - Cancel - </Button>, - ] - - return ( - <PModal variant={ModalVariant.small} isOpen={isOpen} onClose={onCancel} title={title} actions={actions}> - {children} - </PModal> - ) -} - -Modal.propTypes = { - title: PropTypes.string.isRequired, - isOpen: PropTypes.bool, - onSubmit: PropTypes.func.isRequired, - onCancel: PropTypes.func.isRequired, - submitButtonType: PropTypes.string, - submitButtonText: PropTypes.string, - children: PropTypes.node, -} - -Modal.defaultProps = { - submitButtonType: 'primary', - submitButtonText: 'Save', - isOpen: false, -} - -export default Modal diff --git a/opendc-web/opendc-web-ui/src/components/util/modals/TextInputModal.js b/opendc-web/opendc-web-ui/src/components/util/modals/TextInputModal.js deleted file mode 100644 index 392a729e..00000000 --- a/opendc-web/opendc-web-ui/src/components/util/modals/TextInputModal.js +++ /dev/null @@ -1,70 +0,0 @@ -import PropTypes from 'prop-types' -import React, { useRef, useState } from 'react' -import Modal from './Modal' -import { Form, FormGroup, TextInput } from '@patternfly/react-core' - -function TextInputModal({ title, label, isOpen, callback, initialValue }) { - const textInput = useRef(null) - const [isSubmitted, setSubmitted] = useState(false) - const [isValid, setValid] = useState(true) - - const resetState = () => { - textInput.current.value = '' - setSubmitted(false) - setValid(false) - } - const onSubmit = (event) => { - const value = textInput.current.value - setSubmitted(true) - - if (event) { - event.preventDefault() - } - - if (!value) { - setValid(false) - return false - } - - callback(value) - resetState() - return true - } - const onCancel = () => { - callback(undefined) - resetState() - } - - return ( - <Modal title={title} isOpen={isOpen} onSubmit={onSubmit} onCancel={onCancel}> - <Form onSubmit={onSubmit}> - <FormGroup - label={label} - fieldId="text-input" - isRequired - validated={isSubmitted && !isValid ? 'error' : 'default'} - helperTextInvalid="This field cannot be empty" - > - <TextInput - id="text-input" - name="text-input" - isRequired - type="text" - ref={textInput} - defaultValue={initialValue} - /> - </FormGroup> - </Form> - </Modal> - ) -} - -TextInputModal.propTypes = { - title: PropTypes.string.isRequired, - label: PropTypes.string.isRequired, - isOpen: PropTypes.bool.isRequired, - callback: PropTypes.func.isRequired, - initialValue: PropTypes.string, -} - -export default TextInputModal diff --git a/opendc-web/opendc-web-ui/src/config.js b/opendc-web/opendc-web-ui/src/config.js deleted file mode 100644 index 52952d69..00000000 --- a/opendc-web/opendc-web-ui/src/config.js +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright (c) 2022 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -/** - * URL to OpenDC API. - */ -export const apiUrl = process.env.NEXT_PUBLIC_API_BASE_URL - -/** - * Authentication configuration. - */ -export const auth = { - domain: process.env.NEXT_PUBLIC_AUTH0_DOMAIN, - clientId: process.env.NEXT_PUBLIC_AUTH0_CLIENT_ID, - audience: process.env.NEXT_PUBLIC_AUTH0_AUDIENCE, - redirectUri: global.window && global.window.location.origin, -} - -/** - * Sentry DSN for web frontend. - */ -export const sentryDsn = process.env.NEXT_PUBLIC_SENTRY_DSN diff --git a/opendc-web/opendc-web-ui/src/data/experiments.js b/opendc-web/opendc-web-ui/src/data/experiments.js deleted file mode 100644 index ca8912a2..00000000 --- a/opendc-web/opendc-web-ui/src/data/experiments.js +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -import { useQuery } from 'react-query' -import { fetchTraces } from '../api/traces' -import { fetchSchedulers } from '../api/schedulers' - -/** - * Configure the query defaults for the experiment endpoints. - */ -export function configureExperimentClient(queryClient, auth) { - queryClient.setQueryDefaults('traces', { queryFn: () => fetchTraces(auth) }) - queryClient.setQueryDefaults('schedulers', { queryFn: () => fetchSchedulers(auth) }) -} - -/** - * Return the available traces to experiment with. - */ -export function useTraces(options) { - return useQuery('traces', options) -} - -/** - * Return the available schedulers to experiment with. - */ -export function useSchedulers(options) { - return useQuery('schedulers', options) -} diff --git a/opendc-web/opendc-web-ui/src/data/project.js b/opendc-web/opendc-web-ui/src/data/project.js deleted file mode 100644 index 60a8fab6..00000000 --- a/opendc-web/opendc-web-ui/src/data/project.js +++ /dev/null @@ -1,166 +0,0 @@ -/* - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -import { useQuery, useMutation } from 'react-query' -import { addProject, deleteProject, fetchProject, fetchProjects } from '../api/projects' -import { addPortfolio, deletePortfolio, fetchPortfolio, fetchPortfolios } from '../api/portfolios' -import { addScenario, deleteScenario, fetchScenario } from '../api/scenarios' - -/** - * Configure the query defaults for the project endpoints. - */ -export function configureProjectClient(queryClient, auth) { - queryClient.setQueryDefaults('projects', { - queryFn: ({ queryKey }) => (queryKey.length === 1 ? fetchProjects(auth) : fetchProject(auth, queryKey[1])), - }) - - queryClient.setMutationDefaults('addProject', { - mutationFn: (data) => addProject(auth, data), - onSuccess: async (result) => { - queryClient.setQueryData('projects', (old = []) => [...old, result]) - queryClient.setQueryData(['projects', result.id], result) - }, - }) - queryClient.setMutationDefaults('deleteProject', { - mutationFn: (id) => deleteProject(auth, id), - onSuccess: async (result) => { - queryClient.setQueryData('projects', (old = []) => old.filter((project) => project.id !== result.id)) - queryClient.removeQueries(['projects', result.id]) - }, - }) - - queryClient.setQueryDefaults('portfolios', { - queryFn: ({ queryKey }) => - queryKey.length === 2 ? fetchPortfolios(auth, queryKey[1]) : fetchPortfolio(auth, queryKey[1], queryKey[2]), - }) - queryClient.setMutationDefaults('addPortfolio', { - mutationFn: ({ projectId, ...data }) => addPortfolio(auth, projectId, data), - onSuccess: async (result) => { - queryClient.setQueryData(['portfolios', result.project.id], (old = []) => [...old, result]) - queryClient.setQueryData(['portfolios', result.project.id, result.number], result) - }, - }) - queryClient.setMutationDefaults('deletePortfolio', { - mutationFn: ({ projectId, number }) => deletePortfolio(auth, projectId, number), - onSuccess: async (result) => { - queryClient.setQueryData(['portfolios', result.project.id], (old = []) => - old.filter((portfolio) => portfolio.id !== result.id) - ) - queryClient.removeQueries(['portfolios', result.project.id, result.number]) - }, - }) - - queryClient.setQueryDefaults('scenarios', { - queryFn: ({ queryKey }) => fetchScenario(auth, queryKey[1], queryKey[2]), - }) - queryClient.setMutationDefaults('addScenario', { - mutationFn: ({ projectId, portfolioNumber, data }) => addScenario(auth, projectId, portfolioNumber, data), - onSuccess: async (result) => { - // Register updated scenario in cache - queryClient.setQueryData(['scenarios', result.project.id, result.id], result) - queryClient.setQueryData(['portfolios', result.project.id, result.portfolio.number], (old) => ({ - ...old, - scenarios: [...old.scenarios, result], - })) - }, - }) - queryClient.setMutationDefaults('deleteScenario', { - mutationFn: ({ projectId, number }) => deleteScenario(auth, projectId, number), - onSuccess: async (result) => { - queryClient.removeQueries(['scenarios', result.project.id, result.id]) - queryClient.setQueryData(['portfolios', result.project.id, result.portfolio.number], (old) => ({ - ...old, - scenarios: old?.scenarios?.filter((scenario) => scenario.id !== result.id), - })) - }, - }) -} - -/** - * Return the available projects. - */ -export function useProjects(options = {}) { - return useQuery('projects', options) -} - -/** - * Return the project with the specified identifier. - */ -export function useProject(projectId, options = {}) { - return useQuery(['projects', projectId], { enabled: !!projectId, ...options }) -} - -/** - * Create a mutation for a new project. - */ -export function useNewProject() { - return useMutation('addProject') -} - -/** - * Create a mutation for deleting a project. - */ -export function useDeleteProject() { - return useMutation('deleteProject') -} - -/** - * Return the portfolio with the specified identifier. - */ -export function usePortfolio(projectId, portfolioId, options = {}) { - return useQuery(['portfolios', projectId, portfolioId], { enabled: !!(projectId && portfolioId), ...options }) -} - -/** - * Return the portfolios of the specified project. - */ -export function usePortfolios(projectId, options = {}) { - return useQuery(['portfolios', projectId], { enabled: !!projectId, ...options }) -} - -/** - * Create a mutation for a new portfolio. - */ -export function useNewPortfolio() { - return useMutation('addPortfolio') -} - -/** - * Create a mutation for deleting a portfolio. - */ -export function useDeletePortfolio() { - return useMutation('deletePortfolio') -} - -/** - * Create a mutation for a new scenario. - */ -export function useNewScenario() { - return useMutation('addScenario') -} - -/** - * Create a mutation for deleting a scenario. - */ -export function useDeleteScenario() { - return useMutation('deleteScenario') -} diff --git a/opendc-web/opendc-web-ui/src/data/query.js b/opendc-web/opendc-web-ui/src/data/query.js deleted file mode 100644 index 3e5423b9..00000000 --- a/opendc-web/opendc-web-ui/src/data/query.js +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -import { useMemo } from 'react' -import { QueryClient } from 'react-query' -import { useAuth } from '../auth' -import { configureExperimentClient } from './experiments' -import { configureProjectClient } from './project' -import { configureTopologyClient } from './topology' -import { configureUserClient } from './user' - -let queryClient - -function createQueryClient(auth) { - const client = new QueryClient() - configureProjectClient(client, auth) - configureExperimentClient(client, auth) - configureTopologyClient(client, auth) - configureUserClient(client, auth) - return client -} - -function initializeQueryClient(auth) { - const _queryClient = queryClient ?? createQueryClient(auth) - - // For SSG and SSR always create a new query client - if (typeof window === 'undefined') return _queryClient - // Create the query client once in the client - if (!queryClient) queryClient = _queryClient - - return _queryClient -} - -/** - * Obtain a cached query client. - */ -export function useNewQueryClient() { - const auth = useAuth() - return useMemo(() => initializeQueryClient(auth), []) // eslint-disable-line react-hooks/exhaustive-deps -} diff --git a/opendc-web/opendc-web-ui/src/data/topology.js b/opendc-web/opendc-web-ui/src/data/topology.js deleted file mode 100644 index d5e624d5..00000000 --- a/opendc-web/opendc-web-ui/src/data/topology.js +++ /dev/null @@ -1,88 +0,0 @@ -/* - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -import { useQuery, useMutation } from 'react-query' -import { addTopology, deleteTopology, fetchTopologies, fetchTopology, updateTopology } from '../api/topologies' - -/** - * Configure the query defaults for the topology endpoints. - */ -export function configureTopologyClient(queryClient, auth) { - queryClient.setQueryDefaults('topologies', { - queryFn: ({ queryKey }) => - queryKey.length === 2 ? fetchTopologies(auth, queryKey[1]) : fetchTopology(auth, queryKey[1], queryKey[2]), - }) - - queryClient.setMutationDefaults('addTopology', { - mutationFn: ({ projectId, ...data }) => addTopology(auth, projectId, data), - onSuccess: (result) => { - queryClient.setQueryData(['topologies', result.project.id], (old = []) => [...old, result]) - queryClient.setQueryData(['topologies', result.project.id, result.number], result) - }, - }) - queryClient.setMutationDefaults('updateTopology', { - mutationFn: (data) => updateTopology(auth, data), - onSuccess: (result) => { - queryClient.setQueryData(['topologies', result.project.id], (old = []) => - old.map((topology) => (topology.id === result.id ? result : topology)) - ) - queryClient.setQueryData(['topologies', result.project.id, result.number], result) - }, - }) - queryClient.setMutationDefaults('deleteTopology', { - mutationFn: ({ projectId, number }) => deleteTopology(auth, projectId, number), - onSuccess: (result) => { - queryClient.setQueryData(['topologies', result.project.id], (old = []) => - old.filter((topology) => topology.id !== result.id) - ) - queryClient.removeQueries(['topologies', result.project.id, result.number]) - }, - }) -} - -/** - * Fetch the topology with the specified identifier for the specified project. - */ -export function useTopology(projectId, topologyId, options = {}) { - return useQuery(['topologies', projectId, topologyId], { enabled: !!(projectId && topologyId), ...options }) -} - -/** - * Fetch all topologies of the specified project. - */ -export function useTopologies(projectId, options = {}) { - return useQuery(['topologies', projectId], { enabled: !!projectId, ...options }) -} - -/** - * Create a mutation for a new topology. - */ -export function useNewTopology() { - return useMutation('addTopology') -} - -/** - * Create a mutation for deleting a topology. - */ -export function useDeleteTopology(options = {}) { - return useMutation('deleteTopology', options) -} diff --git a/opendc-web/opendc-web-ui/src/data/user.js b/opendc-web/opendc-web-ui/src/data/user.js deleted file mode 100644 index 97c0e1e2..00000000 --- a/opendc-web/opendc-web-ui/src/data/user.js +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright (c) 2022 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -import { useQuery } from 'react-query' -import { fetchUser } from '../api/users' - -/** - * Configure the query defaults for the user client. - */ -export function configureUserClient(queryClient, auth) { - queryClient.setQueryDefaults('user', { - queryFn: () => fetchUser(auth), - }) -} - -/** - * Fetch the user data on the server. - */ -export default function useUser(options = {}) { - return useQuery('user', options) -} diff --git a/opendc-web/opendc-web-ui/src/pages/404.js b/opendc-web/opendc-web-ui/src/pages/404.js deleted file mode 100644 index 0939bc56..00000000 --- a/opendc-web/opendc-web-ui/src/pages/404.js +++ /dev/null @@ -1,38 +0,0 @@ -import React from 'react' -import Head from 'next/head' -import { AppPage } from '../components/AppPage' -import { - Bullseye, - EmptyState, - EmptyStateBody, - EmptyStateIcon, - PageSection, - PageSectionVariants, - Title, -} from '@patternfly/react-core' -import { UnknownIcon } from '@patternfly/react-icons' - -const NotFound = () => { - return ( - <AppPage> - <Head> - <title>Page Not Found - OpenDC</title> - </Head> - <PageSection variant={PageSectionVariants.light}> - <Bullseye> - <EmptyState> - <EmptyStateIcon variant="container" component={UnknownIcon} /> - <Title size="lg" headingLevel="h4"> - 404: That page does not exist - </Title> - <EmptyStateBody> - The requested page is not found. Try refreshing the page if it was recently added. - </EmptyStateBody> - </EmptyState> - </Bullseye> - </PageSection> - </AppPage> - ) -} - -export default NotFound diff --git a/opendc-web/opendc-web-ui/src/pages/_app.js b/opendc-web/opendc-web-ui/src/pages/_app.js deleted file mode 100644 index 62ce0539..00000000 --- a/opendc-web/opendc-web-ui/src/pages/_app.js +++ /dev/null @@ -1,108 +0,0 @@ -/* - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -import PropTypes from 'prop-types' -import Head from 'next/head' -import Script from 'next/script' -import { Provider } from 'react-redux' -import { useNewQueryClient } from '../data/query' -import { useStore } from '../redux' -import { AuthProvider, useRequireAuth } from '../auth' -import * as Sentry from '@sentry/react' -import { Integrations } from '@sentry/tracing' -import { QueryClientProvider } from 'react-query' -import { sentryDsn } from '../config' - -import '@patternfly/react-core/dist/styles/base.css' -import '@patternfly/react-styles/css/utilities/Alignment/alignment.css' -import '@patternfly/react-styles/css/utilities/BackgroundColor/BackgroundColor.css' -import '@patternfly/react-styles/css/utilities/BoxShadow/box-shadow.css' -import '@patternfly/react-styles/css/utilities/Display/display.css' -import '@patternfly/react-styles/css/utilities/Flex/flex.css' -import '@patternfly/react-styles/css/utilities/Float/float.css' -import '@patternfly/react-styles/css/utilities/Sizing/sizing.css' -import '@patternfly/react-styles/css/utilities/Spacing/spacing.css' -import '@patternfly/react-styles/css/utilities/Text/text.css' -import '@patternfly/react-styles/css/components/InlineEdit/inline-edit.css' -import '../style/index.css' - -// This setup is necessary to forward the Auth0 context to the Redux context -function Inner({ Component, pageProps }) { - // Force user to be authorized - useRequireAuth() - - const queryClient = useNewQueryClient() - const store = useStore(pageProps.initialReduxState, { queryClient }) - return ( - <QueryClientProvider client={queryClient}> - <Provider store={store}> - <Component {...pageProps} /> - </Provider> - </QueryClientProvider> - ) -} - -Inner.propTypes = { - Component: PropTypes.func, - pageProps: PropTypes.shape({ - initialReduxState: PropTypes.object, - }).isRequired, -} - -// Initialize Sentry if the user has configured a DSN -if (process.browser && sentryDsn) { - Sentry.init({ - environment: process.env.NODE_ENV, - dsn: sentryDsn, - integrations: [new Integrations.BrowserTracing()], - tracesSampleRate: 0.1, - }) -} - -export default function App(props) { - return ( - <> - <Head> - <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" /> - <meta name="theme-color" content="#00A6D6" /> - </Head> - <Sentry.ErrorBoundary fallback={'An error has occurred'}> - <AuthProvider> - <Inner {...props} /> - </AuthProvider> - </Sentry.ErrorBoundary> - {/* Google Analytics */} - <Script async src="https://www.googletagmanager.com/gtag/js?id=UA-84285092-3" /> - <Script - id="gtag" - dangerouslySetInnerHTML={{ - __html: ` - window.dataLayer = window.dataLayer || []; - function gtag(){dataLayer.push(arguments);} - gtag('js', new Date()); - gtag('config', 'UA-84285092-3'); - `, - }} - /> - </> - ) -} diff --git a/opendc-web/opendc-web-ui/src/pages/_document.js b/opendc-web/opendc-web-ui/src/pages/_document.js deleted file mode 100644 index 9f84b2ab..00000000 --- a/opendc-web/opendc-web-ui/src/pages/_document.js +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -import Document, { Html, Head, Main, NextScript } from 'next/document' - -class OpenDCDocument extends Document { - render() { - return ( - <Html lang="en"> - <Head> - <meta charSet="utf-8" /> - <meta name="theme-color" content="#00A6D6" /> - <meta - name="description" - content="Collaborative Datacenter Simulation and Exploration for Everybody" - /> - <meta name="author" content="@Large Research" /> - <meta - name="keywords" - content="OpenDC, Datacenter, Simulation, Simulator, Collaborative, Distributed, Cluster" - /> - <link rel="manifest" href="/manifest.json" /> - <link rel="shortcut icon" href="/favicon.ico" /> - - {/* Twitter Card data */} - <meta name="twitter:card" content="summary" /> - <meta name="twitter:site" content="@LargeResearch" /> - <meta name="twitter:title" content="OpenDC" /> - <meta - name="twitter:description" - content="Collaborative Datacenter Simulation and Exploration for Everybody" - /> - <meta name="twitter:creator" content="@LargeResearch" /> - <meta name="twitter:image" content="http://opendc.org/img/logo.png" /> - - {/* OpenGraph meta tags */} - <meta property="og:title" content="OpenDC" /> - <meta property="og:site_name" content="OpenDC" /> - <meta property="og:type" content="website" /> - <meta property="og:image" content="http://opendc.org/img/logo.png" /> - <meta property="og:url" content="http://opendc.org/" /> - <meta - property="og:description" - content="OpenDC provides collaborative online datacenter modeling, diverse and effective datacenter simulation, and exploratory datacenter performance feedback." - /> - <meta property="og:locale" content="en_US" /> - </Head> - <body> - <Main /> - <NextScript /> - </body> - </Html> - ) - } -} - -export default OpenDCDocument diff --git a/opendc-web/opendc-web-ui/src/pages/logout.js b/opendc-web/opendc-web-ui/src/pages/logout.js deleted file mode 100644 index 38d5968e..00000000 --- a/opendc-web/opendc-web-ui/src/pages/logout.js +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -import React from 'react' -import Head from 'next/head' -import { AppPage } from '../components/AppPage' -import { PageSection, PageSectionVariants } from '@patternfly/react-core' - -function Logout() { - return ( - <AppPage> - <Head> - <title>Logged Out - OpenDC</title> - </Head> - <PageSection variant={PageSectionVariants.light}>Logged out successfully</PageSection> - </AppPage> - ) -} - -export default Logout diff --git a/opendc-web/opendc-web-ui/src/pages/projects/[project]/index.js b/opendc-web/opendc-web-ui/src/pages/projects/[project]/index.js deleted file mode 100644 index 52938bcd..00000000 --- a/opendc-web/opendc-web-ui/src/pages/projects/[project]/index.js +++ /dev/null @@ -1,75 +0,0 @@ -/* - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -import { useRouter } from 'next/router' -import ProjectOverview from '../../../components/projects/ProjectOverview' -import { useProject } from '../../../data/project' -import { AppPage } from '../../../components/AppPage' -import Head from 'next/head' -import Link from 'next/link' -import { - Breadcrumb, - BreadcrumbItem, - PageSection, - PageSectionVariants, - Skeleton, - Text, - TextContent, -} from '@patternfly/react-core' - -function Project() { - const router = useRouter() - const projectId = +router.query['project'] - - const { data: project } = useProject(+projectId) - - const breadcrumb = ( - <Breadcrumb> - <BreadcrumbItem to="/projects" component={Link}> - Projects - </BreadcrumbItem> - <BreadcrumbItem to={`/projects/${projectId}`} component={Link} isActive> - Project details - </BreadcrumbItem> - </Breadcrumb> - ) - - return ( - <AppPage breadcrumb={breadcrumb}> - <Head> - <title>{`${project?.name ?? 'Project'} - OpenDC`}</title> - </Head> - <PageSection variant={PageSectionVariants.light}> - <TextContent> - <Text component="h1"> - {project?.name ?? <Skeleton width="15%" screenreaderText="Loading project" />} - </Text> - </TextContent> - </PageSection> - <PageSection isFilled> - <ProjectOverview projectId={projectId} /> - </PageSection> - </AppPage> - ) -} - -export default Project diff --git a/opendc-web/opendc-web-ui/src/pages/projects/[project]/portfolios/[portfolio].js b/opendc-web/opendc-web-ui/src/pages/projects/[project]/portfolios/[portfolio].js deleted file mode 100644 index 5d1e041b..00000000 --- a/opendc-web/opendc-web-ui/src/pages/projects/[project]/portfolios/[portfolio].js +++ /dev/null @@ -1,121 +0,0 @@ -/* - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -import dynamic from 'next/dynamic' -import { useRouter } from 'next/router' -import Head from 'next/head' -import Link from 'next/link' -import React, { useRef } from 'react' -import { - Breadcrumb, - BreadcrumbItem, - Divider, - PageSection, - PageSectionVariants, - Tab, - TabContent, - Tabs, - TabTitleText, - Text, - TextContent, -} from '@patternfly/react-core' -import { AppPage } from '../../../../components/AppPage' -import ContextSelectionSection from '../../../../components/context/ContextSelectionSection' -import PortfolioSelector from '../../../../components/context/PortfolioSelector' -import PortfolioOverview from '../../../../components/portfolios/PortfolioOverview' -import { usePortfolio } from '../../../../data/project' - -const PortfolioResults = dynamic(() => import('../../../../components/portfolios/PortfolioResults'), { ssr: false }) - -/** - * Page that displays the results in a portfolio. - */ -function Portfolio() { - const router = useRouter() - const projectId = +router.query['project'] - const portfolioNumber = +router.query['portfolio'] - - const overviewRef = useRef(null) - const resultsRef = useRef(null) - - const { data: portfolio } = usePortfolio(projectId, portfolioNumber) - - const breadcrumb = ( - <Breadcrumb> - <BreadcrumbItem to="/projects" component={Link}> - Projects - </BreadcrumbItem> - <BreadcrumbItem to={`/projects/${projectId}`} component={Link}> - Project details - </BreadcrumbItem> - <BreadcrumbItem to={`/projects/${projectId}/portfolios/${portfolioNumber}`} component={Link} isActive> - Portfolio - </BreadcrumbItem> - </Breadcrumb> - ) - - const contextSelectors = ( - <ContextSelectionSection> - <PortfolioSelector activePortfolio={portfolio} /> - </ContextSelectionSection> - ) - - return ( - <AppPage breadcrumb={breadcrumb} contextSelectors={contextSelectors}> - <Head> - <title>Portfolio - OpenDC</title> - </Head> - <PageSection variant={PageSectionVariants.light}> - <TextContent> - <Text component="h1">Portfolio</Text> - </TextContent> - </PageSection> - <PageSection type="tabs" variant={PageSectionVariants.light} stickyOnBreakpoint={{ default: 'top' }}> - <Divider component="div" /> - <Tabs defaultActiveKey={0} className="pf-m-page-insets"> - <Tab - eventKey={0} - title={<TabTitleText>Overview</TabTitleText>} - tabContentId="overview" - tabContentRef={overviewRef} - /> - <Tab - eventKey={1} - title={<TabTitleText>Results</TabTitleText>} - tabContentId="results" - tabContentRef={resultsRef} - /> - </Tabs> - </PageSection> - <PageSection isFilled> - <TabContent eventKey={0} id="overview" ref={overviewRef} aria-label="Overview tab"> - <PortfolioOverview projectId={projectId} portfolioId={portfolioNumber} /> - </TabContent> - <TabContent eventKey={1} id="results" ref={resultsRef} aria-label="Results tab" hidden> - <PortfolioResults projectId={projectId} portfolioId={portfolioNumber} /> - </TabContent> - </PageSection> - </AppPage> - ) -} - -export default Portfolio diff --git a/opendc-web/opendc-web-ui/src/pages/projects/[project]/topologies/[topology].js b/opendc-web/opendc-web-ui/src/pages/projects/[project]/topologies/[topology].js deleted file mode 100644 index 48359365..00000000 --- a/opendc-web/opendc-web-ui/src/pages/projects/[project]/topologies/[topology].js +++ /dev/null @@ -1,142 +0,0 @@ -/* - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -import dynamic from 'next/dynamic' -import { useRouter } from 'next/router' -import Head from 'next/head' -import Link from 'next/link' -import ContextSelectionSection from '../../../../components/context/ContextSelectionSection' -import TopologySelector from '../../../../components/context/TopologySelector' -import TopologyOverview from '../../../../components/topologies/TopologyOverview' -import { useDispatch } from 'react-redux' -import React, { useEffect, useState } from 'react' -import { AppPage } from '../../../../components/AppPage' -import { - Breadcrumb, - BreadcrumbItem, - Divider, - PageSection, - PageSectionVariants, - Tab, - TabContent, - Tabs, - TabTitleText, - Text, - TextContent, -} from '@patternfly/react-core' -import { useTopology } from '../../../../data/topology' -import { goToRoom } from '../../../../redux/actions/interaction-level' -import { openTopology } from '../../../../redux/actions/topology' - -const TopologyMap = dynamic(() => import('../../../../components/topologies/TopologyMap'), { ssr: false }) - -/** - * Page that displays a datacenter topology. - */ -function Topology() { - const router = useRouter() - const projectId = +router.query['project'] - const topologyNumber = +router.query['topology'] - - const { data: topology } = useTopology(projectId, topologyNumber) - - const dispatch = useDispatch() - useEffect(() => { - if (topologyNumber) { - dispatch(openTopology(projectId, topologyNumber)) - } - }, [projectId, topologyNumber, dispatch]) - - const [activeTab, setActiveTab] = useState('overview') - - const breadcrumb = ( - <Breadcrumb> - <BreadcrumbItem to="/projects" component={Link}> - Projects - </BreadcrumbItem> - <BreadcrumbItem to={`/projects/${projectId}`} component={Link}> - Project details - </BreadcrumbItem> - <BreadcrumbItem to={`/projects/${projectId}/topologies/${topologyNumber}`} component={Link} isActive> - Topology - </BreadcrumbItem> - </Breadcrumb> - ) - - const contextSelectors = ( - <ContextSelectionSection> - <TopologySelector activeTopology={topology} /> - </ContextSelectionSection> - ) - - return ( - <AppPage breadcrumb={breadcrumb} contextSelectors={contextSelectors}> - <Head> - <title>{`${topology?.name ?? 'Topologies'} - OpenDC`}</title> - </Head> - <PageSection variant={PageSectionVariants.light}> - <TextContent> - <Text component="h1">Topology</Text> - </TextContent> - </PageSection> - <PageSection type="none" variant={PageSectionVariants.light} className="pf-c-page__main-tabs" sticky="top"> - <Divider component="div" /> - <Tabs - activeKey={activeTab} - onSelect={(_, tabIndex) => setActiveTab(tabIndex)} - className="pf-m-page-insets" - > - <Tab eventKey="overview" title={<TabTitleText>Overview</TabTitleText>} tabContentId="overview" /> - <Tab - eventKey="floor-plan" - title={<TabTitleText>Floor Plan</TabTitleText>} - tabContentId="floor-plan" - /> - </Tabs> - </PageSection> - <PageSection padding={activeTab === 'floor-plan' && { default: 'noPadding' }} isFilled> - <TabContent id="overview" aria-label="Overview tab" hidden={activeTab !== 'overview'}> - <TopologyOverview - projectId={projectId} - topologyNumber={topologyNumber} - onSelect={(type, obj) => { - if (type === 'room') { - dispatch(goToRoom(obj.id)) - setActiveTab('floor-plan') - } - }} - /> - </TabContent> - <TabContent - id="floor-plan" - aria-label="Floor Plan tab" - className="pf-u-h-100" - hidden={activeTab !== 'floor-plan'} - > - <TopologyMap /> - </TabContent> - </PageSection> - </AppPage> - ) -} - -export default Topology diff --git a/opendc-web/opendc-web-ui/src/pages/projects/index.js b/opendc-web/opendc-web-ui/src/pages/projects/index.js deleted file mode 100644 index 97ff105c..00000000 --- a/opendc-web/opendc-web-ui/src/pages/projects/index.js +++ /dev/null @@ -1,116 +0,0 @@ -/* - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -import { PlusIcon } from '@patternfly/react-icons' -import React, { useMemo, useState } from 'react' -import Head from 'next/head' -import ProjectFilterPanel from '../../components/projects/FilterPanel' -import { AppPage } from '../../components/AppPage' -import { - PageSection, - PageSectionVariants, - Title, - Toolbar, - ToolbarItem, - ToolbarContent, - Button, - TextContent, - Text, -} from '@patternfly/react-core' -import ProjectCollection from '../../components/projects/ProjectCollection' -import TextInputModal from '../../components/util/modals/TextInputModal' -import { useProjects, useDeleteProject, useNewProject } from '../../data/project' - -const getVisibleProjects = (projects, filter) => { - switch (filter) { - case 'SHOW_ALL': - return projects - case 'SHOW_OWN': - return projects.filter((project) => project.role === 'OWNER') - case 'SHOW_SHARED': - return projects.filter((project) => project.role !== 'OWNER') - default: - return projects - } -} - -function Projects() { - const { status, data: projects } = useProjects() - const [filter, setFilter] = useState('SHOW_ALL') - const visibleProjects = useMemo(() => getVisibleProjects(projects ?? [], filter), [projects, filter]) - - const { mutate: deleteProject } = useDeleteProject() - const { mutate: addProject } = useNewProject() - - const [isProjectCreationModalVisible, setProjectCreationModalVisible] = useState(false) - const onProjectCreation = (name) => { - if (name) { - addProject({ name }) - } - setProjectCreationModalVisible(false) - } - - return ( - <AppPage> - <Head> - <title>My Projects - OpenDC</title> - </Head> - <PageSection variant={PageSectionVariants.light} isFilled> - <div className="pf-u-mx-auto pf-u-max-width" style={{ '--pf-u-max-width--MaxWidth': '100ch' }}> - <Title className="pf-u-mt-xl-on-md" headingLevel="h1" size="4xl"> - Welcome - </Title> - <TextContent> - <Text component="p">Find all your personal and shared projects</Text> - </TextContent> - <Toolbar inset={{ default: 'insetNone' }}> - <ToolbarContent> - <ToolbarItem> - <ProjectFilterPanel onSelect={setFilter} activeFilter={filter} /> - </ToolbarItem> - <ToolbarItem alignment={{ default: 'alignRight' }}> - <Button icon={<PlusIcon />} onClick={() => setProjectCreationModalVisible(true)}> - Create Project - </Button> - </ToolbarItem> - </ToolbarContent> - </Toolbar> - <ProjectCollection - status={status} - isFiltering={filter !== 'SHOW_ALL'} - projects={visibleProjects} - onDelete={(project) => deleteProject(project.id)} - onCreate={() => setProjectCreationModalVisible(true)} - /> - <TextInputModal - title="New Project" - label="Project name" - isOpen={isProjectCreationModalVisible} - callback={onProjectCreation} - /> - </div> - </PageSection> - </AppPage> - ) -} - -export default Projects diff --git a/opendc-web/opendc-web-ui/src/redux/actions/interaction-level.js b/opendc-web/opendc-web-ui/src/redux/actions/interaction-level.js deleted file mode 100644 index 8381eeef..00000000 --- a/opendc-web/opendc-web-ui/src/redux/actions/interaction-level.js +++ /dev/null @@ -1,57 +0,0 @@ -export const GO_FROM_BUILDING_TO_ROOM = 'GO_FROM_BUILDING_TO_ROOM' -export const GO_FROM_ROOM_TO_RACK = 'GO_FROM_ROOM_TO_RACK' -export const GO_FROM_RACK_TO_MACHINE = 'GO_FROM_RACK_TO_MACHINE' -export const GO_DOWN_ONE_INTERACTION_LEVEL = 'GO_DOWN_ONE_INTERACTION_LEVEL' - -export function goToRoom(roomId) { - return { - type: GO_FROM_BUILDING_TO_ROOM, - roomId, - } -} - -export function goFromBuildingToRoom(roomId) { - return (dispatch, getState) => { - const { interactionLevel } = getState() - if (interactionLevel.mode !== 'BUILDING') { - return - } - - dispatch({ - type: GO_FROM_BUILDING_TO_ROOM, - roomId, - }) - } -} - -export function goFromRoomToRack(tileId) { - return (dispatch, getState) => { - const { interactionLevel } = getState() - if (interactionLevel.mode !== 'ROOM') { - return - } - dispatch({ - type: GO_FROM_ROOM_TO_RACK, - tileId, - }) - } -} - -export function goFromRackToMachine(position) { - return (dispatch, getState) => { - const { interactionLevel } = getState() - if (interactionLevel.mode !== 'RACK') { - return - } - dispatch({ - type: GO_FROM_RACK_TO_MACHINE, - position, - }) - } -} - -export function goDownOneInteractionLevel() { - return { - type: GO_DOWN_ONE_INTERACTION_LEVEL, - } -} diff --git a/opendc-web/opendc-web-ui/src/redux/actions/topology/building.js b/opendc-web/opendc-web-ui/src/redux/actions/topology/building.js deleted file mode 100644 index c12417b9..00000000 --- a/opendc-web/opendc-web-ui/src/redux/actions/topology/building.js +++ /dev/null @@ -1,113 +0,0 @@ -import { v4 as uuid } from 'uuid' -import { addRoom, deleteRoom } from './room' - -export const START_NEW_ROOM_CONSTRUCTION = 'START_NEW_ROOM_CONSTRUCTION' -export const START_NEW_ROOM_CONSTRUCTION_SUCCEEDED = 'START_NEW_ROOM_CONSTRUCTION_SUCCEEDED' -export const FINISH_NEW_ROOM_CONSTRUCTION = 'FINISH_NEW_ROOM_CONSTRUCTION' -export const CANCEL_NEW_ROOM_CONSTRUCTION = 'CANCEL_NEW_ROOM_CONSTRUCTION' -export const CANCEL_NEW_ROOM_CONSTRUCTION_SUCCEEDED = 'CANCEL_NEW_ROOM_CONSTRUCTION_SUCCEEDED' -export const START_ROOM_EDIT = 'START_ROOM_EDIT' -export const FINISH_ROOM_EDIT = 'FINISH_ROOM_EDIT' -export const ADD_TILE = 'ADD_TILE' -export const DELETE_TILE = 'DELETE_TILE' - -export function startNewRoomConstruction() { - return (dispatch, getState) => { - const { topology } = getState() - const topologyId = topology.root.id - const room = { - id: uuid(), - name: 'Room', - topologyId, - tiles: [], - } - - dispatch(addRoom(topologyId, room)) - dispatch(startNewRoomConstructionSucceeded(room.id)) - } -} - -export function startNewRoomConstructionSucceeded(roomId) { - return { - type: START_NEW_ROOM_CONSTRUCTION_SUCCEEDED, - roomId, - } -} - -export function finishNewRoomConstruction() { - return (dispatch, getState) => { - const { topology, construction } = getState() - if (topology.rooms[construction.currentRoomInConstruction].tiles.length === 0) { - dispatch(cancelNewRoomConstruction()) - return - } - - dispatch({ - type: FINISH_NEW_ROOM_CONSTRUCTION, - }) - } -} - -export function cancelNewRoomConstruction() { - return (dispatch, getState) => { - const { construction } = getState() - const roomId = construction.currentRoomInConstruction - dispatch(deleteRoom(roomId)) - dispatch(cancelNewRoomConstructionSucceeded()) - } -} - -export function cancelNewRoomConstructionSucceeded() { - return { - type: CANCEL_NEW_ROOM_CONSTRUCTION_SUCCEEDED, - } -} - -export function startRoomEdit(roomId) { - return { - type: START_ROOM_EDIT, - roomId: roomId, - } -} - -export function finishRoomEdit() { - return { - type: FINISH_ROOM_EDIT, - } -} - -export function toggleTileAtLocation(positionX, positionY) { - return (dispatch, getState) => { - const { topology, construction } = getState() - - const roomId = construction.currentRoomInConstruction - const tileIds = topology.rooms[roomId].tiles - for (const tileId of tileIds) { - if (topology.tiles[tileId].positionX === positionX && topology.tiles[tileId].positionY === positionY) { - dispatch(deleteTile(tileId)) - return - } - } - - dispatch(addTile(roomId, positionX, positionY)) - } -} - -export function addTile(roomId, positionX, positionY) { - return { - type: ADD_TILE, - tile: { - id: uuid(), - roomId, - positionX, - positionY, - }, - } -} - -export function deleteTile(tileId) { - return { - type: DELETE_TILE, - tileId, - } -} diff --git a/opendc-web/opendc-web-ui/src/redux/actions/topology/index.js b/opendc-web/opendc-web-ui/src/redux/actions/topology/index.js deleted file mode 100644 index d48af37a..00000000 --- a/opendc-web/opendc-web-ui/src/redux/actions/topology/index.js +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -export const OPEN_TOPOLOGY = 'OPEN_TOPOLOGY' -export const STORE_TOPOLOGY = 'STORE_TOPOLOGY' - -export function openTopology(projectId, id) { - return { - type: OPEN_TOPOLOGY, - projectId, - id, - } -} - -export function storeTopology(topology, entities) { - return { - type: STORE_TOPOLOGY, - topology, - entities, - } -} diff --git a/opendc-web/opendc-web-ui/src/redux/actions/topology/machine.js b/opendc-web/opendc-web-ui/src/redux/actions/topology/machine.js deleted file mode 100644 index 93320884..00000000 --- a/opendc-web/opendc-web-ui/src/redux/actions/topology/machine.js +++ /dev/null @@ -1,28 +0,0 @@ -export const DELETE_MACHINE = 'DELETE_MACHINE' -export const ADD_UNIT = 'ADD_UNIT' -export const DELETE_UNIT = 'DELETE_UNIT' - -export function deleteMachine(machineId) { - return { - type: DELETE_MACHINE, - machineId, - } -} - -export function addUnit(machineId, unitType, unitId) { - return { - type: ADD_UNIT, - machineId, - unitType, - unitId, - } -} - -export function deleteUnit(machineId, unitType, unitId) { - return { - type: DELETE_UNIT, - machineId, - unitType, - unitId, - } -} diff --git a/opendc-web/opendc-web-ui/src/redux/actions/topology/rack.js b/opendc-web/opendc-web-ui/src/redux/actions/topology/rack.js deleted file mode 100644 index 1f65952a..00000000 --- a/opendc-web/opendc-web-ui/src/redux/actions/topology/rack.js +++ /dev/null @@ -1,36 +0,0 @@ -import { v4 as uuid } from 'uuid' - -export const EDIT_RACK_NAME = 'EDIT_RACK_NAME' -export const DELETE_RACK = 'DELETE_RACK' -export const ADD_MACHINE = 'ADD_MACHINE' - -export function editRackName(rackId, name) { - return { - type: EDIT_RACK_NAME, - name, - rackId, - } -} - -export function deleteRack(tileId, rackId) { - return { - type: DELETE_RACK, - rackId, - tileId, - } -} - -export function addMachine(rackId, position) { - return { - type: ADD_MACHINE, - machine: { - id: uuid(), - rackId, - position, - cpus: [], - gpus: [], - memories: [], - storages: [], - }, - } -} diff --git a/opendc-web/opendc-web-ui/src/redux/actions/topology/room.js b/opendc-web/opendc-web-ui/src/redux/actions/topology/room.js deleted file mode 100644 index 14cc126c..00000000 --- a/opendc-web/opendc-web-ui/src/redux/actions/topology/room.js +++ /dev/null @@ -1,74 +0,0 @@ -import { v4 as uuid } from 'uuid' -import { - DEFAULT_RACK_SLOT_CAPACITY, - DEFAULT_RACK_POWER_CAPACITY, -} from '../../../components/topologies/map/MapConstants' -import { findTileWithPosition } from '../../../util/tile-calculations' - -export const ADD_ROOM = 'ADD_ROOM' -export const EDIT_ROOM_NAME = 'EDIT_ROOM_NAME' -export const DELETE_ROOM = 'DELETE_ROOM' -export const START_RACK_CONSTRUCTION = 'START_RACK_CONSTRUCTION' -export const STOP_RACK_CONSTRUCTION = 'STOP_RACK_CONSTRUCTION' -export const ADD_RACK_TO_TILE = 'ADD_RACK_TO_TILE' - -export function addRoom(topologyId, room) { - return { - type: ADD_ROOM, - room: { - id: uuid(), - topologyId, - ...room, - }, - } -} - -export function editRoomName(roomId, name) { - return { - type: EDIT_ROOM_NAME, - name, - roomId, - } -} - -export function startRackConstruction() { - return { - type: START_RACK_CONSTRUCTION, - } -} - -export function stopRackConstruction() { - return { - type: STOP_RACK_CONSTRUCTION, - } -} - -export function addRackToTile(positionX, positionY) { - return (dispatch, getState) => { - const { topology, interactionLevel } = getState() - const currentRoom = topology.rooms[interactionLevel.roomId] - const tiles = currentRoom.tiles.map((tileId) => topology.tiles[tileId]) - const tile = findTileWithPosition(tiles, positionX, positionY) - - if (tile !== null) { - dispatch({ - type: ADD_RACK_TO_TILE, - tileId: tile.id, - rack: { - id: uuid(), - name: 'Rack', - capacity: DEFAULT_RACK_SLOT_CAPACITY, - powerCapacityW: DEFAULT_RACK_POWER_CAPACITY, - machines: [], - }, - }) - } - } -} - -export function deleteRoom(roomId) { - return { - type: DELETE_ROOM, - roomId, - } -} diff --git a/opendc-web/opendc-web-ui/src/redux/index.js b/opendc-web/opendc-web-ui/src/redux/index.js deleted file mode 100644 index 53cd9144..00000000 --- a/opendc-web/opendc-web-ui/src/redux/index.js +++ /dev/null @@ -1,59 +0,0 @@ -import { useMemo } from 'react' -import { applyMiddleware, compose, createStore } from 'redux' -import { createLogger } from 'redux-logger' -import createSagaMiddleware from 'redux-saga' -import thunk from 'redux-thunk' -import rootReducer from './reducers' -import rootSaga from './sagas' -import { createReduxEnhancer } from '@sentry/react' -import { sentryDsn } from '../config' - -let store - -function initStore(initialState, ctx) { - const sagaMiddleware = createSagaMiddleware({ context: ctx }) - - const middlewares = [thunk, sagaMiddleware] - - if (process.env.NODE_ENV !== 'production') { - middlewares.push(createLogger()) - } - - let middleware = applyMiddleware(...middlewares) - - if (sentryDsn) { - middleware = compose(middleware, createReduxEnhancer()) - } - - const configuredStore = createStore(rootReducer, initialState, middleware) - sagaMiddleware.run(rootSaga) - store = configuredStore - - return configuredStore -} - -export const initializeStore = (preloadedState, ctx) => { - let _store = store ?? initStore(preloadedState, ctx) - - // After navigating to a page with an initial Redux state, merge that state - // with the current state in the store, and create a new store - if (preloadedState && store) { - _store = initStore({ - ...store.getState(), - ...preloadedState, - }) - // Reset the current store - store = undefined - } - - // For SSG and SSR always create a new store - if (typeof window === 'undefined') return _store - // Create the store once in the client - if (!store) store = _store - - return _store -} - -export function useStore(initialState, ctx) { - return useMemo(() => initializeStore(initialState, ctx), [initialState, ctx]) -} diff --git a/opendc-web/opendc-web-ui/src/redux/reducers/construction-mode.js b/opendc-web/opendc-web-ui/src/redux/reducers/construction-mode.js deleted file mode 100644 index d0aac5ae..00000000 --- a/opendc-web/opendc-web-ui/src/redux/reducers/construction-mode.js +++ /dev/null @@ -1,43 +0,0 @@ -import { combineReducers } from 'redux' -import { GO_DOWN_ONE_INTERACTION_LEVEL } from '../actions/interaction-level' -import { - CANCEL_NEW_ROOM_CONSTRUCTION_SUCCEEDED, - FINISH_NEW_ROOM_CONSTRUCTION, - FINISH_ROOM_EDIT, - START_NEW_ROOM_CONSTRUCTION_SUCCEEDED, - START_ROOM_EDIT, -} from '../actions/topology/building' -import { DELETE_ROOM, START_RACK_CONSTRUCTION, STOP_RACK_CONSTRUCTION } from '../actions/topology/room' - -export function currentRoomInConstruction(state = '-1', action) { - switch (action.type) { - case START_NEW_ROOM_CONSTRUCTION_SUCCEEDED: - return action.roomId - case START_ROOM_EDIT: - return action.roomId - case CANCEL_NEW_ROOM_CONSTRUCTION_SUCCEEDED: - case FINISH_NEW_ROOM_CONSTRUCTION: - case FINISH_ROOM_EDIT: - case DELETE_ROOM: - return '-1' - default: - return state - } -} - -export function inRackConstructionMode(state = false, action) { - switch (action.type) { - case START_RACK_CONSTRUCTION: - return true - case STOP_RACK_CONSTRUCTION: - case GO_DOWN_ONE_INTERACTION_LEVEL: - return false - default: - return state - } -} - -export const construction = combineReducers({ - currentRoomInConstruction, - inRackConstructionMode, -}) diff --git a/opendc-web/opendc-web-ui/src/redux/reducers/index.js b/opendc-web/opendc-web-ui/src/redux/reducers/index.js deleted file mode 100644 index 7ffb1211..00000000 --- a/opendc-web/opendc-web-ui/src/redux/reducers/index.js +++ /dev/null @@ -1,12 +0,0 @@ -import { combineReducers } from 'redux' -import { construction } from './construction-mode' -import { interactionLevel } from './interaction-level' -import topology from './topology' - -const rootReducer = combineReducers({ - topology, - construction, - interactionLevel, -}) - -export default rootReducer diff --git a/opendc-web/opendc-web-ui/src/redux/reducers/interaction-level.js b/opendc-web/opendc-web-ui/src/redux/reducers/interaction-level.js deleted file mode 100644 index b30c68b9..00000000 --- a/opendc-web/opendc-web-ui/src/redux/reducers/interaction-level.js +++ /dev/null @@ -1,68 +0,0 @@ -import { - GO_DOWN_ONE_INTERACTION_LEVEL, - GO_FROM_BUILDING_TO_ROOM, - GO_FROM_RACK_TO_MACHINE, - GO_FROM_ROOM_TO_RACK, -} from '../actions/interaction-level' -import { DELETE_MACHINE } from '../actions/topology/machine' -import { DELETE_RACK } from '../actions/topology/rack' -import { DELETE_ROOM } from '../actions/topology/room' - -export function interactionLevel(state = { mode: 'BUILDING' }, action) { - switch (action.type) { - case GO_FROM_BUILDING_TO_ROOM: - return { - mode: 'ROOM', - roomId: action.roomId, - } - case GO_FROM_ROOM_TO_RACK: - return { - mode: 'RACK', - roomId: state.roomId, - tileId: action.tileId, - } - case GO_FROM_RACK_TO_MACHINE: - return { - mode: 'MACHINE', - roomId: state.roomId, - tileId: state.tileId, - position: action.position, - } - case GO_DOWN_ONE_INTERACTION_LEVEL: - if (state.mode === 'ROOM') { - return { - mode: 'BUILDING', - } - } else if (state.mode === 'RACK') { - return { - mode: 'ROOM', - roomId: state.roomId, - } - } else if (state.mode === 'MACHINE') { - return { - mode: 'RACK', - roomId: state.roomId, - tileId: state.tileId, - } - } else { - return state - } - case DELETE_MACHINE: - return { - mode: 'RACK', - roomId: state.roomId, - tileId: state.tileId, - } - case DELETE_RACK: - return { - mode: 'ROOM', - roomId: state.roomId, - } - case DELETE_ROOM: - return { - mode: 'BUILDING', - } - default: - return state - } -} diff --git a/opendc-web/opendc-web-ui/src/redux/reducers/topology/index.js b/opendc-web/opendc-web-ui/src/redux/reducers/topology/index.js deleted file mode 100644 index 2c849387..00000000 --- a/opendc-web/opendc-web-ui/src/redux/reducers/topology/index.js +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -import { CPU_UNITS, GPU_UNITS, MEMORY_UNITS, STORAGE_UNITS } from '../../../util/unit-specifications' -import machine from './machine' -import rack from './rack' -import room from './room' -import tile from './tile' -import topology from './topology' - -function objects(state = {}, action) { - return { - cpus: CPU_UNITS, - gpus: GPU_UNITS, - memories: MEMORY_UNITS, - storages: STORAGE_UNITS, - machines: machine(state.machines, action, state), - racks: rack(state.racks, action, state), - tiles: tile(state.tiles, action), - rooms: room(state.rooms, action, state), - root: topology(state.root, action, state), - } -} - -export default objects diff --git a/opendc-web/opendc-web-ui/src/redux/reducers/topology/machine.js b/opendc-web/opendc-web-ui/src/redux/reducers/topology/machine.js deleted file mode 100644 index 1789257b..00000000 --- a/opendc-web/opendc-web-ui/src/redux/reducers/topology/machine.js +++ /dev/null @@ -1,47 +0,0 @@ -import produce from 'immer' -import { STORE_TOPOLOGY } from '../../actions/topology' -import { DELETE_MACHINE, ADD_UNIT, DELETE_UNIT } from '../../actions/topology/machine' -import { ADD_MACHINE, DELETE_RACK } from '../../actions/topology/rack' - -function machine(state = {}, action, { racks }) { - switch (action.type) { - case STORE_TOPOLOGY: - return action.entities.machines || {} - case ADD_MACHINE: - return produce(state, (draft) => { - const { machine } = action - draft[machine.id] = machine - }) - case DELETE_MACHINE: - return produce(state, (draft) => { - const { machineId } = action - delete draft[machineId] - }) - case ADD_UNIT: - return produce(state, (draft) => { - const { machineId, unitType, unitId } = action - draft[machineId][unitType].push(unitId) - }) - case DELETE_UNIT: - return produce(state, (draft) => { - const { machineId, unitType, unitId } = action - const units = draft[machineId][unitType] - const index = units.indexOf(unitId) - units.splice(index, 1) - }) - case DELETE_RACK: - return produce(state, (draft) => { - const { rackId } = action - const rack = racks[rackId] - - for (const id of rack.machines) { - const machine = draft[id] - machine.rackId = undefined - } - }) - default: - return state - } -} - -export default machine diff --git a/opendc-web/opendc-web-ui/src/redux/reducers/topology/rack.js b/opendc-web/opendc-web-ui/src/redux/reducers/topology/rack.js deleted file mode 100644 index ca79348a..00000000 --- a/opendc-web/opendc-web-ui/src/redux/reducers/topology/rack.js +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -import produce from 'immer' -import { STORE_TOPOLOGY } from '../../actions/topology' -import { DELETE_MACHINE } from '../../actions/topology/machine' -import { DELETE_RACK, EDIT_RACK_NAME, ADD_MACHINE } from '../../actions/topology/rack' -import { ADD_RACK_TO_TILE } from '../../actions/topology/room' - -function rack(state = {}, action, { machines }) { - switch (action.type) { - case STORE_TOPOLOGY: - return action.entities.racks || {} - case ADD_RACK_TO_TILE: - return produce(state, (draft) => { - const { rack } = action - draft[rack.id] = rack - }) - case EDIT_RACK_NAME: - return produce(state, (draft) => { - const { rackId, name } = action - draft[rackId].name = name - }) - case DELETE_RACK: - return produce(state, (draft) => { - const { rackId } = action - delete draft[rackId] - }) - case ADD_MACHINE: - return produce(state, (draft) => { - const { machine } = action - draft[machine.rackId].machines.push(machine.id) - }) - case DELETE_MACHINE: - return produce(state, (draft) => { - const { machineId } = action - const machine = machines[machineId] - const rack = draft[machine.rackId] - const index = rack.machines.indexOf(machineId) - rack.machines.splice(index, 1) - }) - default: - return state - } -} - -export default rack diff --git a/opendc-web/opendc-web-ui/src/redux/reducers/topology/room.js b/opendc-web/opendc-web-ui/src/redux/reducers/topology/room.js deleted file mode 100644 index c05c8bfa..00000000 --- a/opendc-web/opendc-web-ui/src/redux/reducers/topology/room.js +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -import produce from 'immer' -import { STORE_TOPOLOGY } from '../../actions/topology' -import { ADD_TILE, DELETE_TILE } from '../../actions/topology/building' -import { DELETE_ROOM, EDIT_ROOM_NAME, ADD_ROOM } from '../../actions/topology/room' - -function room(state = {}, action, { tiles }) { - switch (action.type) { - case STORE_TOPOLOGY: - return action.entities.rooms || {} - case ADD_ROOM: - return produce(state, (draft) => { - const { room } = action - draft[room.id] = room - }) - case DELETE_ROOM: - return produce(state, (draft) => { - const { roomId } = action - delete draft[roomId] - }) - case EDIT_ROOM_NAME: - return produce(state, (draft) => { - const { roomId, name } = action - draft[roomId].name = name - }) - case ADD_TILE: - return produce(state, (draft) => { - const { tile } = action - draft[tile.roomId].tiles.push(tile.id) - }) - case DELETE_TILE: - return produce(state, (draft) => { - const { tileId } = action - const tile = tiles[tileId] - const room = draft[tile.roomId] - const index = room.tiles.indexOf(tileId) - room.tiles.splice(index, 1) - }) - default: - return state - } -} - -export default room diff --git a/opendc-web/opendc-web-ui/src/redux/reducers/topology/tile.js b/opendc-web/opendc-web-ui/src/redux/reducers/topology/tile.js deleted file mode 100644 index 24c0e20c..00000000 --- a/opendc-web/opendc-web-ui/src/redux/reducers/topology/tile.js +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -import produce from 'immer' -import { STORE_TOPOLOGY } from '../../actions/topology' -import { ADD_TILE, DELETE_TILE } from '../../actions/topology/building' -import { DELETE_RACK } from '../../actions/topology/rack' -import { ADD_RACK_TO_TILE } from '../../actions/topology/room' - -function tile(state = {}, action) { - switch (action.type) { - case STORE_TOPOLOGY: - return action.entities.tiles || {} - case ADD_TILE: - return produce(state, (draft) => { - const { tile } = action - draft[tile.id] = tile - }) - case DELETE_TILE: - return produce(state, (draft) => { - const { tileId } = action - delete draft[tileId] - }) - case ADD_RACK_TO_TILE: - return produce(state, (draft) => { - const { rack, tileId } = action - draft[tileId].rack = rack.id - }) - case DELETE_RACK: - return produce(state, (draft) => { - const { tileId } = action - draft[tileId].rack = undefined - }) - default: - return state - } -} - -export default tile diff --git a/opendc-web/opendc-web-ui/src/redux/reducers/topology/topology.js b/opendc-web/opendc-web-ui/src/redux/reducers/topology/topology.js deleted file mode 100644 index dff0a69e..00000000 --- a/opendc-web/opendc-web-ui/src/redux/reducers/topology/topology.js +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -import produce from 'immer' -import { STORE_TOPOLOGY } from '../../actions/topology' -import { ADD_ROOM, DELETE_ROOM } from '../../actions/topology/room' - -function topology(state = undefined, action) { - switch (action.type) { - case STORE_TOPOLOGY: - return action.topology - case ADD_ROOM: - return produce(state, (draft) => { - const { room } = action - draft.rooms.push(room.id) - }) - case DELETE_ROOM: - return produce(state, (draft) => { - const { roomId } = action - const index = draft.rooms.indexOf(roomId) - draft.rooms.splice(index, 1) - }) - default: - return state - } -} - -export default topology diff --git a/opendc-web/opendc-web-ui/src/redux/sagas/index.js b/opendc-web/opendc-web-ui/src/redux/sagas/index.js deleted file mode 100644 index 0fabdb6d..00000000 --- a/opendc-web/opendc-web-ui/src/redux/sagas/index.js +++ /dev/null @@ -1,7 +0,0 @@ -import { fork } from 'redux-saga/effects' -import { watchServer, updateServer } from './topology' - -export default function* rootSaga() { - yield fork(watchServer) - yield fork(updateServer) -} diff --git a/opendc-web/opendc-web-ui/src/redux/sagas/topology.js b/opendc-web/opendc-web-ui/src/redux/sagas/topology.js deleted file mode 100644 index 15147bcf..00000000 --- a/opendc-web/opendc-web-ui/src/redux/sagas/topology.js +++ /dev/null @@ -1,76 +0,0 @@ -import { QueryObserver, MutationObserver } from 'react-query' -import { normalize, denormalize } from 'normalizr' -import { select, put, take, race, getContext, call } from 'redux-saga/effects' -import { eventChannel } from 'redux-saga' -import { Topology } from '../../util/topology-schema' -import { storeTopology, OPEN_TOPOLOGY } from '../actions/topology' - -/** - * Update the topology on the server. - */ -export function* updateServer() { - const queryClient = yield getContext('queryClient') - const mutationObserver = new MutationObserver(queryClient, { mutationKey: 'updateTopology' }) - - while (true) { - yield take( - (action) => - action.type.startsWith('EDIT') || action.type.startsWith('ADD') || action.type.startsWith('DELETE') - ) - const topology = yield select((state) => state.topology) - - if (!topology.root) { - continue - } - - const denormalizedTopology = denormalize(topology.root, Topology, topology) - yield call([mutationObserver, mutationObserver.mutate], denormalizedTopology) - } -} - -/** - * Watch the topology on the server for changes. - */ -export function* watchServer() { - let { projectId, id } = yield take(OPEN_TOPOLOGY) - while (true) { - const channel = yield queryObserver(projectId, id) - - while (true) { - const [action, response] = yield race([take(OPEN_TOPOLOGY), take(channel)]) - - if (action) { - projectId = action.projectId - id = action.id - break - } - - const { isFetched, data } = response - // Only update the topology on the client-side when a new topology was fetched - if (isFetched) { - const { result: topologyId, entities } = normalize(data, Topology) - yield put(storeTopology(entities.topologies[topologyId], entities)) - } - } - } -} - -/** - * Observe changes for the topology with the specified identifier. - */ -function* queryObserver(projectId, id) { - const queryClient = yield getContext('queryClient') - const observer = new QueryObserver(queryClient, { queryKey: ['topologies', projectId, id] }) - - return eventChannel((emitter) => { - const unsubscribe = observer.subscribe((result) => { - emitter(result) - }) - - // Update result to make sure we did not miss any query updates - // between creating the observer and subscribing to it. - observer.updateResult() - - return unsubscribe - }) -} diff --git a/opendc-web/opendc-web-ui/src/shapes.js b/opendc-web/opendc-web-ui/src/shapes.js deleted file mode 100644 index 50b82361..00000000 --- a/opendc-web/opendc-web-ui/src/shapes.js +++ /dev/null @@ -1,187 +0,0 @@ -/* - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -import PropTypes from 'prop-types' - -export const ProjectRole = PropTypes.oneOf(['VIEWER', 'EDITOR', 'OWNER']) - -export const Project = PropTypes.shape({ - id: PropTypes.number.isRequired, - name: PropTypes.string.isRequired, - createdAt: PropTypes.string.isRequired, - updatedAt: PropTypes.string.isRequired, - role: ProjectRole, -}) - -export const ProcessingUnit = PropTypes.shape({ - id: PropTypes.string.isRequired, - name: PropTypes.string.isRequired, - clockRateMhz: PropTypes.number.isRequired, - numberOfCores: PropTypes.number.isRequired, - energyConsumptionW: PropTypes.number.isRequired, -}) - -export const StorageUnit = PropTypes.shape({ - id: PropTypes.string.isRequired, - name: PropTypes.string.isRequired, - speedMbPerS: PropTypes.number.isRequired, - sizeMb: PropTypes.number.isRequired, - energyConsumptionW: PropTypes.number.isRequired, -}) - -export const Machine = PropTypes.shape({ - id: PropTypes.string.isRequired, - position: PropTypes.number.isRequired, - cpus: PropTypes.arrayOf(PropTypes.oneOfType([ProcessingUnit, PropTypes.string])), - gpus: PropTypes.arrayOf(PropTypes.oneOfType([ProcessingUnit, PropTypes.string])), - memories: PropTypes.arrayOf(PropTypes.oneOfType([StorageUnit, PropTypes.string])), - storages: PropTypes.arrayOf(PropTypes.oneOfType([StorageUnit, PropTypes.string])), -}) - -export const Rack = PropTypes.shape({ - id: PropTypes.string.isRequired, - capacity: PropTypes.number.isRequired, - powerCapacityW: PropTypes.number.isRequired, - machines: PropTypes.arrayOf(PropTypes.oneOfType([Machine, PropTypes.string])), -}) - -export const Tile = PropTypes.shape({ - id: PropTypes.string.isRequired, - positionX: PropTypes.number.isRequired, - positionY: PropTypes.number.isRequired, - rack: PropTypes.oneOfType([Rack, PropTypes.string]), -}) - -export const Room = PropTypes.shape({ - id: PropTypes.string.isRequired, - name: PropTypes.string.isRequired, - tiles: PropTypes.arrayOf(PropTypes.oneOfType([Tile, PropTypes.string])), -}) - -export const Topology = PropTypes.shape({ - id: PropTypes.number.isRequired, - number: PropTypes.number.isRequired, - project: Project.isRequired, - name: PropTypes.string.isRequired, - rooms: PropTypes.arrayOf(PropTypes.oneOfType([Room, PropTypes.string])), -}) - -export const Phenomena = PropTypes.shape({ - failures: PropTypes.bool.isRequired, - interference: PropTypes.bool.isRequired, -}) - -export const Scheduler = PropTypes.shape({ - name: PropTypes.string.isRequired, -}) - -export const Trace = PropTypes.shape({ - id: PropTypes.number.isRequired, - name: PropTypes.string.isRequired, - type: PropTypes.string.isRequired, -}) - -export const Workload = PropTypes.shape({ - trace: Trace.isRequired, - samplingFraction: PropTypes.number.isRequired, -}) - -export const Targets = PropTypes.shape({ - repeats: PropTypes.number.isRequired, - metrics: PropTypes.arrayOf(PropTypes.string).isRequired, -}) - -export const TopologySummary = PropTypes.shape({ - id: PropTypes.number.isRequired, - number: PropTypes.number.isRequired, - project: Project.isRequired, - name: PropTypes.string.isRequired, -}) - -export const PortfolioSummary = PropTypes.shape({ - id: PropTypes.number.isRequired, - number: PropTypes.number.isRequired, - project: Project.isRequired, - name: PropTypes.string.isRequired, - targets: PropTypes.shape({ - repeats: PropTypes.number.isRequired, - metrics: PropTypes.arrayOf(PropTypes.string).isRequired, - }).isRequired, -}) - -export const ScenarioSummary = PropTypes.shape({ - id: PropTypes.number.isRequired, - number: PropTypes.number.isRequired, - name: PropTypes.string.isRequired, - workload: Workload.isRequired, - topology: TopologySummary.isRequired, - phenomena: Phenomena.isRequired, - schedulerName: PropTypes.string.isRequired, - results: PropTypes.object, -}) - -export const JobState = PropTypes.oneOf(['PENDING', 'CLAIMED', 'RUNNING', 'FAILED', 'FINISHED']) - -export const Job = PropTypes.shape({ - id: PropTypes.number.isRequired, - state: JobState.isRequired, - createdAt: PropTypes.string.isRequired, - updatedAt: PropTypes.string.isRequired, - results: PropTypes.object, -}) - -export const Scenario = PropTypes.shape({ - id: PropTypes.number.isRequired, - number: PropTypes.number.isRequired, - project: Project.isRequired, - portfolio: PortfolioSummary.isRequired, - name: PropTypes.string.isRequired, - workload: Workload.isRequired, - topology: TopologySummary.isRequired, - phenomena: Phenomena.isRequired, - schedulerName: PropTypes.string.isRequired, - jobs: PropTypes.arrayOf(Job).isRequired, -}) - -export const Portfolio = PropTypes.shape({ - id: PropTypes.number.isRequired, - number: PropTypes.number.isRequired, - name: PropTypes.string.isRequired, - project: Project.isRequired, - targets: Targets.isRequired, - scenarios: PropTypes.arrayOf(ScenarioSummary).isRequired, -}) - -export const WallSegment = PropTypes.shape({ - startPosX: PropTypes.number.isRequired, - startPosY: PropTypes.number.isRequired, - isHorizontal: PropTypes.bool.isRequired, - length: PropTypes.number.isRequired, -}) - -export const InteractionLevel = PropTypes.shape({ - mode: PropTypes.string.isRequired, - roomId: PropTypes.string, - rackId: PropTypes.string, -}) - -export const Status = PropTypes.oneOf(['idle', 'loading', 'error', 'success']) diff --git a/opendc-web/opendc-web-ui/src/style/index.css b/opendc-web/opendc-web-ui/src/style/index.css deleted file mode 100644 index 7b7103a4..00000000 --- a/opendc-web/opendc-web-ui/src/style/index.css +++ /dev/null @@ -1,28 +0,0 @@ -/*! - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -body, -#__next { - height: 100%; - - background: #eee; -} diff --git a/opendc-web/opendc-web-ui/src/util/authorizations.js b/opendc-web/opendc-web-ui/src/util/authorizations.js deleted file mode 100644 index 6cb08ba8..00000000 --- a/opendc-web/opendc-web-ui/src/util/authorizations.js +++ /dev/null @@ -1,21 +0,0 @@ -import HomeIcon from '@patternfly/react-icons/dist/js/icons/home-icon' -import EditIcon from '@patternfly/react-icons/dist/js/icons/edit-icon' -import EyeIcon from '@patternfly/react-icons/dist/js/icons/eye-icon' - -export const AUTH_ICON_MAP = { - OWNER: HomeIcon, - EDITOR: EditIcon, - VIEWER: EyeIcon, -} - -export const AUTH_NAME_MAP = { - OWNER: 'Owner', - EDITOR: 'Editor', - VIEWER: 'Viewer', -} - -export const AUTH_DESCRIPTION_MAP = { - OWNER: 'You own this project', - EDITOR: 'You can edit this project', - VIEWER: 'You can view this project', -} diff --git a/opendc-web/opendc-web-ui/src/util/available-metrics.js b/opendc-web/opendc-web-ui/src/util/available-metrics.js deleted file mode 100644 index fda6cd4d..00000000 --- a/opendc-web/opendc-web-ui/src/util/available-metrics.js +++ /dev/null @@ -1,101 +0,0 @@ -export const METRIC_GROUPS = { - 'Host Metrics': [ - 'total_overcommitted_burst', - 'total_power_draw', - 'total_failure_vm_slices', - 'total_granted_burst', - 'total_interfered_burst', - 'total_requested_burst', - 'mean_cpu_usage', - 'mean_cpu_demand', - 'mean_num_deployed_images', - 'max_num_deployed_images', - ], - 'Compute Service Metrics': ['total_vms_submitted', 'total_vms_queued', 'total_vms_finished', 'total_vms_failed'], -} - -export const AVAILABLE_METRICS = [ - 'mean_cpu_usage', - 'mean_cpu_demand', - 'total_requested_burst', - 'total_granted_burst', - 'total_overcommitted_burst', - 'total_interfered_burst', - 'total_power_draw', - 'total_failure_vm_slices', - 'mean_num_deployed_images', - 'max_num_deployed_images', - 'total_vms_submitted', - 'total_vms_queued', - 'total_vms_finished', - 'total_vms_failed', -] - -export const METRIC_NAMES_SHORT = { - total_overcommitted_burst: 'Overcomm. CPU Cycles', - total_granted_burst: 'Granted CPU Cycles', - total_requested_burst: 'Requested CPU Cycles', - total_interfered_burst: 'Interfered CPU Cycles', - total_power_draw: 'Total Power Consumption', - mean_cpu_usage: 'Mean Host CPU Usage', - mean_cpu_demand: 'Mean Host CPU Demand', - mean_num_deployed_images: 'Mean Num. Deployed Images Per Host', - max_num_deployed_images: 'Max. Num. Deployed Images Per Host', - total_failure_vm_slices: 'Total Num. Failed VM Slices', - total_vms_submitted: 'VMs Submitted', - total_vms_queued: 'VMs Queued', - total_vms_finished: 'VMs Finished', - total_vms_failed: 'VMs Failed', -} - -export const METRIC_NAMES = { - total_overcommitted_burst: 'Overcommitted CPU Cycles', - total_granted_burst: 'Granted CPU Cycles', - total_requested_burst: 'Requested CPU Cycles', - total_interfered_burst: 'Interfered CPU Cycles', - total_power_draw: 'Total Power Consumption', - mean_cpu_usage: 'Mean Host CPU Usage', - mean_cpu_demand: 'Mean Host CPU Demand', - mean_num_deployed_images: 'Mean Number of Deployed Images Per Host', - max_num_deployed_images: 'Maximum Number Deployed Images Per Host', - total_failure_vm_slices: 'Failed VM Slices', - total_vms_submitted: 'VMs Submitted', - total_vms_queued: 'VMs Queued', - total_vms_finished: 'VMs Finished', - total_vms_failed: 'VMs Failed', -} - -export const METRIC_UNITS = { - total_overcommitted_burst: 'MFLOP', - total_granted_burst: 'MFLOP', - total_requested_burst: 'MFLOP', - total_interfered_burst: 'MFLOP', - total_power_draw: 'Wh', - mean_cpu_usage: 'MHz', - mean_cpu_demand: 'MHz', - mean_num_deployed_images: 'VMs', - max_num_deployed_images: 'VMs', - total_failure_vm_slices: 'VM Slices', - total_vms_submitted: 'VMs', - total_vms_queued: 'VMs', - total_vms_finished: 'VMs', - total_vms_failed: 'VMs', -} - -export const METRIC_DESCRIPTIONS = { - total_overcommitted_burst: - 'The total CPU clock cycles lost due to overcommitting of resources. This metric is an indicator for resource overload.', - total_requested_burst: 'The total CPU clock cycles that were requested by all virtual machines.', - total_granted_burst: 'The total CPU clock cycles executed by the hosts.', - total_interfered_burst: 'The total CPU clock cycles lost due to resource interference between virtual machines.', - total_power_draw: 'The average power usage in watts.', - mean_cpu_usage: 'The average amount of CPU clock cycles consumed by all virtual machines on a host.', - mean_cpu_demand: 'The average amount of CPU clock cycles requested by all powered on virtual machines on a host.', - mean_num_deployed_images: 'The average number of virtual machines deployed on a host.', - max_num_deployed_images: 'The maximum number of virtual machines deployed at any time.', - total_failure_vm_slices: 'The total amount of CPU clock cycles lost due to failure.', - total_vms_submitted: 'The number of virtual machines scheduled by the compute service.', - total_vms_queued: 'The number of virtual machines still waiting to be scheduled by the compute service.', - total_vms_finished: 'The number of virtual machines that completed.', - total_vms_failed: 'The number of virtual machines that could not be scheduled.', -} diff --git a/opendc-web/opendc-web-ui/src/util/colors.js b/opendc-web/opendc-web-ui/src/util/colors.js deleted file mode 100644 index 34468503..00000000 --- a/opendc-web/opendc-web-ui/src/util/colors.js +++ /dev/null @@ -1,29 +0,0 @@ -export const GRID_COLOR = 'rgba(0, 0, 0, 0.5)' -export const BACKDROP_COLOR = 'rgba(255, 255, 255, 1)' -export const WALL_COLOR = 'rgba(0, 0, 0, 1)' - -export const ROOM_DEFAULT_COLOR = 'rgba(150, 150, 150, 1)' -export const ROOM_IN_CONSTRUCTION_COLOR = 'rgba(51, 153, 255, 1)' -export const ROOM_HOVER_VALID_COLOR = 'rgba(51, 153, 255, 1)' -export const ROOM_HOVER_INVALID_COLOR = 'rgba(255, 102, 0, 1)' -export const ROOM_NAME_COLOR = 'rgba(245, 245, 245, 1)' -export const ROOM_TYPE_COLOR = 'rgba(245, 245, 245, 1)' - -export const TILE_PLUS_COLOR = 'rgba(0, 0, 0, 1)' - -export const OBJECT_BORDER_COLOR = 'rgba(0, 0, 0, 1)' - -export const RACK_BACKGROUND_COLOR = 'rgba(170, 170, 170, 1)' -export const RACK_SPACE_BAR_BACKGROUND_COLOR = 'rgba(222, 235, 247, 0.6)' -export const RACK_SPACE_BAR_FILL_COLOR = 'rgba(91, 155, 213, 0.7)' -export const RACK_ENERGY_BAR_BACKGROUND_COLOR = 'rgba(255, 242, 204, 0.6)' -export const RACK_ENERGY_BAR_FILL_COLOR = 'rgba(244, 215, 0, 0.7)' -export const COOLING_ITEM_BACKGROUND_COLOR = 'rgba(40, 50, 230, 1)' -export const PSU_BACKGROUND_COLOR = 'rgba(230, 50, 60, 1)' - -export const GRAYED_OUT_AREA_COLOR = 'rgba(0, 0, 0, 0.6)' - -export const SIM_LOW_COLOR = 'rgba(197, 224, 180, 1)' -export const SIM_MID_LOW_COLOR = 'rgba(255, 230, 153, 1)' -export const SIM_MID_HIGH_COLOR = 'rgba(248, 203, 173, 1)' -export const SIM_HIGH_COLOR = 'rgba(249, 165, 165, 1)' diff --git a/opendc-web/opendc-web-ui/src/util/date-time.js b/opendc-web/opendc-web-ui/src/util/date-time.js deleted file mode 100644 index 7e2f6623..00000000 --- a/opendc-web/opendc-web-ui/src/util/date-time.js +++ /dev/null @@ -1,81 +0,0 @@ -/** - * Parses and formats the given date-time string representation. - * - * The format assumed is "YYYY-MM-DDTHH:MM:SS". - * - * @param dateTimeString A string expressing a date and a time, in the above mentioned format. - * @returns {string} A human-friendly string version of that date and time. - */ -export function parseAndFormatDateTime(dateTimeString) { - return formatDateTime(new Date(dateTimeString)) -} - -/** - * Serializes the given date and time value to a human-friendly string. - * - * @param dateTime An object representation of a date and time. - * @returns {string} A human-friendly string version of that date and time. - */ -export function formatDateTime(dateTime) { - let date - const currentDate = new Date() - - date = - addPaddingToTwo(dateTime.getDay()) + - '/' + - addPaddingToTwo(dateTime.getMonth()) + - '/' + - addPaddingToTwo(dateTime.getFullYear()) - - if (dateTime.getFullYear() === currentDate.getFullYear() && dateTime.getMonth() === currentDate.getMonth()) { - if (dateTime.getDate() === currentDate.getDate()) { - date = 'Today' - } else if (dateTime.getDate() === currentDate.getDate() - 1) { - date = 'Yesterday' - } - } - - return date + ', ' + addPaddingToTwo(dateTime.getHours()) + ':' + addPaddingToTwo(dateTime.getMinutes()) -} - -/** - * Formats the given number of seconds/ticks to a formatted time representation. - * - * @param seconds The number of seconds. - * @returns {string} A string representation of that amount of second, in the from of HH:MM:SS. - */ -export function convertSecondsToFormattedTime(seconds) { - if (seconds <= 0) { - return '0s' - } - - let hour = Math.floor(seconds / 3600) - let minute = Math.floor(seconds / 60) % 60 - let second = seconds % 60 - - hour = isNaN(hour) ? 0 : hour - minute = isNaN(minute) ? 0 : minute - second = isNaN(second) ? 0 : second - - if (hour === 0 && minute === 0) { - return second + 's' - } else if (hour === 0) { - return minute + 'm' + addPaddingToTwo(second) + 's' - } else { - return hour + 'h' + addPaddingToTwo(minute) + 'm' + addPaddingToTwo(second) + 's' - } -} - -/** - * Pads the given integer to have at least two digits. - * - * @param integer An integer to be padded. - * @returns {string} A string containing the padded integer. - */ -function addPaddingToTwo(integer) { - if (integer < 10) { - return '0' + integer.toString() - } else { - return integer.toString() - } -} diff --git a/opendc-web/opendc-web-ui/src/util/date-time.test.js b/opendc-web/opendc-web-ui/src/util/date-time.test.js deleted file mode 100644 index 431e39f7..00000000 --- a/opendc-web/opendc-web-ui/src/util/date-time.test.js +++ /dev/null @@ -1,21 +0,0 @@ -import { convertSecondsToFormattedTime } from './date-time' - -describe('tick formatting', () => { - it("returns '0s' for numbers <= 0", () => { - expect(convertSecondsToFormattedTime(-1)).toEqual('0s') - expect(convertSecondsToFormattedTime(0)).toEqual('0s') - }) - it('returns only seconds for values under a minute', () => { - expect(convertSecondsToFormattedTime(1)).toEqual('1s') - expect(convertSecondsToFormattedTime(59)).toEqual('59s') - }) - it('returns seconds and minutes for values under an hour', () => { - expect(convertSecondsToFormattedTime(60)).toEqual('1m00s') - expect(convertSecondsToFormattedTime(61)).toEqual('1m01s') - expect(convertSecondsToFormattedTime(3599)).toEqual('59m59s') - }) - it('returns full time for values over an hour', () => { - expect(convertSecondsToFormattedTime(3600)).toEqual('1h00m00s') - expect(convertSecondsToFormattedTime(3601)).toEqual('1h00m01s') - }) -}) diff --git a/opendc-web/opendc-web-ui/src/util/effect-ref.js b/opendc-web/opendc-web-ui/src/util/effect-ref.js deleted file mode 100644 index 78528585..00000000 --- a/opendc-web/opendc-web-ui/src/util/effect-ref.js +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -import { useCallback, useRef } from 'react' - -const noop = () => {} - -/** - * A hook that will invoke the specified callback when the reference returned by this function is initialized. - * The callback can return an optional clean-up function. - */ -export function useEffectRef(callback, deps = []) { - const disposeRef = useRef(noop) - return useCallback((element) => { - disposeRef.current() - disposeRef.current = noop - - if (element) { - disposeRef.current = callback(element) || noop - } - }, deps) // eslint-disable-line react-hooks/exhaustive-deps -} diff --git a/opendc-web/opendc-web-ui/src/util/tile-calculations.js b/opendc-web/opendc-web-ui/src/util/tile-calculations.js deleted file mode 100644 index 374ca48c..00000000 --- a/opendc-web/opendc-web-ui/src/util/tile-calculations.js +++ /dev/null @@ -1,255 +0,0 @@ -export function deriveWallLocations(tiles) { - const { verticalWalls, horizontalWalls } = getWallSegments(tiles) - return mergeWallSegments(verticalWalls, horizontalWalls) -} - -function getWallSegments(tiles) { - const verticalWalls = {} - const horizontalWalls = {} - - tiles.forEach((tile) => { - const x = tile.positionX, - y = tile.positionY - - for (let dX = -1; dX <= 1; dX++) { - for (let dY = -1; dY <= 1; dY++) { - if (Math.abs(dX) === Math.abs(dY)) { - continue - } - - let doInsert = true - for (const tile of tiles) { - if (tile.positionX === x + dX && tile.positionY === y + dY) { - doInsert = false - break - } - } - if (!doInsert) { - continue - } - - if (dX === -1) { - if (verticalWalls[x] === undefined) { - verticalWalls[x] = [] - } - if (verticalWalls[x].indexOf(y) === -1) { - verticalWalls[x].push(y) - } - } else if (dX === 1) { - if (verticalWalls[x + 1] === undefined) { - verticalWalls[x + 1] = [] - } - if (verticalWalls[x + 1].indexOf(y) === -1) { - verticalWalls[x + 1].push(y) - } - } else if (dY === -1) { - if (horizontalWalls[y] === undefined) { - horizontalWalls[y] = [] - } - if (horizontalWalls[y].indexOf(x) === -1) { - horizontalWalls[y].push(x) - } - } else if (dY === 1) { - if (horizontalWalls[y + 1] === undefined) { - horizontalWalls[y + 1] = [] - } - if (horizontalWalls[y + 1].indexOf(x) === -1) { - horizontalWalls[y + 1].push(x) - } - } - } - } - }) - - return { verticalWalls, horizontalWalls } -} - -function mergeWallSegments(vertical, horizontal) { - const result = [] - const walls = [vertical, horizontal] - - for (let i = 0; i < 2; i++) { - const wallList = walls[i] - for (let a in wallList) { - a = parseInt(a, 10) - - wallList[a].sort((a, b) => { - return a - b - }) - - let startPos = wallList[a][0] - const isHorizontal = i === 1 - - if (wallList[a].length === 1) { - const startPosX = isHorizontal ? startPos : a - const startPosY = isHorizontal ? a : startPos - result.push({ - startPosX, - startPosY, - isHorizontal, - length: 1, - }) - } else { - let consecutiveCount = 1 - for (let b = 0; b < wallList[a].length - 1; b++) { - if (b + 1 === wallList[a].length - 1) { - if (wallList[a][b + 1] - wallList[a][b] > 1) { - const startPosX = isHorizontal ? startPos : a - const startPosY = isHorizontal ? a : startPos - result.push({ - startPosX, - startPosY, - isHorizontal, - length: consecutiveCount, - }) - consecutiveCount = 0 - startPos = wallList[a][b + 1] - } - const startPosX = isHorizontal ? startPos : a - const startPosY = isHorizontal ? a : startPos - result.push({ - startPosX, - startPosY, - isHorizontal, - length: consecutiveCount + 1, - }) - break - } else if (wallList[a][b + 1] - wallList[a][b] > 1) { - const startPosX = isHorizontal ? startPos : a - const startPosY = isHorizontal ? a : startPos - result.push({ - startPosX, - startPosY, - isHorizontal, - length: consecutiveCount, - }) - startPos = wallList[a][b + 1] - consecutiveCount = 0 - } - consecutiveCount++ - } - } - } - } - - return result -} - -export function deriveValidNextTilePositions(rooms, selectedTiles) { - const result = [], - newPosition = { x: 0, y: 0 } - let isSurroundingTile - - selectedTiles.forEach((tile) => { - const x = tile.positionX - const y = tile.positionY - result.push({ x, y }) - - for (let dX = -1; dX <= 1; dX++) { - for (let dY = -1; dY <= 1; dY++) { - if (Math.abs(dX) === Math.abs(dY)) { - continue - } - - newPosition.x = x + dX - newPosition.y = y + dY - - isSurroundingTile = true - for (let index in selectedTiles) { - if ( - selectedTiles[index].positionX === newPosition.x && - selectedTiles[index].positionY === newPosition.y - ) { - isSurroundingTile = false - break - } - } - - if (isSurroundingTile && findPositionInRooms(rooms, newPosition.x, newPosition.y) === -1) { - result.push({ x: newPosition.x, y: newPosition.y }) - } - } - } - }) - - return result -} - -export function findPositionInPositions(positions, positionX, positionY) { - for (let i = 0; i < positions.length; i++) { - const position = positions[i] - if (positionX === position.x && positionY === position.y) { - return i - } - } - - return -1 -} - -export function findPositionInRooms(rooms, positionX, positionY) { - for (let i = 0; i < rooms.length; i++) { - const room = rooms[i] - if (findPositionInTiles(room.tiles, positionX, positionY) !== -1) { - return i - } - } - - return -1 -} - -function findPositionInTiles(tiles, positionX, positionY) { - let index = -1 - - for (let i = 0; i < tiles.length; i++) { - const tile = tiles[i] - if (positionX === tile.positionX && positionY === tile.positionY) { - index = i - break - } - } - - return index -} - -export function findTileWithPosition(tiles, positionX, positionY) { - for (let i = 0; i < tiles.length; i++) { - if (tiles[i].positionX === positionX && tiles[i].positionY === positionY) { - return tiles[i] - } - } - - return null -} - -export function calculateRoomListBounds(rooms) { - const min = { x: Number.MAX_VALUE, y: Number.MAX_VALUE } - const max = { x: -1, y: -1 } - - rooms.forEach((room) => { - room.tiles.forEach((tile) => { - if (tile.positionX < min.x) { - min.x = tile.positionX - } - if (tile.positionY < min.y) { - min.y = tile.positionY - } - - if (tile.positionX > max.x) { - max.x = tile.positionX - } - if (tile.positionY > max.y) { - max.y = tile.positionY - } - }) - }) - - max.x++ - max.y++ - - const center = { - x: min.x + (max.x - min.x) / 2.0, - y: min.y + (max.y - min.y) / 2.0, - } - - return { min, center, max } -} diff --git a/opendc-web/opendc-web-ui/src/util/topology-schema.js b/opendc-web/opendc-web-ui/src/util/topology-schema.js deleted file mode 100644 index ff672dd6..00000000 --- a/opendc-web/opendc-web-ui/src/util/topology-schema.js +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright (c) 2021 AtLarge Research - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -import { schema } from 'normalizr' - -const Cpu = new schema.Entity('cpus', {}, { idAttribute: 'id' }) -const Gpu = new schema.Entity('gpus', {}, { idAttribute: 'id' }) -const Memory = new schema.Entity('memories', {}, { idAttribute: 'id' }) -const Storage = new schema.Entity('storages', {}, { idAttribute: 'id' }) - -export const Machine = new schema.Entity( - 'machines', - { - cpus: [Cpu], - gpus: [Gpu], - memories: [Memory], - storages: [Storage], - }, - { idAttribute: 'id' } -) - -export const Rack = new schema.Entity('racks', { machines: [Machine] }, { idAttribute: 'id' }) - -export const Tile = new schema.Entity('tiles', { rack: Rack }, { idAttribute: 'id' }) - -export const Room = new schema.Entity('rooms', { tiles: [Tile] }, { idAttribute: 'id' }) - -export const Topology = new schema.Entity('topologies', { rooms: [Room] }, { idAttribute: 'id' }) diff --git a/opendc-web/opendc-web-ui/src/util/unit-specifications.js b/opendc-web/opendc-web-ui/src/util/unit-specifications.js deleted file mode 100644 index 3e3671cd..00000000 --- a/opendc-web/opendc-web-ui/src/util/unit-specifications.js +++ /dev/null @@ -1,102 +0,0 @@ -export const CPU_UNITS = { - 'cpu-1': { - id: 'cpu-1', - name: 'Intel i7 v6 6700k', - clockRateMhz: 4100, - numberOfCores: 4, - energyConsumptionW: 70, - }, - 'cpu-2': { - id: 'cpu-2', - name: 'Intel i5 v6 6700k', - clockRateMhz: 3500, - numberOfCores: 2, - energyConsumptionW: 50, - }, - 'cpu-3': { - id: 'cpu-3', - name: 'Intel® Xeon® E-2224G', - clockRateMhz: 3500, - numberOfCores: 4, - energyConsumptionW: 71, - }, - 'cpu-4': { - id: 'cpu-4', - name: 'Intel® Xeon® E-2244G', - clockRateMhz: 3800, - numberOfCores: 8, - energyConsumptionW: 71, - }, - 'cpu-5': { - id: 'cpu-5', - name: 'Intel® Xeon® E-2246G', - clockRateMhz: 3600, - numberOfCores: 12, - energyConsumptionW: 80, - }, -} - -export const GPU_UNITS = { - 'gpu-1': { - id: 'gpu-1', - name: 'NVIDIA GTX 4 1080', - clockRateMhz: 1200, - numberOfCores: 200, - energyConsumptionW: 250, - }, - 'gpu-2': { - id: 'gpu-2', - name: 'NVIDIA Tesla V100', - clockRateMhz: 1200, - numberOfCores: 5120, - energyConsumptionW: 250, - }, -} - -export const MEMORY_UNITS = { - 'memory-1': { - id: 'memory-1', - name: 'Samsung PC DRAM K4A4G045WD', - speedMbPerS: 16000, - sizeMb: 4000, - energyConsumptionW: 10, - }, - 'memory-2': { - id: 'memory-2', - name: 'Samsung PC DRAM M393A2K43BB1-CRC', - speedMbPerS: 2400, - sizeMb: 16000, - energyConsumptionW: 10, - }, - 'memory-3': { - id: 'memory-3', - name: 'Crucial MTA18ASF4G72PDZ-3G2E1', - speedMbPerS: 3200, - sizeMb: 32000, - energyConsumptionW: 10, - }, - 'memory-4': { - id: 'memory-4', - name: 'Crucial MTA9ASF2G72PZ-3G2E1', - speedMbPerS: 3200, - sizeMb: 16000, - energyConsumptionW: 10, - }, -} - -export const STORAGE_UNITS = { - 'storage-1': { - id: 'storage-1', - name: 'Samsung EVO 2016 SATA III', - speedMbPerS: 6000, - sizeMb: 250000, - energyConsumptionW: 10, - }, - 'storage-2': { - id: 'storage-2', - name: 'Western Digital MTA9ASF2G72PZ-3G2E1', - speedMbPerS: 6000, - sizeMb: 4000000, - energyConsumptionW: 10, - }, -} diff --git a/resources/experiments/config.json b/resources/experiments/config.json index 4dbf1e91..9527923d 100644 --- a/resources/experiments/config.json +++ b/resources/experiments/config.json @@ -6,5 +6,7 @@ "postgresql" : "5432", "username" : "matt", "password" : "admin", - "database" : "opendc" + "database" : "opendc", + "topic" : "postgres-topic", + "kafka" : "9092" } diff --git a/resources/experiments/schema.proto b/resources/experiments/schema.proto new file mode 100644 index 00000000..2a308edd --- /dev/null +++ b/resources/experiments/schema.proto @@ -0,0 +1,11 @@ +syntax = "proto2"; + +package proto; + +option java_package = "org.opendc.common"; +option java_outer_classname = "ProtobufMetrics"; + +message ProtoExport { + required int32 id = 1; + required int32 tasksactive = 2; +}
\ No newline at end of file diff --git a/resources/experiments/sink-jdbc.properties b/resources/experiments/sink-jdbc.properties new file mode 100644 index 00000000..4a78b2ed --- /dev/null +++ b/resources/experiments/sink-jdbc.properties @@ -0,0 +1,46 @@ +# +# Copyright 2018 Confluent Inc. +# +# Licensed under the Confluent Community License (the "License"); you may not use +# this file except in compliance with the License. You may obtain a copy of the +# License at +# +# http://www.confluent.io/confluent-community-license +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OF ANY KIND, either express or implied. See the License for the +# specific language governing permissions and limitations under the License. +# + +# A simple example that copies from a topic to a Postgres database. +# The first few settings are required for all connectors: +# a name, the connector class to run, and the maximum number of tasks to create: +name=postgresql-sink +connector.class=io.confluent.connect.jdbc.JdbcSinkConnector +tasks.max=1 + +key.converter=org.apache.kafka.connect.storage.StringConverter +value.converter=io.confluent.connect.protobuf.ProtobufConverter +value.converter.schema.registry.url=http://localhost:8081 + +# The topics to consume from - required for sink connectors like this one +topics=postgres-topic + +# Configuration specific to the JDBC sink connector. +# We want to connect to a Postgres database stored in the file test.db and auto-create tables. +connection.url=jdbc:postgresql://127.0.0.1:5432/opendc + +connection.user=matt +connection.password=admin +auto.create=true + +# Define when identifiers should be quoted in DDL and DML statements. +# The default is 'always' to maintain backward compatibility with prior versions. +# Set this to 'never' to avoid quoting fully-qualified or simple table and column names. +#quote.sql.identifiers=always + +# Here are some values that enable JSON formatted files to be ingested by Postgresql + +insert.mode=insert + |
