feat: comprehensive code review fixes — security, performance, quality
Backend security: - Reject Gitea webhooks when webhook_secret is empty (was silently skipping) - Add slowapi rate limiting on login (5/min) and setup (3/min) endpoints - Add CORS middleware with configurable origins - Mask telegram_webhook_secret in settings API response - Protect system-owned command template configs from regular user modification - Increase minimum password length to 8 characters Backend performance: - Batch queries in _resolve_command_context (3 queries instead of 3N) - Concurrent album fetching with asyncio.gather in immich commands - Singleton Jinja2 SandboxedEnvironment (reuse instead of per-render creation) - TTLCache for rate limits (bounded memory, auto-eviction) - Optional aiohttp session reuse in send_reply/send_media_group Backend code quality: - Extract dispatch_helpers.py (shared link_data loading + event filtering) - Extract database/seeds.py from main.py (490 lines → dedicated module) - Split immich_handler.py (415 lines) into commands/immich/ subpackage - Replace bare except blocks with logged warnings - Add per-provider config validation (Pydantic models) - Truncate command input to 512 chars - Expose usage_* and desc_* slots in capabilities and variables API Frontend security: - CSS.escape() for user-controlled querySelector in highlight.ts - Client-side password min 8 chars validation on setup and password change Frontend code quality: - Replace any types with proper interfaces across top files - Decompose targets/+page.svelte into TargetForm + ReceiverSection - Fix $derived.by usage, $state mutation patterns - Add console.warn to empty catch blocks Frontend UX: - Auth redirect via goto() with "Redirecting..." state - Platform-aware Ctrl/Cmd K keyboard hint - Remove stat-card hover transform Frontend accessibility: - Modal: role=dialog, aria-modal, focus trap, restore focus - EntitySelect/IconGridSelect: listbox/option roles, aria-selected/disabled
This commit is contained in:
@@ -6,6 +6,8 @@
|
||||
label: string;
|
||||
icon?: string;
|
||||
desc?: string;
|
||||
disabled?: boolean;
|
||||
disabledHint?: string;
|
||||
}
|
||||
|
||||
let {
|
||||
@@ -62,6 +64,7 @@
|
||||
}
|
||||
|
||||
function selectItem(item: EntityItem) {
|
||||
if (item.disabled) return;
|
||||
value = item.value || null;
|
||||
onselect?.(value);
|
||||
closePalette();
|
||||
@@ -103,6 +106,8 @@
|
||||
|
||||
<!-- Trigger button -->
|
||||
<button type="button" class="es-trigger" class:es-sm={size === 'sm'} onclick={openPalette}
|
||||
aria-expanded={open}
|
||||
aria-haspopup="listbox"
|
||||
style="opacity: {disabled ? 0.5 : 1}; cursor: {disabled ? 'default' : 'pointer'};">
|
||||
{#if selected}
|
||||
{#if selected.icon}
|
||||
@@ -135,15 +140,19 @@
|
||||
<kbd class="ep-kbd">ESC</kbd>
|
||||
</div>
|
||||
|
||||
<div class="ep-list" bind:this={listEl}>
|
||||
<div class="ep-list" bind:this={listEl} role="listbox">
|
||||
{#if filtered.length === 0}
|
||||
<div class="ep-empty">No matches</div>
|
||||
{:else}
|
||||
{#each filtered as item, i}
|
||||
<button
|
||||
class="ep-item"
|
||||
class:ep-highlight={i === highlightIdx}
|
||||
class:ep-highlight={i === highlightIdx && !item.disabled}
|
||||
class:ep-current={String(item.value) === String(value)}
|
||||
class:ep-disabled={item.disabled}
|
||||
role="option"
|
||||
aria-selected={String(item.value) === String(value)}
|
||||
aria-disabled={item.disabled || undefined}
|
||||
onclick={() => selectItem(item)}
|
||||
onmouseenter={() => highlightIdx = i}
|
||||
type="button"
|
||||
@@ -152,7 +161,9 @@
|
||||
<span class="ep-item-icon"><MdiIcon name={item.icon} size={18} /></span>
|
||||
{/if}
|
||||
<span class="ep-item-label">{item.label}</span>
|
||||
{#if item.desc}
|
||||
{#if item.disabled && item.disabledHint}
|
||||
<span class="ep-item-hint">{item.disabledHint}</span>
|
||||
{:else if item.desc}
|
||||
<span class="ep-item-desc">{item.desc}</span>
|
||||
{/if}
|
||||
</button>
|
||||
@@ -292,6 +303,13 @@
|
||||
.ep-item:hover, .ep-item.ep-highlight {
|
||||
background: var(--color-muted);
|
||||
}
|
||||
.ep-item.ep-disabled {
|
||||
opacity: 0.4;
|
||||
cursor: default;
|
||||
}
|
||||
.ep-item.ep-disabled:hover {
|
||||
background: transparent;
|
||||
}
|
||||
.ep-item.ep-current {
|
||||
border-left-color: var(--color-primary);
|
||||
}
|
||||
@@ -319,4 +337,11 @@
|
||||
text-overflow: ellipsis;
|
||||
max-width: 40%;
|
||||
}
|
||||
.ep-item-hint {
|
||||
font-size: 0.7rem;
|
||||
font-style: italic;
|
||||
color: var(--color-muted-foreground);
|
||||
white-space: nowrap;
|
||||
margin-left: auto;
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user