Skip to content

Strapi: Upload & Download & Webhook logs#115

Merged
david-vaclavek merged 28 commits intomainfrom
LOC-3981_strapi_upload__download__webhook_logs
Apr 1, 2026
Merged

Strapi: Upload & Download & Webhook logs#115
david-vaclavek merged 28 commits intomainfrom
LOC-3981_strapi_upload__download__webhook_logs

Conversation

@david-vaclavek
Copy link
Copy Markdown
Contributor

Summary

  • New "Activity Logs" section in the plugin with three tabs (Upload, Download, Webhooks)
  • Server-side log persistence using Strapi's key-value store (max 100 sessions)
  • Upload and download transfers now create log sessions and record individual entries
  • Webhook-triggered downloads are logged separately as webhook events
  • Admin UI with searchable sessions table, detail view with console-style entries
  • Navigation integration in plugin sidebar

Changes

Server (8 files)

  • server/src/db/model/activity-logs.ts (new) — Data model types and store key
  • server/src/services/activity-logs-service.ts (new) — CRUD service for log sessions/entries
  • server/src/controllers/activity-logs-controller.ts (new) — REST endpoints
  • server/src/routes/activity-logs-routes.ts (new) — Route definitions
  • server/src/services/index.ts, controllers/index.ts, routes/index.ts, core/index.ts — Registration
  • server/src/services/localazy-transfer-upload-service.ts — Log session creation during uploads
  • server/src/services/localazy-transfer-download-service.ts — Log session for downloads + webhook distinction

Admin (9 files)

  • admin/src/pages/ActivityLogs.tsx (new) — Main page with tabs, search, sessions table
  • admin/src/pages/ActivityLogDetail.tsx (new) — Detail view with session metadata and log entries
  • admin/src/modules/activity-logs/services/activity-logs-service.ts (new) — API service
  • admin/src/modules/activity-logs/locale/en.ts (new) — Translations
  • admin/src/pages/App.tsx — Routes for activity logs
  • Navigation/routing — Sidebar link, route enum, i18n

Testing

  • All 168 tests pass
  • Lint: 0 new errors
  • No new dependencies added

Note

The task description mentions potential conflict with the multi-user support task (LOC-3982). Currently uses 'Strapi User' as placeholder for initiatedBy — should be updated once user tracking is implemented.

Fibery Task

LOC-3981


🤖 Generated with Claude Code

Adds a new Activity Logs feature to the plugin with:
- Server-side: DB model (Strapi store), service, controller, and routes
  for storing and retrieving log sessions and entries
- Upload/download services now create log sessions and record entries
  during transfers (webhook-triggered downloads logged as webhook type)
- Admin-side: Activity Logs page with three tabs (Upload, Download,
  Webhooks), searchable sessions table, and detail view with console-
  style log entries
- Navigation integration with icon in plugin sidebar

Resolves LOC-3981

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@david-vaclavek
Copy link
Copy Markdown
Contributor Author

david-vaclavek commented Mar 27, 2026

🔍 Review Round 1 — PASS ⚠️

Must Fix

None.

Should Fix

  • Race condition on concurrent writes: The read-modify-write pattern in `addEntry()`, `startSession()`, and `finishSession()` is not atomic. If a webhook-triggered download runs concurrently with a manual upload, both call `getActivityLogs()`, get the same snapshot, mutate independently, and one `saveActivityLogs()` overwrites the other's changes (lost update). This is a real scenario since webhooks fire at any time. Consider a simple mutex around the read-modify-write cycle, or document as a known limitation.
  • Upload service — missing error handling around Localazy API call: In `localazy-transfer-upload-service.ts`, the `LocalazyUploadService.upload()` call is not wrapped in try/catch. If it throws, the session stays "in-progress" forever. The download service handles this correctly throughout.
  • Admin service try/catch is a no-op: In `admin/src/modules/activity-logs/services/activity-logs-service.ts`, all methods have `try { ... } catch (e) { throw e; }` which does nothing. Either remove the try/catch or add meaningful error handling.

