e0bae394ee
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
66 lines
1.4 KiB
Svelte
66 lines
1.4 KiB
Svelte
<script lang="ts">
|
|
import MdiIcon from './MdiIcon.svelte';
|
|
|
|
let { icon, title = '', onclick, disabled = false, variant = 'default', size = 16, class: className = '' } = $props<{
|
|
icon: string;
|
|
title?: string;
|
|
onclick?: (e: MouseEvent) => void;
|
|
disabled?: boolean;
|
|
variant?: 'default' | 'danger' | 'success';
|
|
size?: number;
|
|
class?: string;
|
|
}>();
|
|
</script>
|
|
|
|
<button type="button" {title} {onclick} {disabled}
|
|
class="icon-btn icon-btn-{variant} {className}"
|
|
>
|
|
<MdiIcon name={icon} {size} />
|
|
</button>
|
|
|
|
<style>
|
|
.icon-btn {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
width: 2rem;
|
|
height: 2rem;
|
|
border-radius: 0.5rem;
|
|
border: none;
|
|
background: transparent;
|
|
cursor: pointer;
|
|
transition: all 0.2s ease;
|
|
}
|
|
|
|
.icon-btn:disabled {
|
|
opacity: 0.4;
|
|
pointer-events: none;
|
|
}
|
|
|
|
.icon-btn-default {
|
|
color: var(--color-muted-foreground);
|
|
}
|
|
.icon-btn-default:hover {
|
|
color: var(--color-foreground);
|
|
background: var(--color-muted);
|
|
}
|
|
|
|
.icon-btn-danger {
|
|
color: var(--color-muted-foreground);
|
|
}
|
|
.icon-btn-danger:hover {
|
|
color: var(--color-destructive);
|
|
background: var(--color-error-bg);
|
|
box-shadow: 0 0 8px rgba(239, 68, 68, 0.15);
|
|
}
|
|
|
|
.icon-btn-success {
|
|
color: var(--color-muted-foreground);
|
|
}
|
|
.icon-btn-success:hover {
|
|
color: var(--color-success-fg);
|
|
background: var(--color-success-bg);
|
|
box-shadow: 0 0 8px rgba(5, 150, 105, 0.15);
|
|
}
|
|
</style>
|