diff --git a/.devcontainer/docker-compose.yml b/.devcontainer/docker-compose.yml index f5a314384a..515b5c06d8 100644 --- a/.devcontainer/docker-compose.yml +++ b/.devcontainer/docker-compose.yml @@ -22,7 +22,7 @@ services: # (Adding the "ports" property to this file will not forward from a Codespace.) elasticsearch: - image: exceptionless/elasticsearch:8.16.1 + image: exceptionless/elasticsearch:8.17.0 environment: node.name: elasticsearch cluster.name: exceptionless @@ -38,7 +38,7 @@ services: kibana: depends_on: - elasticsearch - image: docker.elastic.co/kibana/kibana:8.16.1 + image: docker.elastic.co/kibana/kibana:8.17.0 environment: xpack.security.enabled: "false" ports: diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 419ede966f..8e2073779b 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -6,6 +6,9 @@ on: tags: ['v*'] pull_request: +permissions: + pull-requests: write + env: TERM: xterm DOTNET_SYSTEM_CONSOLE_ALLOW_ANSI_COLOR_REDIRECTION: true @@ -15,16 +18,13 @@ env: DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }} jobs: - version: - runs-on: ubuntu-latest timeout-minutes: 30 outputs: version: ${{ steps.version.outputs.version }} steps: - - name: Checkout uses: actions/checkout@v4 with: @@ -50,12 +50,10 @@ jobs: echo "### $version" >> $GITHUB_STEP_SUMMARY test-api: - runs-on: ubuntu-latest timeout-minutes: 30 steps: - - name: Checkout uses: actions/checkout@v4 with: @@ -113,7 +111,6 @@ jobs: run: cat code-coverage-results.md >> $GITHUB_STEP_SUMMARY test-client: - runs-on: ubuntu-latest timeout-minutes: 30 defaults: @@ -121,7 +118,6 @@ jobs: working-directory: src/Exceptionless.Web/ClientApp steps: - - name: Checkout uses: actions/checkout@v4 with: @@ -159,7 +155,6 @@ jobs: run: echo "npm run test:integration" build-and-push-docker: - runs-on: ubuntu-latest needs: [version] timeout-minutes: 30 @@ -167,7 +162,6 @@ jobs: VERSION: ${{needs.version.outputs.version}} steps: - - name: Checkout uses: actions/checkout@v4 with: @@ -266,7 +260,6 @@ jobs: VERSION: ${{needs.version.outputs.version}} steps: - - name: Checkout uses: actions/checkout@v4 with: diff --git a/Dockerfile b/Dockerfile index 6e1576f77a..b9d1209987 100644 --- a/Dockerfile +++ b/Dockerfile @@ -5,7 +5,6 @@ COPY ./*.sln ./NuGet.Config ./ COPY ./src/*.props ./src/ COPY ./tests/*.props ./tests/ COPY ./build/packages/* ./build/packages/ -COPY ./docker/docker-compose.dcproj ./docker/ # Copy the main source project files COPY src/*/*.csproj ./ @@ -99,7 +98,7 @@ ENTRYPOINT ["/app/app-docker-entrypoint.sh"] # completely self-contained -FROM exceptionless/elasticsearch:8.16.1 AS exceptionless +FROM exceptionless/elasticsearch:8.17.0 AS exceptionless WORKDIR /app COPY --from=job-publish /app/src/Exceptionless.Job/out ./ diff --git a/Exceptionless.sln b/Exceptionless.sln index 34cba061b0..04b99ad6db 100644 --- a/Exceptionless.sln +++ b/Exceptionless.sln @@ -11,7 +11,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution .github\workflows\build.yaml = .github\workflows\build.yaml CONTRIBUTING.md = CONTRIBUTING.md src\Directory.Build.props = src\Directory.Build.props - docker\docker-compose.yml = docker\docker-compose.yml Dockerfile = Dockerfile exceptionless.http = exceptionless.http global.json = global.json @@ -28,8 +27,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Exceptionless.Tests", "test EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Exceptionless.Job", "src\Exceptionless.Job\Exceptionless.Job.csproj", "{788BA00C-FFBE-42A9-92A3-89E24FC137B5}" EndProject -Project("{E53339B2-1760-4266-BCC7-CA923CBCF16C}") = "docker-compose", "docker\docker-compose.dcproj", "{9F933018-9E8B-4649-8C9A-D217B5E1C184}" -EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "http", "http", "{97ED03A0-8C49-4B15-8D93-C56AF4DDC30F}" ProjectSection(SolutionItems) = preProject tests\http\admin.http = tests\http\admin.http @@ -44,6 +41,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "http", "http", "{97ED03A0-8 tests\http\webhooks.http = tests\http\webhooks.http EndProjectSection EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Exceptionless.AppHost", "src\Exceptionless.AppHost\Exceptionless.AppHost.csproj", "{EB1AF004-A00D-4016-BA97-5E89177B0074}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -70,10 +69,10 @@ Global {788BA00C-FFBE-42A9-92A3-89E24FC137B5}.Debug|Any CPU.Build.0 = Debug|Any CPU {788BA00C-FFBE-42A9-92A3-89E24FC137B5}.Release|Any CPU.ActiveCfg = Release|Any CPU {788BA00C-FFBE-42A9-92A3-89E24FC137B5}.Release|Any CPU.Build.0 = Release|Any CPU - {9F933018-9E8B-4649-8C9A-D217B5E1C184}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {9F933018-9E8B-4649-8C9A-D217B5E1C184}.Debug|Any CPU.Build.0 = Debug|Any CPU - {9F933018-9E8B-4649-8C9A-D217B5E1C184}.Release|Any CPU.ActiveCfg = Release|Any CPU - {9F933018-9E8B-4649-8C9A-D217B5E1C184}.Release|Any CPU.Build.0 = Release|Any CPU + {EB1AF004-A00D-4016-BA97-5E89177B0074}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {EB1AF004-A00D-4016-BA97-5E89177B0074}.Debug|Any CPU.Build.0 = Debug|Any CPU + {EB1AF004-A00D-4016-BA97-5E89177B0074}.Release|Any CPU.ActiveCfg = Release|Any CPU + {EB1AF004-A00D-4016-BA97-5E89177B0074}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/build/docker/elasticsearch/8.x/Dockerfile b/build/docker/elasticsearch/8.x/Dockerfile index 29277a8e56..32641cd75c 100644 --- a/build/docker/elasticsearch/8.x/Dockerfile +++ b/build/docker/elasticsearch/8.x/Dockerfile @@ -1,5 +1,5 @@ # https://www.docker.elastic.co/ -FROM docker.elastic.co/elasticsearch/elasticsearch:8.16.1 +FROM docker.elastic.co/elasticsearch/elasticsearch:8.17.0 RUN elasticsearch-plugin install -b mapper-size diff --git a/docker/docker-compose.apm.yml b/docker/docker-compose.apm.yml index 0a1bc5a5d5..5a70989f26 100644 --- a/docker/docker-compose.apm.yml +++ b/docker/docker-compose.apm.yml @@ -2,7 +2,7 @@ version: "2.2" services: setup: - image: docker.elastic.co/elasticsearch/elasticsearch:8.16.1 + image: docker.elastic.co/elasticsearch/elasticsearch:8.17.0 volumes: - certs:/usr/share/elasticsearch/config/certs user: "0" @@ -53,7 +53,7 @@ services: depends_on: setup: condition: service_healthy - image: docker.elastic.co/elasticsearch/elasticsearch:8.16.1 + image: docker.elastic.co/elasticsearch/elasticsearch:8.17.0 volumes: - certs:/usr/share/elasticsearch/config/certs - esdata:/usr/share/elasticsearch/data @@ -98,7 +98,7 @@ services: depends_on: elasticsearch: condition: service_healthy - image: docker.elastic.co/kibana/kibana:8.16.1 + image: docker.elastic.co/kibana/kibana:8.17.0 volumes: - certs:/usr/share/kibana/config/certs ports: @@ -124,7 +124,7 @@ services: depends_on: elasticsearch: condition: service_healthy - image: docker.elastic.co/apm/apm-server:8.16.1 + image: docker.elastic.co/apm/apm-server:8.17.0 volumes: - certs:/usr/share/apm-server/certs ports: diff --git a/docker/docker-compose.dcproj b/docker/docker-compose.dcproj deleted file mode 100644 index 1a5989b744..0000000000 --- a/docker/docker-compose.dcproj +++ /dev/null @@ -1,11 +0,0 @@ - - - - 2.1 - Linux - 9f933018-9e8b-4649-8c9a-d217b5e1c184 - - - - - \ No newline at end of file diff --git a/docker/docker-compose.dev.yml b/docker/docker-compose.dev.yml index afc7f5fa2c..1418177d0a 100644 --- a/docker/docker-compose.dev.yml +++ b/docker/docker-compose.dev.yml @@ -50,7 +50,7 @@ services: - appdata:/app/storage elasticsearch: - image: exceptionless/elasticsearch:8.16.1 + image: exceptionless/elasticsearch:8.17.0 environment: discovery.type: single-node xpack.security.enabled: "false" @@ -64,7 +64,7 @@ services: kibana: depends_on: - elasticsearch - image: docker.elastic.co/kibana/kibana:8.16.1 + image: docker.elastic.co/kibana/kibana:8.17.0 ports: - 5601:5601 diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml index 965df5930d..e3df9cf51e 100644 --- a/docker/docker-compose.yml +++ b/docker/docker-compose.yml @@ -1,6 +1,6 @@ services: elasticsearch: - image: exceptionless/elasticsearch:8.16.1 + image: exceptionless/elasticsearch:8.17.0 environment: node.name: elasticsearch cluster.name: exceptionless @@ -16,7 +16,7 @@ services: kibana: depends_on: - elasticsearch - image: docker.elastic.co/kibana/kibana:8.16.1 + image: docker.elastic.co/kibana/kibana:8.17.0 environment: xpack.security.enabled: "false" ports: diff --git a/k8s/elastic-monitor.yaml b/k8s/elastic-monitor.yaml index 168e486c7e..912222dd49 100644 --- a/k8s/elastic-monitor.yaml +++ b/k8s/elastic-monitor.yaml @@ -4,7 +4,7 @@ metadata: name: elastic-monitor namespace: elastic-system spec: - version: 8.16.1 + version: 8.17.0 podDisruptionBudget: {} nodeSets: - name: main @@ -51,7 +51,7 @@ metadata: name: kibana-monitor namespace: elastic-system spec: - version: 8.16.1 + version: 8.17.0 count: 1 elasticsearchRef: name: elastic-monitor @@ -145,7 +145,7 @@ metadata: name: fleet-server namespace: elastic-system spec: - version: 8.16.1 + version: 8.17.0 kibanaRef: name: kibana-monitor elasticsearchRefs: @@ -169,7 +169,7 @@ metadata: name: elastic-agent namespace: elastic-system spec: - version: 8.16.1 + version: 8.17.0 kibanaRef: name: kibana-monitor fleetServerRef: diff --git a/k8s/ex-dev-elasticsearch.yaml b/k8s/ex-dev-elasticsearch.yaml index 5a66c9ece5..6e671e0b68 100644 --- a/k8s/ex-dev-elasticsearch.yaml +++ b/k8s/ex-dev-elasticsearch.yaml @@ -4,8 +4,8 @@ metadata: name: ex-dev namespace: ex-dev spec: - version: 8.16.1 - image: exceptionless/elasticsearch:8.16.1 # https://github.com/exceptionless/Exceptionless/tree/main/build/docker/elasticsearch + version: 8.17.0 + image: exceptionless/elasticsearch:8.17.0 # https://github.com/exceptionless/Exceptionless/tree/main/build/docker/elasticsearch secureSettings: - secretName: ex-dev-snapshots http: @@ -57,7 +57,7 @@ metadata: name: ex-dev namespace: ex-dev spec: - version: 8.16.1 + version: 8.17.0 count: 1 elasticsearchRef: name: ex-dev diff --git a/k8s/ex-prod-elasticsearch.yaml b/k8s/ex-prod-elasticsearch.yaml index 3199717652..a55a769376 100644 --- a/k8s/ex-prod-elasticsearch.yaml +++ b/k8s/ex-prod-elasticsearch.yaml @@ -4,8 +4,8 @@ metadata: name: ex-prod namespace: ex-prod spec: - version: 8.16.1 - image: exceptionless/elasticsearch:8.16.1 # https://github.com/exceptionless/Exceptionless/tree/main/build/docker/elasticsearch + version: 8.17.0 + image: exceptionless/elasticsearch:8.17.0 # https://github.com/exceptionless/Exceptionless/tree/main/build/docker/elasticsearch monitoring: metrics: elasticsearchRefs: @@ -68,7 +68,7 @@ metadata: name: ex-prod namespace: ex-prod spec: - version: 8.16.1 + version: 8.17.0 count: 1 elasticsearchRef: name: ex-prod diff --git a/k8s/exceptionless/values.yaml b/k8s/exceptionless/values.yaml index bf6d1af55e..b8ff2884f1 100644 --- a/k8s/exceptionless/values.yaml +++ b/k8s/exceptionless/values.yaml @@ -51,7 +51,7 @@ elasticsearch: connectionString: image: repository: exceptionless/elasticsearch - tag: 8.16.1 + tag: 8.17.0 pullPolicy: IfNotPresent redis: diff --git a/samples/docker-compose.all-in-one.yml b/samples/docker-compose.all-in-one.yml index fac6b62e1d..1d379bf229 100644 --- a/samples/docker-compose.all-in-one.yml +++ b/samples/docker-compose.all-in-one.yml @@ -20,7 +20,7 @@ services: kibana: depends_on: - elasticsearch - image: docker.elastic.co/kibana/kibana:8.16.1 + image: docker.elastic.co/kibana/kibana:8.17.0 ports: - 5601:5601 diff --git a/samples/docker-compose.yml b/samples/docker-compose.yml index 312c5388e6..9685e310d3 100644 --- a/samples/docker-compose.yml +++ b/samples/docker-compose.yml @@ -44,11 +44,11 @@ services: - ex_appdata:/app/storage elasticsearch: - image: exceptionless/elasticsearch:8.16.1 + image: exceptionless/elasticsearch:8.17.0 environment: discovery.type: single-node xpack.security.enabled: "false" - ES_JAVA_OPTS: -Xms1g -Xmx1g + ES_JAVA_OPTS: -XX:UseSVE=0 -Xms1g -Xmx1g ports: - 9200:9200 - 9300:9300 @@ -58,7 +58,7 @@ services: kibana: depends_on: - elasticsearch - image: docker.elastic.co/kibana/kibana:8.16.1 + image: docker.elastic.co/kibana/kibana:8.17.0 ports: - 5601:5601 diff --git a/src/Exceptionless.AppHost/Exceptionless.AppHost.csproj b/src/Exceptionless.AppHost/Exceptionless.AppHost.csproj new file mode 100644 index 0000000000..74d6e01d75 --- /dev/null +++ b/src/Exceptionless.AppHost/Exceptionless.AppHost.csproj @@ -0,0 +1,27 @@ + + + + + + Exe + net9.0 + enable + enable + true + a9c2ddcc-e51d-4cd1-9782-96e1d74eec87 + + + + + + + + + + + + + + + + diff --git a/src/Exceptionless.AppHost/Extensions/ElasticsearchExtensions.cs b/src/Exceptionless.AppHost/Extensions/ElasticsearchExtensions.cs new file mode 100644 index 0000000000..423bb813ae --- /dev/null +++ b/src/Exceptionless.AppHost/Extensions/ElasticsearchExtensions.cs @@ -0,0 +1,122 @@ +using Aspire.Hosting.Lifecycle; +using Aspire.Hosting.Utils; +using HealthChecks.Elasticsearch; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Diagnostics.HealthChecks; + +namespace Aspire.Hosting; + +/// +/// Provides extension methods for adding Elasticsearch resources to the application model. +/// +public static class ElasticsearchBuilderExtensions +{ + private const int ElasticsearchPort = 9200; + private const int ElasticsearchInternalPort = 9300; + private const int KibanaPort = 5601; + + /// + /// Adds a Elasticsearch container to the application model. The default image is "docker.elastic.co/elasticsearch/elasticsearch". This version the package defaults to the 8.17.0 tag of the Elasticsearch container image + /// + /// The . + /// The name of the resource. This name will be used as the connection string name when referenced in a dependency. + /// The host port to bind the underlying container to. + /// A reference to the . + public static IResourceBuilder AddElasticsearch(this IDistributedApplicationBuilder builder, [ResourceName] string name, int? port = null) + { + ArgumentNullException.ThrowIfNull(builder); + ArgumentNullException.ThrowIfNull(name); + + var elasticsearch = new ElasticsearchResource(name); + + string? connectionString = null; + ElasticsearchOptions? options = null; + + builder.Eventing.Subscribe(elasticsearch, async (@event, ct) => + { + connectionString = await elasticsearch.ConnectionStringExpression.GetValueAsync(ct).ConfigureAwait(false); + if (connectionString is null) + { + throw new DistributedApplicationException($"ConnectionStringAvailableEvent was published for the '{elasticsearch.Name}' resource but the connection string was null."); + } + + options = new ElasticsearchOptions(); + options.UseServer(connectionString); + }); + + var healthCheckKey = $"{name}_check"; + builder.Services.AddHealthChecks() + .Add(new HealthCheckRegistration( + healthCheckKey, + sp => new ElasticsearchHealthCheck(options!), + failureStatus: default, + tags: default, + timeout: default)); + + return builder.AddResource(elasticsearch) + .WithImage(ElasticsearchContainerImageTags.Image, ElasticsearchContainerImageTags.Tag) + .WithImageRegistry(ElasticsearchContainerImageTags.ElasticsearchRegistry) + .WithHttpEndpoint(targetPort: ElasticsearchPort, port: port, name: ElasticsearchResource.PrimaryEndpointName) + .WithEndpoint(targetPort: ElasticsearchInternalPort, name: ElasticsearchResource.InternalEndpointName) + .WithEnvironment("discovery.type", "single-node") + .WithEnvironment("xpack.security.enabled", "false") + .WithEnvironment("action.destructive_requires_name", "false") + .WithEnvironment("ES_JAVA_OPTS", "-Xms1g -Xmx1g") + .WithHealthCheck(healthCheckKey) + .PublishAsConnectionString(); + } + + public static IResourceBuilder WithKibana(this IResourceBuilder builder, Action>? configureContainer = null, string? containerName = null) + { + ArgumentNullException.ThrowIfNull(builder); + + if (builder.ApplicationBuilder.Resources.OfType().SingleOrDefault() is { } existingKibanaResource) + { + var builderForExistingResource = builder.ApplicationBuilder.CreateResourceBuilder(existingKibanaResource); + configureContainer?.Invoke(builderForExistingResource); + return builder; + } + else + { + containerName ??= $"{builder.Resource.Name}-kibana"; + + builder.ApplicationBuilder.Services.TryAddLifecycleHook(); + + var resource = new KibanaResource(containerName); + var resourceBuilder = builder.ApplicationBuilder.AddResource(resource) + .WithImage(ElasticsearchContainerImageTags.KibanaImage, ElasticsearchContainerImageTags.Tag) + .WithImageRegistry(ElasticsearchContainerImageTags.KibanaRegistry) + .WithHttpEndpoint(targetPort: KibanaPort, name: containerName) + .WithEnvironment("xpack.security.enabled", "false") + .ExcludeFromManifest(); + + configureContainer?.Invoke(resourceBuilder); + + return builder; + } + } + + public static IResourceBuilder WithDataVolume(this IResourceBuilder builder, string? name = null) + { + ArgumentNullException.ThrowIfNull(builder); + + return builder.WithVolume(name ?? VolumeNameGenerator.CreateVolumeName(builder, "data"), "/usr/share/elasticsearch/data"); + } + + public static IResourceBuilder WithDataBindMount(this IResourceBuilder builder, string source) + { + ArgumentNullException.ThrowIfNull(builder); + ArgumentNullException.ThrowIfNull(source); + + return builder.WithBindMount(source, "/usr/share/elasticsearch/data"); + } +} + +internal static class ElasticsearchContainerImageTags +{ + public const string ElasticsearchRegistry = "docker.io"; + public const string Image = "exceptionless/elasticsearch"; + public const string KibanaRegistry = "docker.elastic.co"; + public const string KibanaImage = "kibana/kibana"; + public const string Tag = "8.17.0"; +} diff --git a/src/Exceptionless.AppHost/Extensions/ElasticsearchResource.cs b/src/Exceptionless.AppHost/Extensions/ElasticsearchResource.cs new file mode 100644 index 0000000000..ebdd1ea09d --- /dev/null +++ b/src/Exceptionless.AppHost/Extensions/ElasticsearchResource.cs @@ -0,0 +1,72 @@ +namespace Aspire.Hosting; + +/// +/// A resource that represents a Elasticsearch resource independent of the hosting model. +/// +public class ElasticsearchResource : ContainerResource, IResourceWithConnectionString +{ + // this endpoint is used for all API calls over HTTP. + // This includes search and aggregations, monitoring and anything else that uses a HTTP request. + // All client libraries will use this port to talk to Elasticsearch + internal const string PrimaryEndpointName = "http"; + + //this endpoint is a custom binary protocol used for communications between nodes in a cluster. + //For things like cluster updates, master elections, nodes joining/leaving, shard allocation + internal const string InternalEndpointName = "internal"; + + /// The name of the resource. + public ElasticsearchResource(string name) : base(name) + { + } + + private EndpointReference? _primaryEndpoint; + private EndpointReference? _internalEndpoint; + + /// + /// Gets the primary endpoint for the Elasticsearch. This endpoint is used for all API calls over HTTP. + /// + public EndpointReference PrimaryEndpoint => _primaryEndpoint ??= new(this, PrimaryEndpointName); + + /// + /// Gets the internal endpoint for the Elasticsearch. This endpoint used for communications between nodes in a cluster + /// + public EndpointReference InternalEndpoint => _internalEndpoint ??= new(this, InternalEndpointName); + + /// + /// Gets the connection string expression for the Elasticsearch + /// + public ReferenceExpression ConnectionString => + ReferenceExpression.Create($"http://{PrimaryEndpoint.Property(EndpointProperty.Host)}:{PrimaryEndpoint.Property(EndpointProperty.Port)}"); + + + /// + /// Gets the connection string expression for the Elasticsearch server for the manifest. + /// + public ReferenceExpression ConnectionStringExpression + { + get + { + if (this.TryGetLastAnnotation(out var connectionStringAnnotation)) + { + return connectionStringAnnotation.Resource.ConnectionStringExpression; + } + + return ConnectionString; + } + } + + /// + /// Gets the connection string for the Elasticsearch server. + /// + /// A to observe while waiting for the task to complete. + /// A connection string for the Elasticsearch server in the form "http://host:port". + public ValueTask GetConnectionStringAsync(CancellationToken cancellationToken = default) + { + if (this.TryGetLastAnnotation(out var connectionStringAnnotation)) + { + return connectionStringAnnotation.Resource.GetConnectionStringAsync(cancellationToken); + } + + return ConnectionString.GetValueAsync(cancellationToken); + } +} diff --git a/src/Exceptionless.AppHost/Extensions/KibanaConfigWriterHook.cs b/src/Exceptionless.AppHost/Extensions/KibanaConfigWriterHook.cs new file mode 100644 index 0000000000..67bae87b43 --- /dev/null +++ b/src/Exceptionless.AppHost/Extensions/KibanaConfigWriterHook.cs @@ -0,0 +1,37 @@ +using System.Text; +using Aspire.Hosting.Lifecycle; +using Microsoft.Extensions.DependencyInjection; + +namespace Aspire.Hosting; + +internal class KibanaConfigWriterHook : IDistributedApplicationLifecycleHook +{ + public async Task AfterEndpointsAllocatedAsync(DistributedApplicationModel appModel, CancellationToken cancellationToken) + { + if (appModel.Resources.OfType().SingleOrDefault() is not { } kibanaResource) + return; + + var elasticsearchInstances = appModel.Resources.OfType(); + + if (!elasticsearchInstances.Any()) + return; + + var hostsVariableBuilder = new StringBuilder(); + + foreach (var elasticsearchInstance in elasticsearchInstances) + { + if (elasticsearchInstance.PrimaryEndpoint.IsAllocated) + { + var connectionString = await elasticsearchInstance.GetConnectionStringAsync(); + if (hostsVariableBuilder.Length > 0) + hostsVariableBuilder.Append(","); + hostsVariableBuilder.Append(elasticsearchInstance.PrimaryEndpoint.Scheme).Append("://").Append(elasticsearchInstance.PrimaryEndpoint.ContainerHost).Append(":").Append(elasticsearchInstance.PrimaryEndpoint.Port); + } + } + + kibanaResource.Annotations.Add(new EnvironmentCallbackAnnotation(context => + { + context.EnvironmentVariables.Add("ELASTICSEARCH_HOSTS", hostsVariableBuilder.ToString()); + })); + } +} diff --git a/src/Exceptionless.AppHost/Extensions/KibanaResource.cs b/src/Exceptionless.AppHost/Extensions/KibanaResource.cs new file mode 100644 index 0000000000..7e5031a46b --- /dev/null +++ b/src/Exceptionless.AppHost/Extensions/KibanaResource.cs @@ -0,0 +1,9 @@ +namespace Aspire.Hosting; + +/// +/// A resource that represents a Kibana container. +/// +/// The name of the resource. +public class KibanaResource(string name) : ContainerResource(name) +{ +} diff --git a/src/Exceptionless.AppHost/Extensions/MinIoExtensions.cs b/src/Exceptionless.AppHost/Extensions/MinIoExtensions.cs new file mode 100644 index 0000000000..464f3985f8 --- /dev/null +++ b/src/Exceptionless.AppHost/Extensions/MinIoExtensions.cs @@ -0,0 +1,138 @@ +using Foundatio.Storage; + +namespace Aspire.Hosting; + +public static class MinIoExtensions +{ + public static IResourceBuilder AddMinIo( + this IDistributedApplicationBuilder builder, + string name, + Action? configure = null) + { + var options = new MinIoBuilder(); + configure?.Invoke(options); + + var resource = new MinIoResource(name, options.AccessKey, options.SecretKey, options.Bucket ?? "storage"); + + string? connectionString = null; + + builder.Eventing.Subscribe(resource, async (@event, ct) => + { + connectionString = await resource.ConnectionStringExpression.GetValueAsync(ct).ConfigureAwait(false); + + if (connectionString == null) + throw new DistributedApplicationException($"ResourceReadyEvent was published for the '{resource.Name}' resource but the connection string was null."); + + var storage = new S3FileStorage(o => o.ConnectionString(connectionString)); + try + { + storage.Client.PutBucketAsync(options.Bucket ?? "storage", ct).GetAwaiter().GetResult(); + } + catch + { + // ignored + } + }); + + return builder.AddResource(resource) + .WithImage(MinIoContainerImageTags.Image) + .WithImageRegistry(MinIoContainerImageTags.Registry) + .WithImageTag(MinIoContainerImageTags.Tag) + .WithArgs("server", "/data", "--console-address", $":{MinIoResource.DefaultConsolePort}") + .WithEndpoint(port: options.ApiPort, targetPort: MinIoResource.DefaultApiPort, name: MinIoResource.ApiEndpointName) + .WithHttpEndpoint(port: options.ConsolePort, targetPort: MinIoResource.DefaultConsolePort, name: MinIoResource.ConsoleEndpointName) + .ConfigureCredentials(options) + .ConfigureVolume(options); + } + + private static IResourceBuilder ConfigureCredentials( + this IResourceBuilder builder, + MinIoBuilder options) + { + return builder + .WithEnvironment("MINIO_ROOT_USER", options.AccessKey ?? "minioadmin") + .WithEnvironment("MINIO_ROOT_PASSWORD", options.SecretKey ?? "minioadmin"); + } + + private static IResourceBuilder ConfigureVolume( + this IResourceBuilder builder, + MinIoBuilder options) + { + if (!string.IsNullOrEmpty(options.DataVolumePath)) + builder = builder.WithVolume(options.DataVolumePath, "/data"); + + return builder; + } +} + +public class MinIoResource(string name, string? accessKey = null, string? secretKey = null, string? bucket = "storage") + : ContainerResource(name), IResourceWithConnectionString +{ + internal const string ApiEndpointName = "api"; + internal const string ConsoleEndpointName = "console"; + internal const int DefaultApiPort = 9000; + internal const int DefaultConsolePort = 9001; + + private EndpointReference? _apiReference; + private EndpointReference? _consoleReference; + + private EndpointReference ApiEndpoint => + _apiReference ??= new EndpointReference(this, ApiEndpointName); + + private EndpointReference ConsoleEndpoint => + _consoleReference ??= new EndpointReference(this, ConsoleEndpointName); + + public ReferenceExpression ConnectionStringExpression => + ReferenceExpression.Create( + $"ServiceUrl=http://{ApiEndpoint.Property(EndpointProperty.Host)}:{ApiEndpoint.Property(EndpointProperty.Port)};" + + $"AccessKey={AccessKey ?? "minioadmin"};" + + $"SecretKey={SecretKey ?? "minioadmin"};" + + $"Bucket={Bucket}"); + + public string? AccessKey { get; } = accessKey; + public string? SecretKey { get; } = secretKey; + public string? Bucket { get; } = bucket; +} + +public class MinIoBuilder +{ + public int? ApiPort { get; set; } + public int? ConsolePort { get; set; } + public string? AccessKey { get; set; } + public string? SecretKey { get; set; } + public string? Bucket { get; set; } + public string? DataVolumePath { get; set; } + + public MinIoBuilder WithPorts(int? apiPort = null, int? consolePort = null) + { + ApiPort = apiPort; + ConsolePort = consolePort; + return this; + } + + public MinIoBuilder WithCredentials(string accessKey, string secretKey) + { + AccessKey = accessKey; + SecretKey = secretKey; + return this; + } + + public MinIoBuilder WithBucket(string bucket) + { + Bucket = bucket; + return this; + } + + public MinIoBuilder WithDataVolume(string path) + { + DataVolumePath = path; + return this; + } +} + +internal static class MinIoContainerImageTags +{ + internal const string Registry = "docker.io"; + internal const string Image = "minio/minio"; + internal const string Tag = "RELEASE.2024-12-13T22-19-12Z"; +} diff --git a/src/Exceptionless.AppHost/Extensions/RedisExtensions.cs b/src/Exceptionless.AppHost/Extensions/RedisExtensions.cs new file mode 100644 index 0000000000..71e75f94db --- /dev/null +++ b/src/Exceptionless.AppHost/Extensions/RedisExtensions.cs @@ -0,0 +1,28 @@ +using Microsoft.Extensions.Diagnostics.HealthChecks; +using StackExchange.Redis; + +namespace Aspire.Hosting; + +public static class RedisExtensions +{ + public static IResourceBuilder WithClearCommand( + this IResourceBuilder builder) + { + builder.WithCommand( + "clear-cache", + "Clear Cache", + async _ => + { + var redisConnectionString = await builder.Resource.GetConnectionStringAsync() ?? + throw new InvalidOperationException("Unable to get the Redis connection string."); + + await using var connection = await ConnectionMultiplexer.ConnectAsync(redisConnectionString); + + await connection.GetDatabase().ExecuteAsync("FLUSHALL"); + + return CommandResults.Success(); + }, + context => context.ResourceSnapshot.HealthStatus is HealthStatus.Healthy ? ResourceCommandState.Enabled : ResourceCommandState.Disabled); + return builder; + } +} diff --git a/src/Exceptionless.AppHost/Extensions/VolumeNameGenerator.cs b/src/Exceptionless.AppHost/Extensions/VolumeNameGenerator.cs new file mode 100644 index 0000000000..6ecbc551f8 --- /dev/null +++ b/src/Exceptionless.AppHost/Extensions/VolumeNameGenerator.cs @@ -0,0 +1,65 @@ +namespace Aspire.Hosting.Utils; + +internal static class VolumeNameGenerator +{ + public static string CreateVolumeName(IResourceBuilder builder, string suffix) where T : IResource + { + if (!HasOnlyValidChars(suffix)) + { + throw new ArgumentException($"The suffix '{suffix}' contains invalid characters. Only [a-zA-Z0-9_.-] are allowed.", nameof(suffix)); + } + + // Creates a volume name with the form < c > $"{applicationName}-{sha256 of apphost path}-{resourceName}-{suffix}, e.g. "myapplication-a345f2451-postgres-data". + // Create volume name like "{Sanitize(appname).Lower()}-{sha256.Lower()}-postgres-data" + + // Compute a short hash of the content root path to differentiate between multiple AppHost projects with similar volume names + var safeApplicationName = Sanitize(builder.ApplicationBuilder.Environment.ApplicationName).ToLowerInvariant(); + var applicationHash = builder.ApplicationBuilder.Configuration["AppHost:Sha256"]![..10].ToLowerInvariant(); + var resourceName = builder.Resource.Name; + return $"{safeApplicationName}-{applicationHash}-{resourceName}-{suffix}"; + } + + public static string Sanitize(string name) + { + return string.Create(name.Length, name, static (s, name) => + { + // According to the error message from docker CLI, volume names must be of form "[a-zA-Z0-9][a-zA-Z0-9_.-]" + var nameSpan = name.AsSpan(); + + for (var i = 0; i < nameSpan.Length; i++) + { + var c = nameSpan[i]; + + s[i] = IsValidChar(i, c) ? c : '_'; + } + }); + } + + private static bool HasOnlyValidChars(string value) + { + for (var i = 0; i < value.Length; i++) + { + if (!IsValidChar(i, value[i])) + { + return false; + } + } + return true; + } + + private static bool IsValidChar(int i, char c) + { + if (i == 0 && !(char.IsAsciiLetter(c) || char.IsNumber(c))) + { + // First char must be a letter or number + return false; + } + else if (!(char.IsAsciiLetter(c) || char.IsNumber(c) || c == '_' || c == '.' || c == '-')) + { + // Subsequent chars must be a letter, number, underscore, period, or hyphen + return false; + } + + return true; + } +} diff --git a/src/Exceptionless.AppHost/Program.cs b/src/Exceptionless.AppHost/Program.cs new file mode 100644 index 0000000000..0df5bf1581 --- /dev/null +++ b/src/Exceptionless.AppHost/Program.cs @@ -0,0 +1,57 @@ +var builder = DistributedApplication.CreateBuilder(args); + +var elastic = builder.AddElasticsearch("Elasticsearch", port: 9200) + .WithLifetime(ContainerLifetime.Persistent) + .WithContainerName("Exceptionless-Elasticsearch") + .WithDataVolume("exceptionless.data.v1") + .WithKibana(b => b.WithLifetime(ContainerLifetime.Persistent).WithContainerName("Exceptionless-Kibana")); + +var storage = builder.AddMinIo("S3", s => s.WithCredentials("guest", "password").WithPorts(9000).WithBucket("ex-events")) + .WithLifetime(ContainerLifetime.Persistent) + .WithContainerName("Exceptionless-Storage"); + +var cache = builder.AddRedis("Redis", port: 6379) + .WithImageTag("7.4") + .WithLifetime(ContainerLifetime.Persistent) + .WithContainerName("Exceptionless-Redis") + .WithClearCommand() + .WithRedisInsight(b => b.WithLifetime(ContainerLifetime.Persistent).WithContainerName("Exceptionless-RedisInsight")); + +var mail = builder.AddContainer("Mail", "mailhog/mailhog") + .WithLifetime(ContainerLifetime.Persistent) + .WithContainerName("Exceptionless-Mail") + .WithEndpoint(8025, 8025, "http") + .WithEndpoint(1025, 1025); + +builder.AddProject("Jobs", "AllJobs") + .WithReference(cache) + .WithReference(elastic) + .WithReference(storage) + .WithEnvironment("ConnectionStrings:Email", "smtp://localhost:1025") + .WaitFor(elastic) + .WaitFor(cache) + .WaitFor(mail) + .WithHttpHealthCheck("/health"); + +var api = builder.AddProject("Api", "Exceptionless") + .WithReference(cache) + .WithReference(elastic) + .WithReference(storage) + .WithEnvironment("ConnectionStrings:Email", "smtp://localhost:1025") + .WithEnvironment("RunJobsInProcess", "false") + .WaitFor(elastic) + .WaitFor(cache) + .WaitFor(mail) + .WithHttpHealthCheck("/health"); + +builder.AddNpmApp("Web", "../../src/Exceptionless.Web/ClientApp", "dev") + .WithReference(api) + .WithEnvironment("ASPNETCORE_URLS", "http://localhost:5200") + .WithEndpoint(port: 5173, targetPort: 5173, scheme: "http", env: "PORT", isProxied: false); + +builder.AddNpmApp("AngularWeb", "../../src/Exceptionless.Web/ClientApp.angular", "serve") + .WithReference(api) + .WithEnvironment("ASPNETCORE_URLS", "http://localhost:5200") + .WithEndpoint(port: 5100, targetPort: 5100, scheme: "http", env: "PORT", isProxied: false); + +builder.Build().Run(); diff --git a/src/Exceptionless.AppHost/Properties/launchSettings.json b/src/Exceptionless.AppHost/Properties/launchSettings.json new file mode 100644 index 0000000000..a657132e91 --- /dev/null +++ b/src/Exceptionless.AppHost/Properties/launchSettings.json @@ -0,0 +1,42 @@ +{ + "$schema": "https://json.schemastore.org/launchsettings.json", + "profiles": { + "https": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "applicationUrl": "https://localhost:17056;http://localhost:15161", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development", + "DOTNET_ENVIRONMENT": "Development", + "DOTNET_DASHBOARD_OTLP_ENDPOINT_URL": "https://localhost:21210", + "DOTNET_RESOURCE_SERVICE_ENDPOINT_URL": "https://localhost:22299" + } + }, + "https-all": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "applicationUrl": "https://localhost:17056;http://localhost:15161", + "environmentVariables": { + "EX_ALL": "true", + "ASPNETCORE_ENVIRONMENT": "Development", + "DOTNET_ENVIRONMENT": "Development", + "DOTNET_DASHBOARD_OTLP_ENDPOINT_URL": "https://localhost:21210", + "DOTNET_RESOURCE_SERVICE_ENDPOINT_URL": "https://localhost:22299" + } + }, + "http": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "applicationUrl": "http://localhost:15161", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development", + "DOTNET_ENVIRONMENT": "Development", + "DOTNET_DASHBOARD_OTLP_ENDPOINT_URL": "http://localhost:19113", + "DOTNET_RESOURCE_SERVICE_ENDPOINT_URL": "http://localhost:20111" + } + } + } +} diff --git a/src/Exceptionless.AppHost/appsettings.Development.json b/src/Exceptionless.AppHost/appsettings.Development.json new file mode 100644 index 0000000000..0c208ae918 --- /dev/null +++ b/src/Exceptionless.AppHost/appsettings.Development.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } +} diff --git a/src/Exceptionless.AppHost/appsettings.json b/src/Exceptionless.AppHost/appsettings.json new file mode 100644 index 0000000000..31c092aa45 --- /dev/null +++ b/src/Exceptionless.AppHost/appsettings.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning", + "Aspire.Hosting.Dcp": "Warning" + } + } +} diff --git a/src/Exceptionless.Core/Configuration/AppOptions.cs b/src/Exceptionless.Core/Configuration/AppOptions.cs index 1595dbd7e2..02de4342ef 100644 --- a/src/Exceptionless.Core/Configuration/AppOptions.cs +++ b/src/Exceptionless.Core/Configuration/AppOptions.cs @@ -1,4 +1,4 @@ -using System.Diagnostics; +using System.Diagnostics; using Exceptionless.Core.Configuration; using Exceptionless.Core.Extensions; using Microsoft.Extensions.Configuration; @@ -22,7 +22,7 @@ public class AppOptions public string? ExceptionlessApiKey { get; internal set; } /// - /// Configures the exceptionless client server url, which logs all internal errors and log messages. + /// Configures the Exceptionless client server url, which logs all internal errors and log messages. /// public string? ExceptionlessServerUrl { get; internal set; } diff --git a/src/Exceptionless.Core/Configuration/CacheOptions.cs b/src/Exceptionless.Core/Configuration/CacheOptions.cs index a0d788c482..3427e1a1bb 100644 --- a/src/Exceptionless.Core/Configuration/CacheOptions.cs +++ b/src/Exceptionless.Core/Configuration/CacheOptions.cs @@ -20,14 +20,29 @@ public static CacheOptions ReadFromConfiguration(IConfiguration config, AppOptio options.ScopePrefix = !String.IsNullOrEmpty(options.Scope) ? $"{options.Scope}-" : String.Empty; string? cs = config.GetConnectionString("Cache"); - options.Data = cs.ParseConnectionString(); - options.Provider = options.Data.GetString(nameof(options.Provider)); + if (cs != null) + { + options.Data = cs.ParseConnectionString(); + options.Provider = options.Data.GetString(nameof(options.Provider)); + } + else + { + var redisConnectionString = config.GetConnectionString("Redis"); + if (!String.IsNullOrEmpty(redisConnectionString)) + { + options.Provider = "redis"; + } + } string? providerConnectionString = !String.IsNullOrEmpty(options.Provider) ? config.GetConnectionString(options.Provider) : null; if (!String.IsNullOrEmpty(providerConnectionString)) - options.Data.AddRange(providerConnectionString.ParseConnectionString()); + { + var providerOptions = providerConnectionString.ParseConnectionString(defaultKey: "server"); + options.Data ??= new Dictionary(StringComparer.OrdinalIgnoreCase); + options.Data.AddRange(providerOptions); + } - options.ConnectionString = options.Data.BuildConnectionString(new HashSet { nameof(options.Provider) }); + options.ConnectionString = options.Data.BuildConnectionString([nameof(options.Provider)]); return options; } diff --git a/src/Exceptionless.Core/Configuration/CustomEnvironmentVariablesConfiguration.cs b/src/Exceptionless.Core/Configuration/CustomEnvironmentVariablesConfiguration.cs new file mode 100644 index 0000000000..f626ff6788 --- /dev/null +++ b/src/Exceptionless.Core/Configuration/CustomEnvironmentVariablesConfiguration.cs @@ -0,0 +1,56 @@ +using System.Collections; +using Microsoft.Extensions.Configuration; + +namespace Exceptionless.Core.Configuration; + +public static class CustomEnvironmentVariablesExtensions +{ + public static IConfigurationBuilder AddCustomEnvironmentVariables(this IConfigurationBuilder configurationBuilder) + { + configurationBuilder.Add(new CustomEnvironmentVariablesConfigurationSource()); + return configurationBuilder; + } +} + +public class CustomEnvironmentVariablesConfigurationSource : IConfigurationSource +{ + public IConfigurationProvider Build(IConfigurationBuilder builder) + { + return new CustomEnvironmentVariablesConfigurationProvider(); + } +} + +public class CustomEnvironmentVariablesConfigurationProvider : ConfigurationProvider +{ + public override void Load() => Load(Environment.GetEnvironmentVariables()); + + internal void Load(IDictionary envVariables) + { + var data = new Dictionary(StringComparer.OrdinalIgnoreCase); + + IDictionaryEnumerator e = envVariables.GetEnumerator(); + try + { + while (e.MoveNext()) + { + string key = (string)e.Entry.Key; + string? value = (string?)e.Entry.Value; + + var normalizedKey = Normalize(key); + // remove EX_ prefix + if (normalizedKey.StartsWith("EX_")) + data[normalizedKey.Substring(3)] = value; + else + data[normalizedKey] = value; + } + } + finally + { + (e as IDisposable)?.Dispose(); + } + + Data = data; + } + + private static string Normalize(string key) => key.Replace("__", ConfigurationPath.KeyDelimiter); +} diff --git a/src/Exceptionless.Core/Configuration/ElasticsearchOptions.cs b/src/Exceptionless.Core/Configuration/ElasticsearchOptions.cs index 38eabafe72..bce6d41cd7 100644 --- a/src/Exceptionless.Core/Configuration/ElasticsearchOptions.cs +++ b/src/Exceptionless.Core/Configuration/ElasticsearchOptions.cs @@ -51,7 +51,7 @@ public static ElasticsearchOptions ReadFromConfiguration(IConfiguration config, private static void ParseConnectionString(string? connectionString, ElasticsearchOptions options, AppMode appMode) { - var pairs = connectionString.ParseConnectionString(); + var pairs = connectionString.ParseConnectionString(defaultKey: "server"); options.ServerUrl = pairs.GetString("server", "http://localhost:9200"); int shards = pairs.GetValueOrDefault("shards", 1); diff --git a/src/Exceptionless.Core/Configuration/EmailOptions.cs b/src/Exceptionless.Core/Configuration/EmailOptions.cs index 5c8b07aa65..17d560d967 100644 --- a/src/Exceptionless.Core/Configuration/EmailOptions.cs +++ b/src/Exceptionless.Core/Configuration/EmailOptions.cs @@ -16,7 +16,7 @@ public class EmailOptions public string? TestEmailAddress { get; internal set; } /// - /// Email addresses that match this comma delimited list of domains and email addresses will be allowed to be sent out in QA mode + /// Email addresses that match this comma-delimited list of domains and email addresses will be allowed to be sent out in QA mode /// public List AllowedOutboundAddresses { get; internal set; } = null!; diff --git a/src/Exceptionless.Core/Configuration/MessageBusOptions.cs b/src/Exceptionless.Core/Configuration/MessageBusOptions.cs index b69407028f..23327d2387 100644 --- a/src/Exceptionless.Core/Configuration/MessageBusOptions.cs +++ b/src/Exceptionless.Core/Configuration/MessageBusOptions.cs @@ -22,12 +22,27 @@ public static MessageBusOptions ReadFromConfiguration(IConfiguration config, App options.Topic = config.GetValue(nameof(options.Topic), $"{options.ScopePrefix}messages")!; string? cs = config.GetConnectionString("MessageBus"); - options.Data = cs.ParseConnectionString(); - options.Provider = options.Data.GetString(nameof(options.Provider)); + if (cs != null) + { + options.Data = cs.ParseConnectionString(); + options.Provider = options.Data.GetString(nameof(options.Provider)); + } + else + { + var redisConnectionString = config.GetConnectionString("Redis"); + if (!String.IsNullOrEmpty(redisConnectionString)) + { + options.Provider = "redis"; + } + } string? providerConnectionString = !String.IsNullOrEmpty(options.Provider) ? config.GetConnectionString(options.Provider) : null; if (!String.IsNullOrEmpty(providerConnectionString)) - options.Data.AddRange(providerConnectionString.ParseConnectionString()); + { + var providerOptions = providerConnectionString.ParseConnectionString(defaultKey: "server"); + options.Data ??= new Dictionary(StringComparer.OrdinalIgnoreCase); + options.Data.AddRange(providerOptions); + } options.ConnectionString = options.Data.BuildConnectionString(new HashSet { nameof(options.Provider) }); diff --git a/src/Exceptionless.Core/Configuration/QueueOptions.cs b/src/Exceptionless.Core/Configuration/QueueOptions.cs index 9ab058ca66..5955eb25fd 100644 --- a/src/Exceptionless.Core/Configuration/QueueOptions.cs +++ b/src/Exceptionless.Core/Configuration/QueueOptions.cs @@ -13,6 +13,8 @@ public class QueueOptions public string Scope { get; internal set; } = null!; public string ScopePrefix { get; internal set; } = null!; + public bool MetricsPollingEnabled { get; set; } = true; + public TimeSpan MetricsPollingInterval { get; set; } = TimeSpan.FromSeconds(5); public static QueueOptions ReadFromConfiguration(IConfiguration config, AppOptions appOptions) { @@ -20,15 +22,32 @@ public static QueueOptions ReadFromConfiguration(IConfiguration config, AppOptio options.ScopePrefix = !String.IsNullOrEmpty(options.Scope) ? $"{options.Scope}-" : String.Empty; string? cs = config.GetConnectionString("Queue"); - options.Data = cs.ParseConnectionString(); - options.Provider = options.Data.GetString(nameof(options.Provider)); + if (cs != null) + { + options.Data = cs.ParseConnectionString(); + options.Provider = options.Data.GetString(nameof(options.Provider)); + } + else + { + var redisConnectionString = config.GetConnectionString("Redis"); + if (!String.IsNullOrEmpty(redisConnectionString)) + { + options.Provider = "redis"; + } + } string? providerConnectionString = !String.IsNullOrEmpty(options.Provider) ? config.GetConnectionString(options.Provider) : null; if (!String.IsNullOrEmpty(providerConnectionString)) - options.Data.AddRange(providerConnectionString.ParseConnectionString()); + { + var providerOptions = providerConnectionString.ParseConnectionString(defaultKey: "server"); + options.Data ??= new Dictionary(StringComparer.OrdinalIgnoreCase); + options.Data.AddRange(providerOptions); + } options.ConnectionString = options.Data.BuildConnectionString(new HashSet { nameof(options.Provider) }); + options.MetricsPollingInterval = appOptions.AppMode == AppMode.Development ? TimeSpan.FromSeconds(15) : TimeSpan.FromSeconds(5); + return options; } } diff --git a/src/Exceptionless.Core/Configuration/StorageOptions.cs b/src/Exceptionless.Core/Configuration/StorageOptions.cs index 2c114e973d..79fbbba864 100644 --- a/src/Exceptionless.Core/Configuration/StorageOptions.cs +++ b/src/Exceptionless.Core/Configuration/StorageOptions.cs @@ -16,18 +16,31 @@ public class StorageOptions public static StorageOptions ReadFromConfiguration(IConfiguration config, AppOptions appOptions) { - var options = new StorageOptions(); - - options.Scope = appOptions.AppScope; + var options = new StorageOptions { Scope = appOptions.AppScope }; options.ScopePrefix = !String.IsNullOrEmpty(options.Scope) ? $"{options.Scope}-" : String.Empty; string? cs = config.GetConnectionString("Storage"); - options.Data = cs.ParseConnectionString(); - options.Provider = options.Data.GetString(nameof(options.Provider)); + if (cs != null) + { + options.Data = cs.ParseConnectionString(); + options.Provider = options.Data.GetString(nameof(options.Provider)); + } + else + { + var minioConnectionString = config.GetConnectionString("S3"); + if (!String.IsNullOrEmpty(minioConnectionString)) + { + options.Provider = "s3"; + } + } string? providerConnectionString = !String.IsNullOrEmpty(options.Provider) ? config.GetConnectionString(options.Provider) : null; if (!String.IsNullOrEmpty(providerConnectionString)) - options.Data.AddRange(providerConnectionString.ParseConnectionString()); + { + var providerOptions = providerConnectionString.ParseConnectionString(defaultKey: "server"); + options.Data ??= new Dictionary(StringComparer.OrdinalIgnoreCase); + options.Data.AddRange(providerOptions); + } options.ConnectionString = options.Data.BuildConnectionString(new HashSet { nameof(options.Provider) }); diff --git a/src/Exceptionless.Core/Exceptionless.Core.csproj b/src/Exceptionless.Core/Exceptionless.Core.csproj index bf3a748b3c..cb8cbd0905 100644 --- a/src/Exceptionless.Core/Exceptionless.Core.csproj +++ b/src/Exceptionless.Core/Exceptionless.Core.csproj @@ -22,16 +22,16 @@ - - - + + + - + diff --git a/src/Exceptionless.Core/Extensions/IdentityUtils.cs b/src/Exceptionless.Core/Extensions/IdentityUtils.cs index 0453aeea7c..cb05f8f5ff 100644 --- a/src/Exceptionless.Core/Extensions/IdentityUtils.cs +++ b/src/Exceptionless.Core/Extensions/IdentityUtils.cs @@ -130,7 +130,6 @@ public static bool IsUserAuthType(this ClaimsPrincipal principal) /// Gets the token id that authenticated the current user. If null, user logged in via oauth. /// /// - /// public static string? GetLoggedInUsersTokenId(this ClaimsPrincipal principal) { return IsUserAuthType(principal) ? GetClaimValue(principal, LoggedInUsersTokenId) : null; diff --git a/src/Exceptionless.Core/Repositories/Configuration/Indexes/EventIndex.cs b/src/Exceptionless.Core/Repositories/Configuration/Indexes/EventIndex.cs index 2224ff75e0..725bd7672b 100644 --- a/src/Exceptionless.Core/Repositories/Configuration/Indexes/EventIndex.cs +++ b/src/Exceptionless.Core/Repositories/Configuration/Indexes/EventIndex.cs @@ -307,6 +307,7 @@ public sealed class Alias public const string LocationLevel2 = "level2"; public const string LocationLocality = "locality"; + public const string Error = "error"; public const string ErrorCode = "error.code"; public const string ErrorType = "error.type"; public const string ErrorMessage = "error.message"; @@ -322,7 +323,7 @@ public static PropertiesDescriptor AddCopyToMappings(this Prope return descriptor .Text(f => f.Name(EventIndex.Alias.IpAddress).Analyzer(EventIndex.COMMA_WHITESPACE_ANALYZER)) .Text(f => f.Name(EventIndex.Alias.OperatingSystem).Analyzer(EventIndex.WHITESPACE_LOWERCASE_ANALYZER).AddKeywordField()) - .Object(f => f.Name("error").Properties(p1 => p1 + .Object(f => f.Name(EventIndex.Alias.Error).Properties(p1 => p1 .Keyword(f3 => f3.Name("code").IgnoreAbove(1024)) .Text(f3 => f3.Name("message").AddKeywordField()) .Text(f3 => f3.Name("type").Analyzer(EventIndex.TYPENAME_ANALYZER).SearchAnalyzer(EventIndex.WHITESPACE_LOWERCASE_ANALYZER).AddKeywordField()) diff --git a/src/Exceptionless.Core/Repositories/EventRepository.cs b/src/Exceptionless.Core/Repositories/EventRepository.cs index dcd606a439..65e1d7ad43 100644 --- a/src/Exceptionless.Core/Repositories/EventRepository.cs +++ b/src/Exceptionless.Core/Repositories/EventRepository.cs @@ -26,7 +26,7 @@ public EventRepository(ExceptionlessElasticConfiguration configuration, AppOptio // copy to fields AddDefaultExclude(EventIndex.Alias.IpAddress); AddDefaultExclude(EventIndex.Alias.OperatingSystem); - AddDefaultExclude("error"); + AddDefaultExclude(EventIndex.Alias.Error); AddPropertyRequiredForRemove(e => e.Date); } diff --git a/src/Exceptionless.Core/Repositories/StackRepository.cs b/src/Exceptionless.Core/Repositories/StackRepository.cs index b384d4ee30..bd4542f93f 100644 --- a/src/Exceptionless.Core/Repositories/StackRepository.cs +++ b/src/Exceptionless.Core/Repositories/StackRepository.cs @@ -49,7 +49,7 @@ public Task> GetSoftDeleted() public async Task IncrementEventCounterAsync(string organizationId, string projectId, string stackId, DateTime minOccurrenceDateUtc, DateTime maxOccurrenceDateUtc, int count, bool sendNotifications = true) { // If total occurrences are zero (stack data was reset), then set first occurrence date - // Only update the LastOccurrence if the new date is greater then the existing date. + // Only update the LastOccurrence if the new date is greater than the existing date. const string script = @" Instant parseDate(def dt) { if (dt != null) { diff --git a/src/Exceptionless.Insulation/Bootstrapper.cs b/src/Exceptionless.Insulation/Bootstrapper.cs index dd690ec6e4..d47c6916b6 100644 --- a/src/Exceptionless.Insulation/Bootstrapper.cs +++ b/src/Exceptionless.Insulation/Bootstrapper.cs @@ -183,22 +183,22 @@ private static void RegisterQueue(IServiceCollection container, QueueOptions opt private static void RegisterStorage(IServiceCollection container, StorageOptions options) { - if (String.Equals(options.Provider, "aliyun")) + if (String.Equals(options.Provider, "azurestorage")) { - container.ReplaceSingleton(s => new AliyunFileStorage(new AliyunFileStorageOptions + container.ReplaceSingleton(s => new AzureFileStorage(new AzureFileStorageOptions { ConnectionString = options.ConnectionString, + ContainerName = $"{options.ScopePrefix}ex-events", Serializer = s.GetRequiredService(), TimeProvider = s.GetRequiredService(), LoggerFactory = s.GetRequiredService() })); } - else if (String.Equals(options.Provider, "azurestorage")) + else if (String.Equals(options.Provider, "aliyun")) { - container.ReplaceSingleton(s => new AzureFileStorage(new AzureFileStorageOptions + container.ReplaceSingleton(s => new AliyunFileStorage(new AliyunFileStorageOptions { ConnectionString = options.ConnectionString, - ContainerName = $"{options.ScopePrefix}ex-events", Serializer = s.GetRequiredService(), TimeProvider = s.GetRequiredService(), LoggerFactory = s.GetRequiredService() @@ -207,13 +207,21 @@ private static void RegisterStorage(IServiceCollection container, StorageOptions else if (String.Equals(options.Provider, "folder")) { string path = options.Data.GetString("path", "|DataDirectory|\\storage"); - container.AddSingleton(s => new FolderFileStorage(new FolderFileStorageOptions + container.AddSingleton(s => { - Folder = PathHelper.ExpandPath(path), - Serializer = s.GetRequiredService(), - TimeProvider = s.GetRequiredService(), - LoggerFactory = s.GetRequiredService() - })); + IFileStorage storage = new FolderFileStorage(new FolderFileStorageOptions + { + Folder = PathHelper.ExpandPath(path), + Serializer = s.GetRequiredService(), + TimeProvider = s.GetRequiredService(), + LoggerFactory = s.GetRequiredService() + }); + + if (!String.IsNullOrWhiteSpace(options.Scope)) + storage = new ScopedFileStorage(storage, options.Scope); + + return storage; + }); } else if (String.Equals(options.Provider, "minio")) { @@ -227,16 +235,14 @@ private static void RegisterStorage(IServiceCollection container, StorageOptions } else if (String.Equals(options.Provider, "s3")) { - container.ReplaceSingleton(s => new S3FileStorage(new S3FileStorageOptions - { - ConnectionString = options.ConnectionString, - Credentials = GetAWSCredentials(options.Data), - Region = GetAWSRegionEndpoint(options.Data), - Bucket = $"{options.ScopePrefix}{options.Data.GetString("bucket", "ex-events")}", - Serializer = s.GetRequiredService(), - TimeProvider = s.GetRequiredService(), - LoggerFactory = s.GetRequiredService() - })); + container.ReplaceSingleton(s => new S3FileStorage(o => o + .ConnectionString(options.ConnectionString) + .Credentials(GetAWSCredentials(options.Data)) + .Region(GetAWSRegionEndpoint(options.Data)) + .Bucket(options.Data.GetString("bucket", $"{options.ScopePrefix}ex-events")) + .Serializer(s.GetRequiredService()) + .TimeProvider(s.GetRequiredService()) + .LoggerFactory(s.GetRequiredService()))); } } @@ -251,7 +257,9 @@ private static IQueue CreateAzureStorageQueue(IServiceProvider container, WorkItemTimeout = workItemTimeout.GetValueOrDefault(TimeSpan.FromMinutes(5.0)), Serializer = container.GetRequiredService(), TimeProvider = container.GetRequiredService(), - LoggerFactory = container.GetRequiredService() + LoggerFactory = container.GetRequiredService(), + MetricsPollingEnabled = options.MetricsPollingEnabled, + MetricsPollingInterval = options.MetricsPollingInterval }); } @@ -267,7 +275,9 @@ private static IQueue CreateRedisQueue(IServiceProvider container, QueueOp RunMaintenanceTasks = runMaintenanceTasks, Serializer = container.GetRequiredService(), TimeProvider = container.GetRequiredService(), - LoggerFactory = container.GetRequiredService() + LoggerFactory = container.GetRequiredService(), + MetricsPollingEnabled = options.MetricsPollingEnabled, + MetricsPollingInterval = options.MetricsPollingInterval }); } @@ -295,7 +305,9 @@ private static IQueue CreateSQSQueue(IServiceProvider container, QueueOpti WorkItemTimeout = workItemTimeout.GetValueOrDefault(TimeSpan.FromMinutes(5.0)), Serializer = container.GetRequiredService(), TimeProvider = container.GetRequiredService(), - LoggerFactory = container.GetRequiredService() + LoggerFactory = container.GetRequiredService(), + MetricsPollingEnabled = options.MetricsPollingEnabled, + MetricsPollingInterval = options.MetricsPollingInterval }); } diff --git a/src/Exceptionless.Insulation/Exceptionless.Insulation.csproj b/src/Exceptionless.Insulation/Exceptionless.Insulation.csproj index 94aca8e88f..ba1c18b3c8 100644 --- a/src/Exceptionless.Insulation/Exceptionless.Insulation.csproj +++ b/src/Exceptionless.Insulation/Exceptionless.Insulation.csproj @@ -14,13 +14,13 @@ - - + + - + - + diff --git a/src/Exceptionless.Job/Exceptionless.Job.csproj b/src/Exceptionless.Job/Exceptionless.Job.csproj index 5ae38e689a..a7161b3e28 100644 --- a/src/Exceptionless.Job/Exceptionless.Job.csproj +++ b/src/Exceptionless.Job/Exceptionless.Job.csproj @@ -7,7 +7,7 @@ - + @@ -15,12 +15,12 @@ - + - + - - + + diff --git a/src/Exceptionless.Job/Program.cs b/src/Exceptionless.Job/Program.cs index 80da975ef5..2433bb65be 100644 --- a/src/Exceptionless.Job/Program.cs +++ b/src/Exceptionless.Job/Program.cs @@ -1,5 +1,6 @@ -using System.Diagnostics; +using System.Diagnostics; using Exceptionless.Core; +using Exceptionless.Core.Configuration; using Exceptionless.Core.Extensions; using Exceptionless.Core.Jobs; using Exceptionless.Core.Jobs.Elastic; @@ -49,8 +50,7 @@ public static IHostBuilder CreateHostBuilder(string[] args) .SetBasePath(Directory.GetCurrentDirectory()) .AddYamlFile("appsettings.yml", optional: true, reloadOnChange: true) .AddYamlFile($"appsettings.{environment}.yml", optional: true, reloadOnChange: true) - .AddEnvironmentVariables("EX_") - .AddEnvironmentVariables("ASPNETCORE_") + .AddCustomEnvironmentVariables() .AddCommandLine(args) .Build(); @@ -60,6 +60,9 @@ public static IHostBuilder CreateHostBuilder(string[] args) .ForContext(); var options = AppOptions.ReadFromConfiguration(config); + // only poll the queue metrics if this process is going to run the stack event count job + options.QueueOptions.MetricsPollingEnabled = jobOptions.StackEventCount; + var apmConfig = new ApmConfig(config, $"job-{jobOptions.JobName.ToLowerUnderscoredWords('-')}", options.InformationalVersion, options.CacheOptions.Provider == "redis"); Log.Information("Bootstrapping Exceptionless {JobName} job(s) in {AppMode} mode ({InformationalVersion}) on {MachineName} with options {@Options}", jobOptions.JobName ?? "All", environment, options.InformationalVersion, Environment.MachineName, options); @@ -85,13 +88,13 @@ public static IHostBuilder CreateHostBuilder(string[] args) app.UseSerilogRequestLogging(o => { o.MessageTemplate = "TraceId={TraceId} HTTP {RequestMethod} {RequestPath} responded {StatusCode} in {Elapsed:0.0000} ms"; - o.GetLevel = (context, duration, ex) => + o.GetLevel = new Func((context, duration, ex) => { if (ex is not null || context.Response.StatusCode > 499) return LogEventLevel.Error; return duration < 1000 && context.Response.StatusCode < 400 ? LogEventLevel.Debug : LogEventLevel.Information; - }; + }); }); Bootstrapper.LogConfiguration(app.ApplicationServices, options, app.ApplicationServices.GetRequiredService>()); @@ -146,27 +149,27 @@ private static void AddJobs(IServiceCollection services, JobRunnerOptions option services.AddJob(); if (options.CloseInactiveSessions) - services.AddJob(o => o.WaitForStartupActions(true)); + services.AddJob(o => o.WaitForStartupActions()); if (options.DailySummary) - services.AddJob(o => o.WaitForStartupActions(true)); + services.AddJob(o => o.WaitForStartupActions()); if (options.DataMigration) - services.AddJob(o => o.WaitForStartupActions(true)); + services.AddJob(o => o.WaitForStartupActions()); if (options is { DownloadGeoIPDatabase: true, AllJobs: true }) services.AddCronJob("0 1 * * *"); if (options is { DownloadGeoIPDatabase: true, AllJobs: false }) - services.AddJob(o => o.WaitForStartupActions(true)); + services.AddJob(o => o.WaitForStartupActions()); if (options.EventNotifications) - services.AddJob(o => o.WaitForStartupActions(true)); + services.AddJob(o => o.WaitForStartupActions()); if (options.EventPosts) - services.AddJob(o => o.WaitForStartupActions(true)); + services.AddJob(o => o.WaitForStartupActions()); if (options.EventUsage) - services.AddJob(o => o.WaitForStartupActions(true)); + services.AddJob(o => o.WaitForStartupActions()); if (options.EventUserDescriptions) - services.AddJob(o => o.WaitForStartupActions(true)); + services.AddJob(o => o.WaitForStartupActions()); if (options.MailMessage) - services.AddJob(o => o.WaitForStartupActions(true)); + services.AddJob(o => o.WaitForStartupActions()); if (options is { MaintainIndexes: true, AllJobs: true }) services.AddCronJob("10 */2 * * *"); @@ -174,14 +177,14 @@ private static void AddJobs(IServiceCollection services, JobRunnerOptions option services.AddJob(); if (options.Migration) - services.AddJob(o => o.WaitForStartupActions(true)); + services.AddJob(o => o.WaitForStartupActions()); if (options.StackStatus) - services.AddJob(o => o.WaitForStartupActions(true)); + services.AddJob(o => o.WaitForStartupActions()); if (options.StackEventCount) - services.AddJob(o => o.WaitForStartupActions(true)); + services.AddJob(o => o.WaitForStartupActions()); if (options.WebHooks) - services.AddJob(o => o.WaitForStartupActions(true)); + services.AddJob(o => o.WaitForStartupActions()); if (options.WorkItem) - services.AddJob(o => o.WaitForStartupActions(true)); + services.AddJob(o => o.WaitForStartupActions()); } } diff --git a/src/Exceptionless.Job/Properties/launchSettings.json b/src/Exceptionless.Job/Properties/launchSettings.json index 9043c97165..f6f61e578f 100644 --- a/src/Exceptionless.Job/Properties/launchSettings.json +++ b/src/Exceptionless.Job/Properties/launchSettings.json @@ -5,7 +5,7 @@ "environmentVariables": { "EX_AppMode": "Development" }, - "launchBrowser": true, + "launchBrowser": false, "applicationUrl": "https://localhost:5002;http://localhost:5003" }, "CleanupDataJob": { @@ -14,7 +14,7 @@ "environmentVariables": { "EX_AppMode": "Development" }, - "launchBrowser": true, + "launchBrowser": false, "applicationUrl": "https://localhost:5002;http://localhost:5003" }, "DataMigrationJob": { @@ -23,7 +23,7 @@ "environmentVariables": { "EX_AppMode": "Development" }, - "launchBrowser": true, + "launchBrowser": false, "applicationUrl": "https://localhost:5002;http://localhost:5003" }, "MigrationJob": { @@ -32,7 +32,7 @@ "environmentVariables": { "EX_AppMode": "Development" }, - "launchBrowser": true, + "launchBrowser": false, "applicationUrl": "https://localhost:5002;http://localhost:5003" }, "EventPostsJob": { @@ -41,7 +41,7 @@ "environmentVariables": { "EX_AppMode": "Development" }, - "launchBrowser": true, + "launchBrowser": false, "applicationUrl": "https://localhost:5002;http://localhost:5003" }, "EventUserDescriptionsJob": { @@ -50,7 +50,7 @@ "environmentVariables": { "EX_AppMode": "Development" }, - "launchBrowser": true, + "launchBrowser": false, "applicationUrl": "https://localhost:5002;http://localhost:5003" }, "EventNotificationsJob": { @@ -59,7 +59,7 @@ "environmentVariables": { "EX_AppMode": "Development" }, - "launchBrowser": true, + "launchBrowser": false, "applicationUrl": "https://localhost:5002;http://localhost:5003" }, "MailMessageJob": { @@ -68,7 +68,7 @@ "environmentVariables": { "EX_AppMode": "Development" }, - "launchBrowser": true, + "launchBrowser": false, "applicationUrl": "https://localhost:5002;http://localhost:5003" }, "WebHooksJob": { @@ -77,7 +77,7 @@ "environmentVariables": { "EX_AppMode": "Development" }, - "launchBrowser": true, + "launchBrowser": false, "applicationUrl": "https://localhost:5002;http://localhost:5003" }, "CloseInactiveSessionsJob": { @@ -86,7 +86,7 @@ "environmentVariables": { "EX_AppMode": "Development" }, - "launchBrowser": true, + "launchBrowser": false, "applicationUrl": "https://localhost:5002;http://localhost:5003" }, "DailySummaryJob": { @@ -95,7 +95,7 @@ "environmentVariables": { "EX_AppMode": "Development" }, - "launchBrowser": true, + "launchBrowser": false, "applicationUrl": "https://localhost:5002;http://localhost:5003" }, "DownloadGeoIPDatabaseJob": { @@ -104,7 +104,7 @@ "environmentVariables": { "EX_AppMode": "Development" }, - "launchBrowser": true, + "launchBrowser": false, "applicationUrl": "https://localhost:5002;http://localhost:5003" }, "WorkItemJob": { @@ -113,7 +113,7 @@ "environmentVariables": { "EX_AppMode": "Development" }, - "launchBrowser": true, + "launchBrowser": false, "applicationUrl": "https://localhost:5002;http://localhost:5003" }, "MaintainIndexesJob": { @@ -122,7 +122,7 @@ "environmentVariables": { "EX_AppMode": "Development" }, - "launchBrowser": true, + "launchBrowser": false, "applicationUrl": "https://localhost:5002;http://localhost:5003" }, "StackEventCountJob": { @@ -131,7 +131,7 @@ "environmentVariables": { "EX_AppMode": "Development" }, - "launchBrowser": true, + "launchBrowser": false, "applicationUrl": "https://localhost:5002;http://localhost:5003" }, "EventSnapshotJob": { @@ -140,8 +140,8 @@ "environmentVariables": { "EX_AppMode": "Development" }, - "launchBrowser": true, + "launchBrowser": false, "applicationUrl": "https://localhost:5002;http://localhost:5003" } } -} \ No newline at end of file +} diff --git a/src/Exceptionless.Job/appsettings.Development.yml b/src/Exceptionless.Job/appsettings.Development.yml index 438c3a12c0..bdd125522a 100644 --- a/src/Exceptionless.Job/appsettings.Development.yml +++ b/src/Exceptionless.Job/appsettings.Development.yml @@ -5,7 +5,7 @@ ConnectionStrings: # Cache: provider=redis; # MessageBus: provider=redis; # Queue: provider=redis; - Storage: provider=folder;path=..\Exceptionless.Web\storage +# Storage: provider=folder;path=..\Exceptionless.Web\storage Email: smtp://localhost:1025 # Base url for the ui used to build links in emails and other places. diff --git a/src/Exceptionless.Web/ApmExtensions.cs b/src/Exceptionless.Web/ApmExtensions.cs index 3c96da994d..ef11a669f2 100644 --- a/src/Exceptionless.Web/ApmExtensions.cs +++ b/src/Exceptionless.Web/ApmExtensions.cs @@ -36,7 +36,7 @@ public static IHostBuilder AddApm(this IHostBuilder builder, ApmConfig config) b.AddAspNetCoreInstrumentation(o => { - o.Filter = context => + o.Filter = new Func(context => { if (context.Request.Path.StartsWithSegments("/api/v2/push", StringComparison.OrdinalIgnoreCase)) return false; @@ -48,7 +48,7 @@ public static IHostBuilder AddApm(this IHostBuilder builder, ApmConfig config) return false; return true; - }; + }); }); b.AddElasticsearchClientInstrumentation(c => @@ -129,6 +129,7 @@ public static IHostBuilder AddApm(this IHostBuilder builder, ApmConfig config) b.AddHttpClientInstrumentation(); b.AddAspNetCoreInstrumentation(); b.AddMeter("Exceptionless", "Foundatio"); + b.AddMeter("System.Runtime"); b.AddRuntimeInstrumentation(); b.AddProcessInstrumentation(); diff --git a/src/Exceptionless.Web/ClientApp/.vscode/settings.json b/src/Exceptionless.Web/ClientApp/.vscode/settings.json index e32feba4c4..54c3a26f7e 100644 --- a/src/Exceptionless.Web/ClientApp/.vscode/settings.json +++ b/src/Exceptionless.Web/ClientApp/.vscode/settings.json @@ -50,7 +50,7 @@ }, "editor.formatOnSave": true, "eslint.validate": ["javascript", "svelte"], - "search.exclude": { + "files.exclude": { ".svelte-kit": true, "build": true }, diff --git a/src/Exceptionless.Web/ClientApp/README.md b/src/Exceptionless.Web/ClientApp/README.md index 141ac6a41f..4d77de7e2a 100644 --- a/src/Exceptionless.Web/ClientApp/README.md +++ b/src/Exceptionless.Web/ClientApp/README.md @@ -1,22 +1,8 @@ -# create-svelte - -Everything you need to build a Svelte project, powered by [`create-svelte`](https://github.com/sveltejs/kit/tree/main/packages/create-svelte). - -## Creating a project - -If you're seeing this, you've probably already done this step. Congrats! - -```bash -# create a new project in the current directory -npm create svelte@latest - -# create a new project in my-app -npm create svelte@latest my-app -``` +# Exceptionless User Interface ## Developing -Once you've created a project and installed dependencies with `npm install` (or `pnpm install` or `yarn`), start a development server: +Once you've created a project and installed dependencies with `npm install`, start a development server: ```bash npm run dev @@ -35,8 +21,6 @@ npm run build You can preview the production build with `npm run preview`. -> To deploy your app, you may need to install an [adapter](https://kit.svelte.dev/docs/adapters) for your target environment. - ## Upgrading components You can upgrade [shadcn-svelte components](https://www.shadcn-svelte.com/) by running the following command diff --git a/src/Exceptionless.Web/ClientApp/api-templates/class-data-contract.ejs b/src/Exceptionless.Web/ClientApp/api-templates/class-data-contract.ejs index c523cc8fee..3a193331c5 100644 --- a/src/Exceptionless.Web/ClientApp/api-templates/class-data-contract.ejs +++ b/src/Exceptionless.Web/ClientApp/api-templates/class-data-contract.ejs @@ -2,11 +2,10 @@ const { contract, utils } = it; const { formatDescription, require, _ } = utils; %> -export class <%~ contract.name %> { +class <%~ contract.name %> { <% for (const field of contract.$content) { %> <%~ includeFile('@base/object-field-jsdoc.ejs', { ...it, field }) %> <%~ includeFile('./object-field-class-validator.ejs', { ...it, field }) %> - <%~ field.name %><%~ field.isRequired || !field.nullable ? '!' : '' %><%~ field.nullable ? '?' : '' %>: <%~ field.value.replaceAll('any', 'unknown') %>; + <%~ field.name %><%~ (field.isRequired && !field.nullable) || !field.nullable || !field.nullable ? '!' : '' %><%~ field.nullable ? '?' : '' %>: <%~ field.value.replaceAll('any', 'unknown') %>; <% } %> } - diff --git a/src/Exceptionless.Web/ClientApp/api-templates/data-contracts.ejs b/src/Exceptionless.Web/ClientApp/api-templates/data-contracts.ejs index 9d568fa470..baf5a53f2e 100644 --- a/src/Exceptionless.Web/ClientApp/api-templates/data-contracts.ejs +++ b/src/Exceptionless.Web/ClientApp/api-templates/data-contracts.ejs @@ -28,7 +28,7 @@ const dataContractTemplates = { }, type: (contract) => { return `type ${contract.name}${buildGenerics(contract)} = ${contract.content}`; - }, + } } %> @@ -47,6 +47,7 @@ import { IsNumber, IsMongoId, IsUrl, + Matches, Min, MinDate, MinLength, diff --git a/src/Exceptionless.Web/ClientApp/api-templates/object-field-class-validator.ejs b/src/Exceptionless.Web/ClientApp/api-templates/object-field-class-validator.ejs index d0c3c4b22e..1dc614ec55 100644 --- a/src/Exceptionless.Web/ClientApp/api-templates/object-field-class-validator.ejs +++ b/src/Exceptionless.Web/ClientApp/api-templates/object-field-class-validator.ejs @@ -28,7 +28,7 @@ const validationDecorators = _.compact([ !_.isUndefined(field.format) && getFormatValidation(field), !_.isUndefined(field.minLength) && `@MinLength(${field.minLength}, { message: '${field.name} must be at least ${field.minLength} characters long.' })`, !_.isUndefined(field.maxLength) && `@MaxLength(${field.maxLength}, { message: '${field.name} must be at most ${field.maxLength} characters long.' })`, - !_.isUndefined(field.pattern) && `@Matches(${field.pattern}, { message: '${field.name} must match the pattern ${field.pattern}.' })`, + !_.isUndefined(field.pattern) && `@Matches(/${field.pattern}/, { message: '${field.name} must match the pattern ${field.pattern}.' })`, !_.isUndefined(field.type) && (field.type === "object" || (field.type === "array" && field.items.$ref)) && `@ValidateNested({ message: '${field.name} must be a valid nested object.' })`, ]); diff --git a/src/Exceptionless.Web/ClientApp/components.json b/src/Exceptionless.Web/ClientApp/components.json index 2e572474f8..0b6a21a8df 100644 --- a/src/Exceptionless.Web/ClientApp/components.json +++ b/src/Exceptionless.Web/ClientApp/components.json @@ -1,17 +1,17 @@ { "$schema": "https://next.shadcn-svelte.com/schema.json", - "aliases": { - "components": "$comp", - "hooks": "$lib/hooks", - "ui": "$lib/features/shared/components/ui", - "utils": "$lib/utils" - }, - "registry": "https://next.shadcn-svelte.com/registry", "style": "new-york", "tailwind": { - "baseColor": "zinc", "config": "tailwind.config.js", - "css": "src/app.css" + "css": "src/app.css", + "baseColor": "zinc" + }, + "aliases": { + "components": "$comp", + "utils": "$lib/utils", + "ui": "$comp/ui", + "hooks": "$lib/hooks" }, - "typescript": true + "typescript": true, + "registry": "https://next.shadcn-svelte.com/registry" } diff --git a/src/Exceptionless.Web/ClientApp/package-lock.json b/src/Exceptionless.Web/ClientApp/package-lock.json index 20dbd9b245..50c7ab1dee 100644 --- a/src/Exceptionless.Web/ClientApp/package-lock.json +++ b/src/Exceptionless.Web/ClientApp/package-lock.json @@ -9,58 +9,60 @@ "version": "8.0.0", "dependencies": { "@exceptionless/browser": "^3.1.0", - "@exceptionless/fetchclient": "^0.29.0", - "@iconify-json/mdi": "^1.2.1", + "@exceptionless/fetchclient": "^0.40.0", + "@iconify-json/mdi": "^1.2.3", "@tanstack/svelte-query": "https://pkg.pr.new/@tanstack/svelte-query@28f98f9", "@tanstack/svelte-query-devtools": "https://pkg.pr.new/@tanstack/svelte-query-devtools@28f98f9", "@tanstack/svelte-table": "^9.0.0-alpha.10", "@typeschema/class-validator": "^0.3.0", - "bits-ui": "^1.0.0-next.64", + "bits-ui": "^1.0.0-next.78", "class-validator": "^0.14.1", "clsx": "^2.1.1", - "formsnap": "^2.0.0-next.1", - "mode-watcher": "^0.5.0", + "formsnap": "^2.0.0", + "lucide-svelte": "^0.474.0", + "mode-watcher": "^0.5.1", "oidc-client-ts": "^3.1.0", "pretty-ms": "^9.2.0", - "runed": "^0.16.1", + "runed": "^0.23.2", "svelte-sonner": "^0.3.28", - "svelte-time": "^0.9.0", - "sveltekit-superforms": "^2.21.0", - "tailwind-merge": "^2.5.5", - "tailwind-variants": "^0.3.0", - "tailwindcss": "^3.4.16", + "svelte-time": "^2.0.0", + "sveltekit-search-params": "^4.0.0-next.0", + "sveltekit-superforms": "^2.23.1", + "tailwind-merge": "^2.6.0", + "tailwind-variants": "^0.3.1", + "tailwindcss": "^3.4.17", "tailwindcss-animate": "^1.0.7", "throttle-debounce": "^5.0.2", - "unplugin-icons": "^0.21.0" + "unplugin-icons": "^22.0.0" }, "devDependencies": { - "@iconify-json/lucide": "^1.2.17", - "@playwright/test": "^1.49.0", - "@sveltejs/adapter-static": "^3.0.6", - "@sveltejs/kit": "^2.9.0", - "@sveltejs/vite-plugin-svelte": "^5.0.1", + "@iconify-json/lucide": "^1.2.25", + "@playwright/test": "^1.50.0", + "@sveltejs/adapter-static": "^3.0.8", + "@sveltejs/kit": "^2.16.1", + "@sveltejs/vite-plugin-svelte": "^5.0.3", "@types/eslint": "^9.6.1", - "@types/node": "^22.10.1", + "@types/node": "^22.12.0", "@types/throttle-debounce": "^5.0.2", "autoprefixer": "^10.4.20", "cross-env": "^7.0.3", - "eslint": "^9.16.0", - "eslint-config-prettier": "^9.1.0", - "eslint-plugin-perfectionist": "^4.2.0", + "eslint": "^9.19.0", + "eslint-config-prettier": "^10.0.1", + "eslint-plugin-perfectionist": "^4.7.0", "eslint-plugin-svelte": "^2.46.1", "npm-run-all": "^4.1.5", - "postcss": "^8.4.49", - "prettier": "^3.4.1", - "prettier-plugin-svelte": "^3.3.2", - "prettier-plugin-tailwindcss": "^0.6.9", - "svelte": "^5.5.3", - "svelte-check": "^4.1.1", + "postcss": "^8.5.1", + "prettier": "^3.4.2", + "prettier-plugin-svelte": "^3.3.3", + "prettier-plugin-tailwindcss": "^0.6.11", + "svelte": "^5.19.6", + "svelte-check": "^4.1.4", "swagger-typescript-api": "^13.0.23", "tslib": "^2.8.1", - "typescript": "^5.7.2", - "typescript-eslint": "^8.17.0", - "vite": "^6.0.2", - "vitest": "2.1.6" + "typescript": "^5.7.3", + "typescript-eslint": "^8.22.0", + "vite": "^6.0.11", + "vitest": "3.0.4" } }, "node_modules/@alloc/quick-lru": { @@ -111,19 +113,19 @@ } }, "node_modules/@ark/schema": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@ark/schema/-/schema-0.25.0.tgz", - "integrity": "sha512-1Air2M9Je8C/4+YNhJ1QPkoFbERX3PhulDVNW1RmpOpyUjUSM5lcuuyq357jp3a7+M3a5RV2PNdI1XZ/ah8l8Q==", + "version": "0.35.0", + "resolved": "https://registry.npmjs.org/@ark/schema/-/schema-0.35.0.tgz", + "integrity": "sha512-cV3tiYpIFIt4JB4ZrXSUJ6v2nnAzbX5DqaNWRMpk4pVQV15HMEawg6wYn+JKEI16vfRCIcGqxxdGBvE0zIxqKw==", "license": "MIT", "optional": true, "dependencies": { - "@ark/util": "0.25.0" + "@ark/util": "0.35.0" } }, "node_modules/@ark/util": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/@ark/util/-/util-0.25.0.tgz", - "integrity": "sha512-yo2Me+tYnmr6E0E3maZzu643/rL07oR25yBHkH24gllssqYcd6EPCvZE23GEKgbk0iac9J73GlJ9pkgZj43Q2g==", + "version": "0.35.0", + "resolved": "https://registry.npmjs.org/@ark/util/-/util-0.35.0.tgz", + "integrity": "sha512-KxhwMOGMoV605zcqoBVyNFEtKAc3PKy95IWmVz9dwuUzIQ+sh7Bk9KEnlGPXcPAD8DCDs/10deHeO1kfWWONvg==", "license": "MIT", "optional": true }, @@ -165,23 +167,10 @@ "node": ">=6.9.0" } }, - "node_modules/@effect/schema": { - "version": "0.75.5", - "resolved": "https://registry.npmjs.org/@effect/schema/-/schema-0.75.5.tgz", - "integrity": "sha512-TQInulTVCuF+9EIbJpyLP6dvxbQJMphrnRqgexm/Ze39rSjfhJuufF7XvU3SxTgg3HnL7B/kpORTJbHhlE6thw==", - "license": "MIT", - "optional": true, - "dependencies": { - "fast-check": "^3.21.0" - }, - "peerDependencies": { - "effect": "^3.9.2" - } - }, "node_modules/@esbuild/aix-ppc64": { - "version": "0.24.0", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.24.0.tgz", - "integrity": "sha512-WtKdFM7ls47zkKHFVzMz8opM7LkcsIp9amDUBIAWirg70RM71WRSjdILPsY5Uv1D42ZpUfaPILDlfactHgsRkw==", + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.24.2.tgz", + "integrity": "sha512-thpVCb/rhxE/BnMLQ7GReQLLN8q9qbHmI55F4489/ByVg2aQaQ6kbcLb6FHkocZzQhxc4gx0sCk0tJkKBFzDhA==", "cpu": [ "ppc64" ], @@ -195,9 +184,9 @@ } }, "node_modules/@esbuild/android-arm": { - "version": "0.24.0", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.24.0.tgz", - "integrity": "sha512-arAtTPo76fJ/ICkXWetLCc9EwEHKaeya4vMrReVlEIUCAUncH7M4bhMQ+M9Vf+FFOZJdTNMXNBrWwW+OXWpSew==", + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.24.2.tgz", + "integrity": "sha512-tmwl4hJkCfNHwFB3nBa8z1Uy3ypZpxqxfTQOcHX+xRByyYgunVbZ9MzUUfb0RxaHIMnbHagwAxuTL+tnNM+1/Q==", "cpu": [ "arm" ], @@ -211,9 +200,9 @@ } }, "node_modules/@esbuild/android-arm64": { - "version": "0.24.0", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.24.0.tgz", - "integrity": "sha512-Vsm497xFM7tTIPYK9bNTYJyF/lsP590Qc1WxJdlB6ljCbdZKU9SY8i7+Iin4kyhV/KV5J2rOKsBQbB77Ab7L/w==", + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.24.2.tgz", + "integrity": "sha512-cNLgeqCqV8WxfcTIOeL4OAtSmL8JjcN6m09XIgro1Wi7cF4t/THaWEa7eL5CMoMBdjoHOTh/vwTO/o2TRXIyzg==", "cpu": [ "arm64" ], @@ -227,9 +216,9 @@ } }, "node_modules/@esbuild/android-x64": { - "version": "0.24.0", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.24.0.tgz", - "integrity": "sha512-t8GrvnFkiIY7pa7mMgJd7p8p8qqYIz1NYiAoKc75Zyv73L3DZW++oYMSHPRarcotTKuSs6m3hTOa5CKHaS02TQ==", + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.24.2.tgz", + "integrity": "sha512-B6Q0YQDqMx9D7rvIcsXfmJfvUYLoP722bgfBlO5cGvNVb5V/+Y7nhBE3mHV9OpxBf4eAS2S68KZztiPaWq4XYw==", "cpu": [ "x64" ], @@ -243,9 +232,9 @@ } }, "node_modules/@esbuild/darwin-arm64": { - "version": "0.24.0", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.24.0.tgz", - "integrity": "sha512-CKyDpRbK1hXwv79soeTJNHb5EiG6ct3efd/FTPdzOWdbZZfGhpbcqIpiD0+vwmpu0wTIL97ZRPZu8vUt46nBSw==", + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.24.2.tgz", + "integrity": "sha512-kj3AnYWc+CekmZnS5IPu9D+HWtUI49hbnyqk0FLEJDbzCIQt7hg7ucF1SQAilhtYpIujfaHr6O0UHlzzSPdOeA==", "cpu": [ "arm64" ], @@ -259,9 +248,9 @@ } }, "node_modules/@esbuild/darwin-x64": { - "version": "0.24.0", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.24.0.tgz", - "integrity": "sha512-rgtz6flkVkh58od4PwTRqxbKH9cOjaXCMZgWD905JOzjFKW+7EiUObfd/Kav+A6Gyud6WZk9w+xu6QLytdi2OA==", + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.24.2.tgz", + "integrity": "sha512-WeSrmwwHaPkNR5H3yYfowhZcbriGqooyu3zI/3GGpF8AyUdsrrP0X6KumITGA9WOyiJavnGZUwPGvxvwfWPHIA==", "cpu": [ "x64" ], @@ -275,9 +264,9 @@ } }, "node_modules/@esbuild/freebsd-arm64": { - "version": "0.24.0", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.24.0.tgz", - "integrity": "sha512-6Mtdq5nHggwfDNLAHkPlyLBpE5L6hwsuXZX8XNmHno9JuL2+bg2BX5tRkwjyfn6sKbxZTq68suOjgWqCicvPXA==", + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.24.2.tgz", + "integrity": "sha512-UN8HXjtJ0k/Mj6a9+5u6+2eZ2ERD7Edt1Q9IZiB5UZAIdPnVKDoG7mdTVGhHJIeEml60JteamR3qhsr1r8gXvg==", "cpu": [ "arm64" ], @@ -291,9 +280,9 @@ } }, "node_modules/@esbuild/freebsd-x64": { - "version": "0.24.0", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.24.0.tgz", - "integrity": "sha512-D3H+xh3/zphoX8ck4S2RxKR6gHlHDXXzOf6f/9dbFt/NRBDIE33+cVa49Kil4WUjxMGW0ZIYBYtaGCa2+OsQwQ==", + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.24.2.tgz", + "integrity": "sha512-TvW7wE/89PYW+IevEJXZ5sF6gJRDY/14hyIGFXdIucxCsbRmLUcjseQu1SyTko+2idmCw94TgyaEZi9HUSOe3Q==", "cpu": [ "x64" ], @@ -307,9 +296,9 @@ } }, "node_modules/@esbuild/linux-arm": { - "version": "0.24.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.24.0.tgz", - "integrity": "sha512-gJKIi2IjRo5G6Glxb8d3DzYXlxdEj2NlkixPsqePSZMhLudqPhtZ4BUrpIuTjJYXxvF9njql+vRjB2oaC9XpBw==", + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.24.2.tgz", + "integrity": "sha512-n0WRM/gWIdU29J57hJyUdIsk0WarGd6To0s+Y+LwvlC55wt+GT/OgkwoXCXvIue1i1sSNWblHEig00GBWiJgfA==", "cpu": [ "arm" ], @@ -323,9 +312,9 @@ } }, "node_modules/@esbuild/linux-arm64": { - "version": "0.24.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.24.0.tgz", - "integrity": "sha512-TDijPXTOeE3eaMkRYpcy3LarIg13dS9wWHRdwYRnzlwlA370rNdZqbcp0WTyyV/k2zSxfko52+C7jU5F9Tfj1g==", + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.24.2.tgz", + "integrity": "sha512-7HnAD6074BW43YvvUmE/35Id9/NB7BeX5EoNkK9obndmZBUk8xmJJeU7DwmUeN7tkysslb2eSl6CTrYz6oEMQg==", "cpu": [ "arm64" ], @@ -339,9 +328,9 @@ } }, "node_modules/@esbuild/linux-ia32": { - "version": "0.24.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.24.0.tgz", - "integrity": "sha512-K40ip1LAcA0byL05TbCQ4yJ4swvnbzHscRmUilrmP9Am7//0UjPreh4lpYzvThT2Quw66MhjG//20mrufm40mA==", + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.24.2.tgz", + "integrity": "sha512-sfv0tGPQhcZOgTKO3oBE9xpHuUqguHvSo4jl+wjnKwFpapx+vUDcawbwPNuBIAYdRAvIDBfZVvXprIj3HA+Ugw==", "cpu": [ "ia32" ], @@ -355,9 +344,9 @@ } }, "node_modules/@esbuild/linux-loong64": { - "version": "0.24.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.24.0.tgz", - "integrity": "sha512-0mswrYP/9ai+CU0BzBfPMZ8RVm3RGAN/lmOMgW4aFUSOQBjA31UP8Mr6DDhWSuMwj7jaWOT0p0WoZ6jeHhrD7g==", + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.24.2.tgz", + "integrity": "sha512-CN9AZr8kEndGooS35ntToZLTQLHEjtVB5n7dl8ZcTZMonJ7CCfStrYhrzF97eAecqVbVJ7APOEe18RPI4KLhwQ==", "cpu": [ "loong64" ], @@ -371,9 +360,9 @@ } }, "node_modules/@esbuild/linux-mips64el": { - "version": "0.24.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.24.0.tgz", - "integrity": "sha512-hIKvXm0/3w/5+RDtCJeXqMZGkI2s4oMUGj3/jM0QzhgIASWrGO5/RlzAzm5nNh/awHE0A19h/CvHQe6FaBNrRA==", + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.24.2.tgz", + "integrity": "sha512-iMkk7qr/wl3exJATwkISxI7kTcmHKE+BlymIAbHO8xanq/TjHaaVThFF6ipWzPHryoFsesNQJPE/3wFJw4+huw==", "cpu": [ "mips64el" ], @@ -387,9 +376,9 @@ } }, "node_modules/@esbuild/linux-ppc64": { - "version": "0.24.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.24.0.tgz", - "integrity": "sha512-HcZh5BNq0aC52UoocJxaKORfFODWXZxtBaaZNuN3PUX3MoDsChsZqopzi5UupRhPHSEHotoiptqikjN/B77mYQ==", + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.24.2.tgz", + "integrity": "sha512-shsVrgCZ57Vr2L8mm39kO5PPIb+843FStGt7sGGoqiiWYconSxwTiuswC1VJZLCjNiMLAMh34jg4VSEQb+iEbw==", "cpu": [ "ppc64" ], @@ -403,9 +392,9 @@ } }, "node_modules/@esbuild/linux-riscv64": { - "version": "0.24.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.24.0.tgz", - "integrity": "sha512-bEh7dMn/h3QxeR2KTy1DUszQjUrIHPZKyO6aN1X4BCnhfYhuQqedHaa5MxSQA/06j3GpiIlFGSsy1c7Gf9padw==", + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.24.2.tgz", + "integrity": "sha512-4eSFWnU9Hhd68fW16GD0TINewo1L6dRrB+oLNNbYyMUAeOD2yCK5KXGK1GH4qD/kT+bTEXjsyTCiJGHPZ3eM9Q==", "cpu": [ "riscv64" ], @@ -419,9 +408,9 @@ } }, "node_modules/@esbuild/linux-s390x": { - "version": "0.24.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.24.0.tgz", - "integrity": "sha512-ZcQ6+qRkw1UcZGPyrCiHHkmBaj9SiCD8Oqd556HldP+QlpUIe2Wgn3ehQGVoPOvZvtHm8HPx+bH20c9pvbkX3g==", + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.24.2.tgz", + "integrity": "sha512-S0Bh0A53b0YHL2XEXC20bHLuGMOhFDO6GN4b3YjRLK//Ep3ql3erpNcPlEFed93hsQAjAQDNsvcK+hV90FubSw==", "cpu": [ "s390x" ], @@ -435,9 +424,9 @@ } }, "node_modules/@esbuild/linux-x64": { - "version": "0.24.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.24.0.tgz", - "integrity": "sha512-vbutsFqQ+foy3wSSbmjBXXIJ6PL3scghJoM8zCL142cGaZKAdCZHyf+Bpu/MmX9zT9Q0zFBVKb36Ma5Fzfa8xA==", + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.24.2.tgz", + "integrity": "sha512-8Qi4nQcCTbLnK9WoMjdC9NiTG6/E38RNICU6sUNqK0QFxCYgoARqVqxdFmWkdonVsvGqWhmm7MO0jyTqLqwj0Q==", "cpu": [ "x64" ], @@ -450,10 +439,26 @@ "node": ">=18" } }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.24.2.tgz", + "integrity": "sha512-wuLK/VztRRpMt9zyHSazyCVdCXlpHkKm34WUyinD2lzK07FAHTq0KQvZZlXikNWkDGoT6x3TD51jKQ7gMVpopw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, "node_modules/@esbuild/netbsd-x64": { - "version": "0.24.0", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.24.0.tgz", - "integrity": "sha512-hjQ0R/ulkO8fCYFsG0FZoH+pWgTTDreqpqY7UnQntnaKv95uP5iW3+dChxnx7C3trQQU40S+OgWhUVwCjVFLvg==", + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.24.2.tgz", + "integrity": "sha512-VefFaQUc4FMmJuAxmIHgUmfNiLXY438XrL4GDNV1Y1H/RW3qow68xTwjZKfj/+Plp9NANmzbH5R40Meudu8mmw==", "cpu": [ "x64" ], @@ -467,9 +472,9 @@ } }, "node_modules/@esbuild/openbsd-arm64": { - "version": "0.24.0", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.24.0.tgz", - "integrity": "sha512-MD9uzzkPQbYehwcN583yx3Tu5M8EIoTD+tUgKF982WYL9Pf5rKy9ltgD0eUgs8pvKnmizxjXZyLt0z6DC3rRXg==", + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.24.2.tgz", + "integrity": "sha512-YQbi46SBct6iKnszhSvdluqDmxCJA+Pu280Av9WICNwQmMxV7nLRHZfjQzwbPs3jeWnuAhE9Jy0NrnJ12Oz+0A==", "cpu": [ "arm64" ], @@ -483,9 +488,9 @@ } }, "node_modules/@esbuild/openbsd-x64": { - "version": "0.24.0", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.24.0.tgz", - "integrity": "sha512-4ir0aY1NGUhIC1hdoCzr1+5b43mw99uNwVzhIq1OY3QcEwPDO3B7WNXBzaKY5Nsf1+N11i1eOfFcq+D/gOS15Q==", + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.24.2.tgz", + "integrity": "sha512-+iDS6zpNM6EnJyWv0bMGLWSWeXGN/HTaF/LXHXHwejGsVi+ooqDfMCCTerNFxEkM3wYVcExkeGXNqshc9iMaOA==", "cpu": [ "x64" ], @@ -499,9 +504,9 @@ } }, "node_modules/@esbuild/sunos-x64": { - "version": "0.24.0", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.24.0.tgz", - "integrity": "sha512-jVzdzsbM5xrotH+W5f1s+JtUy1UWgjU0Cf4wMvffTB8m6wP5/kx0KiaLHlbJO+dMgtxKV8RQ/JvtlFcdZ1zCPA==", + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.24.2.tgz", + "integrity": "sha512-hTdsW27jcktEvpwNHJU4ZwWFGkz2zRJUz8pvddmXPtXDzVKTTINmlmga3ZzwcuMpUvLw7JkLy9QLKyGpD2Yxig==", "cpu": [ "x64" ], @@ -515,9 +520,9 @@ } }, "node_modules/@esbuild/win32-arm64": { - "version": "0.24.0", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.24.0.tgz", - "integrity": "sha512-iKc8GAslzRpBytO2/aN3d2yb2z8XTVfNV0PjGlCxKo5SgWmNXx82I/Q3aG1tFfS+A2igVCY97TJ8tnYwpUWLCA==", + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.24.2.tgz", + "integrity": "sha512-LihEQ2BBKVFLOC9ZItT9iFprsE9tqjDjnbulhHoFxYQtQfai7qfluVODIYxt1PgdoyQkz23+01rzwNwYfutxUQ==", "cpu": [ "arm64" ], @@ -531,9 +536,9 @@ } }, "node_modules/@esbuild/win32-ia32": { - "version": "0.24.0", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.24.0.tgz", - "integrity": "sha512-vQW36KZolfIudCcTnaTpmLQ24Ha1RjygBo39/aLkM2kmjkWmZGEJ5Gn9l5/7tzXA42QGIoWbICfg6KLLkIw6yw==", + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.24.2.tgz", + "integrity": "sha512-q+iGUwfs8tncmFC9pcnD5IvRHAzmbwQ3GPS5/ceCyHdjXubwQWI12MKWSNSMYLJMq23/IUCvJMS76PDqXe1fxA==", "cpu": [ "ia32" ], @@ -547,9 +552,9 @@ } }, "node_modules/@esbuild/win32-x64": { - "version": "0.24.0", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.24.0.tgz", - "integrity": "sha512-7IAFPrjSQIJrGsK6flwg7NFmwBoSTyF3rl7If0hNUFQU4ilTsEPL6GuMuU9BfIWVVGuRnuIidkSMC+c0Otu8IA==", + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.24.2.tgz", + "integrity": "sha512-7VTgWzgMGvup6aSqDPLiW5zHaxYJGTO4OokMjIlrCtf+VpEL+cXKtCvg723iguPYI5oaUNdS+/V7OU2gvXVWEg==", "cpu": [ "x64" ], @@ -620,11 +625,14 @@ } }, "node_modules/@eslint/core": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.9.0.tgz", - "integrity": "sha512-7ATR9F0e4W85D/0w7cU0SNj7qkAexMG+bAHEZOjo9akvGuhHE2m7umzWzfnpa0XAg5Kxc1BWmtPMV67jJ+9VUg==", + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.10.0.tgz", + "integrity": "sha512-gFHJ+xBOo4G3WRlR1e/3G8A6/KZAH6zcE/hkLRCZTi/B9avAG365QhFA8uOGzTMqgTghpn7/fSnscW++dpMSAw==", "dev": true, "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } @@ -654,9 +662,9 @@ } }, "node_modules/@eslint/js": { - "version": "9.16.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.16.0.tgz", - "integrity": "sha512-tw2HxzQkrbeuvyj1tG2Yqq+0H9wGoI2IMk4EOsQeX+vmd75FtJAzf+gTA69WF+baUKRYQ3x2kbLE08js5OsTVg==", + "version": "9.19.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.19.0.tgz", + "integrity": "sha512-rbq9/g38qjfqFLOVPvwjIvFFdNziEC5S65jmjPw5r6A//QH+W91akh9irMwjDN8zKUTak6W9EsAv4m/7Wnw0UQ==", "dev": true, "license": "MIT", "engines": { @@ -674,12 +682,13 @@ } }, "node_modules/@eslint/plugin-kit": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.2.3.tgz", - "integrity": "sha512-2b/g5hRmpbb1o4GnTZax9N9m0FXzz9OV42ZzI4rDDMDuHUqigAiQCEWChBWCY4ztAGVRjoWT19v0yMmc5/L5kA==", + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.2.5.tgz", + "integrity": "sha512-lB05FkqEdUg2AA0xEbUz0SnkXT1LcCTa438W4IWTUh4hdOnVbQyOJ81OrDXsJk/LSiJHubgGEFoR5EHq1NsH1A==", "dev": true, "license": "Apache-2.0", "dependencies": { + "@eslint/core": "^0.10.0", "levn": "^0.4.1" }, "engines": { @@ -703,9 +712,9 @@ "license": "Apache-2.0" }, "node_modules/@exceptionless/fetchclient": { - "version": "0.29.0", - "resolved": "https://registry.npmjs.org/@exceptionless/fetchclient/-/fetchclient-0.29.0.tgz", - "integrity": "sha512-ZwfPIgWda22rwhzk06Am67UWh8h/8oIw0/36LDs0MmxwVrLK+kprUVuZ2TFOvGWPRm+vs8AzBS4X9VzzKSwLLg==", + "version": "0.40.0", + "resolved": "https://registry.npmjs.org/@exceptionless/fetchclient/-/fetchclient-0.40.0.tgz", + "integrity": "sha512-hWFiSkLSW03YGdQOSSL6EhH/MyI+vF9d+MkOxkFbrE5hQP+/517q9lqrh8yTfIfJiJX9CvtY7MzZ7/p8ieTYLA==", "license": "Apache-2.0" }, "node_modules/@exodus/schemasafe": { @@ -848,9 +857,9 @@ } }, "node_modules/@iconify-json/lucide": { - "version": "1.2.17", - "resolved": "https://registry.npmjs.org/@iconify-json/lucide/-/lucide-1.2.17.tgz", - "integrity": "sha512-y+4P1DxD2h4d4fGYxikUdMf0o21DD0GIE/YIgixEBIXKbE90LTOFqmoxkGyPpaGk3vT2qE2w/28+sdmBMFsd5w==", + "version": "1.2.25", + "resolved": "https://registry.npmjs.org/@iconify-json/lucide/-/lucide-1.2.25.tgz", + "integrity": "sha512-OsHihLfqdjxwoLWDEnGesrYbCG6VCCoLvmb1U6UJhggsppW+D3DUY/LhhcppZM4Yw3AM4USDIn/gkzqjdRYgEw==", "dev": true, "license": "ISC", "dependencies": { @@ -858,9 +867,9 @@ } }, "node_modules/@iconify-json/mdi": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@iconify-json/mdi/-/mdi-1.2.1.tgz", - "integrity": "sha512-dSkQU78gsZV6Yxnq78+LuX7jzeFC/5NAmz7O3rh558GimGFcwMVY/OtqRowIzjqJBmMmWZft7wkFV4TrwRXjlg==", + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@iconify-json/mdi/-/mdi-1.2.3.tgz", + "integrity": "sha512-O3cLwbDOK7NNDf2ihaQOH5F9JglnulNDFV7WprU2dSoZu3h3cWH//h74uQAB87brHmvFVxIOkuBX2sZSzYhScg==", "license": "Apache-2.0", "dependencies": { "@iconify/types": "*" @@ -873,18 +882,19 @@ "license": "MIT" }, "node_modules/@iconify/utils": { - "version": "2.1.33", - "resolved": "https://registry.npmjs.org/@iconify/utils/-/utils-2.1.33.tgz", - "integrity": "sha512-jP9h6v/g0BIZx0p7XGJJVtkVnydtbgTgt9mVNcGDYwaa7UhdHdI9dvoq+gKj9sijMSJKxUPEG2JyjsgXjxL7Kw==", + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/@iconify/utils/-/utils-2.2.1.tgz", + "integrity": "sha512-0/7J7hk4PqXmxo5PDBDxmnecw5PxklZJfNjIVG9FM0mEfVrvfudS22rYWsqVk6gR3UJ/mSYS90X4R3znXnqfNA==", "license": "MIT", "dependencies": { - "@antfu/install-pkg": "^0.4.0", + "@antfu/install-pkg": "^0.4.1", "@antfu/utils": "^0.7.10", "@iconify/types": "^2.0.0", - "debug": "^4.3.6", + "debug": "^4.4.0", + "globals": "^15.13.0", "kolorist": "^1.8.0", - "local-pkg": "^0.5.0", - "mlly": "^1.7.1" + "local-pkg": "^0.5.1", + "mlly": "^1.7.3" } }, "node_modules/@iconify/utils/node_modules/@antfu/install-pkg": { @@ -900,6 +910,18 @@ "url": "https://github.com/sponsors/antfu" } }, + "node_modules/@iconify/utils/node_modules/globals": { + "version": "15.14.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-15.14.0.tgz", + "integrity": "sha512-OkToC372DtlQeje9/zHIo5CT8lRP/FUgEOKBEhU4e0abL7J7CD24fD9ohiLN5hagG/kWCYj4K5oaxxtj2Z0Dig==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/@internationalized/date": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/@internationalized/date/-/date-3.6.0.tgz", @@ -1020,13 +1042,13 @@ } }, "node_modules/@playwright/test": { - "version": "1.49.0", - "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.49.0.tgz", - "integrity": "sha512-DMulbwQURa8rNIQrf94+jPJQ4FmOVdpE5ZppRNvWVjvhC+6sOeo28r8MgIpQRYouXRtt/FCCXU7zn20jnHR4Qw==", + "version": "1.50.0", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.50.0.tgz", + "integrity": "sha512-ZGNXbt+d65EGjBORQHuYKj+XhCewlwpnSd/EDuLPZGSiEWmgOJB5RmMCCYGy5aMfTs9wx61RivfDKi8H/hcMvw==", "dev": true, "license": "Apache-2.0", "dependencies": { - "playwright": "1.49.0" + "playwright": "1.50.0" }, "bin": { "playwright": "cli.js" @@ -1042,9 +1064,9 @@ "license": "MIT" }, "node_modules/@poppinss/macroable": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@poppinss/macroable/-/macroable-1.0.3.tgz", - "integrity": "sha512-B4iV6QxW//Fn17+qF1EMZRmoThIUJlCtcO85yoRDJnMyHeAthjz4ig9OTkfGGXKtQhcdPX0me75gU5K9J897+w==", + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@poppinss/macroable/-/macroable-1.0.4.tgz", + "integrity": "sha512-ct43jurbe7lsUX5eIrj4ijO3j/6zIPp7CDnFWXDs7UPAbw1Pu1iH3oAmFdP4jcskKJBURH5M9oTtyeiUXyHX8Q==", "license": "MIT", "optional": true, "engines": { @@ -1310,16 +1332,16 @@ "optional": true }, "node_modules/@sinclair/typebox": { - "version": "0.34.9", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.9.tgz", - "integrity": "sha512-KTuEZ4UHIp8rNgbLrsQwmGo4cCVj/AHPG3DsI1VvfzudG8dzpZNCV4qm4NWfYY02ReB5INVyuq6xGrl3Ks8vAQ==", + "version": "0.34.14", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.14.tgz", + "integrity": "sha512-TJ7Al17j3+by5y2QkTLcF/oBVMbgXBhILVgi9PuwpxQVZZvGh5BFRzWbJPmZVNKpbRLjuMzFuRwR+tdFPqCkvA==", "license": "MIT", "optional": true }, "node_modules/@sveltejs/adapter-static": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/@sveltejs/adapter-static/-/adapter-static-3.0.6.tgz", - "integrity": "sha512-MGJcesnJWj7FxDcB/GbrdYD3q24Uk0PIL4QIX149ku+hlJuj//nxUbb0HxUTpjkecWfHjVveSUnUaQWnPRXlpg==", + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@sveltejs/adapter-static/-/adapter-static-3.0.8.tgz", + "integrity": "sha512-YaDrquRpZwfcXbnlDsSrBQNCChVOT9MGuSg+dMAyfsAa1SmiAhrA5jUYUiIMC59G92kIbY/AaQOWcBdq+lh+zg==", "dev": true, "license": "MIT", "peerDependencies": { @@ -1327,24 +1349,22 @@ } }, "node_modules/@sveltejs/kit": { - "version": "2.9.0", - "resolved": "https://registry.npmjs.org/@sveltejs/kit/-/kit-2.9.0.tgz", - "integrity": "sha512-W3E7ed3ChB6kPqRs2H7tcHp+Z7oiTFC6m+lLyAQQuyXeqw6LdNuuwEUla+5VM0OGgqQD+cYD6+7Xq80vVm17Vg==", - "hasInstallScript": true, + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/@sveltejs/kit/-/kit-2.16.1.tgz", + "integrity": "sha512-2pF5sgGJx9brYZ/9nNDYnh5KX0JguPF14dnvvtf/MqrvlWrDj/e7Rk3LBJPecFLLK1GRs6ZniD24gFPqZm/NFw==", "license": "MIT", "dependencies": { "@types/cookie": "^0.6.0", "cookie": "^0.6.0", "devalue": "^5.1.0", - "esm-env": "^1.2.1", + "esm-env": "^1.2.2", "import-meta-resolve": "^4.1.0", "kleur": "^4.1.5", "magic-string": "^0.30.5", "mrmime": "^2.0.0", "sade": "^1.8.1", "set-cookie-parser": "^2.6.0", - "sirv": "^3.0.0", - "tiny-glob": "^0.2.9" + "sirv": "^3.0.0" }, "bin": { "svelte-kit": "svelte-kit.js" @@ -1359,17 +1379,17 @@ } }, "node_modules/@sveltejs/vite-plugin-svelte": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte/-/vite-plugin-svelte-5.0.1.tgz", - "integrity": "sha512-D5l5+STmywGoLST07T9mrqqFFU+xgv5fqyTWM+VbxTvQ6jujNn4h3lQNCvlwVYs4Erov8i0K5Rwr3LQtmBYmBw==", + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte/-/vite-plugin-svelte-5.0.3.tgz", + "integrity": "sha512-MCFS6CrQDu1yGwspm4qtli0e63vaPCehf6V7pIMP15AsWgMKrqDGCPFF/0kn4SP0ii4aySu4Pa62+fIRGFMjgw==", "license": "MIT", "dependencies": { - "@sveltejs/vite-plugin-svelte-inspector": "^4.0.0", - "debug": "^4.3.7", + "@sveltejs/vite-plugin-svelte-inspector": "^4.0.1", + "debug": "^4.4.0", "deepmerge": "^4.3.1", "kleur": "^4.1.5", - "magic-string": "^0.30.13", - "vitefu": "^1.0.3" + "magic-string": "^0.30.15", + "vitefu": "^1.0.4" }, "engines": { "node": "^18.0.0 || ^20.0.0 || >=22" @@ -1522,9 +1542,9 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "22.10.1", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.1.tgz", - "integrity": "sha512-qKgsUwfHZV2WCWLAnVP1JqnpE6Im6h3Y0+fYgMTasNQ7V++CBX5OT1as0g0f+OyubbFqhf6XVNIsmN4IIhEgGQ==", + "version": "22.12.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.12.0.tgz", + "integrity": "sha512-Fll2FZ1riMjNmlmJOdAyY5pUbkftXslB5DgEzlIuNaiWhXd00FhWxVC/r4yV/4wBb9JfImTu+jiSvXTkJ7F/gA==", "devOptional": true, "license": "MIT", "dependencies": { @@ -1583,21 +1603,21 @@ } }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.17.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.17.0.tgz", - "integrity": "sha512-HU1KAdW3Tt8zQkdvNoIijfWDMvdSweFYm4hWh+KwhPstv+sCmWb89hCIP8msFm9N1R/ooh9honpSuvqKWlYy3w==", + "version": "8.22.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.22.0.tgz", + "integrity": "sha512-4Uta6REnz/xEJMvwf72wdUnC3rr4jAQf5jnTkeRQ9b6soxLxhDEbS/pfMPoJLDfFPNVRdryqWUIV/2GZzDJFZw==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/regexpp": "^4.10.0", - "@typescript-eslint/scope-manager": "8.17.0", - "@typescript-eslint/type-utils": "8.17.0", - "@typescript-eslint/utils": "8.17.0", - "@typescript-eslint/visitor-keys": "8.17.0", + "@typescript-eslint/scope-manager": "8.22.0", + "@typescript-eslint/type-utils": "8.22.0", + "@typescript-eslint/utils": "8.22.0", + "@typescript-eslint/visitor-keys": "8.22.0", "graphemer": "^1.4.0", "ignore": "^5.3.1", "natural-compare": "^1.4.0", - "ts-api-utils": "^1.3.0" + "ts-api-utils": "^2.0.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1608,25 +1628,21 @@ }, "peerDependencies": { "@typescript-eslint/parser": "^8.0.0 || ^8.0.0-alpha.0", - "eslint": "^8.57.0 || ^9.0.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <5.8.0" } }, "node_modules/@typescript-eslint/parser": { - "version": "8.17.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.17.0.tgz", - "integrity": "sha512-Drp39TXuUlD49F7ilHHCG7TTg8IkA+hxCuULdmzWYICxGXvDXmDmWEjJYZQYgf6l/TFfYNE167m7isnc3xlIEg==", + "version": "8.22.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.22.0.tgz", + "integrity": "sha512-MqtmbdNEdoNxTPzpWiWnqNac54h8JDAmkWtJExBVVnSrSmi9z+sZUt0LfKqk9rjqmKOIeRhO4fHHJ1nQIjduIQ==", "dev": true, - "license": "BSD-2-Clause", + "license": "MIT", "dependencies": { - "@typescript-eslint/scope-manager": "8.17.0", - "@typescript-eslint/types": "8.17.0", - "@typescript-eslint/typescript-estree": "8.17.0", - "@typescript-eslint/visitor-keys": "8.17.0", + "@typescript-eslint/scope-manager": "8.22.0", + "@typescript-eslint/types": "8.22.0", + "@typescript-eslint/typescript-estree": "8.22.0", + "@typescript-eslint/visitor-keys": "8.22.0", "debug": "^4.3.4" }, "engines": { @@ -1637,23 +1653,19 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <5.8.0" } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "8.17.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.17.0.tgz", - "integrity": "sha512-/ewp4XjvnxaREtqsZjF4Mfn078RD/9GmiEAtTeLQ7yFdKnqwTOgRMSvFz4et9U5RiJQ15WTGXPLj89zGusvxBg==", + "version": "8.22.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.22.0.tgz", + "integrity": "sha512-/lwVV0UYgkj7wPSw0o8URy6YI64QmcOdwHuGuxWIYznO6d45ER0wXUbksr9pYdViAofpUCNJx/tAzNukgvaaiQ==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.17.0", - "@typescript-eslint/visitor-keys": "8.17.0" + "@typescript-eslint/types": "8.22.0", + "@typescript-eslint/visitor-keys": "8.22.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1664,16 +1676,16 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "8.17.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.17.0.tgz", - "integrity": "sha512-q38llWJYPd63rRnJ6wY/ZQqIzPrBCkPdpIsaCfkR3Q4t3p6sb422zougfad4TFW9+ElIFLVDzWGiGAfbb/v2qw==", + "version": "8.22.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.22.0.tgz", + "integrity": "sha512-NzE3aB62fDEaGjaAYZE4LH7I1MUwHooQ98Byq0G0y3kkibPJQIXVUspzlFOmOfHhiDLwKzMlWxaNv+/qcZurJA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/typescript-estree": "8.17.0", - "@typescript-eslint/utils": "8.17.0", + "@typescript-eslint/typescript-estree": "8.22.0", + "@typescript-eslint/utils": "8.22.0", "debug": "^4.3.4", - "ts-api-utils": "^1.3.0" + "ts-api-utils": "^2.0.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1683,18 +1695,14 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <5.8.0" } }, "node_modules/@typescript-eslint/types": { - "version": "8.17.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.17.0.tgz", - "integrity": "sha512-gY2TVzeve3z6crqh2Ic7Cr+CAv6pfb0Egee7J5UAVWCpVvDI/F71wNfolIim4FE6hT15EbpZFVUj9j5i38jYXA==", + "version": "8.22.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.22.0.tgz", + "integrity": "sha512-0S4M4baNzp612zwpD4YOieP3VowOARgK2EkN/GBn95hpyF8E2fbMT55sRHWBq+Huaqk3b3XK+rxxlM8sPgGM6A==", "dev": true, "license": "MIT", "engines": { @@ -1706,20 +1714,20 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.17.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.17.0.tgz", - "integrity": "sha512-JqkOopc1nRKZpX+opvKqnM3XUlM7LpFMD0lYxTqOTKQfCWAmxw45e3qlOCsEqEB2yuacujivudOFpCnqkBDNMw==", + "version": "8.22.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.22.0.tgz", + "integrity": "sha512-SJX99NAS2ugGOzpyhMza/tX+zDwjvwAtQFLsBo3GQxiGcvaKlqGBkmZ+Y1IdiSi9h4Q0Lr5ey+Cp9CGWNY/F/w==", "dev": true, - "license": "BSD-2-Clause", + "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.17.0", - "@typescript-eslint/visitor-keys": "8.17.0", + "@typescript-eslint/types": "8.22.0", + "@typescript-eslint/visitor-keys": "8.22.0", "debug": "^4.3.4", "fast-glob": "^3.3.2", "is-glob": "^4.0.3", "minimatch": "^9.0.4", "semver": "^7.6.0", - "ts-api-utils": "^1.3.0" + "ts-api-utils": "^2.0.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1728,10 +1736,8 @@ "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "peerDependencies": { + "typescript": ">=4.8.4 <5.8.0" } }, "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { @@ -1761,16 +1767,16 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "8.17.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.17.0.tgz", - "integrity": "sha512-bQC8BnEkxqG8HBGKwG9wXlZqg37RKSMY7v/X8VEWD8JG2JuTHuNK0VFvMPMUKQcbk6B+tf05k+4AShAEtCtJ/w==", + "version": "8.22.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.22.0.tgz", + "integrity": "sha512-T8oc1MbF8L+Bk2msAvCUzjxVB2Z2f+vXYfcucE2wOmYs7ZUwco5Ep0fYZw8quNwOiw9K8GYVL+Kgc2pETNTLOg==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", - "@typescript-eslint/scope-manager": "8.17.0", - "@typescript-eslint/types": "8.17.0", - "@typescript-eslint/typescript-estree": "8.17.0" + "@typescript-eslint/scope-manager": "8.22.0", + "@typescript-eslint/types": "8.22.0", + "@typescript-eslint/typescript-estree": "8.22.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1780,22 +1786,18 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <5.8.0" } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.17.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.17.0.tgz", - "integrity": "sha512-1Hm7THLpO6ww5QU6H/Qp+AusUUl+z/CAm3cNZZ0jQvon9yicgO7Rwd+/WWRpMKLYV6p2UvdbR27c86rzCPpreg==", + "version": "8.22.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.22.0.tgz", + "integrity": "sha512-AWpYAXnUgvLNabGTy3uBylkgZoosva/miNd1I8Bz3SjotmQPbVqhO4Cczo8AsZ44XVErEBPr/CRSgaj8sG7g0w==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.17.0", + "@typescript-eslint/types": "8.22.0", "eslint-visitor-keys": "^4.2.0" }, "engines": { @@ -1807,9 +1809,9 @@ } }, "node_modules/@vinejs/compiler": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/@vinejs/compiler/-/compiler-2.5.1.tgz", - "integrity": "sha512-efiO/SCQSMCqz6LDZTI4R3Ceq1ik3K2IqefEbbch+ko4dZncaYmQWJpX/fXVwgmO78jTZuerzD4I2WphPJUCwg==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@vinejs/compiler/-/compiler-3.0.0.tgz", + "integrity": "sha512-v9Lsv59nR56+bmy2p0+czjZxsLHwaibJ+SV5iK9JJfehlJMa501jUJQqqz4X/OqKXrxtE3uTQmSqjUqzF3B2mw==", "license": "MIT", "optional": true, "engines": { @@ -1817,51 +1819,51 @@ } }, "node_modules/@vinejs/vine": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@vinejs/vine/-/vine-2.1.0.tgz", - "integrity": "sha512-09aJ2OauxpblqiNqd8qC9RAzzm5SV6fTqZhE4e25j4cM7fmNoXRTjM7Oo8llFADMO4eSA44HqYEO3mkRRYdbYw==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@vinejs/vine/-/vine-3.0.0.tgz", + "integrity": "sha512-GeCAHLzKkL2kMFqatgqyiiNh+FILOSAV8x8imBDo6AWQ91w30Kaxw4FnzUDqgcd9z8aCYOBQ7RJxBBGfyr+USQ==", "license": "MIT", "optional": true, "dependencies": { - "@poppinss/macroable": "^1.0.2", - "@types/validator": "^13.11.9", - "@vinejs/compiler": "^2.5.0", + "@poppinss/macroable": "^1.0.3", + "@types/validator": "^13.12.2", + "@vinejs/compiler": "^3.0.0", "camelcase": "^8.0.0", - "dayjs": "^1.11.11", + "dayjs": "^1.11.13", "dlv": "^1.1.3", "normalize-url": "^8.0.1", - "validator": "^13.11.0" + "validator": "^13.12.0" }, "engines": { "node": ">=18.16.0" } }, "node_modules/@vitest/expect": { - "version": "2.1.6", - "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-2.1.6.tgz", - "integrity": "sha512-9M1UR9CAmrhJOMoSwVnPh2rELPKhYo0m/CSgqw9PyStpxtkwhmdM6XYlXGKeYyERY1N6EIuzkQ7e3Lm1WKCoUg==", + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-3.0.4.tgz", + "integrity": "sha512-Nm5kJmYw6P2BxhJPkO3eKKhGYKRsnqJqf+r0yOGRKpEP+bSCBDsjXgiu1/5QFrnPMEgzfC38ZEjvCFgaNBC0Eg==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/spy": "2.1.6", - "@vitest/utils": "2.1.6", + "@vitest/spy": "3.0.4", + "@vitest/utils": "3.0.4", "chai": "^5.1.2", - "tinyrainbow": "^1.2.0" + "tinyrainbow": "^2.0.0" }, "funding": { "url": "https://opencollective.com/vitest" } }, "node_modules/@vitest/mocker": { - "version": "2.1.6", - "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-2.1.6.tgz", - "integrity": "sha512-MHZp2Z+Q/A3am5oD4WSH04f9B0T7UvwEb+v5W0kCYMhtXGYbdyl2NUk1wdSMqGthmhpiThPDp/hEoVwu16+u1A==", + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-3.0.4.tgz", + "integrity": "sha512-gEef35vKafJlfQbnyOXZ0Gcr9IBUsMTyTLXsEQwuyYAerpHqvXhzdBnDFuHLpFqth3F7b6BaFr4qV/Cs1ULx5A==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/spy": "2.1.6", + "@vitest/spy": "3.0.4", "estree-walker": "^3.0.3", - "magic-string": "^0.30.12" + "magic-string": "^0.30.17" }, "funding": { "url": "https://opencollective.com/vitest" @@ -1880,64 +1882,65 @@ } }, "node_modules/@vitest/pretty-format": { - "version": "2.1.8", - "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-2.1.8.tgz", - "integrity": "sha512-9HiSZ9zpqNLKlbIDRWOnAWqgcA7xu+8YxXSekhr0Ykab7PAYFkhkwoqVArPOtJhPmYeE2YHgKZlj3CP36z2AJQ==", + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-3.0.4.tgz", + "integrity": "sha512-ts0fba+dEhK2aC9PFuZ9LTpULHpY/nd6jhAQ5IMU7Gaj7crPCTdCFfgvXxruRBLFS+MLraicCuFXxISEq8C93g==", "dev": true, "license": "MIT", "dependencies": { - "tinyrainbow": "^1.2.0" + "tinyrainbow": "^2.0.0" }, "funding": { "url": "https://opencollective.com/vitest" } }, "node_modules/@vitest/runner": { - "version": "2.1.6", - "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-2.1.6.tgz", - "integrity": "sha512-SjkRGSFyrA82m5nz7To4CkRSEVWn/rwQISHoia/DB8c6IHIhaE/UNAo+7UfeaeJRE979XceGl00LNkIz09RFsA==", + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-3.0.4.tgz", + "integrity": "sha512-dKHzTQ7n9sExAcWH/0sh1elVgwc7OJ2lMOBrAm73J7AH6Pf9T12Zh3lNE1TETZaqrWFXtLlx3NVrLRb5hCK+iw==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/utils": "2.1.6", - "pathe": "^1.1.2" + "@vitest/utils": "3.0.4", + "pathe": "^2.0.2" }, "funding": { "url": "https://opencollective.com/vitest" } }, + "node_modules/@vitest/runner/node_modules/pathe": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.2.tgz", + "integrity": "sha512-15Ztpk+nov8DR524R4BF7uEuzESgzUEAV4Ah7CUMNGXdE5ELuvxElxGXndBl32vMSsWa1jpNf22Z+Er3sKwq+w==", + "dev": true, + "license": "MIT" + }, "node_modules/@vitest/snapshot": { - "version": "2.1.6", - "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-2.1.6.tgz", - "integrity": "sha512-5JTWHw8iS9l3v4/VSuthCndw1lN/hpPB+mlgn1BUhFbobeIUj1J1V/Bj2t2ovGEmkXLTckFjQddsxS5T6LuVWw==", + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-3.0.4.tgz", + "integrity": "sha512-+p5knMLwIk7lTQkM3NonZ9zBewzVp9EVkVpvNta0/PlFWpiqLaRcF4+33L1it3uRUCh0BGLOaXPPGEjNKfWb4w==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/pretty-format": "2.1.6", - "magic-string": "^0.30.12", - "pathe": "^1.1.2" + "@vitest/pretty-format": "3.0.4", + "magic-string": "^0.30.17", + "pathe": "^2.0.2" }, "funding": { "url": "https://opencollective.com/vitest" } }, - "node_modules/@vitest/snapshot/node_modules/@vitest/pretty-format": { - "version": "2.1.6", - "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-2.1.6.tgz", - "integrity": "sha512-exZyLcEnHgDMKc54TtHca4McV4sKT+NKAe9ix/yhd/qkYb/TP8HTyXRFDijV19qKqTZM0hPL4753zU/U8L/gAA==", + "node_modules/@vitest/snapshot/node_modules/pathe": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.2.tgz", + "integrity": "sha512-15Ztpk+nov8DR524R4BF7uEuzESgzUEAV4Ah7CUMNGXdE5ELuvxElxGXndBl32vMSsWa1jpNf22Z+Er3sKwq+w==", "dev": true, - "license": "MIT", - "dependencies": { - "tinyrainbow": "^1.2.0" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } + "license": "MIT" }, "node_modules/@vitest/spy": { - "version": "2.1.6", - "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-2.1.6.tgz", - "integrity": "sha512-oTFObV8bd4SDdRka5O+mSh5w9irgx5IetrD5i+OsUUsk/shsBoHifwCzy45SAORzAhtNiprUVaK3hSCCzZh1jQ==", + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-3.0.4.tgz", + "integrity": "sha512-sXIMF0oauYyUy2hN49VFTYodzEAu744MmGcPR3ZBsPM20G+1/cSW/n1U+3Yu/zHxX2bIDe1oJASOkml+osTU6Q==", "dev": true, "license": "MIT", "dependencies": { @@ -1948,28 +1951,15 @@ } }, "node_modules/@vitest/utils": { - "version": "2.1.6", - "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-2.1.6.tgz", - "integrity": "sha512-ixNkFy3k4vokOUTU2blIUvOgKq/N2PW8vKIjZZYsGJCMX69MRa9J2sKqX5hY/k5O5Gty3YJChepkqZ3KM9LyIQ==", + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-3.0.4.tgz", + "integrity": "sha512-8BqC1ksYsHtbWH+DfpOAKrFw3jl3Uf9J7yeFh85Pz52IWuh1hBBtyfEbRNNZNjl8H8A5yMLH9/t+k7HIKzQcZQ==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/pretty-format": "2.1.6", + "@vitest/pretty-format": "3.0.4", "loupe": "^3.1.2", - "tinyrainbow": "^1.2.0" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/@vitest/utils/node_modules/@vitest/pretty-format": { - "version": "2.1.6", - "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-2.1.6.tgz", - "integrity": "sha512-exZyLcEnHgDMKc54TtHca4McV4sKT+NKAe9ix/yhd/qkYb/TP8HTyXRFDijV19qKqTZM0hPL4753zU/U8L/gAA==", - "dev": true, - "license": "MIT", - "dependencies": { - "tinyrainbow": "^1.2.0" + "tinyrainbow": "^2.0.0" }, "funding": { "url": "https://opencollective.com/vitest" @@ -2092,14 +2082,14 @@ } }, "node_modules/arktype": { - "version": "2.0.0-rc.25", - "resolved": "https://registry.npmjs.org/arktype/-/arktype-2.0.0-rc.25.tgz", - "integrity": "sha512-ck1kRLda55Pv6L3vIIjRb6A22LqUcjitxPGS3OscOKwFcxNReaj+ItJJiysebBbbUy+ESouXTJ+vvck33spGDQ==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/arktype/-/arktype-2.0.0.tgz", + "integrity": "sha512-LVuXndcKO+zB7Hg9laGJmXYD5BZYhm/cUFL5pgB/75/3sGnvN8fsQ/yuBlf4JSn90g4rOoU7+9aTvdn5lToWOg==", "license": "MIT", "optional": true, "dependencies": { - "@ark/schema": "0.25.0", - "@ark/util": "0.25.0" + "@ark/schema": "0.35.0", + "@ark/util": "0.35.0" } }, "node_modules/array-buffer-byte-length": { @@ -2234,17 +2224,17 @@ } }, "node_modules/bits-ui": { - "version": "1.0.0-next.65", - "resolved": "https://registry.npmjs.org/bits-ui/-/bits-ui-1.0.0-next.65.tgz", - "integrity": "sha512-6pJ2dYP28kkYhvXKyUh22ocTyzciRrx752FJ8+oKUDQ07qnP4O/D8oAFo9pbZVU1MJ3wSMTXkpOVI/DrkJhChQ==", + "version": "1.0.0-next.78", + "resolved": "https://registry.npmjs.org/bits-ui/-/bits-ui-1.0.0-next.78.tgz", + "integrity": "sha512-jZjG2ObZ/CNyCNaXecpItC7hRXqJAgEfMhr06/eNrf3wHiiPyhdcy4OkzLcJyxeOrDyj+xma8cZTd3JRWqJdAw==", "license": "MIT", "dependencies": { "@floating-ui/core": "^1.6.4", "@floating-ui/dom": "^1.6.7", "@internationalized/date": "^3.5.6", "esm-env": "^1.1.2", - "runed": "^0.15.2", - "svelte-toolbelt": "^0.4.4" + "runed": "^0.22.0", + "svelte-toolbelt": "^0.7.0" }, "engines": { "node": ">=18", @@ -2254,13 +2244,13 @@ "url": "https://github.com/sponsors/huntabyte" }, "peerDependencies": { - "svelte": "^5.0.0-next.1" + "svelte": "^5.11.0" } }, "node_modules/bits-ui/node_modules/runed": { - "version": "0.15.4", - "resolved": "https://registry.npmjs.org/runed/-/runed-0.15.4.tgz", - "integrity": "sha512-kmbpstUd7v2FdlBM+MT78IyuOVd38tq/e7MHvVb0fnVCsPSPMD/m2Xh+wUhzg9qCJgxRjBbIKu68DlH/x5VXJA==", + "version": "0.22.0", + "resolved": "https://registry.npmjs.org/runed/-/runed-0.22.0.tgz", + "integrity": "sha512-ZWVXWhOr0P5xdNgtviz6D1ivLUDWKLCbeC5SUEJ3zBkqLReVqWHenFxMNFeFaiC5bfxhFxyxzyzB+98uYFtwdA==", "funding": [ "https://github.com/sponsors/huntabyte", "https://github.com/sponsors/tglide" @@ -2269,7 +2259,7 @@ "esm-env": "^1.0.0" }, "peerDependencies": { - "svelte": "^5.0.0-next.1" + "svelte": "^5.7.0" } }, "node_modules/brace-expansion": { @@ -2794,9 +2784,9 @@ "license": "MIT" }, "node_modules/debug": { - "version": "4.3.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", - "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", + "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", "license": "MIT", "dependencies": { "ms": "^2.1.3" @@ -2897,13 +2887,13 @@ "license": "MIT" }, "node_modules/effect": { - "version": "3.10.19", - "resolved": "https://registry.npmjs.org/effect/-/effect-3.10.19.tgz", - "integrity": "sha512-Bc+unZVpHQ/0QkydshNk97OjDXT17Y1M4rBjDZaEPjD6YmlcZxhadEo325OfdPQKWCKEHTdRGtX8/bfQ0RLTIw==", + "version": "3.12.7", + "resolved": "https://registry.npmjs.org/effect/-/effect-3.12.7.tgz", + "integrity": "sha512-BsDTgSjLbL12g0+vGn5xkOgOVhRSaR3VeHmjcUb0gLvpXACJ9OgmlfeH+/FaAZwM5+omIF3I/j1gC5KJrbK1Aw==", "license": "MIT", "optional": true, "dependencies": { - "fast-check": "^3.21.0" + "fast-check": "^3.23.1" } }, "node_modules/electron-to-chromium": { @@ -3033,9 +3023,9 @@ } }, "node_modules/es-module-lexer": { - "version": "1.5.4", - "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.5.4.tgz", - "integrity": "sha512-MVNK56NiMrOwitFB7cqDwq0CQutbw+0BvLshJSse0MUNU+y1FC3bUS/AQg7oUng+/wKrrki7JfmwtVHkVfPLlw==", + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.6.0.tgz", + "integrity": "sha512-qqnD1yMU6tk/jnaMosogGySTZP8YtUgAffA9nMN+E/rjxcfRQ6IEk7IiozUjgxKoFHBGjTLnrHB/YC45r/59EQ==", "dev": true, "license": "MIT" }, @@ -3093,9 +3083,9 @@ "license": "MIT" }, "node_modules/esbuild": { - "version": "0.24.0", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.24.0.tgz", - "integrity": "sha512-FuLPevChGDshgSicjisSooU0cemp/sGXR841D5LHMB7mTVOmsEHcAxaH3irL53+8YDIeVNQEySh4DaYU/iuPqQ==", + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.24.2.tgz", + "integrity": "sha512-+9egpBW8I3CD5XPe0n6BfT5fxLzxrlDzqydF3aviG+9ni1lDC/OvMHcxqEFV0+LANZG5R1bFMWfUrjVsdwxJvA==", "hasInstallScript": true, "license": "MIT", "bin": { @@ -3105,30 +3095,31 @@ "node": ">=18" }, "optionalDependencies": { - "@esbuild/aix-ppc64": "0.24.0", - "@esbuild/android-arm": "0.24.0", - "@esbuild/android-arm64": "0.24.0", - "@esbuild/android-x64": "0.24.0", - "@esbuild/darwin-arm64": "0.24.0", - "@esbuild/darwin-x64": "0.24.0", - "@esbuild/freebsd-arm64": "0.24.0", - "@esbuild/freebsd-x64": "0.24.0", - "@esbuild/linux-arm": "0.24.0", - "@esbuild/linux-arm64": "0.24.0", - "@esbuild/linux-ia32": "0.24.0", - "@esbuild/linux-loong64": "0.24.0", - "@esbuild/linux-mips64el": "0.24.0", - "@esbuild/linux-ppc64": "0.24.0", - "@esbuild/linux-riscv64": "0.24.0", - "@esbuild/linux-s390x": "0.24.0", - "@esbuild/linux-x64": "0.24.0", - "@esbuild/netbsd-x64": "0.24.0", - "@esbuild/openbsd-arm64": "0.24.0", - "@esbuild/openbsd-x64": "0.24.0", - "@esbuild/sunos-x64": "0.24.0", - "@esbuild/win32-arm64": "0.24.0", - "@esbuild/win32-ia32": "0.24.0", - "@esbuild/win32-x64": "0.24.0" + "@esbuild/aix-ppc64": "0.24.2", + "@esbuild/android-arm": "0.24.2", + "@esbuild/android-arm64": "0.24.2", + "@esbuild/android-x64": "0.24.2", + "@esbuild/darwin-arm64": "0.24.2", + "@esbuild/darwin-x64": "0.24.2", + "@esbuild/freebsd-arm64": "0.24.2", + "@esbuild/freebsd-x64": "0.24.2", + "@esbuild/linux-arm": "0.24.2", + "@esbuild/linux-arm64": "0.24.2", + "@esbuild/linux-ia32": "0.24.2", + "@esbuild/linux-loong64": "0.24.2", + "@esbuild/linux-mips64el": "0.24.2", + "@esbuild/linux-ppc64": "0.24.2", + "@esbuild/linux-riscv64": "0.24.2", + "@esbuild/linux-s390x": "0.24.2", + "@esbuild/linux-x64": "0.24.2", + "@esbuild/netbsd-arm64": "0.24.2", + "@esbuild/netbsd-x64": "0.24.2", + "@esbuild/openbsd-arm64": "0.24.2", + "@esbuild/openbsd-x64": "0.24.2", + "@esbuild/sunos-x64": "0.24.2", + "@esbuild/win32-arm64": "0.24.2", + "@esbuild/win32-ia32": "0.24.2", + "@esbuild/win32-x64": "0.24.2" } }, "node_modules/esbuild-runner": { @@ -3179,19 +3170,19 @@ } }, "node_modules/eslint": { - "version": "9.16.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.16.0.tgz", - "integrity": "sha512-whp8mSQI4C8VXd+fLgSM0lh3UlmcFtVwUQjyKCFfsp+2ItAIYhlq/hqGahGqHE6cv9unM41VlqKk2VtKYR2TaA==", + "version": "9.19.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.19.0.tgz", + "integrity": "sha512-ug92j0LepKlbbEv6hD911THhoRHmbdXt2gX+VDABAW/Ir7D3nqKdv5Pf5vtlyY6HQMTEP2skXY43ueqTCWssEA==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.12.1", "@eslint/config-array": "^0.19.0", - "@eslint/core": "^0.9.0", + "@eslint/core": "^0.10.0", "@eslint/eslintrc": "^3.2.0", - "@eslint/js": "9.16.0", - "@eslint/plugin-kit": "^0.2.3", + "@eslint/js": "9.19.0", + "@eslint/plugin-kit": "^0.2.5", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.1", @@ -3199,7 +3190,7 @@ "@types/json-schema": "^7.0.15", "ajv": "^6.12.4", "chalk": "^4.0.0", - "cross-spawn": "^7.0.5", + "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", "eslint-scope": "^8.2.0", @@ -3255,27 +3246,27 @@ } }, "node_modules/eslint-config-prettier": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-9.1.0.tgz", - "integrity": "sha512-NSWl5BFQWEPi1j4TjVNItzYV7dZXZ+wP6I6ZhrBGpChQhZRUaElihE9uRRkcbRnNb76UMKDF3r+WTmNcGPKsqw==", + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-10.0.1.tgz", + "integrity": "sha512-lZBts941cyJyeaooiKxAtzoPHTN+GbQTJFAIdQbRhA4/8whaAraEh47Whw/ZFfrjNSnlAxqfm9i0XVAEkULjCw==", "dev": true, "license": "MIT", "bin": { - "eslint-config-prettier": "bin/cli.js" + "eslint-config-prettier": "build/bin/cli.js" }, "peerDependencies": { "eslint": ">=7.0.0" } }, "node_modules/eslint-plugin-perfectionist": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-perfectionist/-/eslint-plugin-perfectionist-4.2.0.tgz", - "integrity": "sha512-hEMFx5xfSc/0OLZXJhSaLUKkFxATuRf4yL2iVfxEcxkkb17DfoLZY9eH960dPSw5uB7o+4avqP3rtkNp1Vwo7w==", + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-perfectionist/-/eslint-plugin-perfectionist-4.7.0.tgz", + "integrity": "sha512-e2ODzm2SsAztFWY3ZRJd1K702vyl8Sapacjc3JluOW294CfA3+jfjin+UxjcrK48EvlNIMOp+JJB9N54YR2LRw==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "^8.17.0", - "@typescript-eslint/utils": "^8.17.0", + "@typescript-eslint/types": "^8.21.0", + "@typescript-eslint/utils": "^8.21.0", "natural-orderby": "^5.0.0" }, "engines": { @@ -3351,9 +3342,9 @@ } }, "node_modules/esm-env": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/esm-env/-/esm-env-1.2.1.tgz", - "integrity": "sha512-U9JedYYjCnadUlXk7e1Kr+aENQhtUaoaV9+gZm1T8LC/YBAPJx3NSPIAurFOC0U5vrdSevnUJS2/wUVxGwPhng==", + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/esm-env/-/esm-env-1.2.2.tgz", + "integrity": "sha512-Epxrv+Nr/CaL4ZcFGPJIYLWFom+YeV1DqMLHJoEd9SYRxNbaFruBwfEX/kkHUJf55j2+TUbmDcmuilbP1TmXHA==", "license": "MIT" }, "node_modules/espree": { @@ -3388,13 +3379,12 @@ } }, "node_modules/esrap": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/esrap/-/esrap-1.2.3.tgz", - "integrity": "sha512-ZlQmCCK+n7SGoqo7DnfKaP1sJZa49P01/dXzmjCASSo04p72w8EksT2NMK8CEX8DhKsfJXANioIw8VyHNsBfvQ==", + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/esrap/-/esrap-1.4.3.tgz", + "integrity": "sha512-Xddc1RsoFJ4z9nR7W7BFaEPIp4UXoeQ0+077UdWLxbafMQFyU79sQJMk7kxNgRwQ9/aVgaKacCHC2pUACGwmYw==", "license": "MIT", "dependencies": { - "@jridgewell/sourcemap-codec": "^1.4.15", - "@types/estree": "^1.0.1" + "@jridgewell/sourcemap-codec": "^1.4.15" } }, "node_modules/esrecurse": { @@ -3464,9 +3454,9 @@ } }, "node_modules/fast-check": { - "version": "3.23.1", - "resolved": "https://registry.npmjs.org/fast-check/-/fast-check-3.23.1.tgz", - "integrity": "sha512-u/MudsoQEgBUZgR5N1v87vEgybeVYus9VnDVaIkxkkGP2jt54naghQ3PCQHJiogS8U/GavZCUPFfx3Xkp+NaHw==", + "version": "3.23.2", + "resolved": "https://registry.npmjs.org/fast-check/-/fast-check-3.23.2.tgz", + "integrity": "sha512-h5+1OzzfCC3Ef7VbtKdcv7zsstUQwUDlYpUTvjeUsJAssPgLn7QzbboPtL5ro04Mq0rPOsMzl7q5hIbRs2wD1A==", "funding": [ { "type": "individual", @@ -3641,12 +3631,12 @@ } }, "node_modules/formsnap": { - "version": "2.0.0-next.1", - "resolved": "https://registry.npmjs.org/formsnap/-/formsnap-2.0.0-next.1.tgz", - "integrity": "sha512-ha8r9eMmsGEGMY+ljV3FEyTtB72E7dt95y9HHUbCcaDnjbz3Q6n00BHLz7dfBZ9rqyaMeIO200EmP1IcYMExeg==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/formsnap/-/formsnap-2.0.0.tgz", + "integrity": "sha512-W61elddvdzeBEs10nNvwxQnx/FctJFHBXPk9uluNQAckHo1nuSUvSQGIjtLjTKIbQdQnwEOoxqWrk9tuv0U7hA==", "license": "MIT", "dependencies": { - "svelte-toolbelt": "^0.4.4" + "svelte-toolbelt": "^0.5.0" }, "engines": { "node": ">=18", @@ -3657,6 +3647,25 @@ "sveltekit-superforms": "^2.19.0" } }, + "node_modules/formsnap/node_modules/svelte-toolbelt": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/svelte-toolbelt/-/svelte-toolbelt-0.5.0.tgz", + "integrity": "sha512-t3tenZcnfQoIeRuQf/jBU7bvTeT3TGkcEE+1EUr5orp0lR7NEpprflpuie3x9Dn0W9nOKqs3HwKGJeeN5Ok1sQ==", + "funding": [ + "https://github.com/sponsors/huntabyte" + ], + "dependencies": { + "clsx": "^2.1.1", + "style-to-object": "^1.0.8" + }, + "engines": { + "node": ">=18", + "pnpm": ">=8.7.0" + }, + "peerDependencies": { + "svelte": "^5.0.0-next.126" + } + }, "node_modules/fraction.js": { "version": "4.3.7", "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz", @@ -3857,18 +3866,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "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==", - "license": "MIT" - }, - "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==", - "license": "MIT" - }, "node_modules/gopd": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.1.0.tgz", @@ -4591,12 +4588,6 @@ "dev": true, "license": "MIT" }, - "node_modules/just-clone": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/just-clone/-/just-clone-6.2.0.tgz", - "integrity": "sha512-1IynUYEc/HAwxhi3WDpIpxJbZpMCvvrrmZVqvj9EhpvbH8lls7HhdhiByjL7DkAaWlLIzpC0Xc/VPvy/UxLNjA==", - "license": "MIT" - }, "node_modules/jwt-decode": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/jwt-decode/-/jwt-decode-4.0.0.tgz", @@ -4755,10 +4746,19 @@ "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", "license": "ISC" }, + "node_modules/lucide-svelte": { + "version": "0.474.0", + "resolved": "https://registry.npmjs.org/lucide-svelte/-/lucide-svelte-0.474.0.tgz", + "integrity": "sha512-yOSqjXPoEDOXCceBIfDaed6RinOvhp03ShiTXH6O+vlXE/NsyjQpktL8gm2vGDxi9d81HMuPFN1dwhVURh6mGg==", + "license": "ISC", + "peerDependencies": { + "svelte": "^3 || ^4 || ^5.0.0-next.42" + } + }, "node_modules/magic-string": { - "version": "0.30.14", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.14.tgz", - "integrity": "sha512-5c99P1WKTed11ZC0HMJOj6CDIue6F8ySu+bJL+85q1zBEIY8IklrJ1eiKC2NDRh3Ct3FcvmJPyQHb9erXMTJNw==", + "version": "0.30.17", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz", + "integrity": "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==", "license": "MIT", "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0" @@ -4836,9 +4836,9 @@ } }, "node_modules/mode-watcher": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/mode-watcher/-/mode-watcher-0.5.0.tgz", - "integrity": "sha512-5E6fh/aXhAVv+U+DbeM0hCmskQE9u7WSmvnCRijJB/MJu7HtB73sjiCaZ9n1M8QHmzLrBFo8XBAUcWXkDm8Z9A==", + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/mode-watcher/-/mode-watcher-0.5.1.tgz", + "integrity": "sha512-adEC6T7TMX/kzQlaO/MtiQOSFekZfQu4MC+lXyoceQG+U5sKpJWZ4yKXqw846ExIuWJgedkOIPqAYYRk/xHm+w==", "license": "MIT", "peerDependencies": { "svelte": "^4.0.0 || ^5.0.0-next.1" @@ -5603,24 +5603,24 @@ } }, "node_modules/pkg-types": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.2.1.tgz", - "integrity": "sha512-sQoqa8alT3nHjGuTjuKgOnvjo4cljkufdtLMnO2LBP/wRwuDlo1tkaEdMxCRhyGRPacv/ztlZgDPm2b7FAmEvw==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.3.0.tgz", + "integrity": "sha512-kS7yWjVFCkIw9hqdJBoMxDdzEngmkr5FXeWZZfQ6GoYacjVnsW6l2CcYW/0ThD0vF4LPJgVYnrg4d0uuhwYQbg==", "license": "MIT", "dependencies": { "confbox": "^0.1.8", - "mlly": "^1.7.2", + "mlly": "^1.7.3", "pathe": "^1.1.2" } }, "node_modules/playwright": { - "version": "1.49.0", - "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.49.0.tgz", - "integrity": "sha512-eKpmys0UFDnfNb3vfsf8Vx2LEOtflgRebl0Im2eQQnYMA4Aqd+Zw8bEOB+7ZKvN76901mRnqdsiOGKxzVTbi7A==", + "version": "1.50.0", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.50.0.tgz", + "integrity": "sha512-+GinGfGTrd2IfX1TA4N2gNmeIksSb+IAe589ZH+FlmpV3MYTx6+buChGIuDLQwrGNCw2lWibqV50fU510N7S+w==", "dev": true, "license": "Apache-2.0", "dependencies": { - "playwright-core": "1.49.0" + "playwright-core": "1.50.0" }, "bin": { "playwright": "cli.js" @@ -5633,9 +5633,9 @@ } }, "node_modules/playwright-core": { - "version": "1.49.0", - "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.49.0.tgz", - "integrity": "sha512-R+3KKTQF3npy5GTiKH/T+kdhoJfJojjHESR1YEWhYuEKRVfVaxH3+4+GvXE5xyCngCxhxnykk0Vlah9v8fs3jA==", + "version": "1.50.0", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.50.0.tgz", + "integrity": "sha512-CXkSSlr4JaZs2tZHI40DsZUN/NIwgaUPsyLuOAaIZp2CyF2sN5MM5NJsyB188lFSSozFxQ5fPT4qM+f0tH/6wQ==", "dev": true, "license": "Apache-2.0", "bin": { @@ -5656,9 +5656,9 @@ } }, "node_modules/postcss": { - "version": "8.4.49", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.49.tgz", - "integrity": "sha512-OCVPnIObs4N29kxTjzLfUryOkvZEq+pf8jTF0lg8E7uETuWHA+v7j3c/xJmiqpX450191LlmZfUKkXxkTry7nA==", + "version": "8.5.1", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.1.tgz", + "integrity": "sha512-6oz2beyjc5VMn/KV1pPw8fliQkhBXrVn1Z3TVyqZxU8kZpzEKhBdmCFqI6ZbmGtamQvQGuU1sgPTk8ZrXDD7jQ==", "funding": [ { "type": "opencollective", @@ -5675,7 +5675,7 @@ ], "license": "MIT", "dependencies": { - "nanoid": "^3.3.7", + "nanoid": "^3.3.8", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" }, @@ -5858,9 +5858,9 @@ } }, "node_modules/prettier": { - "version": "3.4.1", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.4.1.tgz", - "integrity": "sha512-G+YdqtITVZmOJje6QkXQWzl3fSfMxFwm1tjTyo9exhkmWSqC4Yhd1+lug++IlR2mvRVAxEDDWYkQdeSztajqgg==", + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.4.2.tgz", + "integrity": "sha512-e9MewbtFo+Fevyuxn/4rrcDAaq0IYxPGLvObpQjiZBMAzB9IGmzlnG9RZy3FFas+eBMu2vA0CszMeduow5dIuQ==", "dev": true, "license": "MIT", "bin": { @@ -5874,9 +5874,9 @@ } }, "node_modules/prettier-plugin-svelte": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/prettier-plugin-svelte/-/prettier-plugin-svelte-3.3.2.tgz", - "integrity": "sha512-kRPjH8wSj2iu+dO+XaUv4vD8qr5mdDmlak3IT/7AOgGIMRG86z/EHOLauFcClKEnOUf4A4nOA7sre5KrJD4Raw==", + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/prettier-plugin-svelte/-/prettier-plugin-svelte-3.3.3.tgz", + "integrity": "sha512-yViK9zqQ+H2qZD1w/bH7W8i+bVfKrD8GIFjkFe4Thl6kCT9SlAsXVNmt3jCvQOCsnOhcvYgsoVlRV/Eu6x5nNw==", "dev": true, "license": "MIT", "peerDependencies": { @@ -5885,9 +5885,9 @@ } }, "node_modules/prettier-plugin-tailwindcss": { - "version": "0.6.9", - "resolved": "https://registry.npmjs.org/prettier-plugin-tailwindcss/-/prettier-plugin-tailwindcss-0.6.9.tgz", - "integrity": "sha512-r0i3uhaZAXYP0At5xGfJH876W3HHGHDp+LCRUJrs57PBeQ6mYHMwr25KH8NPX44F2yGTvdnH7OqCshlQx183Eg==", + "version": "0.6.11", + "resolved": "https://registry.npmjs.org/prettier-plugin-tailwindcss/-/prettier-plugin-tailwindcss-0.6.11.tgz", + "integrity": "sha512-YxaYSIvZPAqhrrEpRtonnrXdghZg1irNg4qrjboCXrpybLWVs55cW2N3juhspVJiO0JBvYJT8SYsJpc8OQSnsA==", "dev": true, "license": "MIT", "engines": { @@ -5898,7 +5898,7 @@ "@prettier/plugin-pug": "*", "@shopify/prettier-plugin-liquid": "*", "@trivago/prettier-plugin-sort-imports": "*", - "@zackad/prettier-plugin-twig-melody": "*", + "@zackad/prettier-plugin-twig": "*", "prettier": "^3.0", "prettier-plugin-astro": "*", "prettier-plugin-css-order": "*", @@ -5925,7 +5925,7 @@ "@trivago/prettier-plugin-sort-imports": { "optional": true }, - "@zackad/prettier-plugin-twig-melody": { + "@zackad/prettier-plugin-twig": { "optional": true }, "prettier-plugin-astro": { @@ -6245,9 +6245,9 @@ } }, "node_modules/runed": { - "version": "0.16.1", - "resolved": "https://registry.npmjs.org/runed/-/runed-0.16.1.tgz", - "integrity": "sha512-k9ylt7sfEQiqOo2FmuilkLSk92pDzMSeVHFb8aPJGFPQPG9ErUxhfcHQwnmUhT03F8oQeO6bKB/TDoPKhdXbTA==", + "version": "0.23.2", + "resolved": "https://registry.npmjs.org/runed/-/runed-0.23.2.tgz", + "integrity": "sha512-AhHCb5/B+YQW6ar1pzhGQOQy+byfjCH63ofuhrexSWwQKhC0EbQ60Z/wMYwETLo3ZubhwlNryxBt0seOMOrVFQ==", "funding": [ "https://github.com/sponsors/huntabyte", "https://github.com/sponsors/tglide" @@ -6256,7 +6256,7 @@ "esm-env": "^1.0.0" }, "peerDependencies": { - "svelte": "^5.0.0-next.1" + "svelte": "^5.7.0" } }, "node_modules/sade": { @@ -6889,9 +6889,9 @@ } }, "node_modules/svelte": { - "version": "5.5.3", - "resolved": "https://registry.npmjs.org/svelte/-/svelte-5.5.3.tgz", - "integrity": "sha512-0j7XTSg5iXcLNCFcEsIZPtHO7SQeE0KgMcyF1K4K7HkjdKVPumz7dnxeXq5lGJRHfVAMZKqpEJ46rPKPKRJ57Q==", + "version": "5.19.6", + "resolved": "https://registry.npmjs.org/svelte/-/svelte-5.19.6.tgz", + "integrity": "sha512-6ydekB3qyqUal+UhfMjmVOjRGtxysR8vuiMhi2nwuBtPJWnctVlsGspjVFB05qmR+TXI1emuqtZt81c0XiFleA==", "license": "MIT", "dependencies": { "@ampproject/remapping": "^2.3.0", @@ -6901,8 +6901,9 @@ "acorn-typescript": "^1.4.13", "aria-query": "^5.3.1", "axobject-query": "^4.1.0", + "clsx": "^2.1.1", "esm-env": "^1.2.1", - "esrap": "^1.2.3", + "esrap": "^1.4.3", "is-reference": "^3.0.3", "locate-character": "^3.0.0", "magic-string": "^0.30.11", @@ -6913,9 +6914,9 @@ } }, "node_modules/svelte-check": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/svelte-check/-/svelte-check-4.1.1.tgz", - "integrity": "sha512-NfaX+6Qtc8W/CyVGS/F7/XdiSSyXz+WGYA9ZWV3z8tso14V2vzjfXviKaTFEzB7g8TqfgO2FOzP6XT4ApSTUTw==", + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/svelte-check/-/svelte-check-4.1.4.tgz", + "integrity": "sha512-v0j7yLbT29MezzaQJPEDwksybTE2Ups9rUxEXy92T06TiA0cbqcO8wAOwNUVkFW6B0hsYHA+oAX3BS8b/2oHtw==", "dev": true, "license": "MIT", "dependencies": { @@ -7052,23 +7053,24 @@ } }, "node_modules/svelte-time": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/svelte-time/-/svelte-time-0.9.0.tgz", - "integrity": "sha512-XLEflTNZCjJM2ltCJL0NTpN9J2KBZXxupfl9S2sRFx+utSuQXbs3YIM14DIuENsjG1Qn5NtzRb0gviIB7lCMxQ==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/svelte-time/-/svelte-time-2.0.0.tgz", + "integrity": "sha512-sMPmIs2XifPXtBQ6CAObuthLcMOPuGJS8HYDkYLghcpkLZzMAKUC2Kf6uCFOnW9cGy6U47WcYhFnJQ0xYgjDIg==", "license": "MIT", "dependencies": { - "dayjs": "^1.11.10" + "dayjs": "^1.11.13" } }, "node_modules/svelte-toolbelt": { - "version": "0.4.6", - "resolved": "https://registry.npmjs.org/svelte-toolbelt/-/svelte-toolbelt-0.4.6.tgz", - "integrity": "sha512-k8OUvXBUifHZcAlWeY/HLg/4J0v5m2iOfOhn8fDmjt4AP8ZluaDh9eBFus9lFiLX6O5l6vKqI1dKL5wy7090NQ==", + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/svelte-toolbelt/-/svelte-toolbelt-0.7.0.tgz", + "integrity": "sha512-i/Tv4NwAWWqJnK5H0F8y/ubDnogDYlwwyzKhrspTUFzrFuGnYshqd2g4/R43ds841wmaFiSW/HsdsdWhPOlrAA==", "funding": [ "https://github.com/sponsors/huntabyte" ], "dependencies": { "clsx": "^2.1.1", + "runed": "^0.20.0", "style-to-object": "^1.0.8" }, "engines": { @@ -7076,13 +7078,38 @@ "pnpm": ">=8.7.0" }, "peerDependencies": { - "svelte": "^5.0.0-next.126" + "svelte": "^5.0.0" + } + }, + "node_modules/svelte-toolbelt/node_modules/runed": { + "version": "0.20.0", + "resolved": "https://registry.npmjs.org/runed/-/runed-0.20.0.tgz", + "integrity": "sha512-YqPxaUdWL5nUXuSF+/v8a+NkVN8TGyEGbQwTA25fLY35MR/2bvZ1c6sCbudoo1kT4CAJPh4kUkcgGVxW127WKw==", + "funding": [ + "https://github.com/sponsors/huntabyte", + "https://github.com/sponsors/tglide" + ], + "dependencies": { + "esm-env": "^1.0.0" + }, + "peerDependencies": { + "svelte": "^5.7.0" + } + }, + "node_modules/sveltekit-search-params": { + "version": "4.0.0-next.0", + "resolved": "https://registry.npmjs.org/sveltekit-search-params/-/sveltekit-search-params-4.0.0-next.0.tgz", + "integrity": "sha512-haFr5seakusriPePAv2F5/lsOa37rr9AOGDa5JOZ9CaB0Vrl8V8PdtqNWfh/kF0jlANdkdqz5gKK66nhhHT8tg==", + "license": "MIT", + "peerDependencies": { + "@sveltejs/kit": "^2.0.0", + "svelte": "^5.0.0" } }, "node_modules/sveltekit-superforms": { - "version": "2.21.0", - "resolved": "https://registry.npmjs.org/sveltekit-superforms/-/sveltekit-superforms-2.21.0.tgz", - "integrity": "sha512-HCctd/r6h4z7hcWmqD/aOvC0Y7KxIsDYUtHCuYGdP6H2Tg9f5fqf8xHZ7+gu4NHQGcv9L32+xtcsRz0h94MNXg==", + "version": "2.23.1", + "resolved": "https://registry.npmjs.org/sveltekit-superforms/-/sveltekit-superforms-2.23.1.tgz", + "integrity": "sha512-SPj5ac4SMg8SPyP0Zi3ynwXJa7r9U1CTyn+YSyck67zLsjt367Sro4SZnl3yASrLd5kJ6Y57cgIdYJ2aWNArXw==", "funding": [ { "type": "github", @@ -7100,49 +7127,43 @@ "license": "MIT", "dependencies": { "devalue": "^5.1.1", - "just-clone": "^6.2.0", "memoize-weak": "^1.0.2", - "ts-deepmerge": "^7.0.1" + "ts-deepmerge": "^7.0.2" }, "optionalDependencies": { - "@effect/schema": "^0.75.3", "@exodus/schemasafe": "^1.3.0", "@gcornut/valibot-json-schema": "^0.31.0", - "@sinclair/typebox": "^0.34.9", + "@sinclair/typebox": "^0.34.14", "@typeschema/class-validator": "^0.3.0", - "@vinejs/vine": "^2.0.0", - "arktype": "^2.0.0-rc.23", + "@vinejs/vine": "^3.0.0", + "arktype": "^2.0.0", "class-validator": "^0.14.1", - "effect": "^3.9.1", + "effect": "^3.12.5", "joi": "^17.13.3", "json-schema-to-ts": "^3.1.1", "superstruct": "^2.0.2", - "valibot": "^1.0.0-beta.3", - "yup": "^1.4.0", - "zod": "^3.23.8", - "zod-to-json-schema": "^3.23.3" + "valibot": "1.0.0-beta.11", + "yup": "^1.6.1", + "zod": "^3.24.1", + "zod-to-json-schema": "^3.24.1" }, "peerDependencies": { - "@effect/schema": "^0.75.3", "@exodus/schemasafe": "^1.3.0", "@sinclair/typebox": "^0.34.9", "@sveltejs/kit": "1.x || 2.x", "@typeschema/class-validator": "^0.3.0", - "@vinejs/vine": "^1.8.0 || ^2.0.0", + "@vinejs/vine": "^1.8.0 || ^2.0.0 || ^3.0.0", "arktype": ">=2.0.0-rc.23", "class-validator": "^0.14.1", - "effect": "^3.8.2", + "effect": "^3.10.0", "joi": "^17.13.1", "superstruct": "^2.0.2", "svelte": "3.x || 4.x || >=5.0.0-next.51", "valibot": ">=1.0.0-beta.3", "yup": "^1.4.0", - "zod": "^3.23.8" + "zod": "^3.24.1" }, "peerDependenciesMeta": { - "@effect/schema": { - "optional": true - }, "@exodus/schemasafe": { "optional": true }, @@ -7285,9 +7306,9 @@ } }, "node_modules/tailwind-merge": { - "version": "2.5.5", - "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-2.5.5.tgz", - "integrity": "sha512-0LXunzzAZzo0tEPxV3I297ffKZPlKDrjj7NXphC8V5ak9yHC5zRmxnOe2m/Rd/7ivsOMJe3JZ2JVocoDdQTRBA==", + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-2.6.0.tgz", + "integrity": "sha512-P+Vu1qXfzediirmHOC3xKGAYeZtPcV9g76X+xg2FD4tYgR71ewMA35Y3sCz3zhiN/dwefRpJX0yBcgwi1fXNQA==", "license": "MIT", "funding": { "type": "github", @@ -7295,12 +7316,12 @@ } }, "node_modules/tailwind-variants": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/tailwind-variants/-/tailwind-variants-0.3.0.tgz", - "integrity": "sha512-ho2k5kn+LB1fT5XdNS3Clb96zieWxbStE9wNLK7D0AV64kdZMaYzAKo0fWl6fXLPY99ffF9oBJnIj5escEl/8A==", + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/tailwind-variants/-/tailwind-variants-0.3.1.tgz", + "integrity": "sha512-krn67M3FpPwElg4FsZrOQd0U26o7UDH/QOkK8RNaiCCrr052f6YJPBUfNKnPo/s/xRzNPtv1Mldlxsg8Tb46BQ==", "license": "MIT", "dependencies": { - "tailwind-merge": "^2.5.4" + "tailwind-merge": "2.5.4" }, "engines": { "node": ">=16.x", @@ -7310,10 +7331,20 @@ "tailwindcss": "*" } }, + "node_modules/tailwind-variants/node_modules/tailwind-merge": { + "version": "2.5.4", + "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-2.5.4.tgz", + "integrity": "sha512-0q8cfZHMu9nuYP/b5Shb7Y7Sh1B7Nnl5GqNr1U+n2p6+mybvRtayrQ+0042Z5byvTA8ihjlP8Odo8/VnHbZu4Q==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/dcastil" + } + }, "node_modules/tailwindcss": { - "version": "3.4.16", - "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.16.tgz", - "integrity": "sha512-TI4Cyx7gDiZ6r44ewaJmt0o6BrMCT5aK5e0rmJ/G9Xq3w7CX/5VXl/zIPEJZFUK5VEqwByyhqNPycPlvcK4ZNw==", + "version": "3.4.17", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.17.tgz", + "integrity": "sha512-w33E2aCvSDP0tW9RZuNXadXlkHXqFzSkQew/aIa2i/Sj8fThxwovwlXHSPXTbAHwEIhBFXAedUhP2tueAKP8Og==", "license": "MIT", "dependencies": { "@alloc/quick-lru": "^5.2.0", @@ -7488,16 +7519,6 @@ "license": "MIT", "optional": true }, - "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==", - "license": "MIT", - "dependencies": { - "globalyzer": "0.1.0", - "globrex": "^0.1.2" - } - }, "node_modules/tinybench": { "version": "2.9.0", "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", @@ -7506,9 +7527,9 @@ "license": "MIT" }, "node_modules/tinyexec": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.1.tgz", - "integrity": "sha512-WiCJLEECkO18gwqIp6+hJg0//p23HXp4S+gGtAKu3mI2F2/sXC4FvHvXvB0zJVVaTPhx1/tOwdbRsa1sOBIKqQ==", + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.2.tgz", + "integrity": "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==", "license": "MIT" }, "node_modules/tinypool": { @@ -7522,9 +7543,9 @@ } }, "node_modules/tinyrainbow": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-1.2.0.tgz", - "integrity": "sha512-weEDEq7Z5eTHPDh4xjX789+fHfF+P8boiFB+0vbWzpbnbsEr/GRaohi/uMKxg8RZMXnl1ItAi/IUHWMsjDV7kQ==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-2.0.0.tgz", + "integrity": "sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==", "dev": true, "license": "MIT", "engines": { @@ -7584,16 +7605,16 @@ "optional": true }, "node_modules/ts-api-utils": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.4.3.tgz", - "integrity": "sha512-i3eMG77UTMD0hZhgRS562pv83RC6ukSAC2GMNWc+9dieh/+jDM5u5YG+NHX6VNDRHQcHwmsTHctP9LhbC3WxVw==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.0.0.tgz", + "integrity": "sha512-xCt/TOAc+EOHS1XPnijD3/yzpH6qg2xppZO1YDqGoVsNXfQfzHpOdNuXwrwOU8u4ITXJyDCTyt8w5g1sZv9ynQ==", "dev": true, "license": "MIT", "engines": { - "node": ">=16" + "node": ">=18.12" }, "peerDependencies": { - "typescript": ">=4.2.0" + "typescript": ">=4.8.4" } }, "node_modules/ts-deepmerge": { @@ -7722,9 +7743,9 @@ } }, "node_modules/typescript": { - "version": "5.7.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.7.2.tgz", - "integrity": "sha512-i5t66RHxDvVN40HfDd1PsEThGNnlMCMT3jMUuoh9/0TaqWevNontacunWyN02LA9/fIbEWlcHZcgTKb9QoaLfg==", + "version": "5.7.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.7.3.tgz", + "integrity": "sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw==", "devOptional": true, "license": "Apache-2.0", "bin": { @@ -7736,15 +7757,15 @@ } }, "node_modules/typescript-eslint": { - "version": "8.17.0", - "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.17.0.tgz", - "integrity": "sha512-409VXvFd/f1br1DCbuKNFqQpXICoTB+V51afcwG1pn1a3Cp92MqAUges3YjwEdQ0cMUoCIodjVDAYzyD8h3SYA==", + "version": "8.22.0", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.22.0.tgz", + "integrity": "sha512-Y2rj210FW1Wb6TWXzQc5+P+EWI9/zdS57hLEc0gnyuvdzWo8+Y8brKlbj0muejonhMI/xAZCnZZwjbIfv1CkOw==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/eslint-plugin": "8.17.0", - "@typescript-eslint/parser": "8.17.0", - "@typescript-eslint/utils": "8.17.0" + "@typescript-eslint/eslint-plugin": "8.22.0", + "@typescript-eslint/parser": "8.22.0", + "@typescript-eslint/utils": "8.22.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -7754,12 +7775,8 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <5.8.0" } }, "node_modules/ufo": { @@ -7792,31 +7809,31 @@ "license": "MIT" }, "node_modules/unplugin": { - "version": "1.16.0", - "resolved": "https://registry.npmjs.org/unplugin/-/unplugin-1.16.0.tgz", - "integrity": "sha512-5liCNPuJW8dqh3+DM6uNM2EI3MLLpCKp/KY+9pB5M2S2SR2qvvDHhKgBOaTWEbZTAws3CXfB0rKTIolWKL05VQ==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/unplugin/-/unplugin-2.1.0.tgz", + "integrity": "sha512-us4j03/499KhbGP8BU7Hrzrgseo+KdfJYWcbcajCOqsAyb8Gk0Yn2kiUIcZISYCb1JFaZfIuG3b42HmguVOKCQ==", "license": "MIT", "dependencies": { "acorn": "^8.14.0", "webpack-virtual-modules": "^0.6.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=18.12.0" } }, "node_modules/unplugin-icons": { - "version": "0.21.0", - "resolved": "https://registry.npmjs.org/unplugin-icons/-/unplugin-icons-0.21.0.tgz", - "integrity": "sha512-sRic+yj7cCbpDFwrRj+m55ogOZi6PQRDc/WUEmjHLAnc90v0g5UVxE0cVAZgBOsAPCieizZJui/sgrCYrVx8mQ==", + "version": "22.0.0", + "resolved": "https://registry.npmjs.org/unplugin-icons/-/unplugin-icons-22.0.0.tgz", + "integrity": "sha512-+1jIt2wynxL+GISehNok8MIb9RaCufIZCHJs0HKbxOljJL9m4NtOhva+dZhNtSKtfQ62Hwd/RRbniSVuuD4Xow==", "license": "MIT", "dependencies": { "@antfu/install-pkg": "^0.5.0", "@antfu/utils": "^0.7.10", - "@iconify/utils": "^2.1.33", - "debug": "^4.3.7", + "@iconify/utils": "^2.2.1", + "debug": "^4.4.0", "kolorist": "^1.8.0", "local-pkg": "^0.5.1", - "unplugin": "^1.16.0" + "unplugin": "^2.1.0" }, "funding": { "url": "https://github.com/sponsors/antfu" @@ -7898,9 +7915,9 @@ "license": "MIT" }, "node_modules/valibot": { - "version": "1.0.0-beta.9", - "resolved": "https://registry.npmjs.org/valibot/-/valibot-1.0.0-beta.9.tgz", - "integrity": "sha512-yEX8gMAZ2R1yI2uwOO4NCtVnJQx36zn3vD0omzzj9FhcoblvPukENIiRZXKZwCnqSeV80bMm8wNiGhQ0S8fiww==", + "version": "1.0.0-beta.11", + "resolved": "https://registry.npmjs.org/valibot/-/valibot-1.0.0-beta.11.tgz", + "integrity": "sha512-Ztl5Iks1Ql7Z6CwkS5oyqguN3G8tmUiNlsHpqbDt6DLMpm+eu+n8Q7f921gI3uHvNZ8xDVkd4cEJP5t+lELOfw==", "license": "MIT", "optional": true, "peerDependencies": { @@ -7933,12 +7950,12 @@ } }, "node_modules/vite": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/vite/-/vite-6.0.2.tgz", - "integrity": "sha512-XdQ+VsY2tJpBsKGs0wf3U/+azx8BBpYRHFAyKm5VeEZNOJZRB63q7Sc8Iup3k0TrN3KO6QgyzFf+opSbfY1y0g==", + "version": "6.0.11", + "resolved": "https://registry.npmjs.org/vite/-/vite-6.0.11.tgz", + "integrity": "sha512-4VL9mQPKoHy4+FE0NnRE/kbY51TOfaknxAjt3fJbGJxhIpBZiqVzlZDEesWWsuREXHwNdAoOFZ9MkPEVXczHwg==", "license": "MIT", "dependencies": { - "esbuild": "^0.24.0", + "esbuild": "^0.24.2", "postcss": "^8.4.49", "rollup": "^4.23.0" }, @@ -8004,16 +8021,16 @@ } }, "node_modules/vite-node": { - "version": "2.1.6", - "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-2.1.6.tgz", - "integrity": "sha512-DBfJY0n9JUwnyLxPSSUmEePT21j8JZp/sR9n+/gBwQU6DcQOioPdb8/pibWfXForbirSagZCilseYIwaL3f95A==", + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-3.0.4.tgz", + "integrity": "sha512-7JZKEzcYV2Nx3u6rlvN8qdo3QV7Fxyt6hx+CCKz9fbWxdX5IvUOmTWEAxMrWxaiSf7CKGLJQ5rFu8prb/jBjOA==", "dev": true, "license": "MIT", "dependencies": { "cac": "^6.7.14", - "debug": "^4.3.7", - "es-module-lexer": "^1.5.4", - "pathe": "^1.1.2", + "debug": "^4.4.0", + "es-module-lexer": "^1.6.0", + "pathe": "^2.0.2", "vite": "^5.0.0 || ^6.0.0" }, "bin": { @@ -8026,6 +8043,13 @@ "url": "https://opencollective.com/vitest" } }, + "node_modules/vite-node/node_modules/pathe": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.2.tgz", + "integrity": "sha512-15Ztpk+nov8DR524R4BF7uEuzESgzUEAV4Ah7CUMNGXdE5ELuvxElxGXndBl32vMSsWa1jpNf22Z+Er3sKwq+w==", + "dev": true, + "license": "MIT" + }, "node_modules/vite/node_modules/fsevents": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", @@ -8059,31 +8083,31 @@ } }, "node_modules/vitest": { - "version": "2.1.6", - "resolved": "https://registry.npmjs.org/vitest/-/vitest-2.1.6.tgz", - "integrity": "sha512-isUCkvPL30J4c5O5hgONeFRsDmlw6kzFEdLQHLezmDdKQHy8Ke/B/dgdTMEgU0vm+iZ0TjW8GuK83DiahBoKWQ==", + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-3.0.4.tgz", + "integrity": "sha512-6XG8oTKy2gnJIFTHP6LD7ExFeNLxiTkK3CfMvT7IfR8IN+BYICCf0lXUQmX7i7JoxUP8QmeP4mTnWXgflu4yjw==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/expect": "2.1.6", - "@vitest/mocker": "2.1.6", - "@vitest/pretty-format": "^2.1.6", - "@vitest/runner": "2.1.6", - "@vitest/snapshot": "2.1.6", - "@vitest/spy": "2.1.6", - "@vitest/utils": "2.1.6", + "@vitest/expect": "3.0.4", + "@vitest/mocker": "3.0.4", + "@vitest/pretty-format": "^3.0.4", + "@vitest/runner": "3.0.4", + "@vitest/snapshot": "3.0.4", + "@vitest/spy": "3.0.4", + "@vitest/utils": "3.0.4", "chai": "^5.1.2", - "debug": "^4.3.7", + "debug": "^4.4.0", "expect-type": "^1.1.0", - "magic-string": "^0.30.12", - "pathe": "^1.1.2", + "magic-string": "^0.30.17", + "pathe": "^2.0.2", "std-env": "^3.8.0", "tinybench": "^2.9.0", - "tinyexec": "^0.3.1", - "tinypool": "^1.0.1", - "tinyrainbow": "^1.2.0", + "tinyexec": "^0.3.2", + "tinypool": "^1.0.2", + "tinyrainbow": "^2.0.0", "vite": "^5.0.0 || ^6.0.0", - "vite-node": "2.1.6", + "vite-node": "3.0.4", "why-is-node-running": "^2.3.0" }, "bin": { @@ -8097,9 +8121,10 @@ }, "peerDependencies": { "@edge-runtime/vm": "*", + "@types/debug": "^4.1.12", "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", - "@vitest/browser": "2.1.6", - "@vitest/ui": "2.1.6", + "@vitest/browser": "3.0.4", + "@vitest/ui": "3.0.4", "happy-dom": "*", "jsdom": "*" }, @@ -8107,6 +8132,9 @@ "@edge-runtime/vm": { "optional": true }, + "@types/debug": { + "optional": true + }, "@types/node": { "optional": true }, @@ -8124,6 +8152,13 @@ } } }, + "node_modules/vitest/node_modules/pathe": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.2.tgz", + "integrity": "sha512-15Ztpk+nov8DR524R4BF7uEuzESgzUEAV4Ah7CUMNGXdE5ELuvxElxGXndBl32vMSsWa1jpNf22Z+Er3sKwq+w==", + "dev": true, + "license": "MIT" + }, "node_modules/webidl-conversions": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", @@ -8472,9 +8507,9 @@ } }, "node_modules/yup": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/yup/-/yup-1.4.0.tgz", - "integrity": "sha512-wPbgkJRCqIf+OHyiTBQoJiP5PFuAXaWiJK6AmYkzQAh5/c2K9hzSApBZG5wV9KoKSePF7sAxmNSvh/13YHkFDg==", + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/yup/-/yup-1.6.1.tgz", + "integrity": "sha512-JED8pB50qbA4FOkDol0bYF/p60qSEDQqBD0/qeIrUCG1KbPBIQ776fCUNb9ldbPcSTxA69g/47XTo4TqWiuXOA==", "license": "MIT", "optional": true, "dependencies": { @@ -8491,9 +8526,9 @@ "license": "MIT" }, "node_modules/zod": { - "version": "3.23.8", - "resolved": "https://registry.npmjs.org/zod/-/zod-3.23.8.tgz", - "integrity": "sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==", + "version": "3.24.1", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.24.1.tgz", + "integrity": "sha512-muH7gBL9sI1nciMZV67X5fTKKBLtwpZ5VBp1vsOQzj1MhrBZ4wlVCm3gedKZWLp0Oyel8sIGfeiz54Su+OVT+A==", "license": "MIT", "optional": true, "funding": { @@ -8501,13 +8536,13 @@ } }, "node_modules/zod-to-json-schema": { - "version": "3.23.5", - "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.23.5.tgz", - "integrity": "sha512-5wlSS0bXfF/BrL4jPAbz9da5hDlDptdEppYfe+x4eIJ7jioqKG9uUxOwPzqof09u/XeVdrgFu29lZi+8XNDJtA==", + "version": "3.24.1", + "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.24.1.tgz", + "integrity": "sha512-3h08nf3Vw3Wl3PK+q3ow/lIil81IT2Oa7YpQyUUDsEWbXveMesdfK1xBd2RhCkynwZndAxixji/7SYJJowr62w==", "license": "ISC", "optional": true, "peerDependencies": { - "zod": "^3.23.3" + "zod": "^3.24.1" } } } diff --git a/src/Exceptionless.Web/ClientApp/package.json b/src/Exceptionless.Web/ClientApp/package.json index 961b48a3a0..ffd78e20c3 100644 --- a/src/Exceptionless.Web/ClientApp/package.json +++ b/src/Exceptionless.Web/ClientApp/package.json @@ -19,63 +19,65 @@ "generate-models": "swagger-typescript-api -p http://localhost:5200/docs/v2/swagger.json -o ./src/lib/generated -n api.ts --no-client --templates api-templates", "generate-templates": "swagger-typescript-api generate-templates -o api-templates", "test:integration": "playwright test", - "test:unit": "vitest", + "test:unit": "vitest run", "upgrade": "ncu -i" }, "devDependencies": { - "@iconify-json/lucide": "^1.2.17", - "@playwright/test": "^1.49.0", - "@sveltejs/adapter-static": "^3.0.6", - "@sveltejs/kit": "^2.9.0", - "@sveltejs/vite-plugin-svelte": "^5.0.1", + "@iconify-json/lucide": "^1.2.25", + "@playwright/test": "^1.50.0", + "@sveltejs/adapter-static": "^3.0.8", + "@sveltejs/kit": "^2.16.1", + "@sveltejs/vite-plugin-svelte": "^5.0.3", "@types/eslint": "^9.6.1", - "@types/node": "^22.10.1", + "@types/node": "^22.12.0", "@types/throttle-debounce": "^5.0.2", "autoprefixer": "^10.4.20", "cross-env": "^7.0.3", - "eslint": "^9.16.0", - "eslint-config-prettier": "^9.1.0", - "eslint-plugin-perfectionist": "^4.2.0", + "eslint": "^9.19.0", + "eslint-config-prettier": "^10.0.1", + "eslint-plugin-perfectionist": "^4.7.0", "eslint-plugin-svelte": "^2.46.1", "npm-run-all": "^4.1.5", - "postcss": "^8.4.49", - "prettier": "^3.4.1", - "prettier-plugin-svelte": "^3.3.2", - "prettier-plugin-tailwindcss": "^0.6.9", - "svelte": "^5.5.3", - "svelte-check": "^4.1.1", + "postcss": "^8.5.1", + "prettier": "^3.4.2", + "prettier-plugin-svelte": "^3.3.3", + "prettier-plugin-tailwindcss": "^0.6.11", + "svelte": "^5.19.6", + "svelte-check": "^4.1.4", "swagger-typescript-api": "^13.0.23", "tslib": "^2.8.1", - "typescript": "^5.7.2", - "typescript-eslint": "^8.17.0", - "vite": "^6.0.2", - "vitest": "2.1.6" + "typescript": "^5.7.3", + "typescript-eslint": "^8.22.0", + "vite": "^6.0.11", + "vitest": "3.0.4" }, "dependencies": { "@exceptionless/browser": "^3.1.0", - "@exceptionless/fetchclient": "^0.29.0", - "@iconify-json/mdi": "^1.2.1", + "@exceptionless/fetchclient": "^0.40.0", + "@iconify-json/mdi": "^1.2.3", "@tanstack/svelte-query": "https://pkg.pr.new/@tanstack/svelte-query@28f98f9", "@tanstack/svelte-query-devtools": "https://pkg.pr.new/@tanstack/svelte-query-devtools@28f98f9", "@tanstack/svelte-table": "^9.0.0-alpha.10", "@typeschema/class-validator": "^0.3.0", - "bits-ui": "^1.0.0-next.64", + "bits-ui": "^1.0.0-next.78", "class-validator": "^0.14.1", "clsx": "^2.1.1", - "formsnap": "^2.0.0-next.1", - "mode-watcher": "^0.5.0", + "formsnap": "^2.0.0", + "lucide-svelte": "^0.474.0", + "mode-watcher": "^0.5.1", "oidc-client-ts": "^3.1.0", "pretty-ms": "^9.2.0", - "runed": "^0.16.1", + "runed": "^0.23.2", "svelte-sonner": "^0.3.28", - "svelte-time": "^0.9.0", - "sveltekit-superforms": "^2.21.0", - "tailwind-merge": "^2.5.5", - "tailwind-variants": "^0.3.0", - "tailwindcss": "^3.4.16", + "svelte-time": "^2.0.0", + "sveltekit-search-params": "^4.0.0-next.0", + "sveltekit-superforms": "^2.23.1", + "tailwind-merge": "^2.6.0", + "tailwind-variants": "^0.3.1", + "tailwindcss": "^3.4.17", "tailwindcss-animate": "^1.0.7", "throttle-debounce": "^5.0.2", - "unplugin-icons": "^0.21.0" + "unplugin-icons": "^22.0.0" }, "type": "module" } diff --git a/src/Exceptionless.Web/ClientApp/resources.md b/src/Exceptionless.Web/ClientApp/resources.md index 2eb7bcfc13..18e083b05c 100644 --- a/src/Exceptionless.Web/ClientApp/resources.md +++ b/src/Exceptionless.Web/ClientApp/resources.md @@ -102,6 +102,13 @@ ## Networking +Analyze current naming patterns +Define consistent naming convention rules: +Remove redundant terms +Use HTTP verb prefixes +Keep resource names singular/plural based on return type +Order: [httpVerb][Resource][Qualifier][Query] + - - - diff --git a/src/Exceptionless.Web/ClientApp/src/lib/features/auth/index.svelte.ts b/src/Exceptionless.Web/ClientApp/src/lib/features/auth/index.svelte.ts index a3c2b51dee..ed9cd0aa2c 100644 --- a/src/Exceptionless.Web/ClientApp/src/lib/features/auth/index.svelte.ts +++ b/src/Exceptionless.Web/ClientApp/src/lib/features/auth/index.svelte.ts @@ -1,13 +1,29 @@ import { goto } from '$app/navigation'; -import { page } from '$app/stores'; +import { page } from '$app/state'; import { env } from '$env/dynamic/public'; -import { AuthJSONSerializer, persisted } from '$shared/persisted.svelte'; import { useFetchClient } from '@exceptionless/fetchclient'; -import { get } from 'svelte/store'; +import { PersistedState } from 'runed'; import type { Login, TokenResult } from './models'; -export const accessToken = persisted('satellizer_token', null, new AuthJSONSerializer()); +const authSerializer = { + deserialize: (value: null | string): null | string => { + if (value === '') { + return null; + } + + return value; + }, + serialize: (value: null | string): string => { + if (value === null) { + return ''; + } + + return value; + } +}; + +export const accessToken = new PersistedState('satellizer_token', null, { serializer: authSerializer }); export const enableAccountCreation = env.PUBLIC_ENABLE_ACCOUNT_CREATION === 'true'; export const facebookClientId = env.PUBLIC_FACEBOOK_APPID; @@ -67,7 +83,7 @@ export async function googleLogin(redirectUrl?: string) { } export async function gotoLogin() { - const { url } = get(page); + const url = page.url; const isAuthPath = url.pathname.startsWith('/next/login') || url.pathname.startsWith('/next/logout'); const redirect = url.pathname === '/next/' || isAuthPath ? '/next/login' : `/next/login?redirect=${url.pathname}`; await goto(redirect, { replaceState: true }); @@ -98,7 +114,7 @@ export async function login(email: string, password: string) { }); if (response.ok && response.data?.token) { - accessToken.value = response.data.token; + accessToken.current = response.data.token; } else if (response.status === 401) { response.problem.setErrorMessage('Invalid email or password'); } @@ -109,7 +125,7 @@ export async function login(email: string, password: string) { export async function logout() { const client = useFetchClient(); await client.get('auth/logout', { expectedStatusCodes: [200, 401] }); - accessToken.value = null; + accessToken.current = null; } async function oauthLogin(options: { @@ -158,7 +174,7 @@ async function oauthLogin(options: { }); if (response.ok && response.data?.token) { - accessToken.value = response.data.token; + accessToken.current = response.data.token; await goto(options.redirectUrl || '/'); } } diff --git a/src/Exceptionless.Web/ClientApp/src/lib/features/events/api.svelte.ts b/src/Exceptionless.Web/ClientApp/src/lib/features/events/api.svelte.ts index 0d84780661..62664b15ac 100644 --- a/src/Exceptionless.Web/ClientApp/src/lib/features/events/api.svelte.ts +++ b/src/Exceptionless.Web/ClientApp/src/lib/features/events/api.svelte.ts @@ -1,13 +1,15 @@ import type { WebSocketMessageValue } from '$features/websockets/models'; +import type { CountResult, WorkInProgressResult } from '$shared/models'; import { accessToken } from '$features/auth/index.svelte'; +import { DEFAULT_OFFSET } from '$shared/api/api.svelte'; import { type ProblemDetails, useFetchClient } from '@exceptionless/fetchclient'; -import { createQuery, QueryClient, useQueryClient } from '@tanstack/svelte-query'; +import { createMutation, createQuery, QueryClient, useQueryClient } from '@tanstack/svelte-query'; import type { PersistentEvent } from './models'; export async function invalidatePersistentEventQueries(queryClient: QueryClient, message: WebSocketMessageValue<'PersistentEventChanged'>) { - const { id, stack_id } = message; + const { id, project_id, stack_id } = message; if (id) { await queryClient.invalidateQueries({ queryKey: queryKeys.id(id) }); } @@ -16,29 +18,56 @@ export async function invalidatePersistentEventQueries(queryClient: QueryClient, await queryClient.invalidateQueries({ queryKey: queryKeys.stacks(stack_id) }); } + if (project_id) { + await queryClient.invalidateQueries({ queryKey: queryKeys.projectsCount(project_id) }); + } + if (!id && !stack_id) { await queryClient.invalidateQueries({ queryKey: queryKeys.type }); + } else { + await queryClient.invalidateQueries({ queryKey: queryKeys.count() }); } } export const queryKeys = { + count: () => [...queryKeys.type, 'count'] as const, + deleteEvent: (ids: string[] | undefined) => [...queryKeys.type, 'delete', ...(ids ?? [])] as const, id: (id: string | undefined) => [...queryKeys.type, id] as const, + projectsCount: (id: string | undefined) => [...queryKeys.type, 'projects', id] as const, stacks: (id: string | undefined) => [...queryKeys.type, 'stacks', id] as const, + stacksCount: (id: string | undefined) => [...queryKeys.stacks(id), 'count'] as const, type: ['PersistentEvent'] as const }; -export interface GetEventByIdProps { - id: string | undefined; +export interface DeleteEventsRequest { + route: { + ids: string[] | undefined; + }; } -export interface GetEventsByStackIdProps { - limit?: number; - stackId: string | undefined; +export interface GetCountRequest { + params?: { + aggregations?: string; + filter?: string; + mode?: 'stack_new'; + offset?: string; + time?: string; + }; +} + +export interface GetEventRequest { + params?: { + offset?: string; + time?: string; + }; + route: { + id: string | undefined; + }; } export type GetEventsMode = 'stack_frequent' | 'stack_new' | 'stack_recent' | 'stack_users' | 'summary' | null; -export interface IGetEventsParams { +export interface GetEventsParams { after?: string; before?: string; filter?: string; @@ -50,26 +79,161 @@ export interface IGetEventsParams { time?: string; } -export function getEventByIdQuery(props: GetEventByIdProps) { +export interface GetProjectCountRequest { + params?: { + aggregations?: string; + filter?: string; + mode?: 'stack_new'; + offset?: string; + time?: string; + }; + route: { + projectId: string | undefined; + }; +} + +export interface GetStackCountRequest { + params?: { + aggregations?: string; + filter?: string; + mode?: 'stack_new'; + offset?: string; + time?: string; + }; + route: { + stackId: string | undefined; + }; +} + +export interface GetStackEventsRequest { + params?: { + after?: string; + before?: string; + filter?: string; + limit?: number; + mode?: GetEventsMode; + offset?: string; + sort?: string; + time?: string; + }; + route: { + stackId: string | undefined; + }; +} + +export function deleteEvent(request: DeleteEventsRequest) { + const queryClient = useQueryClient(); + return createMutation(() => ({ + enabled: () => !!accessToken.current && !!request.route.ids?.length, + mutationFn: async () => { + const client = useFetchClient(); + const response = await client.deleteJSON(`events/${request.route.ids?.join(',')}`); + + return response.data!; + }, + mutationKey: queryKeys.deleteEvent(request.route.ids), + onError: () => { + request.route.ids?.forEach((id) => queryClient.invalidateQueries({ queryKey: queryKeys.id(id) })); + }, + onSuccess: () => { + request.route.ids?.forEach((id) => queryClient.invalidateQueries({ queryKey: queryKeys.id(id) })); + } + })); +} + +export function getCountQuery(request: GetCountRequest) { + const queryClient = useQueryClient(); + + return createQuery(() => ({ + enabled: () => !!accessToken.current, + queryClient, + queryFn: async ({ signal }: { signal: AbortSignal }) => { + const client = useFetchClient(); + const response = await client.getJSON('events/count', { + params: { + ...(DEFAULT_OFFSET ? { offset: DEFAULT_OFFSET } : {}), + ...request.params + }, + signal + }); + + return response.data!; + }, + queryKey: queryKeys.count() + })); +} + +export function getEventQuery(request: GetEventRequest) { return createQuery(() => ({ - enabled: () => !!accessToken.value && !!props.id, + enabled: () => !!accessToken.current && !!request.route.id, queryFn: async ({ signal }: { signal: AbortSignal }) => { const client = useFetchClient(); - const response = await client.getJSON(`events/${props.id}`, { + const response = await client.getJSON(`events/${request.route.id}`, { + params: { + ...(DEFAULT_OFFSET ? { offset: DEFAULT_OFFSET } : {}), + ...request.params + }, + signal + }); + + return response.data!; + }, + queryKey: queryKeys.id(request.route.id) + })); +} + +export function getProjectCountQuery(request: GetProjectCountRequest) { + const queryClient = useQueryClient(); + + return createQuery(() => ({ + enabled: () => !!accessToken.current && !!request.route.projectId, + queryClient, + queryFn: async ({ signal }: { signal: AbortSignal }) => { + const client = useFetchClient(); + const response = await client.getJSON(`/projects/${request.route.projectId}/events/count`, { + params: { + ...(DEFAULT_OFFSET ? { offset: DEFAULT_OFFSET } : {}), + ...request.params + }, + signal + }); + + return response.data!; + }, + queryKey: queryKeys.projectsCount(request.route.projectId) + })); +} + +export function getStackCountQuery(request: GetStackCountRequest) { + const queryClient = useQueryClient(); + + return createQuery(() => ({ + enabled: () => !!accessToken.current && !!request.route.stackId, + queryClient, + queryFn: async ({ signal }: { signal: AbortSignal }) => { + const client = useFetchClient(); + const response = await client.getJSON('events/count', { + params: { + ...(DEFAULT_OFFSET ? { offset: DEFAULT_OFFSET } : {}), + ...request.params, + filter: request.params?.filter?.includes(`stack:${request.route.stackId}`) + ? request.params.filter + : [request.params?.filter, `stack:${request.route.stackId}`].filter(Boolean).join(' ') + }, signal }); return response.data!; }, - queryKey: queryKeys.id(props.id) + queryKey: queryKeys.stacksCount(request.route.stackId) })); } -export function getEventsByStackIdQuery(props: GetEventsByStackIdProps) { +export function getStackEventsQuery(request: GetStackEventsRequest) { const queryClient = useQueryClient(); return createQuery(() => ({ - enabled: () => !!accessToken.value && !!props.stackId, + enabled: () => !!accessToken.current && !!request.route.stackId, onSuccess: (data: PersistentEvent[]) => { data.forEach((event) => { queryClient.setQueryData(queryKeys.id(event.id!), event); @@ -78,15 +242,16 @@ export function getEventsByStackIdQuery(props: GetEventsByStackIdProps) { queryClient, queryFn: async ({ signal }: { signal: AbortSignal }) => { const client = useFetchClient(); - const response = await client.getJSON(`stacks/${props.stackId}/events`, { + const response = await client.getJSON(`stacks/${request.route.stackId}/events`, { params: { - limit: props.limit ?? 10 + ...(DEFAULT_OFFSET ? { offset: DEFAULT_OFFSET } : {}), + ...request.params }, signal }); return response.data!; }, - queryKey: queryKeys.stacks(props.stackId) + queryKey: queryKeys.stacks(request.route.stackId) })); } diff --git a/src/Exceptionless.Web/ClientApp/src/lib/features/events/components/dialogs/remove-event-dialog.svelte b/src/Exceptionless.Web/ClientApp/src/lib/features/events/components/dialogs/remove-event-dialog.svelte new file mode 100644 index 0000000000..1a5f17027a --- /dev/null +++ b/src/Exceptionless.Web/ClientApp/src/lib/features/events/components/dialogs/remove-event-dialog.svelte @@ -0,0 +1,52 @@ + + + + + + + Delete + {#if count === 1} + Event + {:else} + Events + {/if} + + + Are you sure you want to delete + {#if count === 1} + this event + {:else} + events + {/if}? + + + + Cancel + + Delete + {#if count === 1} + Event + {:else} + Events + {/if} + + + + diff --git a/src/Exceptionless.Web/ClientApp/src/lib/features/events/components/EventsDrawer.svelte b/src/Exceptionless.Web/ClientApp/src/lib/features/events/components/events-drawer.svelte similarity index 54% rename from src/Exceptionless.Web/ClientApp/src/lib/features/events/components/EventsDrawer.svelte rename to src/Exceptionless.Web/ClientApp/src/lib/features/events/components/events-drawer.svelte index c2a008c6bc..cedd3b05b9 100644 --- a/src/Exceptionless.Web/ClientApp/src/lib/features/events/components/EventsDrawer.svelte +++ b/src/Exceptionless.Web/ClientApp/src/lib/features/events/components/events-drawer.svelte @@ -1,36 +1,35 @@ -{#if eventResponse.isLoading} -

