diff --git a/docs/stacks/vue/coalesce-vue-vuetify/components/c-admin-audit-log-page.md b/docs/stacks/vue/coalesce-vue-vuetify/components/c-admin-audit-log-page.md index 2e1caa51a..91cc31abc 100644 --- a/docs/stacks/vue/coalesce-vue-vuetify/components/c-admin-audit-log-page.md +++ b/docs/stacks/vue/coalesce-vue-vuetify/components/c-admin-audit-log-page.md @@ -4,13 +4,12 @@ A full-featured page for interacting with Coalesce's [Audit Logging](/topics/audit-logging.md). Presents a view similar to [c-admin-table-page](/stacks/vue/coalesce-vue-vuetify/components/c-admin-table-page.md) with content optimized for viewing audit log records. Designed to be routed to directly with [vue-router](https://router.vuejs.org/). -Currently only supports Vue3. - ## Examples ``` ts +import { CAdminAuditLogPage } from 'coalesce-vue-vuetify3'; const router = new Router({ // ... routes: [ diff --git a/docs/topics/audit-logging.md b/docs/topics/audit-logging.md index 0e6e381f8..27c79380f 100644 --- a/docs/topics/audit-logging.md +++ b/docs/topics/audit-logging.md @@ -84,6 +84,20 @@ When you're inheriting from `DefaultObjectChange` for your `IObjectChange` imple The operation context class passed to `WithAugmentation` will be injected from the application service provider if available; otherwise, a new instance will be constructed using dependencies from the application service provider. To make an injected dependency optional, make the constructor parameter nullable with a default value of `null`, or create [alternate constructors](https://learn.microsoft.com/en-us/dotnet/core/extensions/dependency-injection#multiple-constructor-discovery-rules). +### 4. Add the UI + +For Vue applications, the [c-admin-audit-log-page](/stacks/vue/coalesce-vue-vuetify/components/c-admin-audit-log-page.md) component provides an out-of-the-box user interface for browsing through audit logs. Simply define the following route in your application's router: + +``` ts +import { CAdminAuditLogPage } from 'coalesce-vue-vuetify3'; + +{ + path: '/admin/audit-logs', + component: CAdminAuditLogPage, + props: { type: 'ObjectChange' } +} +``` + ## Configuration ### Suppression diff --git a/playground/Coalesce.Domain/AppDbContext.cs b/playground/Coalesce.Domain/AppDbContext.cs index 3d36284e2..69f794494 100644 --- a/playground/Coalesce.Domain/AppDbContext.cs +++ b/playground/Coalesce.Domain/AppDbContext.cs @@ -41,7 +41,10 @@ protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) .UseCoalesceAuditLogging(x => x .WithAugmentation() .WithMergeWindow(TimeSpan.FromSeconds(15)) - // TODO: add nice spot here to setup Z EFPLUS config + .ConfigureAudit(x => x + // Just a random example of EFPlus config: + .ExcludeProperty(p => p.ProfilePic) + ) ); } diff --git a/playground/Coalesce.Domain/Coalesce.Domain.csproj b/playground/Coalesce.Domain/Coalesce.Domain.csproj index 809777718..8c1bc4eb3 100644 --- a/playground/Coalesce.Domain/Coalesce.Domain.csproj +++ b/playground/Coalesce.Domain/Coalesce.Domain.csproj @@ -2,24 +2,14 @@ - net6.0;net7.0;net8.0 false enable - - TargetFramework=$(TargetFramework) - - - TargetFramework=$(TargetFramework) - + + diff --git a/playground/Coalesce.Domain/Migrations/20231013200054_AddAuditLogging.Designer.cs b/playground/Coalesce.Domain/Migrations/20231017172408_AddAuditLogging.Designer.cs similarity index 97% rename from playground/Coalesce.Domain/Migrations/20231013200054_AddAuditLogging.Designer.cs rename to playground/Coalesce.Domain/Migrations/20231017172408_AddAuditLogging.Designer.cs index 1c671eab0..80cf55e21 100644 --- a/playground/Coalesce.Domain/Migrations/20231013200054_AddAuditLogging.Designer.cs +++ b/playground/Coalesce.Domain/Migrations/20231017172408_AddAuditLogging.Designer.cs @@ -12,7 +12,7 @@ namespace Coalesce.Domain.Migrations { [DbContext(typeof(AppDbContext))] - [Migration("20231013200054_AddAuditLogging")] + [Migration("20231017172408_AddAuditLogging")] partial class AddAuditLogging { protected override void BuildTargetModel(ModelBuilder modelBuilder) @@ -190,15 +190,24 @@ protected override void BuildTargetModel(ModelBuilder modelBuilder) SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"), 1L, 1); + b.Property("ClientIp") + .HasColumnType("nvarchar(max)"); + b.Property("Date") .HasColumnType("datetimeoffset"); + b.Property("Endpoint") + .HasColumnType("nvarchar(max)"); + b.Property("KeyValue") .HasColumnType("nvarchar(450)"); b.Property("Message") .HasColumnType("nvarchar(max)"); + b.Property("Referrer") + .HasColumnType("nvarchar(max)"); + b.Property("State") .HasColumnType("tinyint"); diff --git a/playground/Coalesce.Domain/Migrations/20231013200054_AddAuditLogging.cs b/playground/Coalesce.Domain/Migrations/20231017172408_AddAuditLogging.cs similarity index 94% rename from playground/Coalesce.Domain/Migrations/20231013200054_AddAuditLogging.cs rename to playground/Coalesce.Domain/Migrations/20231017172408_AddAuditLogging.cs index b6e07a9d4..479e90131 100644 --- a/playground/Coalesce.Domain/Migrations/20231013200054_AddAuditLogging.cs +++ b/playground/Coalesce.Domain/Migrations/20231017172408_AddAuditLogging.cs @@ -40,7 +40,10 @@ protected override void Up(MigrationBuilder migrationBuilder) Type = table.Column(type: "varchar(100)", maxLength: 100, nullable: false), KeyValue = table.Column(type: "nvarchar(450)", nullable: true), State = table.Column(type: "tinyint", nullable: false), - Date = table.Column(type: "datetimeoffset", nullable: false) + Date = table.Column(type: "datetimeoffset", nullable: false), + ClientIp = table.Column(type: "nvarchar(max)", nullable: true), + Referrer = table.Column(type: "nvarchar(max)", nullable: true), + Endpoint = table.Column(type: "nvarchar(max)", nullable: true) }, constraints: table => { diff --git a/playground/Coalesce.Domain/Migrations/AppDbContextModelSnapshot.cs b/playground/Coalesce.Domain/Migrations/AppDbContextModelSnapshot.cs index 58da75f1e..03f22f46e 100644 --- a/playground/Coalesce.Domain/Migrations/AppDbContextModelSnapshot.cs +++ b/playground/Coalesce.Domain/Migrations/AppDbContextModelSnapshot.cs @@ -188,15 +188,24 @@ protected override void BuildModel(ModelBuilder modelBuilder) SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property("Id"), 1L, 1); + b.Property("ClientIp") + .HasColumnType("nvarchar(max)"); + b.Property("Date") .HasColumnType("datetimeoffset"); + b.Property("Endpoint") + .HasColumnType("nvarchar(max)"); + b.Property("KeyValue") .HasColumnType("nvarchar(450)"); b.Property("Message") .HasColumnType("nvarchar(max)"); + b.Property("Referrer") + .HasColumnType("nvarchar(max)"); + b.Property("State") .HasColumnType("tinyint"); diff --git a/playground/Coalesce.Web.Vue2/src/main.ts b/playground/Coalesce.Web.Vue2/src/main.ts index 166c9fa44..4d9ae8fba 100644 --- a/playground/Coalesce.Web.Vue2/src/main.ts +++ b/playground/Coalesce.Web.Vue2/src/main.ts @@ -24,7 +24,7 @@ import $metadata from '@/metadata.g'; // viewmodels.g has sideeffects - it populates the global lookup on ViewModel and ListViewModel. import '@/viewmodels.g'; -import CoalesceVuetify, { CAdminTablePage, CAdminEditorPage } from 'coalesce-vue-vuetify'; +import CoalesceVuetify, { CAdminTablePage, CAdminEditorPage, CAdminAuditLogPage } from 'coalesce-vue-vuetify'; Vue.use(CoalesceVuetify, { metadata: $metadata }); @@ -47,6 +47,7 @@ const router = new VueRouter({ mode: 'history', routes: [ path: '/test', component: () => import("./components/test.vue"), }, + { path: '/audit-logs', component: CAdminAuditLogPage, props: {type: 'ObjectChange'} }, { path: '/admin/:type', name: 'coalesce-admin-list', component: CAdminTablePage, diff --git a/playground/Coalesce.Web.Vue3/src/App.vue b/playground/Coalesce.Web.Vue3/src/App.vue index a37c6f5d6..5299fa9ba 100644 --- a/playground/Coalesce.Web.Vue3/src/App.vue +++ b/playground/Coalesce.Web.Vue3/src/App.vue @@ -1,44 +1,42 @@ - - \ No newline at end of file +.router-transition-enter-active, +.router-transition-leave-active { + // transition: 0.2s cubic-bezier(0.25, 0.8, 0.5, 1); + transition: 0.1s ease-out; +} +.router-transition-move { + transition: transform 0.4s; +} +.router-transition-enter, +.router-transition-leave-to { + opacity: 0; + // transform: translateY(5px); +} + diff --git a/src/IntelliTect.Coalesce.AuditLogging/CoalesceAuditLoggingBuilder.cs b/src/IntelliTect.Coalesce.AuditLogging/CoalesceAuditLoggingBuilder.cs index acaf7233a..1d36bf918 100644 --- a/src/IntelliTect.Coalesce.AuditLogging/CoalesceAuditLoggingBuilder.cs +++ b/src/IntelliTect.Coalesce.AuditLogging/CoalesceAuditLoggingBuilder.cs @@ -17,6 +17,13 @@ public CoalesceAuditLoggingBuilder(AuditOptions options) this.options = options; } + /// + /// Configures the operation context service that will be used to populate additional contextual + /// fields on audit log entries. The service will be injected from the application service + /// provider if available; otherwise, a new instance will be constructed using dependencies + /// from the application service provider. To make an injected dependency optional, make the + /// constructor parameter nullable with a default value of `null`, or create alternate constructors. + /// public CoalesceAuditLoggingBuilder WithAugmentation() where T : IAuditOperationContext { diff --git a/src/coalesce-vue-vuetify2/src/components/admin/c-admin-audit-log-page.vue b/src/coalesce-vue-vuetify2/src/components/admin/c-admin-audit-log-page.vue new file mode 100644 index 000000000..e27168f06 --- /dev/null +++ b/src/coalesce-vue-vuetify2/src/components/admin/c-admin-audit-log-page.vue @@ -0,0 +1,448 @@ + + + + + diff --git a/src/coalesce-vue-vuetify2/src/components/index.ts b/src/coalesce-vue-vuetify2/src/components/index.ts index 0d4051990..05752ebf6 100644 --- a/src/coalesce-vue-vuetify2/src/components/index.ts +++ b/src/coalesce-vue-vuetify2/src/components/index.ts @@ -1,4 +1,5 @@ // In alphabetical order: +export { default as CAdminAuditLogPage } from "./admin/c-admin-audit-log-page.vue"; export { default as CAdminDisplay } from "./admin/c-admin-display"; export { default as CAdminEditor } from "./admin/c-admin-editor.vue"; export { default as CAdminEditorPage } from "./admin/c-admin-editor-page.vue"; @@ -27,6 +28,7 @@ export { default as CTable } from "./display/c-table.vue"; import "vue"; declare module "vue" { interface GlobalComponents { + CAdminAuditLogPage: typeof import(".")["CAdminAuditLogPage"]; CAdminDisplay: typeof import(".")["CAdminDisplay"]; CAdminEditor: typeof import(".")["CAdminEditor"]; CAdminEditorPage: typeof import(".")["CAdminEditorPage"]; diff --git a/src/coalesce-vue-vuetify3/src/components/admin/c-admin-audit-log-page.vue b/src/coalesce-vue-vuetify3/src/components/admin/c-admin-audit-log-page.vue index 3b64b0490..3174e564d 100644 --- a/src/coalesce-vue-vuetify3/src/components/admin/c-admin-audit-log-page.vue +++ b/src/coalesce-vue-vuetify3/src/components/admin/c-admin-audit-log-page.vue @@ -222,6 +222,7 @@ import { computed } from "vue"; import { differenceInMilliseconds } from "date-fns"; import { + HiddenAreas, ListViewModel, ModelApiClient, ModelReferenceNavigationProperty, @@ -293,6 +294,7 @@ const userPropMeta = computed(() => { const otherProps = computed(() => { return Object.values(list.$metadata.props).filter( (p) => + ((p.hidden || 0) & HiddenAreas.List) != HiddenAreas.List && p != userPropMeta.value && !["id", "type", "keyValue", "date", "state", "properties"].includes( p.name @@ -401,9 +403,11 @@ defineExpose({ white-space: pre-wrap; vertical-align: top; padding: 0 2px; - } - td:first-child { - text-align: right; + + &:first-child { + white-space: nowrap; + text-align: right; + } } } }