Rehman.DbSentinel 2.0.0

dotnet add package Rehman.DbSentinel --version 2.0.0
                    
NuGet\Install-Package Rehman.DbSentinel -Version 2.0.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Rehman.DbSentinel" Version="2.0.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Rehman.DbSentinel" Version="2.0.0" />
                    
Directory.Packages.props
<PackageReference Include="Rehman.DbSentinel" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add Rehman.DbSentinel --version 2.0.0
                    
#r "nuget: Rehman.DbSentinel, 2.0.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package Rehman.DbSentinel@2.0.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=Rehman.DbSentinel&version=2.0.0
                    
Install as a Cake Addin
#tool nuget:?package=Rehman.DbSentinel&version=2.0.0
                    
Install as a Cake Tool

Rehman.DbSentinel

NuGet License: MIT

A production-ready, read-only embedded database insights dashboard for .NET core web applications.
Think pgAdmin or SSMS — but living inside your app at /app-data-insights.

Supports PostgreSQL · SQL Server · SQLite


Features

Category Feature
Schema Explorer Collapsible sidebar tree: Tables → Columns, Constraints, Relations, Indexes
Table Context Menu Right-click: Top 100, Top 1000, Last 100, Last 1000, SELECT Script, Query Editor
Data View Paginated table data, column sort, global fuzzy search with match highlighting
Schema Tab Full columns list with PK/FK badges, constraints table, CREATE TABLE script
Statistics Tab Row count, column count, size on disk, all indexes
Query Editor CodeMirror SQL editor, syntax highlight, table+column autocomplete, Ctrl+Enter to run
Query History Every query stored with IP, duration, row count, success/error status
Row Hierarchy Right-click → View Full Hierarchy: parent rows (green), current (grey), child rows (red)
Change Tracking Auto-creates _log tables per tracked table in dbsentinel schema
Diff Viewer Git-style green/red diff for every log entry
Log Dashboard Dedicated tab: total entries card, creates/updates/deletes cards, daily line chart
Main Dashboard Stats cards, largest tables, daily changes bar chart, health summary
Retention Cleanup Background service runs every 24h, deletes logs older than LogRetentionDays
Read-Only Enforcement Server-side QueryValidator blocks INSERT/UPDATE/DELETE/DROP/ALTER etc.
CSV Export Export any table data view to CSV with one click

Installation

dotnet add package Rehman.DbSentinel

Quick Start

1. Configure in Program.cs

using Rehman.DbSentinel;
using Rehman.DbSentinel.Configuration;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddDbSentinel(options =>
{
    options.ConnectionString = "Host=localhost;Database=myapp;Username=postgres;Password=secret";
    options.DatabaseType     = DatabaseType.PostgreSQL;

    options.TrackTables = new[] { "Users", "Orders", "Products" };

    options.LogRetentionDays = 30;
    options.DashboardTitle   = "MyApp — Database Insights";
    options.RoutePrefix      = "app-data-insights";
    options.MaxQueryRows     = 5000;
});

builder.Services.AddControllers();

var app = builder.Build();

app.UseDbSentinel();   // ← mounts the dashboard + creates internal tables

app.UseHttpsRedirection();
app.MapControllers();
app.Run();

2. Open the Dashboard

Navigate to http://localhost:5000/app-data-insights


Database Connection Examples

PostgreSQL

options.ConnectionString = "Host=localhost;Port=5432;Database=myapp;Username=postgres;Password=secret";
options.DatabaseType     = DatabaseType.PostgreSQL;

SQL Server

options.ConnectionString = "Server=localhost;Database=MyApp;Trusted_Connection=true;TrustServerCertificate=true;";
options.DatabaseType     = DatabaseType.SqlServer;

SQLite

options.ConnectionString = "Data Source=myapp.db";
options.DatabaseType     = DatabaseType.SQLite;

Configuration Reference

Option Type Default Description
ConnectionString string — Database connection string
DatabaseType DatabaseType PostgreSQL PostgreSQL, SqlServer, or SQLite
TrackTables string[] [] Tables to create change logs for
LogRetentionDays int 30 Days before log entries are auto-deleted. 0 = never
RoutePrefix string app-data-insights URL route for the dashboard
MaxQueryRows int 5000 Hard cap on rows returned per query
EnableQueryHistory bool true Save every executed query to history
DashboardTitle string DbSentinel — Database Insights Title shown in the browser tab
SentinelSchema string dbsentinel Schema for internal tables (PG/SQL Server). Prefix for SQLite

