diff --git a/.codex/environments/environment.toml b/.codex/environments/environment.toml index 0d6d8393fd..df9fe419ab 100644 --- a/.codex/environments/environment.toml +++ b/.codex/environments/environment.toml @@ -82,7 +82,7 @@ export DOTNET_ENVIRONMENT=Development export ASPNETCORE_ENVIRONMENT=Development if command -v aspire >/dev/null 2>&1; then - aspire run + aspire run --nologo --non-interactive else dotnet run --project src/Exceptionless.AppHost/Exceptionless.AppHost.csproj fi diff --git a/build/update-config-next.sh b/build/update-config-next.sh index 61a2b2750d..6eac88f9b8 100644 --- a/build/update-config-next.sh +++ b/build/update-config-next.sh @@ -1,6 +1,7 @@ #!/bin/bash ApiUrl="${EX_ApiUrl:-}" +ClientSetupShowServerUrl="${EX_ClientSetupShowServerUrl:-true}" EnableAccountCreation="${EX_EnableAccountCreation:-true}" OAuth="${EX_ConnectionStrings__OAuth:-}" @@ -37,6 +38,7 @@ config=" PUBLIC_ENABLE_ACCOUNT_CREATION: '$EnableAccountCreation', PUBLIC_SYSTEM_NOTIFICATION_MESSAGE: '$EX_NotificationMessage', PUBLIC_EXCEPTIONLESS_API_KEY: '$EX_ExceptionlessApiKey', + PUBLIC_EXCEPTIONLESS_CLIENT_SETUP_SHOW_SERVER_URL: '$ClientSetupShowServerUrl', PUBLIC_EXCEPTIONLESS_SERVER_URL: '$EX_ExceptionlessServerUrl', PUBLIC_STRIPE_PUBLISHABLE_KEY: '$EX_StripePublishableApiKey', PUBLIC_FACEBOOK_APPID: '$FacebookAppId', diff --git a/k8s/ex-prod-values.yaml b/k8s/ex-prod-values.yaml index 7af1e2b288..4a0ac0488d 100644 --- a/k8s/ex-prod-values.yaml +++ b/k8s/ex-prod-values.yaml @@ -28,6 +28,7 @@ jobs: maxReplicaCount: 5 config: + EX_ClientSetupShowServerUrl: "false" EX_EnableSnapshotJobs: "true" EX_SmtpFrom: "Exceptionless " EX_ContactEmailAddress: "support@exceptionless.com" diff --git a/src/Exceptionless.AppHost/Program.cs b/src/Exceptionless.AppHost/Program.cs index f0bb50f674..59003cfe45 100644 --- a/src/Exceptionless.AppHost/Program.cs +++ b/src/Exceptionless.AppHost/Program.cs @@ -14,6 +14,8 @@ int oldAppLiveReloadPort = worktreePorts?.OldAppLiveReload ?? 35729; string oldAppAspNetCoreUrls = String.Concat("http://localhost:", oldAppHttpPort); int appPort = worktreePorts?.AppHttps ?? 7131; +const int DefaultApiHttpsPort = 7111; +string exceptionlessServerUrl = worktreePorts?.ApiHttpsUrl ?? $"https://api-ex.dev.localhost:{DefaultApiHttpsPort}"; const string SharedEmailConnectionString = "smtp://localhost:1025"; var elastic = builder.AddElasticsearch("Elasticsearch", port: 9200) @@ -172,14 +174,13 @@ oldApp.WithEnvironment("API_HTTP", worktreePorts.ApiHttpUrl) .WithEnvironment("API_HTTPS", worktreePorts.ApiHttpsUrl); } -#pragma warning restore ASPIREBROWSERLOGS001 -#pragma warning disable ASPIREBROWSERLOGS001 var app = builder.AddViteApp("App", "../Exceptionless.Web/ClientApp") .WithBrowserLogs() .WithReference(api) .WithReference(oldApp) .RemoveJavaScriptDebuggingAnnotation() + .WithEnvironment("PUBLIC_EXCEPTIONLESS_SERVER_URL", exceptionlessServerUrl) .WithEnvironment("PORT", appPort.ToString()) .WithEndpoint("http", e => { diff --git a/src/Exceptionless.Web/ClientApp/.env b/src/Exceptionless.Web/ClientApp/.env index b404d03402..c7364a03d3 100644 --- a/src/Exceptionless.Web/ClientApp/.env +++ b/src/Exceptionless.Web/ClientApp/.env @@ -1,12 +1,13 @@ PUBLIC_BASE_URL= PUBLIC_ENABLE_ACCOUNT_CREATION=true -PUBLIC_SYSTEM_NOTIFICATION_MESSAGE= PUBLIC_EXCEPTIONLESS_API_KEY= +PUBLIC_EXCEPTIONLESS_CLIENT_SETUP_SHOW_SERVER_URL=true PUBLIC_EXCEPTIONLESS_SERVER_URL= -PUBLIC_STRIPE_PUBLISHABLE_KEY= PUBLIC_FACEBOOK_APPID= PUBLIC_GITHUB_APPID= PUBLIC_GOOGLE_APPID= -PUBLIC_MICROSOFT_APPID= PUBLIC_INTERCOM_APPID= +PUBLIC_MICROSOFT_APPID= PUBLIC_SLACK_APPID= +PUBLIC_STRIPE_PUBLISHABLE_KEY= +PUBLIC_SYSTEM_NOTIFICATION_MESSAGE= diff --git a/src/Exceptionless.Web/ClientApp/e2e/support/exceptionless-journey.ts b/src/Exceptionless.Web/ClientApp/e2e/support/exceptionless-journey.ts index 1a32eb1247..10623ad3cb 100644 --- a/src/Exceptionless.Web/ClientApp/e2e/support/exceptionless-journey.ts +++ b/src/Exceptionless.Web/ClientApp/e2e/support/exceptionless-journey.ts @@ -118,7 +118,7 @@ export class ExceptionlessE2EJourney { this.projectId = getIdFromUrl(this.page, /\/project\/([^/]+)\/configure/); } - await expect(this.page.getByText('Select your project type.')).toBeVisible(); + await expect(this.page.getByText('Choose your project type.')).toBeVisible(); await selectProjectType(this.page, 'Bash Shell'); await expect(this.page.getByText('Execute the following in your shell.')).toBeVisible(); @@ -222,7 +222,7 @@ export class ExceptionlessE2EJourney { await this.page.waitForURL(/\/next\/project\/[^/]+\/configure/, { timeout: 30_000 }); this.projectId = getIdFromUrl(this.page, /\/project\/([^/]+)\/configure/); this.organizationId = await this.getOrganizationIdByName(); - await expect(this.page.getByText('Select your project type.')).toBeVisible(); + await expect(this.page.getByText('Choose your project type.')).toBeVisible(); } async submitRepresentativeEvent(): Promise { diff --git a/src/Exceptionless.Web/ClientApp/src/lib/features/organizations/components/notifications/project-configuration-notification.svelte b/src/Exceptionless.Web/ClientApp/src/lib/features/organizations/components/notifications/project-configuration-notification.svelte index 743190cc76..9f93a65049 100644 --- a/src/Exceptionless.Web/ClientApp/src/lib/features/organizations/components/notifications/project-configuration-notification.svelte +++ b/src/Exceptionless.Web/ClientApp/src/lib/features/organizations/components/notifications/project-configuration-notification.svelte @@ -24,20 +24,8 @@ - We haven't received any data! + We haven't received any events yet! - Please configure your clients for - {#if projects.length === 1} - the - {/if} - {#each projects as project, index (project.id)} - {#if index > 0}, - {/if}{project.name} - {/each} - {#if projects.length === 1} - project - {:else} - projects - {/if} and start becoming exceptionless! + Open Client setup for this project and start becoming Exceptionless! diff --git a/src/Exceptionless.Web/ClientApp/src/lib/features/organizations/components/notifications/setup-first-project-notification.svelte b/src/Exceptionless.Web/ClientApp/src/lib/features/organizations/components/notifications/setup-first-project-notification.svelte index 919dd1e5ea..435131085d 100644 --- a/src/Exceptionless.Web/ClientApp/src/lib/features/organizations/components/notifications/setup-first-project-notification.svelte +++ b/src/Exceptionless.Web/ClientApp/src/lib/features/organizations/components/notifications/setup-first-project-notification.svelte @@ -13,6 +13,6 @@ Please add a new project - and start becoming exceptionless in less than 60 seconds! + and start becoming Exceptionless in less than 60 seconds! diff --git a/src/Exceptionless.Web/ClientApp/src/lib/features/projects/components/table/project-actions-cell.svelte b/src/Exceptionless.Web/ClientApp/src/lib/features/projects/components/table/project-actions-cell.svelte index c6d527c87d..e3aee0f7c3 100644 --- a/src/Exceptionless.Web/ClientApp/src/lib/features/projects/components/table/project-actions-cell.svelte +++ b/src/Exceptionless.Web/ClientApp/src/lib/features/projects/components/table/project-actions-cell.svelte @@ -57,7 +57,7 @@ goto(resolve('/(app)/project/[projectId]/configure', { projectId: project.id }))}> - Download & Configure Client + Client Setup (showRemoveProjectDialog = true)} disabled={removeProject.isPending}> diff --git a/src/Exceptionless.Web/ClientApp/src/routes/(app)/+layout.svelte b/src/Exceptionless.Web/ClientApp/src/routes/(app)/+layout.svelte index cfc598a67f..9deb9cb990 100644 --- a/src/Exceptionless.Web/ClientApp/src/routes/(app)/+layout.svelte +++ b/src/Exceptionless.Web/ClientApp/src/routes/(app)/+layout.svelte @@ -10,7 +10,6 @@ import { getIntercomTokenQuery } from '$features/auth/api.svelte'; import { accessToken, gotoLogin } from '$features/auth/index.svelte'; import { UpgradeRequiredDialog } from '$features/billing'; - import { upgradeRequiredDialog } from '$features/billing/upgrade-required.svelte'; import { invalidatePersistentEventQueries } from '$features/events/api.svelte'; import { filterUsesPremiumFeatures } from '$features/events/premium-filter'; import { buildIntercomBootOptions, IntercomShell } from '$features/intercom'; @@ -525,9 +524,7 @@ {/snippet} - {#if upgradeRequiredDialog.open} - - {/if} + {/if} diff --git a/src/Exceptionless.Web/ClientApp/src/routes/(app)/organization/[organizationId]/routes.svelte.ts b/src/Exceptionless.Web/ClientApp/src/routes/(app)/organization/[organizationId]/routes.svelte.ts index df4dd437d8..5fd6836d04 100644 --- a/src/Exceptionless.Web/ClientApp/src/routes/(app)/organization/[organizationId]/routes.svelte.ts +++ b/src/Exceptionless.Web/ClientApp/src/routes/(app)/organization/[organizationId]/routes.svelte.ts @@ -23,12 +23,6 @@ export function routes(): NavigationItem[] { icon: Settings, title: 'General' }, - { - group: 'Organization Settings', - href: resolve('/(app)/organization/[organizationId]/usage', { organizationId }), - icon: Usage, - title: 'Usage' - }, { group: 'Organization Settings', href: resolve('/(app)/organization/[organizationId]/users', { organizationId }), @@ -47,6 +41,12 @@ export function routes(): NavigationItem[] { icon: Zap, show: (ctx) => !!ctx.user?.roles?.includes('global'), title: 'Features' + }, + { + group: 'Organization Settings', + href: resolve('/(app)/organization/[organizationId]/usage', { organizationId }), + icon: Usage, + title: 'Usage' } ]; } diff --git a/src/Exceptionless.Web/ClientApp/src/routes/(app)/project/[projectId]/api-keys/+page.svelte b/src/Exceptionless.Web/ClientApp/src/routes/(app)/project/[projectId]/api-keys/+page.svelte index 0d1e1bcf4a..4c69ffdaed 100644 --- a/src/Exceptionless.Web/ClientApp/src/routes/(app)/project/[projectId]/api-keys/+page.svelte +++ b/src/Exceptionless.Web/ClientApp/src/routes/(app)/project/[projectId]/api-keys/+page.svelte @@ -1,9 +1,10 @@
- Create and manage API keys for authenticating your applications with Exceptionless +
+ Create and manage API keys for applications and services sending events to Exceptionless. + + To install an SDK and start sending events, open + Client setup. + +
{#snippet toolbarChildren()} diff --git a/src/Exceptionless.Web/ClientApp/src/routes/(app)/project/[projectId]/configure/+page.svelte b/src/Exceptionless.Web/ClientApp/src/routes/(app)/project/[projectId]/configure/+page.svelte index ff72f87c39..1b341eb762 100644 --- a/src/Exceptionless.Web/ClientApp/src/routes/(app)/project/[projectId]/configure/+page.svelte +++ b/src/Exceptionless.Web/ClientApp/src/routes/(app)/project/[projectId]/configure/+page.svelte @@ -6,19 +6,18 @@ import { A, CodeBlock, Muted, P } from '$comp/typography'; import { Button } from '$comp/ui/button'; import * as Select from '$comp/ui/select'; - import { Spinner } from '$comp/ui/spinner'; import { env } from '$env/dynamic/public'; import { ProjectFilter } from '$features/events/components/filters'; import { getIntercom } from '$features/intercom'; import { openSupportChat } from '$features/intercom/chat'; import { organization } from '$features/organizations/context.svelte'; import { useHideOrganizationNotifications } from '$features/organizations/hooks/use-hide-organization-notifications.svelte'; - import { generateSampleData } from '$features/projects/api.svelte'; import { getProjectDefaultTokenQuery, patchToken } from '$features/tokens/api.svelte'; import EnableTokenDialog from '$features/tokens/components/dialogs/enable-token-dialog.svelte'; import { ChangeType, type WebSocketMessageValue } from '$features/websockets/models'; + import ArrowLeft from '@lucide/svelte/icons/arrow-left'; + import Bot from '@lucide/svelte/icons/bot'; import Events from '@lucide/svelte/icons/calendar-days'; - import Database from '@lucide/svelte/icons/database'; import NotificationSettings from '@lucide/svelte/icons/mail'; import { queryParamsState } from 'kit-query-params'; import { useEventListener } from 'runed'; @@ -40,7 +39,10 @@ }); const apiKey = $derived(defaultTokenQuery.data?.id || 'YOUR_API_KEY'); - const serverUrl = env.PUBLIC_API_URL || window.location.origin; + const serverUrl = (env.PUBLIC_EXCEPTIONLESS_SERVER_URL || '').trim(); + const showServerUrl = env.PUBLIC_EXCEPTIONLESS_CLIENT_SETUP_SHOW_SERVER_URL !== 'false'; + const shouldShowServerUrl = showServerUrl && serverUrl.length > 0; + const eventSubmissionUrl = serverUrl.length > 0 ? `${serverUrl}/api/v2/events` : 'YOUR_EXCEPTIONLESS_SERVER_URL/api/v2/events'; const isTokenDisabled = $derived(defaultTokenQuery.data?.is_disabled ?? false); const isTokenSuspended = $derived(defaultTokenQuery.data?.is_suspended ?? false); @@ -55,14 +57,6 @@ } }); - const generateSampleDataMutation = generateSampleData({ - route: { - get id() { - return projectId; - } - } - }); - async function enableToken() { toast.dismiss(toastId); @@ -75,18 +69,6 @@ } } - async function generateProjectSampleData() { - toast.dismiss(toastId); - - try { - await generateSampleDataMutation.mutateAsync(); - toastId = toast.success('Sample data generation has been queued. Events will appear shortly.'); - } catch (error) { - toastId = toast.error('Failed to generate sample data. Please try again.'); - throw error; - } - } - interface ProjectType { config?: string; id: string; @@ -185,7 +167,18 @@ }); const codeSamples = $derived({ - aspNetCore: `using Exceptionless; + aspNetCore: shouldShowServerUrl + ? `using Exceptionless; + +var builder = WebApplication.CreateBuilder(args); +builder.Services.AddExceptionless(c => { + c.ApiKey = "${apiKey}"; + c.ServerUrl = "${serverUrl}"; +}); + +var app = builder.Build(); +app.UseExceptionless();` + : `using Exceptionless; var builder = WebApplication.CreateBuilder(args); builder.Services.AddExceptionless("${apiKey}"); @@ -193,7 +186,7 @@ builder.Services.AddExceptionless("${apiKey}"); var app = builder.Build(); app.UseExceptionless();`, - bashShell: `curl "${serverUrl}/api/v2/events" \\ + bashShell: `curl "${eventSubmissionUrl}" \\ --request POST \\ --header "Authorization: Bearer ${apiKey}" \\ --header "Content-Type: application/json" \\ @@ -202,19 +195,27 @@ app.UseExceptionless();`, browserJs: `import { Exceptionless } from "@exceptionless/browser"; await Exceptionless.startup(c => { - c.apiKey = "${apiKey}"; + c.apiKey = "${apiKey}";${shouldShowServerUrl ? `\n c.serverUrl = "${serverUrl}";` : ''} });`, - exceptionless: `using Exceptionless; + exceptionless: shouldShowServerUrl + ? `using Exceptionless; + +ExceptionlessClient.Default.Configuration.ApiKey = "${apiKey}"; +ExceptionlessClient.Default.Configuration.ServerUrl = "${serverUrl}"; +ExceptionlessClient.Default.Startup();` + : `using Exceptionless; ExceptionlessClient.Default.Startup("${apiKey}");`, - legacyAppConfigSectionXml: ``, + legacyAppConfigSectionXml: shouldShowServerUrl + ? `` + : ``, nodeJs: `import { Exceptionless } from "@exceptionless/node"; await Exceptionless.startup(c => { - c.apiKey = "${apiKey}"; + c.apiKey = "${apiKey}";${shouldShowServerUrl ? `\n c.serverUrl = "${serverUrl}";` : ''} });`, powerShell: `$body = @{ @@ -227,7 +228,7 @@ $header = @{ "Content-Type"="application/json" } -Invoke-RestMethod -Uri "${serverUrl}/api/v2/events" -Method "Post" -Body $body -Headers $header`, +Invoke-RestMethod -Uri "${eventSubmissionUrl}" -Method "Post" -Body $body -Headers $header`, reactNativeExpoPlugin: `{ "expo": { @@ -238,7 +239,7 @@ Invoke-RestMethod -Uri "${serverUrl}/api/v2/events" -Method "Post" -Body $body - reactNativeJs: `import { Exceptionless } from "@exceptionless/react-native"; await Exceptionless.startup(c => { - c.apiKey = "${apiKey}"; + c.apiKey = "${apiKey}";${shouldShowServerUrl ? `\n c.serverUrl = "${serverUrl}";` : ''} });`, webApi: `public static void Register(HttpConfiguration config) { @@ -246,7 +247,7 @@ await Exceptionless.startup(c => { }`, webApiInAspNet: `protected void Application_Start() { - ExceptionlessClient.Default.Configuration.ApiKey = "${apiKey}"; + ExceptionlessClient.Default.Configuration.ApiKey = "${apiKey}";${shouldShowServerUrl ? `\n ExceptionlessClient.Default.Configuration.ServerUrl = "${serverUrl}";` : ''} ExceptionlessClient.Default.Startup(); }`, webApiRegister: `using Exceptionless; @@ -264,7 +265,9 @@ public class Startup { }`, webApiRegisterAspNet: `Exceptionless.ExceptionlessClient.Default.RegisterWebApi(GlobalConfiguration.Configuration)`, - windowsAttributeConfiguration: `[assembly: Exceptionless.Configuration.Exceptionless("${apiKey}")]`, + windowsAttributeConfiguration: shouldShowServerUrl + ? `[assembly: Exceptionless.Configuration.Exceptionless("${apiKey}", ServerUrl = "${serverUrl}")]` + : `[assembly: Exceptionless.Configuration.Exceptionless("${apiKey}")]`, windowsRegister: `using Exceptionless; internal static class Program { @@ -347,7 +350,7 @@ public partial class App : Application {
- The Exceptionless client can be integrated into your project in just a few easy steps + Choose your project type and follow the steps below to connect your application to Exceptionless. {#if isTokenDisabled} @@ -382,7 +385,7 @@ public partial class App : Application {
  1. -

    Select your project type.

    +

    Choose your project type.

    + - +
{#if isTokenDisabled} diff --git a/src/Exceptionless.Web/ClientApp/src/routes/(app)/project/[projectId]/manage/+page.svelte b/src/Exceptionless.Web/ClientApp/src/routes/(app)/project/[projectId]/manage/+page.svelte index fc83f02850..031d35550d 100644 --- a/src/Exceptionless.Web/ClientApp/src/routes/(app)/project/[projectId]/manage/+page.svelte +++ b/src/Exceptionless.Web/ClientApp/src/routes/(app)/project/[projectId]/manage/+page.svelte @@ -18,11 +18,10 @@ import { ariaInvalid, getFormErrorMessages, mapFieldErrors, problemDetailsToFormErrors } from '$features/shared/validation'; import { ProblemDetails } from '@exceptionless/fetchclient'; import AlertTriangle from '@lucide/svelte/icons/alert-triangle'; - import Bot from '@lucide/svelte/icons/bot'; + import CloudDownload from '@lucide/svelte/icons/cloud-download'; import Database from '@lucide/svelte/icons/database'; import Stacks from '@lucide/svelte/icons/layers'; import NotificationSettings from '@lucide/svelte/icons/mail'; - import Send from '@lucide/svelte/icons/send'; import X from '@lucide/svelte/icons/x'; import { createForm } from '@tanstack/svelte-form'; import { toast } from 'svelte-sonner'; @@ -174,14 +173,11 @@ Go To Stacks -