Suggestions

  • `formatDate`, `formatDuration`, `getStatusColor` are duplicated between `ActivityLogs.tsx` and `ActivityLogDetail.tsx`. Extract to a shared utils file.
  • `clearSessions` DELETE endpoint clears ALL log types at once. Consider accepting a `type` parameter for clearing a single category.
  • Visiting Activity Logs sets it as the default landing page via `updatePluginSettings({ defaultRoute: PLUGIN_ROUTES.ACTIVITY_LOGS })`. Is this intentional for a logs page?

🧪 Manual Testing Checklist

Upload logging

  • Trigger an upload → session appears in Upload tab with status "Completed"
  • Trigger upload with no content transfer setup → session shows "Failed"
  • Click into session → entries show chronological log messages with timestamps
  • Verify duration is calculated correctly

Download logging

  • Trigger manual download → session appears in Download tab (not Webhooks)
  • Trigger download that creates new locales → locale creation entries appear
  • Trigger download with no Localazy project connected → session shows "Failed"

Webhook logging

  • Trigger webhook-initiated download → session appears in Webhooks tab with initiatedBy="Webhook"
  • Webhook download does NOT also appear in Download tab

Session list & search

  • Sessions sorted newest-first
  • Search by status text ("Completed") → filtering works
  • Search by date → filtering works
  • Switch tabs → search resets, correct sessions load
  • Empty state message when no sessions exist

Session detail view

  • Click session row → navigates to detail page
  • Back button returns to list
  • Search entries by message text
  • Entries with `data` field render JSON correctly

FIFO eviction

  • Create >100 sessions (or temporarily lower MAX_SESSIONS) → oldest evicted

Clear logs

  • Click "Clear logs" → confirmation dialog appears
  • Cancel → logs NOT cleared
  • Confirm → all logs removed, success alert shows and auto-dismisses after 3s

Concurrency

  • Trigger upload + webhook-download simultaneously → verify both sessions recorded (check for lost updates)

Navigation

  • "Activity Logs" in sidebar with clock icon
  • Route `/activity-logs` loads correctly
  • Route `/activity-logs/:sessionId` loads correctly
  • Non-existent session ID shows error/404

Summary

Well-structured feature with clean separation between data model, service, controller, and UI. FIFO eviction at 100 sessions is simple and effective. The main concerns are: (1) non-atomic read-modify-write on the shared store could lose entries under concurrent operations, and (2) upload service missing error handling around the API call leaves sessions stuck in "in-progress". Both are low-severity but worth addressing. The admin try/catch no-ops should be cleaned up.

DECISION: PASS_WITH_SUGGESTIONS

- Add mutex around read-modify-write operations in activity logs service
  to prevent lost updates during concurrent upload + webhook sessions
- Wrap LocalazyUploadService.upload() call in try/catch so sessions are
  properly marked as failed if the API call throws
- Remove no-op try/catch wrappers in admin activity logs service
- Extract shared formatDate/formatDuration/getStatusColor to utils file
- Remove defaultRoute update from Activity Logs page (not appropriate
  for a logs page to become the landing page)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@david-vaclavek
Copy link
Copy Markdown
Contributor Author

david-vaclavek commented Mar 27, 2026

🔍 Review Round 2 — PASS ✅

Previous Round Resolution

Issue Round Status
Race condition on concurrent writes #1 ✅ Resolved — `withLock` mutex wraps all write operations
Upload service missing error handling #1 ✅ Resolved — try/catch around `LocalazyUploadService.upload()` with proper session finalization
Admin service try/catch no-ops #1 ✅ Resolved — removed
DRY: duplicated format utils (suggestion) #1 ✅ Resolved — extracted to `format-utils.ts`
Default route set on Activity Logs visit (suggestion) #1 ⚠️ Reverted — this was intentional. Activity Logs should set itself as the last-visited route like all other pages. Please restore the `updatePluginSettings({ defaultRoute: PLUGIN_ROUTES.ACTIVITY_LOGS })` call.