Security

Read-Only Enforcement

Every user query passes through QueryValidator before execution:

  1. Comment stripping — block comments /* */ and line comments -- are removed before parsing
  2. String literal stripping — quoted values are ignored so 'DROP TABLE' can't hide a keyword
  3. Keyword blocklist — INSERT, UPDATE, DELETE, DROP, ALTER, TRUNCATE, EXEC, GRANT, REVOKE, MERGE, CREATE, CALL, PRAGMA, ATTACH, VACUUM, DBCC, BULK, SHUTDOWN, KILL and more
  4. Must start with SELECT or WITH — only SELECT statements and CTEs are allowed
  5. Single statement only — stacked queries (; DROP TABLE) are rejected
  6. Query timeout — 30-second hard timeout on all executions
  7. Row limit — all queries are wrapped with LIMIT / TOP using MaxQueryRows

Protecting the Dashboard

DbSentinel exposes schema and data — protect it in production:

// Option A: Localhost-only access
app.Use(async (ctx, next) => {
    if (ctx.Request.Path.StartsWithSegments("/app-data-insights")) {
        var ip = ctx.Connection.RemoteIpAddress?.ToString();
        if (ip != "127.0.0.1" && ip != "::1") { ctx.Response.StatusCode = 403; return; }
    }
    await next();
});
app.UseDbSentinel();

// Option B: API key header
app.Use(async (ctx, next) => {
    if (ctx.Request.Path.StartsWithSegments("/app-data-insights")) {
        if (ctx.Request.Headers["X-Sentinel-Key"] != configuration["DbSentinel:Key"])
        { ctx.Response.StatusCode = 401; return; }
    }
    await next();
});
app.UseDbSentinel();

// Option C: ASP.NET Core auth role
app.Use(async (ctx, next) => {
    if (ctx.Request.Path.StartsWithSegments("/app-data-insights") && !ctx.User.IsInRole("Admin"))
    { ctx.Response.StatusCode = 403; return; }
    await next();
});
app.UseDbSentinel();

Tip: Use a dedicated read-only database user for the connection string. Belt-and-suspenders with the query validation.


Change Tracking

Automatic Log Table Creation

On app.UseDbSentinel(), for each table in TrackTables, a log table is created automatically:

dbsentinel.Users_log
dbsentinel.Orders_log
dbsentinel.Products_log

Log table schema:

CREATE TABLE dbsentinel.Users_log (
    log_id         BIGSERIAL PRIMARY KEY,
    operation_type VARCHAR(10) NOT NULL,   -- INSERT, UPDATE, DELETE
    old_data       JSONB,                  -- Previous state  (NULL for INSERT)
    new_data       JSONB,                  -- New state       (NULL for DELETE)
    timestamp      TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    db_user        VARCHAR(100)
);

Logging from Application Code

Inject ITrackingService into your services:

public class OrderService
{
    private readonly ITrackingService _tracking;
    private readonly AppDbContext _db;

    public OrderService(ITrackingService tracking, AppDbContext db)
    {
        _tracking = tracking;
        _db = db;
    }

    public async Task UpdateAsync(int id, UpdateOrderDto dto)
    {
        var order = await _db.Orders.FindAsync(id);
        var before = new { order.Status, order.Total };   // capture snapshot

        order.Status = dto.Status;
        order.Total  = dto.Total;
        await _db.SaveChangesAsync();

        await _tracking.LogChangeAsync("Orders", "UPDATE", before, order, dbUser: "api");
    }

    public async Task DeleteAsync(int id)
    {
        var order = await _db.Orders.FindAsync(id);
        _db.Orders.Remove(order!);
        await _db.SaveChangesAsync();

        await _tracking.LogChangeAsync("Orders", "DELETE", order, null, dbUser: "api");
    }
}

Database-Level Triggers (Optional)

For 100% capture without app-side calls, apply the trigger SQL scripts in the Migrations/ folder:

# PostgreSQL — installs a universal trigger function + attaches to your tables
psql -d myapp -f Migrations/postgresql_setup.sql

