├── global.json ├── src ├── Ui │ ├── Website │ │ ├── Pages │ │ │ ├── Monitoring.razor │ │ │ ├── ServiceBus.razor │ │ │ ├── ServiceBus.razor.cs │ │ │ ├── Queues.razor │ │ │ ├── Topics.razor │ │ │ └── Rules.razor │ │ ├── Models │ │ │ ├── DirectionChange.cs │ │ │ ├── OperationType.cs │ │ │ ├── RequeueMessage.cs │ │ │ ├── MessagesUploadDialogResult.cs │ │ │ ├── UserSettings.cs │ │ │ ├── ServiceBusConnectionSettings.cs │ │ │ ├── ServiceBusConnectionFolder.cs │ │ │ ├── KeyValueTypePair.cs │ │ │ ├── TopicSubscriptionsModel.cs │ │ │ ├── CurrentMessagesEntity.cs │ │ │ ├── ConnectionFolder.cs │ │ │ ├── RuleFormModel.cs │ │ │ ├── Validators │ │ │ │ ├── ReceiveMessageFormValidator.cs │ │ │ │ └── RuleFormValidator.cs │ │ │ ├── ConnectionMenuItem.cs │ │ │ ├── SaveConnectionForm.cs │ │ │ ├── FolderSettings.cs │ │ │ ├── ReceiveMessagesForm.cs │ │ │ ├── MessageDetailsModel.cs │ │ │ ├── TopicFormModel.cs │ │ │ └── SubscriptionFormModel.cs │ │ ├── Jobs │ │ │ ├── JobStatus.cs │ │ │ ├── JobsHelper.cs │ │ │ ├── JobCompletedEventHandler.cs │ │ │ ├── IJob.cs │ │ │ ├── PurgeMessagesJob.cs │ │ │ ├── DeleteMessageJob.cs │ │ │ └── ResendMessagesJob.cs │ │ ├── ViewModels │ │ │ ├── TopicRemovedEventHandler.cs │ │ │ ├── TopicAddedEventHandler.cs │ │ │ ├── ConnectionOperationEventHandler.cs │ │ │ ├── QueueOperationEventHandler.cs │ │ │ ├── SubscriptionOperationEventHandler.cs │ │ │ ├── IJobsViewModel.cs │ │ │ ├── INavigationViewModel.cs │ │ │ ├── IMessagesViewModel.cs │ │ │ ├── IRulesViewModel.cs │ │ │ ├── ITopicViewModel.cs │ │ │ ├── IQueueViewModel.cs │ │ │ ├── ISubscriptionViewModel.cs │ │ │ ├── IConnectionsViewModel.cs │ │ │ └── JobsViewModel.cs │ │ ├── Exceptions │ │ │ ├── ConverterException.cs │ │ │ └── FolderSettingsForConnectionNotFoundException.cs │ │ ├── Extensions │ │ │ ├── TimeSpanExtensions.cs │ │ │ ├── INotifyPropertyChangedExtensions.cs │ │ │ ├── DateTimeExtensions.cs │ │ │ ├── IListExtensions.cs │ │ │ └── StringExtensions.cs │ │ ├── _Imports.razor │ │ ├── ISettingsService.cs │ │ ├── App.razor │ │ ├── Validation │ │ │ └── ConnectionStringValidationAttribute.cs │ │ ├── Shared │ │ │ ├── ViewDialog.razor │ │ │ ├── ConfirmDialog.razor │ │ │ ├── Queue │ │ │ │ └── CloneDialog.razor │ │ │ ├── Connections │ │ │ │ └── FolderDialog.razor │ │ │ ├── ServiceBusNavMenu.razor │ │ │ ├── Jobs.razor │ │ │ ├── Messages │ │ │ │ ├── PurgeMessagesDialog.razor │ │ │ │ └── MessagesUploadDialog.razor │ │ │ ├── MenuItem.razor │ │ │ ├── TopicNestedMenuItem.razor │ │ │ ├── TopicSubNestedMenuItem.razor │ │ │ └── MainLayout.razor │ │ ├── FluentValidator.cs │ │ ├── WellKnown.cs │ │ ├── ServiceCollectionExtensions.cs │ │ ├── Mappings │ │ │ ├── MessageMappings.cs │ │ │ ├── TopicMappings.cs │ │ │ ├── SubscriptionMappings.cs │ │ │ └── QueueMappings.cs │ │ └── Website.csproj │ └── Website.Host │ │ ├── build │ │ ├── icon.png │ │ ├── icon512x512.png │ │ └── entitlements.mac.inherit.plist │ │ ├── wwwroot │ │ └── favicon.ico │ │ ├── Pages │ │ ├── _Host.cshtml │ │ ├── Error.cshtml.cs │ │ ├── _Layout.cshtml │ │ └── Error.cshtml │ │ ├── appsettings.json │ │ ├── appsettings.Development.json │ │ ├── _Imports.razor │ │ ├── App.razor │ │ ├── Website.Host.csproj │ │ ├── electron.manifest.json │ │ ├── DefaultSettingsService.cs │ │ ├── Program.cs │ │ └── ManagementStorage.cs ├── Core │ ├── ServiceBus.Contracts │ │ ├── Types │ │ │ ├── Result.cs │ │ │ ├── LockDuration.cs │ │ │ ├── PurgeResult.cs │ │ │ ├── ResendResult.cs │ │ │ ├── Rule.cs │ │ │ ├── ReceiveType.cs │ │ │ ├── ReceiveMode.cs │ │ │ ├── AccessRights.cs │ │ │ ├── MessageState.cs │ │ │ ├── SubQueue.cs │ │ │ ├── RuleType.cs │ │ │ ├── UploadFileType.cs │ │ │ ├── OperationResult.cs │ │ │ ├── TopicProperties.cs │ │ │ ├── TopicStructure.cs │ │ │ ├── QueueDetails.cs │ │ │ ├── SubscriptionTimeSettings.cs │ │ │ ├── TopicDetails.cs │ │ │ ├── TopicSettings.cs │ │ │ ├── TopicTimeSettings.cs │ │ │ ├── SubscriptionProperties.cs │ │ │ ├── SharedAccessAuthorizationRule.cs │ │ │ ├── ServiceBusEntityStatus.cs │ │ │ ├── QueueTimeSettings.cs │ │ │ ├── SubscriptionDetails.cs │ │ │ ├── QueueSettings.cs │ │ │ ├── SubscriptionSettings.cs │ │ │ ├── Message.cs │ │ │ ├── QueueProperties.cs │ │ │ ├── TopicInfo.cs │ │ │ ├── SubscriptionInfo.cs │ │ │ ├── ApplicationProperty.cs │ │ │ ├── QueueInfo.cs │ │ │ ├── UpdateTopicOptions.cs │ │ │ ├── CreateTopicOptions.cs │ │ │ ├── SendMessage.cs │ │ │ ├── UpdateSubscriptionOptions.cs │ │ │ ├── CreateSubscriptionOptions.cs │ │ │ ├── MessageSystemProperties.cs │ │ │ ├── CreateQueueOptions.cs │ │ │ └── UpdateQueueOptions.cs │ │ ├── Excepions │ │ │ ├── MessageTooBigException.cs │ │ │ ├── ValidationException.cs │ │ │ └── ServiceBusOperationException.cs │ │ ├── ServiceBus.Contracts.csproj │ │ ├── ITopicService.cs │ │ ├── IRuleService.cs │ │ ├── IQueueService.cs │ │ ├── ISubscriptionService.cs │ │ └── IMessageService.cs │ ├── Common │ │ ├── ErrorCodes.cs │ │ ├── StringExtensions.cs │ │ └── Common.csproj │ ├── Relay │ │ └── Relay.csproj │ ├── NotificationHub │ │ └── NotificationHub.csproj │ ├── Management │ │ ├── ServiceBusConnectionDoesntExist.cs │ │ ├── IManagementStorage.cs │ │ ├── ServiceCollectionExtensions.cs │ │ ├── Management.csproj │ │ ├── ServiceBusConnectionStringHelper.cs │ │ └── ConnectionManagement.cs │ ├── Management.Contracts │ │ ├── ServiceBusTransportType.cs │ │ ├── Management.Contracts.csproj │ │ ├── IConnectionManagement.cs │ │ └── ServiceBusConnection.cs │ └── ServiceBus │ │ ├── ServiceCollectionExtensions.cs │ │ ├── ServiceBus.csproj │ │ ├── Extensions │ │ └── ApplicationPropertyTypeExtensions.cs │ │ └── Mappings │ │ └── MessageMappings.cs └── Api │ └── Host │ ├── appsettings.json │ ├── Queries │ ├── TopicQueryExtensions.cs │ ├── SubscriptionQueryExtensions.cs │ ├── QueueQueryExtensions.cs │ └── MessageQueryExtensions.cs │ ├── Host.csproj │ ├── ConnectionManagement.cs │ ├── Program.cs │ └── Mutations │ ├── MessagingMutationExtensions.cs │ └── QueueMutationExtensions.cs ├── .config └── dotnet-tools.json ├── nuget.config ├── LICENSE.md ├── README.md └── .gitignore /global.json: -------------------------------------------------------------------------------- 1 | { 2 | "sdk": { 3 | "version": "8.0.203" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /src/Ui/Website/Pages/Monitoring.razor: -------------------------------------------------------------------------------- 1 |

Monitoring

2 | 3 | @code { 4 | 5 | } -------------------------------------------------------------------------------- /src/Ui/Website.Host/build/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Carael/CrossBusExplorer/HEAD/src/Ui/Website.Host/build/icon.png -------------------------------------------------------------------------------- /src/Ui/Website.Host/wwwroot/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Carael/CrossBusExplorer/HEAD/src/Ui/Website.Host/wwwroot/favicon.ico -------------------------------------------------------------------------------- /src/Ui/Website/Pages/ServiceBus.razor: -------------------------------------------------------------------------------- 1 | @page "/servicebus/{ServiceBusName}" 2 | 3 | Service bus @ServiceBusName 4 | -------------------------------------------------------------------------------- /src/Core/ServiceBus.Contracts/Types/Result.cs: -------------------------------------------------------------------------------- 1 | namespace CrossBusExplorer.ServiceBus.Contracts.Types; 2 | 3 | public record Result(long Count); -------------------------------------------------------------------------------- /src/Ui/Website.Host/build/icon512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Carael/CrossBusExplorer/HEAD/src/Ui/Website.Host/build/icon512x512.png -------------------------------------------------------------------------------- /src/Core/ServiceBus.Contracts/Types/LockDuration.cs: -------------------------------------------------------------------------------- 1 | namespace CrossBusExplorer.ServiceBus.Contracts.Types; 2 | 3 | public record LockDuration(); -------------------------------------------------------------------------------- /src/Core/ServiceBus.Contracts/Types/PurgeResult.cs: -------------------------------------------------------------------------------- 1 | namespace CrossBusExplorer.ServiceBus.Contracts.Types; 2 | 3 | public record PurgeResult(long PurgedCount); -------------------------------------------------------------------------------- /src/Ui/Website/Models/DirectionChange.cs: -------------------------------------------------------------------------------- 1 | namespace CrossBusExplorer.Website.Models; 2 | 3 | public enum DirectionChange 4 | { 5 | Up, 6 | Down 7 | } -------------------------------------------------------------------------------- /src/Core/ServiceBus.Contracts/Types/ResendResult.cs: -------------------------------------------------------------------------------- 1 | namespace CrossBusExplorer.ServiceBus.Contracts.Types; 2 | 3 | public record ResendResult(long ResendCount); -------------------------------------------------------------------------------- /src/Core/ServiceBus.Contracts/Types/Rule.cs: -------------------------------------------------------------------------------- 1 | namespace CrossBusExplorer.ServiceBus.Contracts.Types; 2 | 3 | public record Rule(string Name, RuleType Type, string? Value); -------------------------------------------------------------------------------- /src/Ui/Website/Models/OperationType.cs: -------------------------------------------------------------------------------- 1 | namespace CrossBusExplorer.Website.Pages; 2 | 3 | public enum OperationType 4 | { 5 | Create, 6 | Update, 7 | Delete 8 | } -------------------------------------------------------------------------------- /src/Core/ServiceBus.Contracts/Types/ReceiveType.cs: -------------------------------------------------------------------------------- 1 | namespace CrossBusExplorer.ServiceBus.Contracts.Types; 2 | 3 | public enum ReceiveType 4 | { 5 | All, 6 | ByCount 7 | } -------------------------------------------------------------------------------- /src/Ui/Website/Jobs/JobStatus.cs: -------------------------------------------------------------------------------- 1 | namespace CrossBusExplorer.Website.Jobs; 2 | 3 | public enum JobStatus 4 | { 5 | Running, 6 | Succeeded, 7 | Failed, 8 | Cancelled 9 | } -------------------------------------------------------------------------------- /src/Core/ServiceBus.Contracts/Excepions/MessageTooBigException.cs: -------------------------------------------------------------------------------- 1 | namespace CrossBusExplorer.ServiceBus.Contracts; 2 | 3 | public class MessageTooBigException:Exception 4 | { 5 | 6 | } -------------------------------------------------------------------------------- /src/Core/ServiceBus.Contracts/Types/ReceiveMode.cs: -------------------------------------------------------------------------------- 1 | namespace CrossBusExplorer.ServiceBus.Contracts.Types; 2 | 3 | public enum ReceiveMode 4 | { 5 | PeekLock, 6 | ReceiveAndDelete 7 | } -------------------------------------------------------------------------------- /src/Ui/Website/ViewModels/TopicRemovedEventHandler.cs: -------------------------------------------------------------------------------- 1 | namespace CrossBusExplorer.Website.ViewModels; 2 | 3 | public delegate void TopicRemovedEventHandler(string connectionName, string topicName); -------------------------------------------------------------------------------- /src/Core/ServiceBus.Contracts/Types/AccessRights.cs: -------------------------------------------------------------------------------- 1 | namespace CrossBusExplorer.ServiceBus.Contracts.Types; 2 | 3 | public enum AccessRights 4 | { 5 | Manage = 0, 6 | Send = 1, 7 | Listen = 2 8 | } -------------------------------------------------------------------------------- /src/Api/Host/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft.AspNetCore": "Warning" 6 | } 7 | }, 8 | "AllowedHosts": "*" 9 | } 10 | -------------------------------------------------------------------------------- /src/Core/ServiceBus.Contracts/Types/MessageState.cs: -------------------------------------------------------------------------------- 1 | namespace CrossBusExplorer.ServiceBus.Contracts.Types; 2 | 3 | public enum MessageState 4 | { 5 | Active = 0, 6 | Deferred = 1, 7 | Scheduled = 2 8 | } -------------------------------------------------------------------------------- /src/Core/ServiceBus.Contracts/Types/SubQueue.cs: -------------------------------------------------------------------------------- 1 | namespace CrossBusExplorer.ServiceBus.Contracts.Types; 2 | 3 | public enum SubQueue 4 | { 5 | None = 0, 6 | DeadLetter = 1, 7 | TransferDeadLetter = 2 8 | } -------------------------------------------------------------------------------- /src/Core/ServiceBus.Contracts/Types/RuleType.cs: -------------------------------------------------------------------------------- 1 | namespace CrossBusExplorer.ServiceBus.Contracts.Types; 2 | 3 | public enum RuleType 4 | { 5 | Sql, 6 | CorrelationId, 7 | TrueFilter, 8 | FalseFilter 9 | } -------------------------------------------------------------------------------- /src/Core/ServiceBus.Contracts/Types/UploadFileType.cs: -------------------------------------------------------------------------------- 1 | namespace CrossBusExplorer.ServiceBus.Contracts.Types; 2 | 3 | public enum UploadFileType 4 | { 5 | Body = 0, 6 | BodyWithApplicationProperties = 1 7 | } 8 | -------------------------------------------------------------------------------- /src/Core/ServiceBus.Contracts/Types/OperationResult.cs: -------------------------------------------------------------------------------- 1 | namespace CrossBusExplorer.ServiceBus.Contracts.Types; 2 | 3 | public record OperationResult(bool Success); 4 | 5 | public record OperationResult(bool Success, TModel? Data); -------------------------------------------------------------------------------- /src/Ui/Website/Exceptions/ConverterException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | namespace CrossBusExplorer.Website.Exceptions; 3 | 4 | public class ConverterException : Exception 5 | { 6 | public ConverterException(string message):base(message){} 7 | } -------------------------------------------------------------------------------- /src/Ui/Website/Models/RequeueMessage.cs: -------------------------------------------------------------------------------- 1 | using CrossBusExplorer.ServiceBus.Contracts.Types; 2 | namespace CrossBusExplorer.Website.Models; 3 | 4 | public record RequeueMessage( 5 | string QueueOrTopicName, 6 | MessageDetailsModel Message); -------------------------------------------------------------------------------- /src/Ui/Website/Pages/ServiceBus.razor.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Components; 2 | namespace CrossBusExplorer.Website.Pages; 3 | 4 | public partial class ServiceBus 5 | { 6 | [Parameter] 7 | public string ServiceBusName { get; set; } 8 | } -------------------------------------------------------------------------------- /src/Ui/Website.Host/Pages/_Host.cshtml: -------------------------------------------------------------------------------- 1 | @page "/" 2 | @namespace Website.Host.Pages 3 | @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers 4 | @{ 5 | Layout = "_Layout"; 6 | } 7 | 8 | -------------------------------------------------------------------------------- /src/Ui/Website/ViewModels/TopicAddedEventHandler.cs: -------------------------------------------------------------------------------- 1 | using CrossBusExplorer.ServiceBus.Contracts.Types; 2 | namespace CrossBusExplorer.Website.ViewModels; 3 | 4 | public delegate void TopicAddedEventHandler(string connectionName, TopicInfo topicInfo); -------------------------------------------------------------------------------- /.config/dotnet-tools.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 1, 3 | "isRoot": true, 4 | "tools": { 5 | "electronsharp.cli": { 6 | "version": "24.0.0.37423", 7 | "commands": [ 8 | "electron-sharp" 9 | ] 10 | } 11 | } 12 | } -------------------------------------------------------------------------------- /src/Core/Common/ErrorCodes.cs: -------------------------------------------------------------------------------- 1 | namespace CrossBusExplorer.ServiceBus; 2 | 3 | public static class ErrorCodes 4 | { 5 | public static string InvalidArgument => "InvalidArgument"; 6 | public static string InvalidOperation => "InvalidOperation"; 7 | } -------------------------------------------------------------------------------- /src/Core/ServiceBus.Contracts/Types/TopicProperties.cs: -------------------------------------------------------------------------------- 1 | namespace CrossBusExplorer.ServiceBus.Contracts.Types; 2 | 3 | public record TopicProperties( 4 | long MaxQueueSizeInMegabytes, 5 | long? MaxMessageSizeInKilobytes, 6 | string? UserMetadata); -------------------------------------------------------------------------------- /src/Core/ServiceBus.Contracts/Types/TopicStructure.cs: -------------------------------------------------------------------------------- 1 | namespace CrossBusExplorer.ServiceBus.Contracts.Types; 2 | 3 | public record TopicStructure( 4 | string Name, 5 | bool IsFolder, 6 | string? FullName, 7 | IList ChildTopics); -------------------------------------------------------------------------------- /src/Ui/Website/Jobs/JobsHelper.cs: -------------------------------------------------------------------------------- 1 | namespace CrossBusExplorer.Website.Jobs; 2 | 3 | public static class JobsHelper 4 | { 5 | public static int GetProgress(long totalCount, long purgedCount) => 6 | (int)((float)purgedCount / totalCount * 100); 7 | } -------------------------------------------------------------------------------- /src/Ui/Website.Host/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft": "Warning", 6 | "Microsoft.Hosting.Lifetime": "Information" 7 | } 8 | }, 9 | "AllowedHosts": "*" 10 | } 11 | -------------------------------------------------------------------------------- /src/Core/ServiceBus.Contracts/Types/QueueDetails.cs: -------------------------------------------------------------------------------- 1 | namespace CrossBusExplorer.ServiceBus.Contracts.Types; 2 | 3 | public record QueueDetails( 4 | QueueInfo Info, 5 | QueueSettings Settings, 6 | QueueTimeSettings TimeSettings, 7 | QueueProperties Properties); -------------------------------------------------------------------------------- /src/Core/ServiceBus.Contracts/Types/SubscriptionTimeSettings.cs: -------------------------------------------------------------------------------- 1 | namespace CrossBusExplorer.ServiceBus.Contracts.Types; 2 | 3 | public record SubscriptionTimeSettings( 4 | TimeSpan AutoDeleteOnIdle, 5 | TimeSpan DefaultMessageTimeToLive, 6 | TimeSpan LockDuration); -------------------------------------------------------------------------------- /src/Core/ServiceBus.Contracts/Types/TopicDetails.cs: -------------------------------------------------------------------------------- 1 | namespace CrossBusExplorer.ServiceBus.Contracts.Types; 2 | 3 | public record TopicDetails( 4 | TopicInfo Info, 5 | TopicSettings Settings, 6 | TopicTimeSettings TimeSettings, 7 | TopicProperties Properties); -------------------------------------------------------------------------------- /src/Ui/Website/Jobs/JobCompletedEventHandler.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | namespace CrossBusExplorer.Website.Jobs; 3 | 4 | public delegate Task JobCompletedEventHandler( 5 | string connectionName, 6 | string queueOrTopicName, 7 | string? subscriptionName); -------------------------------------------------------------------------------- /src/Core/ServiceBus.Contracts/Types/TopicSettings.cs: -------------------------------------------------------------------------------- 1 | namespace CrossBusExplorer.ServiceBus.Contracts.Types; 2 | 3 | public record TopicSettings( 4 | bool EnableBatchedOperations, 5 | bool EnablePartitioning, 6 | bool RequiresDuplicateDetection, 7 | bool SupportOrdering); -------------------------------------------------------------------------------- /src/Core/ServiceBus.Contracts/Types/TopicTimeSettings.cs: -------------------------------------------------------------------------------- 1 | namespace CrossBusExplorer.ServiceBus.Contracts.Types; 2 | 3 | public record TopicTimeSettings( 4 | TimeSpan AutoDeleteOnIdle, 5 | TimeSpan DefaultMessageTimeToLive, 6 | TimeSpan DuplicateDetectionHistoryTimeWindow); -------------------------------------------------------------------------------- /src/Ui/Website.Host/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "DetailedErrors": true, 3 | "Logging": { 4 | "LogLevel": { 5 | "Default": "Information", 6 | "Microsoft": "Warning", 7 | "Microsoft.Hosting.Lifetime": "Information" 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/Ui/Website/Models/MessagesUploadDialogResult.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using CrossBusExplorer.ServiceBus.Contracts.Types; 3 | namespace CrossBusExplorer.Website.Models; 4 | 5 | public record MessagesUploadDialogResult(UploadFileType Type, IList FilesContent); 6 | -------------------------------------------------------------------------------- /src/Ui/Website/ViewModels/ConnectionOperationEventHandler.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using CrossBusExplorer.Website.Models; 3 | namespace CrossBusExplorer.Website.ViewModels; 4 | 5 | public delegate void SettingsChangedEventHandler(IEnumerable folderSettings); -------------------------------------------------------------------------------- /src/Core/Relay/Relay.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net8.0 5 | enable 6 | enable 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /src/Core/ServiceBus.Contracts/Types/SubscriptionProperties.cs: -------------------------------------------------------------------------------- 1 | namespace CrossBusExplorer.ServiceBus.Contracts.Types; 2 | 3 | public record SubscriptionProperties( 4 | int MaxDeliveryCount, 5 | string? UserMetadata, 6 | string? ForwardTo, 7 | string? ForwardDeadLetteredMessagesTo); -------------------------------------------------------------------------------- /src/Ui/Website/Extensions/TimeSpanExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | namespace CrossBusExplorer.Website.Extensions; 3 | 4 | public static class TimeSpanExtensions 5 | { 6 | public static string ToTimeSpanString(this TimeSpan timeSpan) 7 | => timeSpan.ToString(@"ddd\.hh\:mm\:ss"); 8 | } -------------------------------------------------------------------------------- /src/Core/ServiceBus.Contracts/Types/SharedAccessAuthorizationRule.cs: -------------------------------------------------------------------------------- 1 | namespace CrossBusExplorer.ServiceBus.Contracts.Types; 2 | 3 | public record SharedAccessAuthorizationRule( 4 | string KeyName, 5 | string PrimaryKey, 6 | string SecondaryKey, 7 | IEnumerable Rights); -------------------------------------------------------------------------------- /src/Ui/Website/Pages/Queues.razor: -------------------------------------------------------------------------------- 1 | @page "/queues/{ConnectionName}" 2 | Topics from @ConnectionName 3 | Topics from @ConnectionName 4 | 5 | @code { 6 | [Parameter] 7 | public string? ConnectionName { get; set; } 8 | } -------------------------------------------------------------------------------- /src/Ui/Website/Pages/Topics.razor: -------------------------------------------------------------------------------- 1 | @page "/topics/{ConnectionName}" 2 | Topics from @ConnectionName 3 | Topics from @ConnectionName 4 | 5 | @code { 6 | [Parameter] 7 | public string? ConnectionName { get; set; } 8 | } -------------------------------------------------------------------------------- /src/Core/ServiceBus.Contracts/Types/ServiceBusEntityStatus.cs: -------------------------------------------------------------------------------- 1 | namespace CrossBusExplorer.ServiceBus.Contracts.Types; 2 | 3 | public enum ServiceBusEntityStatus 4 | { 5 | Active, 6 | Disabled, 7 | SendDisabled, //TODO: support in ui 8 | ReceiveDisabled //TODO: support in ui 9 | } -------------------------------------------------------------------------------- /src/Ui/Website/Models/UserSettings.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | namespace CrossBusExplorer.Website.Models; 3 | 4 | public class UserSettings 5 | { 6 | public bool IsDarkMode { get; set; } 7 | public List FolderSettings { get; set; } = new List(); 8 | } -------------------------------------------------------------------------------- /src/Core/NotificationHub/NotificationHub.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net8.0 5 | enable 6 | enable 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /src/Core/ServiceBus.Contracts/Types/QueueTimeSettings.cs: -------------------------------------------------------------------------------- 1 | namespace CrossBusExplorer.ServiceBus.Contracts.Types; 2 | 3 | public record QueueTimeSettings( 4 | TimeSpan AutoDeleteOnIdle, 5 | TimeSpan DefaultMessageTimeToLive, 6 | TimeSpan DuplicateDetectionHistoryTimeWindow, 7 | TimeSpan LockDuration); -------------------------------------------------------------------------------- /src/Core/ServiceBus.Contracts/Types/SubscriptionDetails.cs: -------------------------------------------------------------------------------- 1 | namespace CrossBusExplorer.ServiceBus.Contracts.Types; 2 | 3 | public record SubscriptionDetails( 4 | SubscriptionInfo Info, 5 | SubscriptionSettings Settings, 6 | SubscriptionTimeSettings TimeSettings, 7 | SubscriptionProperties Properties); -------------------------------------------------------------------------------- /src/Core/Management/ServiceBusConnectionDoesntExist.cs: -------------------------------------------------------------------------------- 1 | namespace CrossBusExplorer.Management; 2 | 3 | public class ServiceBusConnectionDoesntExist : Exception 4 | { 5 | public ServiceBusConnectionDoesntExist(string name) : base( 6 | $"Service Bus connection with name {name} doesn't exist.") 7 | { 8 | } 9 | } -------------------------------------------------------------------------------- /src/Core/ServiceBus.Contracts/Types/QueueSettings.cs: -------------------------------------------------------------------------------- 1 | namespace CrossBusExplorer.ServiceBus.Contracts.Types; 2 | 3 | public record QueueSettings( 4 | bool EnableBatchedOperations, 5 | bool EnableDeadLetteringOnMessageExpiration, 6 | bool EnablePartitioning, 7 | bool RequiresDuplicateDetection, 8 | bool RequiresSession); -------------------------------------------------------------------------------- /src/Core/ServiceBus.Contracts/Types/SubscriptionSettings.cs: -------------------------------------------------------------------------------- 1 | namespace CrossBusExplorer.ServiceBus.Contracts.Types; 2 | 3 | public record SubscriptionSettings( 4 | bool EnableBatchedOperations, 5 | bool EnableDeadLetteringOnMessageExpiration, 6 | bool RequiresSession, 7 | bool EnableDeadLetteringOnFilterEvaluationExceptions); -------------------------------------------------------------------------------- /src/Core/ServiceBus.Contracts/Excepions/ValidationException.cs: -------------------------------------------------------------------------------- 1 | namespace CrossBusExplorer.ServiceBus.Contracts; 2 | 3 | public class ValidationException: Exception 4 | { 5 | public ValidationException(string code, string message) : base(message) 6 | { 7 | Code = code; 8 | } 9 | 10 | public string Code { get; } 11 | } -------------------------------------------------------------------------------- /src/Ui/Website/ViewModels/QueueOperationEventHandler.cs: -------------------------------------------------------------------------------- 1 | using CrossBusExplorer.ServiceBus.Contracts.Types; 2 | using CrossBusExplorer.Website.Pages; 3 | namespace CrossBusExplorer.Website.ViewModels; 4 | 5 | public delegate void QueueOperationEventHandler( 6 | string connectionName, 7 | OperationType operationType, 8 | QueueInfo queueInfo); -------------------------------------------------------------------------------- /src/Core/ServiceBus.Contracts/Types/Message.cs: -------------------------------------------------------------------------------- 1 | using System.Collections; 2 | namespace CrossBusExplorer.ServiceBus.Contracts.Types; 3 | 4 | public record Message( 5 | string Id, 6 | string? Subject, 7 | string Body, 8 | MessageSystemProperties SystemProperties, 9 | IReadOnlyDictionary? ApplicationProperties); 10 | -------------------------------------------------------------------------------- /src/Core/ServiceBus.Contracts/Types/QueueProperties.cs: -------------------------------------------------------------------------------- 1 | namespace CrossBusExplorer.ServiceBus.Contracts.Types; 2 | 3 | public record QueueProperties( 4 | long MaxQueueSizeInMegabytes, 5 | long? MaxMessageSizeInKilobytes, 6 | int MaxDeliveryCount, 7 | string? UserMetadata, 8 | string? ForwardTo, 9 | string? ForwardDeadLetteredMessagesTo); -------------------------------------------------------------------------------- /src/Ui/Website/_Imports.razor: -------------------------------------------------------------------------------- 1 | @using System.Net.Http 2 | @using System.Net.Http.Json 3 | @using Microsoft.AspNetCore.Components.Forms 4 | @using Microsoft.AspNetCore.Components.Routing 5 | @using Microsoft.AspNetCore.Components.Web 6 | @using Microsoft.JSInterop 7 | @using CrossBusExplorer.Website 8 | @using MudBlazor 9 | @using CrossBusExplorer.Website.Extensions -------------------------------------------------------------------------------- /src/Ui/Website/ViewModels/SubscriptionOperationEventHandler.cs: -------------------------------------------------------------------------------- 1 | using CrossBusExplorer.ServiceBus.Contracts.Types; 2 | using CrossBusExplorer.Website.Pages; 3 | namespace CrossBusExplorer.Website.ViewModels; 4 | 5 | public delegate void SubscriptionOperationEventHandler( 6 | string connectionName, 7 | OperationType operationType, 8 | SubscriptionInfo subscription); -------------------------------------------------------------------------------- /src/Core/ServiceBus.Contracts/Excepions/ServiceBusOperationException.cs: -------------------------------------------------------------------------------- 1 | namespace CrossBusExplorer.ServiceBus.Contracts; 2 | 3 | public class ServiceBusOperationException : Exception 4 | { 5 | public ServiceBusOperationException(string code, string message) : base(message) 6 | { 7 | Code = code; 8 | } 9 | 10 | public string Code { get; } 11 | } -------------------------------------------------------------------------------- /src/Ui/Website/ISettingsService.cs: -------------------------------------------------------------------------------- 1 | using System.Threading; 2 | using System.Threading.Tasks; 3 | using CrossBusExplorer.Website.Models; 4 | namespace CrossBusExplorer.Website; 5 | 6 | public interface ISettingsService 7 | { 8 | Task GetAsync(CancellationToken cancellationToken); 9 | Task SaveAsync(UserSettings userSettings, CancellationToken cancellationToken); 10 | } -------------------------------------------------------------------------------- /nuget.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/Core/Common/StringExtensions.cs: -------------------------------------------------------------------------------- 1 | namespace CrossBusExplorer.ServiceBus; 2 | 3 | public static class StringExtensions 4 | { 5 | public static string? RemoveUrl(this string? value) 6 | { 7 | if (string.IsNullOrEmpty(value)) 8 | { 9 | return null; 10 | } 11 | 12 | var uri = new Uri(value); 13 | 14 | return uri.LocalPath.Remove(0,1); 15 | } 16 | } -------------------------------------------------------------------------------- /src/Core/Common/Common.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net8.0 5 | enable 6 | enable 7 | CrossBusExplorer.Common 8 | CrossBusExplorer.Common 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/Ui/Website/Exceptions/FolderSettingsForConnectionNotFoundException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | namespace CrossBusExplorer.Website.Exceptions; 3 | 4 | public class FolderSettingsForConnectionNotFoundException : Exception 5 | { 6 | public FolderSettingsForConnectionNotFoundException(string connectionName) 7 | : base($"Folder settings for {connectionName} was not found.") 8 | { 9 | 10 | } 11 | } -------------------------------------------------------------------------------- /src/Core/Management/IManagementStorage.cs: -------------------------------------------------------------------------------- 1 | using CrossBusExplorer.Management.Contracts; 2 | namespace CrossBusExplorer.Management; 3 | 4 | public interface IManagementStorage 5 | { 6 | Task StoreAsync( 7 | IDictionary connections, 8 | CancellationToken cancellationToken); 9 | Task> ReadAsync(CancellationToken cancellationToken); 10 | } -------------------------------------------------------------------------------- /src/Core/ServiceBus.Contracts/Types/TopicInfo.cs: -------------------------------------------------------------------------------- 1 | namespace CrossBusExplorer.ServiceBus.Contracts.Types; 2 | 3 | public record TopicInfo( 4 | string Name, 5 | ServiceBusEntityStatus Status, 6 | long SizeInBytes, 7 | DateTimeOffset CreatedAt, 8 | DateTimeOffset AccessedAt, 9 | DateTimeOffset UpdatedAt, 10 | long ScheduledMessagesCount) 11 | { 12 | public long SizeInMB => SizeInBytes / 1024 / 1024; 13 | } -------------------------------------------------------------------------------- /src/Ui/Website/ViewModels/IJobsViewModel.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.ObjectModel; 2 | using System.ComponentModel; 3 | using System.Threading.Tasks; 4 | using CrossBusExplorer.Website.Jobs; 5 | namespace CrossBusExplorer.Website.ViewModels; 6 | 7 | public interface IJobsViewModel : INotifyPropertyChanged 8 | { 9 | ObservableCollection Jobs { get; } 10 | Task ScheduleJob(IJob job); 11 | void CancelJob(IJob job); 12 | } -------------------------------------------------------------------------------- /src/Core/Management.Contracts/ServiceBusTransportType.cs: -------------------------------------------------------------------------------- 1 | namespace CrossBusExplorer.Management.Contracts; 2 | 3 | public enum ServiceBusTransportType 4 | { 5 | /// 6 | /// AMQP over TCP transport 7 | /// 8 | AmqpTcp = 0, 9 | 10 | /// 11 | /// AMQP over WebSockets transport (recommended for restrictive network environments) 12 | /// 13 | AmqpWebSockets = 1 14 | } 15 | -------------------------------------------------------------------------------- /src/Ui/Website.Host/_Imports.razor: -------------------------------------------------------------------------------- 1 | @using System.Net.Http 2 | @using Microsoft.AspNetCore.Authorization 3 | @using Microsoft.AspNetCore.Components.Authorization 4 | @using Microsoft.AspNetCore.Components.Forms 5 | @using Microsoft.AspNetCore.Components.Routing 6 | @using Microsoft.AspNetCore.Components.Web 7 | @using Microsoft.AspNetCore.Components.Web.Virtualization 8 | @using Microsoft.JSInterop 9 | @using MudBlazor 10 | @using Website.Host 11 | -------------------------------------------------------------------------------- /src/Ui/Website/App.razor: -------------------------------------------------------------------------------- 1 | @using CrossBusExplorer.Website.Shared 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |

Sorry, there's nothing at this address.

10 |
11 |
12 |
-------------------------------------------------------------------------------- /src/Core/ServiceBus.Contracts/Types/SubscriptionInfo.cs: -------------------------------------------------------------------------------- 1 | namespace CrossBusExplorer.ServiceBus.Contracts.Types; 2 | 3 | public record SubscriptionInfo( 4 | string TopicName, 5 | string SubscriptionName, 6 | ServiceBusEntityStatus Status, 7 | DateTimeOffset CreatedAt, 8 | DateTimeOffset AccessedAt, 9 | DateTimeOffset UpdatedAt, 10 | long ActiveMessagesCount, 11 | long DeadLetterMessagesCount, 12 | long TransferMessagesCount); -------------------------------------------------------------------------------- /src/Core/Management.Contracts/Management.Contracts.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net8.0 5 | enable 6 | enable 7 | CrossBusExplorer.Management.Contracts 8 | CrossBusExplorer.Management.Contracts 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/Core/Management/ServiceCollectionExtensions.cs: -------------------------------------------------------------------------------- 1 | using CrossBusExplorer.Management.Contracts; 2 | using Microsoft.Extensions.DependencyInjection; 3 | namespace CrossBusExplorer.Management; 4 | 5 | public static class ServiceCollectionExtensions 6 | { 7 | public static IServiceCollection AddManagement(this IServiceCollection collection) 8 | { 9 | return collection 10 | .AddScoped(); 11 | } 12 | } -------------------------------------------------------------------------------- /src/Core/ServiceBus.Contracts/ServiceBus.Contracts.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net8.0 5 | enable 6 | enable 7 | CrossBusExplorer.ServiceBus.Contracts 8 | CrossBusExplorer.ServiceBus.Contracts 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/Ui/Website/Validation/ConnectionStringValidationAttribute.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel.DataAnnotations; 2 | using CrossBusExplorer.Management; 3 | namespace CrossBusExplorer.Website; 4 | 5 | public class ConnectionStringValidationAttribute : ValidationAttribute 6 | { 7 | public override bool IsValid(object? value) 8 | { 9 | return value is string connectionString && 10 | ServiceBusConnectionStringHelper.IsValid(connectionString); 11 | } 12 | } -------------------------------------------------------------------------------- /src/Ui/Website/Jobs/IJob.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel; 2 | using System.Threading.Tasks; 3 | namespace CrossBusExplorer.Website.Jobs; 4 | 5 | public interface IJob : INotifyPropertyChanged 6 | { 7 | public event JobCompletedEventHandler? OnCompleted; 8 | int Progress { get; } 9 | Task ExecuteAsync(); 10 | void Cancel(); 11 | string Name { get; } 12 | bool ViewDetails { get; set; } 13 | JobStatus Status { get; } 14 | string? ErrorMessage { get; } 15 | } -------------------------------------------------------------------------------- /src/Core/ServiceBus.Contracts/Types/ApplicationProperty.cs: -------------------------------------------------------------------------------- 1 | namespace CrossBusExplorer.ServiceBus.Contracts.Types; 2 | 3 | public enum ApplicationPropertyType 4 | { 5 | String, 6 | Bool, 7 | Byte, 8 | Sbyte, 9 | Short, 10 | Ushort, 11 | Int, 12 | Uint, 13 | Long, 14 | Ulong, 15 | Float, 16 | Decimal, 17 | Double, 18 | Char, 19 | Guid, 20 | DateTime, 21 | DateTimeOffset, 22 | Stream, 23 | Uri, 24 | TimeSpan, 25 | } 26 | -------------------------------------------------------------------------------- /src/Ui/Website/Models/ServiceBusConnectionSettings.cs: -------------------------------------------------------------------------------- 1 | namespace CrossBusExplorer.Website.Models; 2 | 3 | public class ServiceBusConnectionSettings 4 | { 5 | public ServiceBusConnectionSettings(string name, int index) 6 | { 7 | Name = name; 8 | Index = index; 9 | } 10 | 11 | public string Name { get; } 12 | public int Index { get; private set; } 13 | 14 | public void UpdateIndex(int newIndex) 15 | { 16 | Index = newIndex; 17 | } 18 | } -------------------------------------------------------------------------------- /src/Ui/Website.Host/build/entitlements.mac.inherit.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.cs.allow-jit 6 | 7 | com.apple.security.cs.allow-unsigned-executable-memory 8 | 9 | com.apple.security.cs.allow-dyld-environment-variables 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/Ui/Website.Host/App.razor: -------------------------------------------------------------------------------- 1 | @using CrossBusExplorer.Website.Shared 2 | 3 | 4 | 5 | 6 | 7 | Not found 8 | 9 |

Sorry, there's nothing at this address.

10 |
11 |
12 |
-------------------------------------------------------------------------------- /src/Ui/Website/Shared/ViewDialog.razor: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Close 7 | 8 | 9 | @code { 10 | [CascadingParameter] 11 | MudDialogInstance MudDialog { get; set; } 12 | 13 | [Parameter] 14 | public string ContentText { get; set; } 15 | 16 | void Submit() => MudDialog.Close(DialogResult.Ok(true)); 17 | } -------------------------------------------------------------------------------- /src/Api/Host/Queries/TopicQueryExtensions.cs: -------------------------------------------------------------------------------- 1 | using CrossBusExplorer.ServiceBus.Contracts; 2 | using CrossBusExplorer.ServiceBus.Contracts.Types; 3 | namespace CrossBusExplorer.Host.Queries; 4 | 5 | [ExtendObjectType("Query")] 6 | public class TopicQueryExtensions 7 | { 8 | public IAsyncEnumerable GetTopicsAsync( 9 | [Service] ITopicService topicService, 10 | string connectionName, 11 | CancellationToken cancellationToken) 12 | { 13 | return topicService.GetStructureAsync( 14 | connectionName, 15 | cancellationToken); 16 | } 17 | } -------------------------------------------------------------------------------- /src/Core/ServiceBus.Contracts/Types/QueueInfo.cs: -------------------------------------------------------------------------------- 1 | namespace CrossBusExplorer.ServiceBus.Contracts.Types; 2 | 3 | public record QueueInfo( 4 | string Name, 5 | ServiceBusEntityStatus Status, 6 | long SizeInBytes, 7 | DateTimeOffset CreatedAt, 8 | DateTimeOffset AccessedAt, 9 | DateTimeOffset UpdatedAt, 10 | long ActiveMessagesCount, 11 | long DeadLetterMessagesCount, 12 | long ScheduledMessagesCount, 13 | long InTransferMessagesCount, 14 | long TransferDeadLetterMessagesCount, 15 | long TotalMessagesCount) 16 | { 17 | public long SizeInMB => SizeInBytes / 1024 / 1024; 18 | } -------------------------------------------------------------------------------- /src/Ui/Website/Shared/ConfirmDialog.razor: -------------------------------------------------------------------------------- 1 | 2 | 3 | @ContentText 4 | 5 | 6 | No 7 | Yes 8 | 9 | 10 | 11 | @code { 12 | [CascadingParameter] 13 | MudDialogInstance MudDialog { get; set; } 14 | 15 | [Parameter] 16 | public string ContentText { get; set; } 17 | 18 | void Submit(bool result) => MudDialog.Close(DialogResult.Ok(result)); 19 | } -------------------------------------------------------------------------------- /src/Core/ServiceBus.Contracts/Types/UpdateTopicOptions.cs: -------------------------------------------------------------------------------- 1 | namespace CrossBusExplorer.ServiceBus.Contracts.Types; 2 | 3 | public record UpdateTopicOptions( 4 | string Name, 5 | IReadOnlyList? AuthorizationRules=null, 6 | long? MaxSizeInMegabytes=null, 7 | TimeSpan? DefaultMessageTimeToLive=null, 8 | TimeSpan? AutoDeleteOnIdle=null, 9 | TimeSpan? DuplicateDetectionHistoryTimeWindow=null, 10 | bool? EnableBatchedOperations=null, 11 | bool? SupportOrdering=null, 12 | ServiceBusEntityStatus? Status=null, 13 | long? MaxMessageSizeInKilobytes=null, 14 | string? UserMetadata=null); -------------------------------------------------------------------------------- /src/Core/Management.Contracts/IConnectionManagement.cs: -------------------------------------------------------------------------------- 1 | namespace CrossBusExplorer.Management.Contracts; 2 | 3 | public interface IConnectionManagement 4 | { 5 | Task> GetAsync(CancellationToken cancellationToken); 6 | Task GetAsync(string name, CancellationToken cancellationToken); 7 | Task SaveAsync( 8 | string name, 9 | string connectionString, 10 | string folder, 11 | ServiceBusTransportType transportType, 12 | CancellationToken cancellationToken); 13 | Task DeleteAsync(string name, CancellationToken cancellationToken); 14 | } -------------------------------------------------------------------------------- /src/Core/ServiceBus.Contracts/Types/CreateTopicOptions.cs: -------------------------------------------------------------------------------- 1 | namespace CrossBusExplorer.ServiceBus.Contracts.Types; 2 | 3 | public record CreateTopicOptions( 4 | string Name, 5 | IReadOnlyList? AuthorizationRules, 6 | long? MaxSizeInMegabytes, 7 | TimeSpan? DefaultMessageTimeToLive, 8 | TimeSpan? AutoDeleteOnIdle, 9 | TimeSpan? DuplicateDetectionHistoryTimeWindow, 10 | bool? EnableBatchedOperations, 11 | ServiceBusEntityStatus? Status, 12 | long? MaxMessageSizeInKilobytes, 13 | string? UserMetadata, 14 | bool? RequiresDuplicateDetection, 15 | bool? SupportOrdering, 16 | bool? EnablePartitioning); -------------------------------------------------------------------------------- /src/Core/ServiceBus.Contracts/Types/SendMessage.cs: -------------------------------------------------------------------------------- 1 | namespace CrossBusExplorer.ServiceBus.Contracts.Types; 2 | 3 | public record SendMessage( 4 | string Body, 5 | string? Subject, 6 | string? To, 7 | string? ContentType, 8 | string? CorrelationId, 9 | string? Id, 10 | string? PartitionKey, 11 | string? ReplyTo, 12 | string? SessionId, 13 | DateTimeOffset? ScheduledEnqueueTime, 14 | TimeSpan? TimeToLive, 15 | Dictionary? ApplicationProperties) 16 | { 17 | public static SendMessage CreateFromBody(string body) => 18 | new SendMessage(body, null, null, null, null, null, null, null, null, null, null, null); 19 | } 20 | -------------------------------------------------------------------------------- /src/Core/ServiceBus.Contracts/Types/UpdateSubscriptionOptions.cs: -------------------------------------------------------------------------------- 1 | namespace CrossBusExplorer.ServiceBus.Contracts.Types; 2 | 3 | public record UpdateSubscriptionOptions( 4 | string TopicName, 5 | string SubscriptionName, 6 | TimeSpan? LockDuration=null, 7 | bool? RequiresSession=null, 8 | TimeSpan? DefaultMessageTimeToLive=null, 9 | TimeSpan? AutoDeleteOnIdle=null, 10 | bool? DeadLetteringOnMessageExpiration=null, 11 | int? MaxDeliveryCount=null, 12 | bool? EnableBatchedOperations=null, 13 | ServiceBusEntityStatus? Status=null, 14 | string? ForwardTo=null, 15 | string? ForwardDeadLetteredMessagesTo=null, 16 | string? UserMetadata=null); -------------------------------------------------------------------------------- /src/Core/ServiceBus.Contracts/Types/CreateSubscriptionOptions.cs: -------------------------------------------------------------------------------- 1 | namespace CrossBusExplorer.ServiceBus.Contracts.Types; 2 | 3 | public record CreateSubscriptionOptions( 4 | string TopicName, 5 | string SubscriptionName, 6 | TimeSpan? LockDuration, 7 | bool? RequiresSession, 8 | TimeSpan? DefaultMessageTimeToLive, 9 | TimeSpan? AutoDeleteOnIdle, 10 | bool? DeadLetteringOnMessageExpiration, 11 | bool? EnableDeadLetteringOnFilterEvaluationExceptions, 12 | int? MaxDeliveryCount, 13 | bool? EnableBatchedOperations, 14 | ServiceBusEntityStatus? Status, 15 | string? ForwardTo, 16 | string? ForwardDeadLetteredMessagesTo, 17 | string? UserMetadata); -------------------------------------------------------------------------------- /src/Api/Host/Queries/SubscriptionQueryExtensions.cs: -------------------------------------------------------------------------------- 1 | using CrossBusExplorer.ServiceBus.Contracts; 2 | using CrossBusExplorer.ServiceBus.Contracts.Types; 3 | namespace CrossBusExplorer.Host.Queries; 4 | 5 | [ExtendObjectType("Query")] 6 | public class SubscriptionQueryExtensions 7 | { 8 | public IAsyncEnumerable GetSubscriptionsAsync( 9 | [Service] ISubscriptionService subscriptionService, 10 | string connectionName, 11 | string topicName, 12 | CancellationToken cancellationToken) 13 | { 14 | return subscriptionService.GetAsync( 15 | connectionName, 16 | topicName, 17 | cancellationToken); 18 | } 19 | } -------------------------------------------------------------------------------- /src/Core/ServiceBus/ServiceCollectionExtensions.cs: -------------------------------------------------------------------------------- 1 | using CrossBusExplorer.ServiceBus.Contracts; 2 | using Microsoft.Extensions.DependencyInjection; 3 | 4 | namespace CrossBusExplorer.ServiceBus; 5 | 6 | public static class ServiceCollectionExtensions 7 | { 8 | public static IServiceCollection AddServiceBusServices(this IServiceCollection collection) 9 | { 10 | return collection 11 | .AddScoped() 12 | .AddScoped() 13 | .AddScoped() 14 | .AddScoped() 15 | .AddScoped(); 16 | } 17 | } -------------------------------------------------------------------------------- /src/Ui/Website/Extensions/INotifyPropertyChangedExtensions.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel; 2 | using System.Runtime.CompilerServices; 3 | namespace CrossBusExplorer.Website.Extensions; 4 | //source: http://www.blackwasp.co.uk/INotifyPropertyChangedExt.aspx 5 | 6 | public static class INotifyPropertyChangedExtensions 7 | { 8 | public static void Notify( 9 | this INotifyPropertyChanged sender, 10 | PropertyChangedEventHandler handler, 11 | [CallerMemberName] string propertyName = "") 12 | { 13 | if (handler != null) 14 | { 15 | PropertyChangedEventArgs args = new PropertyChangedEventArgs(propertyName); 16 | handler(sender, args); 17 | } 18 | } 19 | } -------------------------------------------------------------------------------- /src/Ui/Website/Extensions/DateTimeExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | namespace CrossBusExplorer.Website.Extensions; 3 | 4 | public static class DateTimeExtensions 5 | { 6 | public static string ToUniversal(this DateTimeOffset? value) 7 | { 8 | if (value != null && value != DateTimeOffset.MinValue && value != DateTimeOffset.MaxValue) 9 | { 10 | return value.Value.ToString("u"); 11 | } 12 | 13 | return "-"; 14 | } 15 | 16 | public static string ToUniversal(this DateTimeOffset value) 17 | { 18 | if (value != DateTimeOffset.MinValue && value != DateTimeOffset.MaxValue) 19 | { 20 | return value.ToString("u"); 21 | } 22 | 23 | return "-"; 24 | } 25 | } -------------------------------------------------------------------------------- /src/Ui/Website/ViewModels/INavigationViewModel.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.ObjectModel; 2 | using System.ComponentModel; 3 | using System.Threading; 4 | using System.Threading.Tasks; 5 | using CrossBusExplorer.Website.Models; 6 | namespace CrossBusExplorer.Website; 7 | 8 | public interface INavigationViewModel : INotifyPropertyChanged 9 | { 10 | ObservableCollection Folders { get; } 11 | public Task LoadTopics(ConnectionMenuItem menuItem, CancellationToken cancellationToken); 12 | public Task LoadQueues(ConnectionMenuItem menuItem, CancellationToken cancellationToken); 13 | Task LoadSubscriptionsAsync(string connectionName, TopicSubscriptionsModel model); 14 | Task ReloadMenu(); 15 | bool IsLoading { get; } 16 | } -------------------------------------------------------------------------------- /src/Core/Management/Management.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net8.0 5 | enable 6 | enable 7 | CrossBusExplorer.Management 8 | CrossBusExplorer.Management 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /src/Core/ServiceBus.Contracts/Types/MessageSystemProperties.cs: -------------------------------------------------------------------------------- 1 | namespace CrossBusExplorer.ServiceBus.Contracts.Types; 2 | 3 | public record MessageSystemProperties( 4 | string? ContentType, 5 | string? CorrelationId, 6 | string? DeadLetterSource, 7 | string? DeadLetterReason, 8 | string? DeadLetterErrorDescription, 9 | int DeliveryCount, 10 | long EnqueuedSequenceNumber, 11 | DateTimeOffset EnqueuedTime, 12 | DateTimeOffset ExpiresAt, 13 | DateTimeOffset LockedUntil, 14 | string LockToken, 15 | string? PartitionKey, 16 | string? TransactionPartitionKey, 17 | string? ReplyTo, 18 | string? ReplyToSessionId, 19 | DateTimeOffset? ScheduledEnqueueTime, 20 | long SequenceNumber, 21 | string? SessionId, 22 | MessageState? State, 23 | TimeSpan TimeToLive, 24 | string? To); -------------------------------------------------------------------------------- /src/Core/ServiceBus.Contracts/Types/CreateQueueOptions.cs: -------------------------------------------------------------------------------- 1 | namespace CrossBusExplorer.ServiceBus.Contracts.Types; 2 | 3 | public record CreateQueueOptions( 4 | string Name, 5 | IReadOnlyList? AuthorizationRules, 6 | long? MaxSizeInMegabytes, 7 | TimeSpan? LockDuration, 8 | bool? RequiresSession, 9 | TimeSpan? DefaultMessageTimeToLive, 10 | TimeSpan? AutoDeleteOnIdle, 11 | bool? DeadLetteringOnMessageExpiration, 12 | TimeSpan? DuplicateDetectionHistoryTimeWindow, 13 | int? MaxDeliveryCount, 14 | bool? EnableBatchedOperations, 15 | ServiceBusEntityStatus? Status, 16 | string? ForwardTo, 17 | string? ForwardDeadLetteredMessagesTo, 18 | long? MaxMessageSizeInKilobytes, 19 | string? UserMetadata, 20 | bool? RequiresDuplicateDetection, 21 | bool? EnablePartitioning); -------------------------------------------------------------------------------- /src/Core/ServiceBus.Contracts/Types/UpdateQueueOptions.cs: -------------------------------------------------------------------------------- 1 | namespace CrossBusExplorer.ServiceBus.Contracts.Types; 2 | 3 | public record UpdateQueueOptions( 4 | string Name, 5 | IReadOnlyList? AuthorizationRules=null, 6 | long? MaxSizeInMegabytes=null, 7 | TimeSpan? LockDuration=null, 8 | bool? RequiresSession=null, 9 | TimeSpan? DefaultMessageTimeToLive=null, 10 | TimeSpan? AutoDeleteOnIdle=null, 11 | bool? DeadLetteringOnMessageExpiration=null, 12 | TimeSpan? DuplicateDetectionHistoryTimeWindow=null, 13 | int? MaxDeliveryCount=null, 14 | bool? EnableBatchedOperations=null, 15 | ServiceBusEntityStatus? Status=null, 16 | string? ForwardTo=null, 17 | string? ForwardDeadLetteredMessagesTo=null, 18 | long? MaxMessageSizeInKilobytes=null, 19 | string? UserMetadata=null); -------------------------------------------------------------------------------- /src/Ui/Website.Host/Pages/Error.cshtml.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Mvc; 2 | using Microsoft.AspNetCore.Mvc.RazorPages; 3 | using System.Diagnostics; 4 | 5 | namespace Website.Host.Pages 6 | { 7 | [ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)] 8 | [IgnoreAntiforgeryToken] 9 | public class ErrorModel : PageModel 10 | { 11 | public string? RequestId { get; set; } 12 | 13 | public bool ShowRequestId => !string.IsNullOrEmpty(RequestId); 14 | 15 | private readonly ILogger _logger; 16 | 17 | public ErrorModel(ILogger logger) 18 | { 19 | _logger = logger; 20 | } 21 | 22 | public void OnGet() 23 | { 24 | RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier; 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/Ui/Website/Models/ServiceBusConnectionFolder.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using CrossBusExplorer.Management.Contracts; 3 | namespace CrossBusExplorer.Website.Models; 4 | 5 | public class ServiceBusConnectionWithFolder : ServiceBusConnection 6 | { 7 | public string Folder { get; private set; } 8 | 9 | public void UpdateFolder(string folder) 10 | { 11 | Folder = folder; 12 | } 13 | 14 | public ServiceBusConnectionWithFolder(ServiceBusConnection serviceBusConnection, string folder) : base(serviceBusConnection.Name, serviceBusConnection.ConnectionString, serviceBusConnection.Endpoint, serviceBusConnection.FullyQualifiedName, serviceBusConnection.EntityPath, serviceBusConnection.SharedAccessKey, serviceBusConnection.SharedAccessSignature, serviceBusConnection.SharedAccessKeyName, serviceBusConnection.TransportType) 15 | { 16 | Folder = folder; 17 | } 18 | } -------------------------------------------------------------------------------- /src/Api/Host/Host.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net8.0 5 | enable 6 | enable 7 | CrossBusExplorer.Host 8 | CrossBusExplorer.Host 9 | preview 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /src/Ui/Website/Extensions/IListExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | namespace CrossBusExplorer.Website.Extensions; 5 | 6 | public static class IListExtensions 7 | { 8 | public static void AddOrReplace(this IList list, Func func, T item) 9 | { 10 | var listItem = list.FirstOrDefault(func); 11 | if (listItem != null) 12 | { 13 | list[list.IndexOf(listItem)] = item; 14 | } 15 | else 16 | { 17 | list.Add(item); 18 | } 19 | } 20 | 21 | public static void RemoveNonExisting(this IList list, Func func) 22 | { 23 | var nonExistingItems = list.Where(func).ToArray(); 24 | 25 | for (var i = 0; i < nonExistingItems.Length; i++) 26 | { 27 | list.Remove(nonExistingItems[i]); 28 | } 29 | } 30 | } -------------------------------------------------------------------------------- /src/Api/Host/ConnectionManagement.cs: -------------------------------------------------------------------------------- 1 | using CrossBusExplorer.Management.Contracts; 2 | namespace CrossBusExplorer.Host; 3 | 4 | public class ConnectionManagement : IConnectionManagement 5 | { 6 | public Task> GetAsync(CancellationToken cancellationToken) 7 | { 8 | throw new NotImplementedException(); 9 | } 10 | public Task GetAsync(string name, CancellationToken cancellationToken) 11 | { 12 | throw new NotImplementedException(); 13 | } 14 | public Task SaveAsync(string name, string connectionString, string folder, 15 | ServiceBusTransportType transportType, CancellationToken cancellationToken) 16 | { 17 | throw new NotImplementedException(); 18 | } 19 | public Task DeleteAsync(string name, CancellationToken cancellationToken) 20 | { 21 | throw new NotImplementedException(); 22 | } 23 | } -------------------------------------------------------------------------------- /src/Core/ServiceBus/ServiceBus.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net8.0 5 | enable 6 | enable 7 | CrossBusExplorer.ServiceBus 8 | CrossBusExplorer.ServiceBus 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /src/Api/Host/Queries/QueueQueryExtensions.cs: -------------------------------------------------------------------------------- 1 | using CrossBusExplorer.ServiceBus.Contracts; 2 | using CrossBusExplorer.ServiceBus.Contracts.Types; 3 | namespace CrossBusExplorer.Host.Queries; 4 | 5 | [ExtendObjectType("Query")] 6 | public class QueueQueryExtensions 7 | { 8 | public IAsyncEnumerable GetQueuesAsync( 9 | [Service] IQueueService queueService, 10 | string connectionName, 11 | CancellationToken cancellationToken) 12 | { 13 | return queueService.GetAsync( 14 | connectionName, 15 | cancellationToken); 16 | } 17 | 18 | public Task GetQueueAsync( 19 | [Service] IQueueService queueService, 20 | string connectionName, 21 | string name, 22 | CancellationToken cancellationToken) 23 | { 24 | return queueService.GetAsync( 25 | connectionName, 26 | name, 27 | cancellationToken); 28 | } 29 | } -------------------------------------------------------------------------------- /src/Ui/Website.Host/Website.Host.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | net8.0 4 | enable 5 | enable 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | PreserveNewest 22 | 23 | 24 | -------------------------------------------------------------------------------- /src/Api/Host/Program.cs: -------------------------------------------------------------------------------- 1 | using CrossBusExplorer.Host; 2 | using CrossBusExplorer.Host.Mutations; 3 | using CrossBusExplorer.Host.Queries; 4 | using CrossBusExplorer.Management.Contracts; 5 | using CrossBusExplorer.ServiceBus; 6 | 7 | var builder = WebApplication.CreateBuilder(args); 8 | 9 | builder.Services 10 | .AddSingleton() 11 | .AddServiceBusServices() 12 | .AddGraphQLServer() 13 | .AddQueryType() 14 | .AddMutationType() 15 | .AddTypeExtension() 16 | .AddTypeExtension() 17 | .AddTypeExtension() 18 | .AddTypeExtension() 19 | .AddTypeExtension() 20 | .AddTypeExtension() 21 | .AddMutationConventions(); 22 | 23 | var app = builder.Build(); 24 | 25 | app.MapGet("/", () => "Hello World!"); 26 | app.UseWebSockets(); 27 | app.MapGraphQL(); 28 | 29 | app.Run(); -------------------------------------------------------------------------------- /src/Ui/Website/FluentValidator.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using FluentValidation; 5 | namespace CrossBusExplorer.Website; 6 | 7 | /// 8 | /// A glue class to make it easy to define validation rules for single values using FluentValidation 9 | /// You can reuse this class for all your fields, like for the credit card rules above. 10 | /// 11 | /// 12 | public class FluentValueValidator : AbstractValidator 13 | { 14 | public FluentValueValidator(Action> rule) 15 | { 16 | rule(RuleFor(x => x)); 17 | } 18 | 19 | private IEnumerable ValidateValue(T arg) 20 | { 21 | var result = Validate(arg); 22 | if (result.IsValid) 23 | return new string[0]; 24 | return result.Errors.Select(e => e.ErrorMessage); 25 | } 26 | 27 | public Func> Validation => ValidateValue; 28 | } -------------------------------------------------------------------------------- /src/Api/Host/Queries/MessageQueryExtensions.cs: -------------------------------------------------------------------------------- 1 | using CrossBusExplorer.ServiceBus.Contracts; 2 | using CrossBusExplorer.ServiceBus.Contracts.Types; 3 | namespace CrossBusExplorer.Host.Queries; 4 | 5 | [ExtendObjectType("Query")] 6 | public class MessageQueryExtensions 7 | { 8 | public Task> GetMessagesAsync( 9 | [Service] IMessageService messageService, 10 | string connectionName, 11 | string queueOrTopicName, 12 | string? subscriptionName, 13 | SubQueue subQueue, 14 | ReceiveMode mode, 15 | ReceiveType type, 16 | int? messagesCount, 17 | long? fromSequenceNumber, 18 | CancellationToken cancellationToken) 19 | { 20 | return messageService.GetMessagesAsync( 21 | connectionName, 22 | queueOrTopicName, 23 | subscriptionName, 24 | subQueue, 25 | mode, 26 | type, 27 | messagesCount, 28 | fromSequenceNumber, 29 | cancellationToken); 30 | } 31 | } -------------------------------------------------------------------------------- /src/Ui/Website/Models/KeyValueTypePair.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel; 2 | using CrossBusExplorer.ServiceBus.Contracts.Types; 3 | using CrossBusExplorer.Website.Extensions; 4 | namespace CrossBusExplorer.Website.Models; 5 | 6 | public class KeyValueTypePair : INotifyPropertyChanged 7 | { 8 | public event PropertyChangedEventHandler? PropertyChanged; 9 | 10 | private string _key; 11 | 12 | public string Key 13 | { 14 | get => _key; 15 | set 16 | { 17 | _key = value; 18 | this.Notify(PropertyChanged); 19 | } 20 | } 21 | 22 | private string? _value; 23 | 24 | public string? Value 25 | { 26 | get => _value; 27 | set 28 | { 29 | _value = value; 30 | this.Notify(PropertyChanged); 31 | } 32 | } 33 | 34 | private ApplicationPropertyType _type; 35 | 36 | public ApplicationPropertyType Type 37 | { 38 | get => _type; 39 | set 40 | { 41 | _type = value; 42 | this.Notify(PropertyChanged); 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Mariusz Matysek 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/Core/ServiceBus.Contracts/ITopicService.cs: -------------------------------------------------------------------------------- 1 | using CrossBusExplorer.ServiceBus.Contracts.Types; 2 | namespace CrossBusExplorer.ServiceBus.Contracts; 3 | 4 | public interface ITopicService 5 | { 6 | IAsyncEnumerable GetStructureAsync( 7 | string connectionName, 8 | CancellationToken cancellationToken); 9 | Task GetAsync( 10 | string connectionName, 11 | string name, 12 | CancellationToken cancellationToken); 13 | Task DeleteAsync( 14 | string connectionName, 15 | string name, 16 | CancellationToken cancellationToken); 17 | Task> CreateAsync( 18 | string connectionName, 19 | CreateTopicOptions options, 20 | CancellationToken cancellationToken); 21 | Task> CloneAsync( 22 | string connectionName, 23 | string name, 24 | string sourceName, 25 | CancellationToken cancellationToken); 26 | Task> UpdateAsync( 27 | string connectionName, 28 | UpdateTopicOptions options, 29 | CancellationToken cancellationToken); 30 | } -------------------------------------------------------------------------------- /src/Ui/Website/Models/TopicSubscriptionsModel.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using CrossBusExplorer.ServiceBus.Contracts.Types; 4 | using CrossBusExplorer.Website.Extensions; 5 | namespace CrossBusExplorer.Website.Models; 6 | 7 | public class TopicSubscriptionsModel 8 | { 9 | public TopicStructure Topic { get; } 10 | public IList Subscriptions { get; } 11 | public IList ChildrenModels { get; } 12 | public TopicSubscriptionsModel(TopicStructure topic) 13 | { 14 | Topic = topic; 15 | Subscriptions = new List(); 16 | ChildrenModels = topic.ChildTopics.Select(p => new TopicSubscriptionsModel(p)).ToList(); 17 | } 18 | 19 | public bool Loaded { get; set; } 20 | public bool IsLoading { get; set; } 21 | public bool TopicExpanded { get; set; } 22 | public bool ShouldRender { get; set; } 23 | 24 | public void AddSubscription(SubscriptionInfo subscriptionInfo) 25 | { 26 | Subscriptions.AddOrReplace( 27 | p=>p.SubscriptionName.EqualsInvariantIgnoreCase(subscriptionInfo.SubscriptionName), 28 | subscriptionInfo); 29 | } 30 | } -------------------------------------------------------------------------------- /src/Core/ServiceBus.Contracts/IRuleService.cs: -------------------------------------------------------------------------------- 1 | using CrossBusExplorer.ServiceBus.Contracts.Types; 2 | namespace CrossBusExplorer.ServiceBus.Contracts; 3 | 4 | public interface IRuleService 5 | { 6 | IAsyncEnumerable GetAsync( 7 | string connectionName, 8 | string topicName, 9 | string subscriptionName, 10 | CancellationToken cancellationToken); 11 | 12 | Task DeleteAsync( 13 | string connectionName, 14 | string topicName, 15 | string subscriptionName, 16 | string ruleName, 17 | CancellationToken cancellationToken); 18 | 19 | Task> CreateAsync( 20 | string connectionName, 21 | string topicName, 22 | string subscriptionName, 23 | string ruleName, 24 | RuleType type, 25 | string? value, 26 | CancellationToken cancellationToken); 27 | 28 | Task> UpdateAsync( 29 | string connectionName, 30 | string topicName, 31 | string subscriptionName, 32 | string ruleName, 33 | RuleType type, 34 | string? value, 35 | CancellationToken cancellationToken); 36 | } -------------------------------------------------------------------------------- /src/Ui/Website/ViewModels/IMessagesViewModel.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.ObjectModel; 2 | using System.ComponentModel; 3 | using System.Threading; 4 | using System.Threading.Tasks; 5 | using CrossBusExplorer.ServiceBus.Contracts.Types; 6 | using CrossBusExplorer.Website.Models; 7 | using MudBlazor; 8 | namespace CrossBusExplorer.Website.ViewModels; 9 | 10 | public interface IMessagesViewModel : INotifyPropertyChanged 11 | { 12 | ObservableCollection Messages { get; } 13 | bool DialogVisible { get; set; } 14 | bool IsPeekMode(ReceiveMessagesForm formModel); 15 | bool CanPeekMore(ReceiveMessagesForm formModel); 16 | Task PeekMore(ReceiveMessagesForm formModel, CancellationToken cancellationToken); 17 | Task OnSubmitReceiveForm( 18 | MudForm form, 19 | ReceiveMessagesForm formModel, 20 | CancellationToken cancellationToken); 21 | void Initialize(CurrentMessagesEntity entity); 22 | Task ViewMessageDetails(Message? message, bool editMode); 23 | Task Requeue(string queueOrTopicName, MessageDetailsModel message); 24 | Task Delete(Message message, SubQueue subQueue); 25 | Task ImportMessagesFromFileAsync(CancellationToken cancellationToken); 26 | } 27 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Cross Bus Explorer 2 | 3 | Cross Bus Explorer is a Cross Platform Service Bus Explorer (Windows, McOS and Linux) 4 | 5 | ## Features 6 | 7 | - Create, List, Edit, Delete, Disable of Queues, Topics, Subscriptions 8 | - Subscription rules management (create, edit, update) 9 | - Purge messages 10 | - Requeue messages 11 | - Send messages 12 | - Resend edited messages 13 | 14 | ![chrome_SbCnkRVg9b](https://user-images.githubusercontent.com/13761704/207661917-7f1ea66f-3878-4e88-a771-def3b5c52088.gif) 15 | 16 | ## Known issues 17 | ### Linux snap installer 18 | 19 | The snap package is not signed, to install it run following command: 20 | snap install cross-bus-explorer-0.2.0.snap --dangerous 21 | ![image](https://user-images.githubusercontent.com/6861396/206653698-f1146dad-7d03-4f72-ae0a-c5d55cc3ee6d.png) 22 | 23 | 24 | ### Mac Os 25 | 26 | The app is not signed so it's blocked by default. Please go to Privacy and Security and click 'Open anyway' on the option in 'Security' section. Also above the 'Allow applications downloaded from' option need to be set to: 'App Store and identified developers'. 27 | ![image](https://user-images.githubusercontent.com/6861396/206653511-2584d668-f3fa-47f2-acd4-6242788b9996.png) 28 | -------------------------------------------------------------------------------- /src/Ui/Website/ViewModels/IRulesViewModel.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.ObjectModel; 2 | using System.ComponentModel; 3 | using System.Threading; 4 | using System.Threading.Tasks; 5 | using CrossBusExplorer.ServiceBus.Contracts.Types; 6 | using CrossBusExplorer.Website.Models; 7 | using MudBlazor; 8 | namespace CrossBusExplorer.Website.ViewModels; 9 | 10 | public interface IRulesViewModel : INotifyPropertyChanged 11 | { 12 | RuleFormModel? Form { get; } 13 | ObservableCollection Rules { get; } 14 | 15 | bool DialogVisible { get; set; } 16 | 17 | void ShowCreateRuleForm(); 18 | 19 | Task InitializeAsync( 20 | string connectionName, 21 | string topicName, 22 | string subscriptionName, 23 | CancellationToken cancellationToken); 24 | 25 | void ShowEditRuleForm(Rule rule); 26 | 27 | Task DeleteRuleAsync( 28 | string connectionName, 29 | string topicName, 30 | string subscriptionName, 31 | Rule rule, 32 | CancellationToken cancellationToken); 33 | Task OnSubmitFormAsync( 34 | string connectionName, 35 | string topicName, 36 | string subscriptionName, 37 | CancellationToken cancellationToken); 38 | } -------------------------------------------------------------------------------- /src/Ui/Website/ViewModels/ITopicViewModel.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | using CrossBusExplorer.ServiceBus.Contracts.Types; 5 | using CrossBusExplorer.Website.Models; 6 | namespace CrossBusExplorer.Website.ViewModels; 7 | 8 | public interface ITopicViewModel : INotifyPropertyChanged 9 | { 10 | event TopicAddedEventHandler? TopicAdded; 11 | event TopicRemovedEventHandler? TopicRemoved; 12 | TopicFormModel? Form { get; } 13 | TopicDetails? TopicDetails { get; } 14 | Task InitializeForm( 15 | string connectionName, 16 | string? topicName, 17 | CancellationToken cancellationToken); 18 | Task SaveTopicFormAsync(string connectionName); 19 | void NavigateToNewTopicForm(string connectionName); 20 | Task CloneTopic( 21 | string connectionName, 22 | string sourceTopicName, 23 | CancellationToken cancellationToken); 24 | Task DeleteTopic(string connectionName, string topicName, CancellationToken cancellationToken); 25 | Task UpdateTopicStatus( 26 | string connectionName, 27 | string topicName, 28 | ServiceBusEntityStatus active, 29 | CancellationToken cancellationToken); 30 | } -------------------------------------------------------------------------------- /src/Ui/Website.Host/electron.manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "executable": "Website.Host", 3 | "splashscreen": { 4 | "imageFile": "" 5 | }, 6 | "name": "CrossBusExplorer", 7 | "author": "Mariusz Matysek", 8 | "singleInstance": false, 9 | "environment": "Production", 10 | "description": "Cross Bus Explorer", 11 | "aspCoreBackendPort": 8010, 12 | "build": { 13 | "appId": "com.crossbusexplorer.app", 14 | "productName": "Cross Bus Explorer", 15 | "copyright": "Copyright © 2025", 16 | "buildVersion": "0.4.13", 17 | "compression": "maximum", 18 | "directories": { 19 | "output": "../../../bin/Desktop" 20 | }, 21 | "extraResources": [ 22 | { 23 | "from": "./bin", 24 | "to": "bin", 25 | "filter": ["**/*"] 26 | } 27 | ], 28 | "files": [ 29 | { 30 | "from": "./ElectronHostHook/node_modules", 31 | "to": "ElectronHostHook/node_modules", 32 | "filter": ["**/*"] 33 | }, 34 | "**/*" 35 | ], 36 | "win": { 37 | "icon": "../../../build/icon.png" 38 | }, 39 | "linux": { 40 | "icon": "../../../build/icon.png" 41 | }, 42 | "mac": { 43 | "icon": "../../../build/icon512x512.png" 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/Core/ServiceBus.Contracts/IQueueService.cs: -------------------------------------------------------------------------------- 1 | using CrossBusExplorer.ServiceBus.Contracts.Types; 2 | namespace CrossBusExplorer.ServiceBus.Contracts 3 | { 4 | public interface IQueueService 5 | { 6 | IAsyncEnumerable GetAsync( 7 | string connectionName, 8 | CancellationToken cancellationToken); 9 | Task GetAsync( 10 | string connectionName, 11 | string name, 12 | CancellationToken cancellationToken); 13 | Task DeleteAsync( 14 | string connectionName, 15 | string name, 16 | CancellationToken cancellationToken); 17 | Task> CreateAsync( 18 | string connectionName, 19 | CreateQueueOptions options, 20 | CancellationToken cancellationToken); 21 | Task> CloneAsync( 22 | string connectionName, 23 | string name, 24 | string sourceName, 25 | CancellationToken cancellationToken); 26 | Task> UpdateAsync( 27 | string connectionName, 28 | UpdateQueueOptions options, 29 | CancellationToken cancellationToken); 30 | } 31 | } -------------------------------------------------------------------------------- /src/Ui/Website/Models/CurrentMessagesEntity.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | namespace CrossBusExplorer.Website.Models; 3 | 4 | public record CurrentMessagesEntity( 5 | string ConnectionName, 6 | string QueueOrTopicName, 7 | string SubscriptionName) 8 | { 9 | public virtual bool Equals(CurrentMessagesEntity? other) 10 | { 11 | if (ReferenceEquals(null, other)) 12 | return false; 13 | if (ReferenceEquals(this, other)) 14 | return true; 15 | 16 | return string.Equals(ConnectionName, other.ConnectionName, 17 | StringComparison.InvariantCultureIgnoreCase) && 18 | string.Equals(QueueOrTopicName, other.QueueOrTopicName, 19 | StringComparison.InvariantCultureIgnoreCase) && string.Equals(SubscriptionName, 20 | other.SubscriptionName, StringComparison.InvariantCultureIgnoreCase); 21 | } 22 | public override int GetHashCode() 23 | { 24 | var hashCode = new HashCode(); 25 | hashCode.Add(ConnectionName, StringComparer.InvariantCultureIgnoreCase); 26 | hashCode.Add(QueueOrTopicName, StringComparer.InvariantCultureIgnoreCase); 27 | hashCode.Add(SubscriptionName, StringComparer.InvariantCultureIgnoreCase); 28 | return hashCode.ToHashCode(); 29 | } 30 | } -------------------------------------------------------------------------------- /src/Ui/Website/Shared/Queue/CloneDialog.razor: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Clone @SourceDialogName 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | No 14 | Yes 15 | 16 | 17 | 18 | @code { 19 | [CascadingParameter] 20 | MudDialogInstance MudDialog { get; set; } 21 | 22 | [Parameter] 23 | public string SourceDialogName { get; set; } 24 | 25 | [Parameter] 26 | public string ConnectionName { get; set; } 27 | 28 | MudForm form; 29 | string name; 30 | 31 | async Task Submit() 32 | { 33 | await form.Validate(); 34 | 35 | if (form.IsValid) 36 | { 37 | MudDialog.Close(DialogResult.Ok(name)); 38 | } 39 | 40 | } 41 | private void CloseDialog() 42 | { 43 | MudDialog.Close(DialogResult.Cancel()); 44 | } 45 | } -------------------------------------------------------------------------------- /src/Ui/Website.Host/Pages/_Layout.cshtml: -------------------------------------------------------------------------------- 1 | @using Microsoft.AspNetCore.Components.Web 2 | @namespace Website.Host.Pages 3 | @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | @RenderBody() 17 | 18 |
19 | 20 | An error has occurred. This application may no longer respond until reloaded. 21 | 22 | 23 | An unhandled exception has occurred. See browser dev tools for details. 24 | 25 | Reload 26 | 🗙 27 |
28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /src/Ui/Website/Models/ConnectionFolder.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.ObjectModel; 2 | using System.ComponentModel; 3 | using CrossBusExplorer.Website.Extensions; 4 | namespace CrossBusExplorer.Website.Models; 5 | 6 | public class ConnectionFolder : INotifyPropertyChanged 7 | { 8 | public ConnectionFolder(string name) 9 | { 10 | _name = name; 11 | _menuItems = new ObservableCollection(); 12 | _menuItems.CollectionChanged += (_, _) => this.Notify(PropertyChanged); 13 | } 14 | 15 | public event PropertyChangedEventHandler? PropertyChanged; 16 | 17 | private string _name; 18 | public string Name 19 | { 20 | get => _name; 21 | set 22 | { 23 | _name = value; 24 | this.Notify(PropertyChanged); 25 | } 26 | } 27 | 28 | private ObservableCollection _menuItems; 29 | 30 | public ObservableCollection MenuItems 31 | { 32 | get => _menuItems; 33 | private set 34 | { 35 | _menuItems = value; 36 | _menuItems.CollectionChanged += (_, _) => 37 | { 38 | this.Notify(PropertyChanged); 39 | }; 40 | this.Notify(PropertyChanged); 41 | } 42 | } 43 | } -------------------------------------------------------------------------------- /src/Ui/Website/Shared/Connections/FolderDialog.razor: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | @FolderDialogName 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | Close 14 | Save 15 | 16 | 17 | 18 | @code { 19 | [CascadingParameter] 20 | MudDialogInstance MudDialog { get; set; } 21 | 22 | [Parameter] 23 | public string FolderDialogName { get; set; } 24 | 25 | [Parameter] 26 | public string? FolderName { get; set; } 27 | 28 | MudForm form; 29 | 30 | async Task Submit() 31 | { 32 | await form.Validate(); 33 | 34 | if (form.IsValid) 35 | { 36 | MudDialog.Close(DialogResult.Ok(FolderName)); 37 | } 38 | 39 | } 40 | private void CloseDialog() 41 | { 42 | MudDialog.Close(DialogResult.Cancel()); 43 | } 44 | } -------------------------------------------------------------------------------- /src/Ui/Website/Shared/ServiceBusNavMenu.razor: -------------------------------------------------------------------------------- 1 | @using CrossBusExplorer.Management 2 | @using CrossBusExplorer.Management.Contracts 3 | @using CrossBusExplorer.ServiceBus.Contracts 4 | @using CrossBusExplorer.Website.Models 5 | @using System.Security.Cryptography 6 | @using System.Net 7 | @using System.Web 8 | 9 | 10 | 11 | @foreach (var folder in Model.Folders) 12 | { 13 | if (string.IsNullOrEmpty(folder.Name)) 14 | { 15 | foreach (var menuItem in folder.MenuItems) 16 | { 17 | 18 | } 19 | } 20 | else 21 | { 22 | 24 | @foreach (var menuItem in folder.MenuItems) 25 | { 26 | 27 | } 28 | 29 | } 30 | } 31 | 32 | 33 | @code { 34 | [Inject] 35 | private INavigationViewModel Model { get; set; } 36 | 37 | protected override async Task OnInitializedAsync() 38 | { 39 | Model.PropertyChanged += (_, _) => 40 | { 41 | StateHasChanged(); 42 | }; 43 | } 44 | } -------------------------------------------------------------------------------- /src/Ui/Website/Models/RuleFormModel.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel; 2 | using CrossBusExplorer.ServiceBus.Contracts.Types; 3 | using CrossBusExplorer.Website.Extensions; 4 | using CrossBusExplorer.Website.Pages; 5 | using MudBlazor; 6 | namespace CrossBusExplorer.Website.Models; 7 | 8 | public class RuleFormModel : INotifyPropertyChanged 9 | { 10 | public event PropertyChangedEventHandler? PropertyChanged; 11 | 12 | public RuleFormModel(OperationType operationType) 13 | { 14 | OperationType = operationType; 15 | } 16 | 17 | public OperationType OperationType { get; } 18 | 19 | private string? _name; 20 | 21 | public string? Name 22 | { 23 | get => _name; 24 | set 25 | { 26 | _name = value; 27 | this.Notify(PropertyChanged); 28 | } 29 | } 30 | 31 | private RuleType _type; 32 | 33 | public RuleType Type 34 | { 35 | get => _type; 36 | set 37 | { 38 | _type = value; 39 | this.Notify(PropertyChanged); 40 | } 41 | } 42 | 43 | private string? _value; 44 | 45 | public string? Value 46 | { 47 | get => _value; 48 | set 49 | { 50 | _value = value; 51 | this.Notify(PropertyChanged); 52 | } 53 | } 54 | } -------------------------------------------------------------------------------- /src/Core/Management.Contracts/ServiceBusConnection.cs: -------------------------------------------------------------------------------- 1 | namespace CrossBusExplorer.Management.Contracts; 2 | 3 | public class ServiceBusConnection 4 | { 5 | public ServiceBusConnection( 6 | string name, 7 | string connectionString, 8 | Uri endpoint, 9 | string fullyQualifiedName, 10 | string? entityPath, 11 | string sharedAccessKey, 12 | string sharedAccessSignature, 13 | string sharedAccessKeyName, 14 | ServiceBusTransportType transportType = ServiceBusTransportType.AmqpTcp) 15 | { 16 | Name = name; 17 | ConnectionString = connectionString; 18 | Endpoint = endpoint; 19 | FullyQualifiedName = fullyQualifiedName; 20 | EntityPath = entityPath; 21 | SharedAccessKey = sharedAccessKey; 22 | SharedAccessSignature = sharedAccessSignature; 23 | SharedAccessKeyName = sharedAccessKeyName; 24 | TransportType = transportType; 25 | } 26 | public string Name { get; } 27 | public string ConnectionString { get; } 28 | public Uri Endpoint { get; } 29 | public string FullyQualifiedName { get; } 30 | public string? EntityPath { get; } 31 | public string SharedAccessKey { get; } 32 | public string SharedAccessSignature { get; } 33 | public string SharedAccessKeyName { get; } 34 | public ServiceBusTransportType TransportType { get; } 35 | } -------------------------------------------------------------------------------- /src/Core/ServiceBus.Contracts/ISubscriptionService.cs: -------------------------------------------------------------------------------- 1 | using CrossBusExplorer.ServiceBus.Contracts.Types; 2 | namespace CrossBusExplorer.ServiceBus.Contracts; 3 | 4 | public interface ISubscriptionService 5 | { 6 | IAsyncEnumerable GetAsync( 7 | string connectionName, 8 | string topicName, 9 | CancellationToken cancellationToken); 10 | 11 | Task GetAsync( 12 | string connectionName, 13 | string topicName, 14 | string subscriptionName, 15 | CancellationToken cancellationToken); 16 | Task DeleteAsync( 17 | string connectionName, 18 | string topicName, 19 | string subscriptionName, 20 | CancellationToken cancellationToken); 21 | Task> CreateAsync( 22 | string connectionName, 23 | CreateSubscriptionOptions options, 24 | CancellationToken cancellationToken); 25 | Task> CloneAsync( 26 | string connectionName, 27 | string subscriptionName, 28 | string sourceTopicName, 29 | string sourceSubscriptionName, 30 | CancellationToken cancellationToken); 31 | Task> UpdateAsync( 32 | string connectionName, 33 | UpdateSubscriptionOptions options, 34 | CancellationToken cancellationToken); 35 | } -------------------------------------------------------------------------------- /src/Ui/Website/Shared/Jobs.razor: -------------------------------------------------------------------------------- 1 | @using CrossBusExplorer.Website.ViewModels 2 | @using MudBlazor.Utilities 3 | 4 | @foreach (var job in Model.Jobs) 5 | { 6 | 7 | 8 | 9 | 10 | 14 |
15 | @($"{job.Name} {job.Progress}%") 16 | 17 | 21 | 22 | 23 |
24 |
25 |
26 | } 27 | 28 | @code { 29 | [Inject] 30 | public IJobsViewModel Model { get; set; } 31 | 32 | protected override void OnInitialized() 33 | { 34 | Model.PropertyChanged += (_, _) => StateHasChanged(); 35 | } 36 | } -------------------------------------------------------------------------------- /src/Ui/Website/Models/Validators/ReceiveMessageFormValidator.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using CrossBusExplorer.ServiceBus.Contracts.Types; 6 | using CrossBusExplorer.Website.Extensions; 7 | using FluentValidation; 8 | namespace CrossBusExplorer.Website.Models.Validators; 9 | 10 | public class ReceiveMessageFormValidator : AbstractValidator 11 | { 12 | public ReceiveMessageFormValidator() 13 | { 14 | RuleFor(p => p.MessagesCount) 15 | .GreaterThan(0) 16 | .NotEmpty() 17 | .When(p => p.Type == ReceiveType.ByCount) 18 | .WithMessage($"When mode is set to {ReceiveMode.PeekLock} then " + 19 | $"{nameof(ReceiveMessagesForm.MessagesCount)} hast to be" + 20 | $" greater then 0."); 21 | } 22 | 23 | public Func>> ValidateValue => 24 | async (model, propertyName) => 25 | { 26 | var result = await ValidateAsync( 27 | ValidationContext.CreateWithOptions((ReceiveMessagesForm)model, 28 | x => x.IncludeProperties(propertyName.SplitAndGetLastSection('.')))); 29 | if (result.IsValid) 30 | return Array.Empty(); 31 | 32 | return result.Errors.Select(e => e.ErrorMessage); 33 | }; 34 | } -------------------------------------------------------------------------------- /src/Ui/Website/ViewModels/IQueueViewModel.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | using CrossBusExplorer.ServiceBus.Contracts.Types; 5 | using CrossBusExplorer.Website.Models; 6 | namespace CrossBusExplorer.Website.ViewModels; 7 | 8 | public interface IQueueViewModel : INotifyPropertyChanged 9 | { 10 | event QueueOperationEventHandler? OnQueueOperation; 11 | QueueFormModel? Form { get; } 12 | QueueDetails? QueueDetails { get; } 13 | Task InitializeFormAsync( 14 | string connectionName, 15 | string? queueName, 16 | CancellationToken cancellationToken); 17 | Task SaveQueueFormAsync(string connectionName); 18 | void NavigateToNewQueueForm(string connectionName); 19 | Task CloneQueue( 20 | string connectionName, 21 | string sourceQueueName, 22 | CancellationToken cancellationToken); 23 | Task DeleteQueue(string connectionName, string queueName, CancellationToken cancellationToken); 24 | Task UpdateQueueStatus( 25 | string connectionName, 26 | string queueName, 27 | ServiceBusEntityStatus status, 28 | CancellationToken cancellationToken); 29 | Task PurgeMessages( 30 | string connectionName, 31 | string queueName, 32 | CancellationToken cancellationToken); 33 | Task ResendDeadLetters( 34 | string connectionName, 35 | string queueName, 36 | CancellationToken cancellationToken); 37 | } -------------------------------------------------------------------------------- /src/Ui/Website/Models/Validators/RuleFormValidator.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using CrossBusExplorer.ServiceBus.Contracts.Types; 6 | using CrossBusExplorer.Website.Extensions; 7 | using FluentValidation; 8 | namespace CrossBusExplorer.Website.Models.Validators; 9 | 10 | public class RuleFormValidator : AbstractValidator 11 | { 12 | public RuleFormValidator() 13 | { 14 | RuleFor(p => p.Name) 15 | .NotNull() 16 | .NotEmpty() 17 | .WithMessage("Name is required."); 18 | 19 | RuleFor(p => p.Value) 20 | .NotNull() 21 | .NotEmpty() 22 | .When(p => p.Type is RuleType.CorrelationId or RuleType.Sql) 23 | .WithMessage($"When type is set to {RuleType.Sql} or " + 24 | $"{RuleType.CorrelationId} the value must be set."); 25 | } 26 | 27 | public Func>> ValidateValue => 28 | async (model, propertyName) => 29 | { 30 | var result = await ValidateAsync( 31 | ValidationContext.CreateWithOptions( 32 | (RuleFormModel)model, 33 | x => x.IncludeProperties(propertyName.SplitAndGetLastSection('.')))); 34 | 35 | if (result.IsValid) 36 | return Array.Empty(); 37 | 38 | return result.Errors.Select(e => e.ErrorMessage); 39 | }; 40 | } -------------------------------------------------------------------------------- /src/Ui/Website.Host/DefaultSettingsService.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json; 2 | using CrossBusExplorer.Website; 3 | using CrossBusExplorer.Website.Models; 4 | using ElectronSharp.API; 5 | using ElectronSharp.API.Entities; 6 | namespace Website.Host; 7 | 8 | public class DefaultSettingsService : ISettingsService 9 | { 10 | private const string FileName = "settings.json"; 11 | 12 | public async Task GetAsync(CancellationToken cancellationToken) 13 | { 14 | var filePath = await FilePath(cancellationToken); 15 | 16 | if (File.Exists(filePath)) 17 | { 18 | var fileContent = await File.ReadAllTextAsync(filePath, cancellationToken); 19 | return JsonSerializer.Deserialize(fileContent); 20 | } 21 | 22 | return new UserSettings(); 23 | } 24 | 25 | 26 | public async Task SaveAsync(UserSettings userSettings, CancellationToken cancellationToken) 27 | { 28 | await File.WriteAllTextAsync( 29 | await FilePath(cancellationToken), 30 | JsonSerializer.Serialize(userSettings), 31 | cancellationToken); 32 | } 33 | 34 | private async Task FilePath(CancellationToken cancellationToken) 35 | { 36 | var path = HybridSupport.IsElectronActive 37 | ? await Electron.App.GetPathAsync(PathName.UserData, cancellationToken) : 38 | Directory.GetCurrentDirectory(); 39 | 40 | return Path.Combine( 41 | path, 42 | FileName); 43 | } 44 | } -------------------------------------------------------------------------------- /src/Core/ServiceBus.Contracts/IMessageService.cs: -------------------------------------------------------------------------------- 1 | using CrossBusExplorer.ServiceBus.Contracts.Types; 2 | namespace CrossBusExplorer.ServiceBus.Contracts; 3 | 4 | public interface IMessageService 5 | { 6 | Task> GetMessagesAsync( 7 | string connectionName, 8 | string queueOrTopicName, 9 | string? subscriptionName, 10 | SubQueue subQueue, 11 | ReceiveMode mode, 12 | ReceiveType type, 13 | int? messagesCount, 14 | long? fromSequenceNumber, 15 | CancellationToken cancellationToken); 16 | 17 | Task SendMessagesAsync( 18 | string connectionName, 19 | string queueOrTopicName, 20 | IReadOnlyList messages, 21 | CancellationToken cancellationToken); 22 | 23 | IAsyncEnumerable PurgeAsync( 24 | string connectionName, 25 | string topicOrQueueName, 26 | string? subscriptionName, 27 | SubQueue subQueue, 28 | long totalCount, 29 | CancellationToken cancellationToken); 30 | 31 | IAsyncEnumerable ResendAsync(string connectionName, 32 | string topicOrQueueName, 33 | string? subscriptionName, 34 | SubQueue subQueue, 35 | string destinationTopicOrQueueName, 36 | long totalCount, 37 | CancellationToken cancellationToken); 38 | 39 | Task DeleteMessage( 40 | string connectionName, 41 | string queueOrTopicName, 42 | string? subscriptionName, 43 | SubQueue subQueue, 44 | long sequenceNumber, 45 | CancellationToken cancellationToken); 46 | } 47 | -------------------------------------------------------------------------------- /src/Ui/Website.Host/Program.cs: -------------------------------------------------------------------------------- 1 | using CrossBusExplorer.Management; 2 | using CrossBusExplorer.ServiceBus; 3 | using CrossBusExplorer.Website; 4 | using ElectronSharp.API; 5 | using ElectronSharp.API.Entities; 6 | using Microsoft.AspNetCore.Hosting.StaticWebAssets; 7 | using Website.Host; 8 | 9 | var builder = WebApplication.CreateBuilder(args); 10 | 11 | StaticWebAssetsLoader.UseStaticWebAssets(builder.Environment, builder.Configuration); 12 | 13 | builder.Services.AddRazorPages(); 14 | builder.Services.AddServerSideBlazor(); 15 | builder.Services.AddWebsiteServices(); 16 | builder.Services.AddServiceBusServices(); 17 | builder.Services.AddManagement(); 18 | 19 | builder.Services.AddScoped(); 20 | builder.Services.AddScoped(); 21 | 22 | builder.Services.AddElectron(); 23 | builder.WebHost.UseElectron(args); 24 | 25 | var app = builder.Build(); 26 | 27 | app.UseStaticFiles(); 28 | 29 | app.UseRouting(); 30 | 31 | app.MapBlazorHub(); 32 | app.MapFallbackToPage("/_Host"); 33 | 34 | Task.Run(async () => 35 | { 36 | Electron.ReadAuth(); 37 | await Task.Delay(500); 38 | 39 | var browserWindow = await Electron.WindowManager.CreateWindowAsync( 40 | new BrowserWindowOptions 41 | { 42 | ZoomToPageWidth = true, 43 | WebPreferences = new WebPreferences 44 | { 45 | ZoomFactor = 1 46 | }, 47 | Icon = "../../../icon512x512.png" 48 | }); 49 | 50 | browserWindow.OnClose += () => app.StopAsync(); 51 | browserWindow.OnReadyToShow += () => browserWindow.Show(); 52 | }); 53 | 54 | app.Run(); -------------------------------------------------------------------------------- /src/Ui/Website/WellKnown.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using CrossBusExplorer.Website.Exceptions; 3 | using MudBlazor; 4 | namespace CrossBusExplorer.Website; 5 | 6 | internal static class WellKnown 7 | { 8 | internal static int ProgressCompleted = 100; 9 | internal static string DefaultConfirmSuccessResult => "confirm"; 10 | 11 | internal static class Regex 12 | { 13 | public static string TimeSpan = @"\d+\.((0?\d)|(1\d)|(2[0-3]))(:[0-5]\d){2}"; 14 | } 15 | 16 | internal static class Format 17 | { 18 | public static string DateFormat = "dd.MM.yyyy hh:MM:ss"; 19 | } 20 | 21 | public static class Converters 22 | { 23 | public static Converter TimeSpanConverter = new Converter 24 | { 25 | SetFunc = value => value?.ToString(@"dddd\.hh\:mm\:ss"), 26 | GetFunc = text => 27 | { 28 | if (TimeSpan.TryParse(text, out TimeSpan value)) 29 | { 30 | return value; 31 | } 32 | 33 | return null; 34 | } 35 | }; 36 | 37 | public static Converter DateTimeOffsetConverter = 38 | new Converter 39 | { 40 | SetFunc = value => value?.ToString(Format.DateFormat), 41 | GetFunc = text => 42 | { 43 | if (DateTimeOffset.TryParse(text, out DateTimeOffset value)) 44 | { 45 | return value; 46 | } 47 | 48 | return null; 49 | } 50 | }; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/Ui/Website.Host/Pages/Error.cshtml: -------------------------------------------------------------------------------- 1 | @page 2 | @model Website.Host.Pages.ErrorModel 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | Error 11 | 12 | 13 | 14 | 15 | 16 |
17 |
18 |

Error.

19 |

An error occurred while processing your request.

20 | 21 | @if (Model.ShowRequestId) 22 | { 23 |

24 | Request ID: @Model.RequestId 25 |

26 | } 27 | 28 |

Development Mode

29 |

30 | Swapping to the Development environment displays detailed information about the error that occurred. 31 |

32 |

33 | The Development environment shouldn't be enabled for deployed applications. 34 | It can result in displaying sensitive information from exceptions to end users. 35 | For local debugging, enable the Development environment by setting the ASPNETCORE_ENVIRONMENT environment variable to Development 36 | and restarting the app. 37 |

38 |
39 |
40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /src/Api/Host/Mutations/MessagingMutationExtensions.cs: -------------------------------------------------------------------------------- 1 | using Azure.Messaging.ServiceBus; 2 | using CrossBusExplorer.ServiceBus.Contracts; 3 | using CrossBusExplorer.ServiceBus.Contracts.Types; 4 | using SubQueue = CrossBusExplorer.ServiceBus.Contracts.Types.SubQueue; 5 | namespace CrossBusExplorer.Host.Mutations; 6 | 7 | [ExtendObjectType("Mutation")] 8 | public class MessagingMutationExtensions 9 | { 10 | [Error] 11 | [UseMutationConvention(PayloadFieldName = "result")] 12 | public IAsyncEnumerable PurgeAsync( 13 | [Service] IMessageService messageService, 14 | string connectionName, 15 | string queueOrTopicName, 16 | string? subscriptionName, 17 | SubQueue subQueue, 18 | long totalCount, 19 | CancellationToken cancellationToken) 20 | { 21 | return messageService.PurgeAsync( 22 | connectionName, 23 | queueOrTopicName, 24 | subscriptionName, 25 | subQueue, 26 | totalCount, 27 | cancellationToken); 28 | } 29 | 30 | [Error] 31 | [Error] 32 | [UseMutationConvention(PayloadFieldName = "result")] 33 | public async Task SendMessagesAsync( 34 | [Service] IMessageService messageService, 35 | string connectionName, 36 | string queueOrTopicName, 37 | IReadOnlyList messages, 38 | CancellationToken cancellationToken) 39 | { 40 | return await messageService.SendMessagesAsync( 41 | connectionName, 42 | queueOrTopicName, 43 | messages, 44 | cancellationToken); 45 | } 46 | } -------------------------------------------------------------------------------- /src/Ui/Website/Extensions/StringExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using Newtonsoft.Json; 4 | namespace CrossBusExplorer.Website.Extensions; 5 | 6 | public static class StringExtensions 7 | { 8 | public static bool EqualsInvariantIgnoreCase(this string? value, string value2) 9 | { 10 | if (value == null && value2 == null) 11 | { 12 | return true; 13 | } 14 | 15 | if (value == null && value2 != null) 16 | { 17 | return false; 18 | } 19 | 20 | return value.Equals(value2, StringComparison.InvariantCultureIgnoreCase); 21 | } 22 | 23 | 24 | public static TimeSpan? ToTimeSpan(this string? value) 25 | { 26 | return value != null ? TimeSpan.Parse(value) : null; 27 | } 28 | 29 | public static string TryFormatBody(this string value, string? contentType) 30 | { 31 | if (contentType != null) 32 | { 33 | return contentType.Contains("json", StringComparison.InvariantCultureIgnoreCase) 34 | ? TryFormatJson(value) 35 | : value; 36 | } 37 | 38 | return TryFormatJson(value); 39 | } 40 | 41 | public static string SplitAndGetLastSection(this string value, char split) 42 | { 43 | if (value.Contains(split)) 44 | { 45 | return value.Split(split).Last(); 46 | } 47 | 48 | return value; 49 | } 50 | 51 | private static string TryFormatJson(string value) 52 | { 53 | try 54 | { 55 | dynamic parsedJson = JsonConvert.DeserializeObject(value); 56 | return JsonConvert.SerializeObject(parsedJson, Formatting.Indented); 57 | } 58 | catch 59 | { 60 | return value; 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/Ui/Website/ViewModels/ISubscriptionViewModel.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | using CrossBusExplorer.ServiceBus.Contracts.Types; 5 | using CrossBusExplorer.Website.Models; 6 | namespace CrossBusExplorer.Website.ViewModels; 7 | 8 | public interface ISubscriptionViewModel : INotifyPropertyChanged 9 | { 10 | event SubscriptionOperationEventHandler? OnSubscriptionOperation; 11 | SubscriptionFormModel? Form { get; } 12 | SubscriptionDetails? SubscriptionDetails { get; } 13 | Task InitializeFormAsync( 14 | string connectionName, 15 | string topicName, 16 | string? subscriptionName, 17 | CancellationToken cancellationToken); 18 | Task SaveSubscriptionFormAsync(string connectionName); 19 | void NavigateToNewSubscriptionForm(string connectionName, string topicName); 20 | Task CloneSubscriptionAsync( 21 | string connectionName, 22 | string sourceTopicName, 23 | string sourceSubscriptionName, 24 | CancellationToken cancellationToken); 25 | Task DeleteSubscriptionAsync( 26 | string connectionName, 27 | string topicName, 28 | string subscriptionName, 29 | CancellationToken cancellationToken); 30 | Task UpdateSubscriptionStatusAsync( 31 | string connectionName, 32 | string topicName, 33 | string subscriptionName, 34 | ServiceBusEntityStatus status, 35 | CancellationToken cancellationToken); 36 | Task PurgeMessagesAsync( 37 | string connectionName, 38 | string topicName, 39 | string subscriptionName, 40 | CancellationToken cancellationToken); 41 | Task ResendDeadLettersAsync( 42 | string connectionName, 43 | string topicName, 44 | string subscriptionName, 45 | CancellationToken cancellationToken); 46 | } -------------------------------------------------------------------------------- /src/Ui/Website.Host/ManagementStorage.cs: -------------------------------------------------------------------------------- 1 | using System.Text; 2 | using System.Text.Json; 3 | using CrossBusExplorer.Management; 4 | using CrossBusExplorer.Management.Contracts; 5 | using ElectronSharp.API; 6 | using ElectronSharp.API.Entities; 7 | namespace Website.Host; 8 | 9 | public class ManagementStorage : IManagementStorage 10 | { 11 | private const string ServiceBusConnectionsFileName = "cross_bus_explorer_connections.json"; 12 | 13 | public async Task StoreAsync( 14 | IDictionary connections, 15 | CancellationToken cancellationToken) 16 | { 17 | await File.WriteAllTextAsync( 18 | await FilePath(cancellationToken), 19 | JsonSerializer.Serialize(connections), 20 | Encoding.UTF8, 21 | cancellationToken); 22 | } 23 | 24 | public async Task> ReadAsync( 25 | CancellationToken cancellationToken) 26 | { 27 | var path = await FilePath(cancellationToken); 28 | 29 | if (File.Exists(Path.Combine(path))) 30 | { 31 | var serializedData = await File.ReadAllTextAsync(path, cancellationToken); 32 | 33 | return JsonSerializer.Deserialize>( 34 | serializedData) ?? new Dictionary(); 35 | } 36 | 37 | return new Dictionary(); 38 | } 39 | 40 | private async Task FilePath(CancellationToken cancellationToken) 41 | { 42 | var path = HybridSupport.IsElectronActive 43 | ? await Electron.App.GetPathAsync(PathName.UserData, cancellationToken) : 44 | Directory.GetCurrentDirectory(); 45 | 46 | return Path.Combine( 47 | path, 48 | ServiceBusConnectionsFileName); 49 | } 50 | } -------------------------------------------------------------------------------- /src/Ui/Website/ViewModels/IConnectionsViewModel.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.ObjectModel; 3 | using System.ComponentModel; 4 | using System.Threading; 5 | using System.Threading.Tasks; 6 | using CrossBusExplorer.Website.Models; 7 | using CrossBusExplorer.Website.ViewModels; 8 | using Microsoft.AspNetCore.Components.Forms; 9 | namespace CrossBusExplorer.Website; 10 | 11 | public interface IConnectionsViewModel : INotifyPropertyChanged 12 | { 13 | event SettingsChangedEventHandler? OnSettingsChanged; 14 | public SaveConnectionForm? SaveConnectionForm { get; } 15 | bool SaveDialogVisible { get; set; } 16 | ObservableCollection Folders { get; } 17 | ObservableCollection ServiceBusConnections { get; } 18 | Task InitializeAsync(CancellationToken cancellationToken); 19 | void OpenSaveDialog(ServiceBusConnectionWithFolder? model = null); 20 | Task OpenDeleteDialog(ServiceBusConnectionWithFolder item); 21 | void ViewConnectionString(string connectionString); 22 | Task OnValidSaveConnectionSubmit(); 23 | Task SubmitSaveConnectionForm(EditForm editForm); 24 | Task UpdateConnectionPosition(ServiceBusConnectionWithFolder serviceBusConnection, 25 | int index, 26 | string newFolder, CancellationToken cancellationToken); 27 | Task UpdateFolderPositionAsync( 28 | FolderSettings folder, 29 | DirectionChange up, 30 | CancellationToken cancellationToken); 31 | Task OpenNewFolderDialogAsync(CancellationToken cancellationToken); 32 | Task OpenEditFolderDialogAsync( 33 | FolderSettings folderSettings, 34 | Action successCallback, 35 | CancellationToken cancellationToken); 36 | Task OpenDeleteFolderDialogAsync( 37 | FolderSettings folderSettings, 38 | Action successCallback, 39 | CancellationToken cancellationToken); 40 | } -------------------------------------------------------------------------------- /src/Ui/Website/Models/ConnectionMenuItem.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.ObjectModel; 2 | using System.ComponentModel; 3 | using CrossBusExplorer.ServiceBus.Contracts.Types; 4 | using CrossBusExplorer.Website.Extensions; 5 | namespace CrossBusExplorer.Website.Models; 6 | 7 | public class ConnectionMenuItem : INotifyPropertyChanged 8 | { 9 | public event PropertyChangedEventHandler? PropertyChanged; 10 | 11 | public ConnectionMenuItem(string connectionName) 12 | { 13 | ConnectionName = connectionName; 14 | Queues = new ObservableCollection(); 15 | Topics = new ObservableCollection(); 16 | } 17 | 18 | public string ConnectionName { get; } 19 | 20 | private ObservableCollection _queues; 21 | public ObservableCollection Queues 22 | { 23 | get => _queues; 24 | private set 25 | { 26 | _queues = value; 27 | _queues.CollectionChanged += (_, _) => 28 | { 29 | this.Notify(PropertyChanged); 30 | }; 31 | this.Notify(PropertyChanged); 32 | } 33 | } 34 | 35 | private ObservableCollection _topics; 36 | public ObservableCollection Topics 37 | { 38 | get => _topics; 39 | private set 40 | { 41 | _topics = value; 42 | _topics.CollectionChanged += (_, _) => 43 | { 44 | this.Notify(PropertyChanged); 45 | }; 46 | this.Notify(PropertyChanged); 47 | } 48 | } 49 | public bool QueuesLoaded { get; set; } 50 | public bool TopicsLoaded { get; set; } 51 | public bool LoadingQueues { get; set; } 52 | public bool LoadingTopics { get; set; } 53 | public bool QueuesExpanded { get; set; } 54 | public bool TopicsExpanded { get; set; } 55 | } -------------------------------------------------------------------------------- /src/Ui/Website/Models/SaveConnectionForm.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel; 2 | using System.ComponentModel.DataAnnotations; 3 | using CrossBusExplorer.Management; 4 | using CrossBusExplorer.Management.Contracts; 5 | using CrossBusExplorer.Website.Extensions; 6 | using MudBlazor; 7 | namespace CrossBusExplorer.Website.Models; 8 | 9 | public class SaveConnectionForm : INotifyPropertyChanged 10 | { 11 | public event PropertyChangedEventHandler? PropertyChanged; 12 | 13 | private string? _connectionString; 14 | 15 | [Label("Connection string")] 16 | [Required(ErrorMessage = "ConnectionString is required")] 17 | [ConnectionStringValidation(ErrorMessage = "ConnectionString has invalid format!")] 18 | public string? ConnectionString 19 | { 20 | get => _connectionString; 21 | set 22 | { 23 | _connectionString = value; 24 | 25 | if (string.IsNullOrEmpty(Name)) 26 | { 27 | Name = ServiceBusConnectionStringHelper.TryGetNameFromConnectionString( 28 | _connectionString); 29 | } 30 | 31 | this.Notify(PropertyChanged); 32 | } 33 | } 34 | 35 | private string? _name; 36 | [Label("Name")] 37 | public string? Name 38 | { 39 | get => _name; 40 | set 41 | { 42 | _name = value; 43 | this.Notify(PropertyChanged); 44 | } 45 | } 46 | 47 | private string? _folder; 48 | [Label("Folder")] 49 | public string? Folder 50 | { 51 | get => _folder; 52 | set 53 | { 54 | _folder = value; 55 | this.Notify(PropertyChanged); 56 | } 57 | } 58 | 59 | private ServiceBusTransportType _transportType = ServiceBusTransportType.AmqpTcp; 60 | [Label("Transport Type")] 61 | public ServiceBusTransportType TransportType 62 | { 63 | get => _transportType; 64 | set 65 | { 66 | _transportType = value; 67 | this.Notify(PropertyChanged); 68 | } 69 | } 70 | } -------------------------------------------------------------------------------- /src/Core/Management/ServiceBusConnectionStringHelper.cs: -------------------------------------------------------------------------------- 1 | using Azure.Messaging.ServiceBus; 2 | using CrossBusExplorer.Management.Contracts; 3 | using ServiceBusTransportType = CrossBusExplorer.Management.Contracts.ServiceBusTransportType; 4 | namespace CrossBusExplorer.Management; 5 | 6 | public static class ServiceBusConnectionStringHelper 7 | { 8 | public static string? TryGetNameFromConnectionString(string connectionString) 9 | { 10 | try 11 | { 12 | var connectionStringProperties = 13 | ServiceBusConnectionStringProperties.Parse(connectionString); 14 | 15 | if (connectionStringProperties.FullyQualifiedNamespace.IndexOf('.') != -1) 16 | { 17 | return connectionStringProperties.FullyQualifiedNamespace.Split('.')[0]; 18 | } 19 | 20 | return connectionStringProperties.FullyQualifiedNamespace; 21 | } 22 | catch 23 | { 24 | return null; 25 | } 26 | } 27 | 28 | public static bool IsValid(string? connectionString) 29 | { 30 | try 31 | { 32 | ServiceBusConnectionStringProperties.Parse(connectionString); 33 | 34 | return true; 35 | } 36 | catch 37 | { 38 | return false; 39 | } 40 | } 41 | public static ServiceBusConnection GetServiceBusConnection( 42 | string name, 43 | string connectionString, 44 | ServiceBusTransportType transportType = ServiceBusTransportType.AmqpTcp) 45 | { 46 | var properties = 47 | ServiceBusConnectionStringProperties.Parse(connectionString); 48 | 49 | return new ServiceBusConnection( 50 | name, 51 | connectionString, 52 | properties.Endpoint, 53 | properties.FullyQualifiedNamespace, 54 | properties.EntityPath, 55 | properties.SharedAccessKey, 56 | properties.SharedAccessSignature, 57 | properties.SharedAccessKeyName, 58 | transportType); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/Ui/Website/ViewModels/JobsViewModel.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.ObjectModel; 2 | using System.ComponentModel; 3 | using System.Threading.Tasks; 4 | using CrossBusExplorer.Website.Extensions; 5 | using CrossBusExplorer.Website.Jobs; 6 | using MudBlazor; 7 | namespace CrossBusExplorer.Website.ViewModels; 8 | 9 | public class JobsViewModel : IJobsViewModel 10 | { 11 | private readonly ISnackbar _snackbar; 12 | public event PropertyChangedEventHandler? PropertyChanged; 13 | 14 | public JobsViewModel(ISnackbar snackbar) 15 | { 16 | _snackbar = snackbar; 17 | Jobs = new ObservableCollection(); 18 | } 19 | 20 | private ObservableCollection _jobs; 21 | public ObservableCollection Jobs 22 | { 23 | get => _jobs; 24 | set 25 | { 26 | _jobs = value; 27 | _jobs.CollectionChanged += (_, _) => this.Notify(PropertyChanged); 28 | } 29 | } 30 | public async Task ScheduleJob(IJob job) 31 | { 32 | job.PropertyChanged += (_, _) => HandleJobUpdate(job); 33 | Jobs.Add(job); 34 | await job.ExecuteAsync(); 35 | } 36 | 37 | private void HandleJobUpdate(IJob job) 38 | { 39 | if (job.Status != JobStatus.Running) 40 | { 41 | job.PropertyChanged -= (_, _) => HandleJobUpdate(job); 42 | _jobs.Remove(job); 43 | 44 | if (job.Status == JobStatus.Succeeded) 45 | { 46 | _snackbar.Add($"Job {job.Name} completed.", Severity.Success); 47 | } 48 | else if (job.Status == JobStatus.Failed) 49 | { 50 | _snackbar.Add( 51 | job.ErrorMessage ?? $"Error while executing job {job.Name}", 52 | Severity.Error); 53 | } 54 | else if (job.Status == JobStatus.Cancelled) 55 | { 56 | _snackbar.Add($"Job {job.Name} was cancelled.", Severity.Warning); 57 | } 58 | } 59 | 60 | this.Notify(PropertyChanged); 61 | } 62 | public void CancelJob(IJob job) 63 | { 64 | job.Cancel(); 65 | } 66 | } -------------------------------------------------------------------------------- /src/Ui/Website/Models/FolderSettings.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using CrossBusExplorer.Management.Contracts; 4 | using CrossBusExplorer.Website.Extensions; 5 | namespace CrossBusExplorer.Website.Models; 6 | 7 | public class FolderSettings 8 | { 9 | public FolderSettings( 10 | string name, 11 | int index, 12 | List? serviceBusConnectionSettings) 13 | { 14 | Name = name; 15 | Index = index; 16 | ServiceBusConnectionSettings = 17 | serviceBusConnectionSettings ?? new List(); 18 | } 19 | 20 | public string Name { get; private set; } 21 | public int Index { get; private set; } 22 | public List ServiceBusConnectionSettings { get;} 23 | 24 | public void UpdateName(string newName) 25 | { 26 | Name = newName; 27 | } 28 | 29 | public void UpdateIndex(int newIndex) 30 | { 31 | Index = newIndex; 32 | } 33 | 34 | public void AddServiceBusConnectionSetting( 35 | ServiceBusConnection serviceBusConnection) 36 | { 37 | var index = ServiceBusConnectionSettings.MaxBy(p => p.Index)?.Index ?? 0; 38 | 39 | ServiceBusConnectionSettings.Add( 40 | new ServiceBusConnectionSettings( 41 | serviceBusConnection.Name, 42 | index + 1)); 43 | } 44 | 45 | public void UpdateServiceBusConnectionsIndexes(string serviceBusConnectionName, int index) 46 | { 47 | var connectionByName = ServiceBusConnectionSettings 48 | .First(p => p.Name.EqualsInvariantIgnoreCase(serviceBusConnectionName)); 49 | 50 | connectionByName.UpdateIndex(index); 51 | 52 | foreach (ServiceBusConnectionSettings connection in ServiceBusConnectionSettings 53 | .Where(p => !p.Name.EqualsInvariantIgnoreCase(serviceBusConnectionName) && 54 | p.Index >= index) 55 | .OrderBy(p=>p.Index)) 56 | { 57 | connection.UpdateIndex(connection.Index + 1); 58 | } 59 | } 60 | public bool IsDefault() => string.IsNullOrEmpty(Name); 61 | } -------------------------------------------------------------------------------- /src/Ui/Website/ServiceCollectionExtensions.cs: -------------------------------------------------------------------------------- 1 | using CrossBusExplorer.Website.ViewModels; 2 | using Microsoft.Extensions.DependencyInjection; 3 | using MudBlazor; 4 | using MudBlazor.Services; 5 | namespace CrossBusExplorer.Website; 6 | 7 | public static class ServiceCollectionExtensions 8 | { 9 | public static IServiceCollection AddWebsiteServices(this IServiceCollection collection) 10 | { 11 | collection.AddScoped(); 12 | collection.AddScoped(); 13 | collection.AddScoped(); 14 | collection.AddScoped(); 15 | collection.AddScoped(); 16 | collection.AddScoped(); 17 | collection.AddScoped(); 18 | collection.AddScoped(); 19 | collection.AddCustomTheme(); 20 | 21 | return collection.AddMudServices(config => 22 | { 23 | config.SnackbarConfiguration.PositionClass = Defaults.Classes.Position.BottomRight; 24 | 25 | config.SnackbarConfiguration.PreventDuplicates = false; 26 | config.SnackbarConfiguration.NewestOnTop = false; 27 | config.SnackbarConfiguration.ShowCloseIcon = true; 28 | config.SnackbarConfiguration.VisibleStateDuration = 10000; 29 | config.SnackbarConfiguration.HideTransitionDuration = 500; 30 | config.SnackbarConfiguration.ShowTransitionDuration = 500; 31 | config.SnackbarConfiguration.SnackbarVariant = Variant.Filled; 32 | }); 33 | } 34 | 35 | private static IServiceCollection AddCustomTheme(this IServiceCollection collection) 36 | { 37 | collection.AddSingleton(new MudTheme() 38 | { 39 | Palette = new Palette 40 | { 41 | Secondary = Colors.Blue.Darken2 42 | }, 43 | PaletteDark = new PaletteDark 44 | { 45 | Secondary = Colors.Grey.Darken4 46 | } 47 | }); 48 | 49 | return collection; 50 | } 51 | } -------------------------------------------------------------------------------- /src/Api/Host/Mutations/QueueMutationExtensions.cs: -------------------------------------------------------------------------------- 1 | using Azure.Messaging.ServiceBus; 2 | using CrossBusExplorer.ServiceBus.Contracts; 3 | using CrossBusExplorer.ServiceBus.Contracts.Types; 4 | namespace CrossBusExplorer.Host.Mutations; 5 | 6 | [ExtendObjectType("Mutation")] 7 | public class QueueMutationExtensions 8 | { 9 | [UseMutationConvention(PayloadFieldName = "result")] 10 | [Error] 11 | public async Task DeleteQueueAsync( 12 | [Service] IQueueService queueService, 13 | string connectionName, 14 | string name, 15 | CancellationToken cancellationToken) 16 | { 17 | return await queueService.DeleteAsync(connectionName, name, cancellationToken); 18 | } 19 | 20 | [UseMutationConvention(PayloadFieldName = "result")] 21 | [Error] 22 | public async Task> UpdateQueueAsync( 23 | [Service] IQueueService queueService, 24 | string connectionName, 25 | UpdateQueueOptions options, 26 | CancellationToken cancellationToken) 27 | { 28 | return await queueService.UpdateAsync(connectionName, options, cancellationToken); 29 | } 30 | 31 | [UseMutationConvention(PayloadFieldName = "result")] 32 | [Error] 33 | [Error] 34 | public async Task> CreateQueueAsync( 35 | [Service] IQueueService queueService, 36 | string connectionName, 37 | CreateQueueOptions options, 38 | CancellationToken cancellationToken) 39 | { 40 | return await queueService.CreateAsync(connectionName, options, cancellationToken); 41 | } 42 | 43 | [UseMutationConvention(PayloadFieldName = "result")] 44 | [Error] 45 | public async Task> CloneQueueAsync( 46 | [Service] IQueueService queueService, 47 | string connectionName, 48 | string name, 49 | string sourceName, 50 | CancellationToken cancellationToken) 51 | { 52 | return await queueService.CloneAsync(connectionName, name, sourceName, cancellationToken); 53 | } 54 | } -------------------------------------------------------------------------------- /src/Ui/Website/Models/ReceiveMessagesForm.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel; 2 | using System.ComponentModel.DataAnnotations; 3 | using CrossBusExplorer.ServiceBus.Contracts.Types; 4 | using CrossBusExplorer.Website.Extensions; 5 | using MudBlazor; 6 | namespace CrossBusExplorer.Website.Models; 7 | 8 | public class ReceiveMessagesForm : INotifyPropertyChanged 9 | { 10 | public event PropertyChangedEventHandler? PropertyChanged; 11 | 12 | private SubQueue _subQueue; 13 | 14 | [Label("Queue type")] 15 | public SubQueue SubQueue 16 | { 17 | get => _subQueue; 18 | set 19 | { 20 | _subQueue = value; 21 | this.Notify(PropertyChanged); 22 | } 23 | } 24 | 25 | private ReceiveMode _mode; 26 | 27 | [Label("Receive type")] 28 | public ReceiveMode Mode 29 | { 30 | get => _mode; 31 | set 32 | { 33 | _mode = value; 34 | 35 | if (_mode == ReceiveMode.ReceiveAndDelete && _mode != value) 36 | { 37 | FromSequenceNumber = null; 38 | } 39 | 40 | this.Notify(PropertyChanged); 41 | } 42 | } 43 | 44 | private ReceiveType _type; 45 | 46 | [Label("Receive type")] 47 | public ReceiveType Type 48 | { 49 | get => _type; 50 | set 51 | { 52 | if (value == ReceiveType.All && value != _type) 53 | { 54 | MessagesCount = null; 55 | } 56 | else 57 | { 58 | MessagesCount = 10; 59 | } 60 | 61 | _type = value; 62 | 63 | this.Notify(PropertyChanged); 64 | } 65 | } 66 | 67 | private int? _messagesCount; 68 | 69 | public int? MessagesCount 70 | { 71 | get => _messagesCount; 72 | set 73 | { 74 | _messagesCount = value; 75 | this.Notify(PropertyChanged); 76 | } 77 | } 78 | 79 | private long? _fromSequenceNumber; 80 | 81 | public long? FromSequenceNumber 82 | { 83 | get => _fromSequenceNumber; 84 | set 85 | { 86 | _fromSequenceNumber = value; 87 | this.Notify(PropertyChanged); 88 | } 89 | } 90 | } -------------------------------------------------------------------------------- /src/Ui/Website/Shared/Messages/PurgeMessagesDialog.razor: -------------------------------------------------------------------------------- 1 | @using CrossBusExplorer.ServiceBus.Contracts.Types 2 | 3 | 4 | 5 | Select which sub queue to purge 6 | 7 | 8 | 9 | 10 | 11 | 13 | 16 | Active messages 17 | 18 | 21 | Dead letter 22 | 23 | 26 | Transfer dead letter 27 | 28 | 29 | 30 | 31 | 32 | 33 | 37 | Close 38 | 39 | 43 | Purge 44 | 45 | 46 | 47 | 48 | @code { 49 | [CascadingParameter] 50 | MudDialogInstance MudDialog { get; set; } 51 | 52 | private SubQueue _subQueue; 53 | 54 | private void CloseDialog() 55 | { 56 | MudDialog.Close(DialogResult.Cancel()); 57 | } 58 | 59 | private void Submit() 60 | { 61 | MudDialog.Close(DialogResult.Ok(_subQueue)); 62 | } 63 | } -------------------------------------------------------------------------------- /src/Ui/Website/Mappings/MessageMappings.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Collections.ObjectModel; 4 | using System.Linq; 5 | using CrossBusExplorer.ServiceBus.Contracts.Types; 6 | using CrossBusExplorer.ServiceBus.Extensions; 7 | using CrossBusExplorer.Website.Extensions; 8 | using CrossBusExplorer.Website.Models; 9 | namespace CrossBusExplorer.Website.Mappings; 10 | 11 | public static class MessageMappings 12 | { 13 | public static SendMessage ToSendMessage(this MessageDetailsModel message) => 14 | new SendMessage( 15 | message.Body.TryFormatBody(message.ContentType), 16 | message.Subject, 17 | message.To, 18 | message.ContentType, 19 | message.CorrelationId, 20 | message.Id, 21 | message.PartitionKey, 22 | message.ReplyTo, 23 | message.SessionId, 24 | message.ScheduledEnqueueTime, 25 | message.TimeToLive, 26 | message.ApplicationProperties 27 | .ToDictionary(p => p.Key, p => p.Value.GetApplicationPropertyValue(p.Type))); 28 | 29 | public static MessageDetailsModel ToSendMessageModel(this Message message) => 30 | new MessageDetailsModel 31 | { 32 | Body = message.Body.TryFormatBody(message.SystemProperties.ContentType), 33 | Subject = message.Subject, 34 | To = message.SystemProperties.To, 35 | ContentType = message.SystemProperties.ContentType, 36 | CorrelationId = message.SystemProperties.CorrelationId, 37 | Id = message.Id, 38 | PartitionKey = message.SystemProperties.PartitionKey, 39 | ReplyTo = message.SystemProperties.ReplyTo, 40 | SessionId = message.SystemProperties.SessionId, 41 | ScheduledEnqueueTime = 42 | message.SystemProperties.ScheduledEnqueueTime == DateTimeOffset.MinValue ? null 43 | : message.SystemProperties.ScheduledEnqueueTime, 44 | TimeToLive = message.SystemProperties.TimeToLive, 45 | ApplicationProperties = 46 | new ObservableCollection( 47 | message.ApplicationProperties?.Select(p => 48 | new KeyValueTypePair 49 | { 50 | Key = p.Key, 51 | Value = p.Value?.ToString() ?? null, 52 | Type = p.Value.GetApplicationPropertyType() 53 | }).ToList() ?? new List()) 54 | }; 55 | } 56 | -------------------------------------------------------------------------------- /src/Core/Management/ConnectionManagement.cs: -------------------------------------------------------------------------------- 1 | using CrossBusExplorer.Management.Contracts; 2 | namespace CrossBusExplorer.Management; 3 | 4 | public class ConnectionManagement : IConnectionManagement 5 | { 6 | private readonly IManagementStorage _managementStorage; 7 | 8 | public ConnectionManagement(IManagementStorage managementStorage) 9 | { 10 | _managementStorage = managementStorage; 11 | } 12 | 13 | public async Task> GetAsync( 14 | CancellationToken cancellationToken) 15 | { 16 | return (await _managementStorage.ReadAsync(cancellationToken)) 17 | .Select(p => p.Value) 18 | .ToList(); 19 | } 20 | 21 | public async Task GetAsync( 22 | string name, 23 | CancellationToken cancellationToken) 24 | { 25 | var connections = await _managementStorage.ReadAsync(cancellationToken); 26 | 27 | if (connections.ContainsKey(name)) 28 | { 29 | return connections[name]; 30 | } 31 | 32 | throw new ServiceBusConnectionDoesntExist(name); 33 | } 34 | 35 | public async Task SaveAsync( 36 | string name, 37 | string connectionString, 38 | string folder, 39 | ServiceBusTransportType transportType, 40 | CancellationToken cancellationToken) 41 | { 42 | IDictionary connections = 43 | await _managementStorage.ReadAsync(cancellationToken); 44 | 45 | var connection = ServiceBusConnectionStringHelper.GetServiceBusConnection( 46 | name, connectionString, transportType); 47 | 48 | //TODO: add to folder 49 | 50 | if (connections.ContainsKey(name)) 51 | { 52 | connections[name] = connection; 53 | } 54 | else if (connections.Any(p => p.Value.ConnectionString == connectionString)) 55 | { 56 | var keyToRemove = connections.First(p => p.Value.ConnectionString == connectionString) 57 | .Key; 58 | 59 | connections.Remove(keyToRemove); 60 | connections.Add(name, connection); 61 | } 62 | else 63 | { 64 | connections.Add(name, connection); 65 | } 66 | 67 | await _managementStorage.StoreAsync( 68 | connections, 69 | cancellationToken); 70 | 71 | return connection; 72 | } 73 | 74 | public async Task DeleteAsync(string name, CancellationToken cancellationToken) 75 | { 76 | var connections = await _managementStorage.ReadAsync(cancellationToken); 77 | 78 | if (connections.ContainsKey(name)) 79 | { 80 | connections.Remove(name); 81 | 82 | await _managementStorage.StoreAsync( 83 | connections, 84 | cancellationToken); 85 | } 86 | else 87 | { 88 | throw new ServiceBusConnectionDoesntExist(name); 89 | } 90 | } 91 | } -------------------------------------------------------------------------------- /src/Ui/Website/Mappings/TopicMappings.cs: -------------------------------------------------------------------------------- 1 | using CrossBusExplorer.ServiceBus.Contracts.Types; 2 | using CrossBusExplorer.Website.Models; 3 | using CrossBusExplorer.Website.Pages; 4 | namespace CrossBusExplorer.Website.Mappings; 5 | 6 | public static class TopicMappings 7 | { 8 | public static TopicFormModel ToFormModel(this TopicDetails details, OperationType operationType) 9 | { 10 | return new TopicFormModel(operationType) 11 | { 12 | Name = operationType == OperationType.Update ? details.Info.Name : null, 13 | MaxSizeInMegabytes = details.Properties.MaxQueueSizeInMegabytes, 14 | UserMetadata = details.Properties.UserMetadata, 15 | DuplicateDetectionHistoryTimeWindow = details.TimeSettings 16 | .DuplicateDetectionHistoryTimeWindow, 17 | AutoDeleteOnIdle = details.TimeSettings.AutoDeleteOnIdle, 18 | DefaultMessageTimeToLive = 19 | details.TimeSettings.DefaultMessageTimeToLive, 20 | EnableBatchedOperations = details.Settings.EnableBatchedOperations, 21 | RequiresDuplicateDetection = details.Settings.RequiresDuplicateDetection, 22 | EnablePartitioning = details.Settings.EnablePartitioning, 23 | SupportOrdering = details.Settings.SupportOrdering, 24 | MaxMessageSizeInKilobytes = details.Properties.MaxMessageSizeInKilobytes 25 | }; 26 | } 27 | 28 | public static UpdateTopicOptions ToUpdateOptions(this TopicFormModel model) 29 | { 30 | return new UpdateTopicOptions( 31 | model.Name!, 32 | AuthorizationRules: null, 33 | MaxSizeInMegabytes: model.MaxSizeInMegabytes, 34 | DefaultMessageTimeToLive: model.DefaultMessageTimeToLive, 35 | AutoDeleteOnIdle: model.AutoDeleteOnIdle, 36 | DuplicateDetectionHistoryTimeWindow: model.DuplicateDetectionHistoryTimeWindow, 37 | EnableBatchedOperations: model.EnableBatchedOperations, 38 | SupportOrdering: model.SupportOrdering, 39 | Status: null, 40 | MaxMessageSizeInKilobytes: model.MaxMessageSizeInKilobytes, 41 | UserMetadata: model.UserMetadata); 42 | } 43 | 44 | public static CreateTopicOptions ToCreateOptions(this TopicFormModel model) 45 | { 46 | return new CreateTopicOptions( 47 | model.Name!, 48 | AuthorizationRules: null, 49 | MaxSizeInMegabytes: model.MaxSizeInMegabytes, 50 | DefaultMessageTimeToLive: model.DefaultMessageTimeToLive, 51 | AutoDeleteOnIdle: model.AutoDeleteOnIdle, 52 | DuplicateDetectionHistoryTimeWindow: model.DuplicateDetectionHistoryTimeWindow, 53 | EnableBatchedOperations: model.EnableBatchedOperations, 54 | Status: null, 55 | MaxMessageSizeInKilobytes: model.MaxMessageSizeInKilobytes, 56 | UserMetadata: model.UserMetadata, 57 | RequiresDuplicateDetection: model.RequiresDuplicateDetection, 58 | SupportOrdering: model.SupportOrdering, 59 | EnablePartitioning: model.EnablePartitioning); 60 | } 61 | } -------------------------------------------------------------------------------- /src/Ui/Website/Shared/Messages/MessagesUploadDialog.razor: -------------------------------------------------------------------------------- 1 | @using CrossBusExplorer.ServiceBus.Contracts.Types 2 | @using System.IO 3 | @using CrossBusExplorer.Website.Models 4 | 5 | 6 | 7 | Upload messages from file 8 | 9 | Max 200 at once 10 | 11 | 12 | 13 | 14 | 16 | 19 | Body only 20 | 21 | 25 | Body with ApplicationProperties (not yet supported) 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 39 | Select multiple files 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 51 | Close 52 | 53 | 58 | Upload @_fileContents.Count files 59 | 60 | 61 | 62 | 63 | @code { 64 | [CascadingParameter] 65 | MudDialogInstance MudDialog { get; set; } 66 | 67 | IList _fileContents = new List(); 68 | 69 | private UploadFileType _uploadFileType; 70 | 71 | private void CloseDialog() 72 | { 73 | MudDialog.Close(DialogResult.Cancel()); 74 | } 75 | 76 | private void Submit() 77 | { 78 | MudDialog.Close(DialogResult.Ok(new MessagesUploadDialogResult(_uploadFileType, _fileContents))); 79 | } 80 | 81 | private async Task UploadFilesAsync(IReadOnlyList files) 82 | { 83 | _fileContents.Clear(); 84 | 85 | foreach (IBrowserFile file in files) 86 | { 87 | using var reader = new StreamReader(file.OpenReadStream()); 88 | var fileContent = await reader.ReadToEndAsync(default); 89 | 90 | _fileContents.Add(fileContent); 91 | } 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /src/Ui/Website/Shared/MenuItem.razor: -------------------------------------------------------------------------------- 1 | @using System.Web 2 | @using CrossBusExplorer.Website.Models 3 | 5 | 6 | 10 | @if (Value.LoadingQueues) 11 | { 12 | 13 | } 14 | else 15 | { 16 | 19 | Create new queue 20 | 21 | } 22 | 23 | @foreach (var queue in Value.Queues) 24 | { 25 | 28 | 29 | @queue.Name 30 | (@queue.ActiveMessagesCount, @queue.DeadLetterMessagesCount, @queue.InTransferMessagesCount, 31 | @queue.ScheduledMessagesCount) 32 | 33 | 34 | 35 | } 36 | 37 | 38 | 42 | 43 | @if (Value.LoadingTopics) 44 | { 45 | 46 | } 47 | else 48 | { 49 | 52 | Create new topic 53 | 54 | } 55 | @foreach (var topic in Value.Topics) 56 | { 57 | 58 | } 59 | 60 | 61 | 62 | @code { 63 | [Parameter] 64 | public ConnectionMenuItem Value { get; set; } 65 | [Inject] 66 | private INavigationViewModel Model { get; set; } 67 | 68 | protected override async Task OnInitializedAsync() 69 | { 70 | Model.PropertyChanged += (_, _) => 71 | { 72 | StateHasChanged(); 73 | }; 74 | 75 | Value.PropertyChanged += (_, _) => 76 | { 77 | StateHasChanged(); 78 | }; 79 | } 80 | 81 | private async Task OnQueuesExpandChange() 82 | { 83 | await Model.LoadQueues(Value, default); 84 | } 85 | 86 | private async Task OnTopicsExpanded() 87 | { 88 | await Model.LoadTopics(Value, default); 89 | } 90 | } -------------------------------------------------------------------------------- /src/Ui/Website/Jobs/PurgeMessagesJob.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.ComponentModel; 3 | using System.Threading; 4 | using System.Threading.Tasks; 5 | using CrossBusExplorer.ServiceBus.Contracts; 6 | using CrossBusExplorer.ServiceBus.Contracts.Types; 7 | using CrossBusExplorer.Website.Extensions; 8 | using MudBlazor; 9 | namespace CrossBusExplorer.Website.Jobs; 10 | 11 | public class PurgeMessagesJob : IJob 12 | { 13 | private readonly string _connectionName; 14 | private readonly string _queueOrTopicName; 15 | private readonly string? _subscriptionName; 16 | private readonly SubQueue _subQueue; 17 | private readonly long _totalCount; 18 | private readonly IMessageService _messageService; 19 | private readonly ISnackbar _snackbar; 20 | private readonly CancellationTokenSource _cancellationTokenSource; 21 | 22 | public event PropertyChangedEventHandler? PropertyChanged; 23 | 24 | public event JobCompletedEventHandler? OnCompleted; 25 | 26 | public PurgeMessagesJob( 27 | string connectionName, 28 | string queueOrTopicName, 29 | string? subscriptionName, 30 | SubQueue subQueue, 31 | long totalCount, 32 | IMessageService messageService) 33 | { 34 | _connectionName = connectionName; 35 | _queueOrTopicName = queueOrTopicName; 36 | _subscriptionName = subscriptionName; 37 | _subQueue = subQueue; 38 | _totalCount = totalCount; 39 | _messageService = messageService; 40 | _cancellationTokenSource = new CancellationTokenSource(); 41 | } 42 | 43 | private int _progress; 44 | public int Progress 45 | { 46 | get => _progress; 47 | private set 48 | { 49 | _progress = value; 50 | this.Notify(PropertyChanged); 51 | } 52 | } 53 | 54 | public string Name => 55 | $"Purge messages from {_queueOrTopicName} {_subscriptionName}".Trim(); 56 | 57 | public bool ViewDetails { get; set; } 58 | 59 | private JobStatus _status; 60 | public JobStatus Status 61 | { 62 | get => _status; 63 | private set 64 | { 65 | _status = value; 66 | this.Notify(PropertyChanged); 67 | } 68 | } 69 | public string? ErrorMessage { get; private set; } 70 | 71 | public async Task ExecuteAsync() 72 | { 73 | Status = JobStatus.Running; 74 | 75 | try 76 | { 77 | await foreach (var result in _messageService.PurgeAsync( 78 | _connectionName, 79 | _queueOrTopicName, 80 | _subscriptionName, 81 | _subQueue, 82 | _totalCount, 83 | _cancellationTokenSource.Token)) 84 | { 85 | Progress = JobsHelper.GetProgress(_totalCount, result.PurgedCount); 86 | } 87 | 88 | Status = JobStatus.Succeeded; 89 | } 90 | catch (TaskCanceledException) 91 | { 92 | Status = JobStatus.Cancelled; 93 | } 94 | catch (Exception ex) 95 | { 96 | ErrorMessage = $"Job {Name} failed. Error: {ex.Message}."; 97 | Status = JobStatus.Failed; 98 | } 99 | 100 | await OnCompleted(_connectionName, _queueOrTopicName, _subscriptionName); 101 | } 102 | 103 | public void Cancel() 104 | { 105 | _cancellationTokenSource.Cancel(); 106 | } 107 | } -------------------------------------------------------------------------------- /src/Ui/Website/Mappings/SubscriptionMappings.cs: -------------------------------------------------------------------------------- 1 | using Azure.Core; 2 | using CrossBusExplorer.ServiceBus.Contracts.Types; 3 | using CrossBusExplorer.Website.Extensions; 4 | using CrossBusExplorer.Website.Models; 5 | using CrossBusExplorer.Website.Pages; 6 | namespace CrossBusExplorer.Website.Mappings; 7 | 8 | public static class SubscriptionMappings 9 | { 10 | public static SubscriptionFormModel ToFormModel(this SubscriptionDetails details, 11 | OperationType operationType) 12 | { 13 | return new SubscriptionFormModel(operationType) 14 | { 15 | TopicName = details.Info.TopicName, 16 | SubscriptionName = operationType == OperationType.Update ? details.Info.SubscriptionName 17 | : null, 18 | MaxDeliveryCount = details.Properties.MaxDeliveryCount, 19 | UserMetadata = details.Properties.UserMetadata, 20 | ForwardTo = details.Properties.ForwardTo, 21 | ForwardDeadLetteredMessagesTo = details.Properties.ForwardDeadLetteredMessagesTo, 22 | AutoDeleteOnIdle = details.TimeSettings.AutoDeleteOnIdle, 23 | DefaultMessageTimeToLive = 24 | details.TimeSettings.DefaultMessageTimeToLive, 25 | LockDuration = details.TimeSettings.LockDuration, 26 | RequiresSession = details.Settings.RequiresSession, 27 | DeadLetteringOnMessageExpiration = 28 | details.Settings.EnableDeadLetteringOnMessageExpiration, 29 | EnableBatchedOperations = details.Settings.EnableBatchedOperations, 30 | EnableDeadLetteringOnFilterEvaluationExceptions = 31 | details.Settings.EnableDeadLetteringOnFilterEvaluationExceptions 32 | }; 33 | } 34 | 35 | public static UpdateSubscriptionOptions ToUpdateOptions(this SubscriptionFormModel model) 36 | { 37 | return new UpdateSubscriptionOptions( 38 | model.TopicName!, 39 | model.SubscriptionName, 40 | LockDuration: model.LockDuration, 41 | RequiresSession: model.RequiresSession, 42 | DefaultMessageTimeToLive: model.DefaultMessageTimeToLive, 43 | AutoDeleteOnIdle: model.AutoDeleteOnIdle, 44 | DeadLetteringOnMessageExpiration: model.DeadLetteringOnMessageExpiration, 45 | MaxDeliveryCount: model.MaxDeliveryCount, 46 | EnableBatchedOperations: model.EnableBatchedOperations, 47 | Status: null, 48 | ForwardTo: model.ForwardTo, 49 | ForwardDeadLetteredMessagesTo: model.ForwardDeadLetteredMessagesTo, 50 | UserMetadata: model.UserMetadata); 51 | } 52 | 53 | public static CreateSubscriptionOptions ToCreateOptions(this SubscriptionFormModel model) 54 | { 55 | return new CreateSubscriptionOptions( 56 | model.TopicName, 57 | model.SubscriptionName, 58 | LockDuration: model.LockDuration, 59 | RequiresSession: model.RequiresSession, 60 | DefaultMessageTimeToLive: model.DefaultMessageTimeToLive, 61 | AutoDeleteOnIdle: model.AutoDeleteOnIdle, 62 | DeadLetteringOnMessageExpiration: model.DeadLetteringOnMessageExpiration, 63 | EnableDeadLetteringOnFilterEvaluationExceptions: 64 | model.EnableDeadLetteringOnFilterEvaluationExceptions, 65 | MaxDeliveryCount: model.MaxDeliveryCount, 66 | EnableBatchedOperations: model.EnableBatchedOperations, 67 | Status: null, 68 | ForwardTo: model.ForwardTo, 69 | ForwardDeadLetteredMessagesTo: model.ForwardDeadLetteredMessagesTo, 70 | UserMetadata: model.UserMetadata); 71 | } 72 | } -------------------------------------------------------------------------------- /src/Ui/Website/Jobs/DeleteMessageJob.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.ComponentModel; 3 | using System.Threading; 4 | using System.Threading.Tasks; 5 | using CrossBusExplorer.ServiceBus.Contracts; 6 | using CrossBusExplorer.ServiceBus.Contracts.Types; 7 | using CrossBusExplorer.Website.Extensions; 8 | using MudBlazor; 9 | namespace CrossBusExplorer.Website.Jobs; 10 | 11 | public class DeleteMessageJob : IJob 12 | { 13 | private readonly string _connectionName; 14 | private readonly string _queueOrTopicName; 15 | private readonly string? _subscriptionName; 16 | private readonly SubQueue _subQueue; 17 | private readonly long _sequenceNumber; 18 | private readonly IMessageService _messageService; 19 | private readonly ISnackbar _snackbar; 20 | private readonly CancellationTokenSource _cancellationTokenSource; 21 | 22 | public event PropertyChangedEventHandler? PropertyChanged; 23 | 24 | public event JobCompletedEventHandler? OnCompleted; 25 | 26 | public DeleteMessageJob( 27 | string connectionName, 28 | string queueOrTopicName, 29 | string? subscriptionName, 30 | SubQueue subQueue, 31 | long sequenceNumber, 32 | IMessageService messageService) 33 | { 34 | _connectionName = connectionName; 35 | _queueOrTopicName = queueOrTopicName; 36 | _subscriptionName = subscriptionName; 37 | _subQueue = subQueue; 38 | _sequenceNumber = sequenceNumber; 39 | 40 | _messageService = messageService; 41 | _cancellationTokenSource = new CancellationTokenSource(); 42 | } 43 | 44 | private int _progress; 45 | public int Progress 46 | { 47 | get => _progress; 48 | private set 49 | { 50 | _progress = value; 51 | this.Notify(PropertyChanged); 52 | } 53 | } 54 | 55 | public string Name => 56 | $"Delete message {_sequenceNumber} from {_queueOrTopicName} {_subscriptionName}".Trim(); 57 | 58 | public bool ViewDetails { get; set; } 59 | 60 | private JobStatus _status; 61 | public JobStatus Status 62 | { 63 | get => _status; 64 | private set 65 | { 66 | _status = value; 67 | this.Notify(PropertyChanged); 68 | } 69 | } 70 | public string? ErrorMessage { get; private set; } 71 | public string? WarningMessage { get; private set; } 72 | 73 | public async Task ExecuteAsync() 74 | { 75 | Status = JobStatus.Running; 76 | 77 | try 78 | { 79 | Result result = await _messageService.DeleteMessage( 80 | _connectionName, 81 | _queueOrTopicName, 82 | _subscriptionName, 83 | _subQueue, 84 | _sequenceNumber, 85 | _cancellationTokenSource.Token); 86 | 87 | Progress = JobsHelper.GetProgress(1, result.Count); 88 | 89 | Status = JobStatus.Succeeded; 90 | 91 | if (result.Count == 0) 92 | { 93 | WarningMessage = "Message was not not deleted"; 94 | } 95 | } 96 | catch (TaskCanceledException) 97 | { 98 | Status = JobStatus.Cancelled; 99 | } 100 | catch (Exception ex) 101 | { 102 | ErrorMessage = $"Job {Name} failed. Error: {ex.Message}."; 103 | Status = JobStatus.Failed; 104 | } 105 | 106 | await OnCompleted(_connectionName, _queueOrTopicName, _subscriptionName); 107 | } 108 | 109 | public void Cancel() 110 | { 111 | _cancellationTokenSource.Cancel(); 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /src/Ui/Website/Jobs/ResendMessagesJob.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.ComponentModel; 3 | using System.Threading; 4 | using System.Threading.Tasks; 5 | using CrossBusExplorer.ServiceBus.Contracts; 6 | using CrossBusExplorer.ServiceBus.Contracts.Types; 7 | using CrossBusExplorer.Website.Extensions; 8 | namespace CrossBusExplorer.Website.Jobs; 9 | 10 | public class ResendMessagesJob : IJob 11 | { 12 | private readonly string _connectionName; 13 | private readonly string _queueOrTopicName; 14 | private readonly string? _subscriptionName; 15 | private readonly SubQueue _subQueue; 16 | private readonly string _destinationTopicOrQueueName; 17 | private readonly long _totalCount; 18 | private readonly IMessageService _messageService; 19 | private readonly CancellationTokenSource _cancellationTokenSource; 20 | 21 | public event JobCompletedEventHandler? OnCompleted; 22 | public event PropertyChangedEventHandler? PropertyChanged; 23 | 24 | public ResendMessagesJob( 25 | string connectionName, 26 | string queueOrTopicName, 27 | string? subscriptionName, 28 | SubQueue subQueue, 29 | string destinationTopicOrQueueName, 30 | long totalCount, 31 | IMessageService messageService) 32 | { 33 | _connectionName = connectionName; 34 | _queueOrTopicName = queueOrTopicName; 35 | _subscriptionName = subscriptionName; 36 | _subQueue = subQueue; 37 | _destinationTopicOrQueueName = destinationTopicOrQueueName; 38 | _totalCount = totalCount; 39 | _messageService = messageService; 40 | _cancellationTokenSource = new CancellationTokenSource(); 41 | } 42 | 43 | private int _progress; 44 | public int Progress 45 | { 46 | get => _progress; 47 | private set 48 | { 49 | _progress = value; 50 | this.Notify(PropertyChanged); 51 | } 52 | } 53 | public async Task ExecuteAsync() 54 | { 55 | Status = JobStatus.Running; 56 | 57 | try 58 | { 59 | await foreach (var result in _messageService.ResendAsync( 60 | _connectionName, 61 | _queueOrTopicName, 62 | _subscriptionName, 63 | _subQueue, 64 | _destinationTopicOrQueueName, 65 | _totalCount, 66 | _cancellationTokenSource.Token)) 67 | { 68 | Progress = JobsHelper.GetProgress(_totalCount, result.ResendCount); 69 | } 70 | 71 | Status = JobStatus.Succeeded; 72 | } 73 | catch (TaskCanceledException) 74 | { 75 | Status = JobStatus.Cancelled; 76 | } 77 | catch (Exception ex) 78 | { 79 | ErrorMessage = $"Job {Name} failed. Error: {ex.Message}."; 80 | Status = JobStatus.Failed; 81 | } 82 | 83 | await OnCompleted(_connectionName, _queueOrTopicName, _subscriptionName); 84 | } 85 | 86 | private JobStatus _status; 87 | public JobStatus Status 88 | { 89 | get => _status; 90 | private set 91 | { 92 | _status = value; 93 | this.Notify(PropertyChanged); 94 | } 95 | } 96 | public string? ErrorMessage { get; private set; } 97 | 98 | public void Cancel() 99 | { 100 | _cancellationTokenSource.Cancel(); 101 | } 102 | 103 | public string Name => 104 | $"Resend messages from {_queueOrTopicName} {_subscriptionName} " + 105 | $"to {_destinationTopicOrQueueName}.".Trim(); 106 | 107 | public bool ViewDetails { get; set; } 108 | } -------------------------------------------------------------------------------- /src/Ui/Website/Website.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net8.0 5 | Debug;Release 6 | enable 7 | CrossBusExplorer.Website 8 | 9 | 10 | 11 | true 12 | 13 | 14 | 15 | false 16 | 17 | 18 | 19 | Logging 20 | 21 | 22 | 23 | NoLogging 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | <_ContentIncludedByDefault Remove="wwwroot\js\app.js" /> 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | -------------------------------------------------------------------------------- /src/Ui/Website/Models/MessageDetailsModel.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Collections.ObjectModel; 4 | using System.ComponentModel; 5 | using System.Runtime.CompilerServices; 6 | using CrossBusExplorer.Website.Extensions; 7 | namespace CrossBusExplorer.Website.Models; 8 | 9 | public class MessageDetailsModel : INotifyPropertyChanged 10 | { 11 | public event PropertyChangedEventHandler? PropertyChanged; 12 | 13 | public MessageDetailsModel() 14 | { 15 | ApplicationProperties = new ObservableCollection(); 16 | } 17 | 18 | private string _body; 19 | 20 | public string Body 21 | { 22 | get => _body; 23 | set 24 | { 25 | _body = value; 26 | this.Notify(PropertyChanged); 27 | } 28 | } 29 | 30 | private string? _subject; 31 | 32 | public string? Subject 33 | { 34 | get => _subject; 35 | set 36 | { 37 | _subject = value; 38 | this.Notify(PropertyChanged); 39 | } 40 | } 41 | 42 | private string? _to; 43 | 44 | public string? To 45 | { 46 | get => _to; 47 | set 48 | { 49 | _to = value; 50 | this.Notify(PropertyChanged); 51 | } 52 | } 53 | 54 | private string? _contentType; 55 | 56 | public string? ContentType 57 | { 58 | get => _contentType; 59 | set 60 | { 61 | _contentType = value; 62 | this.Notify(PropertyChanged); 63 | } 64 | } 65 | 66 | private string? _correlationId; 67 | 68 | public string? CorrelationId 69 | { 70 | get => _correlationId; 71 | set 72 | { 73 | _correlationId = value; 74 | this.Notify(PropertyChanged); 75 | } 76 | } 77 | 78 | private string? _id; 79 | 80 | public string? Id 81 | { 82 | get => _id; 83 | set 84 | { 85 | _id = value; 86 | this.Notify(PropertyChanged); 87 | } 88 | } 89 | 90 | private string? _partitionKey; 91 | 92 | public string? PartitionKey 93 | { 94 | get => _partitionKey; 95 | set 96 | { 97 | _partitionKey = value; 98 | this.Notify(PropertyChanged); 99 | } 100 | } 101 | 102 | private string? _replyTo; 103 | 104 | public string? ReplyTo 105 | { 106 | get => _replyTo; 107 | set 108 | { 109 | _replyTo = value; 110 | this.Notify(PropertyChanged); 111 | } 112 | } 113 | 114 | private string? _sessionId; 115 | 116 | public string? SessionId 117 | { 118 | get => _sessionId; 119 | set 120 | { 121 | _sessionId = value; 122 | this.Notify(PropertyChanged); 123 | } 124 | } 125 | 126 | private DateTimeOffset? _scheduledEnqueueTime; 127 | 128 | public DateTimeOffset? ScheduledEnqueueTime 129 | { 130 | get => _scheduledEnqueueTime; 131 | set 132 | { 133 | _scheduledEnqueueTime = value; 134 | this.Notify(PropertyChanged); 135 | } 136 | } 137 | 138 | private TimeSpan? _timeToLive; 139 | 140 | public TimeSpan? TimeToLive 141 | { 142 | get => _timeToLive; 143 | set 144 | { 145 | _timeToLive = value; 146 | this.Notify(PropertyChanged); 147 | } 148 | } 149 | 150 | private ObservableCollection _applicationProperties; 151 | 152 | public ObservableCollection ApplicationProperties 153 | { 154 | get => _applicationProperties; 155 | set 156 | { 157 | _applicationProperties = value; 158 | _applicationProperties.CollectionChanged += (_, _) => this.Notify(PropertyChanged); 159 | this.Notify(PropertyChanged); 160 | } 161 | } 162 | } 163 | -------------------------------------------------------------------------------- /src/Ui/Website/Shared/TopicNestedMenuItem.razor: -------------------------------------------------------------------------------- 1 | @using System.Web 2 | @using CrossBusExplorer.Website.Models 3 | 4 | @if (TopicSubscriptionsModel.Topic.IsFolder) 5 | { 6 | 7 | 8 | @if (TopicSubscriptionsModel.TopicExpanded) 9 | { 10 | @foreach (var model in TopicSubscriptionsModel.ChildrenModels) 11 | { 12 | 13 | } 14 | } 15 | 16 | 17 | } 18 | else 19 | { 20 | 21 | 24 | View topic details 25 | 26 | 29 | 30 | @if (TopicSubscriptionsModel.IsLoading) 31 | { 32 | 33 | } 34 | @if (TopicSubscriptionsModel.Loaded) 35 | { 36 | 39 | Create new subscription 40 | 41 | 42 | @foreach (var subscription in TopicSubscriptionsModel.Subscriptions) 43 | { 44 | 47 | 48 | @subscription.SubscriptionName (@subscription.ActiveMessagesCount, 49 | @subscription.DeadLetterMessagesCount, 50 | @subscription.TransferMessagesCount) 51 | 52 | 53 | } 54 | } 55 | 56 | 57 | } 58 | 59 | @code { 60 | [Inject] public INavigationViewModel Model { get; set; } 61 | [Parameter] public TopicSubscriptionsModel TopicSubscriptionsModel { get; set; } = null!; 62 | [Parameter] public string ConnectionName { get; set; } = null!; 63 | 64 | protected override void OnInitialized() 65 | { 66 | Model.PropertyChanged += (_, _) => { StateHasChanged(); }; 67 | 68 | TopicSubscriptionsModel.ShouldRender = true; 69 | } 70 | 71 | protected override bool ShouldRender() => TopicSubscriptionsModel.ShouldRender; 72 | 73 | private async Task OnSubscriptionsExpanded() 74 | { 75 | TopicSubscriptionsModel.ShouldRender = false; 76 | await Model.LoadSubscriptionsAsync(ConnectionName, TopicSubscriptionsModel); 77 | TopicSubscriptionsModel.ShouldRender = true; 78 | } 79 | 80 | private void OnTopicExpandChange() 81 | { 82 | TopicSubscriptionsModel.TopicExpanded = !TopicSubscriptionsModel.TopicExpanded; 83 | } 84 | 85 | } -------------------------------------------------------------------------------- /src/Ui/Website/Shared/TopicSubNestedMenuItem.razor: -------------------------------------------------------------------------------- 1 | @using CrossBusExplorer.Website.Models 2 | @using System.Web 3 | 4 | @if (TopicSubscriptionsModel.Topic.IsFolder) 5 | { 6 | 7 | @if (TopicSubscriptionsModel.TopicExpanded) 8 | { 9 | @foreach (var model in TopicSubscriptionsModel.ChildrenModels) 10 | { 11 | 12 | } 13 | } 14 | 15 | } 16 | else 17 | { 18 | 19 | 22 | View topic details 23 | 24 | 27 | 28 | @if (TopicSubscriptionsModel.IsLoading) 29 | { 30 | 31 | } 32 | @if (TopicSubscriptionsModel.Loaded) 33 | { 34 | 37 | Create new subscription 38 | 39 | 40 | @foreach (var subscription in TopicSubscriptionsModel.Subscriptions) 41 | { 42 | 45 | 48 | @subscription.SubscriptionName (@subscription.ActiveMessagesCount, 49 | @subscription.DeadLetterMessagesCount, 50 | @subscription.TransferMessagesCount) 51 | 52 | 53 | } 54 | } 55 | 56 | 57 | } 58 | 59 | @code { 60 | [Inject] 61 | public INavigationViewModel Model { get; set; } 62 | [Parameter] 63 | public TopicSubscriptionsModel TopicSubscriptionsModel { get; set; } = null!; 64 | [Parameter] 65 | public string ConnectionName { get; set; } = null!; 66 | 67 | protected override void OnInitialized() 68 | { 69 | Model.PropertyChanged += (_, _) => 70 | { 71 | StateHasChanged(); 72 | }; 73 | 74 | TopicSubscriptionsModel.ShouldRender = true; 75 | } 76 | 77 | protected override bool ShouldRender() => TopicSubscriptionsModel.ShouldRender; 78 | 79 | private async Task OnSubscriptionsExpanded() 80 | { 81 | TopicSubscriptionsModel.ShouldRender = false; 82 | await Model.LoadSubscriptionsAsync(ConnectionName, TopicSubscriptionsModel); 83 | TopicSubscriptionsModel.ShouldRender = true; 84 | } 85 | private void OnTopicExpandChange() 86 | { 87 | TopicSubscriptionsModel.TopicExpanded = !TopicSubscriptionsModel.TopicExpanded; 88 | } 89 | } -------------------------------------------------------------------------------- /src/Ui/Website/Mappings/QueueMappings.cs: -------------------------------------------------------------------------------- 1 | using CrossBusExplorer.ServiceBus.Contracts.Types; 2 | using CrossBusExplorer.Website.Extensions; 3 | using CrossBusExplorer.Website.Models; 4 | using CrossBusExplorer.Website.Pages; 5 | namespace CrossBusExplorer.Website.Mappings; 6 | 7 | public static class QueueMappings 8 | { 9 | public static QueueFormModel ToFormModel(this QueueDetails details, OperationType operationType) 10 | { 11 | return new QueueFormModel(operationType) 12 | { 13 | Name = operationType == OperationType.Update ? details.Info.Name : null, 14 | MaxSizeInMegabytes = details.Properties.MaxQueueSizeInMegabytes, 15 | MaxDeliveryCount = details.Properties.MaxDeliveryCount, 16 | UserMetadata = details.Properties.UserMetadata, 17 | ForwardTo = details.Properties.ForwardTo, 18 | ForwardDeadLetteredMessagesTo = details.Properties.ForwardDeadLetteredMessagesTo, 19 | DuplicateDetectionHistoryTimeWindow = details.TimeSettings 20 | .DuplicateDetectionHistoryTimeWindow, 21 | AutoDeleteOnIdle = details.TimeSettings.AutoDeleteOnIdle, 22 | DefaultMessageTimeToLive = 23 | details.TimeSettings.DefaultMessageTimeToLive, 24 | LockDuration = details.TimeSettings.LockDuration, 25 | RequiresSession = details.Settings.RequiresSession, 26 | DeadLetteringOnMessageExpiration = 27 | details.Settings.EnableDeadLetteringOnMessageExpiration, 28 | EnableBatchedOperations = details.Settings.EnableBatchedOperations, 29 | RequiresDuplicateDetection = details.Settings.RequiresDuplicateDetection, 30 | EnablePartitioning = details.Settings.EnablePartitioning, 31 | MaxMessageSizeInKilobytes = details.Properties.MaxMessageSizeInKilobytes 32 | }; 33 | } 34 | 35 | public static UpdateQueueOptions ToUpdateOptions(this QueueFormModel model) 36 | { 37 | return new UpdateQueueOptions( 38 | model.Name!, 39 | AuthorizationRules: null, 40 | MaxSizeInMegabytes: model.MaxSizeInMegabytes, 41 | LockDuration: model.LockDuration, 42 | RequiresSession: model.RequiresSession, 43 | DefaultMessageTimeToLive: model.DefaultMessageTimeToLive, 44 | AutoDeleteOnIdle: model.AutoDeleteOnIdle, 45 | DeadLetteringOnMessageExpiration: model.DeadLetteringOnMessageExpiration, 46 | DuplicateDetectionHistoryTimeWindow: model.DuplicateDetectionHistoryTimeWindow, 47 | MaxDeliveryCount: model.MaxDeliveryCount, 48 | EnableBatchedOperations: model.EnableBatchedOperations, 49 | Status: null, 50 | ForwardTo: model.ForwardTo, 51 | ForwardDeadLetteredMessagesTo: model.ForwardDeadLetteredMessagesTo, 52 | MaxMessageSizeInKilobytes: model.MaxMessageSizeInKilobytes, 53 | UserMetadata: model.UserMetadata); 54 | } 55 | 56 | public static CreateQueueOptions ToCreateOptions(this QueueFormModel model) 57 | { 58 | return new CreateQueueOptions( 59 | model.Name!, 60 | AuthorizationRules: null, 61 | MaxSizeInMegabytes: model.MaxSizeInMegabytes, 62 | LockDuration: model.LockDuration, 63 | RequiresSession: model.RequiresSession, 64 | DefaultMessageTimeToLive: model.DefaultMessageTimeToLive, 65 | AutoDeleteOnIdle: model.AutoDeleteOnIdle, 66 | DeadLetteringOnMessageExpiration: model.DeadLetteringOnMessageExpiration, 67 | DuplicateDetectionHistoryTimeWindow: model.DuplicateDetectionHistoryTimeWindow, 68 | MaxDeliveryCount: model.MaxDeliveryCount, 69 | EnableBatchedOperations: model.EnableBatchedOperations, 70 | Status: null, 71 | ForwardTo: model.ForwardTo, 72 | ForwardDeadLetteredMessagesTo: model.ForwardDeadLetteredMessagesTo, 73 | MaxMessageSizeInKilobytes: model.MaxMessageSizeInKilobytes, 74 | UserMetadata: model.UserMetadata, 75 | RequiresDuplicateDetection: model.RequiresDuplicateDetection, 76 | EnablePartitioning: model.EnablePartitioning); 77 | } 78 | } -------------------------------------------------------------------------------- /src/Ui/Website/Shared/MainLayout.razor: -------------------------------------------------------------------------------- 1 | @using CrossBusExplorer.Website.Models 2 | @inherits LayoutComponentBase 3 | @inject IConnectionsViewModel _connectionsViewModel 4 | @inject ISettingsService SettingsService 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 29 | 30 | 31 | 32 | Normal 33 | Dark 34 | 35 | 36 | 37 | 38 | 39 | @if (_drawerOpen) 40 | { 41 | 42 | 43 | 44 | } 45 | 46 | 47 | 48 | @Body 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | @context 57 | Recover 58 | 59 | 60 | 61 | 62 | 63 | @code { 64 | private ErrorBoundary? _errorBoundary; 65 | private bool _isOpen; 66 | private UserSettings _userSettings = new UserSettings(); 67 | bool _drawerOpen = true; 68 | public bool DarkMode { get; set; } 69 | 70 | [Inject] 71 | private INavigationViewModel _navigationViewModel { get; set; } 72 | 73 | [Inject] 74 | private MudTheme _customTheme { get; set; } 75 | 76 | protected override void OnParametersSet() 77 | { 78 | _errorBoundary?.Recover(); 79 | } 80 | 81 | protected override async Task OnInitializedAsync() 82 | { 83 | await _connectionsViewModel.InitializeAsync(default); 84 | } 85 | 86 | protected override async Task OnAfterRenderAsync(bool firstRender) 87 | { 88 | if (firstRender) 89 | { 90 | _userSettings = await SettingsService.GetAsync(default); 91 | StateHasChanged(); 92 | } 93 | } 94 | 95 | private async Task ChangeDarkMode(bool isDarkMode) 96 | { 97 | _userSettings.IsDarkMode = isDarkMode; 98 | StateHasChanged(); 99 | 100 | await SettingsService.SaveAsync(_userSettings, default); 101 | } 102 | 103 | private async Task ChangeDarkModeInternal(bool isDarkMode) 104 | { 105 | DarkMode = isDarkMode; 106 | await ChangeDarkMode(isDarkMode); 107 | } 108 | 109 | void DrawerToggle() 110 | { 111 | _drawerOpen = !_drawerOpen; 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /src/Ui/Website/Models/TopicFormModel.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.ComponentModel; 3 | using System.ComponentModel.DataAnnotations; 4 | using CrossBusExplorer.Website.Extensions; 5 | using CrossBusExplorer.Website.Pages; 6 | using MudBlazor; 7 | namespace CrossBusExplorer.Website.Models; 8 | 9 | public class TopicFormModel : INotifyPropertyChanged 10 | { 11 | public event PropertyChangedEventHandler? PropertyChanged; 12 | 13 | public TopicFormModel(OperationType operationType) 14 | { 15 | OperationType = operationType; 16 | } 17 | 18 | public OperationType OperationType { get; } 19 | 20 | private string? _name; 21 | 22 | [Label("Name")] 23 | [Required(ErrorMessage = "Name is required")] 24 | public string? Name 25 | { 26 | get => _name; 27 | set 28 | { 29 | _name = value; 30 | this.Notify(PropertyChanged); 31 | } 32 | } 33 | 34 | private long? _maxSizeInMegabytes; 35 | 36 | [Label("Max size in MB")] 37 | [Required(ErrorMessage = "Field is required")] 38 | public long? MaxSizeInMegabytes 39 | { 40 | get => _maxSizeInMegabytes; 41 | set 42 | { 43 | _maxSizeInMegabytes = value; 44 | this.Notify(PropertyChanged); 45 | } 46 | } 47 | 48 | private string? _userMetadata; 49 | 50 | [Label("User metadata")] 51 | public string? UserMetadata 52 | { 53 | get => _userMetadata; 54 | set 55 | { 56 | _userMetadata = value; 57 | this.Notify(PropertyChanged); 58 | } 59 | } 60 | 61 | private TimeSpan? _duplicateDetectionHistoryTimeWindow; 62 | 63 | [Label("Duplicate detection history time window")] 64 | [Required(ErrorMessage = "Field is required. Format: DDDD.HH:MM:SS.")] 65 | public TimeSpan? DuplicateDetectionHistoryTimeWindow 66 | { 67 | get => _duplicateDetectionHistoryTimeWindow; 68 | set 69 | { 70 | _duplicateDetectionHistoryTimeWindow = value; 71 | this.Notify(PropertyChanged); 72 | } 73 | } 74 | 75 | private TimeSpan? _autoDeleteOnIdle; 76 | 77 | [Label("Auto delete queue on idle")] 78 | [Required(ErrorMessage = "Field is required. Format: DDDD.HH:MM:SS.")] 79 | public TimeSpan? AutoDeleteOnIdle 80 | { 81 | get => _autoDeleteOnIdle; 82 | set 83 | { 84 | _autoDeleteOnIdle = value; 85 | this.Notify(PropertyChanged); 86 | } 87 | } 88 | 89 | private TimeSpan? _defaultMessageTimeToLive; 90 | 91 | [Label("Default message time to live")] 92 | [Required(ErrorMessage = "Field is required. Format: DDDD.HH:MM:SS.")] 93 | public TimeSpan? DefaultMessageTimeToLive 94 | { 95 | get => _defaultMessageTimeToLive; 96 | set 97 | { 98 | _defaultMessageTimeToLive = value; 99 | this.Notify(PropertyChanged); 100 | } 101 | } 102 | 103 | private bool? _supportOrdering; 104 | 105 | [Label("Support ordering")] 106 | public bool? SupportOrdering 107 | { 108 | get => _supportOrdering; 109 | set 110 | { 111 | _supportOrdering = value; 112 | this.Notify(PropertyChanged); 113 | } 114 | } 115 | 116 | private bool? _enableBatchedOperations; 117 | 118 | [Label("Enable batch operations")] 119 | public bool? EnableBatchedOperations 120 | { 121 | get => _enableBatchedOperations; 122 | set 123 | { 124 | _enableBatchedOperations = value; 125 | this.Notify(PropertyChanged); 126 | } 127 | } 128 | 129 | private bool? _requiresDuplicateDetection; 130 | 131 | [Label("Requires duplicate detection")] 132 | public bool? RequiresDuplicateDetection 133 | { 134 | get => _requiresDuplicateDetection; 135 | set 136 | { 137 | _requiresDuplicateDetection = value; 138 | this.Notify(PropertyChanged); 139 | } 140 | } 141 | 142 | private bool? _enablePartitioning; 143 | 144 | [Label("Enable partitioning")] 145 | public bool? EnablePartitioning 146 | { 147 | get => _enablePartitioning; 148 | set 149 | { 150 | _enablePartitioning = value; 151 | this.Notify(PropertyChanged); 152 | } 153 | } 154 | 155 | private long? _maxMessageSizeInKilobytes; 156 | 157 | [Label("Max message size in KB")] 158 | public long? MaxMessageSizeInKilobytes 159 | { 160 | get => _maxMessageSizeInKilobytes; 161 | set 162 | { 163 | _maxMessageSizeInKilobytes = value; 164 | this.Notify(PropertyChanged); 165 | } 166 | } 167 | } -------------------------------------------------------------------------------- /src/Core/ServiceBus/Extensions/ApplicationPropertyTypeExtensions.cs: -------------------------------------------------------------------------------- 1 | using System.Globalization; 2 | using CrossBusExplorer.ServiceBus.Contracts.Types; 3 | namespace CrossBusExplorer.ServiceBus.Extensions 4 | { 5 | public static class ApplicationPropertyTypeExtensions 6 | { 7 | public static ApplicationPropertyType GetApplicationPropertyType(this object? value) => 8 | value switch 9 | { 10 | null => ApplicationPropertyType.String, 11 | string _ => ApplicationPropertyType.String, 12 | bool _ => ApplicationPropertyType.Bool, 13 | byte _ => ApplicationPropertyType.Byte, 14 | sbyte _ => ApplicationPropertyType.Sbyte, 15 | short _ => ApplicationPropertyType.Short, 16 | ushort _ => ApplicationPropertyType.Ushort, 17 | int _ => ApplicationPropertyType.Int, 18 | uint _ => ApplicationPropertyType.Uint, 19 | long _ => ApplicationPropertyType.Long, 20 | ulong _ => ApplicationPropertyType.Ulong, 21 | float _ => ApplicationPropertyType.Float, 22 | decimal _ => ApplicationPropertyType.Decimal, 23 | double _ => ApplicationPropertyType.Double, 24 | char _ => ApplicationPropertyType.Char, 25 | Guid _ => ApplicationPropertyType.Guid, 26 | DateTime _ => ApplicationPropertyType.DateTime, 27 | DateTimeOffset _ => ApplicationPropertyType.DateTimeOffset, 28 | Uri _ => ApplicationPropertyType.Uri, 29 | TimeSpan _ => ApplicationPropertyType.TimeSpan, 30 | _ => throw new ArgumentOutOfRangeException( 31 | $"Type {value?.GetType()} is not supported") 32 | }; 33 | 34 | public static object? GetApplicationPropertyValue( 35 | this string? value, 36 | ApplicationPropertyType type) 37 | { 38 | if (value == null) 39 | { 40 | return value; 41 | } 42 | 43 | switch (type) 44 | { 45 | case ApplicationPropertyType.String: 46 | return value; 47 | case ApplicationPropertyType.Bool: 48 | { 49 | if (value.Equals("1", StringComparison.Ordinal) || 50 | value.Equals("true", StringComparison.InvariantCultureIgnoreCase)) 51 | { 52 | return true; 53 | } 54 | 55 | if (value.Equals("0", StringComparison.Ordinal) || 56 | value.Equals("false", StringComparison.InvariantCultureIgnoreCase)) 57 | { 58 | return false; 59 | } 60 | 61 | return int.Parse(value, CultureInfo.InvariantCulture); 62 | } 63 | case ApplicationPropertyType.Byte: 64 | return byte.Parse(value); 65 | case ApplicationPropertyType.Sbyte: 66 | return sbyte.Parse(value); 67 | case ApplicationPropertyType.Short: 68 | return short.Parse(value); 69 | case ApplicationPropertyType.Ushort: 70 | return ushort.Parse(value); 71 | case ApplicationPropertyType.Int: 72 | return int.Parse(value); 73 | case ApplicationPropertyType.Uint: 74 | return uint.Parse(value); 75 | case ApplicationPropertyType.Long: 76 | return long.Parse(value); 77 | case ApplicationPropertyType.Ulong: 78 | return ulong.Parse(value); 79 | case ApplicationPropertyType.Float: 80 | return float.Parse(value); 81 | case ApplicationPropertyType.Decimal: 82 | return decimal.Parse(value); 83 | case ApplicationPropertyType.Double: 84 | return double.Parse(value); 85 | case ApplicationPropertyType.Char: 86 | return char.Parse(value); 87 | case ApplicationPropertyType.Guid: 88 | return Guid.Parse(value); 89 | case ApplicationPropertyType.DateTime: 90 | return DateTime.Parse(value); 91 | case ApplicationPropertyType.DateTimeOffset: 92 | return DateTimeOffset.Parse(value); 93 | case ApplicationPropertyType.Uri: 94 | return new Uri(value); 95 | case ApplicationPropertyType.TimeSpan: 96 | return TimeSpan.Parse(value); 97 | default: 98 | throw new ArgumentOutOfRangeException(nameof(type), type, "Unsupported type"); 99 | } 100 | } 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /src/Core/ServiceBus/Mappings/MessageMappings.cs: -------------------------------------------------------------------------------- 1 | using System.Text; 2 | using Azure.Core.Amqp; 3 | using Azure.Messaging.ServiceBus; 4 | using CrossBusExplorer.ServiceBus.Contracts.Types; 5 | 6 | namespace CrossBusExplorer.ServiceBus.Mappings; 7 | 8 | public static class MessageMappings 9 | { 10 | public static Message MapToMessage(this ServiceBusReceivedMessage receivedMessage) 11 | { 12 | return new Message( 13 | receivedMessage.MessageId, 14 | receivedMessage.Subject, 15 | ReadBody(receivedMessage), 16 | new MessageSystemProperties( 17 | receivedMessage.ContentType, 18 | receivedMessage.CorrelationId, 19 | receivedMessage.DeadLetterSource, 20 | receivedMessage.DeadLetterReason, 21 | receivedMessage.DeadLetterErrorDescription, 22 | receivedMessage.DeliveryCount, 23 | receivedMessage.EnqueuedSequenceNumber, 24 | receivedMessage.EnqueuedTime, 25 | receivedMessage.ExpiresAt, 26 | receivedMessage.LockedUntil, 27 | receivedMessage.LockToken, 28 | receivedMessage.PartitionKey, 29 | receivedMessage.TransactionPartitionKey, 30 | receivedMessage.ReplyTo, 31 | receivedMessage.ReplyToSessionId, 32 | receivedMessage.ScheduledEnqueueTime, 33 | receivedMessage.SequenceNumber, 34 | receivedMessage.SessionId, 35 | (MessageState)receivedMessage.State, 36 | receivedMessage.TimeToLive, 37 | receivedMessage.To 38 | ), 39 | receivedMessage.ApplicationProperties.ToDictionary(p => p.Key, 40 | p => p.Value)); 41 | } 42 | 43 | private static string ReadBody(ServiceBusReceivedMessage receivedMessage) 44 | { 45 | AmqpAnnotatedMessage amqpMessage = receivedMessage.GetRawAmqpMessage(); 46 | if (amqpMessage.Body.TryGetValue(out object? value)) 47 | { 48 | return ReadMessageValue(value); 49 | } 50 | 51 | if (amqpMessage.Body.TryGetData(out IEnumerable>? data)) 52 | { 53 | return ReadMessageData(data); 54 | } 55 | 56 | throw new NotSupportedException("Cannot read the message Body"); 57 | } 58 | 59 | public static ServiceBusMessage ToServiceBusMessage(this SendMessage message) 60 | { 61 | var sbMessage = new ServiceBusMessage(message.Body); 62 | 63 | if (!string.IsNullOrEmpty(message.Subject)) 64 | { 65 | sbMessage.Subject = message.Subject; 66 | } 67 | 68 | if (message.ApplicationProperties != null) 69 | { 70 | foreach (var applicationProperty in message.ApplicationProperties) 71 | { 72 | sbMessage.ApplicationProperties.Add( 73 | applicationProperty.Key, 74 | applicationProperty.Value); 75 | } 76 | } 77 | 78 | if (!string.IsNullOrEmpty(message.To)) 79 | { 80 | sbMessage.To = message.To; 81 | } 82 | 83 | if (!string.IsNullOrEmpty(message.ContentType)) 84 | { 85 | sbMessage.ContentType = message.ContentType; 86 | } 87 | 88 | if (!string.IsNullOrEmpty(message.CorrelationId)) 89 | { 90 | sbMessage.CorrelationId = message.CorrelationId; 91 | } 92 | 93 | if (!string.IsNullOrEmpty(message.Id)) 94 | { 95 | sbMessage.MessageId = message.Id; 96 | } 97 | 98 | if (!string.IsNullOrEmpty(message.PartitionKey)) 99 | { 100 | sbMessage.PartitionKey = message.PartitionKey; 101 | } 102 | 103 | if (!string.IsNullOrEmpty(message.ReplyTo)) 104 | { 105 | sbMessage.ReplyTo = message.ReplyTo; 106 | } 107 | 108 | if (!string.IsNullOrEmpty(message.SessionId)) 109 | { 110 | sbMessage.SessionId = message.SessionId; 111 | } 112 | 113 | if (message.ScheduledEnqueueTime != null) 114 | { 115 | sbMessage.ScheduledEnqueueTime = message.ScheduledEnqueueTime.Value; 116 | } 117 | 118 | if (message.TimeToLive != null) 119 | { 120 | sbMessage.TimeToLive = message.TimeToLive.Value; 121 | } 122 | 123 | return sbMessage; 124 | } 125 | 126 | private static string ReadMessageValue(object? value) 127 | { 128 | return value switch 129 | { 130 | null => string.Empty, 131 | string stringValue => stringValue, 132 | _ => throw new NotSupportedException($"Unknown message Body type {value.GetType().Name}") 133 | }; 134 | } 135 | 136 | private static string ReadMessageData(IEnumerable>? data) 137 | { 138 | if (data is null) 139 | { 140 | return string.Empty; 141 | } 142 | 143 | using var memoryStream = new MemoryStream(); 144 | foreach (var segment in data) 145 | { 146 | memoryStream.Write(segment.Span); 147 | } 148 | 149 | memoryStream.Position = 0; 150 | return Encoding.UTF8.GetString(memoryStream.ToArray()); 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /src/Ui/Website/Models/SubscriptionFormModel.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.ComponentModel; 3 | using System.ComponentModel.DataAnnotations; 4 | using CrossBusExplorer.Website.Extensions; 5 | using CrossBusExplorer.Website.Pages; 6 | using MudBlazor; 7 | namespace CrossBusExplorer.Website.Models; 8 | 9 | public class SubscriptionFormModel: INotifyPropertyChanged 10 | { 11 | public event PropertyChangedEventHandler? PropertyChanged; 12 | 13 | public SubscriptionFormModel(OperationType operationType) 14 | { 15 | OperationType = operationType; 16 | } 17 | 18 | public OperationType OperationType { get; } 19 | 20 | private string? _topicName; 21 | 22 | [Label("Topic name")] 23 | [Required(ErrorMessage = "Topic name is required")] 24 | public string? TopicName 25 | { 26 | get => _topicName; 27 | set 28 | { 29 | _topicName = value; 30 | this.Notify(PropertyChanged); 31 | } 32 | } 33 | 34 | private string? _subscriptionName; 35 | 36 | [Label("Name")] 37 | [Required(ErrorMessage = "Name is required")] 38 | public string? SubscriptionName 39 | { 40 | get => _subscriptionName; 41 | set 42 | { 43 | _subscriptionName = value; 44 | this.Notify(PropertyChanged); 45 | } 46 | } 47 | 48 | private string? _userMetadata; 49 | 50 | [Label("User metadata")] 51 | public string? UserMetadata 52 | { 53 | get => _userMetadata; 54 | set 55 | { 56 | _userMetadata = value; 57 | this.Notify(PropertyChanged); 58 | } 59 | } 60 | 61 | private TimeSpan? _autoDeleteOnIdle; 62 | 63 | [Label("Auto delete queue on idle")] 64 | [Required(ErrorMessage = "Field is required. Format: DDDD.HH:MM:SS.")] 65 | public TimeSpan? AutoDeleteOnIdle 66 | { 67 | get => _autoDeleteOnIdle; 68 | set 69 | { 70 | _autoDeleteOnIdle = value; 71 | this.Notify(PropertyChanged); 72 | } 73 | } 74 | 75 | private TimeSpan? _defaultMessageTimeToLive; 76 | 77 | [Label("Default message time to live")] 78 | [Required(ErrorMessage = "Field is required. Format: DDDD.HH:MM:SS.")] 79 | public TimeSpan? DefaultMessageTimeToLive 80 | { 81 | get => _defaultMessageTimeToLive; 82 | set 83 | { 84 | _defaultMessageTimeToLive = value; 85 | this.Notify(PropertyChanged); 86 | } 87 | } 88 | 89 | private bool? _supportOrdering; 90 | 91 | [Label("Support ordering")] 92 | public bool? SupportOrdering 93 | { 94 | get => _supportOrdering; 95 | set 96 | { 97 | _supportOrdering = value; 98 | this.Notify(PropertyChanged); 99 | } 100 | } 101 | 102 | private bool? _enableBatchedOperations; 103 | 104 | [Label("Enable batch operations")] 105 | public bool? EnableBatchedOperations 106 | { 107 | get => _enableBatchedOperations; 108 | set 109 | { 110 | _enableBatchedOperations = value; 111 | this.Notify(PropertyChanged); 112 | } 113 | } 114 | 115 | private int? _maxDeliveryCount; 116 | 117 | [Label("Max delivery count")] 118 | [Required] 119 | public int? MaxDeliveryCount 120 | { 121 | get => _maxDeliveryCount; 122 | set 123 | { 124 | _maxDeliveryCount = value; 125 | this.Notify(PropertyChanged); 126 | } 127 | } 128 | 129 | 130 | private string? _forwardTo; 131 | 132 | [Label("Forward to (queue or topic)")] 133 | public string? ForwardTo 134 | { 135 | get => _forwardTo; 136 | set 137 | { 138 | _forwardTo = value; 139 | this.Notify(PropertyChanged); 140 | } 141 | } 142 | 143 | private string? _forwardDeadLetteredMessagesTo; 144 | 145 | [Label("Forward deadletter messages to")] 146 | public string? ForwardDeadLetteredMessagesTo 147 | { 148 | get => _forwardDeadLetteredMessagesTo; 149 | set 150 | { 151 | _forwardDeadLetteredMessagesTo = value; 152 | this.Notify(PropertyChanged); 153 | } 154 | } 155 | 156 | private TimeSpan? _lockDuration; 157 | 158 | [Label("Lock duration")] 159 | [Required(ErrorMessage = "Field is required. Format: DDDD.HH:MM:SS.")] 160 | public TimeSpan? LockDuration 161 | { 162 | get => _lockDuration; 163 | set 164 | { 165 | _lockDuration = value; 166 | this.Notify(PropertyChanged); 167 | } 168 | } 169 | 170 | private bool? _requiresSession; 171 | 172 | [Label("Require session")] 173 | public bool? RequiresSession 174 | { 175 | get => _requiresSession; 176 | set 177 | { 178 | _requiresSession = value; 179 | this.Notify(PropertyChanged); 180 | } 181 | } 182 | 183 | private bool? _deadLetteringOnMessageExpiration; 184 | 185 | [Label("Require session")] 186 | public bool? DeadLetteringOnMessageExpiration 187 | { 188 | get => _deadLetteringOnMessageExpiration; 189 | set 190 | { 191 | _deadLetteringOnMessageExpiration = value; 192 | this.Notify(PropertyChanged); 193 | } 194 | } 195 | 196 | private bool? _enableDeadLetteringOnFilterEvaluationExceptions; 197 | 198 | [Label("Enable partitioning")] 199 | public bool? EnableDeadLetteringOnFilterEvaluationExceptions 200 | { 201 | get => _enableDeadLetteringOnFilterEvaluationExceptions; 202 | set 203 | { 204 | _enableDeadLetteringOnFilterEvaluationExceptions = value; 205 | this.Notify(PropertyChanged); 206 | } 207 | } 208 | } -------------------------------------------------------------------------------- /src/Ui/Website/Pages/Rules.razor: -------------------------------------------------------------------------------- 1 | @using CrossBusExplorer.ServiceBus.Contracts.Types 2 | @using CrossBusExplorer.Website.Models 3 | @using CrossBusExplorer.Website.Models.Validators 4 | @using CrossBusExplorer.Website.ViewModels 5 | @inherits LayoutComponentBase 6 | 7 | 8 |
9 | 10 | Create rule 11 | 12 |
13 | 14 | 15 | 16 | 17 | Actions 18 | 19 | 20 | Name 21 | 22 | 23 | Type 24 | 25 | 26 | Value 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | @context.Name 39 | @context.Type 40 | @(context.Value ?? "-") 41 | 42 | 43 |
44 | 45 | 46 | 47 | 48 | @(Model.Form.OperationType == OperationType.Create ? "Create" : "Edit") rule for subscription @SubscriptionName (@TopicName topic) 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 62 | 63 | 64 | 65 | 66 | 68 | 71 | True rule 72 | 73 | 76 | False rule 77 | 78 | 81 | Sql rule 82 | 83 | 86 | CorrelationId rule 87 | 88 | 89 | 90 | 91 | 92 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 109 | Close 110 | 111 | 115 | Submit 116 | 117 | 118 | 119 | 120 | @code { 121 | [Parameter] 122 | public string ConnectionName { get; set; } 123 | [Parameter] 124 | public string TopicName { get; set; } 125 | [Parameter] 126 | public string SubscriptionName { get; set; } 127 | [Inject] 128 | IRulesViewModel Model { get; set; } 129 | MudForm _form; 130 | RuleFormValidator _validator = new RuleFormValidator(); 131 | 132 | protected override async Task OnInitializedAsync() 133 | { 134 | Model.InitializeAsync(ConnectionName, TopicName, SubscriptionName, default); 135 | 136 | Model.PropertyChanged += (_, _) => StateHasChanged(); 137 | } 138 | private async Task OnFormSubmit() 139 | { 140 | await _form.Validate(); 141 | 142 | if (_form.IsValid) 143 | { 144 | await Model.OnSubmitFormAsync(ConnectionName, TopicName, SubscriptionName, default); 145 | } 146 | } 147 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 5 | 6 | # User-specific files 7 | *.suo 8 | *.user 9 | *.userosscache 10 | *.sln.docstates 11 | 12 | # User-specific files (MonoDevelop/Xamarin Studio) 13 | *.userprefs 14 | 15 | # Build results 16 | [Dd]ebug/ 17 | [Dd]ebugPublic/ 18 | [Rr]elease/ 19 | [Rr]eleases/ 20 | x64/ 21 | x86/ 22 | bld/ 23 | [Bb]in/ 24 | [Oo]bj/ 25 | [Ll]og/ 26 | 27 | # Visual Studio 2015 cache/options directory 28 | .vs/ 29 | # Uncomment if you have tasks that create the project's static files in wwwroot 30 | #wwwroot/ 31 | 32 | # MSTest test Results 33 | [Tt]est[Rr]esult*/ 34 | [Bb]uild[Ll]og.* 35 | 36 | # NUNIT 37 | *.VisualState.xml 38 | TestResult.xml 39 | 40 | # Build Results of an ATL Project 41 | [Dd]ebugPS/ 42 | [Rr]eleasePS/ 43 | dlldata.c 44 | 45 | # .NET Core 46 | project.lock.json 47 | project.fragment.lock.json 48 | artifacts/ 49 | **/Properties/launchSettings.json 50 | 51 | *_i.c 52 | *_p.c 53 | *_i.h 54 | *.ilk 55 | *.meta 56 | *.obj 57 | *.pch 58 | *.pdb 59 | *.pgc 60 | *.pgd 61 | *.rsp 62 | *.sbr 63 | *.tlb 64 | *.tli 65 | *.tlh 66 | *.tmp 67 | *.tmp_proj 68 | *.log 69 | *.vspscc 70 | *.vssscc 71 | .builds 72 | *.pidb 73 | *.svclog 74 | *.scc 75 | 76 | # Chutzpah Test files 77 | _Chutzpah* 78 | 79 | # Visual C++ cache files 80 | ipch/ 81 | *.aps 82 | *.ncb 83 | *.opendb 84 | *.opensdf 85 | *.sdf 86 | *.cachefile 87 | *.VC.db 88 | *.VC.VC.opendb 89 | 90 | # Visual Studio profiler 91 | *.psess 92 | *.vsp 93 | *.vspx 94 | *.sap 95 | 96 | # TFS 2012 Local Workspace 97 | $tf/ 98 | 99 | # Guidance Automation Toolkit 100 | *.gpState 101 | 102 | # ReSharper is a .NET coding add-in 103 | _ReSharper*/ 104 | *.[Rr]e[Ss]harper 105 | *.DotSettings.user 106 | 107 | # JustCode is a .NET coding add-in 108 | .JustCode 109 | 110 | # TeamCity is a build add-in 111 | _TeamCity* 112 | 113 | # DotCover is a Code Coverage Tool 114 | *.dotCover 115 | 116 | # Visual Studio code coverage results 117 | *.coverage 118 | *.coveragexml 119 | 120 | # NCrunch 121 | _NCrunch_* 122 | .*crunch*.local.xml 123 | nCrunchTemp_* 124 | 125 | # MightyMoose 126 | *.mm.* 127 | AutoTest.Net/ 128 | 129 | # Web workbench (sass) 130 | .sass-cache/ 131 | 132 | # Installshield output folder 133 | [Ee]xpress/ 134 | 135 | # DocProject is a documentation generator add-in 136 | DocProject/buildhelp/ 137 | DocProject/Help/*.HxT 138 | DocProject/Help/*.HxC 139 | DocProject/Help/*.hhc 140 | DocProject/Help/*.hhk 141 | DocProject/Help/*.hhp 142 | DocProject/Help/Html2 143 | DocProject/Help/html 144 | 145 | # Click-Once directory 146 | publish/ 147 | 148 | # Publish Web Output 149 | *.[Pp]ublish.xml 150 | *.azurePubxml 151 | # TODO: Comment the next line if you want to checkin your web deploy settings 152 | # but database connection strings (with potential passwords) will be unencrypted 153 | *.pubxml 154 | *.publishproj 155 | 156 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 157 | # checkin your Azure Web App publish settings, but sensitive information contained 158 | # in these scripts will be unencrypted 159 | PublishScripts/ 160 | 161 | # NuGet Packages 162 | *.nupkg 163 | # The packages folder can be ignored because of Package Restore 164 | **/packages/* 165 | # except build/, which is used as an MSBuild target. 166 | !**/packages/build/ 167 | # Uncomment if necessary however generally it will be regenerated when needed 168 | #!**/packages/repositories.config 169 | # NuGet v3's project.json files produces more ignorable files 170 | *.nuget.props 171 | *.nuget.targets 172 | .packages 173 | 174 | # Microsoft Azure Build Output 175 | csx/ 176 | *.build.csdef 177 | 178 | # Microsoft Azure Emulator 179 | ecf/ 180 | rcf/ 181 | 182 | # Windows Store app package directories and files 183 | AppPackages/ 184 | BundleArtifacts/ 185 | Package.StoreAssociation.xml 186 | _pkginfo.txt 187 | 188 | # Visual Studio cache files 189 | # files ending in .cache can be ignored 190 | *.[Cc]ache 191 | # but keep track of directories ending in .cache 192 | !*.[Cc]ache/ 193 | 194 | # Others 195 | ClientBin/ 196 | ~$* 197 | *~ 198 | *.dbmdl 199 | *.dbproj.schemaview 200 | *.jfm 201 | *.pfx 202 | *.publishsettings 203 | orleans.codegen.cs 204 | 205 | # Since there are multiple workflows, uncomment next line to ignore bower_components 206 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 207 | #bower_components/ 208 | 209 | # RIA/Silverlight projects 210 | Generated_Code/ 211 | 212 | # Backup & report files from converting an old project file 213 | # to a newer Visual Studio version. Backup files are not needed, 214 | # because we have git ;-) 215 | _UpgradeReport_Files/ 216 | Backup*/ 217 | UpgradeLog*.XML 218 | UpgradeLog*.htm 219 | 220 | # SQL Server files 221 | *.mdf 222 | *.ldf 223 | *.ndf 224 | 225 | # Business Intelligence projects 226 | *.rdl.data 227 | *.bim.layout 228 | *.bim_*.settings 229 | 230 | # Microsoft Fakes 231 | FakesAssemblies/ 232 | 233 | # GhostDoc plugin setting file 234 | *.GhostDoc.xml 235 | 236 | # Node.js Tools for Visual Studio 237 | .ntvs_analysis.dat 238 | node_modules/ 239 | 240 | # Typescript v1 declaration files 241 | typings/ 242 | 243 | # Visual Studio 6 build log 244 | *.plg 245 | 246 | # Visual Studio 6 workspace options file 247 | *.opt 248 | 249 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 250 | *.vbw 251 | 252 | # Visual Studio LightSwitch build output 253 | **/*.HTMLClient/GeneratedArtifacts 254 | **/*.DesktopClient/GeneratedArtifacts 255 | **/*.DesktopClient/ModelManifest.xml 256 | **/*.Server/GeneratedArtifacts 257 | **/*.Server/ModelManifest.xml 258 | _Pvt_Extensions 259 | 260 | # Paket dependency manager 261 | .paket/paket.exe 262 | paket-files/ 263 | 264 | # FAKE - F# Make 265 | .fake/ 266 | 267 | # JetBrains Rider 268 | .idea/ 269 | *.sln.iml 270 | 271 | # CodeRush 272 | .cr/ 273 | 274 | # Python Tools for Visual Studio (PTVS) 275 | __pycache__/ 276 | *.pyc 277 | 278 | # Cake - Uncomment if you are using it 279 | tools/** 280 | !tools/nuget.config 281 | 282 | # Telerik's JustMock configuration file 283 | *.jmconfig 284 | 285 | # BizTalk build output 286 | *.btp.cs 287 | *.btm.cs 288 | *.odx.cs 289 | *.xsd.cs 290 | 291 | .vscode/ 292 | .vs/ 293 | .drop/ 294 | testoutput/ 295 | artifacts/ 296 | Published/ 297 | src/Published 298 | *.vaultconfig.json 299 | src/Ui/reports/ 300 | *.user.json 301 | __mismatch__ 302 | 303 | output 304 | **/All.sln 305 | **/Tests.sln 306 | package.zip 307 | .DS_Store 308 | .nuke/temp 309 | /.tye 310 | --------------------------------------------------------------------------------