Emdash source with visual editor image upload fix
Fixes: 1. media.ts: wrap placeholder generation in try-catch 2. toolbar.ts: check r.ok, display error message in popover
This commit is contained in:
44
packages/plugins/audit-log/CHANGELOG.md
Normal file
44
packages/plugins/audit-log/CHANGELOG.md
Normal file
@@ -0,0 +1,44 @@
|
||||
# @emdash-cms/plugin-audit-log
|
||||
|
||||
## 0.1.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#856](https://github.com/emdash-cms/emdash/pull/856) [`ef3f076`](https://github.com/emdash-cms/emdash/commit/ef3f076c8112e9dffc2a87c019e5521e823f5e86) Thanks [@ask-bonk](https://github.com/apps/ask-bonk)! - Republishes with `emdash` as a `peerDependency` instead of a runtime `dependency`. The previously published `0.1.1` release pinned `emdash` to an ancient `0.1.1` as a hard dependency, which made `npm install` of any template that included this plugin (e.g. the blog template) install two copies of `emdash` side-by-side, or fail outright with ERESOLVE on stricter npm configurations (#819). The package source already declared the dependency correctly; this version simply ships the corrected `package.json`.
|
||||
|
||||
- Updated dependencies [[`e2b3c6c`](https://github.com/emdash-cms/emdash/commit/e2b3c6cd930d5fa6fc607a0b26fd796f5b0f98b2), [`9dfc65c`](https://github.com/emdash-cms/emdash/commit/9dfc65c42c04c41088e0c8f5a8ca4347643e2fea), [`e0dc6fb`](https://github.com/emdash-cms/emdash/commit/e0dc6fb8adadc0e048f3f314d62bfa98d9bb48d4), [`c22fb3a`](https://github.com/emdash-cms/emdash/commit/c22fb3a10d445f12cca91620c9258d50695afa44), [`6a4e9b8`](https://github.com/emdash-cms/emdash/commit/6a4e9b8b0fa6064989224a42b14de435f487a76f), [`0ee372a`](https://github.com/emdash-cms/emdash/commit/0ee372a7f33eecce7d90e12624923d2d9c132adf), [`22a16ee`](https://github.com/emdash-cms/emdash/commit/22a16eed607a4e81391ecb6c45fe2e59aaca92fe), [`1e2b024`](https://github.com/emdash-cms/emdash/commit/1e2b02486ee0407e4f50b8342ba1a9e7d060e405), [`81662e9`](https://github.com/emdash-cms/emdash/commit/81662e98fcf1ad0ee880d4f1af96271c527d7423), [`2f22f57`](https://github.com/emdash-cms/emdash/commit/2f22f57abadf305cf6d3ce07ee78290178e032d1), [`ef3f076`](https://github.com/emdash-cms/emdash/commit/ef3f076c8112e9dffc2a87c019e5521e823f5e86), [`a9c29ea`](https://github.com/emdash-cms/emdash/commit/a9c29ea584300f6cf67206bedcb1d39f05ea1c26), [`e7df21f`](https://github.com/emdash-cms/emdash/commit/e7df21f0adca795cdb233d6e64cd543ead7e2347), [`d5f7c48`](https://github.com/emdash-cms/emdash/commit/d5f7c481a507868f470361cfd715a5828640d45a), [`8ae227c`](https://github.com/emdash-cms/emdash/commit/8ae227cceade5c9852897c7b56f89e7422ee82a1), [`e2d5d16`](https://github.com/emdash-cms/emdash/commit/e2d5d160acea4444945b1ea79c80ca9ce138965b), [`0d98c62`](https://github.com/emdash-cms/emdash/commit/0d98c620a5f407648f3b7f3cbd30b642c74be607), [`64bf5b9`](https://github.com/emdash-cms/emdash/commit/64bf5b98125ca18ec26f7e0e65a71fcbe71fd44f), [`e81aa0f`](https://github.com/emdash-cms/emdash/commit/e81aa0f717be11bacdff30ed9bbc454824268555), [`0041d76`](https://github.com/emdash-cms/emdash/commit/0041d7699b32b77b4cd2ecd77b97340f0dd3abce), [`cee403d`](https://github.com/emdash-cms/emdash/commit/cee403d5c008feb9ca60bb7201e151b828737743), [`a8bac5d`](https://github.com/emdash-cms/emdash/commit/a8bac5d7216e185b1bd9a2aaaeaa9a0306ab066e), [`5b6f059`](https://github.com/emdash-cms/emdash/commit/5b6f059d06175ae0cb740d1ba32867d1ec6b2249), [`a86ff80`](https://github.com/emdash-cms/emdash/commit/a86ff80836fed175508ff06f744c7ad6b805627c), [`d4be24f`](https://github.com/emdash-cms/emdash/commit/d4be24f478a0c8d0a7bba3c299e11105bba3ed94), [`eb6dbd0`](https://github.com/emdash-cms/emdash/commit/eb6dbd056717fd076a8b5fa807d91516a00f5f2f)]:
|
||||
- emdash@0.9.0
|
||||
|
||||
## 0.1.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#363](https://github.com/emdash-cms/emdash/pull/363) [`91e31fb`](https://github.com/emdash-cms/emdash/commit/91e31fb2cab4c0470088c5d61bab6e2028821569) Thanks [@ascorbic](https://github.com/ascorbic)! - Fixes sandboxed plugin entries failing when package exports point to unbuilt TypeScript source. Adds build-time and bundle-time validation to catch misconfigured plugin exports early.
|
||||
|
||||
- Updated dependencies [[`422018a`](https://github.com/emdash-cms/emdash/commit/422018aeb227dffe3da7bfc772d86f9ce9c2bcd1), [`4221ba4`](https://github.com/emdash-cms/emdash/commit/4221ba48bc87ab9fa0b1bae144f6f2920beb4f5a), [`9269759`](https://github.com/emdash-cms/emdash/commit/9269759674bf254863f37d4cf1687fae56082063), [`d6cfc43`](https://github.com/emdash-cms/emdash/commit/d6cfc437f23e3e435a8862cab17d2c19363847d7), [`1bcfc50`](https://github.com/emdash-cms/emdash/commit/1bcfc502112d8756e34a720b8a170eb5486b425a), [`8c693b5`](https://github.com/emdash-cms/emdash/commit/8c693b582d7c5e29bd138161e81d9c8affb53689), [`5b3e33c`](https://github.com/emdash-cms/emdash/commit/5b3e33c26bc2eb30ab2a032960a5d57eb06f148a), [`9d10d27`](https://github.com/emdash-cms/emdash/commit/9d10d2791fe16be901d9d138e434bd79cf9335c4), [`91e31fb`](https://github.com/emdash-cms/emdash/commit/91e31fb2cab4c0470088c5d61bab6e2028821569), [`f112ac4`](https://github.com/emdash-cms/emdash/commit/f112ac48194d1c2302e93756d54b116d3d207c22), [`e9a6f7a`](https://github.com/emdash-cms/emdash/commit/e9a6f7ac3ceeaf5c2d0a557e4cf6cab5f3d7d764), [`b297fdd`](https://github.com/emdash-cms/emdash/commit/b297fdd88dadcabeb93f47abea9f24f70b7d4b71), [`d211452`](https://github.com/emdash-cms/emdash/commit/d2114523a55021f65ee46e44e11157b06334819e), [`8e28cfc`](https://github.com/emdash-cms/emdash/commit/8e28cfc5d66f58f0fb91aa35c02afdd426bb6555), [`38af118`](https://github.com/emdash-cms/emdash/commit/38af118ad517fd9aa83064368543bf64bc32c08a)]:
|
||||
- emdash@0.1.1
|
||||
|
||||
## 0.1.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- [#14](https://github.com/emdash-cms/emdash/pull/14) [`755b501`](https://github.com/emdash-cms/emdash/commit/755b5017906811f97f78f4c0b5a0b62e67b52ec4) Thanks [@ascorbic](https://github.com/ascorbic)! - First beta release
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`755b501`](https://github.com/emdash-cms/emdash/commit/755b5017906811f97f78f4c0b5a0b62e67b52ec4)]:
|
||||
- emdash@0.1.0
|
||||
|
||||
## 0.0.3
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`3c319ed`](https://github.com/emdash-cms/emdash/commit/3c319ed6411a595e6974a86bc58c2a308b91c214)]:
|
||||
- emdash@0.0.3
|
||||
|
||||
## 0.0.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`b09bfd5`](https://github.com/emdash-cms/emdash/commit/b09bfd51cece5e88fe8314668a591ab11de36b4d)]:
|
||||
- emdash@0.0.2
|
||||
45
packages/plugins/audit-log/package.json
Normal file
45
packages/plugins/audit-log/package.json
Normal file
@@ -0,0 +1,45 @@
|
||||
{
|
||||
"name": "@emdash-cms/plugin-audit-log",
|
||||
"version": "0.1.2",
|
||||
"description": "Audit logging plugin for EmDash CMS - tracks content changes",
|
||||
"type": "module",
|
||||
"main": "dist/index.mjs",
|
||||
"exports": {
|
||||
".": {
|
||||
"import": "./dist/index.mjs",
|
||||
"types": "./dist/index.d.mts"
|
||||
},
|
||||
"./sandbox": "./dist/sandbox-entry.mjs"
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
],
|
||||
"keywords": [
|
||||
"emdash",
|
||||
"cms",
|
||||
"plugin",
|
||||
"audit",
|
||||
"logging",
|
||||
"history"
|
||||
],
|
||||
"author": "Matt Kane",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"emdash": "workspace:>=0.9.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"tsdown": "catalog:",
|
||||
"typescript": "catalog:"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "tsdown src/index.ts src/sandbox-entry.ts --format esm --dts --clean",
|
||||
"dev": "tsdown src/index.ts src/sandbox-entry.ts --format esm --dts --watch",
|
||||
"typecheck": "tsgo --noEmit"
|
||||
},
|
||||
"optionalDependencies": {},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/emdash-cms/emdash.git",
|
||||
"directory": "packages/plugins/audit-log"
|
||||
}
|
||||
}
|
||||
53
packages/plugins/audit-log/src/index.ts
Normal file
53
packages/plugins/audit-log/src/index.ts
Normal file
@@ -0,0 +1,53 @@
|
||||
/**
|
||||
* Audit Log Plugin for EmDash CMS
|
||||
*
|
||||
* Tracks all content and media changes for compliance and debugging.
|
||||
*
|
||||
* Features:
|
||||
* - Logs create, update, delete operations
|
||||
* - Tracks before/after state for updates
|
||||
* - Records user information (when available)
|
||||
* - Provides admin UI for viewing audit history
|
||||
* - Configurable retention period (admin settings)
|
||||
* - Uses plugin storage for persistent audit trail
|
||||
*
|
||||
* Demonstrates:
|
||||
* - Plugin storage with indexes and queries
|
||||
* - Admin-configurable settings schema
|
||||
* - Lifecycle hooks (install, activate, deactivate, uninstall)
|
||||
* - content:afterDelete hook
|
||||
*/
|
||||
|
||||
import type { PluginDescriptor } from "emdash";
|
||||
|
||||
export interface AuditEntry {
|
||||
timestamp: string;
|
||||
action: "create" | "update" | "delete" | "media:upload" | "media:delete";
|
||||
collection?: string;
|
||||
resourceId: string;
|
||||
resourceType: "content" | "media";
|
||||
userId?: string;
|
||||
changes?: {
|
||||
before?: Record<string, unknown>;
|
||||
after?: Record<string, unknown>;
|
||||
};
|
||||
metadata?: Record<string, unknown>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the audit log plugin descriptor
|
||||
*/
|
||||
export function auditLogPlugin(): PluginDescriptor {
|
||||
return {
|
||||
id: "audit-log",
|
||||
version: "0.1.0",
|
||||
format: "standard",
|
||||
entrypoint: "@emdash-cms/plugin-audit-log/sandbox",
|
||||
capabilities: ["read:content"],
|
||||
storage: {
|
||||
entries: { indexes: ["timestamp", "action", "resourceType", "collection"] },
|
||||
},
|
||||
adminPages: [{ path: "/history", label: "Audit History", icon: "history" }],
|
||||
adminWidgets: [{ id: "recent-activity", title: "Recent Activity", size: "half" }],
|
||||
};
|
||||
}
|
||||
373
packages/plugins/audit-log/src/sandbox-entry.ts
Normal file
373
packages/plugins/audit-log/src/sandbox-entry.ts
Normal file
@@ -0,0 +1,373 @@
|
||||
/**
|
||||
* Sandbox Entry Point -- Audit Log
|
||||
*
|
||||
* Canonical plugin implementation using the standard format.
|
||||
* Runs in both trusted (in-process) and sandboxed (isolate) modes.
|
||||
*
|
||||
* Note: The beforeSaveCache is module-scoped. In sandbox isolates that persist
|
||||
* across hook invocations within a request, this works correctly. In isolates
|
||||
* that don't persist, updates will be logged without "before" state (graceful
|
||||
* degradation -- the entry is still recorded).
|
||||
*/
|
||||
|
||||
import { definePlugin } from "emdash";
|
||||
import type { PluginContext } from "emdash";
|
||||
|
||||
interface ContentSaveEvent {
|
||||
content: Record<string, unknown> & {
|
||||
id?: string | number;
|
||||
slug?: string;
|
||||
status?: string;
|
||||
data?: Record<string, unknown>;
|
||||
};
|
||||
collection: string;
|
||||
isNew: boolean;
|
||||
}
|
||||
|
||||
interface ContentDeleteEvent {
|
||||
id: string;
|
||||
collection: string;
|
||||
}
|
||||
|
||||
interface MediaUploadEvent {
|
||||
media: { id: string };
|
||||
}
|
||||
|
||||
interface AuditEntry {
|
||||
timestamp: string;
|
||||
action: "create" | "update" | "delete" | "media:upload" | "media:delete";
|
||||
collection?: string;
|
||||
resourceId: string;
|
||||
resourceType: "content" | "media";
|
||||
userId?: string;
|
||||
changes?: {
|
||||
before?: Record<string, unknown>;
|
||||
after?: Record<string, unknown>;
|
||||
};
|
||||
metadata?: Record<string, unknown>;
|
||||
}
|
||||
|
||||
// ── Helpers ──
|
||||
|
||||
function isRecord(value: unknown): value is Record<string, unknown> {
|
||||
return typeof value === "object" && value !== null && !Array.isArray(value);
|
||||
}
|
||||
|
||||
function isAuditEntry(value: unknown): value is AuditEntry {
|
||||
return (
|
||||
isRecord(value) &&
|
||||
typeof value.timestamp === "string" &&
|
||||
typeof value.action === "string" &&
|
||||
typeof value.resourceId === "string" &&
|
||||
typeof value.resourceType === "string"
|
||||
);
|
||||
}
|
||||
|
||||
// In-memory cache for content state before save/delete.
|
||||
// Works within a single request lifecycle if the isolate persists.
|
||||
const beforeSaveCache = new Map<string, unknown>();
|
||||
|
||||
// ── Plugin definition ──
|
||||
|
||||
export default definePlugin({
|
||||
hooks: {
|
||||
"plugin:install": async (_event: unknown, ctx: PluginContext) => {
|
||||
ctx.log.info("Audit log plugin installed");
|
||||
},
|
||||
|
||||
"plugin:activate": async (_event: unknown, ctx: PluginContext) => {
|
||||
ctx.log.info("Audit log plugin activated");
|
||||
},
|
||||
|
||||
"plugin:deactivate": async (_event: unknown, ctx: PluginContext) => {
|
||||
ctx.log.info("Audit log plugin deactivated");
|
||||
},
|
||||
|
||||
"plugin:uninstall": async (_event: unknown, ctx: PluginContext) => {
|
||||
ctx.log.info("Audit log plugin uninstalled");
|
||||
},
|
||||
|
||||
"content:beforeSave": {
|
||||
handler: async (event: ContentSaveEvent, ctx: PluginContext) => {
|
||||
if (!event.isNew && event.content.id) {
|
||||
const contentId =
|
||||
typeof event.content.id === "string" ? event.content.id : String(event.content.id);
|
||||
try {
|
||||
if (ctx.content) {
|
||||
const existing = await ctx.content.get(event.collection, contentId);
|
||||
if (existing) {
|
||||
beforeSaveCache.set(`${event.collection}:${contentId}`, existing);
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
// Ignore -- best effort
|
||||
}
|
||||
}
|
||||
return event.content;
|
||||
},
|
||||
},
|
||||
|
||||
"content:afterSave": {
|
||||
handler: async (event: ContentSaveEvent, ctx: PluginContext) => {
|
||||
const contentId =
|
||||
typeof event.content.id === "string" ? event.content.id : String(event.content.id ?? "");
|
||||
const cacheKey = `${event.collection}:${contentId}`;
|
||||
const before = beforeSaveCache.get(cacheKey);
|
||||
beforeSaveCache.delete(cacheKey);
|
||||
|
||||
const beforeRecord = isRecord(before) ? before : undefined;
|
||||
const afterRecord = isRecord(event.content.data) ? event.content.data : undefined;
|
||||
|
||||
const entry: AuditEntry = {
|
||||
timestamp: new Date().toISOString(),
|
||||
action: event.isNew ? "create" : "update",
|
||||
collection: event.collection,
|
||||
resourceId: contentId,
|
||||
resourceType: "content",
|
||||
changes:
|
||||
beforeRecord || afterRecord ? { before: beforeRecord, after: afterRecord } : undefined,
|
||||
metadata: { slug: event.content.slug, status: event.content.status },
|
||||
};
|
||||
|
||||
try {
|
||||
await ctx.storage.entries!.put(`${Date.now()}-${contentId}`, entry);
|
||||
} catch (error) {
|
||||
ctx.log.error("Failed to persist entry", error);
|
||||
}
|
||||
|
||||
const icon = event.isNew ? "+" : "~";
|
||||
ctx.log.info(`${icon} ${entry.action} content/${event.collection}/${contentId}`);
|
||||
},
|
||||
},
|
||||
|
||||
"content:beforeDelete": {
|
||||
handler: async (event: ContentDeleteEvent, ctx: PluginContext) => {
|
||||
if (ctx.content) {
|
||||
try {
|
||||
const existing = await ctx.content.get(event.collection, event.id);
|
||||
if (existing) {
|
||||
beforeSaveCache.set(`delete:${event.collection}:${event.id}`, existing);
|
||||
}
|
||||
} catch {
|
||||
// Ignore
|
||||
}
|
||||
}
|
||||
return true;
|
||||
},
|
||||
},
|
||||
|
||||
"content:afterDelete": {
|
||||
handler: async (event: ContentDeleteEvent, ctx: PluginContext) => {
|
||||
const cacheKey = `delete:${event.collection}:${event.id}`;
|
||||
const beforeData = beforeSaveCache.get(cacheKey);
|
||||
beforeSaveCache.delete(cacheKey);
|
||||
|
||||
const beforeRecord = isRecord(beforeData) ? beforeData : undefined;
|
||||
const entry: AuditEntry = {
|
||||
timestamp: new Date().toISOString(),
|
||||
action: "delete",
|
||||
collection: event.collection,
|
||||
resourceId: event.id,
|
||||
resourceType: "content",
|
||||
changes: beforeRecord ? { before: beforeRecord } : undefined,
|
||||
};
|
||||
|
||||
try {
|
||||
await ctx.storage.entries!.put(`${Date.now()}-${event.id}`, entry);
|
||||
} catch (error) {
|
||||
ctx.log.error("Failed to persist entry", error);
|
||||
}
|
||||
|
||||
ctx.log.info(`- delete content/${event.collection}/${event.id}`);
|
||||
},
|
||||
},
|
||||
|
||||
"media:afterUpload": {
|
||||
handler: async (event: MediaUploadEvent, ctx: PluginContext) => {
|
||||
const entry: AuditEntry = {
|
||||
timestamp: new Date().toISOString(),
|
||||
action: "media:upload",
|
||||
resourceId: event.media.id,
|
||||
resourceType: "media",
|
||||
};
|
||||
|
||||
try {
|
||||
await ctx.storage.entries!.put(`${Date.now()}-${event.media.id}`, entry);
|
||||
} catch (error) {
|
||||
ctx.log.error("Failed to persist entry", error);
|
||||
}
|
||||
|
||||
ctx.log.info(`+ media:upload media/${event.media.id}`);
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
routes: {
|
||||
// Block Kit admin handler -- returns plain block objects (no @emdash-cms/blocks import needed)
|
||||
admin: {
|
||||
handler: async (
|
||||
routeCtx: { input: unknown; request: { url: string } },
|
||||
ctx: PluginContext,
|
||||
) => {
|
||||
const interaction = routeCtx.input as {
|
||||
type: string;
|
||||
page?: string;
|
||||
action_id?: string;
|
||||
value?: string;
|
||||
};
|
||||
|
||||
if (interaction.type === "page_load" && interaction.page === "/history") {
|
||||
return buildHistoryBlocks(ctx);
|
||||
}
|
||||
if (interaction.type === "page_load" && interaction.page === "widget:recent-activity") {
|
||||
return buildRecentBlocks(ctx);
|
||||
}
|
||||
if (interaction.type === "block_action" && interaction.action_id === "load-page") {
|
||||
return buildHistoryBlocks(ctx, interaction.value);
|
||||
}
|
||||
return { blocks: [] };
|
||||
},
|
||||
},
|
||||
|
||||
recent: {
|
||||
handler: async (
|
||||
_routeCtx: { input: unknown; request: { url: string } },
|
||||
ctx: PluginContext,
|
||||
) => {
|
||||
try {
|
||||
const result = await ctx.storage.entries!.query({
|
||||
orderBy: { timestamp: "desc" },
|
||||
limit: 5,
|
||||
});
|
||||
return {
|
||||
entries: result.items
|
||||
.filter((item: { id: string; data: unknown }) => isAuditEntry(item.data))
|
||||
.map((item: { id: string; data: unknown }) => ({
|
||||
id: item.id,
|
||||
...(item.data as AuditEntry),
|
||||
})),
|
||||
};
|
||||
} catch (error) {
|
||||
ctx.log.error("Failed to fetch recent entries", error);
|
||||
return { entries: [] };
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
history: {
|
||||
handler: async (
|
||||
routeCtx: { input: unknown; request: { url: string } },
|
||||
ctx: PluginContext,
|
||||
) => {
|
||||
try {
|
||||
const url = new URL(routeCtx.request.url);
|
||||
const limit = Math.min(parseInt(url.searchParams.get("limit") || "50", 10) || 50, 100);
|
||||
const cursor = url.searchParams.get("cursor") || undefined;
|
||||
|
||||
const result = await ctx.storage.entries!.query({
|
||||
orderBy: { timestamp: "desc" },
|
||||
limit,
|
||||
cursor,
|
||||
});
|
||||
return {
|
||||
entries: result.items
|
||||
.filter((item: { id: string; data: unknown }) => isAuditEntry(item.data))
|
||||
.map((item: { id: string; data: unknown }) => ({
|
||||
id: item.id,
|
||||
...(item.data as AuditEntry),
|
||||
})),
|
||||
cursor: result.cursor,
|
||||
hasMore: result.hasMore,
|
||||
};
|
||||
} catch (error) {
|
||||
ctx.log.error("Failed to fetch history", error);
|
||||
return { entries: [], cursor: undefined, hasMore: false };
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
// ── Block Kit helpers (plain objects, no @emdash-cms/blocks import) ──
|
||||
|
||||
async function buildHistoryBlocks(ctx: PluginContext, cursor?: string) {
|
||||
try {
|
||||
const result = await ctx.storage.entries!.query({
|
||||
orderBy: { timestamp: "desc" },
|
||||
limit: 50,
|
||||
cursor,
|
||||
});
|
||||
const entries = result.items
|
||||
.filter((item: { id: string; data: unknown }) => isAuditEntry(item.data))
|
||||
.map((item: { id: string; data: unknown }) => ({
|
||||
id: item.id,
|
||||
...(item.data as AuditEntry),
|
||||
}));
|
||||
|
||||
return {
|
||||
blocks: [
|
||||
{ type: "header", text: "Audit History" },
|
||||
{ type: "context", text: "Track all content and media changes" },
|
||||
{ type: "divider" },
|
||||
{
|
||||
type: "table",
|
||||
blockId: "history-table",
|
||||
columns: [
|
||||
{ key: "action", label: "Action", format: "badge" },
|
||||
{ key: "resource", label: "Resource", format: "code" },
|
||||
{ key: "collection", label: "Collection", format: "text" },
|
||||
{ key: "time", label: "Time", format: "relative_time" },
|
||||
],
|
||||
rows: entries.map((e) => ({
|
||||
action: e.action,
|
||||
resource: e.resourceId,
|
||||
collection: e.collection ?? "-",
|
||||
time: e.timestamp,
|
||||
})),
|
||||
pageActionId: "load-page",
|
||||
nextCursor: result.cursor,
|
||||
emptyText: "No audit entries yet",
|
||||
},
|
||||
{ type: "context", text: `Showing ${entries.length} entries` },
|
||||
],
|
||||
};
|
||||
} catch (error) {
|
||||
ctx.log.error("Failed to fetch history", error);
|
||||
return { blocks: [{ type: "context", text: "Failed to load audit history" }] };
|
||||
}
|
||||
}
|
||||
|
||||
async function buildRecentBlocks(ctx: PluginContext) {
|
||||
try {
|
||||
const result = await ctx.storage.entries!.query({
|
||||
orderBy: { timestamp: "desc" },
|
||||
limit: 5,
|
||||
});
|
||||
const entries = result.items
|
||||
.filter((item: { id: string; data: unknown }) => isAuditEntry(item.data))
|
||||
.map((item: { id: string; data: unknown }) => ({
|
||||
id: item.id,
|
||||
...(item.data as AuditEntry),
|
||||
}));
|
||||
|
||||
if (entries.length === 0) {
|
||||
return { blocks: [{ type: "context", text: "No recent activity" }] };
|
||||
}
|
||||
|
||||
return {
|
||||
blocks: [
|
||||
{
|
||||
type: "fields",
|
||||
fields: entries.slice(0, 4).map((e) => ({
|
||||
label: e.action,
|
||||
value: `${e.collection ? `${e.collection}/` : ""}${e.resourceId}`,
|
||||
})),
|
||||
},
|
||||
{ type: "context", text: `${entries.length} changes` },
|
||||
],
|
||||
};
|
||||
} catch (error) {
|
||||
ctx.log.error("Failed to fetch recent activity", error);
|
||||
return { blocks: [{ type: "context", text: "Failed to load activity" }] };
|
||||
}
|
||||
}
|
||||
10
packages/plugins/audit-log/tsconfig.json
Normal file
10
packages/plugins/audit-log/tsconfig.json
Normal file
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"extends": "../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "./dist",
|
||||
"rootDir": "./src",
|
||||
"jsx": "react-jsx"
|
||||
},
|
||||
"include": ["src/**/*"],
|
||||
"exclude": ["node_modules", "dist"]
|
||||
}
|
||||
Reference in New Issue
Block a user