Must Fix

None.

Should Fix

  • Restore `updatePluginSettings` call in `ActivityLogs.tsx`: The `defaultRoute` update was removed based on my incorrect suggestion. This is the standard pattern for all plugin pages — Activity Logs should also set itself as the last-visited route. Please add it back.

🧪 Manual Testing Checklist

No new scenarios — Round 1 checklist still applies.

Summary

3 should-fix items and 1 suggestion resolved. However, the `updatePluginSettings({ defaultRoute })` removal was incorrect — this is the standard last-visited-route pattern and should be restored. The mutex, upload error handling, try/catch cleanup, and DRY utils are all good.

DECISION: PASS_WITH_SUGGESTIONS

david-vaclavek and others added 18 commits March 27, 2026 15:34
The defaultRoute tracks the last visited page, not a permanent landing
page — Activity Logs should participate in this like other pages.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Click any column header to sort ascending/descending. Active sort column
is highlighted with a caret icon. Supports sorting by status, date,
duration, initiated by, and summary. Default sort is by date descending.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Sort column and direction are saved to localStorage and restored on
page load, so the user's preference survives navigation and refreshes.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Each tab (upload, download, webhook) now remembers its own sort column
and direction. Preferences are stored in plugin settings (Strapi store)
with a 1s debounce to avoid excessive API calls during rapid toggling.
Loaded on page init so they persist across browsers and sessions.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Box passes alignItems directly to the DOM element, triggering a React
warning. Flex handles flex props correctly without leaking to the DOM.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Reserve a fixed 8px slot for the sort caret icon in every header so
columns don't resize when the active sort column changes.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Adds From/To date pickers alongside the search field. Sessions are
filtered client-side by startedAt timestamp. The dateTo filter includes
the full end-of-day. Uses Strapi DS DatePicker component with clear
support. Date range persists across tab switches.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Strapi DatePicker returns dates at midnight UTC, which in positive UTC
offset timezones (e.g. Europe/Warsaw) appears as the previous day.
Normalize by constructing a new Date from UTC year/month/day components
in local time.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Store raw DatePicker Date as-is (for correct display), and normalize
only in the filter by extracting UTC year/month/day to construct local
start-of-day and end-of-day for timestamp comparison. This avoids the
double-conversion that caused a 2-day offset.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace broken native date inputs with Strapi DS DatePicker. Fix
timezone round-trip by converting onChange local Date to UTC midnight
before storing, so the value prop stays consistent with DatePicker's
internal UTC-based CalendarDate conversion.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Tab switches no longer clear search/date filters or show the full-page
loader. Only the sessions table content reloads while filters stay
intact.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace count-based FIFO eviction with time-based retention. Sessions
older than 1 year are pruned when a new session starts.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add generic exportAsJson(data, filename) in @common/utils for reuse
across the plugin. Activity Logs page gets an "Export logs" button that
fetches all sessions with full entries and downloads as JSON.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Use Strapi DS Pagination component with 20 items per page, always
visible. Page resets on filter/sort/tab changes. Add info note below
page subtitle about 1-year log retention policy.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The nested DesignSystemProvider (needed for button theming) injects a
GlobalStyle with h1-h6 { font: unset } that strips heading styles from
Strapi's own provider. Fix with a shared HeadingFixGlobalStyle that
restores correct Typography variant sizes using data-strapi-header
attribute selectors.

- Plugin pages: DesignSystemProvider + HeadingFixGlobalStyle + removed as='h2'
- Settings pages: DesignSystemProvider + HeadingFixGlobalStyle (as='h2' kept)
- Log detail: BackButton from @strapi/strapi/admin replaces custom ghost button

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@david-vaclavek
Copy link
Copy Markdown
Contributor Author

🔍 Review Round 3 — PASS ⚠️

Previous Round Resolution