# SQL Server
sqlcmd -S localhost -d MyApp -i Migrations/sqlserver_setup.sql

# SQLite
sqlite3 myapp.db < Migrations/sqlite_setup.sql

Query Editor

  • Syntax highlighting — CodeMirror with SQL mode
  • Auto-complete — Ctrl+Space for table and column suggestions loaded from live schema
  • Ctrl+Enter — run query without clicking
  • Format — basic SQL keyword uppercasing
  • History panel — last 30 queries shown with timestamp, duration, row count, status
  • Click history item — loads it back into the editor
  • Read-only enforced — server validates before executing, not just client-side

Table Context Menu (Right-Click on Tree)

Option SQL Generated
Get Top 100 Rows SELECT * FROM "table" LIMIT 100
Get Top 1000 Rows SELECT * FROM "table" LIMIT 1000
Get Last 100 Rows SELECT * FROM "table" ORDER BY pk DESC LIMIT 100
Get Last 1000 Rows SELECT * FROM "table" ORDER BY pk DESC LIMIT 1000
SELECT Script Formatted SELECT col1, col2... FROM "table" copied to clipboard
Open Query Editor Focuses the Query Editor tab

Row Hierarchy Viewer

Right-click any data row → View Full Hierarchy

┌──────────────────────────────────────────────────────┐
│  ↑ Parent Rows (they reference)    [Light Green]     │
│  ┌────────────────────────────────────────────────┐  │
│  │ customers · via customer_id                    │  │
│  │ id: 5  name: Alice  email: alice@example.com   │  │
│  └────────────────────────────────────────────────┘  │
│                                                      │
│  ● Current Row                     [Grey]            │
│  id: 42  customer_id: 5  total: 150.00               │
│                                                      │
│  ↓ Child Rows (they reference this) [Light Red]      │
│  ┌────────────────────────────────────────────────┐  │
│  │ order_items · via order_id                     │  │
│  │ id: 101  product: Widget  qty: 3               │  │
│  │ id: 102  product: Gadget  qty: 1               │  │
│  └────────────────────────────────────────────────┘  │
└──────────────────────────────────────────────────────┘

Internal API Endpoints

All endpoints are under /{routePrefix}/api/:

Method Endpoint Description
GET tables All tables with row counts
GET schema-tree Full schema: tables + columns + constraints
GET stats Database-level stats
GET config Dashboard configuration
GET query-history?limit=50 Last N executed queries
GET log-summary?days=30 Aggregate log stats + daily breakdown
GET daily-changes?days=30 Per-day INSERT/UPDATE/DELETE counts
GET tables/{name}/columns Table columns
GET tables/{name}/constraints Table constraints and FK refs
GET tables/{name}/indexes Table indexes
GET tables/{name}/stats Row count, size, indexes, CREATE script
GET tables/{name}/data?limit&offset&search&orderBy&dir Paginated table data
GET tables/{name}/logs?limit=100 Change log entries
POST query Execute a SELECT query { "sql": "..." }
GET hierarchy?table&pkCol&pkVal Row parent/child hierarchy

Retention

Log retention runs automatically every 24 hours via RetentionBackgroundService.

It deletes:

  • query_history entries older than LogRetentionDays
  • All {Table}_log entries older than LogRetentionDays

Set LogRetentionDays = 0 to disable automatic cleanup entirely.


License

MIT © Muhammad Rehman Tahir

Product Compatible and additional computed target framework versions.
.NET net5.0 is compatible.  net5.0-windows was computed.  net6.0 is compatible.  net6.0-android was computed.  net6.0-ios was computed.  net6.0-maccatalyst was computed.  net6.0-macos was computed.  net6.0-tvos was computed.  net6.0-windows was computed.  net7.0 is compatible.  net7.0-android was computed.  net7.0-ios was computed.  net7.0-maccatalyst was computed.  net7.0-macos was computed.  net7.0-tvos was computed.  net7.0-windows was computed.  net8.0 is compatible.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 is compatible.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 was computed.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
.NET Core netcoreapp2.1 is compatible.  netcoreapp2.2 was computed.  netcoreapp3.0 was computed.  netcoreapp3.1 is compatible. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
2.0.0 246 5/12/2026
1.0.0 115 5/11/2026