feat(middleware): harden routing with fail-closed policy, custom subdomain management, and perf fixes
- Fix IPv6 host parsing with _strip_port() utility - Remove dangerous StorePlatform→Store.subdomain silent fallback - Close storefront gate bypass when frontend_type is None - Add custom subdomain management UI and API for stores - Add domain health diagnostic tool - Convert db.add() in loops to db.add_all() (24 PERF-006 fixes) - Add tests for all new functionality (18 subdomain service tests) - Add .github templates for validator compliance Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -492,6 +492,117 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Custom Subdomains (per-platform) -->
|
||||
<div class="px-4 py-3 mb-8 bg-white rounded-lg shadow-md dark:bg-gray-800">
|
||||
<div class="flex items-center justify-between mb-4">
|
||||
<h3 class="text-lg font-semibold text-gray-700 dark:text-gray-200">
|
||||
Custom Subdomains
|
||||
</h3>
|
||||
<button
|
||||
@click="loadCustomSubdomains()"
|
||||
class="flex items-center px-3 py-1.5 text-sm font-medium text-gray-600 bg-gray-200 rounded-lg hover:bg-gray-300 dark:bg-gray-600 dark:text-gray-300 dark:hover:bg-gray-500">
|
||||
<span x-html="$icon('refresh', 'w-4 h-4 mr-1')"></span>
|
||||
Refresh
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<p class="text-xs text-gray-500 dark:text-gray-400 mb-4">
|
||||
Each platform membership can have a custom subdomain override. If not set, the store's default subdomain
|
||||
(<strong x-text="store?.subdomain"></strong>) is used on each platform.
|
||||
</p>
|
||||
|
||||
<!-- Loading -->
|
||||
<div x-show="customSubdomainsLoading" class="text-center py-4">
|
||||
<p class="text-sm text-gray-500 dark:text-gray-400">Loading subdomains...</p>
|
||||
</div>
|
||||
|
||||
<!-- Subdomains List -->
|
||||
<div x-show="!customSubdomainsLoading && customSubdomains.length > 0" class="space-y-3">
|
||||
<template x-for="entry in customSubdomains" :key="entry.store_platform_id">
|
||||
<div class="p-3 bg-gray-50 rounded-lg dark:bg-gray-700">
|
||||
<div class="flex items-center justify-between mb-2">
|
||||
<div class="flex items-center gap-2">
|
||||
<span x-html="$icon('globe', 'w-4 h-4 text-purple-500')"></span>
|
||||
<span class="text-sm font-medium text-gray-700 dark:text-gray-200" x-text="entry.platform_name"></span>
|
||||
<span class="px-1.5 py-0.5 text-xs font-medium text-gray-600 bg-gray-200 rounded-full dark:bg-gray-600 dark:text-gray-300" x-text="entry.platform_code"></span>
|
||||
</div>
|
||||
<div class="flex items-center gap-1">
|
||||
<!-- Edit button (show when not editing) -->
|
||||
<button
|
||||
x-show="editingSubdomainPlatformId !== entry.platform_id"
|
||||
@click="startEditSubdomain(entry)"
|
||||
class="px-2 py-1 text-xs font-medium text-blue-600 hover:text-blue-700 dark:text-blue-400"
|
||||
title="Edit subdomain">
|
||||
<span x-html="$icon('edit', 'w-4 h-4')"></span>
|
||||
</button>
|
||||
<!-- Clear button (show when custom subdomain is set and not editing) -->
|
||||
<button
|
||||
x-show="entry.custom_subdomain && editingSubdomainPlatformId !== entry.platform_id"
|
||||
@click="clearCustomSubdomain(entry.platform_id, entry.custom_subdomain)"
|
||||
class="px-2 py-1 text-xs font-medium text-red-600 hover:text-red-700 dark:text-red-400"
|
||||
title="Clear custom subdomain">
|
||||
<span x-html="$icon('delete', 'w-4 h-4')"></span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Display mode -->
|
||||
<div x-show="editingSubdomainPlatformId !== entry.platform_id" class="text-sm">
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="text-gray-500 dark:text-gray-400">Custom:</span>
|
||||
<template x-if="entry.custom_subdomain">
|
||||
<span>
|
||||
<span class="font-medium text-purple-600 dark:text-purple-400" x-text="entry.custom_subdomain"></span>
|
||||
<span class="text-gray-400" x-text="'.' + entry.platform_domain" x-show="entry.platform_domain"></span>
|
||||
</span>
|
||||
</template>
|
||||
<template x-if="!entry.custom_subdomain">
|
||||
<span class="text-gray-400 italic">Not set</span>
|
||||
</template>
|
||||
</div>
|
||||
<div class="flex items-center gap-2 mt-1">
|
||||
<span class="text-gray-500 dark:text-gray-400">Default:</span>
|
||||
<span class="text-gray-600 dark:text-gray-300" x-text="entry.default_url || '--'"></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Edit mode -->
|
||||
<div x-show="editingSubdomainPlatformId === entry.platform_id" x-transition class="mt-2">
|
||||
<div class="flex items-center gap-2">
|
||||
<input
|
||||
type="text"
|
||||
x-model="editingSubdomainValue"
|
||||
:placeholder="entry.default_subdomain || 'custom-subdomain'"
|
||||
@keydown.enter="saveCustomSubdomain(entry.platform_id)"
|
||||
@keydown.escape="cancelEditSubdomain()"
|
||||
class="flex-1 px-3 py-1.5 text-sm border rounded-lg dark:bg-gray-800 dark:border-gray-600 dark:text-gray-300 focus:outline-none focus:ring-2 focus:ring-purple-500" />
|
||||
<span class="text-sm text-gray-400" x-text="'.' + entry.platform_domain" x-show="entry.platform_domain"></span>
|
||||
</div>
|
||||
<div class="flex justify-end gap-2 mt-2">
|
||||
<button
|
||||
@click="cancelEditSubdomain()"
|
||||
class="px-3 py-1 text-xs font-medium text-gray-600 bg-gray-200 rounded-lg hover:bg-gray-300 dark:bg-gray-600 dark:text-gray-300 dark:hover:bg-gray-500">
|
||||
Cancel
|
||||
</button>
|
||||
<button
|
||||
@click="saveCustomSubdomain(entry.platform_id)"
|
||||
:disabled="!editingSubdomainValue || subdomainSaving"
|
||||
class="px-3 py-1 text-xs font-medium text-white bg-purple-600 rounded-lg hover:bg-purple-700 disabled:opacity-50 disabled:cursor-not-allowed">
|
||||
<span x-show="!subdomainSaving">Save</span>
|
||||
<span x-show="subdomainSaving">Saving...</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
<!-- No Platform Memberships -->
|
||||
<div x-show="!customSubdomainsLoading && customSubdomains.length === 0" class="text-center py-4">
|
||||
<p class="text-sm text-gray-500 dark:text-gray-400">No active platform memberships found.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- More Actions -->
|
||||
<div class="px-4 py-3 bg-white rounded-lg shadow-md dark:bg-gray-800">
|
||||
<h3 class="mb-4 text-lg font-semibold text-gray-700 dark:text-gray-200">
|
||||
|
||||
Reference in New Issue
Block a user