Issue Round Status
Restore `updatePluginSettings({ defaultRoute })` #2 ✅ Resolved
All Round 1 items (mutex, upload error, try/catch, DRY) #1 ✅ Confirmed no regressions

Must Fix

None.

Should Fix

  • Export triggers N+1 API requests: `exportLogsAsJson` fetches sessions per type (3 requests), then fetches each session's detail individually (N requests). With hundreds of sessions, this fires hundreds of sequential HTTP requests. Consider a server-side bulk export endpoint (`GET /activity-logs/export`) returning all sessions with entries in one response.
  • Pagination renders all page links without truncation: `Array.from({ length: totalPages })` renders every page link. With 1-year retention at PAGE_SIZE=20, this could produce hundreds of `` elements. Add ellipsis/truncation (show first, last, and a window around current page).

Suggestions

  • `emptyActivityLogs` is a shared mutable object — `getActivityLogs()` returns it directly when store is empty, then `startSession` mutates it via `.unshift()`. Low risk but cleaner to return a fresh object.
  • Consider limiting exported log data — `entry.data` could contain translation values. Acceptable for admin-only export, but worth a product decision.

🧪 Manual Testing Checklist (delta — new features)

Sorting

  • Sort by each column (status, startedAt, duration, initiatedBy, summary) → ascending/descending toggle works
  • Switch tabs → sort preference persists per tab (upload/download/webhook stored independently)
  • Reload page → sort preferences restored from plugin settings

Date range filter

  • Set "From" and "To" dates → sessions outside range hidden
  • Set "From" only (no "To") → only lower bound applies
  • Set "To" only (no "From") → only upper bound applies
  • Clear date filters → all sessions reappear
  • Test timezone boundary: session at 23:59 local on selected date is included

Pagination

  • Navigate through pages → correct 20 items per page
  • Change filter/search while on page 3 → page resets to 1
  • Test with 50+ sessions → verify performance and that all page links render (will be many without truncation)

Export

  • Click "Export logs" → JSON file downloads with filename `localazy-activity-logs-YYYY-MM-DD.json`
  • Verify exported JSON contains all session types with full entry detail
  • Export with zero logs → graceful handling (empty or no download)
  • Export with many sessions → verify it completes (note: N+1 requests, may be slow)

Retention

  • Verify retention is now time-based (1 year) instead of count-based (100)
  • Create a session, manually backdate `startedAt` to >1 year ago → trigger new session → old one evicted

Summary

18 new commits add well-structured client-side sorting, date filtering, pagination, and JSON export. Code is clean and correct. Two should-fix items: N+1 export pattern will degrade with many sessions, and pagination links need truncation at scale. No blocking issues.

DECISION: PASS_WITH_SUGGESTIONS

david-vaclavek and others added 2 commits March 30, 2026 12:56
- Add PluginErrorBoundary with friendly error page and disconnect button
  for cases where the Localazy project is deleted or session is invalid.
  Uses functional ErrorFallback with hooks, wrapped by thin class boundary.
- Add server-side bulk export endpoint (GET /activity-logs/export) to
  replace N+1 client-side fetching during log export.
- Add pagination truncation with Dots — shows first, last, and a window
  of 2 around the current page instead of rendering all page links.
- Fix mutable emptyActivityLogs shared reference.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@david-vaclavek
Copy link
Copy Markdown
Contributor Author

🔍 Review Round 4 — PASS ✅

Previous Round Resolution

Issue Round Status
N+1 export API requests #3 ✅ Resolved — bulk `GET /export` endpoint, single request
Pagination without truncation #3 ✅ Resolved — `Dots` component with ±2 window around current page
Shared mutable `emptyActivityLogs` (suggestion) #3 ✅ Resolved — returns fresh `{ sessions: [] }`
All prior rounds #1-2 ✅ No regressions

Must Fix

None.

Should Fix

None.