Loading...

-{:else if eventResponse.isSuccess} - - - + + + + + + {#if eventResponse.isSuccess} Occurred On () - - {#if projectResponse.data} - - Project - - {projectResponse.data.name} - - {/if} - {#if stackResponse.data} - - Stack - - {stackResponse.data.title} - + {:else} + + + {/if} + + + {#if projectResponse.isSuccess} + Project + + {projectResponse.data.name} + {:else} + + + {/if} - - + + + +{#if eventResponse.isSuccess} {#each tabs as tab (tab)} @@ -174,5 +172,16 @@ {/each} {:else} - + + + + {#each { length: 5 } as name, index (`${name}-${index}`)} + + + + + + {/each} + + {/if} diff --git a/src/Exceptionless.Web/ClientApp/src/lib/features/events/components/ExtendedDataItem.svelte b/src/Exceptionless.Web/ClientApp/src/lib/features/events/components/extended-data-item.svelte similarity index 76% rename from src/Exceptionless.Web/ClientApp/src/lib/features/events/components/ExtendedDataItem.svelte rename to src/Exceptionless.Web/ClientApp/src/lib/features/events/components/extended-data-item.svelte index 04ee27573d..df84c2aa6f 100644 --- a/src/Exceptionless.Web/ClientApp/src/lib/features/events/components/ExtendedDataItem.svelte +++ b/src/Exceptionless.Web/ClientApp/src/lib/features/events/components/extended-data-item.svelte @@ -1,10 +1,10 @@ diff --git a/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/filters/ClickableBooleanFilter.svelte b/src/Exceptionless.Web/ClientApp/src/lib/features/events/components/filters/boolean-faceted-filter-trigger.svelte similarity index 74% rename from src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/filters/ClickableBooleanFilter.svelte rename to src/Exceptionless.Web/ClientApp/src/lib/features/events/components/filters/boolean-faceted-filter-trigger.svelte index 241f750dd5..2c7cf9afe1 100644 --- a/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/filters/ClickableBooleanFilter.svelte +++ b/src/Exceptionless.Web/ClientApp/src/lib/features/events/components/filters/boolean-faceted-filter-trigger.svelte @@ -1,9 +1,9 @@ - { filter.value = value; filterChanged(filter); @@ -19,4 +19,4 @@ }} value={filter.value} {...props} -> +> diff --git a/src/Exceptionless.Web/ClientApp/src/lib/features/events/components/filters/date-faceted-filter-builder.svelte b/src/Exceptionless.Web/ClientApp/src/lib/features/events/components/filters/date-faceted-filter-builder.svelte new file mode 100644 index 0000000000..f03561e2cd --- /dev/null +++ b/src/Exceptionless.Web/ClientApp/src/lib/features/events/components/filters/date-faceted-filter-builder.svelte @@ -0,0 +1,27 @@ + diff --git a/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/filters/ClickableDateFilter.svelte b/src/Exceptionless.Web/ClientApp/src/lib/features/events/components/filters/date-faceted-filter-trigger.svelte similarity index 74% rename from src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/filters/ClickableDateFilter.svelte rename to src/Exceptionless.Web/ClientApp/src/lib/features/events/components/filters/date-faceted-filter-trigger.svelte index b1a624be11..e905bbd6ed 100644 --- a/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/filters/ClickableDateFilter.svelte +++ b/src/Exceptionless.Web/ClientApp/src/lib/features/events/components/filters/date-faceted-filter-trigger.svelte @@ -1,9 +1,9 @@ - { filter.value = value; filterChanged(filter); @@ -45,4 +45,4 @@ {title} value={filter.value as string} {...props} -> +> diff --git a/src/Exceptionless.Web/ClientApp/src/lib/features/events/components/filters.ts b/src/Exceptionless.Web/ClientApp/src/lib/features/events/components/filters/helpers.ts similarity index 74% rename from src/Exceptionless.Web/ClientApp/src/lib/features/events/components/filters.ts rename to src/Exceptionless.Web/ClientApp/src/lib/features/events/components/filters/helpers.ts index 480438854f..e482a27402 100644 --- a/src/Exceptionless.Web/ClientApp/src/lib/features/events/components/filters.ts +++ b/src/Exceptionless.Web/ClientApp/src/lib/features/events/components/filters/helpers.ts @@ -1,4 +1,8 @@ -import { getKeywordFilter, getOrganizationFilter, getProjectFilter, getStackFilter, type IFilter } from '$comp/filters/filters.svelte'; +import type { IFilter } from '$comp/faceted-filter'; + +import { organization } from '$features/organizations/context.svelte'; + +import { getKeywordFilter, getProjectFilter, getStackFilter } from './models.svelte'; export function shouldRefreshPersistentEventChanged( filters: IFilter[], @@ -37,10 +41,7 @@ export function shouldRefreshPersistentEventChanged( } if (organization_id) { - const organizationFilter = getOrganizationFilter(filters); - if (organizationFilter && !organizationFilter.isEmpty()) { - return organizationFilter.value === organization_id; - } + return organization.current === organization_id; } return true; diff --git a/src/Exceptionless.Web/ClientApp/src/lib/features/events/components/filters/index.ts b/src/Exceptionless.Web/ClientApp/src/lib/features/events/components/filters/index.ts new file mode 100644 index 0000000000..8f0b912b93 --- /dev/null +++ b/src/Exceptionless.Web/ClientApp/src/lib/features/events/components/filters/index.ts @@ -0,0 +1,112 @@ +import BooleanFacetedFilterBuilder from './boolean-faceted-filter-builder.svelte'; +import BooleanFacetedFilterTrigger from './boolean-faceted-filter-trigger.svelte'; +import BooleanFacetedFilter from './boolean-faceted-filter.svelte'; +import DateFacetedFilterBuilder from './date-faceted-filter-builder.svelte'; +import DateFacetedFilterTrigger from './date-faceted-filter-trigger.svelte'; +import DateFacetedFilter from './date-faceted-filter.svelte'; +import KeywordFacetedFilterBuilder from './keyword-faceted-filter-builder.svelte'; +import KeywordFacetedFilter from './keyword-faceted-filter.svelte'; +export { + type BooleanFilter, + type DateFilter, + KeywordFilter, + type NumberFilter, + ProjectFilter, + ReferenceFilter, + SessionFilter, + StatusFilter, + type StringFilter, + TypeFilter, + type VersionFilter +} from './models.svelte'; +import NumberFacetedFilterBuilder from './number-faceted-filter-builder.svelte'; +import NumberFacetedFilterTrigger from './number-faceted-filter-trigger.svelte'; +import NumberFacetedFilter from './number-faceted-filter.svelte'; +import ProjectFacetedFilterBuilder from './project-faceted-filter-builder.svelte'; +import ProjectFacetedFilterTrigger from './project-faceted-filter-trigger.svelte'; +import ProjectFacetedFilter from './project-faceted-filter.svelte'; +import ReferenceFacetedFilterBuilder from './reference-faceted-filter-builder.svelte'; +import ReferenceFacetedFilterTrigger from './reference-faceted-filter-trigger.svelte'; +import ReferenceFacetedFilter from './reference-faceted-filter.svelte'; +import SessionFacetedFilterBuilder from './session-faceted-filter-builder.svelte'; +import SessionFacetedFilterTrigger from './session-faceted-filter-trigger.svelte'; +import SessionFacetedFilter from './session-faceted-filter.svelte'; +import StatusFacetedFilterBuilder from './status-faceted-filter-builder.svelte'; +import StatusFacetedFilterTrigger from './status-faceted-filter-trigger.svelte'; +import StatusFacetedFilter from './status-faceted-filter.svelte'; +import StringFacetedFilterBuilder from './string-faceted-filter-builder.svelte'; +import StringFacetedFilterTrigger from './string-faceted-filter-trigger.svelte'; +import StringFacetedFilter from './string-faceted-filter.svelte'; +import TypeFacetedFilterBuilder from './type-faceted-filter-builder.svelte'; +import TypeFacetedFilterTrigger from './type-faceted-filter-trigger.svelte'; +import TypeFacetedFilter from './type-faceted-filter.svelte'; +import VersionFacetedFilterBuilder from './version-faceted-filter-builder.svelte'; +import VersionFacetedFilterTrigger from './version-faceted-filter-trigger.svelte'; +import VersionFacetedFilter from './version-faceted-filter.svelte'; + +export { + BooleanFacetedFilter as Boolean, + BooleanFacetedFilterBuilder as BooleanBuilder, + BooleanFacetedFilter, + BooleanFacetedFilterBuilder, + BooleanFacetedFilterTrigger, + BooleanFacetedFilterTrigger as BooleanTrigger, + DateFacetedFilter as Date, + DateFacetedFilterBuilder as DateBuilder, + DateFacetedFilter, + DateFacetedFilterBuilder, + DateFacetedFilterTrigger, + DateFacetedFilterTrigger as DateTrigger, + KeywordFacetedFilter as Keyword, + KeywordFacetedFilterBuilder as KeywordBuilder, + KeywordFacetedFilter, + KeywordFacetedFilterBuilder, + NumberFacetedFilter as Number, + NumberFacetedFilterBuilder as NumberBuilder, + NumberFacetedFilter, + NumberFacetedFilterBuilder, + NumberFacetedFilterTrigger, + NumberFacetedFilterTrigger as NumberTrigger, + ProjectFacetedFilter as Project, + ProjectFacetedFilterBuilder as ProjectBuilder, + ProjectFacetedFilter, + ProjectFacetedFilterBuilder, + ProjectFacetedFilterTrigger, + ProjectFacetedFilterTrigger as ProjectTrigger, + ReferenceFacetedFilter as Reference, + ReferenceFacetedFilterBuilder as ReferenceBuilder, + ReferenceFacetedFilter, + ReferenceFacetedFilterBuilder, + ReferenceFacetedFilterTrigger, + ReferenceFacetedFilterTrigger as ReferenceTrigger, + SessionFacetedFilter as Session, + SessionFacetedFilterBuilder as SessionBuilder, + SessionFacetedFilter, + SessionFacetedFilterBuilder, + SessionFacetedFilterTrigger, + SessionFacetedFilterTrigger as SessionTrigger, + StatusFacetedFilter as Status, + StatusFacetedFilterBuilder as StatusBuilder, + StatusFacetedFilter, + StatusFacetedFilterBuilder, + StatusFacetedFilterTrigger, + StatusFacetedFilterTrigger as StatusTrigger, + StringFacetedFilter as String, + StringFacetedFilterBuilder as StringBuilder, + StringFacetedFilter, + StringFacetedFilterBuilder, + StringFacetedFilterTrigger, + StringFacetedFilterTrigger as StringTrigger, + TypeFacetedFilter as Type, + TypeFacetedFilterBuilder as TypeBuilder, + TypeFacetedFilter, + TypeFacetedFilterBuilder, + TypeFacetedFilterTrigger, + TypeFacetedFilterTrigger as TypeTrigger, + VersionFacetedFilter as Version, + VersionFacetedFilterBuilder as VersionBuilder, + VersionFacetedFilter, + VersionFacetedFilterBuilder, + VersionFacetedFilterTrigger, + VersionFacetedFilterTrigger as VersionTrigger +}; diff --git a/src/Exceptionless.Web/ClientApp/src/lib/features/events/components/filters/keyword-faceted-filter-builder.svelte b/src/Exceptionless.Web/ClientApp/src/lib/features/events/components/filters/keyword-faceted-filter-builder.svelte new file mode 100644 index 0000000000..eb785e12b1 --- /dev/null +++ b/src/Exceptionless.Web/ClientApp/src/lib/features/events/components/filters/keyword-faceted-filter-builder.svelte @@ -0,0 +1,26 @@ + diff --git a/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/filters/facets/KeywordFacetedFilter.svelte b/src/Exceptionless.Web/ClientApp/src/lib/features/events/components/filters/keyword-faceted-filter.svelte similarity index 62% rename from src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/filters/facets/KeywordFacetedFilter.svelte rename to src/Exceptionless.Web/ClientApp/src/lib/features/events/components/filters/keyword-faceted-filter.svelte index 4074ac6969..7fa08a1dd0 100644 --- a/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/filters/facets/KeywordFacetedFilter.svelte +++ b/src/Exceptionless.Web/ClientApp/src/lib/features/events/components/filters/keyword-faceted-filter.svelte @@ -1,14 +1,14 @@ - { filter.value = value; filterChanged(filter); @@ -20,4 +20,4 @@ {title} value={filter.value} {...props} -> +> diff --git a/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/filters/filters.svelte.ts b/src/Exceptionless.Web/ClientApp/src/lib/features/events/components/filters/models.svelte.ts similarity index 68% rename from src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/filters/filters.svelte.ts rename to src/Exceptionless.Web/ClientApp/src/lib/features/events/components/filters/models.svelte.ts index 43f205ab77..8f38404ce9 100644 --- a/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/filters/filters.svelte.ts +++ b/src/Exceptionless.Web/ClientApp/src/lib/features/events/components/filters/models.svelte.ts @@ -1,23 +1,16 @@ +import type { IFilter } from '$comp/faceted-filter'; import type { PersistentEventKnownTypes } from '$features/events/models'; import type { StackStatus } from '$features/stacks/models'; -import type { Serializer } from '$shared/persisted.svelte'; - -export interface IFilter { - isEmpty(): boolean; - readonly key: string; - reset(): void; - toFilter(): string; - readonly type: string; -} export class BooleanFilter implements IFilter { + public id: string = crypto.randomUUID(); public term = $state(); public type: string = 'boolean'; public value = $state(); public get key(): string { - return `${this.type}:${this.term}`; + return `${this.type}-${this.term}`; } constructor(term?: string, value?: boolean) { @@ -55,13 +48,14 @@ export class BooleanFilter implements IFilter { } export class DateFilter implements IFilter { + public id: string = crypto.randomUUID(); public term = $state(); public type: string = 'date'; public value = $state(); public get key(): string { - return `${this.type}:${this.term}`; + return `${this.type}-${this.term}`; } constructor(term?: string, value?: Date | string) { @@ -99,30 +93,8 @@ export class DateFilter implements IFilter { } } -export class FilterSerializer implements Serializer { - public deserialize(text: string): IFilter[] { - if (!text) { - return []; - } - - const data: unknown[] = JSON.parse(text); - const filters: IFilter[] = []; - for (const filterData of data) { - const filter = getFilter(filterData as Omit); - if (filter) { - filters.push(filter); - } - } - - return filters; - } - - public serialize(object: IFilter[]): string { - return JSON.stringify(object); - } -} - export class KeywordFilter implements IFilter { + public id: string = crypto.randomUUID(); public type: string = 'keyword'; public value = $state(); @@ -160,13 +132,14 @@ export class KeywordFilter implements IFilter { } export class NumberFilter implements IFilter { + public id: string = crypto.randomUUID(); public term = $state(); public type: string = 'number'; public value = $state(); public get key(): string { - return `${this.type}:${this.term}`; + return `${this.type}-${this.term}`; } constructor(term?: string, value?: number) { @@ -203,44 +176,8 @@ export class NumberFilter implements IFilter { } } -export class OrganizationFilter implements IFilter { - public type: string = 'organization'; - public value = $state(); - - public get key(): string { - return this.type; - } - - constructor(value?: string) { - this.value = value; - } - - public isEmpty(): boolean { - return !this.value?.trim(); - } - - public reset(): void { - this.value = undefined; - } - - public toFilter(): string { - if (this.isEmpty()) { - return ''; - } - - return `organization:${this.value}`; - } - - public toJSON() { - return { - type: this.type, - value: this.value - }; - } -} - export class ProjectFilter implements IFilter { - public organization = $state(); + public id: string = crypto.randomUUID(); public type: string = 'project'; public value = $state([]); @@ -249,8 +186,7 @@ export class ProjectFilter implements IFilter { return this.type; } - constructor(organization: string | undefined, value: string[] = []) { - this.organization = organization; + constructor(value: string[] = []) { this.value = value; } @@ -276,7 +212,6 @@ export class ProjectFilter implements IFilter { public toJSON() { return { - organization: this.organization, type: this.type, value: this.value }; @@ -284,6 +219,7 @@ export class ProjectFilter implements IFilter { } export class ReferenceFilter implements IFilter { + public id: string = crypto.randomUUID(); public type: string = 'reference'; public value = $state(); @@ -321,6 +257,7 @@ export class ReferenceFilter implements IFilter { } export class SessionFilter implements IFilter { + public id: string = crypto.randomUUID(); public type: string = 'session'; public value = $state(); @@ -359,6 +296,7 @@ export class SessionFilter implements IFilter { } export class StatusFilter implements IFilter { + public id: string = crypto.randomUUID(); public type: string = 'status'; public value = $state([]); @@ -400,13 +338,14 @@ export class StatusFilter implements IFilter { } export class StringFilter implements IFilter { + public id: string = crypto.randomUUID(); public term = $state(); public type: string = 'string'; public value = $state(); public get key(): string { - return `${this.type}:${this.term}`; + return `${this.type}-${this.term}`; } constructor(term?: string, value?: string) { @@ -444,6 +383,7 @@ export class StringFilter implements IFilter { } export class TypeFilter implements IFilter { + public id: string = crypto.randomUUID(); public type: string = 'type'; public value = $state([]); @@ -485,13 +425,14 @@ export class TypeFilter implements IFilter { } export class VersionFilter implements IFilter { + public id: string = crypto.randomUUID(); public term = $state(); public type: string = 'version'; public value = $state(); public get key(): string { - return `${this.type}:${this.term}`; + return `${this.type}-${this.term}`; } constructor(term?: string, value?: string) { @@ -528,35 +469,42 @@ export class VersionFilter implements IFilter { } } -export function filterChanged(filters: IFilter[], updated: IFilter): IFilter[] { - return processFilterRules(setFilter(filters, updated), updated); +export function filterChanged(filters: IFilter[], addedOrUpdated: IFilter): IFilter[] { + const index = filters.findIndex((f) => f.id === addedOrUpdated.id); + if (index === -1) { + return processFilterRules([...filters, addedOrUpdated]); + } + + return processFilterRules([...filters.slice(0, index), addedOrUpdated, ...filters.slice(index + 1)]); } -export function filterRemoved(filters: IFilter[], defaultFilters: IFilter[], removed?: IFilter): IFilter[] { +export const filterSerializer = { + deserialize: (value: string): IFilter[] => { + if (!value) { + return []; + } + + const data: unknown[] = JSON.parse(value); + const filters: IFilter[] = []; + for (const filterData of data) { + const filter = getFilter(filterData as Omit); + if (filter) { + filters.push(filter); + } + } + + return filters; + }, + serialize: JSON.stringify +}; + +export function filterRemoved(filters: IFilter[], removed?: IFilter): IFilter[] { // If detail is undefined, remove all filters. if (!removed) { - return defaultFilters; - } else if (defaultFilters.find((f) => f.key === removed.key)) { - return processFilterRules(setFilter(filters, removed), removed); - } else { - return processFilterRules( - filters.filter((f) => f.key !== removed.key), - removed - ); + return []; } -} -export function getDefaultFilters(includeDateFilter = true): IFilter[] { - return [ - new OrganizationFilter(), - new ProjectFilter(undefined, []), - new StatusFilter([]), - new TypeFilter([]), - new DateFilter('date', 'last week'), - new ReferenceFilter(), - new SessionFilter(), - new KeywordFilter() - ].filter((f) => includeDateFilter || f.type !== 'date'); + return filters.filter((f) => f.id !== removed.id); } export function getFilter(filter: Omit & Record): IFilter | undefined { @@ -569,10 +517,8 @@ export function getFilter(filter: Omit f.type === 'keyword') as KeywordFilter; } -export function getOrganizationFilter(filters: IFilter[]): OrganizationFilter | undefined { - return filters.find((f) => f.type === 'organization') as OrganizationFilter; -} - export function getProjectFilter(filters: IFilter[]): ProjectFilter { return filters.find((f) => f.type === 'project') as ProjectFilter; } @@ -606,41 +548,6 @@ export function getStackFilter(filters: IFilter[]): StringFilter | undefined { return filters.find((f) => f.type === 'string') as StringFilter; } -export function processFilterRules(filters: IFilter[], changed?: IFilter): IFilter[] { - // Allow only one filter per type and term. - const groupedFilters: Partial> = Object.groupBy(filters, (f: IFilter) => f.key); - const filtered: IFilter[] = []; - Object.entries(groupedFilters).forEach(([, items]) => { - if (items && items.length > 0) { - filtered.push(items[0] as IFilter); - } - }); - - const projectFilter = filtered.find((f) => f.type === 'project') as ProjectFilter; - if (projectFilter) { - let organizationFilter = filtered.find((f) => f.type === 'organization') as OrganizationFilter; - - // If there is a project filter, verify the organization filter is set - if (!organizationFilter) { - organizationFilter = new OrganizationFilter(projectFilter.organization); - filtered.push(organizationFilter); - } - - // If the organization filter changes and organization is not set on the project filter, clear the project filter - if (changed?.type === 'organization' && projectFilter.organization !== organizationFilter.value) { - projectFilter.organization = organizationFilter.value; - projectFilter.value = []; - } - - // If the project filter changes and the organization filter is not set, set it - if (organizationFilter.value !== projectFilter.organization) { - organizationFilter.value = projectFilter.organization; - } - } - - return filtered; -} - export function quote(value?: null | string): string | undefined { return value ? `"${value}"` : undefined; } @@ -656,33 +563,6 @@ export function quoteIfSpecialCharacters(value?: null | string): null | string | return value; } -/** - * Mutates the given array of filters by adding or updating the specified filter. - * If a filter with the same key and term already exists, it will be updated with the new filter's value. - * If the filter does not exist, it will be added to the array. - * @param filters - The array of filters to be mutated. - * @param filter - The filter to be added or updated. - * @returns The mutated array of filters. - */ -export function setFilter(filters: IFilter[], filter: IFilter): IFilter[] { - const existingFilter = filters.find((f) => f.key === filter.key && ('term' in f && 'term' in filter ? f.term === filter.term : true)); - if (existingFilter) { - if ('value' in existingFilter && 'value' in filter) { - if (Array.isArray(existingFilter.value) && Array.isArray(filter.value)) { - existingFilter.value = [...new Set([...existingFilter.value, ...filter.value])]; - } else { - existingFilter.value = filter.value; - } - } else { - Object.assign(existingFilter, filter); - } - } else { - filters.push(filter); - } - - return filters; -} - export function toFilter(filters: IFilter[]): string { return filters .map((f) => f.toFilter()) @@ -690,3 +570,34 @@ export function toFilter(filters: IFilter[]): string { .join(' ') .trim(); } + +function processFilterRules(filters: IFilter[]): IFilter[] { + // 1. There can only be one date filter by term at a time. + // 2. There can only be one project filter. + + const uniqueFilters = new Map(); + for (const filter of filters) { + if (filter.type === 'project' || filter.type === 'date') { + const existingFilter = uniqueFilters.get(filter.key); + if (existingFilter) { + if ('value' in existingFilter && 'value' in filter) { + if (Array.isArray(existingFilter.value) && Array.isArray(filter.value)) { + existingFilter.value = [...new Set([...existingFilter.value, ...filter.value])]; + } else if (filter.value !== undefined) { + existingFilter.value = filter.value; + } + } else { + const { id, ...props } = filter; + console.trace(`Filter with key ${existingFilter.type} (${id}) already exists. Merging properties`); + Object.assign(existingFilter, props); + } + } + + uniqueFilters.set(filter.key, existingFilter ?? filter); + } else { + uniqueFilters.set(filter.id, filter); + } + } + + return Array.from(uniqueFilters.values()); +} diff --git a/src/Exceptionless.Web/ClientApp/src/lib/features/events/components/filters/number-faceted-filter-builder.svelte b/src/Exceptionless.Web/ClientApp/src/lib/features/events/components/filters/number-faceted-filter-builder.svelte new file mode 100644 index 0000000000..fb4dd719dd --- /dev/null +++ b/src/Exceptionless.Web/ClientApp/src/lib/features/events/components/filters/number-faceted-filter-builder.svelte @@ -0,0 +1,27 @@ + diff --git a/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/filters/ClickableNumberFilter.svelte b/src/Exceptionless.Web/ClientApp/src/lib/features/events/components/filters/number-faceted-filter-trigger.svelte similarity index 74% rename from src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/filters/ClickableNumberFilter.svelte rename to src/Exceptionless.Web/ClientApp/src/lib/features/events/components/filters/number-faceted-filter-trigger.svelte index 8eacf218ef..65f17e38d2 100644 --- a/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/filters/ClickableNumberFilter.svelte +++ b/src/Exceptionless.Web/ClientApp/src/lib/features/events/components/filters/number-faceted-filter-trigger.svelte @@ -1,9 +1,9 @@ - { filter.value = value; filterChanged(filter); @@ -19,4 +19,4 @@ }} value={filter.value} {...props} -> +> diff --git a/src/Exceptionless.Web/ClientApp/src/lib/features/events/components/filters/organization-defaults-faceted-filter-builder.svelte b/src/Exceptionless.Web/ClientApp/src/lib/features/events/components/filters/organization-defaults-faceted-filter-builder.svelte new file mode 100644 index 0000000000..7df844cc93 --- /dev/null +++ b/src/Exceptionless.Web/ClientApp/src/lib/features/events/components/filters/organization-defaults-faceted-filter-builder.svelte @@ -0,0 +1,99 @@ + + +{#each eventsBooleanFilters as { priority, term, title }} + +{/each} +{#if includeDateFacets} + {#each eventsDateFilters as { priority, term, title }} + + {/each} +{/if} +{#each eventsGeoFilters as { priority, term, title }} + +{/each} +{#each eventsNumberFilters as { priority, term, title }} + +{/each} + + + + +{#each eventsStringFilters as { priority, term, title }} + +{/each} + +{#each eventsVersionFilters as { priority, term, title }} + +{/each} diff --git a/src/Exceptionless.Web/ClientApp/src/lib/features/events/components/filters/project-faceted-filter-builder.svelte b/src/Exceptionless.Web/ClientApp/src/lib/features/events/components/filters/project-faceted-filter-builder.svelte new file mode 100644 index 0000000000..69987ddc2c --- /dev/null +++ b/src/Exceptionless.Web/ClientApp/src/lib/features/events/components/filters/project-faceted-filter-builder.svelte @@ -0,0 +1,26 @@ + diff --git a/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/filters/ClickableProjectFilter.svelte b/src/Exceptionless.Web/ClientApp/src/lib/features/events/components/filters/project-faceted-filter-trigger.svelte similarity index 51% rename from src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/filters/ClickableProjectFilter.svelte rename to src/Exceptionless.Web/ClientApp/src/lib/features/events/components/filters/project-faceted-filter-trigger.svelte index 042a354082..02dd898c36 100644 --- a/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/filters/ClickableProjectFilter.svelte +++ b/src/Exceptionless.Web/ClientApp/src/lib/features/events/components/filters/project-faceted-filter-trigger.svelte @@ -1,22 +1,20 @@ - changed(new ProjectFilter(organization, value))} {title} {...props}> + changed(new ProjectFilter(value))} {title} {...props}> {#snippet children()} - + {/snippet} diff --git a/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/filters/facets/ProjectFacetedFilter.svelte b/src/Exceptionless.Web/ClientApp/src/lib/features/events/components/filters/project-faceted-filter.svelte similarity index 55% rename from src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/filters/facets/ProjectFacetedFilter.svelte rename to src/Exceptionless.Web/ClientApp/src/lib/features/events/components/filters/project-faceted-filter.svelte index bde2731e1a..81257c8cbb 100644 --- a/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/filters/facets/ProjectFacetedFilter.svelte +++ b/src/Exceptionless.Web/ClientApp/src/lib/features/events/components/filters/project-faceted-filter.svelte @@ -1,18 +1,23 @@ - { filter.value = values; filterChanged(filter); @@ -48,4 +53,4 @@ {title} values={filter.value} {...props} -> +> diff --git a/src/Exceptionless.Web/ClientApp/src/lib/features/events/components/filters/reference-faceted-filter-builder.svelte b/src/Exceptionless.Web/ClientApp/src/lib/features/events/components/filters/reference-faceted-filter-builder.svelte new file mode 100644 index 0000000000..48101fa2f3 --- /dev/null +++ b/src/Exceptionless.Web/ClientApp/src/lib/features/events/components/filters/reference-faceted-filter-builder.svelte @@ -0,0 +1,26 @@ + diff --git a/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/filters/ClickableReferenceFilter.svelte b/src/Exceptionless.Web/ClientApp/src/lib/features/events/components/filters/reference-faceted-filter-trigger.svelte similarity index 72% rename from src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/filters/ClickableReferenceFilter.svelte rename to src/Exceptionless.Web/ClientApp/src/lib/features/events/components/filters/reference-faceted-filter-trigger.svelte index 443cfdf9a4..0e54166a62 100644 --- a/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/filters/ClickableReferenceFilter.svelte +++ b/src/Exceptionless.Web/ClientApp/src/lib/features/events/components/filters/reference-faceted-filter-trigger.svelte @@ -1,9 +1,9 @@ - { filter.value = value; filterChanged(filter); @@ -20,4 +20,4 @@ {title} value={filter.value} {...props} -> +> diff --git a/src/Exceptionless.Web/ClientApp/src/lib/features/events/components/filters/session-faceted-filter-builder.svelte b/src/Exceptionless.Web/ClientApp/src/lib/features/events/components/filters/session-faceted-filter-builder.svelte new file mode 100644 index 0000000000..b49d83101d --- /dev/null +++ b/src/Exceptionless.Web/ClientApp/src/lib/features/events/components/filters/session-faceted-filter-builder.svelte @@ -0,0 +1,26 @@ + diff --git a/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/filters/ClickableSessionFilter.svelte b/src/Exceptionless.Web/ClientApp/src/lib/features/events/components/filters/session-faceted-filter-trigger.svelte similarity index 73% rename from src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/filters/ClickableSessionFilter.svelte rename to src/Exceptionless.Web/ClientApp/src/lib/features/events/components/filters/session-faceted-filter-trigger.svelte index 30650fa3ae..46eb65aa15 100644 --- a/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/filters/ClickableSessionFilter.svelte +++ b/src/Exceptionless.Web/ClientApp/src/lib/features/events/components/filters/session-faceted-filter-trigger.svelte @@ -1,9 +1,9 @@ - { filter.value = value; filterChanged(filter); @@ -20,4 +20,4 @@ {title} value={filter.value} {...props} -> +> diff --git a/src/Exceptionless.Web/ClientApp/src/lib/features/events/components/filters/status-faceted-filter-builder.svelte b/src/Exceptionless.Web/ClientApp/src/lib/features/events/components/filters/status-faceted-filter-builder.svelte new file mode 100644 index 0000000000..87806b5f15 --- /dev/null +++ b/src/Exceptionless.Web/ClientApp/src/lib/features/events/components/filters/status-faceted-filter-builder.svelte @@ -0,0 +1,26 @@ + diff --git a/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/filters/ClickableStatusFilter.svelte b/src/Exceptionless.Web/ClientApp/src/lib/features/events/components/filters/status-faceted-filter-trigger.svelte similarity index 75% rename from src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/filters/ClickableStatusFilter.svelte rename to src/Exceptionless.Web/ClientApp/src/lib/features/events/components/filters/status-faceted-filter-trigger.svelte index ee4df4e548..6e65f900a7 100644 --- a/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/filters/ClickableStatusFilter.svelte +++ b/src/Exceptionless.Web/ClientApp/src/lib/features/events/components/filters/status-faceted-filter-trigger.svelte @@ -3,9 +3,9 @@ import { A, type AProps } from '$comp/typography'; import { cn } from '$lib/utils'; - import IconFilter from '~icons/mdi/filter'; + import Filter from 'lucide-svelte/icons/filter'; - import { StatusFilter } from './filters.svelte'; + import { StatusFilter } from './models.svelte'; type Props = AProps & { changed: (filter: StatusFilter) => void; @@ -18,6 +18,6 @@ changed(new StatusFilter(value))} {title} {...props}> {#snippet children()} - + {/snippet} diff --git a/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/filters/facets/StatusFacetedFilter.svelte b/src/Exceptionless.Web/ClientApp/src/lib/features/events/components/filters/status-faceted-filter.svelte similarity index 69% rename from src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/filters/facets/StatusFacetedFilter.svelte rename to src/Exceptionless.Web/ClientApp/src/lib/features/events/components/filters/status-faceted-filter.svelte index 3a04edd623..8f56681d2f 100644 --- a/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/filters/facets/StatusFacetedFilter.svelte +++ b/src/Exceptionless.Web/ClientApp/src/lib/features/events/components/filters/status-faceted-filter.svelte @@ -1,16 +1,16 @@ - { filter.value = values as StackStatus[]; filterChanged(filter); @@ -23,4 +23,4 @@ {title} values={filter.value} {...props} -> +> diff --git a/src/Exceptionless.Web/ClientApp/src/lib/features/events/components/filters/string-faceted-filter-builder.svelte b/src/Exceptionless.Web/ClientApp/src/lib/features/events/components/filters/string-faceted-filter-builder.svelte new file mode 100644 index 0000000000..7cb57e32e3 --- /dev/null +++ b/src/Exceptionless.Web/ClientApp/src/lib/features/events/components/filters/string-faceted-filter-builder.svelte @@ -0,0 +1,27 @@ + diff --git a/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/filters/ClickableStringFilter.svelte b/src/Exceptionless.Web/ClientApp/src/lib/features/events/components/filters/string-faceted-filter-trigger.svelte similarity index 76% rename from src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/filters/ClickableStringFilter.svelte rename to src/Exceptionless.Web/ClientApp/src/lib/features/events/components/filters/string-faceted-filter-trigger.svelte index 87b0fae364..7b3bb5a4b8 100644 --- a/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/filters/ClickableStringFilter.svelte +++ b/src/Exceptionless.Web/ClientApp/src/lib/features/events/components/filters/string-faceted-filter-trigger.svelte @@ -1,9 +1,9 @@ - { filter.value = value; filterChanged(filter); @@ -19,4 +19,4 @@ }} value={filter.value} {...props} -> +> diff --git a/src/Exceptionless.Web/ClientApp/src/lib/features/events/components/filters/type-faceted-filter-builder.svelte b/src/Exceptionless.Web/ClientApp/src/lib/features/events/components/filters/type-faceted-filter-builder.svelte new file mode 100644 index 0000000000..df0bfa57a1 --- /dev/null +++ b/src/Exceptionless.Web/ClientApp/src/lib/features/events/components/filters/type-faceted-filter-builder.svelte @@ -0,0 +1,26 @@ + diff --git a/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/filters/ClickableTypeFilter.svelte b/src/Exceptionless.Web/ClientApp/src/lib/features/events/components/filters/type-faceted-filter-trigger.svelte similarity index 72% rename from src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/filters/ClickableTypeFilter.svelte rename to src/Exceptionless.Web/ClientApp/src/lib/features/events/components/filters/type-faceted-filter-trigger.svelte index 33709c1c69..78426cb20c 100644 --- a/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/filters/ClickableTypeFilter.svelte +++ b/src/Exceptionless.Web/ClientApp/src/lib/features/events/components/filters/type-faceted-filter-trigger.svelte @@ -1,9 +1,9 @@ - { + { filter.value = values; filterChanged(filter); }} @@ -22,4 +22,4 @@ {title} values={filter.value} {...props} -> +> diff --git a/src/Exceptionless.Web/ClientApp/src/lib/features/events/components/filters/version-faceted-filter-builder.svelte b/src/Exceptionless.Web/ClientApp/src/lib/features/events/components/filters/version-faceted-filter-builder.svelte new file mode 100644 index 0000000000..1a8eabbfda --- /dev/null +++ b/src/Exceptionless.Web/ClientApp/src/lib/features/events/components/filters/version-faceted-filter-builder.svelte @@ -0,0 +1,27 @@ + diff --git a/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/filters/ClickableVersionFilter.svelte b/src/Exceptionless.Web/ClientApp/src/lib/features/events/components/filters/version-faceted-filter-trigger.svelte similarity index 74% rename from src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/filters/ClickableVersionFilter.svelte rename to src/Exceptionless.Web/ClientApp/src/lib/features/events/components/filters/version-faceted-filter-trigger.svelte index 0ee767981e..29804cbde7 100644 --- a/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/filters/ClickableVersionFilter.svelte +++ b/src/Exceptionless.Web/ClientApp/src/lib/features/events/components/filters/version-faceted-filter-trigger.svelte @@ -1,9 +1,9 @@ - { filter.value = value; filterChanged(filter); @@ -19,4 +19,4 @@ }} value={filter.value} {...props} -> +> diff --git a/src/Exceptionless.Web/ClientApp/src/lib/features/events/components/LogLevel.svelte b/src/Exceptionless.Web/ClientApp/src/lib/features/events/components/log-level.svelte similarity index 100% rename from src/Exceptionless.Web/ClientApp/src/lib/features/events/components/LogLevel.svelte rename to src/Exceptionless.Web/ClientApp/src/lib/features/events/components/log-level.svelte diff --git a/src/Exceptionless.Web/ClientApp/src/lib/features/events/components/SimpleStackTrace.svelte b/src/Exceptionless.Web/ClientApp/src/lib/features/events/components/simple-stack-trace.svelte similarity index 53% rename from src/Exceptionless.Web/ClientApp/src/lib/features/events/components/SimpleStackTrace.svelte rename to src/Exceptionless.Web/ClientApp/src/lib/features/events/components/simple-stack-trace.svelte index 237b28233c..5dcf8e3356 100644 --- a/src/Exceptionless.Web/ClientApp/src/lib/features/events/components/SimpleStackTrace.svelte +++ b/src/Exceptionless.Web/ClientApp/src/lib/features/events/components/simple-stack-trace.svelte @@ -4,7 +4,7 @@ import { Code } from '$comp/typography'; import { getErrors } from '$features/events/persistent-event'; - import StackTraceHeader from './StackTraceHeader.svelte'; + import StackTraceHeader from './stack-trace-header.svelte'; interface Props { error: SimpleErrorInfo; @@ -19,9 +19,8 @@ const errors = getErrors(error); -
{#each errors.reverse() as error, index}{#if error.stack_trace}
{cleanStackTrace( - error.stack_trace - )}
{#if index < errors.length - 1}
--- End of inner error stack trace ---
{/if}{/if}{/each}
{#each errors.reverse() as error, index}{#if error.stack_trace}
{cleanStackTrace(error.stack_trace)}
{#if index < errors.length - 1}
--- End of inner error stack trace ---
{/if}{/if}{/each}
diff --git a/src/Exceptionless.Web/ClientApp/src/lib/features/events/components/StackTraceHeader.svelte b/src/Exceptionless.Web/ClientApp/src/lib/features/events/components/stack-trace-header.svelte similarity index 88% rename from src/Exceptionless.Web/ClientApp/src/lib/features/events/components/StackTraceHeader.svelte rename to src/Exceptionless.Web/ClientApp/src/lib/features/events/components/stack-trace-header.svelte index bcc87b98e1..e4ed30a9f5 100644 --- a/src/Exceptionless.Web/ClientApp/src/lib/features/events/components/StackTraceHeader.svelte +++ b/src/Exceptionless.Web/ClientApp/src/lib/features/events/components/stack-trace-header.svelte @@ -8,7 +8,7 @@ let { errors }: Props = $props(); -{#each errors as error, index}
+{#each errors as error, index}
{#if index > 0}---> {/if}{#if error.type}{error.type}:{/if}{#if error.message}{error.message}{/if}
diff --git a/src/Exceptionless.Web/ClientApp/src/lib/features/events/components/StackTrace.svelte b/src/Exceptionless.Web/ClientApp/src/lib/features/events/components/stack-trace.svelte similarity index 52% rename from src/Exceptionless.Web/ClientApp/src/lib/features/events/components/StackTrace.svelte rename to src/Exceptionless.Web/ClientApp/src/lib/features/events/components/stack-trace.svelte index f915b593dc..e61455a87e 100644 --- a/src/Exceptionless.Web/ClientApp/src/lib/features/events/components/StackTrace.svelte +++ b/src/Exceptionless.Web/ClientApp/src/lib/features/events/components/stack-trace.svelte @@ -4,7 +4,7 @@ import { Code } from '$comp/typography'; import { getErrors, getStackFrame } from '$features/events/persistent-event'; - import StackTraceHeader from './StackTraceHeader.svelte'; + import StackTraceHeader from './stack-trace-header.svelte'; interface Props { error: ErrorInfo; @@ -15,8 +15,8 @@ const errors = getErrors(error); -
{#each errors.reverse() as error, index}{#if error.stack_trace}
{#each error.stack_trace as frame}{getStackFrame(frame)}
{/each}{#if index < errors.length - 1}
--- End of inner exception stack trace ---
{/if}
{/if}{/each}
{#each error.stack_trace as frame}{getStackFrame(frame)}
{/each}{#if index < errors.length - 1}
--- End of inner exception stack trace ---
{/if}
{/if}{/each} diff --git a/src/Exceptionless.Web/ClientApp/src/lib/features/events/components/summary/EventErrorSummary.svelte b/src/Exceptionless.Web/ClientApp/src/lib/features/events/components/summary/event-error-summary.svelte similarity index 90% rename from src/Exceptionless.Web/ClientApp/src/lib/features/events/components/summary/EventErrorSummary.svelte rename to src/Exceptionless.Web/ClientApp/src/lib/features/events/components/summary/event-error-summary.svelte index 91ddfec4d8..c72ef8598f 100644 --- a/src/Exceptionless.Web/ClientApp/src/lib/features/events/components/summary/EventErrorSummary.svelte +++ b/src/Exceptionless.Web/ClientApp/src/lib/features/events/components/summary/event-error-summary.svelte @@ -1,6 +1,6 @@ - - - - {#if toolbarChildren} - {@render toolbarChildren()} - {/if} - - - - - - diff --git a/src/Exceptionless.Web/ClientApp/src/lib/features/events/components/table/events-bulk-actions-dropdown-menu.svelte b/src/Exceptionless.Web/ClientApp/src/lib/features/events/components/table/events-bulk-actions-dropdown-menu.svelte new file mode 100644 index 0000000000..b5186e33ac --- /dev/null +++ b/src/Exceptionless.Web/ClientApp/src/lib/features/events/components/table/events-bulk-actions-dropdown-menu.svelte @@ -0,0 +1,62 @@ + + + + + + + + + + + Bulk Actions + (openRemoveEventDialog = true)} class="text-destructive" title="Delete event">Delete + + + + +{#if openRemoveEventDialog} + +{/if} diff --git a/src/Exceptionless.Web/ClientApp/src/lib/features/events/components/table/events-data-table.svelte b/src/Exceptionless.Web/ClientApp/src/lib/features/events/components/table/events-data-table.svelte new file mode 100644 index 0000000000..73700d4f39 --- /dev/null +++ b/src/Exceptionless.Web/ClientApp/src/lib/features/events/components/table/events-data-table.svelte @@ -0,0 +1,53 @@ + + + + + {#if toolbarChildren} + {@render toolbarChildren()} + {/if} + + + {#if isLoading} + + + + {:else} + + {/if} + {#if bodyChildren} + {@render bodyChildren()} + {/if} + + + {#if footerChildren} + {@render footerChildren()} + {:else} + + +
+ + +
+ {/if} +
+
diff --git a/src/Exceptionless.Web/ClientApp/src/lib/features/events/components/table/EventsUserIdentitySummaryCell.svelte b/src/Exceptionless.Web/ClientApp/src/lib/features/events/components/table/events-user-identity-summary-cell.svelte similarity index 100% rename from src/Exceptionless.Web/ClientApp/src/lib/features/events/components/table/EventsUserIdentitySummaryCell.svelte rename to src/Exceptionless.Web/ClientApp/src/lib/features/events/components/table/events-user-identity-summary-cell.svelte diff --git a/src/Exceptionless.Web/ClientApp/src/lib/features/events/components/table/options.svelte.ts b/src/Exceptionless.Web/ClientApp/src/lib/features/events/components/table/options.svelte.ts index 3236355af3..02048e8b23 100644 --- a/src/Exceptionless.Web/ClientApp/src/lib/features/events/components/table/options.svelte.ts +++ b/src/Exceptionless.Web/ClientApp/src/lib/features/events/components/table/options.svelte.ts @@ -1,11 +1,10 @@ import type { FetchClientResponse } from '@exceptionless/fetchclient'; -import NumberFormatter from '$comp/formatters/Number.svelte'; -import TimeAgo from '$comp/formatters/TimeAgo.svelte'; +import NumberFormatter from '$comp/formatters/number.svelte'; +import TimeAgo from '$comp/formatters/time-ago.svelte'; import { Checkbox } from '$comp/ui/checkbox'; import { nameof } from '$lib/utils'; -import { DEFAULT_LIMIT } from '$shared/api'; -import { persisted } from '$shared/persisted.svelte'; +import { DEFAULT_LIMIT } from '$shared/api/api.svelte'; import { type ColumnDef, type ColumnSort, @@ -17,14 +16,15 @@ import { type Updater, type VisibilityState } from '@tanstack/svelte-table'; +import { PersistedState } from 'runed'; -import type { GetEventsMode, IGetEventsParams } from '../../api.svelte'; +import type { GetEventsMode, GetEventsParams } from '../../api.svelte'; import type { EventSummaryModel, StackSummaryModel, SummaryModel, SummaryTemplateKeys } from '../summary/index'; -import Summary from '../summary/Summary.svelte'; -import EventsUserIdentitySummaryCell from './EventsUserIdentitySummaryCell.svelte'; -import StackStatusCell from './StackStatusCell.svelte'; -import StackUsersSummaryCell from './StackUsersSummaryCell.svelte'; +import Summary from '../summary/summary.svelte'; +import EventsUserIdentitySummaryCell from './events-user-identity-summary-cell.svelte'; +import StackStatusCell from './stack-status-cell.svelte'; +import StackUsersSummaryCell from './stack-users-summary-cell.svelte'; export function getColumns>(mode: GetEventsMode = 'summary'): ColumnDef[] { const columns: ColumnDef[] = [ @@ -138,7 +138,7 @@ export function getColumns>( - params: IGetEventsParams, + params: GetEventsParams, configureOptions: (options: TableOptions) => TableOptions = (options) => options ) { let _parameters = $state(params); @@ -169,15 +169,16 @@ export function getTableContext previousPageIndex ? (_meta.links.next?.after as string) : undefined, - before: currentPageInfo.pageIndex < previousPageIndex && currentPageInfo.pageIndex > 0 ? (_meta.links.previous?.before as string) : undefined, - limit: currentPageInfo.pageSize + after: currentPageInfo.pageIndex > previousPageIndex ? nextLink : undefined, + before: currentPageInfo.pageIndex < previousPageIndex && currentPageInfo.pageIndex > 0 ? previousLink : undefined, + limit: currentPageInfo.pageSize, + page: !nextLink && !previousLink && currentPageInfo.pageIndex !== 0 ? currentPageInfo.pageIndex + 1 : undefined }; }; @@ -188,6 +189,7 @@ export function getTableContext 0 ? sorting() @@ -202,6 +204,9 @@ export function getTableContext(key: string, initialValue: T): [() => T, (updater: Updater) => void] { - const persistedValue = persisted(key, initialValue); + const persistedValue = new PersistedState(key, initialValue); return [ - () => persistedValue.value, + () => persistedValue.current, (updater: Updater) => { if (updater instanceof Function) { - persistedValue.value = updater(persistedValue.value); + persistedValue.current = updater(persistedValue.current); } else { - persistedValue.value = updater; + persistedValue.current = updater; } } ]; diff --git a/src/Exceptionless.Web/ClientApp/src/lib/features/events/components/table/StackStatusCell.svelte b/src/Exceptionless.Web/ClientApp/src/lib/features/events/components/table/stack-status-cell.svelte similarity index 100% rename from src/Exceptionless.Web/ClientApp/src/lib/features/events/components/table/StackStatusCell.svelte rename to src/Exceptionless.Web/ClientApp/src/lib/features/events/components/table/stack-status-cell.svelte diff --git a/src/Exceptionless.Web/ClientApp/src/lib/features/events/components/table/StackUsersSummaryCell.svelte b/src/Exceptionless.Web/ClientApp/src/lib/features/events/components/table/stack-users-summary-cell.svelte similarity index 100% rename from src/Exceptionless.Web/ClientApp/src/lib/features/events/components/table/StackUsersSummaryCell.svelte rename to src/Exceptionless.Web/ClientApp/src/lib/features/events/components/table/stack-users-summary-cell.svelte diff --git a/src/Exceptionless.Web/ClientApp/src/lib/features/events/components/views/Environment.svelte b/src/Exceptionless.Web/ClientApp/src/lib/features/events/components/views/environment.svelte similarity index 84% rename from src/Exceptionless.Web/ClientApp/src/lib/features/events/components/views/Environment.svelte rename to src/Exceptionless.Web/ClientApp/src/lib/features/events/components/views/environment.svelte index 859db631a3..0389af689c 100644 --- a/src/Exceptionless.Web/ClientApp/src/lib/features/events/components/views/Environment.svelte +++ b/src/Exceptionless.Web/ClientApp/src/lib/features/events/components/views/environment.svelte @@ -1,14 +1,14 @@ + +{#if canRefresh} +
+ +
+{:else} + +{/if} diff --git a/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/CopyToClipboardButton.svelte b/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/copy-to-clipboard-button.svelte similarity index 89% rename from src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/CopyToClipboardButton.svelte rename to src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/copy-to-clipboard-button.svelte index d4861d3e98..b215d3353c 100644 --- a/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/CopyToClipboardButton.svelte +++ b/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/copy-to-clipboard-button.svelte @@ -3,8 +3,8 @@ import type { VariantProps } from 'tailwind-variants'; import { Button, type ButtonProps, type buttonVariants } from '$comp/ui/button'; + import ClipboardCopy from 'lucide-svelte/icons/clipboard-copy'; import { toast } from 'svelte-sonner'; - import IconContentCopy from '~icons/mdi/content-copy'; type Props = ButtonProps & { children?: Snippet; @@ -29,7 +29,7 @@ {#if children} {@render children()} {:else} - + {/if} diff --git a/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/DarkModeButton.svelte b/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/dark-mode-button.svelte similarity index 72% rename from src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/DarkModeButton.svelte rename to src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/dark-mode-button.svelte index af6a61a908..41fd1789e8 100644 --- a/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/DarkModeButton.svelte +++ b/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/dark-mode-button.svelte @@ -1,9 +1,9 @@ @@ -55,9 +58,9 @@ {/each} - + {#if children} + {@render children()} + {/if} {#each table.getRowModel().rows as row (row.id)} {#each row.getVisibleCells() as cell (cell.id)} diff --git a/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/data-table/data-table-column-header.svelte b/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/data-table/data-table-column-header.svelte index 4257a53541..cfc6ec9d80 100644 --- a/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/data-table/data-table-column-header.svelte +++ b/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/data-table/data-table-column-header.svelte @@ -9,10 +9,10 @@ import { Button } from '$comp/ui/button'; import * as DropdownMenu from '$comp/ui/dropdown-menu'; import { cn } from '$lib/utils'; - import IconArrowDownward from '~icons/mdi/arrow-downward'; - import IconArrowUpward from '~icons/mdi/arrow-upward'; - import IconEyeOff from '~icons/mdi/eye-off-outline'; - import IconUnfoldMore from '~icons/mdi/unfold-more-horizontal'; + import ArrowDown from 'lucide-svelte/icons/arrow-down'; + import ArrowUp from 'lucide-svelte/icons/arrow-up'; + import ChevronsUpDown from 'lucide-svelte/icons/chevrons-up-down'; + import EyeOff from 'lucide-svelte/icons/eye-off'; type Props = HTMLAttributes & { column: Column; @@ -31,25 +31,23 @@ {@render children()} {/if} {#if column.getIsSorted() === 'desc'} - + {:else if column.getIsSorted() === 'asc'} - + {:else} - + {/if} {/snippet} - column.toggleSorting(false)} - >Asc + column.toggleSorting(false)}>Asc column.toggleSorting(true)} - >DescDesc column.toggleVisibility(false)} - >HideHide diff --git a/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/data-table/data-table-empty.svelte b/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/data-table/data-table-empty.svelte new file mode 100644 index 0000000000..4bb908a0b3 --- /dev/null +++ b/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/data-table/data-table-empty.svelte @@ -0,0 +1,27 @@ + + + + + diff --git a/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/data-table/data-table-footer.svelte b/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/data-table/data-table-footer.svelte new file mode 100644 index 0000000000..f43cc58db5 --- /dev/null +++ b/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/data-table/data-table-footer.svelte @@ -0,0 +1,33 @@ + + + + +
+ {#if children} + {@render children()} + {:else} + +
+ + +
+ {/if} +
diff --git a/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/data-table/data-table-loading.svelte b/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/data-table/data-table-loading.svelte new file mode 100644 index 0000000000..fe5f1f3c76 --- /dev/null +++ b/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/data-table/data-table-loading.svelte @@ -0,0 +1,41 @@ + + + + +{#if showLoading} + + {#if children} + {@render children()} + {:else} + {#each table.getVisibleLeafColumns() as cell (cell.id)} + + {#if shouldRenderCell(cell)} + + {/if} + + {/each} + {/if} + +{/if} diff --git a/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/data-table/data-table-page-count.svelte b/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/data-table/data-table-page-count.svelte new file mode 100644 index 0000000000..3a2f611b92 --- /dev/null +++ b/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/data-table/data-table-page-count.svelte @@ -0,0 +1,19 @@ + + + + +
+ Page of +
diff --git a/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/data-table/data-table-pagination.svelte b/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/data-table/data-table-pagination.svelte index 1d8c3147b2..6790e6ac89 100644 --- a/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/data-table/data-table-pagination.svelte +++ b/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/data-table/data-table-pagination.svelte @@ -4,50 +4,32 @@ -
-
- {#if table.getSelectedRowModel().rows.length > 0} - {Object.keys(table.getSelectedRowModel().rows).length} of{' '} - {table.getRowModel().rows.length} row(s) selected. - {/if} -
-
- {@render children()} - -
- Page of -
-
- {#if table.getState().pagination.pageIndex > 1} - - {/if} - - -
-
+
+ {#if table.getState().pagination.pageIndex > 1} + + {/if} + +
diff --git a/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/data-table/data-table-refresh.svelte b/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/data-table/data-table-refresh.svelte new file mode 100644 index 0000000000..f5494625de --- /dev/null +++ b/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/data-table/data-table-refresh.svelte @@ -0,0 +1,30 @@ + + + + + + + {#if children} + {@render children()} + {:else} + New data is available! + + {/if} + + diff --git a/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/data-table/data-table-selection.svelte b/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/data-table/data-table-selection.svelte new file mode 100644 index 0000000000..8f8a2058fa --- /dev/null +++ b/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/data-table/data-table-selection.svelte @@ -0,0 +1,20 @@ + + + + +
+ {#if table.getSelectedRowModel().rows.length > 0} + {Object.keys(table.getSelectedRowModel().rows).length} of{' '} + {table.getRowModel().rows.length} row(s) selected. + {/if} +
diff --git a/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/data-table/data-table-view-options.svelte b/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/data-table/data-table-view-options.svelte index 3e37406254..390d4e9db2 100644 --- a/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/data-table/data-table-view-options.svelte +++ b/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/data-table/data-table-view-options.svelte @@ -7,7 +7,7 @@ import { Button } from '$comp/ui/button'; import * as DropdownMenu from '$comp/ui/dropdown-menu'; - import IconViewColumn from '~icons/mdi/view-column'; + import ViewColumn from 'lucide-svelte/icons/columns-3'; interface Props { table: Table; @@ -19,7 +19,7 @@ diff --git a/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/data-table/index.ts b/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/data-table/index.ts index 6d4b70e516..8bb59b7207 100644 --- a/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/data-table/index.ts +++ b/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/data-table/index.ts @@ -1,19 +1,36 @@ import Body from './data-table-body.svelte'; +import Empty from './data-table-empty.svelte'; +import Footer from './data-table-footer.svelte'; +import Loading from './data-table-loading.svelte'; +import PageCount from './data-table-page-count.svelte'; import PageSize from './data-table-page-size.svelte'; import Pagination from './data-table-pagination.svelte'; +import Refresh from './data-table-refresh.svelte'; +import Selection from './data-table-selection.svelte'; import Toolbar from './data-table-toolbar.svelte'; import Root from './data-table.svelte'; export { Body, - // Root as DataTable, Body as DataTableBody, + Empty as DataTableEmpty, + Footer as DataTableFooter, + Loading as DataTableLoading, + PageCount as DataTablePageCount, PageSize as DataTablePageSize, Pagination as DataTablePagination, + Refresh as DataTableRefresh, + Selection as DataTableSelection, Toolbar as DataTableToolbar, + Empty, + Footer, + Loading, + PageCount, PageSize, Pagination, + Refresh, Root, + Selection, Toolbar }; diff --git a/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/DateRangeDropdown.svelte b/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/date-range-dropdown.svelte similarity index 100% rename from src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/DateRangeDropdown.svelte rename to src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/date-range-dropdown.svelte diff --git a/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/delayed-render.svelte b/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/delayed-render.svelte new file mode 100644 index 0000000000..046d6ee7bf --- /dev/null +++ b/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/delayed-render.svelte @@ -0,0 +1,31 @@ + + +{#if shouldRender && children} + {@render children()} +{/if} diff --git a/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/ErrorMessage.svelte b/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/error-message.svelte similarity index 87% rename from src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/ErrorMessage.svelte rename to src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/error-message.svelte index a75400875d..2831fa9254 100644 --- a/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/ErrorMessage.svelte +++ b/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/error-message.svelte @@ -1,5 +1,5 @@ - Loading + Loading diff --git a/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/filters/facets/base/BooleanFacetedFilter.svelte b/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/faceted-filter/faceted-filter-boolean.svelte similarity index 100% rename from src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/filters/facets/base/BooleanFacetedFilter.svelte rename to src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/faceted-filter/faceted-filter-boolean.svelte diff --git a/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/faceted-filter/faceted-filter-builder-context.svelte.ts b/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/faceted-filter/faceted-filter-builder-context.svelte.ts new file mode 100644 index 0000000000..0452ac73b6 --- /dev/null +++ b/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/faceted-filter/faceted-filter-builder-context.svelte.ts @@ -0,0 +1,14 @@ +import type { Component } from 'svelte'; + +import { SvelteMap } from 'svelte/reactivity'; + +import type { FacetedFilterProps, IFilter } from './models'; + +export interface FacetFilterBuilder { + component: Component>; + create: (filter?: TFilter) => TFilter; + priority: number; + title: string; +} + +export const builderContext = $state(new SvelteMap>()); diff --git a/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/faceted-filter/faceted-filter-builder.svelte b/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/faceted-filter/faceted-filter-builder.svelte index 0b51da796d..744d97c941 100644 --- a/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/faceted-filter/faceted-filter-builder.svelte +++ b/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/faceted-filter/faceted-filter-builder.svelte @@ -1,35 +1,63 @@ {#snippet children()} {/snippet} @@ -77,8 +97,8 @@ No results found. - {#each facets as facet (facet.filter.key)} - onFacetSelected(facet)} value={facet.filter.key}>{facet.title} + {#each sortedBuilders as [key, builder]} + onFacetSelected(builder)} value={key}>{builder.title} {/each} @@ -86,7 +106,7 @@ - {#if visible.length > 0} + {#if facets.length > 0} Clear filters {/if} Close @@ -95,9 +115,11 @@ -{#each facets as facet (facet.filter.key)} - {#if isVisible(facet)} - {@const Facet = facet.component} - - {/if} +{#if children} + {@render children()} +{/if} + +{#each facets as facet (facet.filter.id)} + {@const Facet = facet.component} + {/each} diff --git a/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/filters/facets/base/DropDownFacetedFilter.svelte b/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/faceted-filter/faceted-filter-drop-down.svelte similarity index 90% rename from src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/filters/facets/base/DropDownFacetedFilter.svelte rename to src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/faceted-filter/faceted-filter-drop-down.svelte index b9c83161a0..478a365546 100644 --- a/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/filters/facets/base/DropDownFacetedFilter.svelte +++ b/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/faceted-filter/faceted-filter-drop-down.svelte @@ -1,12 +1,12 @@ - - changed(new OrganizationFilter(value))} {title} {...props}> - {#snippet children()} - - {/snippet} - diff --git a/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/filters/facets/OrganizationFacetedFilter.svelte b/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/filters/facets/OrganizationFacetedFilter.svelte deleted file mode 100644 index 8f43b8e057..0000000000 --- a/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/filters/facets/OrganizationFacetedFilter.svelte +++ /dev/null @@ -1,47 +0,0 @@ - - - { - filter.value = value; - filterChanged(filter); - }} - loading={response.isLoading} - noOptionsText="No organizations found." - {options} - remove={() => { - filter.value = undefined; - filterRemoved(filter); - }} - {title} - value={filter.value} - {...props} -> diff --git a/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/filters/facets/index.ts b/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/filters/facets/index.ts deleted file mode 100644 index 59a70096f9..0000000000 --- a/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/filters/facets/index.ts +++ /dev/null @@ -1,99 +0,0 @@ -import type { Component } from 'svelte'; - -import { - type BooleanFilter, - type DateFilter, - type IFilter, - KeywordFilter, - type NumberFilter, - OrganizationFilter, - ProjectFilter, - ReferenceFilter, - SessionFilter, - StatusFilter, - type StringFilter, - TypeFilter, - type VersionFilter -} from '../filters.svelte'; -import BooleanFacetedFilter from './BooleanFacetedFilter.svelte'; -import DateFacetedFilter from './DateFacetedFilter.svelte'; -import KeywordFacetedFilter from './KeywordFacetedFilter.svelte'; -import NumberFacetedFilter from './NumberFacetedFilter.svelte'; -import OrganizationFacetedFilter from './OrganizationFacetedFilter.svelte'; -import ProjectFacetedFilter from './ProjectFacetedFilter.svelte'; -import ReferenceFacetedFilter from './ReferenceFacetedFilter.svelte'; -import SessionFacetedFilter from './SessionFacetedFilter.svelte'; -import StatusFacetedFilter from './StatusFacetedFilter.svelte'; -import StringFacetedFilter from './StringFacetedFilter.svelte'; -import TypeFacetedFilter from './TypeFacetedFilter.svelte'; -import VersionFacetedFilter from './VersionFacetedFilter.svelte'; - -export type FacetedFilterProps = { - filter: TFilter; - filterChanged: (filter: TFilter) => void; - filterRemoved: (filter: TFilter) => void; - open: boolean; - title: string; -}; - -export class FacetedFilter { - constructor( - public title: string, - public component: Component>, - public filter: TFilter, - public open: boolean = false - ) {} -} - -export function toFacetedFilters(filters: IFilter[]): FacetedFilter[] { - return filters.map((filter) => { - switch (filter.type) { - case 'boolean': { - const booleanFilter = filter as BooleanFilter; - return new FacetedFilter((booleanFilter.term as string) ?? 'Boolean', BooleanFacetedFilter, booleanFilter); - } - case 'date': { - const dateFilter = filter as DateFilter; - const title = dateFilter.term === 'date' ? 'Date Range' : (dateFilter.term ?? 'Date'); - return new FacetedFilter(title, DateFacetedFilter, dateFilter); - } - case 'keyword': { - return new FacetedFilter('Keyword', KeywordFacetedFilter, filter as KeywordFilter); - } - case 'number': { - const numberFilter = filter as NumberFilter; - return new FacetedFilter((numberFilter.term as string) ?? 'Number', NumberFacetedFilter, numberFilter); - } - case 'organization': { - return new FacetedFilter('Organization', OrganizationFacetedFilter, filter as OrganizationFilter); - } - case 'project': { - return new FacetedFilter('Project', ProjectFacetedFilter, filter as ProjectFilter); - } - case 'reference': { - return new FacetedFilter('Reference', ReferenceFacetedFilter, filter as ReferenceFilter); - } - case 'session': { - return new FacetedFilter('Session', SessionFacetedFilter, filter as SessionFilter); - } - case 'status': { - return new FacetedFilter('Status', StatusFacetedFilter, filter as StatusFilter); - } - case 'string': { - const stringFilter = filter as StringFilter; - return new FacetedFilter((stringFilter.term as string) ?? 'String', StringFacetedFilter, stringFilter); - } - case 'type': { - return new FacetedFilter('Type', TypeFacetedFilter, filter as TypeFilter); - } - case 'version': { - const versionFilter = filter as VersionFilter; - return new FacetedFilter((versionFilter.term as string) ?? 'Version', VersionFacetedFilter, versionFilter); - } - default: { - throw new Error(`Unknown filter type: ${filter.type}`); - } - } - }) as unknown as FacetedFilter[]; - // TODO: look into why unknown is required here. -} diff --git a/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/form/PasswordInput.svelte b/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/form/password-input.svelte similarity index 100% rename from src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/form/PasswordInput.svelte rename to src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/form/password-input.svelte diff --git a/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/formatters/Bytes.svelte b/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/formatters/bytes.svelte similarity index 100% rename from src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/formatters/Bytes.svelte rename to src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/formatters/bytes.svelte diff --git a/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/formatters/DateTime.svelte b/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/formatters/date-time.svelte similarity index 100% rename from src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/formatters/DateTime.svelte rename to src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/formatters/date-time.svelte diff --git a/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/formatters/Duration.svelte b/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/formatters/duration.svelte similarity index 100% rename from src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/formatters/Duration.svelte rename to src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/formatters/duration.svelte diff --git a/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/formatters/Number.svelte b/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/formatters/number.svelte similarity index 100% rename from src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/formatters/Number.svelte rename to src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/formatters/number.svelte diff --git a/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/formatters/percentage.svelte b/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/formatters/percentage.svelte new file mode 100644 index 0000000000..a1fbec8a33 --- /dev/null +++ b/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/formatters/percentage.svelte @@ -0,0 +1,19 @@ + + +{#if percent !== null && !isNaN(percent) && isFinite(percent)} + {Intl.NumberFormat(undefined, formatOptions).format(percent / 100)} +{:else} + {Intl.NumberFormat(undefined, formatOptions).format(0)} +{/if} diff --git a/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/formatters/TimeAgo.svelte b/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/formatters/time-ago.svelte similarity index 100% rename from src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/formatters/TimeAgo.svelte rename to src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/formatters/time-ago.svelte diff --git a/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/live.svelte b/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/live.svelte new file mode 100644 index 0000000000..a8efa3d166 --- /dev/null +++ b/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/live.svelte @@ -0,0 +1,19 @@ + + +{#if live} + +{:else} + +{/if} diff --git a/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/Loading.svelte b/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/loading.svelte similarity index 93% rename from src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/Loading.svelte rename to src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/loading.svelte index 113dd5517e..3c07cfa885 100644 --- a/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/Loading.svelte +++ b/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/loading.svelte @@ -25,7 +25,7 @@
-
- +
diff --git a/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/typography/A.svelte b/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/typography/a.svelte similarity index 67% rename from src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/typography/A.svelte rename to src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/typography/a.svelte index 74336ddec5..566b78cb0a 100644 --- a/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/typography/A.svelte +++ b/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/typography/a.svelte @@ -1,12 +1,12 @@ - + {#if children} {@render children()} {/if} diff --git a/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/typography/A.ts b/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/typography/a.ts similarity index 100% rename from src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/typography/A.ts rename to src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/typography/a.ts diff --git a/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/typography/Blockquote.svelte b/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/typography/blockquote.svelte similarity index 100% rename from src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/typography/Blockquote.svelte rename to src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/typography/blockquote.svelte diff --git a/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/typography/Code.svelte b/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/typography/code.svelte similarity index 100% rename from src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/typography/Code.svelte rename to src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/typography/code.svelte diff --git a/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/typography/H1.svelte b/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/typography/h1.svelte similarity index 100% rename from src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/typography/H1.svelte rename to src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/typography/h1.svelte diff --git a/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/typography/H2.svelte b/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/typography/h2.svelte similarity index 100% rename from src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/typography/H2.svelte rename to src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/typography/h2.svelte diff --git a/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/typography/H3.svelte b/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/typography/h3.svelte similarity index 100% rename from src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/typography/H3.svelte rename to src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/typography/h3.svelte diff --git a/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/typography/H4.svelte b/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/typography/h4.svelte similarity index 100% rename from src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/typography/H4.svelte rename to src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/typography/h4.svelte diff --git a/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/typography/index.ts b/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/typography/index.ts index f78f86eb82..2ff2e68b49 100644 --- a/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/typography/index.ts +++ b/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/typography/index.ts @@ -1,18 +1,18 @@ -import type { Props as AProps } from './A'; +import type { Props as AProps } from './a'; -import A from './A.svelte'; -import Blockquote from './Blockquote.svelte'; -import Code from './Code.svelte'; -import H1 from './H1.svelte'; -import H2 from './H2.svelte'; -import H3 from './H3.svelte'; -import H4 from './H4.svelte'; -import Large from './Large.svelte'; -import Lead from './Lead.svelte'; -import List from './List.svelte'; -import Muted from './Muted.svelte'; -import P from './P.svelte'; -import Small from './Small.svelte'; +import A from './a.svelte'; +import Blockquote from './blockquote.svelte'; +import Code from './code.svelte'; +import H1 from './h1.svelte'; +import H2 from './h2.svelte'; +import H3 from './h3.svelte'; +import H4 from './h4.svelte'; +import Large from './large.svelte'; +import Lead from './lead.svelte'; +import List from './list.svelte'; +import Muted from './muted.svelte'; +import P from './p.svelte'; +import Small from './small.svelte'; export { A, Blockquote, Code, H1, H2, H3, H4, Large, Lead, List, Muted, P, Small }; export type { AProps }; diff --git a/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/typography/Large.svelte b/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/typography/large.svelte similarity index 100% rename from src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/typography/Large.svelte rename to src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/typography/large.svelte diff --git a/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/typography/Lead.svelte b/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/typography/lead.svelte similarity index 100% rename from src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/typography/Lead.svelte rename to src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/typography/lead.svelte diff --git a/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/typography/List.svelte b/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/typography/list.svelte similarity index 100% rename from src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/typography/List.svelte rename to src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/typography/list.svelte diff --git a/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/typography/Muted.svelte b/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/typography/muted.svelte similarity index 100% rename from src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/typography/Muted.svelte rename to src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/typography/muted.svelte diff --git a/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/typography/P.svelte b/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/typography/p.svelte similarity index 100% rename from src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/typography/P.svelte rename to src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/typography/p.svelte diff --git a/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/typography/Small.svelte b/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/typography/small.svelte similarity index 100% rename from src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/typography/Small.svelte rename to src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/typography/small.svelte diff --git a/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/ui/alert-dialog/alert-dialog-action.svelte b/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/ui/alert-dialog/alert-dialog-action.svelte new file mode 100644 index 0000000000..de060ae74d --- /dev/null +++ b/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/ui/alert-dialog/alert-dialog-action.svelte @@ -0,0 +1,13 @@ + + + diff --git a/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/ui/alert-dialog/alert-dialog-cancel.svelte b/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/ui/alert-dialog/alert-dialog-cancel.svelte new file mode 100644 index 0000000000..ad3889f08d --- /dev/null +++ b/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/ui/alert-dialog/alert-dialog-cancel.svelte @@ -0,0 +1,17 @@ + + + diff --git a/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/ui/alert-dialog/alert-dialog-content.svelte b/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/ui/alert-dialog/alert-dialog-content.svelte new file mode 100644 index 0000000000..858b8bc1b9 --- /dev/null +++ b/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/ui/alert-dialog/alert-dialog-content.svelte @@ -0,0 +1,26 @@ + + + + + + diff --git a/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/ui/alert-dialog/alert-dialog-description.svelte b/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/ui/alert-dialog/alert-dialog-description.svelte new file mode 100644 index 0000000000..600ef8c53b --- /dev/null +++ b/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/ui/alert-dialog/alert-dialog-description.svelte @@ -0,0 +1,16 @@ + + + diff --git a/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/ui/alert-dialog/alert-dialog-footer.svelte b/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/ui/alert-dialog/alert-dialog-footer.svelte new file mode 100644 index 0000000000..91ecaba643 --- /dev/null +++ b/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/ui/alert-dialog/alert-dialog-footer.svelte @@ -0,0 +1,20 @@ + + +
+ {@render children?.()} +
diff --git a/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/ui/alert-dialog/alert-dialog-header.svelte b/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/ui/alert-dialog/alert-dialog-header.svelte new file mode 100644 index 0000000000..44a7b082e7 --- /dev/null +++ b/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/ui/alert-dialog/alert-dialog-header.svelte @@ -0,0 +1,20 @@ + + +
+ {@render children?.()} +
diff --git a/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/ui/alert-dialog/alert-dialog-overlay.svelte b/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/ui/alert-dialog/alert-dialog-overlay.svelte new file mode 100644 index 0000000000..62acab8a4e --- /dev/null +++ b/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/ui/alert-dialog/alert-dialog-overlay.svelte @@ -0,0 +1,19 @@ + + + diff --git a/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/ui/alert-dialog/alert-dialog-title.svelte b/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/ui/alert-dialog/alert-dialog-title.svelte new file mode 100644 index 0000000000..ef197dce7d --- /dev/null +++ b/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/ui/alert-dialog/alert-dialog-title.svelte @@ -0,0 +1,18 @@ + + + diff --git a/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/ui/alert-dialog/index.ts b/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/ui/alert-dialog/index.ts new file mode 100644 index 0000000000..dd20f99cf9 --- /dev/null +++ b/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/ui/alert-dialog/index.ts @@ -0,0 +1,40 @@ +import { AlertDialog as AlertDialogPrimitive } from "bits-ui"; + +import Title from "./alert-dialog-title.svelte"; +import Action from "./alert-dialog-action.svelte"; +import Cancel from "./alert-dialog-cancel.svelte"; +import Footer from "./alert-dialog-footer.svelte"; +import Header from "./alert-dialog-header.svelte"; +import Overlay from "./alert-dialog-overlay.svelte"; +import Content from "./alert-dialog-content.svelte"; +import Description from "./alert-dialog-description.svelte"; + +const Root = AlertDialogPrimitive.Root; +const Trigger = AlertDialogPrimitive.Trigger; +const Portal = AlertDialogPrimitive.Portal; + +export { + Root, + Title, + Action, + Cancel, + Portal, + Footer, + Header, + Trigger, + Overlay, + Content, + Description, + // + Root as AlertDialog, + Title as AlertDialogTitle, + Action as AlertDialogAction, + Cancel as AlertDialogCancel, + Portal as AlertDialogPortal, + Footer as AlertDialogFooter, + Header as AlertDialogHeader, + Trigger as AlertDialogTrigger, + Overlay as AlertDialogOverlay, + Content as AlertDialogContent, + Description as AlertDialogDescription, +}; diff --git a/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/ui/badge/badge.svelte b/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/ui/badge/badge.svelte index e3b585e644..3409342ff2 100644 --- a/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/ui/badge/badge.svelte +++ b/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/ui/badge/badge.svelte @@ -42,7 +42,7 @@ this={href ? "a" : "span"} bind:this={ref} {href} - class={cn(badgeVariants({ variant, className }))} + class={cn(badgeVariants({ variant }), className)} {...restProps} > {@render children?.()} diff --git a/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/ui/breadcrumb/breadcrumb-ellipsis.svelte b/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/ui/breadcrumb/breadcrumb-ellipsis.svelte new file mode 100644 index 0000000000..3b568c41cc --- /dev/null +++ b/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/ui/breadcrumb/breadcrumb-ellipsis.svelte @@ -0,0 +1,23 @@ + + + diff --git a/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/ui/breadcrumb/breadcrumb-item.svelte b/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/ui/breadcrumb/breadcrumb-item.svelte new file mode 100644 index 0000000000..4e2a3ed861 --- /dev/null +++ b/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/ui/breadcrumb/breadcrumb-item.svelte @@ -0,0 +1,16 @@ + + +
  • + {@render children?.()} +
  • diff --git a/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/ui/breadcrumb/breadcrumb-link.svelte b/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/ui/breadcrumb/breadcrumb-link.svelte new file mode 100644 index 0000000000..c6b10194bf --- /dev/null +++ b/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/ui/breadcrumb/breadcrumb-link.svelte @@ -0,0 +1,31 @@ + + +{#if child} + {@render child({ props: attrs })} +{:else} +
    + {@render children?.()} + +{/if} diff --git a/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/ui/breadcrumb/breadcrumb-list.svelte b/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/ui/breadcrumb/breadcrumb-list.svelte new file mode 100644 index 0000000000..77c4e327ff --- /dev/null +++ b/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/ui/breadcrumb/breadcrumb-list.svelte @@ -0,0 +1,23 @@ + + +
      + {@render children?.()} +
    diff --git a/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/ui/breadcrumb/breadcrumb-page.svelte b/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/ui/breadcrumb/breadcrumb-page.svelte new file mode 100644 index 0000000000..1db5a05739 --- /dev/null +++ b/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/ui/breadcrumb/breadcrumb-page.svelte @@ -0,0 +1,23 @@ + + + + {@render children?.()} + diff --git a/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/ui/breadcrumb/breadcrumb-separator.svelte b/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/ui/breadcrumb/breadcrumb-separator.svelte new file mode 100644 index 0000000000..e550e91a4e --- /dev/null +++ b/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/ui/breadcrumb/breadcrumb-separator.svelte @@ -0,0 +1,27 @@ + + + diff --git a/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/ui/breadcrumb/breadcrumb.svelte b/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/ui/breadcrumb/breadcrumb.svelte new file mode 100644 index 0000000000..81d249f9bc --- /dev/null +++ b/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/ui/breadcrumb/breadcrumb.svelte @@ -0,0 +1,15 @@ + + + diff --git a/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/ui/breadcrumb/index.ts b/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/ui/breadcrumb/index.ts new file mode 100644 index 0000000000..dc914ec345 --- /dev/null +++ b/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/ui/breadcrumb/index.ts @@ -0,0 +1,25 @@ +import Root from "./breadcrumb.svelte"; +import Ellipsis from "./breadcrumb-ellipsis.svelte"; +import Item from "./breadcrumb-item.svelte"; +import Separator from "./breadcrumb-separator.svelte"; +import Link from "./breadcrumb-link.svelte"; +import List from "./breadcrumb-list.svelte"; +import Page from "./breadcrumb-page.svelte"; + +export { + Root, + Ellipsis, + Item, + Separator, + Link, + List, + Page, + // + Root as Breadcrumb, + Ellipsis as BreadcrumbEllipsis, + Item as BreadcrumbItem, + Separator as BreadcrumbSeparator, + Link as BreadcrumbLink, + List as BreadcrumbList, + Page as BreadcrumbPage, +}; diff --git a/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/ui/button/button.svelte b/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/ui/button/button.svelte index 868867fbf1..33aa6157a4 100644 --- a/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/ui/button/button.svelte +++ b/src/Exceptionless.Web/ClientApp/src/lib/features/shared/components/ui/button/button.svelte @@ -18,6 +18,7 @@ }, size: { default: "h-9 px-4 py-2", + xs: "h-6 rounded-md px-1 text-xs", sm: "h-8 rounded-md px-3 text-xs", lg: "h-10 rounded-md px-8", icon: "h-9 w-9", @@ -57,7 +58,7 @@ {#if href} @@ -66,7 +67,7 @@ {:else} + + + + Stack Options + + updateCritical()} + title="All future occurrences will be marked as critical" + > + Future Occurrences Are Critical + + + promoteToExternal()} title="Used to promote stacks to external systems"> + + Promote To External + + (openAddStackReferenceDialog = true)} title="Add a reference link to an external resource"> + + Add Reference Link + + + (openRemoveStackDialog = true)} class="text-destructive" title="Delete this stack"> + + Delete + + + + + +{#if openAddStackReferenceDialog} + +{/if} +{#if openRemoveStackDialog} + +{/if} +{#if openRequiresPromotedWebHookDialog} + +{/if} diff --git a/src/Exceptionless.Web/ClientApp/src/lib/features/stacks/components/stack-references.svelte b/src/Exceptionless.Web/ClientApp/src/lib/features/stacks/components/stack-references.svelte new file mode 100644 index 0000000000..a9b895ebf2 --- /dev/null +++ b/src/Exceptionless.Web/ClientApp/src/lib/features/stacks/components/stack-references.svelte @@ -0,0 +1,57 @@ + + +{#if stack.references?.length > 0} +
      + {#each stack.references as reference (reference)} +
    • + + + {reference} + + + +
    • + {/each} +
    +{/if} + +{#if openRemoveStackReferenceDialog} + +{/if} diff --git a/src/Exceptionless.Web/ClientApp/src/lib/features/stacks/components/stack-status-dropdown-menu.svelte b/src/Exceptionless.Web/ClientApp/src/lib/features/stacks/components/stack-status-dropdown-menu.svelte new file mode 100644 index 0000000000..47cd3f51ca --- /dev/null +++ b/src/Exceptionless.Web/ClientApp/src/lib/features/stacks/components/stack-status-dropdown-menu.svelte @@ -0,0 +1,143 @@ + + + + + + + + + Update Status + + markOpen()}>Open + (openMarkStackFixedInVersionDialog = true)}>Fixed + + markSnoozed()} + >Snoozed + + markSnoozed('6hours')}>6 Hours + markSnoozed('day')}>1 Day + markSnoozed('week')}>1 Week + markSnoozed('month')}>1 Month + + + markIgnored()}>Ignored + (openMarkStackDiscardedDialog = true)}>Discarded + + + + +{#if openMarkStackDiscardedDialog} + +{/if} +{#if openMarkStackFixedInVersionDialog} + +{/if} diff --git a/src/Exceptionless.Web/ClientApp/src/lib/features/stacks/components/stacks-bulk-actions-dropdown-menu.svelte b/src/Exceptionless.Web/ClientApp/src/lib/features/stacks/components/stacks-bulk-actions-dropdown-menu.svelte new file mode 100644 index 0000000000..1b2a54e5ae --- /dev/null +++ b/src/Exceptionless.Web/ClientApp/src/lib/features/stacks/components/stacks-bulk-actions-dropdown-menu.svelte @@ -0,0 +1,193 @@ + + + + + + + + + + + Bulk Actions + + markOpen()}>Mark Open + (openMarkStackFixedInVersionDialog = true)}>Mark Fixed + + markSnoozed()} + >Mark Snoozed + + markSnoozed('6hours')}>6 Hours + markSnoozed('day')}>1 Day + markSnoozed('week')}>1 Week + markSnoozed('month')}>1 Month + + + markIgnored()}>Mark Ignored + (openMarkStackDiscardedDialog = true)}>Mark Discarded + + (openRemoveStackDialog = true)} class="text-destructive" title="Delete stacks">Delete + + + + +{#if openMarkStackDiscardedDialog} + +{/if} +{#if openMarkStackFixedInVersionDialog} + +{/if} +{#if openRemoveStackDialog} + +{/if} diff --git a/src/Exceptionless.Web/ClientApp/src/lib/features/stacks/models.ts b/src/Exceptionless.Web/ClientApp/src/lib/features/stacks/models.ts index 18737b19bd..ce0194a366 100644 --- a/src/Exceptionless.Web/ClientApp/src/lib/features/stacks/models.ts +++ b/src/Exceptionless.Web/ClientApp/src/lib/features/stacks/models.ts @@ -1 +1,13 @@ export { Stack, StackStatus } from '$generated/api'; +import { IsOptional, IsSemVer, IsUrl } from 'class-validator'; + +export class FixedInVersionForm { + @IsOptional() + @IsSemVer() + version?: string; +} + +export class ReferenceLinkForm { + @IsUrl({ require_tld: false }) + url!: string; +} diff --git a/src/Exceptionless.Web/ClientApp/src/lib/features/users/api.svelte.ts b/src/Exceptionless.Web/ClientApp/src/lib/features/users/api.svelte.ts index f867d116af..b431f7a6ce 100644 --- a/src/Exceptionless.Web/ClientApp/src/lib/features/users/api.svelte.ts +++ b/src/Exceptionless.Web/ClientApp/src/lib/features/users/api.svelte.ts @@ -4,7 +4,7 @@ import { accessToken } from '$features/auth/index.svelte'; import { ProblemDetails, useFetchClient } from '@exceptionless/fetchclient'; import { createMutation, createQuery, QueryClient, useQueryClient } from '@tanstack/svelte-query'; -import { UpdateEmailAddressResult, type UpdateUser, User } from './models'; +import { UpdateEmailAddressResult, type UpdateUser, UpdateUserEmailAddress, User } from './models'; export async function invalidateUserQueries(queryClient: QueryClient, message: WebSocketMessageValue<'UserChanged'>) { const { id } = message; @@ -24,21 +24,27 @@ export const queryKeys = { id: (id: string | undefined) => [...queryKeys.type, id] as const, idEmailAddress: (id?: string) => [...queryKeys.id(id), 'email-address'] as const, me: () => [...queryKeys.type, 'me'] as const, + patchUser: (id: string | undefined) => [...queryKeys.id(id), 'patch'] as const, + postEmailAddress: (id: string | undefined) => [...queryKeys.idEmailAddress(id), 'update'] as const, type: ['User'] as const }; -export interface UpdateEmailAddressProps { - id: string | undefined; +export interface PatchUserRequest { + route: { + id: string | undefined; + }; } -export interface UpdateUserProps { - id: string | undefined; +export interface PostEmailAddressRequest { + route: { + id: string | undefined; + }; } export function getMeQuery() { const queryClient = useQueryClient(); return createQuery(() => ({ - enabled: () => !!accessToken.value, + enabled: () => !!accessToken.current, onSuccess: (data: User) => { queryClient.setQueryData(queryKeys.id(data.id!), data); }, @@ -55,51 +61,51 @@ export function getMeQuery() { })); } -export function mutateEmailAddress(props: UpdateEmailAddressProps) { +export function patchUser(request: PatchUserRequest) { const queryClient = useQueryClient(); - return createMutation>(() => ({ - enabled: () => !!accessToken.value && !!props.id, - mutationFn: async (data: Pick) => { + return createMutation(() => ({ + enabled: () => !!accessToken.current && !!request.route.id, + mutationFn: async (data: UpdateUser) => { const client = useFetchClient(); - const response = await client.postJSON(`users/${props.id}/email-address/${data.email_address}`); + const response = await client.patchJSON(`users/${request.route.id}`, data); return response.data!; }, - mutationKey: queryKeys.idEmailAddress(props.id), - onSuccess: (data, variables) => { - const partialUserData: Partial = { email_address: variables.email_address, is_email_address_verified: data.is_verified }; - - const user = queryClient.getQueryData(queryKeys.id(props.id)); - if (user) { - queryClient.setQueryData(queryKeys.id(props.id), { ...user, ...partialUserData }); - } + mutationKey: queryKeys.patchUser(request.route.id), + onError: () => { + queryClient.invalidateQueries({ queryKey: queryKeys.id(request.route.id) }); + }, + onSuccess: (data) => { + queryClient.setQueryData(queryKeys.id(request.route.id), data); const currentUser = queryClient.getQueryData(queryKeys.me()); - if (currentUser?.id === props.id) { - queryClient.setQueryData(queryKeys.me(), { ...currentUser, ...partialUserData }); + if (currentUser?.id === request.route.id) { + queryClient.setQueryData(queryKeys.me(), data); } } })); } -export function mutateUser(props: UpdateUserProps) { +export function postEmailAddress(request: PostEmailAddressRequest) { const queryClient = useQueryClient(); - return createMutation(() => ({ - enabled: () => !!accessToken.value && !!props.id, - mutationFn: async (data: UpdateUser) => { + return createMutation(() => ({ + enabled: () => !!accessToken.current && !!request.route.id, + mutationFn: async (data: Pick) => { const client = useFetchClient(); - const response = await client.patchJSON(`users/${props.id}`, data); + const response = await client.postJSON(`users/${request.route.id}/email-address/${data.email_address}`); return response.data!; }, - mutationKey: queryKeys.id(props.id), - onError: () => { - queryClient.invalidateQueries({ queryKey: queryKeys.id(props.id) }); - }, - onSuccess: (data) => { - queryClient.setQueryData(queryKeys.id(props.id), data); + mutationKey: queryKeys.postEmailAddress(request.route.id), + onSuccess: (data, variables) => { + const partialUserData: Partial = { email_address: variables.email_address, is_email_address_verified: data.is_verified }; + + const user = queryClient.getQueryData(queryKeys.id(request.route.id)); + if (user) { + queryClient.setQueryData(queryKeys.id(request.route.id), { ...user, ...partialUserData }); + } const currentUser = queryClient.getQueryData(queryKeys.me()); - if (currentUser?.id === props.id) { - queryClient.setQueryData(queryKeys.me(), data); + if (currentUser?.id === request.route.id) { + queryClient.setQueryData(queryKeys.me(), { ...currentUser, ...partialUserData }); } } })); diff --git a/src/Exceptionless.Web/ClientApp/src/lib/features/users/gravatar.svelte.ts b/src/Exceptionless.Web/ClientApp/src/lib/features/users/gravatar.svelte.ts index 679a55cd5d..4fa64add5d 100644 --- a/src/Exceptionless.Web/ClientApp/src/lib/features/users/gravatar.svelte.ts +++ b/src/Exceptionless.Web/ClientApp/src/lib/features/users/gravatar.svelte.ts @@ -1,24 +1,19 @@ +import { getInitials } from '$shared/strings'; + import { getMeQuery } from './api.svelte'; -export function getGravatarFromCurrentUser(query?: ReturnType) { +export interface Gravatar { + initials: string; + src: Promise; +} +export function getGravatarFromCurrentUser(query?: ReturnType): Gravatar { const meQuery = query ?? getMeQuery(); const fullName = $derived(meQuery.data?.full_name); const emailAddress = $derived(meQuery.data?.email_address); return { get initials() { - if (!fullName) { - return 'NA'; - } - - const initials = fullName - .split(' ') - .map((name) => name.trim()) - .filter((name) => name.length > 0) - .map((name) => name[0]) - .join(''); - - return initials.length > 2 ? initials.substring(0, 2) : initials; + return getInitials(fullName); }, get src() { return emailAddress ? getGravatarSrc(emailAddress) : Promise.resolve(null); diff --git a/src/Exceptionless.Web/ClientApp/src/lib/features/users/models.ts b/src/Exceptionless.Web/ClientApp/src/lib/features/users/models.ts index aae2787569..b49d50e846 100644 --- a/src/Exceptionless.Web/ClientApp/src/lib/features/users/models.ts +++ b/src/Exceptionless.Web/ClientApp/src/lib/features/users/models.ts @@ -1,4 +1,4 @@ -import { IsOptional } from 'class-validator'; +import { IsEmail, IsOptional } from 'class-validator'; export { UpdateEmailAddressResult, User } from '$generated/api'; @@ -6,3 +6,8 @@ export class UpdateUser { @IsOptional() email_notifications_enabled?: boolean; @IsOptional() full_name?: string; } + +export class UpdateUserEmailAddress { + @IsEmail({ require_tld: false }, { message: 'Email must be a valid email address.' }) + email_address!: string; +} diff --git a/src/Exceptionless.Web/ClientApp/src/lib/features/websockets/WebSocketClient.svelte.ts b/src/Exceptionless.Web/ClientApp/src/lib/features/websockets/web-socket-client.svelte.ts similarity index 96% rename from src/Exceptionless.Web/ClientApp/src/lib/features/websockets/WebSocketClient.svelte.ts rename to src/Exceptionless.Web/ClientApp/src/lib/features/websockets/web-socket-client.svelte.ts index f0b3df6d97..08fa17d549 100644 --- a/src/Exceptionless.Web/ClientApp/src/lib/features/websockets/WebSocketClient.svelte.ts +++ b/src/Exceptionless.Web/ClientApp/src/lib/features/websockets/web-socket-client.svelte.ts @@ -22,8 +22,8 @@ export class WebSocketClient { const visibility = new DocumentVisibility(); $effect(() => { - if (this.accessToken !== accessToken.value) { - this.accessToken = accessToken.value; + if (this.accessToken !== accessToken.current) { + this.accessToken = accessToken.current; this.close(); } else if (!visibility.visible) { this.close(); diff --git a/src/Exceptionless.Web/ClientApp/src/lib/generated/api.ts b/src/Exceptionless.Web/ClientApp/src/lib/generated/api.ts index 730409eb69..f8d5b249fe 100644 --- a/src/Exceptionless.Web/ClientApp/src/lib/generated/api.ts +++ b/src/Exceptionless.Web/ClientApp/src/lib/generated/api.ts @@ -506,3 +506,7 @@ export class WebHook { /** @format date-time */ @IsDate({ message: 'created_utc must be a valid date and time.' }) created_utc!: string; } + +export class WorkInProgressResult { + @IsDefined({ message: 'workers is required.' }) workers!: string[]; +} diff --git a/src/Exceptionless.Web/ClientApp/src/routes/(app)/(components)/layouts/Navbar.svelte b/src/Exceptionless.Web/ClientApp/src/routes/(app)/(components)/layouts/Navbar.svelte deleted file mode 100644 index f8db209ad4..0000000000 --- a/src/Exceptionless.Web/ClientApp/src/routes/(app)/(components)/layouts/Navbar.svelte +++ /dev/null @@ -1,127 +0,0 @@ - - - diff --git a/src/Exceptionless.Web/ClientApp/src/routes/(app)/(components)/layouts/Footer.svelte b/src/Exceptionless.Web/ClientApp/src/routes/(app)/(components)/layouts/footer.svelte similarity index 83% rename from src/Exceptionless.Web/ClientApp/src/routes/(app)/(components)/layouts/Footer.svelte rename to src/Exceptionless.Web/ClientApp/src/routes/(app)/(components)/layouts/footer.svelte index ae5d3fe726..d47d71bff7 100644 --- a/src/Exceptionless.Web/ClientApp/src/routes/(app)/(components)/layouts/Footer.svelte +++ b/src/Exceptionless.Web/ClientApp/src/routes/(app)/(components)/layouts/footer.svelte @@ -1,9 +1,9 @@ @@ -32,16 +32,16 @@
    diff --git a/src/Exceptionless.Web/ClientApp/src/routes/(app)/(components)/layouts/navbar.svelte b/src/Exceptionless.Web/ClientApp/src/routes/(app)/(components)/layouts/navbar.svelte new file mode 100644 index 0000000000..693909f825 --- /dev/null +++ b/src/Exceptionless.Web/ClientApp/src/routes/(app)/(components)/layouts/navbar.svelte @@ -0,0 +1,49 @@ + + + diff --git a/src/Exceptionless.Web/ClientApp/src/routes/(app)/(components)/layouts/sidebar-organization-switcher.svelte b/src/Exceptionless.Web/ClientApp/src/routes/(app)/(components)/layouts/sidebar-organization-switcher.svelte new file mode 100644 index 0000000000..499b24b182 --- /dev/null +++ b/src/Exceptionless.Web/ClientApp/src/routes/(app)/(components)/layouts/sidebar-organization-switcher.svelte @@ -0,0 +1,98 @@ + + +{#if organizations && organizations.length > 1} + + + + + {#snippet child({ props })} + + + {getInitials(activeOrganization?.name)} + +
    + + {activeOrganization?.name} + + {activeOrganization?.plan_name} +
    + +
    + {/snippet} +
    + + Organizations + {#if organizations} + {#each organizations as organization, index (organization.name)} + onOrganizationSelected(organization)} + data-active={organization.id === selected} + class="gap-2 p-2 data-[active=true]:bg-accent data-[active=true]:text-accent-foreground" + > + + {getInitials(organization.name)} + + {organization.name} + ⌘{index + 1} + + {/each} + {/if} + + +
    + +
    +
    Add organization
    +
    +
    +
    +
    +
    +{:else if isLoading} + + + + + +
    + + +
    +
    +
    +
    +
    +{/if} diff --git a/src/Exceptionless.Web/ClientApp/src/routes/(app)/(components)/layouts/sidebar-user.svelte b/src/Exceptionless.Web/ClientApp/src/routes/(app)/(components)/layouts/sidebar-user.svelte new file mode 100644 index 0000000000..9344bc48db --- /dev/null +++ b/src/Exceptionless.Web/ClientApp/src/routes/(app)/(components)/layouts/sidebar-user.svelte @@ -0,0 +1,150 @@ + + +{#if isLoading} + + + + +
    + + +
    +
    +
    +
    +{:else} + + + + + {#snippet child({ props })} + + + {#await gravatar.src} + {gravatar.initials} + {:then src} + + {/await} + {gravatar.initials} + +
    + {user?.full_name} + {user?.email_address} +
    + +
    + {/snippet} +
    + + +
    + + {#await gravatar.src} + {gravatar.initials} + {:then src} + + {/await} + {gravatar.initials} + +
    + {user?.full_name} + {user?.email_address} +
    +
    +
    + + + + + + Account + ⇧⌘ga + + + + + + + Help + + + + + Documentation + ⌘gw + + + + Support + ⌘gs + + + + GitHub + ⌘gg + + + + API Reference + + + + Keyboard shortcuts + ⌘K + + + + + + + Log out + ⇧⌘Q + +
    +
    +
    +
    +{/if} diff --git a/src/Exceptionless.Web/ClientApp/src/routes/(app)/(components)/layouts/Sidebar.svelte b/src/Exceptionless.Web/ClientApp/src/routes/(app)/(components)/layouts/sidebar.svelte similarity index 62% rename from src/Exceptionless.Web/ClientApp/src/routes/(app)/(components)/layouts/Sidebar.svelte rename to src/Exceptionless.Web/ClientApp/src/routes/(app)/(components)/layouts/sidebar.svelte index 21b82a825d..0c97bd86b4 100644 --- a/src/Exceptionless.Web/ClientApp/src/routes/(app)/(components)/layouts/Sidebar.svelte +++ b/src/Exceptionless.Web/ClientApp/src/routes/(app)/(components)/layouts/sidebar.svelte @@ -1,28 +1,37 @@ - - - + + + {#if header} + {@render header()} + {/if} + + + {#each dashboardRoutes as route (route.href)} {@const Icon = route.icon} - + {#snippet child({ props })} @@ -36,4 +45,9 @@ + + {#if footer} + {@render footer()} + {/if} + diff --git a/src/Exceptionless.Web/ClientApp/src/routes/(app)/(components)/NavigationCommand.svelte b/src/Exceptionless.Web/ClientApp/src/routes/(app)/(components)/navigation-command.svelte similarity index 95% rename from src/Exceptionless.Web/ClientApp/src/routes/(app)/(components)/NavigationCommand.svelte rename to src/Exceptionless.Web/ClientApp/src/routes/(app)/(components)/navigation-command.svelte index 1d397bb7a9..1376ed52a3 100644 --- a/src/Exceptionless.Web/ClientApp/src/routes/(app)/(components)/NavigationCommand.svelte +++ b/src/Exceptionless.Web/ClientApp/src/routes/(app)/(components)/navigation-command.svelte @@ -27,7 +27,7 @@ {#each items as route (route.href)} - + {#if route.icon} {@const Icon = route.icon} diff --git a/src/Exceptionless.Web/ClientApp/src/routes/(app)/+layout.svelte b/src/Exceptionless.Web/ClientApp/src/routes/(app)/+layout.svelte index 368c7f2a0b..2a7e076559 100644 --- a/src/Exceptionless.Web/ClientApp/src/routes/(app)/+layout.svelte +++ b/src/Exceptionless.Web/ClientApp/src/routes/(app)/+layout.svelte @@ -1,33 +1,37 @@ {#if isAuthenticated} - - + + + {#snippet header()} + + {/snippet} + + {#snippet footer()} + + {/snippet} +
    - {#key $page.url.pathname} + {#key page.url.pathname}
    {@render children()}
    {/key}
    - +
    {/if} diff --git a/src/Exceptionless.Web/ClientApp/src/routes/(app)/+page.svelte b/src/Exceptionless.Web/ClientApp/src/routes/(app)/+page.svelte index a0c7663af5..abaedabe51 100644 --- a/src/Exceptionless.Web/ClientApp/src/routes/(app)/+page.svelte +++ b/src/Exceptionless.Web/ClientApp/src/routes/(app)/+page.svelte @@ -1,63 +1,73 @@ diff --git a/src/Exceptionless.Web/ClientApp/src/routes/(app)/account/appearance/(components)/ThemePreview.svelte b/src/Exceptionless.Web/ClientApp/src/routes/(app)/account/appearance/(components)/theme-preview.svelte similarity index 87% rename from src/Exceptionless.Web/ClientApp/src/routes/(app)/account/appearance/(components)/ThemePreview.svelte rename to src/Exceptionless.Web/ClientApp/src/routes/(app)/account/appearance/(components)/theme-preview.svelte index e6408eec63..c1a6803bf3 100644 --- a/src/Exceptionless.Web/ClientApp/src/routes/(app)/account/appearance/(components)/ThemePreview.svelte +++ b/src/Exceptionless.Web/ClientApp/src/routes/(app)/account/appearance/(components)/theme-preview.svelte @@ -21,11 +21,11 @@
    -
    +
    -
    +
    @@ -41,11 +41,11 @@
    -
    +
    -
    +
    diff --git a/src/Exceptionless.Web/ClientApp/src/routes/(app)/account/appearance/+page.svelte b/src/Exceptionless.Web/ClientApp/src/routes/(app)/account/appearance/+page.svelte index dcce14eabc..0c756e8ee5 100644 --- a/src/Exceptionless.Web/ClientApp/src/routes/(app)/account/appearance/+page.svelte +++ b/src/Exceptionless.Web/ClientApp/src/routes/(app)/account/appearance/+page.svelte @@ -5,7 +5,7 @@ import { Separator } from '$comp/ui/separator'; import { setMode, userPrefersMode } from 'mode-watcher'; - import ThemePreview from './(components)/ThemePreview.svelte'; + import ThemePreview from './(components)/theme-preview.svelte'; function onUserThemePreferenceChange(mode?: string) { setMode(mode as 'dark' | 'light' | 'system'); diff --git a/src/Exceptionless.Web/ClientApp/src/routes/(app)/account/manage/+page.svelte b/src/Exceptionless.Web/ClientApp/src/routes/(app)/account/manage/+page.svelte index ceb08a68a9..d7ed33c147 100644 --- a/src/Exceptionless.Web/ClientApp/src/routes/(app)/account/manage/+page.svelte +++ b/src/Exceptionless.Web/ClientApp/src/routes/(app)/account/manage/+page.svelte @@ -1,15 +1,14 @@
    @@ -17,7 +17,7 @@
    • - +

      Wisconsin

      Signed in on @@ -29,7 +29,7 @@
    • - +

      Texas

      Signed in on diff --git a/src/Exceptionless.Web/ClientApp/src/routes/(app)/account/verify/+page.svelte b/src/Exceptionless.Web/ClientApp/src/routes/(app)/account/verify/+page.svelte index c8eeabbb3a..1a59b1fc5a 100644 --- a/src/Exceptionless.Web/ClientApp/src/routes/(app)/account/verify/+page.svelte +++ b/src/Exceptionless.Web/ClientApp/src/routes/(app)/account/verify/+page.svelte @@ -1,11 +1,11 @@