diff --git a/backend/.gitignore b/backend/.gitignore new file mode 100644 index 0000000..c2c0c5e --- /dev/null +++ b/backend/.gitignore @@ -0,0 +1,21 @@ +## .NET +bin/ +obj/ +*.user +*.suo +*.cache +*.dll +*.pdb + +## IDE +.vs/ +.idea/ +*.swp +*~ + +## OS +.DS_Store +Thumbs.db + +## Environment +.env \ No newline at end of file diff --git a/backend/Configurations/AgentConfiguration.cs b/backend/Configurations/AgentConfiguration.cs new file mode 100644 index 0000000..0f9d304 --- /dev/null +++ b/backend/Configurations/AgentConfiguration.cs @@ -0,0 +1,88 @@ +using ControlCenter.Api.Entities; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata.Builders; + +namespace ControlCenter.Api.Configurations; + +/// +/// EF Core entity type configuration for the agents table. +/// Enforces snake_case naming, required fields, and index design. +/// +public class AgentConfiguration : IEntityTypeConfiguration +{ + public void Configure(EntityTypeBuilder builder) + { + // Table name — snake_case + builder.ToTable("agents"); + + // Primary key + builder.HasKey(a => a.Id); + builder.Property(a => a.Id) + .HasColumnName("id") + .ValueGeneratedOnAdd(); + + // Status — stored as PostgreSQL enum via Npgsql + builder.Property(a => a.Status) + .HasColumnName("status") + .HasColumnType("agent_status") + .IsRequired(); + + // Task — nullable text + builder.Property(a => a.Task) + .HasColumnName("task") + .HasColumnType("text"); + + // Progress — nullable integer (0–100) + builder.Property(a => a.Progress) + .HasColumnName("progress"); + + // Session key — required, not null + builder.Property(a => a.SessionKey) + .HasColumnName("session_key") + .HasColumnType("text") + .IsRequired(); + + // Channel — required, not null + builder.Property(a => a.Channel) + .HasColumnName("channel") + .HasColumnType("text") + .IsRequired(); + + // Last activity — required, defaults to now() + builder.Property(a => a.LastActivity) + .HasColumnName("last_activity") + .HasColumnType("timestamptz") + .IsRequired(); + + // Created at — auto-set on insert + builder.Property(a => a.CreatedAt) + .HasColumnName("created_at") + .HasColumnType("timestamptz") + .IsRequired() + .HasDefaultValueSql("now()"); + + // Updated at — auto-set on insert and update + builder.Property(a => a.UpdatedAt) + .HasColumnName("updated_at") + .HasColumnType("timestamptz") + .IsRequired() + .HasDefaultValueSql("now()"); + + // Indexes + // Sessions are looked up by session_key frequently + builder.HasIndex(a => a.SessionKey) + .HasDatabaseName("ix_agents_session_key") + .IsUnique(); + + // Agents are filtered by channel for channel-specific queries + builder.HasIndex(a => a.Channel) + .HasDatabaseName("ix_agents_channel"); + + // Agents are filtered by status for fleet health monitoring + builder.HasIndex(a => a.Status) + .HasDatabaseName("ix_agents_status"); + + // Check constraint: progress must be 0–100 if present + builder.ToTable(t => t.HasCheckConstraint("ck_agents_progress_range", "progress IS NULL OR (progress >= 0 AND progress <= 100)")); + } +} \ No newline at end of file diff --git a/backend/ControlCenter.Api.csproj b/backend/ControlCenter.Api.csproj new file mode 100644 index 0000000..d2896da --- /dev/null +++ b/backend/ControlCenter.Api.csproj @@ -0,0 +1,18 @@ + + + + net9.0 + enable + enable + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + diff --git a/backend/ControlCenter.Api.http b/backend/ControlCenter.Api.http new file mode 100644 index 0000000..f621eb8 --- /dev/null +++ b/backend/ControlCenter.Api.http @@ -0,0 +1,6 @@ +@ControlCenter.Api_HostAddress = http://localhost:5178 + +GET {{ControlCenter.Api_HostAddress}}/weatherforecast/ +Accept: application/json + +### diff --git a/backend/Data/AppDbContext.cs b/backend/Data/AppDbContext.cs new file mode 100644 index 0000000..3277f00 --- /dev/null +++ b/backend/Data/AppDbContext.cs @@ -0,0 +1,29 @@ +using ControlCenter.Api.Configurations; +using ControlCenter.Api.Entities; +using Microsoft.EntityFrameworkCore; + +namespace ControlCenter.Api.Data; + +/// +/// EF Core DbContext for the Control Center database. +/// All table and column names use snake_case via explicit HasColumnName configuration. +/// +public class AppDbContext : DbContext +{ + public AppDbContext(DbContextOptions options) : base(options) { } + + public DbSet Agents => Set(); + + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + // Apply all entity type configurations from the Configurations namespace + modelBuilder.ApplyConfigurationsFromAssembly(typeof(AgentConfiguration).Assembly); + + // Map the AgentStatus enum to a PostgreSQL enum type named "agent_status" + // This must be called after ApplyConfigurations to ensure the model is built + // before the enum mapping is applied. + modelBuilder.HasPostgresEnum(); + + base.OnModelCreating(modelBuilder); + } +} \ No newline at end of file diff --git a/backend/Data/AppDbContextFactory.cs b/backend/Data/AppDbContextFactory.cs new file mode 100644 index 0000000..7b82843 --- /dev/null +++ b/backend/Data/AppDbContextFactory.cs @@ -0,0 +1,27 @@ +using Microsoft.EntityFrameworkCore; +using ControlCenter.Api.Entities; + +namespace ControlCenter.Api.Data; + +/// +/// Design-time factory for AppDbContext, used by EF Core tools (dotnet ef) +/// to create migrations without requiring a running application. +/// +public class AppDbContextFactory : Microsoft.EntityFrameworkCore.Design.IDesignTimeDbContextFactory +{ + public AppDbContext CreateDbContext(string[] args) + { + var optionsBuilder = new DbContextOptionsBuilder(); + + // Connection string for design-time operations (migrations). + // In production, this comes from appsettings / environment variables. + var connectionString = "Host=localhost;Database=control_center;Username=postgres;Password=postgres"; + + optionsBuilder.UseNpgsql(connectionString, npgsqlOptions => + { + npgsqlOptions.MigrationsAssembly(typeof(AppDbContext).Assembly.FullName); + }); + + return new AppDbContext(optionsBuilder.Options); + } +} \ No newline at end of file diff --git a/backend/Dtos/AgentStatusUpdateDto.cs b/backend/Dtos/AgentStatusUpdateDto.cs new file mode 100644 index 0000000..b67e74d --- /dev/null +++ b/backend/Dtos/AgentStatusUpdateDto.cs @@ -0,0 +1,75 @@ +namespace ControlCenter.Api.Dtos; + +/// +/// Data transfer object for broadcasting agent status updates +/// to all connected SignalR clients. +/// +public class AgentStatusUpdateDto +{ + /// + /// Agent identifier, e.g. "otto", "dex", "rex". + /// Not null — every update must identify the agent it refers to. + /// + public string AgentId { get; set; } = string.Empty; + + /// + /// Human-readable display name, e.g. "Otto", "Dex". + /// Not null — used by clients to render agent cards. + /// + public string DisplayName { get; set; } = string.Empty; + + /// + /// Role description, e.g. "Orchestrator Agent", "Backend Specialist". + /// Not null — provides context for the agent's function. + /// + public string Role { get; set; } = string.Empty; + + /// + /// Current operational status of the agent. + /// Maps to values as lowercase strings: + /// "active", "idle", "thinking", "error". + /// + public string Status { get; set; } = string.Empty; + + /// + /// Description of the agent's current task, if any. + /// Null when the agent is idle with no active task. + /// + public string? CurrentTask { get; set; } + + /// + /// Task progress percentage (0–100). + /// Null when progress is not trackable for the current task. + /// + public int? TaskProgress { get; set; } + + /// + /// Elapsed time string for the current task, e.g. "04m 12s". + /// Null when no task is active. + /// + public string? TaskElapsed { get; set; } + + /// + /// Full session key, e.g. "agent:otto:telegram:direct:8787451565". + /// Not null — uniquely identifies the agent session. + /// + public string SessionKey { get; set; } = string.Empty; + + /// + /// Communication channel the agent is operating on, e.g. "telegram", "discord", "slack". + /// Not null — every agent session operates on exactly one channel. + /// + public string Channel { get; set; } = string.Empty; + + /// + /// ISO 8601 timestamp of the agent's last activity. + /// Not null — used by clients to detect stale connections. + /// + public string LastActivity { get; set; } = string.Empty; + + /// + /// Error message when the agent status is "error". + /// Null when the agent is not in an error state. + /// + public string? ErrorMessage { get; set; } +} \ No newline at end of file diff --git a/backend/Entities/Agent.cs b/backend/Entities/Agent.cs new file mode 100644 index 0000000..67be593 --- /dev/null +++ b/backend/Entities/Agent.cs @@ -0,0 +1,59 @@ +namespace ControlCenter.Api.Entities; + +/// +/// Represents an agent's current state in the Control Center. +/// Each row tracks one agent session's status, task, and activity. +/// +public class Agent +{ + /// + /// Primary key — UUID generated on insert. + /// + public Guid Id { get; set; } + + /// + /// Current operational status of the agent. + /// Stored as an enum in PostgreSQL via Npgsql. + /// + public AgentStatus Status { get; set; } = AgentStatus.Idle; + + /// + /// Description of the agent's current task, if any. + /// Nullable — not all agents have an active task. + /// + public string? Task { get; set; } + + /// + /// Task progress percentage (0–100). + /// Nullable — progress is only meaningful when an agent has a trackable task. + /// + public int? Progress { get; set; } + + /// + /// Full session key, e.g. "agent:otto:telegram:direct:8787451565". + /// Not null — every agent row must be associated with a session. + /// + public string SessionKey { get; set; } = string.Empty; + + /// + /// Communication channel the agent is operating on, e.g. "telegram", "discord", "slack". + /// Not null — every agent session operates on exactly one channel. + /// + public string Channel { get; set; } = string.Empty; + + /// + /// Timestamp of the agent's last activity. + /// Default: current UTC timestamp on insert. + /// + public DateTime LastActivity { get; set; } = DateTime.UtcNow; + + /// + /// Row creation timestamp. Set automatically on insert. + /// + public DateTime CreatedAt { get; set; } = DateTime.UtcNow; + + /// + /// Row last-update timestamp. Updated automatically on any modification. + /// + public DateTime UpdatedAt { get; set; } = DateTime.UtcNow; +} \ No newline at end of file diff --git a/backend/Entities/AgentStatus.cs b/backend/Entities/AgentStatus.cs new file mode 100644 index 0000000..3846fcc --- /dev/null +++ b/backend/Entities/AgentStatus.cs @@ -0,0 +1,13 @@ +namespace ControlCenter.Api.Entities; + +/// +/// Agent operational status enum. +/// Maps to the agent_status enum type in PostgreSQL. +/// +public enum AgentStatus +{ + Active = 0, + Idle = 1, + Thinking = 2, + Error = 3 +} \ No newline at end of file diff --git a/backend/Hubs/AgentStatusHub.cs b/backend/Hubs/AgentStatusHub.cs new file mode 100644 index 0000000..213804b --- /dev/null +++ b/backend/Hubs/AgentStatusHub.cs @@ -0,0 +1,155 @@ +using ControlCenter.Api.Dtos; +using Microsoft.AspNetCore.SignalR; + +namespace ControlCenter.Api.Hubs; + +/// +/// SignalR hub for broadcasting agent status updates to connected clients. +/// +/// +/// Clients call to broadcast a status change, +/// and the hub relays it to all connected clients via the +/// callback. +/// +/// +/// +/// Server-side code should use +/// via IHubContext<AgentStatusHub, IAgentStatusClient> for background-service broadcasts. +/// +/// +/// +/// Architecture note: This hub bridges OpenClaw Gateway events to SignalR clients. +/// A background service subscribes to Gateway events and pushes them through +/// this hub's extension methods. +/// +/// +public class AgentStatusHub : Hub +{ + private readonly ILogger _logger; + + /// + /// Initializes a new instance of the class. + /// + /// Logger for diagnostic output. + public AgentStatusHub(ILogger logger) + { + _logger = logger; + } + + /// + /// Broadcasts an agent status update to all connected clients. + /// + /// + /// Any connected client (or server-side caller) can invoke this method + /// to push a status update to every subscriber. The update is relayed + /// through the callback. + /// + /// + /// The agent status update payload to broadcast. + public async Task SendStatusUpdate(AgentStatusUpdateDto update) + { + _logger.LogInformation( + "Broadcasting status update for agent {AgentId}: {Status}", + update.AgentId, update.Status); + + await Clients.All.AgentStatusChanged(update); + } + + /// + /// Adds the calling connection to the fleet group. + /// Once joined, the client will receive all agent status updates. + /// + public async Task JoinFleet() + { + await Groups.AddToGroupAsync(Context.ConnectionId, FleetGroupName); + _logger.LogDebug("Connection {ConnectionId} joined fleet group", Context.ConnectionId); + } + + /// + /// Removes the calling connection from the fleet group. + /// + public async Task LeaveFleet() + { + await Groups.RemoveFromGroupAsync(Context.ConnectionId, FleetGroupName); + _logger.LogDebug("Connection {ConnectionId} left fleet group", Context.ConnectionId); + } + + /// + /// Overrides to log disconnections. + /// SignalR automatically removes disconnected connections from all groups. + /// + /// Exception that caused the disconnection, if any. + public override Task OnDisconnectedAsync(Exception? exception) + { + _logger.LogDebug("Connection {ConnectionId} disconnected", Context.ConnectionId); + return base.OnDisconnectedAsync(exception); + } + + /// + /// The SignalR group name for the entire fleet (all agents). + /// + internal const string FleetGroupName = "fleet"; +} + +/// +/// Strongly-typed client interface for the AgentStatus SignalR hub. +/// Defines the methods the server can invoke on connected clients +/// to push real-time agent status updates. +/// +public interface IAgentStatusClient +{ + /// + /// Pushes an agent status change to all subscribed clients. + /// Fired whenever an agent's operational status changes + /// (e.g., idle → active, active → thinking, active → error). + /// + /// The full status update payload. + /// A Task that completes when the client has processed the update. + Task AgentStatusChanged(AgentStatusUpdateDto update); +} + +/// +/// Extension methods for pushing real-time agent updates through +/// the of . +/// +/// +/// These methods are intended to be called from background services +/// or other server-side code that detects an agent state change, +/// using the injected IHubContext<AgentStatusHub, IAgentStatusClient>. +/// +/// +public static class AgentStatusHubExtensions +{ + /// + /// Pushes an agent status update to all connected clients. + /// + /// + /// Call this from any background service when an agent's + /// operational status changes (e.g., the Gateway reports a + /// session transition from "running" to "done"). + /// + /// + /// The hub context injected via DI. + /// The agent status update payload. + /// A Task that completes when the message has been sent to all clients. + public static async Task PushStatusUpdateAsync( + this IHubContext hubContext, + AgentStatusUpdateDto update) + { + await hubContext.Clients.All.AgentStatusChanged(update); + } + + /// + /// Pushes an agent status update to clients subscribed to the fleet group. + /// + /// The hub context injected via DI. + /// The agent status update payload. + /// A Task that completes when the message has been sent to the fleet group. + public static async Task PushStatusUpdateToFleetAsync( + this IHubContext hubContext, + AgentStatusUpdateDto update) + { + await hubContext.Clients.Group(AgentStatusHub.FleetGroupName) + .AgentStatusChanged(update); + } +} \ No newline at end of file diff --git a/backend/Migrations/20260426101703_CreateAgentsTable.Designer.cs b/backend/Migrations/20260426101703_CreateAgentsTable.Designer.cs new file mode 100644 index 0000000..8b99837 --- /dev/null +++ b/backend/Migrations/20260426101703_CreateAgentsTable.Designer.cs @@ -0,0 +1,94 @@ +// +using System; +using ControlCenter.Api.Data; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace ControlCenter.Api.Migrations +{ + [DbContext(typeof(AppDbContext))] + [Migration("20260426101703_CreateAgentsTable")] + partial class CreateAgentsTable + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "9.0.4") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.HasPostgresEnum(modelBuilder, "agent_status", new[] { "active", "idle", "thinking", "error" }); + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("ControlCenter.Api.Entities.Agent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("Channel") + .IsRequired() + .HasColumnType("text") + .HasColumnName("channel"); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamptz") + .HasColumnName("created_at") + .HasDefaultValueSql("now()"); + + b.Property("LastActivity") + .HasColumnType("timestamptz") + .HasColumnName("last_activity"); + + b.Property("Progress") + .HasColumnType("integer") + .HasColumnName("progress"); + + b.Property("SessionKey") + .IsRequired() + .HasColumnType("text") + .HasColumnName("session_key"); + + b.Property("Status") + .HasColumnType("agent_status") + .HasColumnName("status"); + + b.Property("Task") + .HasColumnType("text") + .HasColumnName("task"); + + b.Property("UpdatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamptz") + .HasColumnName("updated_at") + .HasDefaultValueSql("now()"); + + b.HasKey("Id"); + + b.HasIndex("Channel") + .HasDatabaseName("ix_agents_channel"); + + b.HasIndex("SessionKey") + .IsUnique() + .HasDatabaseName("ix_agents_session_key"); + + b.HasIndex("Status") + .HasDatabaseName("ix_agents_status"); + + b.ToTable("agents", null, t => + { + t.HasCheckConstraint("ck_agents_progress_range", "progress IS NULL OR (progress >= 0 AND progress <= 100)"); + }); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/backend/Migrations/20260426101703_CreateAgentsTable.cs b/backend/Migrations/20260426101703_CreateAgentsTable.cs new file mode 100644 index 0000000..4b38049 --- /dev/null +++ b/backend/Migrations/20260426101703_CreateAgentsTable.cs @@ -0,0 +1,61 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace ControlCenter.Api.Migrations +{ + /// + public partial class CreateAgentsTable : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AlterDatabase() + .Annotation("Npgsql:Enum:agent_status", "active,idle,thinking,error"); + + migrationBuilder.CreateTable( + name: "agents", + columns: table => new + { + id = table.Column(type: "uuid", nullable: false), + status = table.Column(type: "agent_status", nullable: false), + task = table.Column(type: "text", nullable: true), + progress = table.Column(type: "integer", nullable: true), + session_key = table.Column(type: "text", nullable: false), + channel = table.Column(type: "text", nullable: false), + last_activity = table.Column(type: "timestamptz", nullable: false), + created_at = table.Column(type: "timestamptz", nullable: false, defaultValueSql: "now()"), + updated_at = table.Column(type: "timestamptz", nullable: false, defaultValueSql: "now()") + }, + constraints: table => + { + table.PrimaryKey("PK_agents", x => x.id); + table.CheckConstraint("ck_agents_progress_range", "progress IS NULL OR (progress >= 0 AND progress <= 100)"); + }); + + migrationBuilder.CreateIndex( + name: "ix_agents_channel", + table: "agents", + column: "channel"); + + migrationBuilder.CreateIndex( + name: "ix_agents_session_key", + table: "agents", + column: "session_key", + unique: true); + + migrationBuilder.CreateIndex( + name: "ix_agents_status", + table: "agents", + column: "status"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "agents"); + } + } +} diff --git a/backend/Migrations/AppDbContextModelSnapshot.cs b/backend/Migrations/AppDbContextModelSnapshot.cs new file mode 100644 index 0000000..c32b60a --- /dev/null +++ b/backend/Migrations/AppDbContextModelSnapshot.cs @@ -0,0 +1,91 @@ +// +using System; +using ControlCenter.Api.Data; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace ControlCenter.Api.Migrations +{ + [DbContext(typeof(AppDbContext))] + partial class AppDbContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "9.0.4") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.HasPostgresEnum(modelBuilder, "agent_status", new[] { "active", "idle", "thinking", "error" }); + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("ControlCenter.Api.Entities.Agent", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("Channel") + .IsRequired() + .HasColumnType("text") + .HasColumnName("channel"); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamptz") + .HasColumnName("created_at") + .HasDefaultValueSql("now()"); + + b.Property("LastActivity") + .HasColumnType("timestamptz") + .HasColumnName("last_activity"); + + b.Property("Progress") + .HasColumnType("integer") + .HasColumnName("progress"); + + b.Property("SessionKey") + .IsRequired() + .HasColumnType("text") + .HasColumnName("session_key"); + + b.Property("Status") + .HasColumnType("agent_status") + .HasColumnName("status"); + + b.Property("Task") + .HasColumnType("text") + .HasColumnName("task"); + + b.Property("UpdatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("timestamptz") + .HasColumnName("updated_at") + .HasDefaultValueSql("now()"); + + b.HasKey("Id"); + + b.HasIndex("Channel") + .HasDatabaseName("ix_agents_channel"); + + b.HasIndex("SessionKey") + .IsUnique() + .HasDatabaseName("ix_agents_session_key"); + + b.HasIndex("Status") + .HasDatabaseName("ix_agents_status"); + + b.ToTable("agents", null, t => + { + t.HasCheckConstraint("ck_agents_progress_range", "progress IS NULL OR (progress >= 0 AND progress <= 100)"); + }); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/backend/Program.cs b/backend/Program.cs new file mode 100644 index 0000000..c45c355 --- /dev/null +++ b/backend/Program.cs @@ -0,0 +1,38 @@ +using ControlCenter.Api.Data; +using ControlCenter.Api.Hubs; +using Microsoft.EntityFrameworkCore; + +var builder = WebApplication.CreateBuilder(args); + +// Add services to the container. +builder.Services.AddOpenApi(); + +// Register SignalR for real-time agent status updates +builder.Services.AddSignalR(); + +// Register DbContext with PostgreSQL +builder.Services.AddDbContext(options => +{ + var connectionString = builder.Configuration.GetConnectionString("DefaultConnection") + ?? "Host=localhost;Database=control_center;Username=postgres;Password=postgres"; + + options.UseNpgsql(connectionString, npgsqlOptions => + { + npgsqlOptions.MigrationsAssembly(typeof(AppDbContext).Assembly.FullName); + }); +}); + +var app = builder.Build(); + +// Configure the HTTP request pipeline. +if (app.Environment.IsDevelopment()) +{ + app.MapOpenApi(); +} + +app.UseHttpsRedirection(); + +// Map SignalR hubs +app.MapHub("/hubs/agent-status"); + +app.Run(); \ No newline at end of file diff --git a/backend/Properties/launchSettings.json b/backend/Properties/launchSettings.json new file mode 100644 index 0000000..5a40284 --- /dev/null +++ b/backend/Properties/launchSettings.json @@ -0,0 +1,23 @@ +{ + "$schema": "https://json.schemastore.org/launchsettings.json", + "profiles": { + "http": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": false, + "applicationUrl": "http://localhost:5178", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "https": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": false, + "applicationUrl": "https://localhost:7041;http://localhost:5178", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff --git a/backend/appsettings.Development.json b/backend/appsettings.Development.json new file mode 100644 index 0000000..ff66ba6 --- /dev/null +++ b/backend/appsettings.Development.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } +} diff --git a/backend/appsettings.json b/backend/appsettings.json new file mode 100644 index 0000000..0181f7d --- /dev/null +++ b/backend/appsettings.json @@ -0,0 +1,12 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*", + "ConnectionStrings": { + "DefaultConnection": "Host=localhost;Database=control_center;Username=postgres;Password=postgres" + } +}