🧪 Manual Testing Checklist (delta)

  • Bulk export: Click "Export logs" → verify single API request to `/export` endpoint (check Network tab), JSON file downloads with all sessions
  • Pagination truncation: With 50+ sessions → verify dots appear instead of listing all page numbers, first/last pages always visible
  • Error boundary: Trigger an error in the plugin (e.g., corrupt a session ID in the URL) → verify error boundary catches it with a friendly message instead of a blank screen

Summary

All Round 3 should-fix items resolved. Bulk export endpoint eliminates N+1 requests, pagination truncation with Dots keeps the paginator usable at scale, and the shared mutable object is fixed. Bonus: error boundary added for crash resilience. Clean across all 4 rounds.

DECISION: PASS

david-vaclavek and others added 4 commits March 31, 2026 11:42
Highlight matching text in status, date, initiated by, and summary
columns when searching. Use lodash debounce (300ms) to avoid re-rendering
the table on every keystroke. Update search placeholder to clarify
which fields are searchable.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Extract from monolithic page files into focused modules:
- models/activity-logs.ts — shared types (SessionItem, SessionDetail, etc.)
- components/HighlightMatch — search term highlighting (both pages)
- components/StatusLabel — status with color + optional highlight
- components/SortableHeader — clickable column header with caret
- components/SessionMetadata — status/date/duration display (detail page)
- components/SessionsTable — full table with sort, filter, pagination
- hooks/use-debounced-search — shared debounced search (both pages)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add @typescript-eslint/no-floating-promises with type-aware linting.
Fix 32 violations across 17 files using void prefix for fire-and-forget
promises, await for test assertions, and return for error interceptor.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add a webhook configuration section to Global Settings with:
- Server endpoints to check webhook status, register webhook, and get
  Strapi URL (with localhost fallback from host/port config)
- Modal walkthrough with 3 steps: verify URL, confirm endpoint, register
- Webhook identified by customId ('strapi-plugin-localazy') so URL
  changes in Localazy don't break the pairing
- Detects deleted webhooks and prompts for reconfiguration
- Triggers on 'project_published' event
- Includes ngrok hint for local development
- Link to Localazy webhook docs
- Webhook config section placed first in Download Settings as prerequisite

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Use useTheme() from styled-components to access theme colors instead of
hardcoded hex values. Fixes unreadable hover rows and borders in dark
mode across Activity Logs, session detail, sortable headers, search
highlight, and status column.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@david-vaclavek
Copy link
Copy Markdown
Contributor Author

🔍 Review Round 5 — NEEDS WORK ❌

Previous Round Resolution

All prior items (rounds 1-4) remain resolved. No regressions.

Must Fix

  • Webhook setup — no URL validation on server: `setupWebhook` controller accepts any string including empty/blank. An attacker (or accident) could register a webhook pointing to an internal service (SSRF-adjacent via Localazy). Add: require non-empty, must start with `http://` or `https://`, validate with `new URL(url)`.
    • File: `server/src/controllers/localazy-project-controller.ts`
  • Dead `deleted` state variant: `WebhookState` type includes `'deleted'` but it's never set — the else branch always returns `'not_configured'`. Either remove the dead variant or implement detection logic (compare against saved config to distinguish "never set up" vs "deleted externally").
    • File: `admin/src/modules/plugin-settings/components/WebhookSetup.tsx`

Should Fix

  • `HighlightMatch` only highlights first occurrence: If search term appears multiple times in a string, only the first match is highlighted. Use a split/rejoin or regex-based approach for all occurrences.
  • `useDebouncedSearch` doesn't cancel on unmount: If the component unmounts before the debounce fires, it calls setState on an unmounted component. Add `debouncedSetQuery.cancel()` in a cleanup return.
  • `getStrapiUrl` exposes internal server config: Returns localhost/port when `server.url` is not set. Low-risk (behind admin auth) but could confuse users who submit localhost as a public webhook URL. Consider a warning flag when URL is non-public (localhost, 127.0.0.1, private IPs).

Suggestions

  • Delay test assertion (`delay.test.ts`) checks promise identity, not delay behavior. Consider a proper timing test.
  • `WebhookSetup` checkStatus error handler silently falls back to `'not_configured'`. Consider a distinct `'error'` state with retry button.

🧪 Manual Testing Checklist (delta)

Search highlighting

  • Verify highlight spans appear for partial matches across all columns
  • Type special regex characters (., (, [) in search → no error/crash
  • Type quickly → table doesn't re-filter until 300ms after last keystroke

Webhook setup (new feature)

  • Open Global Settings → Webhook Setup modal → verify Strapi URL is prefilled
  • Submit a valid public URL → verify webhook appears in Localazy project settings
  • Reconfigure existing webhook → verify old one is replaced, not duplicated
  • Simulate API error during setup → verify error alert in modal
  • Delete webhook externally in Localazy → reload → verify state shows "not configured"
  • Submit empty URL → verify button is disabled / request blocked
  • Submit non-HTTP URL (e.g., `ftp://...`) → verify server rejects it (after fix)
  • Submit localhost/internal URL → verify warning (after fix)

Dark mode

  • Toggle Strapi to dark mode → verify Activity Logs table is readable (rows, borders, hover states, highlights)
  • Verify status badges use theme-appropriate colors

Component refactor

  • Verify Activity Logs list page still shows all columns, sorting, pagination, filtering
  • Verify detail page still shows session metadata and entry list with search

Summary

The webhook setup feature is well-structured but needs server-side URL validation (must-fix for security) and has a dead type variant. Search highlighting and debounce hook have minor issues. Dark mode and component refactor are clean. First NEEDS WORK in this PR's review history.

DECISION: NEEDS_WORK

Must-fix:
- Server-side webhook URL validation (non-empty, http/https, parseable)
- Remove dead 'deleted' state variant from WebhookState type

Should-fix:
- HighlightMatch now highlights all occurrences using regex split,
  with special characters escaped to prevent regex errors
- useDebouncedSearch cancels pending debounce on unmount
- getStrapiUrl returns isLocal flag for localhost/private IPs, modal
  shows warning when URL is non-public
- Replace Alert with custom info/warning blocks (no close button)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@david-vaclavek
Copy link
Copy Markdown
Contributor Author

🔍 Review Round 6 — PASS ✅

Previous Round Resolution

Issue Round Status
Webhook URL validation (must-fix) #5 ✅ Resolved — `new URL()` + protocol check + non-empty validation
Dead `deleted` state variant (must-fix) #5 ✅ Resolved — removed from type union
HighlightMatch first-only (should-fix) #5 ✅ Resolved — regex split/rejoin with special char escaping
Debounce unmount cleanup (should-fix) #5 ✅ Resolved — `useEffect` cleanup with `cancel()`
getStrapiUrl local warning (should-fix) #5 ✅ Resolved — `isLocal` flag with private IP regex + UI warning

Must Fix

None.

Should Fix

None.

🧪 Manual Testing Checklist

No new scenarios — Round 5 checklist still applies, specifically:

  • Submit non-HTTP URL → server returns 400 "must use http or https"
  • Submit malformed URL → server returns 400 "Invalid webhook URL"
  • Local URL detected → yellow warning banner appears in modal
  • Public URL → no warning shown

Summary

All 5 Round 5 items resolved cleanly in a single commit. URL validation covers empty, non-string, invalid URL, and non-HTTP protocols. Local address detection uses a comprehensive regex covering localhost, 127.0.0.1, 0.0.0.0, and RFC 1918 private ranges. Highlight and debounce fixes are correct. Clean resolution of the first NEEDS WORK round.

DECISION: PASS

@david-vaclavek david-vaclavek merged commit 10c49e0 into main Apr 1, 2026
6 checks passed
@david-vaclavek david-vaclavek deleted the LOC-3981_strapi_upload__download__webhook_logs branch April 1, 2026 12:07
@localazy-bot localazy-bot Bot mentioned this pull request Apr 1, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants