feat: add visual admin panel with page builder and settings editor
- Add /admin route with password-protected visual page builder
- Page Builder: manage hero images, standalone albums, subpages, and sections
- Album Picker: browse all shared Immich albums with search and filtering
- Settings Editor: theme, grid, footer, legal, SEO configuration
- YAML service with atomic writes and automatic backups (content/.backups/)
- HMAC-signed admin sessions (24h expiry, HttpOnly cookies)
- API endpoints: /api/admin/{auth,gallery,settings,albums,reload}
- Enable with ADMIN_PASSWORD env var; disabled by default
- invalidateConfigCache() export for hot-reload after saves
2026-05-29 17:04:37 +02:00
|
|
|
|
'use client';
|
|
|
|
|
|
|
|
|
|
|
|
import { useState, useEffect, useCallback } from 'react';
|
2026-05-29 17:59:25 +02:00
|
|
|
|
import {
|
|
|
|
|
|
DndContext,
|
|
|
|
|
|
closestCenter,
|
|
|
|
|
|
KeyboardSensor,
|
|
|
|
|
|
PointerSensor,
|
|
|
|
|
|
useSensor,
|
|
|
|
|
|
useSensors,
|
|
|
|
|
|
DragEndEvent,
|
|
|
|
|
|
} from '@dnd-kit/core';
|
|
|
|
|
|
import {
|
|
|
|
|
|
arrayMove,
|
|
|
|
|
|
SortableContext,
|
|
|
|
|
|
sortableKeyboardCoordinates,
|
|
|
|
|
|
useSortable,
|
|
|
|
|
|
verticalListSortingStrategy,
|
|
|
|
|
|
horizontalListSortingStrategy,
|
|
|
|
|
|
} from '@dnd-kit/sortable';
|
|
|
|
|
|
import { CSS } from '@dnd-kit/utilities';
|
feat: add visual admin panel with page builder and settings editor
- Add /admin route with password-protected visual page builder
- Page Builder: manage hero images, standalone albums, subpages, and sections
- Album Picker: browse all shared Immich albums with search and filtering
- Settings Editor: theme, grid, footer, legal, SEO configuration
- YAML service with atomic writes and automatic backups (content/.backups/)
- HMAC-signed admin sessions (24h expiry, HttpOnly cookies)
- API endpoints: /api/admin/{auth,gallery,settings,albums,reload}
- Enable with ADMIN_PASSWORD env var; disabled by default
- invalidateConfigCache() export for hot-reload after saves
2026-05-29 17:04:37 +02:00
|
|
|
|
import AlbumPicker from './AlbumPicker';
|
2026-05-29 17:59:25 +02:00
|
|
|
|
import AssetPicker from './AssetPicker';
|
security: add Trivy container scan to Docker publish workflow (#399)
* feat(admin): add backup manager UI with 1-click restore
* feat(admin): collapse older backup entries by default
* fix(theme): isolate admin layout, instant DOM theme updates, and add Light/Dark mode toggle
* feat(admin): replace text controls for Photo Frame and Hero Style with visual preview cards
* fix(admin): define missing CSS rules for button system (.btn, .preset-btn)
* feat(admin): redesign theme settings with segmented mode toggle, accent color swatches, and custom iOS-style toggle cards
* feat(admin): upgrade all settings tabs with visual grid previews, Google SEO snippet preview, and toggle cards
* feat(admin): overhaul Pages Builder UX with slide-over drawer, card hover badges, real-time search, and floating save bar
* fix(admin): remove leftover dangling syntax snippet in PageBuilder.tsx
* fix(admin): add position: relative to subpage-tile so hover overlay is contained inside the tile
* style(admin): replace all emojis with clean Leica-style vector SVG icons
* fix(types): extend IconProps with SVGProps to allow style prop on Leica SVG icons
* feat(admin): add subpage live-preview, privacy analytics, and asset protection settings
* fix(typecheck): fix AlbumEntry property access in subpage drawer preview mode
* fix(types): fix ImmichAlbumInfo property names thumbnailAssetId and albumName in PageBuilder.tsx
* fix(types): add protection and watermark optional properties to AppConfig interface
* feat(settings): add toggle for disabling analytics tracking (defaults to enabled)
* feat(ux): show status banner when analytics tracking is disabled + add live sync indicator
* feat(watermark): render customizable watermark overlay on Lightbox images + rename section to Portfolio Features & Modules
* fix(types): import type LightboxWatermark in PhotoGrid.tsx
* fix(types): destructure watermark prop in PhotoGrid function signature
* feat(seo): add configurable subpage title template and metadata descriptions
* feat(seo): add generateMetadata to about page for title template support
* feat(proofing): add client proofing, favorites selection, compact URL bitmasking, and export modal
* feat(essay): add Photo Essay storytelling mode with RSC markdown parser and fullbleed/pair layouts
* feat(subpages): add page enabled toggle to deactivate subpages without deleting them
* feat(admin): add visual essay block builder for photo essay storytelling pages
* security: add Trivy container scan to Docker publish workflow
Scans the built image for CRITICAL/HIGH CVEs on every release and
uploads results to the GitHub Security tab. Non-blocking for now
(exit-code 0) so releases aren't gated on findings. Also documents
the scan and existing Dependabot coverage in the README.
---------
Co-authored-by: ralksta <ralksta@users.noreply.github.com>
2026-08-07 11:08:04 +02:00
|
|
|
|
import { EssayBlockEditor } from './EssayBlockEditor';
|
feat: add visual admin panel with page builder and settings editor
- Add /admin route with password-protected visual page builder
- Page Builder: manage hero images, standalone albums, subpages, and sections
- Album Picker: browse all shared Immich albums with search and filtering
- Settings Editor: theme, grid, footer, legal, SEO configuration
- YAML service with atomic writes and automatic backups (content/.backups/)
- HMAC-signed admin sessions (24h expiry, HttpOnly cookies)
- API endpoints: /api/admin/{auth,gallery,settings,albums,reload}
- Enable with ADMIN_PASSWORD env var; disabled by default
- invalidateConfigCache() export for hot-reload after saves
2026-05-29 17:04:37 +02:00
|
|
|
|
|
2026-05-31 21:27:13 +02:00
|
|
|
|
// ── Icons ──────────────────────────────────────────────────────
|
|
|
|
|
|
const Icons = {
|
|
|
|
|
|
Drag: () => (
|
|
|
|
|
|
<svg width="12" height="18" viewBox="0 0 12 18" fill="currentColor" className="svg-icon svg-drag">
|
|
|
|
|
|
<circle cx="2" cy="2" r="1.5" />
|
|
|
|
|
|
<circle cx="2" cy="9" r="1.5" />
|
|
|
|
|
|
<circle cx="2" cy="16" r="1.5" />
|
|
|
|
|
|
<circle cx="10" cy="2" r="1.5" />
|
|
|
|
|
|
<circle cx="10" cy="9" r="1.5" />
|
|
|
|
|
|
<circle cx="10" cy="16" r="1.5" />
|
|
|
|
|
|
</svg>
|
|
|
|
|
|
),
|
|
|
|
|
|
Edit: () => (
|
|
|
|
|
|
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" className="svg-icon">
|
|
|
|
|
|
<path d="M12 20h9" />
|
|
|
|
|
|
<path d="M16.5 3.5a2.12 2.12 0 0 1 3 3L7 19l-4 1 1-4Z" />
|
|
|
|
|
|
</svg>
|
|
|
|
|
|
),
|
|
|
|
|
|
Trash: () => (
|
|
|
|
|
|
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" className="svg-icon">
|
|
|
|
|
|
<path d="M3 6h18" />
|
|
|
|
|
|
<path d="M19 6v14c0 1-1 2-2 2H7c-1 0-2-1-2-2V6" />
|
|
|
|
|
|
<path d="M8 6V4c0-1 1-2 2-2h4c1 0 2 1 2 2v2" />
|
|
|
|
|
|
</svg>
|
|
|
|
|
|
),
|
|
|
|
|
|
Close: () => (
|
|
|
|
|
|
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" className="svg-icon">
|
|
|
|
|
|
<path d="M18 6 6 18M6 6l12 12" />
|
|
|
|
|
|
</svg>
|
|
|
|
|
|
),
|
|
|
|
|
|
Folder: () => (
|
|
|
|
|
|
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" className="svg-icon">
|
|
|
|
|
|
<path d="M20 20a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2h-7.9a2 2 0 0 1-1.69-.9L9.6 3.9A2 2 0 0 0 7.93 3H4a2 2 0 0 0-2 2v13a2 2 0 0 0 2 2Z" />
|
|
|
|
|
|
</svg>
|
|
|
|
|
|
),
|
|
|
|
|
|
Camera: () => (
|
|
|
|
|
|
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" className="svg-icon">
|
|
|
|
|
|
<path d="M14.5 4h-5L7 7H4a2 2 0 0 0-2 2v9a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2V9a2 2 0 0 0-2-2h-3Z" />
|
|
|
|
|
|
<circle cx="12" cy="13" r="3" />
|
|
|
|
|
|
</svg>
|
|
|
|
|
|
),
|
|
|
|
|
|
ExternalLink: () => (
|
|
|
|
|
|
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" className="svg-icon">
|
|
|
|
|
|
<path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6" />
|
|
|
|
|
|
<polyline points="15 3 21 3 21 9" />
|
|
|
|
|
|
<line x1="10" y1="14" x2="21" y2="3" />
|
|
|
|
|
|
</svg>
|
|
|
|
|
|
),
|
|
|
|
|
|
Search: () => (
|
|
|
|
|
|
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" className="svg-icon">
|
|
|
|
|
|
<circle cx="11" cy="11" r="8" />
|
|
|
|
|
|
<path d="m21 21-4.3-4.3" />
|
|
|
|
|
|
</svg>
|
|
|
|
|
|
),
|
|
|
|
|
|
Lock: () => (
|
|
|
|
|
|
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" className="svg-icon">
|
|
|
|
|
|
<rect width="18" height="11" x="3" y="11" rx="2" ry="2" />
|
|
|
|
|
|
<path d="M7 11V7a5 5 0 0 1 10 0v4" />
|
|
|
|
|
|
</svg>
|
|
|
|
|
|
),
|
|
|
|
|
|
Plus: () => (
|
|
|
|
|
|
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" className="svg-icon">
|
|
|
|
|
|
<path d="M5 12h14M12 5v14" />
|
|
|
|
|
|
</svg>
|
|
|
|
|
|
),
|
|
|
|
|
|
Home: () => (
|
|
|
|
|
|
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" className="svg-icon">
|
|
|
|
|
|
<path d="m3 9 9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z" />
|
|
|
|
|
|
<polyline points="9 22 9 12 15 12 15 22" />
|
|
|
|
|
|
</svg>
|
|
|
|
|
|
),
|
|
|
|
|
|
Copy: () => (
|
|
|
|
|
|
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" className="svg-icon">
|
|
|
|
|
|
<rect width="14" height="14" x="8" y="8" rx="2" ry="2" />
|
|
|
|
|
|
<path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" />
|
|
|
|
|
|
</svg>
|
|
|
|
|
|
),
|
|
|
|
|
|
Check: () => (
|
|
|
|
|
|
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" className="svg-icon">
|
|
|
|
|
|
<polyline points="20 6 9 17 4 12" />
|
|
|
|
|
|
</svg>
|
|
|
|
|
|
),
|
|
|
|
|
|
Image: () => (
|
|
|
|
|
|
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" className="svg-icon">
|
|
|
|
|
|
<rect width="18" height="18" x="3" y="3" rx="2" ry="2" />
|
|
|
|
|
|
<circle cx="9" cy="9" r="2" />
|
|
|
|
|
|
<path d="m21 15-3.086-3.086a2 2 0 0 0-2.828 0L6 21" />
|
|
|
|
|
|
</svg>
|
|
|
|
|
|
),
|
|
|
|
|
|
};
|
|
|
|
|
|
|
feat: add visual admin panel with page builder and settings editor
- Add /admin route with password-protected visual page builder
- Page Builder: manage hero images, standalone albums, subpages, and sections
- Album Picker: browse all shared Immich albums with search and filtering
- Settings Editor: theme, grid, footer, legal, SEO configuration
- YAML service with atomic writes and automatic backups (content/.backups/)
- HMAC-signed admin sessions (24h expiry, HttpOnly cookies)
- API endpoints: /api/admin/{auth,gallery,settings,albums,reload}
- Enable with ADMIN_PASSWORD env var; disabled by default
- invalidateConfigCache() export for hot-reload after saves
2026-05-29 17:04:37 +02:00
|
|
|
|
// ── Types ──────────────────────────────────────────────────────
|
|
|
|
|
|
interface AlbumEntry {
|
|
|
|
|
|
id: string;
|
|
|
|
|
|
title?: string;
|
|
|
|
|
|
description?: string;
|
|
|
|
|
|
password?: string;
|
2026-05-30 15:33:43 +02:00
|
|
|
|
heroImage?: string;
|
feat: add visual admin panel with page builder and settings editor
- Add /admin route with password-protected visual page builder
- Page Builder: manage hero images, standalone albums, subpages, and sections
- Album Picker: browse all shared Immich albums with search and filtering
- Settings Editor: theme, grid, footer, legal, SEO configuration
- YAML service with atomic writes and automatic backups (content/.backups/)
- HMAC-signed admin sessions (24h expiry, HttpOnly cookies)
- API endpoints: /api/admin/{auth,gallery,settings,albums,reload}
- Enable with ADMIN_PASSWORD env var; disabled by default
- invalidateConfigCache() export for hot-reload after saves
2026-05-29 17:04:37 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
interface Section {
|
|
|
|
|
|
title: string;
|
|
|
|
|
|
description?: string;
|
|
|
|
|
|
albums: AlbumEntry[];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
interface Subpage {
|
|
|
|
|
|
name: string;
|
|
|
|
|
|
title?: string;
|
|
|
|
|
|
subtitle?: string;
|
|
|
|
|
|
password?: string;
|
security: add Trivy container scan to Docker publish workflow (#399)
* feat(admin): add backup manager UI with 1-click restore
* feat(admin): collapse older backup entries by default
* fix(theme): isolate admin layout, instant DOM theme updates, and add Light/Dark mode toggle
* feat(admin): replace text controls for Photo Frame and Hero Style with visual preview cards
* fix(admin): define missing CSS rules for button system (.btn, .preset-btn)
* feat(admin): redesign theme settings with segmented mode toggle, accent color swatches, and custom iOS-style toggle cards
* feat(admin): upgrade all settings tabs with visual grid previews, Google SEO snippet preview, and toggle cards
* feat(admin): overhaul Pages Builder UX with slide-over drawer, card hover badges, real-time search, and floating save bar
* fix(admin): remove leftover dangling syntax snippet in PageBuilder.tsx
* fix(admin): add position: relative to subpage-tile so hover overlay is contained inside the tile
* style(admin): replace all emojis with clean Leica-style vector SVG icons
* fix(types): extend IconProps with SVGProps to allow style prop on Leica SVG icons
* feat(admin): add subpage live-preview, privacy analytics, and asset protection settings
* fix(typecheck): fix AlbumEntry property access in subpage drawer preview mode
* fix(types): fix ImmichAlbumInfo property names thumbnailAssetId and albumName in PageBuilder.tsx
* fix(types): add protection and watermark optional properties to AppConfig interface
* feat(settings): add toggle for disabling analytics tracking (defaults to enabled)
* feat(ux): show status banner when analytics tracking is disabled + add live sync indicator
* feat(watermark): render customizable watermark overlay on Lightbox images + rename section to Portfolio Features & Modules
* fix(types): import type LightboxWatermark in PhotoGrid.tsx
* fix(types): destructure watermark prop in PhotoGrid function signature
* feat(seo): add configurable subpage title template and metadata descriptions
* feat(seo): add generateMetadata to about page for title template support
* feat(proofing): add client proofing, favorites selection, compact URL bitmasking, and export modal
* feat(essay): add Photo Essay storytelling mode with RSC markdown parser and fullbleed/pair layouts
* feat(subpages): add page enabled toggle to deactivate subpages without deleting them
* feat(admin): add visual essay block builder for photo essay storytelling pages
* security: add Trivy container scan to Docker publish workflow
Scans the built image for CRITICAL/HIGH CVEs on every release and
uploads results to the GitHub Security tab. Non-blocking for now
(exit-code 0) so releases aren't gated on findings. Also documents
the scan and existing Dependabot coverage in the README.
---------
Co-authored-by: ralksta <ralksta@users.noreply.github.com>
2026-08-07 11:08:04 +02:00
|
|
|
|
enabled?: boolean;
|
|
|
|
|
|
essayText?: string;
|
|
|
|
|
|
essayFile?: string;
|
feat: add visual admin panel with page builder and settings editor
- Add /admin route with password-protected visual page builder
- Page Builder: manage hero images, standalone albums, subpages, and sections
- Album Picker: browse all shared Immich albums with search and filtering
- Settings Editor: theme, grid, footer, legal, SEO configuration
- YAML service with atomic writes and automatic backups (content/.backups/)
- HMAC-signed admin sessions (24h expiry, HttpOnly cookies)
- API endpoints: /api/admin/{auth,gallery,settings,albums,reload}
- Enable with ADMIN_PASSWORD env var; disabled by default
- invalidateConfigCache() export for hot-reload after saves
2026-05-29 17:04:37 +02:00
|
|
|
|
sections?: Section[];
|
|
|
|
|
|
albums: AlbumEntry[];
|
|
|
|
|
|
grid?: { columns?: number; gap?: number; aspectRatio?: string; layout?: string };
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
interface GalleryState {
|
|
|
|
|
|
hero: string[];
|
|
|
|
|
|
albums: AlbumEntry[];
|
|
|
|
|
|
subpages: Subpage[];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-31 21:27:13 +02:00
|
|
|
|
interface ActiveEditAlbumAddress {
|
|
|
|
|
|
type: 'standalone' | 'subpage' | 'section';
|
|
|
|
|
|
subpageIndex?: number;
|
|
|
|
|
|
sectionIndex?: number;
|
|
|
|
|
|
albumIndex: number;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
feat: add visual admin panel with page builder and settings editor
- Add /admin route with password-protected visual page builder
- Page Builder: manage hero images, standalone albums, subpages, and sections
- Album Picker: browse all shared Immich albums with search and filtering
- Settings Editor: theme, grid, footer, legal, SEO configuration
- YAML service with atomic writes and automatic backups (content/.backups/)
- HMAC-signed admin sessions (24h expiry, HttpOnly cookies)
- API endpoints: /api/admin/{auth,gallery,settings,albums,reload}
- Enable with ADMIN_PASSWORD env var; disabled by default
- invalidateConfigCache() export for hot-reload after saves
2026-05-29 17:04:37 +02:00
|
|
|
|
interface ImmichAlbumInfo {
|
|
|
|
|
|
id: string;
|
|
|
|
|
|
albumName: string;
|
|
|
|
|
|
description: string;
|
|
|
|
|
|
thumbnailAssetId: string | null;
|
|
|
|
|
|
assetCount: number;
|
|
|
|
|
|
isConfigured: boolean;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ── Helpers ────────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
|
|
function parseAlbumEntries(
|
|
|
|
|
|
raw:
|
|
|
|
|
|
| Array<
|
2026-05-30 15:33:43 +02:00
|
|
|
|
| string
|
|
|
|
|
|
| Record<
|
|
|
|
|
|
string,
|
|
|
|
|
|
string | { title: string; description?: string; password?: string; heroImage?: string }
|
|
|
|
|
|
>
|
feat: add visual admin panel with page builder and settings editor
- Add /admin route with password-protected visual page builder
- Page Builder: manage hero images, standalone albums, subpages, and sections
- Album Picker: browse all shared Immich albums with search and filtering
- Settings Editor: theme, grid, footer, legal, SEO configuration
- YAML service with atomic writes and automatic backups (content/.backups/)
- HMAC-signed admin sessions (24h expiry, HttpOnly cookies)
- API endpoints: /api/admin/{auth,gallery,settings,albums,reload}
- Enable with ADMIN_PASSWORD env var; disabled by default
- invalidateConfigCache() export for hot-reload after saves
2026-05-29 17:04:37 +02:00
|
|
|
|
>
|
|
|
|
|
|
| undefined,
|
|
|
|
|
|
): AlbumEntry[] {
|
|
|
|
|
|
if (!raw) return [];
|
|
|
|
|
|
return raw.map((entry) => {
|
|
|
|
|
|
if (typeof entry === 'string') return { id: entry };
|
|
|
|
|
|
const [id, value] = Object.entries(entry)[0];
|
|
|
|
|
|
if (typeof value === 'string') return { id, title: value };
|
2026-05-30 15:33:43 +02:00
|
|
|
|
return {
|
|
|
|
|
|
id,
|
|
|
|
|
|
title: value.title,
|
|
|
|
|
|
description: value.description,
|
|
|
|
|
|
password: value.password,
|
|
|
|
|
|
heroImage: value.heroImage,
|
|
|
|
|
|
};
|
feat: add visual admin panel with page builder and settings editor
- Add /admin route with password-protected visual page builder
- Page Builder: manage hero images, standalone albums, subpages, and sections
- Album Picker: browse all shared Immich albums with search and filtering
- Settings Editor: theme, grid, footer, legal, SEO configuration
- YAML service with atomic writes and automatic backups (content/.backups/)
- HMAC-signed admin sessions (24h expiry, HttpOnly cookies)
- API endpoints: /api/admin/{auth,gallery,settings,albums,reload}
- Enable with ADMIN_PASSWORD env var; disabled by default
- invalidateConfigCache() export for hot-reload after saves
2026-05-29 17:04:37 +02:00
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function serializeAlbumEntries(
|
|
|
|
|
|
entries: AlbumEntry[],
|
|
|
|
|
|
): Array<
|
2026-05-30 15:33:43 +02:00
|
|
|
|
| string
|
|
|
|
|
|
| Record<string, string | { title: string; description?: string; password?: string; heroImage?: string }>
|
feat: add visual admin panel with page builder and settings editor
- Add /admin route with password-protected visual page builder
- Page Builder: manage hero images, standalone albums, subpages, and sections
- Album Picker: browse all shared Immich albums with search and filtering
- Settings Editor: theme, grid, footer, legal, SEO configuration
- YAML service with atomic writes and automatic backups (content/.backups/)
- HMAC-signed admin sessions (24h expiry, HttpOnly cookies)
- API endpoints: /api/admin/{auth,gallery,settings,albums,reload}
- Enable with ADMIN_PASSWORD env var; disabled by default
- invalidateConfigCache() export for hot-reload after saves
2026-05-29 17:04:37 +02:00
|
|
|
|
> {
|
|
|
|
|
|
return entries.map((entry) => {
|
2026-05-30 15:33:43 +02:00
|
|
|
|
if (!entry.title && !entry.description && !entry.password && !entry.heroImage) return entry.id;
|
|
|
|
|
|
if (entry.title && !entry.description && !entry.password && !entry.heroImage) {
|
feat: add visual admin panel with page builder and settings editor
- Add /admin route with password-protected visual page builder
- Page Builder: manage hero images, standalone albums, subpages, and sections
- Album Picker: browse all shared Immich albums with search and filtering
- Settings Editor: theme, grid, footer, legal, SEO configuration
- YAML service with atomic writes and automatic backups (content/.backups/)
- HMAC-signed admin sessions (24h expiry, HttpOnly cookies)
- API endpoints: /api/admin/{auth,gallery,settings,albums,reload}
- Enable with ADMIN_PASSWORD env var; disabled by default
- invalidateConfigCache() export for hot-reload after saves
2026-05-29 17:04:37 +02:00
|
|
|
|
return { [entry.id]: entry.title };
|
|
|
|
|
|
}
|
2026-05-30 15:33:43 +02:00
|
|
|
|
const val: { title: string; description?: string; password?: string; heroImage?: string } = {
|
feat: add visual admin panel with page builder and settings editor
- Add /admin route with password-protected visual page builder
- Page Builder: manage hero images, standalone albums, subpages, and sections
- Album Picker: browse all shared Immich albums with search and filtering
- Settings Editor: theme, grid, footer, legal, SEO configuration
- YAML service with atomic writes and automatic backups (content/.backups/)
- HMAC-signed admin sessions (24h expiry, HttpOnly cookies)
- API endpoints: /api/admin/{auth,gallery,settings,albums,reload}
- Enable with ADMIN_PASSWORD env var; disabled by default
- invalidateConfigCache() export for hot-reload after saves
2026-05-29 17:04:37 +02:00
|
|
|
|
title: entry.title || '',
|
|
|
|
|
|
};
|
|
|
|
|
|
if (entry.description) val.description = entry.description;
|
|
|
|
|
|
if (entry.password) val.password = entry.password;
|
2026-05-30 15:33:43 +02:00
|
|
|
|
if (entry.heroImage) val.heroImage = entry.heroImage;
|
feat: add visual admin panel with page builder and settings editor
- Add /admin route with password-protected visual page builder
- Page Builder: manage hero images, standalone albums, subpages, and sections
- Album Picker: browse all shared Immich albums with search and filtering
- Settings Editor: theme, grid, footer, legal, SEO configuration
- YAML service with atomic writes and automatic backups (content/.backups/)
- HMAC-signed admin sessions (24h expiry, HttpOnly cookies)
- API endpoints: /api/admin/{auth,gallery,settings,albums,reload}
- Enable with ADMIN_PASSWORD env var; disabled by default
- invalidateConfigCache() export for hot-reload after saves
2026-05-29 17:04:37 +02:00
|
|
|
|
return { [entry.id]: val };
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-29 17:59:25 +02:00
|
|
|
|
// ── Sortable Hero Tile ─────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
|
|
function SortableHeroTile({
|
|
|
|
|
|
id,
|
|
|
|
|
|
index,
|
|
|
|
|
|
onRemove,
|
|
|
|
|
|
}: {
|
|
|
|
|
|
id: string;
|
|
|
|
|
|
index: number;
|
|
|
|
|
|
onRemove: () => void;
|
|
|
|
|
|
}) {
|
|
|
|
|
|
const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({
|
|
|
|
|
|
id: `hero-${index}`,
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
const style = {
|
|
|
|
|
|
transform: CSS.Transform.toString(transform),
|
|
|
|
|
|
transition,
|
|
|
|
|
|
opacity: isDragging ? 0.5 : 1,
|
|
|
|
|
|
zIndex: isDragging ? 10 : undefined,
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
|
<div ref={setNodeRef} style={style} className="hero-tile" {...attributes}>
|
|
|
|
|
|
<div className="hero-tile-drag" {...listeners} title="Drag to reorder">
|
2026-05-31 21:27:13 +02:00
|
|
|
|
<Icons.Drag />
|
2026-05-29 17:59:25 +02:00
|
|
|
|
</div>
|
|
|
|
|
|
<img src={`/api/admin/thumbnail/${id}`} alt="" loading="lazy" />
|
|
|
|
|
|
<button className="hero-tile-remove" onClick={onRemove} title="Remove">
|
2026-05-31 21:27:13 +02:00
|
|
|
|
<Icons.Close />
|
2026-05-29 17:59:25 +02:00
|
|
|
|
</button>
|
|
|
|
|
|
<span className="hero-tile-index">{index + 1}</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ── Sortable Album Card ────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
|
|
function SortableAlbumCard({
|
|
|
|
|
|
album,
|
|
|
|
|
|
index,
|
|
|
|
|
|
name,
|
|
|
|
|
|
count,
|
|
|
|
|
|
thumbnailId,
|
|
|
|
|
|
onRemove,
|
2026-05-31 21:27:13 +02:00
|
|
|
|
onEdit,
|
2026-05-29 17:59:25 +02:00
|
|
|
|
}: {
|
|
|
|
|
|
album: AlbumEntry;
|
|
|
|
|
|
index: number;
|
|
|
|
|
|
name: string;
|
|
|
|
|
|
count: number;
|
|
|
|
|
|
thumbnailId: string | null;
|
|
|
|
|
|
onRemove: () => void;
|
2026-05-31 21:27:13 +02:00
|
|
|
|
onEdit: () => void;
|
2026-05-29 17:59:25 +02:00
|
|
|
|
}) {
|
|
|
|
|
|
const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({
|
|
|
|
|
|
id: `album-${album.id}-${index}`,
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
const style = {
|
|
|
|
|
|
transform: CSS.Transform.toString(transform),
|
|
|
|
|
|
transition,
|
|
|
|
|
|
opacity: isDragging ? 0.5 : 1,
|
|
|
|
|
|
zIndex: isDragging ? 10 : undefined,
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
|
<div ref={setNodeRef} style={style} {...attributes}>
|
|
|
|
|
|
<AlbumCard
|
|
|
|
|
|
album={album}
|
|
|
|
|
|
name={name}
|
|
|
|
|
|
count={count}
|
|
|
|
|
|
thumbnailId={thumbnailId}
|
|
|
|
|
|
onRemove={onRemove}
|
2026-05-31 21:27:13 +02:00
|
|
|
|
onEdit={onEdit}
|
2026-05-29 17:59:25 +02:00
|
|
|
|
dragListeners={listeners}
|
|
|
|
|
|
/>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ── Sortable Subpage Tile ──────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
|
|
function SortableSubpageTile({
|
|
|
|
|
|
sp,
|
|
|
|
|
|
spIndex,
|
|
|
|
|
|
isActive,
|
|
|
|
|
|
onClick,
|
|
|
|
|
|
getFirstThumb,
|
|
|
|
|
|
}: {
|
|
|
|
|
|
sp: Subpage;
|
|
|
|
|
|
spIndex: number;
|
|
|
|
|
|
isActive: boolean;
|
|
|
|
|
|
onClick: () => void;
|
|
|
|
|
|
getFirstThumb: (sp: Subpage) => string | null;
|
|
|
|
|
|
}) {
|
|
|
|
|
|
const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({
|
|
|
|
|
|
id: `subpage-${spIndex}`,
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
const style = {
|
|
|
|
|
|
transform: CSS.Transform.toString(transform),
|
|
|
|
|
|
transition,
|
|
|
|
|
|
opacity: isDragging ? 0.5 : 1,
|
|
|
|
|
|
zIndex: isDragging ? 10 : undefined,
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const totalAlbums =
|
|
|
|
|
|
sp.albums.length + (sp.sections?.reduce((sum, sec) => sum + sec.albums.length, 0) || 0);
|
|
|
|
|
|
const firstThumb = getFirstThumb(sp);
|
security: add Trivy container scan to Docker publish workflow (#399)
* feat(admin): add backup manager UI with 1-click restore
* feat(admin): collapse older backup entries by default
* fix(theme): isolate admin layout, instant DOM theme updates, and add Light/Dark mode toggle
* feat(admin): replace text controls for Photo Frame and Hero Style with visual preview cards
* fix(admin): define missing CSS rules for button system (.btn, .preset-btn)
* feat(admin): redesign theme settings with segmented mode toggle, accent color swatches, and custom iOS-style toggle cards
* feat(admin): upgrade all settings tabs with visual grid previews, Google SEO snippet preview, and toggle cards
* feat(admin): overhaul Pages Builder UX with slide-over drawer, card hover badges, real-time search, and floating save bar
* fix(admin): remove leftover dangling syntax snippet in PageBuilder.tsx
* fix(admin): add position: relative to subpage-tile so hover overlay is contained inside the tile
* style(admin): replace all emojis with clean Leica-style vector SVG icons
* fix(types): extend IconProps with SVGProps to allow style prop on Leica SVG icons
* feat(admin): add subpage live-preview, privacy analytics, and asset protection settings
* fix(typecheck): fix AlbumEntry property access in subpage drawer preview mode
* fix(types): fix ImmichAlbumInfo property names thumbnailAssetId and albumName in PageBuilder.tsx
* fix(types): add protection and watermark optional properties to AppConfig interface
* feat(settings): add toggle for disabling analytics tracking (defaults to enabled)
* feat(ux): show status banner when analytics tracking is disabled + add live sync indicator
* feat(watermark): render customizable watermark overlay on Lightbox images + rename section to Portfolio Features & Modules
* fix(types): import type LightboxWatermark in PhotoGrid.tsx
* fix(types): destructure watermark prop in PhotoGrid function signature
* feat(seo): add configurable subpage title template and metadata descriptions
* feat(seo): add generateMetadata to about page for title template support
* feat(proofing): add client proofing, favorites selection, compact URL bitmasking, and export modal
* feat(essay): add Photo Essay storytelling mode with RSC markdown parser and fullbleed/pair layouts
* feat(subpages): add page enabled toggle to deactivate subpages without deleting them
* feat(admin): add visual essay block builder for photo essay storytelling pages
* security: add Trivy container scan to Docker publish workflow
Scans the built image for CRITICAL/HIGH CVEs on every release and
uploads results to the GitHub Security tab. Non-blocking for now
(exit-code 0) so releases aren't gated on findings. Also documents
the scan and existing Dependabot coverage in the README.
---------
Co-authored-by: ralksta <ralksta@users.noreply.github.com>
2026-08-07 11:08:04 +02:00
|
|
|
|
const slug = sp.name.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '');
|
2026-05-29 17:59:25 +02:00
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
|
<div
|
|
|
|
|
|
ref={setNodeRef}
|
|
|
|
|
|
style={style}
|
|
|
|
|
|
className={`subpage-tile ${isActive ? 'active' : ''}`}
|
|
|
|
|
|
onClick={onClick}
|
|
|
|
|
|
{...attributes}
|
|
|
|
|
|
>
|
|
|
|
|
|
<div className="subpage-tile-drag" {...listeners} title="Drag to reorder">
|
2026-05-31 21:27:13 +02:00
|
|
|
|
<Icons.Drag />
|
2026-05-29 17:59:25 +02:00
|
|
|
|
</div>
|
security: add Trivy container scan to Docker publish workflow (#399)
* feat(admin): add backup manager UI with 1-click restore
* feat(admin): collapse older backup entries by default
* fix(theme): isolate admin layout, instant DOM theme updates, and add Light/Dark mode toggle
* feat(admin): replace text controls for Photo Frame and Hero Style with visual preview cards
* fix(admin): define missing CSS rules for button system (.btn, .preset-btn)
* feat(admin): redesign theme settings with segmented mode toggle, accent color swatches, and custom iOS-style toggle cards
* feat(admin): upgrade all settings tabs with visual grid previews, Google SEO snippet preview, and toggle cards
* feat(admin): overhaul Pages Builder UX with slide-over drawer, card hover badges, real-time search, and floating save bar
* fix(admin): remove leftover dangling syntax snippet in PageBuilder.tsx
* fix(admin): add position: relative to subpage-tile so hover overlay is contained inside the tile
* style(admin): replace all emojis with clean Leica-style vector SVG icons
* fix(types): extend IconProps with SVGProps to allow style prop on Leica SVG icons
* feat(admin): add subpage live-preview, privacy analytics, and asset protection settings
* fix(typecheck): fix AlbumEntry property access in subpage drawer preview mode
* fix(types): fix ImmichAlbumInfo property names thumbnailAssetId and albumName in PageBuilder.tsx
* fix(types): add protection and watermark optional properties to AppConfig interface
* feat(settings): add toggle for disabling analytics tracking (defaults to enabled)
* feat(ux): show status banner when analytics tracking is disabled + add live sync indicator
* feat(watermark): render customizable watermark overlay on Lightbox images + rename section to Portfolio Features & Modules
* fix(types): import type LightboxWatermark in PhotoGrid.tsx
* fix(types): destructure watermark prop in PhotoGrid function signature
* feat(seo): add configurable subpage title template and metadata descriptions
* feat(seo): add generateMetadata to about page for title template support
* feat(proofing): add client proofing, favorites selection, compact URL bitmasking, and export modal
* feat(essay): add Photo Essay storytelling mode with RSC markdown parser and fullbleed/pair layouts
* feat(subpages): add page enabled toggle to deactivate subpages without deleting them
* feat(admin): add visual essay block builder for photo essay storytelling pages
* security: add Trivy container scan to Docker publish workflow
Scans the built image for CRITICAL/HIGH CVEs on every release and
uploads results to the GitHub Security tab. Non-blocking for now
(exit-code 0) so releases aren't gated on findings. Also documents
the scan and existing Dependabot coverage in the README.
---------
Co-authored-by: ralksta <ralksta@users.noreply.github.com>
2026-08-07 11:08:04 +02:00
|
|
|
|
|
|
|
|
|
|
{sp.enabled === false && (
|
|
|
|
|
|
<span className="subpage-badge-protected" style={{ background: '#e60012', color: '#fff' }} title="Page is disabled">
|
|
|
|
|
|
Disabled
|
|
|
|
|
|
</span>
|
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
|
|
{sp.password && (
|
|
|
|
|
|
<span className="subpage-badge-protected" title="Password protected">
|
|
|
|
|
|
<Icons.Lock /> Password
|
|
|
|
|
|
</span>
|
|
|
|
|
|
)}
|
|
|
|
|
|
|
2026-05-29 17:59:25 +02:00
|
|
|
|
<div className="subpage-tile-cover">
|
|
|
|
|
|
{firstThumb ? (
|
|
|
|
|
|
<img src={`/api/admin/thumbnail/${firstThumb}`} alt="" loading="lazy" />
|
|
|
|
|
|
) : (
|
2026-05-31 21:27:13 +02:00
|
|
|
|
<div className="subpage-tile-placeholder">
|
|
|
|
|
|
<Icons.Folder />
|
|
|
|
|
|
</div>
|
2026-05-29 17:59:25 +02:00
|
|
|
|
)}
|
security: add Trivy container scan to Docker publish workflow (#399)
* feat(admin): add backup manager UI with 1-click restore
* feat(admin): collapse older backup entries by default
* fix(theme): isolate admin layout, instant DOM theme updates, and add Light/Dark mode toggle
* feat(admin): replace text controls for Photo Frame and Hero Style with visual preview cards
* fix(admin): define missing CSS rules for button system (.btn, .preset-btn)
* feat(admin): redesign theme settings with segmented mode toggle, accent color swatches, and custom iOS-style toggle cards
* feat(admin): upgrade all settings tabs with visual grid previews, Google SEO snippet preview, and toggle cards
* feat(admin): overhaul Pages Builder UX with slide-over drawer, card hover badges, real-time search, and floating save bar
* fix(admin): remove leftover dangling syntax snippet in PageBuilder.tsx
* fix(admin): add position: relative to subpage-tile so hover overlay is contained inside the tile
* style(admin): replace all emojis with clean Leica-style vector SVG icons
* fix(types): extend IconProps with SVGProps to allow style prop on Leica SVG icons
* feat(admin): add subpage live-preview, privacy analytics, and asset protection settings
* fix(typecheck): fix AlbumEntry property access in subpage drawer preview mode
* fix(types): fix ImmichAlbumInfo property names thumbnailAssetId and albumName in PageBuilder.tsx
* fix(types): add protection and watermark optional properties to AppConfig interface
* feat(settings): add toggle for disabling analytics tracking (defaults to enabled)
* feat(ux): show status banner when analytics tracking is disabled + add live sync indicator
* feat(watermark): render customizable watermark overlay on Lightbox images + rename section to Portfolio Features & Modules
* fix(types): import type LightboxWatermark in PhotoGrid.tsx
* fix(types): destructure watermark prop in PhotoGrid function signature
* feat(seo): add configurable subpage title template and metadata descriptions
* feat(seo): add generateMetadata to about page for title template support
* feat(proofing): add client proofing, favorites selection, compact URL bitmasking, and export modal
* feat(essay): add Photo Essay storytelling mode with RSC markdown parser and fullbleed/pair layouts
* feat(subpages): add page enabled toggle to deactivate subpages without deleting them
* feat(admin): add visual essay block builder for photo essay storytelling pages
* security: add Trivy container scan to Docker publish workflow
Scans the built image for CRITICAL/HIGH CVEs on every release and
uploads results to the GitHub Security tab. Non-blocking for now
(exit-code 0) so releases aren't gated on findings. Also documents
the scan and existing Dependabot coverage in the README.
---------
Co-authored-by: ralksta <ralksta@users.noreply.github.com>
2026-08-07 11:08:04 +02:00
|
|
|
|
<div className="subpage-hover-overlay">
|
|
|
|
|
|
<span className="hover-action-btn"><Icons.Edit /> Edit Page</span>
|
|
|
|
|
|
</div>
|
2026-05-29 17:59:25 +02:00
|
|
|
|
</div>
|
|
|
|
|
|
<div className="subpage-tile-info">
|
security: add Trivy container scan to Docker publish workflow (#399)
* feat(admin): add backup manager UI with 1-click restore
* feat(admin): collapse older backup entries by default
* fix(theme): isolate admin layout, instant DOM theme updates, and add Light/Dark mode toggle
* feat(admin): replace text controls for Photo Frame and Hero Style with visual preview cards
* fix(admin): define missing CSS rules for button system (.btn, .preset-btn)
* feat(admin): redesign theme settings with segmented mode toggle, accent color swatches, and custom iOS-style toggle cards
* feat(admin): upgrade all settings tabs with visual grid previews, Google SEO snippet preview, and toggle cards
* feat(admin): overhaul Pages Builder UX with slide-over drawer, card hover badges, real-time search, and floating save bar
* fix(admin): remove leftover dangling syntax snippet in PageBuilder.tsx
* fix(admin): add position: relative to subpage-tile so hover overlay is contained inside the tile
* style(admin): replace all emojis with clean Leica-style vector SVG icons
* fix(types): extend IconProps with SVGProps to allow style prop on Leica SVG icons
* feat(admin): add subpage live-preview, privacy analytics, and asset protection settings
* fix(typecheck): fix AlbumEntry property access in subpage drawer preview mode
* fix(types): fix ImmichAlbumInfo property names thumbnailAssetId and albumName in PageBuilder.tsx
* fix(types): add protection and watermark optional properties to AppConfig interface
* feat(settings): add toggle for disabling analytics tracking (defaults to enabled)
* feat(ux): show status banner when analytics tracking is disabled + add live sync indicator
* feat(watermark): render customizable watermark overlay on Lightbox images + rename section to Portfolio Features & Modules
* fix(types): import type LightboxWatermark in PhotoGrid.tsx
* fix(types): destructure watermark prop in PhotoGrid function signature
* feat(seo): add configurable subpage title template and metadata descriptions
* feat(seo): add generateMetadata to about page for title template support
* feat(proofing): add client proofing, favorites selection, compact URL bitmasking, and export modal
* feat(essay): add Photo Essay storytelling mode with RSC markdown parser and fullbleed/pair layouts
* feat(subpages): add page enabled toggle to deactivate subpages without deleting them
* feat(admin): add visual essay block builder for photo essay storytelling pages
* security: add Trivy container scan to Docker publish workflow
Scans the built image for CRITICAL/HIGH CVEs on every release and
uploads results to the GitHub Security tab. Non-blocking for now
(exit-code 0) so releases aren't gated on findings. Also documents
the scan and existing Dependabot coverage in the README.
---------
Co-authored-by: ralksta <ralksta@users.noreply.github.com>
2026-08-07 11:08:04 +02:00
|
|
|
|
<div className="subpage-tile-title-row">
|
|
|
|
|
|
<span className="subpage-tile-name">{sp.title || sp.name}</span>
|
|
|
|
|
|
<span className="subpage-tile-slug">/{slug}</span>
|
|
|
|
|
|
</div>
|
2026-05-29 17:59:25 +02:00
|
|
|
|
<span className="subpage-tile-meta">
|
security: add Trivy container scan to Docker publish workflow (#399)
* feat(admin): add backup manager UI with 1-click restore
* feat(admin): collapse older backup entries by default
* fix(theme): isolate admin layout, instant DOM theme updates, and add Light/Dark mode toggle
* feat(admin): replace text controls for Photo Frame and Hero Style with visual preview cards
* fix(admin): define missing CSS rules for button system (.btn, .preset-btn)
* feat(admin): redesign theme settings with segmented mode toggle, accent color swatches, and custom iOS-style toggle cards
* feat(admin): upgrade all settings tabs with visual grid previews, Google SEO snippet preview, and toggle cards
* feat(admin): overhaul Pages Builder UX with slide-over drawer, card hover badges, real-time search, and floating save bar
* fix(admin): remove leftover dangling syntax snippet in PageBuilder.tsx
* fix(admin): add position: relative to subpage-tile so hover overlay is contained inside the tile
* style(admin): replace all emojis with clean Leica-style vector SVG icons
* fix(types): extend IconProps with SVGProps to allow style prop on Leica SVG icons
* feat(admin): add subpage live-preview, privacy analytics, and asset protection settings
* fix(typecheck): fix AlbumEntry property access in subpage drawer preview mode
* fix(types): fix ImmichAlbumInfo property names thumbnailAssetId and albumName in PageBuilder.tsx
* fix(types): add protection and watermark optional properties to AppConfig interface
* feat(settings): add toggle for disabling analytics tracking (defaults to enabled)
* feat(ux): show status banner when analytics tracking is disabled + add live sync indicator
* feat(watermark): render customizable watermark overlay on Lightbox images + rename section to Portfolio Features & Modules
* fix(types): import type LightboxWatermark in PhotoGrid.tsx
* fix(types): destructure watermark prop in PhotoGrid function signature
* feat(seo): add configurable subpage title template and metadata descriptions
* feat(seo): add generateMetadata to about page for title template support
* feat(proofing): add client proofing, favorites selection, compact URL bitmasking, and export modal
* feat(essay): add Photo Essay storytelling mode with RSC markdown parser and fullbleed/pair layouts
* feat(subpages): add page enabled toggle to deactivate subpages without deleting them
* feat(admin): add visual essay block builder for photo essay storytelling pages
* security: add Trivy container scan to Docker publish workflow
Scans the built image for CRITICAL/HIGH CVEs on every release and
uploads results to the GitHub Security tab. Non-blocking for now
(exit-code 0) so releases aren't gated on findings. Also documents
the scan and existing Dependabot coverage in the README.
---------
Co-authored-by: ralksta <ralksta@users.noreply.github.com>
2026-08-07 11:08:04 +02:00
|
|
|
|
<Icons.Folder /> {totalAlbums} album{totalAlbums !== 1 ? 's' : ''}
|
2026-05-29 17:59:25 +02:00
|
|
|
|
</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
feat: add visual admin panel with page builder and settings editor
- Add /admin route with password-protected visual page builder
- Page Builder: manage hero images, standalone albums, subpages, and sections
- Album Picker: browse all shared Immich albums with search and filtering
- Settings Editor: theme, grid, footer, legal, SEO configuration
- YAML service with atomic writes and automatic backups (content/.backups/)
- HMAC-signed admin sessions (24h expiry, HttpOnly cookies)
- API endpoints: /api/admin/{auth,gallery,settings,albums,reload}
- Enable with ADMIN_PASSWORD env var; disabled by default
- invalidateConfigCache() export for hot-reload after saves
2026-05-29 17:04:37 +02:00
|
|
|
|
// ── Component ──────────────────────────────────────────────────
|
|
|
|
|
|
|
|
|
|
|
|
export default function PageBuilder() {
|
|
|
|
|
|
const [gallery, setGallery] = useState<GalleryState>({ hero: [], albums: [], subpages: [] });
|
|
|
|
|
|
const [immichAlbums, setImmichAlbums] = useState<ImmichAlbumInfo[]>([]);
|
|
|
|
|
|
const [loading, setLoading] = useState(true);
|
|
|
|
|
|
const [saving, setSaving] = useState(false);
|
|
|
|
|
|
const [dirty, setDirty] = useState(false);
|
|
|
|
|
|
const [saveMessage, setSaveMessage] = useState('');
|
2026-05-29 17:29:26 +02:00
|
|
|
|
const [expandedSubpage, setExpandedSubpage] = useState<number | null>(null);
|
security: add Trivy container scan to Docker publish workflow (#399)
* feat(admin): add backup manager UI with 1-click restore
* feat(admin): collapse older backup entries by default
* fix(theme): isolate admin layout, instant DOM theme updates, and add Light/Dark mode toggle
* feat(admin): replace text controls for Photo Frame and Hero Style with visual preview cards
* fix(admin): define missing CSS rules for button system (.btn, .preset-btn)
* feat(admin): redesign theme settings with segmented mode toggle, accent color swatches, and custom iOS-style toggle cards
* feat(admin): upgrade all settings tabs with visual grid previews, Google SEO snippet preview, and toggle cards
* feat(admin): overhaul Pages Builder UX with slide-over drawer, card hover badges, real-time search, and floating save bar
* fix(admin): remove leftover dangling syntax snippet in PageBuilder.tsx
* fix(admin): add position: relative to subpage-tile so hover overlay is contained inside the tile
* style(admin): replace all emojis with clean Leica-style vector SVG icons
* fix(types): extend IconProps with SVGProps to allow style prop on Leica SVG icons
* feat(admin): add subpage live-preview, privacy analytics, and asset protection settings
* fix(typecheck): fix AlbumEntry property access in subpage drawer preview mode
* fix(types): fix ImmichAlbumInfo property names thumbnailAssetId and albumName in PageBuilder.tsx
* fix(types): add protection and watermark optional properties to AppConfig interface
* feat(settings): add toggle for disabling analytics tracking (defaults to enabled)
* feat(ux): show status banner when analytics tracking is disabled + add live sync indicator
* feat(watermark): render customizable watermark overlay on Lightbox images + rename section to Portfolio Features & Modules
* fix(types): import type LightboxWatermark in PhotoGrid.tsx
* fix(types): destructure watermark prop in PhotoGrid function signature
* feat(seo): add configurable subpage title template and metadata descriptions
* feat(seo): add generateMetadata to about page for title template support
* feat(proofing): add client proofing, favorites selection, compact URL bitmasking, and export modal
* feat(essay): add Photo Essay storytelling mode with RSC markdown parser and fullbleed/pair layouts
* feat(subpages): add page enabled toggle to deactivate subpages without deleting them
* feat(admin): add visual essay block builder for photo essay storytelling pages
* security: add Trivy container scan to Docker publish workflow
Scans the built image for CRITICAL/HIGH CVEs on every release and
uploads results to the GitHub Security tab. Non-blocking for now
(exit-code 0) so releases aren't gated on findings. Also documents
the scan and existing Dependabot coverage in the README.
---------
Co-authored-by: ralksta <ralksta@users.noreply.github.com>
2026-08-07 11:08:04 +02:00
|
|
|
|
const [drawerMode, setDrawerMode] = useState<'edit' | 'preview'>('edit');
|
feat: add visual admin panel with page builder and settings editor
- Add /admin route with password-protected visual page builder
- Page Builder: manage hero images, standalone albums, subpages, and sections
- Album Picker: browse all shared Immich albums with search and filtering
- Settings Editor: theme, grid, footer, legal, SEO configuration
- YAML service with atomic writes and automatic backups (content/.backups/)
- HMAC-signed admin sessions (24h expiry, HttpOnly cookies)
- API endpoints: /api/admin/{auth,gallery,settings,albums,reload}
- Enable with ADMIN_PASSWORD env var; disabled by default
- invalidateConfigCache() export for hot-reload after saves
2026-05-29 17:04:37 +02:00
|
|
|
|
const [pickerTarget, setPickerTarget] = useState<{
|
|
|
|
|
|
type: 'standalone' | 'subpage' | 'section';
|
|
|
|
|
|
subpageIndex?: number;
|
|
|
|
|
|
sectionIndex?: number;
|
|
|
|
|
|
} | null>(null);
|
2026-05-31 21:27:13 +02:00
|
|
|
|
const [heroPickerTarget, setHeroPickerTarget] = useState<{
|
|
|
|
|
|
albumId?: string;
|
|
|
|
|
|
onSelect: (assetId: string) => void;
|
|
|
|
|
|
currentAssetIds?: string[];
|
|
|
|
|
|
title?: string;
|
|
|
|
|
|
} | null>(null);
|
2026-05-31 21:17:52 +02:00
|
|
|
|
const [searchQuery, setSearchQuery] = useState('');
|
2026-05-31 21:27:13 +02:00
|
|
|
|
const [editingAlbumAddress, setEditingAlbumAddress] = useState<ActiveEditAlbumAddress | null>(null);
|
2026-05-29 17:59:25 +02:00
|
|
|
|
|
|
|
|
|
|
// DnD sensors
|
|
|
|
|
|
const sensors = useSensors(
|
|
|
|
|
|
useSensor(PointerSensor, { activationConstraint: { distance: 5 } }),
|
|
|
|
|
|
useSensor(KeyboardSensor, { coordinateGetter: sortableKeyboardCoordinates }),
|
|
|
|
|
|
);
|
feat: add visual admin panel with page builder and settings editor
- Add /admin route with password-protected visual page builder
- Page Builder: manage hero images, standalone albums, subpages, and sections
- Album Picker: browse all shared Immich albums with search and filtering
- Settings Editor: theme, grid, footer, legal, SEO configuration
- YAML service with atomic writes and automatic backups (content/.backups/)
- HMAC-signed admin sessions (24h expiry, HttpOnly cookies)
- API endpoints: /api/admin/{auth,gallery,settings,albums,reload}
- Enable with ADMIN_PASSWORD env var; disabled by default
- invalidateConfigCache() export for hot-reload after saves
2026-05-29 17:04:37 +02:00
|
|
|
|
|
|
|
|
|
|
// Load data
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
|
loadData();
|
|
|
|
|
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
|
|
|
|
}, []);
|
|
|
|
|
|
|
2026-05-29 17:59:25 +02:00
|
|
|
|
// ── Keyboard shortcut: ⌘+S / Ctrl+S ─────────────────────────
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
|
function handleKeyDown(e: KeyboardEvent) {
|
|
|
|
|
|
if ((e.metaKey || e.ctrlKey) && e.key === 's') {
|
|
|
|
|
|
e.preventDefault();
|
|
|
|
|
|
if (dirty && !saving) {
|
|
|
|
|
|
handleSave();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
window.addEventListener('keydown', handleKeyDown);
|
|
|
|
|
|
return () => window.removeEventListener('keydown', handleKeyDown);
|
|
|
|
|
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
|
|
|
|
}, [dirty, saving, gallery]);
|
|
|
|
|
|
|
|
|
|
|
|
// ── Unsaved changes guard ────────────────────────────────────
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
|
function handleBeforeUnload(e: BeforeUnloadEvent) {
|
|
|
|
|
|
if (dirty) {
|
|
|
|
|
|
e.preventDefault();
|
|
|
|
|
|
e.returnValue = '';
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
window.addEventListener('beforeunload', handleBeforeUnload);
|
|
|
|
|
|
return () => window.removeEventListener('beforeunload', handleBeforeUnload);
|
|
|
|
|
|
}, [dirty]);
|
|
|
|
|
|
|
feat: add visual admin panel with page builder and settings editor
- Add /admin route with password-protected visual page builder
- Page Builder: manage hero images, standalone albums, subpages, and sections
- Album Picker: browse all shared Immich albums with search and filtering
- Settings Editor: theme, grid, footer, legal, SEO configuration
- YAML service with atomic writes and automatic backups (content/.backups/)
- HMAC-signed admin sessions (24h expiry, HttpOnly cookies)
- API endpoints: /api/admin/{auth,gallery,settings,albums,reload}
- Enable with ADMIN_PASSWORD env var; disabled by default
- invalidateConfigCache() export for hot-reload after saves
2026-05-29 17:04:37 +02:00
|
|
|
|
async function loadData() {
|
|
|
|
|
|
setLoading(true);
|
|
|
|
|
|
try {
|
|
|
|
|
|
const [galleryRes, albumsRes] = await Promise.all([
|
|
|
|
|
|
fetch('/api/admin/gallery'),
|
|
|
|
|
|
fetch('/api/admin/albums'),
|
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
|
|
if (galleryRes.ok) {
|
|
|
|
|
|
const { gallery: raw } = await galleryRes.json();
|
|
|
|
|
|
setGallery(parseGalleryYaml(raw));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (albumsRes.ok) {
|
|
|
|
|
|
const { albums } = await albumsRes.json();
|
|
|
|
|
|
setImmichAlbums(albums);
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch (err) {
|
|
|
|
|
|
console.error('Failed to load admin data:', err);
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
setLoading(false);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function parseGalleryYaml(raw: Record<string, unknown>): GalleryState {
|
|
|
|
|
|
const hero = Array.isArray(raw.hero) ? raw.hero : raw.hero ? [raw.hero as string] : [];
|
|
|
|
|
|
const albums = parseAlbumEntries(
|
|
|
|
|
|
raw.albums as Array<string | Record<string, string>> | undefined,
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
let subpages: Subpage[] = [];
|
|
|
|
|
|
if (Array.isArray(raw.subpages)) {
|
|
|
|
|
|
subpages = (raw.subpages as Array<Record<string, unknown>>).map((sp) => ({
|
|
|
|
|
|
name: (sp.name as string) || '',
|
|
|
|
|
|
title: sp.title as string | undefined,
|
|
|
|
|
|
subtitle: sp.subtitle as string | undefined,
|
|
|
|
|
|
password: sp.password as string | undefined,
|
security: add Trivy container scan to Docker publish workflow (#399)
* feat(admin): add backup manager UI with 1-click restore
* feat(admin): collapse older backup entries by default
* fix(theme): isolate admin layout, instant DOM theme updates, and add Light/Dark mode toggle
* feat(admin): replace text controls for Photo Frame and Hero Style with visual preview cards
* fix(admin): define missing CSS rules for button system (.btn, .preset-btn)
* feat(admin): redesign theme settings with segmented mode toggle, accent color swatches, and custom iOS-style toggle cards
* feat(admin): upgrade all settings tabs with visual grid previews, Google SEO snippet preview, and toggle cards
* feat(admin): overhaul Pages Builder UX with slide-over drawer, card hover badges, real-time search, and floating save bar
* fix(admin): remove leftover dangling syntax snippet in PageBuilder.tsx
* fix(admin): add position: relative to subpage-tile so hover overlay is contained inside the tile
* style(admin): replace all emojis with clean Leica-style vector SVG icons
* fix(types): extend IconProps with SVGProps to allow style prop on Leica SVG icons
* feat(admin): add subpage live-preview, privacy analytics, and asset protection settings
* fix(typecheck): fix AlbumEntry property access in subpage drawer preview mode
* fix(types): fix ImmichAlbumInfo property names thumbnailAssetId and albumName in PageBuilder.tsx
* fix(types): add protection and watermark optional properties to AppConfig interface
* feat(settings): add toggle for disabling analytics tracking (defaults to enabled)
* feat(ux): show status banner when analytics tracking is disabled + add live sync indicator
* feat(watermark): render customizable watermark overlay on Lightbox images + rename section to Portfolio Features & Modules
* fix(types): import type LightboxWatermark in PhotoGrid.tsx
* fix(types): destructure watermark prop in PhotoGrid function signature
* feat(seo): add configurable subpage title template and metadata descriptions
* feat(seo): add generateMetadata to about page for title template support
* feat(proofing): add client proofing, favorites selection, compact URL bitmasking, and export modal
* feat(essay): add Photo Essay storytelling mode with RSC markdown parser and fullbleed/pair layouts
* feat(subpages): add page enabled toggle to deactivate subpages without deleting them
* feat(admin): add visual essay block builder for photo essay storytelling pages
* security: add Trivy container scan to Docker publish workflow
Scans the built image for CRITICAL/HIGH CVEs on every release and
uploads results to the GitHub Security tab. Non-blocking for now
(exit-code 0) so releases aren't gated on findings. Also documents
the scan and existing Dependabot coverage in the README.
---------
Co-authored-by: ralksta <ralksta@users.noreply.github.com>
2026-08-07 11:08:04 +02:00
|
|
|
|
enabled: sp.enabled !== false,
|
|
|
|
|
|
essayText: sp.essayText as string | undefined,
|
|
|
|
|
|
essayFile: sp.essayFile as string | undefined,
|
feat: add visual admin panel with page builder and settings editor
- Add /admin route with password-protected visual page builder
- Page Builder: manage hero images, standalone albums, subpages, and sections
- Album Picker: browse all shared Immich albums with search and filtering
- Settings Editor: theme, grid, footer, legal, SEO configuration
- YAML service with atomic writes and automatic backups (content/.backups/)
- HMAC-signed admin sessions (24h expiry, HttpOnly cookies)
- API endpoints: /api/admin/{auth,gallery,settings,albums,reload}
- Enable with ADMIN_PASSWORD env var; disabled by default
- invalidateConfigCache() export for hot-reload after saves
2026-05-29 17:04:37 +02:00
|
|
|
|
albums: parseAlbumEntries(sp.albums as Array<string | Record<string, string>> | undefined),
|
|
|
|
|
|
sections: sp.sections
|
|
|
|
|
|
? (sp.sections as Array<Record<string, unknown>>).map((sec) => ({
|
|
|
|
|
|
title: (sec.title as string) || '',
|
|
|
|
|
|
description: sec.description as string | undefined,
|
|
|
|
|
|
albums: parseAlbumEntries(sec.albums as Array<string | Record<string, string>>),
|
|
|
|
|
|
}))
|
|
|
|
|
|
: undefined,
|
|
|
|
|
|
grid: sp.grid as Subpage['grid'],
|
|
|
|
|
|
}));
|
|
|
|
|
|
} else if (raw.subpages && typeof raw.subpages === 'object') {
|
|
|
|
|
|
subpages = Object.entries(raw.subpages as Record<string, unknown>).map(([name, value]) => {
|
|
|
|
|
|
if (Array.isArray(value)) {
|
security: add Trivy container scan to Docker publish workflow (#399)
* feat(admin): add backup manager UI with 1-click restore
* feat(admin): collapse older backup entries by default
* fix(theme): isolate admin layout, instant DOM theme updates, and add Light/Dark mode toggle
* feat(admin): replace text controls for Photo Frame and Hero Style with visual preview cards
* fix(admin): define missing CSS rules for button system (.btn, .preset-btn)
* feat(admin): redesign theme settings with segmented mode toggle, accent color swatches, and custom iOS-style toggle cards
* feat(admin): upgrade all settings tabs with visual grid previews, Google SEO snippet preview, and toggle cards
* feat(admin): overhaul Pages Builder UX with slide-over drawer, card hover badges, real-time search, and floating save bar
* fix(admin): remove leftover dangling syntax snippet in PageBuilder.tsx
* fix(admin): add position: relative to subpage-tile so hover overlay is contained inside the tile
* style(admin): replace all emojis with clean Leica-style vector SVG icons
* fix(types): extend IconProps with SVGProps to allow style prop on Leica SVG icons
* feat(admin): add subpage live-preview, privacy analytics, and asset protection settings
* fix(typecheck): fix AlbumEntry property access in subpage drawer preview mode
* fix(types): fix ImmichAlbumInfo property names thumbnailAssetId and albumName in PageBuilder.tsx
* fix(types): add protection and watermark optional properties to AppConfig interface
* feat(settings): add toggle for disabling analytics tracking (defaults to enabled)
* feat(ux): show status banner when analytics tracking is disabled + add live sync indicator
* feat(watermark): render customizable watermark overlay on Lightbox images + rename section to Portfolio Features & Modules
* fix(types): import type LightboxWatermark in PhotoGrid.tsx
* fix(types): destructure watermark prop in PhotoGrid function signature
* feat(seo): add configurable subpage title template and metadata descriptions
* feat(seo): add generateMetadata to about page for title template support
* feat(proofing): add client proofing, favorites selection, compact URL bitmasking, and export modal
* feat(essay): add Photo Essay storytelling mode with RSC markdown parser and fullbleed/pair layouts
* feat(subpages): add page enabled toggle to deactivate subpages without deleting them
* feat(admin): add visual essay block builder for photo essay storytelling pages
* security: add Trivy container scan to Docker publish workflow
Scans the built image for CRITICAL/HIGH CVEs on every release and
uploads results to the GitHub Security tab. Non-blocking for now
(exit-code 0) so releases aren't gated on findings. Also documents
the scan and existing Dependabot coverage in the README.
---------
Co-authored-by: ralksta <ralksta@users.noreply.github.com>
2026-08-07 11:08:04 +02:00
|
|
|
|
return { name, albums: parseAlbumEntries(value), sections: undefined, enabled: true };
|
feat: add visual admin panel with page builder and settings editor
- Add /admin route with password-protected visual page builder
- Page Builder: manage hero images, standalone albums, subpages, and sections
- Album Picker: browse all shared Immich albums with search and filtering
- Settings Editor: theme, grid, footer, legal, SEO configuration
- YAML service with atomic writes and automatic backups (content/.backups/)
- HMAC-signed admin sessions (24h expiry, HttpOnly cookies)
- API endpoints: /api/admin/{auth,gallery,settings,albums,reload}
- Enable with ADMIN_PASSWORD env var; disabled by default
- invalidateConfigCache() export for hot-reload after saves
2026-05-29 17:04:37 +02:00
|
|
|
|
}
|
|
|
|
|
|
const sp = value as Record<string, unknown>;
|
|
|
|
|
|
return {
|
|
|
|
|
|
name,
|
|
|
|
|
|
title: sp.title as string | undefined,
|
|
|
|
|
|
subtitle: sp.subtitle as string | undefined,
|
|
|
|
|
|
password: sp.password as string | undefined,
|
security: add Trivy container scan to Docker publish workflow (#399)
* feat(admin): add backup manager UI with 1-click restore
* feat(admin): collapse older backup entries by default
* fix(theme): isolate admin layout, instant DOM theme updates, and add Light/Dark mode toggle
* feat(admin): replace text controls for Photo Frame and Hero Style with visual preview cards
* fix(admin): define missing CSS rules for button system (.btn, .preset-btn)
* feat(admin): redesign theme settings with segmented mode toggle, accent color swatches, and custom iOS-style toggle cards
* feat(admin): upgrade all settings tabs with visual grid previews, Google SEO snippet preview, and toggle cards
* feat(admin): overhaul Pages Builder UX with slide-over drawer, card hover badges, real-time search, and floating save bar
* fix(admin): remove leftover dangling syntax snippet in PageBuilder.tsx
* fix(admin): add position: relative to subpage-tile so hover overlay is contained inside the tile
* style(admin): replace all emojis with clean Leica-style vector SVG icons
* fix(types): extend IconProps with SVGProps to allow style prop on Leica SVG icons
* feat(admin): add subpage live-preview, privacy analytics, and asset protection settings
* fix(typecheck): fix AlbumEntry property access in subpage drawer preview mode
* fix(types): fix ImmichAlbumInfo property names thumbnailAssetId and albumName in PageBuilder.tsx
* fix(types): add protection and watermark optional properties to AppConfig interface
* feat(settings): add toggle for disabling analytics tracking (defaults to enabled)
* feat(ux): show status banner when analytics tracking is disabled + add live sync indicator
* feat(watermark): render customizable watermark overlay on Lightbox images + rename section to Portfolio Features & Modules
* fix(types): import type LightboxWatermark in PhotoGrid.tsx
* fix(types): destructure watermark prop in PhotoGrid function signature
* feat(seo): add configurable subpage title template and metadata descriptions
* feat(seo): add generateMetadata to about page for title template support
* feat(proofing): add client proofing, favorites selection, compact URL bitmasking, and export modal
* feat(essay): add Photo Essay storytelling mode with RSC markdown parser and fullbleed/pair layouts
* feat(subpages): add page enabled toggle to deactivate subpages without deleting them
* feat(admin): add visual essay block builder for photo essay storytelling pages
* security: add Trivy container scan to Docker publish workflow
Scans the built image for CRITICAL/HIGH CVEs on every release and
uploads results to the GitHub Security tab. Non-blocking for now
(exit-code 0) so releases aren't gated on findings. Also documents
the scan and existing Dependabot coverage in the README.
---------
Co-authored-by: ralksta <ralksta@users.noreply.github.com>
2026-08-07 11:08:04 +02:00
|
|
|
|
enabled: sp.enabled !== false,
|
|
|
|
|
|
essayText: sp.essayText as string | undefined,
|
|
|
|
|
|
essayFile: sp.essayFile as string | undefined,
|
feat: add visual admin panel with page builder and settings editor
- Add /admin route with password-protected visual page builder
- Page Builder: manage hero images, standalone albums, subpages, and sections
- Album Picker: browse all shared Immich albums with search and filtering
- Settings Editor: theme, grid, footer, legal, SEO configuration
- YAML service with atomic writes and automatic backups (content/.backups/)
- HMAC-signed admin sessions (24h expiry, HttpOnly cookies)
- API endpoints: /api/admin/{auth,gallery,settings,albums,reload}
- Enable with ADMIN_PASSWORD env var; disabled by default
- invalidateConfigCache() export for hot-reload after saves
2026-05-29 17:04:37 +02:00
|
|
|
|
albums: parseAlbumEntries(
|
|
|
|
|
|
sp.albums as Array<string | Record<string, string>> | undefined,
|
|
|
|
|
|
),
|
|
|
|
|
|
sections: undefined,
|
|
|
|
|
|
grid: sp.grid as Subpage['grid'],
|
|
|
|
|
|
};
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return { hero, albums, subpages };
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const markDirty = useCallback(() => {
|
|
|
|
|
|
setDirty(true);
|
|
|
|
|
|
setSaveMessage('');
|
|
|
|
|
|
}, []);
|
|
|
|
|
|
|
|
|
|
|
|
// ── Save ──────────────────────────────────────────────────────
|
|
|
|
|
|
async function handleSave() {
|
|
|
|
|
|
setSaving(true);
|
|
|
|
|
|
setSaveMessage('');
|
|
|
|
|
|
|
|
|
|
|
|
const yamlData: Record<string, unknown> = {};
|
|
|
|
|
|
|
|
|
|
|
|
if (gallery.hero.length > 0) {
|
|
|
|
|
|
yamlData.hero = gallery.hero;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (gallery.albums.length > 0) {
|
|
|
|
|
|
yamlData.albums = serializeAlbumEntries(gallery.albums);
|
|
|
|
|
|
}
|
|
|
|
|
|
if (gallery.subpages.length > 0) {
|
|
|
|
|
|
yamlData.subpages = gallery.subpages.map((sp) => {
|
|
|
|
|
|
const entry: Record<string, unknown> = { name: sp.name };
|
|
|
|
|
|
if (sp.title) entry.title = sp.title;
|
|
|
|
|
|
if (sp.subtitle) entry.subtitle = sp.subtitle;
|
|
|
|
|
|
if (sp.password) entry.password = sp.password;
|
security: add Trivy container scan to Docker publish workflow (#399)
* feat(admin): add backup manager UI with 1-click restore
* feat(admin): collapse older backup entries by default
* fix(theme): isolate admin layout, instant DOM theme updates, and add Light/Dark mode toggle
* feat(admin): replace text controls for Photo Frame and Hero Style with visual preview cards
* fix(admin): define missing CSS rules for button system (.btn, .preset-btn)
* feat(admin): redesign theme settings with segmented mode toggle, accent color swatches, and custom iOS-style toggle cards
* feat(admin): upgrade all settings tabs with visual grid previews, Google SEO snippet preview, and toggle cards
* feat(admin): overhaul Pages Builder UX with slide-over drawer, card hover badges, real-time search, and floating save bar
* fix(admin): remove leftover dangling syntax snippet in PageBuilder.tsx
* fix(admin): add position: relative to subpage-tile so hover overlay is contained inside the tile
* style(admin): replace all emojis with clean Leica-style vector SVG icons
* fix(types): extend IconProps with SVGProps to allow style prop on Leica SVG icons
* feat(admin): add subpage live-preview, privacy analytics, and asset protection settings
* fix(typecheck): fix AlbumEntry property access in subpage drawer preview mode
* fix(types): fix ImmichAlbumInfo property names thumbnailAssetId and albumName in PageBuilder.tsx
* fix(types): add protection and watermark optional properties to AppConfig interface
* feat(settings): add toggle for disabling analytics tracking (defaults to enabled)
* feat(ux): show status banner when analytics tracking is disabled + add live sync indicator
* feat(watermark): render customizable watermark overlay on Lightbox images + rename section to Portfolio Features & Modules
* fix(types): import type LightboxWatermark in PhotoGrid.tsx
* fix(types): destructure watermark prop in PhotoGrid function signature
* feat(seo): add configurable subpage title template and metadata descriptions
* feat(seo): add generateMetadata to about page for title template support
* feat(proofing): add client proofing, favorites selection, compact URL bitmasking, and export modal
* feat(essay): add Photo Essay storytelling mode with RSC markdown parser and fullbleed/pair layouts
* feat(subpages): add page enabled toggle to deactivate subpages without deleting them
* feat(admin): add visual essay block builder for photo essay storytelling pages
* security: add Trivy container scan to Docker publish workflow
Scans the built image for CRITICAL/HIGH CVEs on every release and
uploads results to the GitHub Security tab. Non-blocking for now
(exit-code 0) so releases aren't gated on findings. Also documents
the scan and existing Dependabot coverage in the README.
---------
Co-authored-by: ralksta <ralksta@users.noreply.github.com>
2026-08-07 11:08:04 +02:00
|
|
|
|
if (sp.enabled === false) entry.enabled = false;
|
|
|
|
|
|
if (sp.essayText) entry.essayText = sp.essayText;
|
|
|
|
|
|
if (sp.essayFile) entry.essayFile = sp.essayFile;
|
feat: add visual admin panel with page builder and settings editor
- Add /admin route with password-protected visual page builder
- Page Builder: manage hero images, standalone albums, subpages, and sections
- Album Picker: browse all shared Immich albums with search and filtering
- Settings Editor: theme, grid, footer, legal, SEO configuration
- YAML service with atomic writes and automatic backups (content/.backups/)
- HMAC-signed admin sessions (24h expiry, HttpOnly cookies)
- API endpoints: /api/admin/{auth,gallery,settings,albums,reload}
- Enable with ADMIN_PASSWORD env var; disabled by default
- invalidateConfigCache() export for hot-reload after saves
2026-05-29 17:04:37 +02:00
|
|
|
|
if (sp.grid) entry.grid = sp.grid;
|
|
|
|
|
|
|
|
|
|
|
|
if (sp.sections && sp.sections.length > 0) {
|
|
|
|
|
|
entry.sections = sp.sections.map((sec) => {
|
|
|
|
|
|
const s: Record<string, unknown> = {
|
|
|
|
|
|
title: sec.title,
|
|
|
|
|
|
albums: serializeAlbumEntries(sec.albums),
|
|
|
|
|
|
};
|
|
|
|
|
|
if (sec.description) s.description = sec.description;
|
|
|
|
|
|
return s;
|
|
|
|
|
|
});
|
|
|
|
|
|
if (sp.albums.length > 0) {
|
|
|
|
|
|
entry.albums = serializeAlbumEntries(sp.albums);
|
|
|
|
|
|
}
|
|
|
|
|
|
} else {
|
|
|
|
|
|
entry.albums = serializeAlbumEntries(sp.albums);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return entry;
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
const res = await fetch('/api/admin/gallery', {
|
|
|
|
|
|
method: 'PUT',
|
|
|
|
|
|
headers: { 'Content-Type': 'application/json' },
|
|
|
|
|
|
body: JSON.stringify({ gallery: yamlData }),
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
if (res.ok) {
|
2026-05-29 17:20:56 +02:00
|
|
|
|
const data = await res.json();
|
feat: add visual admin panel with page builder and settings editor
- Add /admin route with password-protected visual page builder
- Page Builder: manage hero images, standalone albums, subpages, and sections
- Album Picker: browse all shared Immich albums with search and filtering
- Settings Editor: theme, grid, footer, legal, SEO configuration
- YAML service with atomic writes and automatic backups (content/.backups/)
- HMAC-signed admin sessions (24h expiry, HttpOnly cookies)
- API endpoints: /api/admin/{auth,gallery,settings,albums,reload}
- Enable with ADMIN_PASSWORD env var; disabled by default
- invalidateConfigCache() export for hot-reload after saves
2026-05-29 17:04:37 +02:00
|
|
|
|
setDirty(false);
|
2026-05-29 17:20:56 +02:00
|
|
|
|
setSaveMessage(data.message || 'Saved successfully!');
|
|
|
|
|
|
setTimeout(() => setSaveMessage(''), 5000);
|
feat: add visual admin panel with page builder and settings editor
- Add /admin route with password-protected visual page builder
- Page Builder: manage hero images, standalone albums, subpages, and sections
- Album Picker: browse all shared Immich albums with search and filtering
- Settings Editor: theme, grid, footer, legal, SEO configuration
- YAML service with atomic writes and automatic backups (content/.backups/)
- HMAC-signed admin sessions (24h expiry, HttpOnly cookies)
- API endpoints: /api/admin/{auth,gallery,settings,albums,reload}
- Enable with ADMIN_PASSWORD env var; disabled by default
- invalidateConfigCache() export for hot-reload after saves
2026-05-29 17:04:37 +02:00
|
|
|
|
} else {
|
|
|
|
|
|
const err = await res.json();
|
|
|
|
|
|
setSaveMessage(`Error: ${err.error}`);
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch {
|
|
|
|
|
|
setSaveMessage('Error: Failed to save');
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
setSaving(false);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-29 17:59:25 +02:00
|
|
|
|
// ── Hero Picker ──────────────────────────────────────────────
|
|
|
|
|
|
function handleHeroSelect(assetId: string) {
|
|
|
|
|
|
setGallery((g) => ({ ...g, hero: [...g.hero, assetId] }));
|
2026-05-31 21:27:13 +02:00
|
|
|
|
setHeroPickerTarget(null);
|
2026-05-29 17:59:25 +02:00
|
|
|
|
markDirty();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
feat: add visual admin panel with page builder and settings editor
- Add /admin route with password-protected visual page builder
- Page Builder: manage hero images, standalone albums, subpages, and sections
- Album Picker: browse all shared Immich albums with search and filtering
- Settings Editor: theme, grid, footer, legal, SEO configuration
- YAML service with atomic writes and automatic backups (content/.backups/)
- HMAC-signed admin sessions (24h expiry, HttpOnly cookies)
- API endpoints: /api/admin/{auth,gallery,settings,albums,reload}
- Enable with ADMIN_PASSWORD env var; disabled by default
- invalidateConfigCache() export for hot-reload after saves
2026-05-29 17:04:37 +02:00
|
|
|
|
// ── Album Picker Handlers ────────────────────────────────────
|
|
|
|
|
|
function handlePickAlbum(albumId: string) {
|
|
|
|
|
|
if (!pickerTarget) return;
|
|
|
|
|
|
|
|
|
|
|
|
const entry: AlbumEntry = { id: albumId };
|
|
|
|
|
|
|
|
|
|
|
|
if (pickerTarget.type === 'standalone') {
|
|
|
|
|
|
setGallery((g) => ({ ...g, albums: [...g.albums, entry] }));
|
|
|
|
|
|
} else if (pickerTarget.type === 'subpage' && pickerTarget.subpageIndex != null) {
|
|
|
|
|
|
setGallery((g) => {
|
|
|
|
|
|
const subpages = [...g.subpages];
|
|
|
|
|
|
const sp = { ...subpages[pickerTarget.subpageIndex!] };
|
|
|
|
|
|
sp.albums = [...sp.albums, entry];
|
|
|
|
|
|
subpages[pickerTarget.subpageIndex!] = sp;
|
|
|
|
|
|
return { ...g, subpages };
|
|
|
|
|
|
});
|
|
|
|
|
|
} else if (
|
|
|
|
|
|
pickerTarget.type === 'section' &&
|
|
|
|
|
|
pickerTarget.subpageIndex != null &&
|
|
|
|
|
|
pickerTarget.sectionIndex != null
|
|
|
|
|
|
) {
|
|
|
|
|
|
setGallery((g) => {
|
|
|
|
|
|
const subpages = [...g.subpages];
|
|
|
|
|
|
const sp = { ...subpages[pickerTarget.subpageIndex!] };
|
|
|
|
|
|
const sections = [...(sp.sections || [])];
|
|
|
|
|
|
const sec = { ...sections[pickerTarget.sectionIndex!] };
|
|
|
|
|
|
sec.albums = [...sec.albums, entry];
|
|
|
|
|
|
sections[pickerTarget.sectionIndex!] = sec;
|
|
|
|
|
|
sp.sections = sections;
|
|
|
|
|
|
subpages[pickerTarget.subpageIndex!] = sp;
|
|
|
|
|
|
return { ...g, subpages };
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
setPickerTarget(null);
|
|
|
|
|
|
markDirty();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-29 17:59:25 +02:00
|
|
|
|
// ── Drag & Drop Handlers ─────────────────────────────────────
|
|
|
|
|
|
function handleHeroDragEnd(event: DragEndEvent) {
|
|
|
|
|
|
const { active, over } = event;
|
|
|
|
|
|
if (!over || active.id === over.id) return;
|
|
|
|
|
|
|
|
|
|
|
|
const oldIndex = gallery.hero.findIndex((_, i) => `hero-${i}` === active.id);
|
|
|
|
|
|
const newIndex = gallery.hero.findIndex((_, i) => `hero-${i}` === over.id);
|
|
|
|
|
|
|
|
|
|
|
|
if (oldIndex !== -1 && newIndex !== -1) {
|
|
|
|
|
|
setGallery((g) => ({ ...g, hero: arrayMove(g.hero, oldIndex, newIndex) }));
|
|
|
|
|
|
markDirty();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function handleAlbumDragEnd(event: DragEndEvent) {
|
|
|
|
|
|
const { active, over } = event;
|
|
|
|
|
|
if (!over || active.id === over.id) return;
|
|
|
|
|
|
|
|
|
|
|
|
const oldIndex = gallery.albums.findIndex((a, i) => `album-${a.id}-${i}` === active.id);
|
|
|
|
|
|
const newIndex = gallery.albums.findIndex((a, i) => `album-${a.id}-${i}` === over.id);
|
|
|
|
|
|
|
|
|
|
|
|
if (oldIndex !== -1 && newIndex !== -1) {
|
|
|
|
|
|
setGallery((g) => ({ ...g, albums: arrayMove(g.albums, oldIndex, newIndex) }));
|
|
|
|
|
|
markDirty();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function handleSubpageDragEnd(event: DragEndEvent) {
|
|
|
|
|
|
const { active, over } = event;
|
|
|
|
|
|
if (!over || active.id === over.id) return;
|
|
|
|
|
|
|
|
|
|
|
|
const oldIndex = gallery.subpages.findIndex((_, i) => `subpage-${i}` === active.id);
|
|
|
|
|
|
const newIndex = gallery.subpages.findIndex((_, i) => `subpage-${i}` === over.id);
|
|
|
|
|
|
|
|
|
|
|
|
if (oldIndex !== -1 && newIndex !== -1) {
|
|
|
|
|
|
setGallery((g) => ({ ...g, subpages: arrayMove(g.subpages, oldIndex, newIndex) }));
|
|
|
|
|
|
if (expandedSubpage === oldIndex) setExpandedSubpage(newIndex);
|
|
|
|
|
|
else if (expandedSubpage !== null) {
|
|
|
|
|
|
if (oldIndex < expandedSubpage && newIndex >= expandedSubpage)
|
|
|
|
|
|
setExpandedSubpage(expandedSubpage - 1);
|
|
|
|
|
|
else if (oldIndex > expandedSubpage && newIndex <= expandedSubpage)
|
|
|
|
|
|
setExpandedSubpage(expandedSubpage + 1);
|
|
|
|
|
|
}
|
|
|
|
|
|
markDirty();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function handleSubpageAlbumDragEnd(spIndex: number) {
|
|
|
|
|
|
return (event: DragEndEvent) => {
|
|
|
|
|
|
const { active, over } = event;
|
|
|
|
|
|
if (!over || active.id === over.id) return;
|
|
|
|
|
|
|
|
|
|
|
|
const sp = gallery.subpages[spIndex];
|
|
|
|
|
|
const oldIndex = sp.albums.findIndex((a, i) => `album-${a.id}-${i}` === active.id);
|
|
|
|
|
|
const newIndex = sp.albums.findIndex((a, i) => `album-${a.id}-${i}` === over.id);
|
|
|
|
|
|
|
|
|
|
|
|
if (oldIndex !== -1 && newIndex !== -1) {
|
|
|
|
|
|
setGallery((g) => {
|
|
|
|
|
|
const subpages = [...g.subpages];
|
|
|
|
|
|
const sp2 = { ...subpages[spIndex] };
|
|
|
|
|
|
sp2.albums = arrayMove(sp2.albums, oldIndex, newIndex);
|
|
|
|
|
|
subpages[spIndex] = sp2;
|
|
|
|
|
|
return { ...g, subpages };
|
|
|
|
|
|
});
|
|
|
|
|
|
markDirty();
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
feat: add visual admin panel with page builder and settings editor
- Add /admin route with password-protected visual page builder
- Page Builder: manage hero images, standalone albums, subpages, and sections
- Album Picker: browse all shared Immich albums with search and filtering
- Settings Editor: theme, grid, footer, legal, SEO configuration
- YAML service with atomic writes and automatic backups (content/.backups/)
- HMAC-signed admin sessions (24h expiry, HttpOnly cookies)
- API endpoints: /api/admin/{auth,gallery,settings,albums,reload}
- Enable with ADMIN_PASSWORD env var; disabled by default
- invalidateConfigCache() export for hot-reload after saves
2026-05-29 17:04:37 +02:00
|
|
|
|
// ── Subpage Management ────────────────────────────────────────
|
|
|
|
|
|
function addSubpage() {
|
|
|
|
|
|
setGallery((g) => ({
|
|
|
|
|
|
...g,
|
|
|
|
|
|
subpages: [
|
|
|
|
|
|
...g.subpages,
|
|
|
|
|
|
{ name: `New Page ${g.subpages.length + 1}`, albums: [], sections: undefined },
|
|
|
|
|
|
],
|
|
|
|
|
|
}));
|
|
|
|
|
|
markDirty();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function removeSubpage(index: number) {
|
|
|
|
|
|
if (!confirm('Remove this subpage?')) return;
|
|
|
|
|
|
setGallery((g) => ({
|
|
|
|
|
|
...g,
|
|
|
|
|
|
subpages: g.subpages.filter((_, i) => i !== index),
|
|
|
|
|
|
}));
|
|
|
|
|
|
markDirty();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function updateSubpage(index: number, updates: Partial<Subpage>) {
|
|
|
|
|
|
setGallery((g) => {
|
|
|
|
|
|
const subpages = [...g.subpages];
|
|
|
|
|
|
subpages[index] = { ...subpages[index], ...updates };
|
|
|
|
|
|
return { ...g, subpages };
|
|
|
|
|
|
});
|
|
|
|
|
|
markDirty();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ── Section Management ────────────────────────────────────────
|
|
|
|
|
|
function addSection(subpageIndex: number) {
|
|
|
|
|
|
setGallery((g) => {
|
|
|
|
|
|
const subpages = [...g.subpages];
|
|
|
|
|
|
const sp = { ...subpages[subpageIndex] };
|
|
|
|
|
|
sp.sections = [...(sp.sections || []), { title: 'New Section', albums: [] }];
|
|
|
|
|
|
subpages[subpageIndex] = sp;
|
|
|
|
|
|
return { ...g, subpages };
|
|
|
|
|
|
});
|
|
|
|
|
|
markDirty();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function removeSection(subpageIndex: number, sectionIndex: number) {
|
|
|
|
|
|
setGallery((g) => {
|
|
|
|
|
|
const subpages = [...g.subpages];
|
|
|
|
|
|
const sp = { ...subpages[subpageIndex] };
|
|
|
|
|
|
sp.sections = (sp.sections || []).filter((_, i) => i !== sectionIndex);
|
|
|
|
|
|
subpages[subpageIndex] = sp;
|
|
|
|
|
|
return { ...g, subpages };
|
|
|
|
|
|
});
|
|
|
|
|
|
markDirty();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function updateSection(subpageIndex: number, sectionIndex: number, updates: Partial<Section>) {
|
|
|
|
|
|
setGallery((g) => {
|
|
|
|
|
|
const subpages = [...g.subpages];
|
|
|
|
|
|
const sp = { ...subpages[subpageIndex] };
|
|
|
|
|
|
const sections = [...(sp.sections || [])];
|
|
|
|
|
|
sections[sectionIndex] = { ...sections[sectionIndex], ...updates };
|
|
|
|
|
|
sp.sections = sections;
|
|
|
|
|
|
subpages[subpageIndex] = sp;
|
|
|
|
|
|
return { ...g, subpages };
|
|
|
|
|
|
});
|
|
|
|
|
|
markDirty();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ── Album Removal ────────────────────────────────────────────
|
|
|
|
|
|
function removeStandaloneAlbum(index: number) {
|
|
|
|
|
|
setGallery((g) => ({
|
|
|
|
|
|
...g,
|
|
|
|
|
|
albums: g.albums.filter((_, i) => i !== index),
|
|
|
|
|
|
}));
|
|
|
|
|
|
markDirty();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function removeSubpageAlbum(subpageIndex: number, albumIndex: number) {
|
|
|
|
|
|
setGallery((g) => {
|
|
|
|
|
|
const subpages = [...g.subpages];
|
|
|
|
|
|
const sp = { ...subpages[subpageIndex] };
|
|
|
|
|
|
sp.albums = sp.albums.filter((_, i) => i !== albumIndex);
|
|
|
|
|
|
subpages[subpageIndex] = sp;
|
|
|
|
|
|
return { ...g, subpages };
|
|
|
|
|
|
});
|
|
|
|
|
|
markDirty();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function removeSectionAlbum(subpageIndex: number, sectionIndex: number, albumIndex: number) {
|
|
|
|
|
|
setGallery((g) => {
|
|
|
|
|
|
const subpages = [...g.subpages];
|
|
|
|
|
|
const sp = { ...subpages[subpageIndex] };
|
|
|
|
|
|
const sections = [...(sp.sections || [])];
|
|
|
|
|
|
const sec = { ...sections[sectionIndex] };
|
|
|
|
|
|
sec.albums = sec.albums.filter((_, i) => i !== albumIndex);
|
|
|
|
|
|
sections[sectionIndex] = sec;
|
|
|
|
|
|
sp.sections = sections;
|
|
|
|
|
|
subpages[subpageIndex] = sp;
|
|
|
|
|
|
return { ...g, subpages };
|
|
|
|
|
|
});
|
|
|
|
|
|
markDirty();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ── Hero Management ──────────────────────────────────────────
|
|
|
|
|
|
function removeHero(index: number) {
|
|
|
|
|
|
setGallery((g) => ({
|
|
|
|
|
|
...g,
|
|
|
|
|
|
hero: g.hero.filter((_, i) => i !== index),
|
|
|
|
|
|
}));
|
|
|
|
|
|
markDirty();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ── Helpers ──────────────────────────────────────────────────
|
|
|
|
|
|
function getAlbumName(id: string): string {
|
|
|
|
|
|
const found = immichAlbums.find((a) => a.id === id);
|
|
|
|
|
|
return found?.albumName || id.slice(0, 8) + '...';
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function getAlbumCount(id: string): number {
|
|
|
|
|
|
const found = immichAlbums.find((a) => a.id === id);
|
|
|
|
|
|
return found?.assetCount || 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-29 17:14:58 +02:00
|
|
|
|
function getAlbumThumbnailId(id: string): string | null {
|
|
|
|
|
|
const found = immichAlbums.find((a) => a.id === id);
|
|
|
|
|
|
return found?.thumbnailAssetId || null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-29 17:29:26 +02:00
|
|
|
|
function getFirstSubpageThumb(sp: Subpage): string | null {
|
|
|
|
|
|
for (const album of sp.albums) {
|
|
|
|
|
|
const thumb = getAlbumThumbnailId(album.id);
|
|
|
|
|
|
if (thumb) return thumb;
|
|
|
|
|
|
}
|
|
|
|
|
|
if (sp.sections) {
|
|
|
|
|
|
for (const sec of sp.sections) {
|
|
|
|
|
|
for (const album of sec.albums) {
|
|
|
|
|
|
const thumb = getAlbumThumbnailId(album.id);
|
|
|
|
|
|
if (thumb) return thumb;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
return null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-29 17:59:25 +02:00
|
|
|
|
function slugify(str: string): string {
|
|
|
|
|
|
return str
|
|
|
|
|
|
.toLowerCase()
|
|
|
|
|
|
.replace(/[^a-z0-9]+/g, '-')
|
|
|
|
|
|
.replace(/^-|-$/g, '');
|
|
|
|
|
|
}
|
|
|
|
|
|
|
feat: add visual admin panel with page builder and settings editor
- Add /admin route with password-protected visual page builder
- Page Builder: manage hero images, standalone albums, subpages, and sections
- Album Picker: browse all shared Immich albums with search and filtering
- Settings Editor: theme, grid, footer, legal, SEO configuration
- YAML service with atomic writes and automatic backups (content/.backups/)
- HMAC-signed admin sessions (24h expiry, HttpOnly cookies)
- API endpoints: /api/admin/{auth,gallery,settings,albums,reload}
- Enable with ADMIN_PASSWORD env var; disabled by default
- invalidateConfigCache() export for hot-reload after saves
2026-05-29 17:04:37 +02:00
|
|
|
|
// ── Render ───────────────────────────────────────────────────
|
|
|
|
|
|
if (loading) {
|
|
|
|
|
|
return (
|
|
|
|
|
|
<div className="admin-loading">
|
|
|
|
|
|
<div className="admin-spinner" />
|
|
|
|
|
|
</div>
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-31 21:17:52 +02:00
|
|
|
|
// Filter standalone albums
|
|
|
|
|
|
const filteredAlbums = gallery.albums.filter((album) => {
|
|
|
|
|
|
if (!searchQuery) return true;
|
|
|
|
|
|
const name = getAlbumName(album.id).toLowerCase();
|
|
|
|
|
|
const description = (album.description || '').toLowerCase();
|
|
|
|
|
|
const overrideTitle = (album.title || '').toLowerCase();
|
|
|
|
|
|
const query = searchQuery.toLowerCase();
|
|
|
|
|
|
return name.includes(query) || description.includes(query) || overrideTitle.includes(query) || album.id.toLowerCase().includes(query);
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// Filter subpages
|
|
|
|
|
|
const filteredSubpages = gallery.subpages
|
|
|
|
|
|
.map((sp, index) => ({ sp, index }))
|
|
|
|
|
|
.filter(({ sp }) => {
|
|
|
|
|
|
if (!searchQuery) return true;
|
|
|
|
|
|
const query = searchQuery.toLowerCase();
|
|
|
|
|
|
const name = sp.name.toLowerCase();
|
|
|
|
|
|
const title = (sp.title || '').toLowerCase();
|
|
|
|
|
|
const subtitle = (sp.subtitle || '').toLowerCase();
|
|
|
|
|
|
|
|
|
|
|
|
if (name.includes(query) || title.includes(query) || subtitle.includes(query)) return true;
|
|
|
|
|
|
|
|
|
|
|
|
const albums = sp.albums || [];
|
|
|
|
|
|
const hasMatchingAlbum = albums.some((a) => {
|
|
|
|
|
|
const aName = getAlbumName(a.id).toLowerCase();
|
|
|
|
|
|
const aTitle = (a.title || '').toLowerCase();
|
|
|
|
|
|
const aDesc = (a.description || '').toLowerCase();
|
|
|
|
|
|
return aName.includes(query) || aTitle.includes(query) || aDesc.includes(query) || a.id.toLowerCase().includes(query);
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
return hasMatchingAlbum;
|
|
|
|
|
|
});
|
|
|
|
|
|
|
feat: add visual admin panel with page builder and settings editor
- Add /admin route with password-protected visual page builder
- Page Builder: manage hero images, standalone albums, subpages, and sections
- Album Picker: browse all shared Immich albums with search and filtering
- Settings Editor: theme, grid, footer, legal, SEO configuration
- YAML service with atomic writes and automatic backups (content/.backups/)
- HMAC-signed admin sessions (24h expiry, HttpOnly cookies)
- API endpoints: /api/admin/{auth,gallery,settings,albums,reload}
- Enable with ADMIN_PASSWORD env var; disabled by default
- invalidateConfigCache() export for hot-reload after saves
2026-05-29 17:04:37 +02:00
|
|
|
|
return (
|
|
|
|
|
|
<div className="page-builder">
|
|
|
|
|
|
{/* Save Bar */}
|
|
|
|
|
|
<div className={`save-bar ${dirty ? 'dirty' : ''}`}>
|
|
|
|
|
|
<div className="save-bar-left">
|
|
|
|
|
|
{dirty && <span className="unsaved-badge">Unsaved changes</span>}
|
|
|
|
|
|
{saveMessage && (
|
|
|
|
|
|
<span
|
|
|
|
|
|
className={`save-message ${saveMessage.startsWith('Error') ? 'error' : 'success'}`}
|
|
|
|
|
|
>
|
|
|
|
|
|
{saveMessage}
|
|
|
|
|
|
</span>
|
|
|
|
|
|
)}
|
2026-05-29 17:59:25 +02:00
|
|
|
|
{!dirty && !saveMessage && <span className="save-hint">⌘S to save</span>}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div className="save-bar-right">
|
|
|
|
|
|
<a
|
chore(release): v0.10.0 (#400)
Photo Essay mode, client proofing, studio-modern preset, admin panel overhaul, watermark, cookieless analytics, subpage toggle, SEO title templates. Analytics API now admin-only, Trivy scan on publish. Album order mirrors the Immich timeline; map theme, admin status panel, lightbox counter and Docker health check fixed.
See CHANGELOG.md for the full list and the health-check upgrade note.
2026-08-10 09:56:20 +02:00
|
|
|
|
href="/?fresh=1"
|
2026-05-29 17:59:25 +02:00
|
|
|
|
target="_blank"
|
|
|
|
|
|
rel="noopener noreferrer"
|
|
|
|
|
|
className="admin-btn admin-btn-ghost admin-btn-preview"
|
chore(release): v0.10.0 (#400)
Photo Essay mode, client proofing, studio-modern preset, admin panel overhaul, watermark, cookieless analytics, subpage toggle, SEO title templates. Analytics API now admin-only, Trivy scan on publish. Album order mirrors the Immich timeline; map theme, admin status panel, lightbox counter and Docker health check fixed.
See CHANGELOG.md for the full list and the health-check upgrade note.
2026-08-10 09:56:20 +02:00
|
|
|
|
title="Open site in new tab (bypassing cache)"
|
2026-05-29 17:59:25 +02:00
|
|
|
|
>
|
|
|
|
|
|
↗ Preview Site
|
|
|
|
|
|
</a>
|
|
|
|
|
|
<button
|
|
|
|
|
|
className="admin-btn admin-btn-primary"
|
|
|
|
|
|
onClick={handleSave}
|
|
|
|
|
|
disabled={!dirty || saving}
|
|
|
|
|
|
>
|
|
|
|
|
|
{saving ? 'Saving...' : 'Save Changes'}
|
|
|
|
|
|
</button>
|
feat: add visual admin panel with page builder and settings editor
- Add /admin route with password-protected visual page builder
- Page Builder: manage hero images, standalone albums, subpages, and sections
- Album Picker: browse all shared Immich albums with search and filtering
- Settings Editor: theme, grid, footer, legal, SEO configuration
- YAML service with atomic writes and automatic backups (content/.backups/)
- HMAC-signed admin sessions (24h expiry, HttpOnly cookies)
- API endpoints: /api/admin/{auth,gallery,settings,albums,reload}
- Enable with ADMIN_PASSWORD env var; disabled by default
- invalidateConfigCache() export for hot-reload after saves
2026-05-29 17:04:37 +02:00
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
2026-05-31 21:17:52 +02:00
|
|
|
|
{/* Search Bar */}
|
|
|
|
|
|
<div className="builder-search-container">
|
|
|
|
|
|
<div className="builder-search-wrapper">
|
security: add Trivy container scan to Docker publish workflow (#399)
* feat(admin): add backup manager UI with 1-click restore
* feat(admin): collapse older backup entries by default
* fix(theme): isolate admin layout, instant DOM theme updates, and add Light/Dark mode toggle
* feat(admin): replace text controls for Photo Frame and Hero Style with visual preview cards
* fix(admin): define missing CSS rules for button system (.btn, .preset-btn)
* feat(admin): redesign theme settings with segmented mode toggle, accent color swatches, and custom iOS-style toggle cards
* feat(admin): upgrade all settings tabs with visual grid previews, Google SEO snippet preview, and toggle cards
* feat(admin): overhaul Pages Builder UX with slide-over drawer, card hover badges, real-time search, and floating save bar
* fix(admin): remove leftover dangling syntax snippet in PageBuilder.tsx
* fix(admin): add position: relative to subpage-tile so hover overlay is contained inside the tile
* style(admin): replace all emojis with clean Leica-style vector SVG icons
* fix(types): extend IconProps with SVGProps to allow style prop on Leica SVG icons
* feat(admin): add subpage live-preview, privacy analytics, and asset protection settings
* fix(typecheck): fix AlbumEntry property access in subpage drawer preview mode
* fix(types): fix ImmichAlbumInfo property names thumbnailAssetId and albumName in PageBuilder.tsx
* fix(types): add protection and watermark optional properties to AppConfig interface
* feat(settings): add toggle for disabling analytics tracking (defaults to enabled)
* feat(ux): show status banner when analytics tracking is disabled + add live sync indicator
* feat(watermark): render customizable watermark overlay on Lightbox images + rename section to Portfolio Features & Modules
* fix(types): import type LightboxWatermark in PhotoGrid.tsx
* fix(types): destructure watermark prop in PhotoGrid function signature
* feat(seo): add configurable subpage title template and metadata descriptions
* feat(seo): add generateMetadata to about page for title template support
* feat(proofing): add client proofing, favorites selection, compact URL bitmasking, and export modal
* feat(essay): add Photo Essay storytelling mode with RSC markdown parser and fullbleed/pair layouts
* feat(subpages): add page enabled toggle to deactivate subpages without deleting them
* feat(admin): add visual essay block builder for photo essay storytelling pages
* security: add Trivy container scan to Docker publish workflow
Scans the built image for CRITICAL/HIGH CVEs on every release and
uploads results to the GitHub Security tab. Non-blocking for now
(exit-code 0) so releases aren't gated on findings. Also documents
the scan and existing Dependabot coverage in the README.
---------
Co-authored-by: ralksta <ralksta@users.noreply.github.com>
2026-08-07 11:08:04 +02:00
|
|
|
|
<span className="builder-search-icon"><Icons.Search /></span>
|
2026-05-31 21:17:52 +02:00
|
|
|
|
<input
|
|
|
|
|
|
type="text"
|
|
|
|
|
|
className="builder-search-input"
|
|
|
|
|
|
placeholder="Search albums or subpages..."
|
|
|
|
|
|
value={searchQuery}
|
|
|
|
|
|
onChange={(e) => setSearchQuery(e.target.value)}
|
|
|
|
|
|
/>
|
|
|
|
|
|
{searchQuery && (
|
|
|
|
|
|
<button className="builder-search-clear" onClick={() => setSearchQuery('')} title="Clear search">
|
|
|
|
|
|
×
|
|
|
|
|
|
</button>
|
|
|
|
|
|
)}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
feat: add visual admin panel with page builder and settings editor
- Add /admin route with password-protected visual page builder
- Page Builder: manage hero images, standalone albums, subpages, and sections
- Album Picker: browse all shared Immich albums with search and filtering
- Settings Editor: theme, grid, footer, legal, SEO configuration
- YAML service with atomic writes and automatic backups (content/.backups/)
- HMAC-signed admin sessions (24h expiry, HttpOnly cookies)
- API endpoints: /api/admin/{auth,gallery,settings,albums,reload}
- Enable with ADMIN_PASSWORD env var; disabled by default
- invalidateConfigCache() export for hot-reload after saves
2026-05-29 17:04:37 +02:00
|
|
|
|
{/* Hero Section */}
|
|
|
|
|
|
<section className="builder-section">
|
|
|
|
|
|
<div className="builder-section-header">
|
2026-05-31 21:27:13 +02:00
|
|
|
|
<h2><Icons.Home /> Homepage Hero</h2>
|
|
|
|
|
|
<button
|
|
|
|
|
|
className="admin-btn admin-btn-sm"
|
|
|
|
|
|
onClick={() =>
|
|
|
|
|
|
setHeroPickerTarget({
|
|
|
|
|
|
onSelect: handleHeroSelect,
|
|
|
|
|
|
currentAssetIds: gallery.hero,
|
|
|
|
|
|
title: 'Pick Hero Image for Homepage',
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
>
|
|
|
|
|
|
<Icons.Plus /> Add Hero
|
feat: add visual admin panel with page builder and settings editor
- Add /admin route with password-protected visual page builder
- Page Builder: manage hero images, standalone albums, subpages, and sections
- Album Picker: browse all shared Immich albums with search and filtering
- Settings Editor: theme, grid, footer, legal, SEO configuration
- YAML service with atomic writes and automatic backups (content/.backups/)
- HMAC-signed admin sessions (24h expiry, HttpOnly cookies)
- API endpoints: /api/admin/{auth,gallery,settings,albums,reload}
- Enable with ADMIN_PASSWORD env var; disabled by default
- invalidateConfigCache() export for hot-reload after saves
2026-05-29 17:04:37 +02:00
|
|
|
|
</button>
|
|
|
|
|
|
</div>
|
2026-05-29 17:59:25 +02:00
|
|
|
|
<DndContext
|
|
|
|
|
|
sensors={sensors}
|
|
|
|
|
|
collisionDetection={closestCenter}
|
|
|
|
|
|
onDragEnd={handleHeroDragEnd}
|
|
|
|
|
|
>
|
|
|
|
|
|
<SortableContext
|
|
|
|
|
|
items={gallery.hero.map((_, i) => `hero-${i}`)}
|
|
|
|
|
|
strategy={horizontalListSortingStrategy}
|
|
|
|
|
|
>
|
|
|
|
|
|
<div className="hero-grid">
|
|
|
|
|
|
{gallery.hero.length === 0 && (
|
|
|
|
|
|
<p className="empty-hint">
|
|
|
|
|
|
No hero images configured. Add photos to show a hero carousel on the homepage.
|
|
|
|
|
|
</p>
|
|
|
|
|
|
)}
|
|
|
|
|
|
{gallery.hero.map((id, i) => (
|
|
|
|
|
|
<SortableHeroTile
|
|
|
|
|
|
key={`hero-${i}`}
|
|
|
|
|
|
id={id}
|
|
|
|
|
|
index={i}
|
|
|
|
|
|
onRemove={() => removeHero(i)}
|
|
|
|
|
|
/>
|
|
|
|
|
|
))}
|
feat: add visual admin panel with page builder and settings editor
- Add /admin route with password-protected visual page builder
- Page Builder: manage hero images, standalone albums, subpages, and sections
- Album Picker: browse all shared Immich albums with search and filtering
- Settings Editor: theme, grid, footer, legal, SEO configuration
- YAML service with atomic writes and automatic backups (content/.backups/)
- HMAC-signed admin sessions (24h expiry, HttpOnly cookies)
- API endpoints: /api/admin/{auth,gallery,settings,albums,reload}
- Enable with ADMIN_PASSWORD env var; disabled by default
- invalidateConfigCache() export for hot-reload after saves
2026-05-29 17:04:37 +02:00
|
|
|
|
</div>
|
2026-05-29 17:59:25 +02:00
|
|
|
|
</SortableContext>
|
|
|
|
|
|
</DndContext>
|
feat: add visual admin panel with page builder and settings editor
- Add /admin route with password-protected visual page builder
- Page Builder: manage hero images, standalone albums, subpages, and sections
- Album Picker: browse all shared Immich albums with search and filtering
- Settings Editor: theme, grid, footer, legal, SEO configuration
- YAML service with atomic writes and automatic backups (content/.backups/)
- HMAC-signed admin sessions (24h expiry, HttpOnly cookies)
- API endpoints: /api/admin/{auth,gallery,settings,albums,reload}
- Enable with ADMIN_PASSWORD env var; disabled by default
- invalidateConfigCache() export for hot-reload after saves
2026-05-29 17:04:37 +02:00
|
|
|
|
</section>
|
|
|
|
|
|
|
|
|
|
|
|
{/* Standalone Albums */}
|
|
|
|
|
|
<section className="builder-section">
|
|
|
|
|
|
<div className="builder-section-header">
|
|
|
|
|
|
<h2>📷 Standalone Albums</h2>
|
|
|
|
|
|
<button
|
|
|
|
|
|
className="admin-btn admin-btn-sm"
|
|
|
|
|
|
onClick={() => setPickerTarget({ type: 'standalone' })}
|
|
|
|
|
|
>
|
|
|
|
|
|
+ Add Album
|
|
|
|
|
|
</button>
|
|
|
|
|
|
</div>
|
2026-05-29 17:59:25 +02:00
|
|
|
|
<DndContext
|
|
|
|
|
|
sensors={sensors}
|
|
|
|
|
|
collisionDetection={closestCenter}
|
|
|
|
|
|
onDragEnd={handleAlbumDragEnd}
|
|
|
|
|
|
>
|
|
|
|
|
|
<SortableContext
|
2026-05-31 21:17:52 +02:00
|
|
|
|
items={filteredAlbums.map((a) => {
|
|
|
|
|
|
const originalIndex = gallery.albums.findIndex((x) => x.id === a.id);
|
|
|
|
|
|
return `album-${a.id}-${originalIndex}`;
|
|
|
|
|
|
})}
|
2026-05-29 17:59:25 +02:00
|
|
|
|
strategy={verticalListSortingStrategy}
|
|
|
|
|
|
>
|
|
|
|
|
|
<div className="album-list">
|
2026-05-31 21:17:52 +02:00
|
|
|
|
{filteredAlbums.length === 0 && (
|
2026-05-29 17:59:25 +02:00
|
|
|
|
<p className="empty-hint">
|
2026-05-31 21:17:52 +02:00
|
|
|
|
{searchQuery ? 'No matching standalone albums found.' : 'No standalone albums. These show directly on the homepage.'}
|
2026-05-29 17:59:25 +02:00
|
|
|
|
</p>
|
|
|
|
|
|
)}
|
2026-05-31 21:17:52 +02:00
|
|
|
|
{filteredAlbums.map((album) => {
|
|
|
|
|
|
const originalIndex = gallery.albums.findIndex((a) => a.id === album.id);
|
|
|
|
|
|
return (
|
|
|
|
|
|
<SortableAlbumCard
|
|
|
|
|
|
key={`${album.id}-${originalIndex}`}
|
|
|
|
|
|
album={album}
|
|
|
|
|
|
index={originalIndex}
|
|
|
|
|
|
name={getAlbumName(album.id)}
|
|
|
|
|
|
count={getAlbumCount(album.id)}
|
|
|
|
|
|
thumbnailId={getAlbumThumbnailId(album.id)}
|
|
|
|
|
|
onRemove={() => removeStandaloneAlbum(originalIndex)}
|
2026-05-31 21:27:13 +02:00
|
|
|
|
onEdit={() => setEditingAlbumAddress({ type: 'standalone', albumIndex: originalIndex })}
|
2026-05-31 21:17:52 +02:00
|
|
|
|
/>
|
|
|
|
|
|
);
|
|
|
|
|
|
})}
|
2026-05-29 17:59:25 +02:00
|
|
|
|
</div>
|
|
|
|
|
|
</SortableContext>
|
|
|
|
|
|
</DndContext>
|
feat: add visual admin panel with page builder and settings editor
- Add /admin route with password-protected visual page builder
- Page Builder: manage hero images, standalone albums, subpages, and sections
- Album Picker: browse all shared Immich albums with search and filtering
- Settings Editor: theme, grid, footer, legal, SEO configuration
- YAML service with atomic writes and automatic backups (content/.backups/)
- HMAC-signed admin sessions (24h expiry, HttpOnly cookies)
- API endpoints: /api/admin/{auth,gallery,settings,albums,reload}
- Enable with ADMIN_PASSWORD env var; disabled by default
- invalidateConfigCache() export for hot-reload after saves
2026-05-29 17:04:37 +02:00
|
|
|
|
</section>
|
|
|
|
|
|
|
|
|
|
|
|
{/* Subpages */}
|
|
|
|
|
|
<section className="builder-section">
|
|
|
|
|
|
<div className="builder-section-header">
|
|
|
|
|
|
<h2>📂 Subpages</h2>
|
|
|
|
|
|
<button className="admin-btn admin-btn-sm" onClick={addSubpage}>
|
|
|
|
|
|
+ New Subpage
|
|
|
|
|
|
</button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
{gallery.subpages.length === 0 && (
|
|
|
|
|
|
<p className="empty-hint">
|
|
|
|
|
|
No subpages. Create one to group albums under a custom URL path.
|
|
|
|
|
|
</p>
|
|
|
|
|
|
)}
|
|
|
|
|
|
|
2026-05-31 21:17:52 +02:00
|
|
|
|
{gallery.subpages.length > 0 && filteredSubpages.length === 0 && (
|
|
|
|
|
|
<p className="empty-hint">
|
|
|
|
|
|
No matching subpages found.
|
|
|
|
|
|
</p>
|
|
|
|
|
|
)}
|
|
|
|
|
|
|
2026-05-29 17:59:25 +02:00
|
|
|
|
{/* Collapsed overview grid with DnD */}
|
|
|
|
|
|
<DndContext
|
|
|
|
|
|
sensors={sensors}
|
|
|
|
|
|
collisionDetection={closestCenter}
|
|
|
|
|
|
onDragEnd={handleSubpageDragEnd}
|
|
|
|
|
|
>
|
|
|
|
|
|
<SortableContext
|
2026-05-31 21:17:52 +02:00
|
|
|
|
items={filteredSubpages.map(({ index }) => `subpage-${index}`)}
|
2026-05-29 17:59:25 +02:00
|
|
|
|
strategy={horizontalListSortingStrategy}
|
|
|
|
|
|
>
|
|
|
|
|
|
<div className="subpage-grid">
|
2026-05-31 21:17:52 +02:00
|
|
|
|
{filteredSubpages.map(({ sp, index }) => (
|
2026-05-29 17:59:25 +02:00
|
|
|
|
<SortableSubpageTile
|
2026-05-31 21:17:52 +02:00
|
|
|
|
key={`subpage-${index}`}
|
2026-05-29 17:59:25 +02:00
|
|
|
|
sp={sp}
|
2026-05-31 21:17:52 +02:00
|
|
|
|
spIndex={index}
|
|
|
|
|
|
isActive={expandedSubpage === index}
|
|
|
|
|
|
onClick={() => setExpandedSubpage(expandedSubpage === index ? null : index)}
|
2026-05-29 17:59:25 +02:00
|
|
|
|
getFirstThumb={getFirstSubpageThumb}
|
|
|
|
|
|
/>
|
|
|
|
|
|
))}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</SortableContext>
|
|
|
|
|
|
</DndContext>
|
feat: add visual admin panel with page builder and settings editor
- Add /admin route with password-protected visual page builder
- Page Builder: manage hero images, standalone albums, subpages, and sections
- Album Picker: browse all shared Immich albums with search and filtering
- Settings Editor: theme, grid, footer, legal, SEO configuration
- YAML service with atomic writes and automatic backups (content/.backups/)
- HMAC-signed admin sessions (24h expiry, HttpOnly cookies)
- API endpoints: /api/admin/{auth,gallery,settings,albums,reload}
- Enable with ADMIN_PASSWORD env var; disabled by default
- invalidateConfigCache() export for hot-reload after saves
2026-05-29 17:04:37 +02:00
|
|
|
|
|
security: add Trivy container scan to Docker publish workflow (#399)
* feat(admin): add backup manager UI with 1-click restore
* feat(admin): collapse older backup entries by default
* fix(theme): isolate admin layout, instant DOM theme updates, and add Light/Dark mode toggle
* feat(admin): replace text controls for Photo Frame and Hero Style with visual preview cards
* fix(admin): define missing CSS rules for button system (.btn, .preset-btn)
* feat(admin): redesign theme settings with segmented mode toggle, accent color swatches, and custom iOS-style toggle cards
* feat(admin): upgrade all settings tabs with visual grid previews, Google SEO snippet preview, and toggle cards
* feat(admin): overhaul Pages Builder UX with slide-over drawer, card hover badges, real-time search, and floating save bar
* fix(admin): remove leftover dangling syntax snippet in PageBuilder.tsx
* fix(admin): add position: relative to subpage-tile so hover overlay is contained inside the tile
* style(admin): replace all emojis with clean Leica-style vector SVG icons
* fix(types): extend IconProps with SVGProps to allow style prop on Leica SVG icons
* feat(admin): add subpage live-preview, privacy analytics, and asset protection settings
* fix(typecheck): fix AlbumEntry property access in subpage drawer preview mode
* fix(types): fix ImmichAlbumInfo property names thumbnailAssetId and albumName in PageBuilder.tsx
* fix(types): add protection and watermark optional properties to AppConfig interface
* feat(settings): add toggle for disabling analytics tracking (defaults to enabled)
* feat(ux): show status banner when analytics tracking is disabled + add live sync indicator
* feat(watermark): render customizable watermark overlay on Lightbox images + rename section to Portfolio Features & Modules
* fix(types): import type LightboxWatermark in PhotoGrid.tsx
* fix(types): destructure watermark prop in PhotoGrid function signature
* feat(seo): add configurable subpage title template and metadata descriptions
* feat(seo): add generateMetadata to about page for title template support
* feat(proofing): add client proofing, favorites selection, compact URL bitmasking, and export modal
* feat(essay): add Photo Essay storytelling mode with RSC markdown parser and fullbleed/pair layouts
* feat(subpages): add page enabled toggle to deactivate subpages without deleting them
* feat(admin): add visual essay block builder for photo essay storytelling pages
* security: add Trivy container scan to Docker publish workflow
Scans the built image for CRITICAL/HIGH CVEs on every release and
uploads results to the GitHub Security tab. Non-blocking for now
(exit-code 0) so releases aren't gated on findings. Also documents
the scan and existing Dependabot coverage in the README.
---------
Co-authored-by: ralksta <ralksta@users.noreply.github.com>
2026-08-07 11:08:04 +02:00
|
|
|
|
{/* Expanded subpage detail (Slide-Over Drawer) */}
|
2026-05-29 17:29:26 +02:00
|
|
|
|
{expandedSubpage !== null && gallery.subpages[expandedSubpage] && (
|
security: add Trivy container scan to Docker publish workflow (#399)
* feat(admin): add backup manager UI with 1-click restore
* feat(admin): collapse older backup entries by default
* fix(theme): isolate admin layout, instant DOM theme updates, and add Light/Dark mode toggle
* feat(admin): replace text controls for Photo Frame and Hero Style with visual preview cards
* fix(admin): define missing CSS rules for button system (.btn, .preset-btn)
* feat(admin): redesign theme settings with segmented mode toggle, accent color swatches, and custom iOS-style toggle cards
* feat(admin): upgrade all settings tabs with visual grid previews, Google SEO snippet preview, and toggle cards
* feat(admin): overhaul Pages Builder UX with slide-over drawer, card hover badges, real-time search, and floating save bar
* fix(admin): remove leftover dangling syntax snippet in PageBuilder.tsx
* fix(admin): add position: relative to subpage-tile so hover overlay is contained inside the tile
* style(admin): replace all emojis with clean Leica-style vector SVG icons
* fix(types): extend IconProps with SVGProps to allow style prop on Leica SVG icons
* feat(admin): add subpage live-preview, privacy analytics, and asset protection settings
* fix(typecheck): fix AlbumEntry property access in subpage drawer preview mode
* fix(types): fix ImmichAlbumInfo property names thumbnailAssetId and albumName in PageBuilder.tsx
* fix(types): add protection and watermark optional properties to AppConfig interface
* feat(settings): add toggle for disabling analytics tracking (defaults to enabled)
* feat(ux): show status banner when analytics tracking is disabled + add live sync indicator
* feat(watermark): render customizable watermark overlay on Lightbox images + rename section to Portfolio Features & Modules
* fix(types): import type LightboxWatermark in PhotoGrid.tsx
* fix(types): destructure watermark prop in PhotoGrid function signature
* feat(seo): add configurable subpage title template and metadata descriptions
* feat(seo): add generateMetadata to about page for title template support
* feat(proofing): add client proofing, favorites selection, compact URL bitmasking, and export modal
* feat(essay): add Photo Essay storytelling mode with RSC markdown parser and fullbleed/pair layouts
* feat(subpages): add page enabled toggle to deactivate subpages without deleting them
* feat(admin): add visual essay block builder for photo essay storytelling pages
* security: add Trivy container scan to Docker publish workflow
Scans the built image for CRITICAL/HIGH CVEs on every release and
uploads results to the GitHub Security tab. Non-blocking for now
(exit-code 0) so releases aren't gated on findings. Also documents
the scan and existing Dependabot coverage in the README.
---------
Co-authored-by: ralksta <ralksta@users.noreply.github.com>
2026-08-07 11:08:04 +02:00
|
|
|
|
<div className="subpage-drawer-backdrop" onClick={() => setExpandedSubpage(null)}>
|
|
|
|
|
|
<div className="subpage-drawer-container" onClick={(e) => e.stopPropagation()}>
|
|
|
|
|
|
{(() => {
|
|
|
|
|
|
const sp = gallery.subpages[expandedSubpage];
|
|
|
|
|
|
const spIndex = expandedSubpage;
|
|
|
|
|
|
return (
|
|
|
|
|
|
<>
|
|
|
|
|
|
<div className="subpage-drawer-header">
|
|
|
|
|
|
<div className="subpage-drawer-title-group">
|
|
|
|
|
|
<span className="subpage-drawer-breadcrumb">Pages / Subpages</span>
|
|
|
|
|
|
<h3 className="subpage-drawer-title">{sp.title || sp.name || 'Untitled Page'}</h3>
|
2026-05-29 17:29:26 +02:00
|
|
|
|
</div>
|
security: add Trivy container scan to Docker publish workflow (#399)
* feat(admin): add backup manager UI with 1-click restore
* feat(admin): collapse older backup entries by default
* fix(theme): isolate admin layout, instant DOM theme updates, and add Light/Dark mode toggle
* feat(admin): replace text controls for Photo Frame and Hero Style with visual preview cards
* fix(admin): define missing CSS rules for button system (.btn, .preset-btn)
* feat(admin): redesign theme settings with segmented mode toggle, accent color swatches, and custom iOS-style toggle cards
* feat(admin): upgrade all settings tabs with visual grid previews, Google SEO snippet preview, and toggle cards
* feat(admin): overhaul Pages Builder UX with slide-over drawer, card hover badges, real-time search, and floating save bar
* fix(admin): remove leftover dangling syntax snippet in PageBuilder.tsx
* fix(admin): add position: relative to subpage-tile so hover overlay is contained inside the tile
* style(admin): replace all emojis with clean Leica-style vector SVG icons
* fix(types): extend IconProps with SVGProps to allow style prop on Leica SVG icons
* feat(admin): add subpage live-preview, privacy analytics, and asset protection settings
* fix(typecheck): fix AlbumEntry property access in subpage drawer preview mode
* fix(types): fix ImmichAlbumInfo property names thumbnailAssetId and albumName in PageBuilder.tsx
* fix(types): add protection and watermark optional properties to AppConfig interface
* feat(settings): add toggle for disabling analytics tracking (defaults to enabled)
* feat(ux): show status banner when analytics tracking is disabled + add live sync indicator
* feat(watermark): render customizable watermark overlay on Lightbox images + rename section to Portfolio Features & Modules
* fix(types): import type LightboxWatermark in PhotoGrid.tsx
* fix(types): destructure watermark prop in PhotoGrid function signature
* feat(seo): add configurable subpage title template and metadata descriptions
* feat(seo): add generateMetadata to about page for title template support
* feat(proofing): add client proofing, favorites selection, compact URL bitmasking, and export modal
* feat(essay): add Photo Essay storytelling mode with RSC markdown parser and fullbleed/pair layouts
* feat(subpages): add page enabled toggle to deactivate subpages without deleting them
* feat(admin): add visual essay block builder for photo essay storytelling pages
* security: add Trivy container scan to Docker publish workflow
Scans the built image for CRITICAL/HIGH CVEs on every release and
uploads results to the GitHub Security tab. Non-blocking for now
(exit-code 0) so releases aren't gated on findings. Also documents
the scan and existing Dependabot coverage in the README.
---------
Co-authored-by: ralksta <ralksta@users.noreply.github.com>
2026-08-07 11:08:04 +02:00
|
|
|
|
<div className="subpage-drawer-header-actions">
|
|
|
|
|
|
<div className="segmented-control" style={{ padding: '2px' }}>
|
|
|
|
|
|
<button
|
|
|
|
|
|
type="button"
|
|
|
|
|
|
className={`segment-btn ${drawerMode === 'edit' ? 'active' : ''}`}
|
|
|
|
|
|
onClick={() => setDrawerMode('edit')}
|
|
|
|
|
|
style={{ padding: '0.2rem 0.6rem', fontSize: '0.75rem' }}
|
|
|
|
|
|
>
|
|
|
|
|
|
<Icons.Edit /> Edit
|
|
|
|
|
|
</button>
|
|
|
|
|
|
<button
|
|
|
|
|
|
type="button"
|
|
|
|
|
|
className={`segment-btn ${drawerMode === 'preview' ? 'active' : ''}`}
|
|
|
|
|
|
onClick={() => setDrawerMode('preview')}
|
|
|
|
|
|
style={{ padding: '0.2rem 0.6rem', fontSize: '0.75rem' }}
|
|
|
|
|
|
>
|
|
|
|
|
|
<Icons.Search /> Live Preview
|
|
|
|
|
|
</button>
|
|
|
|
|
|
</div>
|
2026-05-29 17:29:26 +02:00
|
|
|
|
|
security: add Trivy container scan to Docker publish workflow (#399)
* feat(admin): add backup manager UI with 1-click restore
* feat(admin): collapse older backup entries by default
* fix(theme): isolate admin layout, instant DOM theme updates, and add Light/Dark mode toggle
* feat(admin): replace text controls for Photo Frame and Hero Style with visual preview cards
* fix(admin): define missing CSS rules for button system (.btn, .preset-btn)
* feat(admin): redesign theme settings with segmented mode toggle, accent color swatches, and custom iOS-style toggle cards
* feat(admin): upgrade all settings tabs with visual grid previews, Google SEO snippet preview, and toggle cards
* feat(admin): overhaul Pages Builder UX with slide-over drawer, card hover badges, real-time search, and floating save bar
* fix(admin): remove leftover dangling syntax snippet in PageBuilder.tsx
* fix(admin): add position: relative to subpage-tile so hover overlay is contained inside the tile
* style(admin): replace all emojis with clean Leica-style vector SVG icons
* fix(types): extend IconProps with SVGProps to allow style prop on Leica SVG icons
* feat(admin): add subpage live-preview, privacy analytics, and asset protection settings
* fix(typecheck): fix AlbumEntry property access in subpage drawer preview mode
* fix(types): fix ImmichAlbumInfo property names thumbnailAssetId and albumName in PageBuilder.tsx
* fix(types): add protection and watermark optional properties to AppConfig interface
* feat(settings): add toggle for disabling analytics tracking (defaults to enabled)
* feat(ux): show status banner when analytics tracking is disabled + add live sync indicator
* feat(watermark): render customizable watermark overlay on Lightbox images + rename section to Portfolio Features & Modules
* fix(types): import type LightboxWatermark in PhotoGrid.tsx
* fix(types): destructure watermark prop in PhotoGrid function signature
* feat(seo): add configurable subpage title template and metadata descriptions
* feat(seo): add generateMetadata to about page for title template support
* feat(proofing): add client proofing, favorites selection, compact URL bitmasking, and export modal
* feat(essay): add Photo Essay storytelling mode with RSC markdown parser and fullbleed/pair layouts
* feat(subpages): add page enabled toggle to deactivate subpages without deleting them
* feat(admin): add visual essay block builder for photo essay storytelling pages
* security: add Trivy container scan to Docker publish workflow
Scans the built image for CRITICAL/HIGH CVEs on every release and
uploads results to the GitHub Security tab. Non-blocking for now
(exit-code 0) so releases aren't gated on findings. Also documents
the scan and existing Dependabot coverage in the README.
---------
Co-authored-by: ralksta <ralksta@users.noreply.github.com>
2026-08-07 11:08:04 +02:00
|
|
|
|
<a
|
chore(release): v0.10.0 (#400)
Photo Essay mode, client proofing, studio-modern preset, admin panel overhaul, watermark, cookieless analytics, subpage toggle, SEO title templates. Analytics API now admin-only, Trivy scan on publish. Album order mirrors the Immich timeline; map theme, admin status panel, lightbox counter and Docker health check fixed.
See CHANGELOG.md for the full list and the health-check upgrade note.
2026-08-10 09:56:20 +02:00
|
|
|
|
href={`/${slugify(sp.name)}?fresh=1`}
|
security: add Trivy container scan to Docker publish workflow (#399)
* feat(admin): add backup manager UI with 1-click restore
* feat(admin): collapse older backup entries by default
* fix(theme): isolate admin layout, instant DOM theme updates, and add Light/Dark mode toggle
* feat(admin): replace text controls for Photo Frame and Hero Style with visual preview cards
* fix(admin): define missing CSS rules for button system (.btn, .preset-btn)
* feat(admin): redesign theme settings with segmented mode toggle, accent color swatches, and custom iOS-style toggle cards
* feat(admin): upgrade all settings tabs with visual grid previews, Google SEO snippet preview, and toggle cards
* feat(admin): overhaul Pages Builder UX with slide-over drawer, card hover badges, real-time search, and floating save bar
* fix(admin): remove leftover dangling syntax snippet in PageBuilder.tsx
* fix(admin): add position: relative to subpage-tile so hover overlay is contained inside the tile
* style(admin): replace all emojis with clean Leica-style vector SVG icons
* fix(types): extend IconProps with SVGProps to allow style prop on Leica SVG icons
* feat(admin): add subpage live-preview, privacy analytics, and asset protection settings
* fix(typecheck): fix AlbumEntry property access in subpage drawer preview mode
* fix(types): fix ImmichAlbumInfo property names thumbnailAssetId and albumName in PageBuilder.tsx
* fix(types): add protection and watermark optional properties to AppConfig interface
* feat(settings): add toggle for disabling analytics tracking (defaults to enabled)
* feat(ux): show status banner when analytics tracking is disabled + add live sync indicator
* feat(watermark): render customizable watermark overlay on Lightbox images + rename section to Portfolio Features & Modules
* fix(types): import type LightboxWatermark in PhotoGrid.tsx
* fix(types): destructure watermark prop in PhotoGrid function signature
* feat(seo): add configurable subpage title template and metadata descriptions
* feat(seo): add generateMetadata to about page for title template support
* feat(proofing): add client proofing, favorites selection, compact URL bitmasking, and export modal
* feat(essay): add Photo Essay storytelling mode with RSC markdown parser and fullbleed/pair layouts
* feat(subpages): add page enabled toggle to deactivate subpages without deleting them
* feat(admin): add visual essay block builder for photo essay storytelling pages
* security: add Trivy container scan to Docker publish workflow
Scans the built image for CRITICAL/HIGH CVEs on every release and
uploads results to the GitHub Security tab. Non-blocking for now
(exit-code 0) so releases aren't gated on findings. Also documents
the scan and existing Dependabot coverage in the README.
---------
Co-authored-by: ralksta <ralksta@users.noreply.github.com>
2026-08-07 11:08:04 +02:00
|
|
|
|
target="_blank"
|
|
|
|
|
|
rel="noopener noreferrer"
|
|
|
|
|
|
className="admin-btn admin-btn-xs admin-btn-ghost"
|
chore(release): v0.10.0 (#400)
Photo Essay mode, client proofing, studio-modern preset, admin panel overhaul, watermark, cookieless analytics, subpage toggle, SEO title templates. Analytics API now admin-only, Trivy scan on publish. Album order mirrors the Immich timeline; map theme, admin status panel, lightbox counter and Docker health check fixed.
See CHANGELOG.md for the full list and the health-check upgrade note.
2026-08-10 09:56:20 +02:00
|
|
|
|
title="Open live page in new tab (bypassing cache)"
|
security: add Trivy container scan to Docker publish workflow (#399)
* feat(admin): add backup manager UI with 1-click restore
* feat(admin): collapse older backup entries by default
* fix(theme): isolate admin layout, instant DOM theme updates, and add Light/Dark mode toggle
* feat(admin): replace text controls for Photo Frame and Hero Style with visual preview cards
* fix(admin): define missing CSS rules for button system (.btn, .preset-btn)
* feat(admin): redesign theme settings with segmented mode toggle, accent color swatches, and custom iOS-style toggle cards
* feat(admin): upgrade all settings tabs with visual grid previews, Google SEO snippet preview, and toggle cards
* feat(admin): overhaul Pages Builder UX with slide-over drawer, card hover badges, real-time search, and floating save bar
* fix(admin): remove leftover dangling syntax snippet in PageBuilder.tsx
* fix(admin): add position: relative to subpage-tile so hover overlay is contained inside the tile
* style(admin): replace all emojis with clean Leica-style vector SVG icons
* fix(types): extend IconProps with SVGProps to allow style prop on Leica SVG icons
* feat(admin): add subpage live-preview, privacy analytics, and asset protection settings
* fix(typecheck): fix AlbumEntry property access in subpage drawer preview mode
* fix(types): fix ImmichAlbumInfo property names thumbnailAssetId and albumName in PageBuilder.tsx
* fix(types): add protection and watermark optional properties to AppConfig interface
* feat(settings): add toggle for disabling analytics tracking (defaults to enabled)
* feat(ux): show status banner when analytics tracking is disabled + add live sync indicator
* feat(watermark): render customizable watermark overlay on Lightbox images + rename section to Portfolio Features & Modules
* fix(types): import type LightboxWatermark in PhotoGrid.tsx
* fix(types): destructure watermark prop in PhotoGrid function signature
* feat(seo): add configurable subpage title template and metadata descriptions
* feat(seo): add generateMetadata to about page for title template support
* feat(proofing): add client proofing, favorites selection, compact URL bitmasking, and export modal
* feat(essay): add Photo Essay storytelling mode with RSC markdown parser and fullbleed/pair layouts
* feat(subpages): add page enabled toggle to deactivate subpages without deleting them
* feat(admin): add visual essay block builder for photo essay storytelling pages
* security: add Trivy container scan to Docker publish workflow
Scans the built image for CRITICAL/HIGH CVEs on every release and
uploads results to the GitHub Security tab. Non-blocking for now
(exit-code 0) so releases aren't gated on findings. Also documents
the scan and existing Dependabot coverage in the README.
---------
Co-authored-by: ralksta <ralksta@users.noreply.github.com>
2026-08-07 11:08:04 +02:00
|
|
|
|
>
|
|
|
|
|
|
/{slugify(sp.name)} ↗
|
|
|
|
|
|
</a>
|
2026-05-29 17:29:26 +02:00
|
|
|
|
<button
|
security: add Trivy container scan to Docker publish workflow (#399)
* feat(admin): add backup manager UI with 1-click restore
* feat(admin): collapse older backup entries by default
* fix(theme): isolate admin layout, instant DOM theme updates, and add Light/Dark mode toggle
* feat(admin): replace text controls for Photo Frame and Hero Style with visual preview cards
* fix(admin): define missing CSS rules for button system (.btn, .preset-btn)
* feat(admin): redesign theme settings with segmented mode toggle, accent color swatches, and custom iOS-style toggle cards
* feat(admin): upgrade all settings tabs with visual grid previews, Google SEO snippet preview, and toggle cards
* feat(admin): overhaul Pages Builder UX with slide-over drawer, card hover badges, real-time search, and floating save bar
* fix(admin): remove leftover dangling syntax snippet in PageBuilder.tsx
* fix(admin): add position: relative to subpage-tile so hover overlay is contained inside the tile
* style(admin): replace all emojis with clean Leica-style vector SVG icons
* fix(types): extend IconProps with SVGProps to allow style prop on Leica SVG icons
* feat(admin): add subpage live-preview, privacy analytics, and asset protection settings
* fix(typecheck): fix AlbumEntry property access in subpage drawer preview mode
* fix(types): fix ImmichAlbumInfo property names thumbnailAssetId and albumName in PageBuilder.tsx
* fix(types): add protection and watermark optional properties to AppConfig interface
* feat(settings): add toggle for disabling analytics tracking (defaults to enabled)
* feat(ux): show status banner when analytics tracking is disabled + add live sync indicator
* feat(watermark): render customizable watermark overlay on Lightbox images + rename section to Portfolio Features & Modules
* fix(types): import type LightboxWatermark in PhotoGrid.tsx
* fix(types): destructure watermark prop in PhotoGrid function signature
* feat(seo): add configurable subpage title template and metadata descriptions
* feat(seo): add generateMetadata to about page for title template support
* feat(proofing): add client proofing, favorites selection, compact URL bitmasking, and export modal
* feat(essay): add Photo Essay storytelling mode with RSC markdown parser and fullbleed/pair layouts
* feat(subpages): add page enabled toggle to deactivate subpages without deleting them
* feat(admin): add visual essay block builder for photo essay storytelling pages
* security: add Trivy container scan to Docker publish workflow
Scans the built image for CRITICAL/HIGH CVEs on every release and
uploads results to the GitHub Security tab. Non-blocking for now
(exit-code 0) so releases aren't gated on findings. Also documents
the scan and existing Dependabot coverage in the README.
---------
Co-authored-by: ralksta <ralksta@users.noreply.github.com>
2026-08-07 11:08:04 +02:00
|
|
|
|
className="admin-btn-icon"
|
|
|
|
|
|
onClick={() => setExpandedSubpage(null)}
|
|
|
|
|
|
title="Close drawer"
|
2026-05-29 17:29:26 +02:00
|
|
|
|
>
|
security: add Trivy container scan to Docker publish workflow (#399)
* feat(admin): add backup manager UI with 1-click restore
* feat(admin): collapse older backup entries by default
* fix(theme): isolate admin layout, instant DOM theme updates, and add Light/Dark mode toggle
* feat(admin): replace text controls for Photo Frame and Hero Style with visual preview cards
* fix(admin): define missing CSS rules for button system (.btn, .preset-btn)
* feat(admin): redesign theme settings with segmented mode toggle, accent color swatches, and custom iOS-style toggle cards
* feat(admin): upgrade all settings tabs with visual grid previews, Google SEO snippet preview, and toggle cards
* feat(admin): overhaul Pages Builder UX with slide-over drawer, card hover badges, real-time search, and floating save bar
* fix(admin): remove leftover dangling syntax snippet in PageBuilder.tsx
* fix(admin): add position: relative to subpage-tile so hover overlay is contained inside the tile
* style(admin): replace all emojis with clean Leica-style vector SVG icons
* fix(types): extend IconProps with SVGProps to allow style prop on Leica SVG icons
* feat(admin): add subpage live-preview, privacy analytics, and asset protection settings
* fix(typecheck): fix AlbumEntry property access in subpage drawer preview mode
* fix(types): fix ImmichAlbumInfo property names thumbnailAssetId and albumName in PageBuilder.tsx
* fix(types): add protection and watermark optional properties to AppConfig interface
* feat(settings): add toggle for disabling analytics tracking (defaults to enabled)
* feat(ux): show status banner when analytics tracking is disabled + add live sync indicator
* feat(watermark): render customizable watermark overlay on Lightbox images + rename section to Portfolio Features & Modules
* fix(types): import type LightboxWatermark in PhotoGrid.tsx
* fix(types): destructure watermark prop in PhotoGrid function signature
* feat(seo): add configurable subpage title template and metadata descriptions
* feat(seo): add generateMetadata to about page for title template support
* feat(proofing): add client proofing, favorites selection, compact URL bitmasking, and export modal
* feat(essay): add Photo Essay storytelling mode with RSC markdown parser and fullbleed/pair layouts
* feat(subpages): add page enabled toggle to deactivate subpages without deleting them
* feat(admin): add visual essay block builder for photo essay storytelling pages
* security: add Trivy container scan to Docker publish workflow
Scans the built image for CRITICAL/HIGH CVEs on every release and
uploads results to the GitHub Security tab. Non-blocking for now
(exit-code 0) so releases aren't gated on findings. Also documents
the scan and existing Dependabot coverage in the README.
---------
Co-authored-by: ralksta <ralksta@users.noreply.github.com>
2026-08-07 11:08:04 +02:00
|
|
|
|
✕
|
2026-05-29 17:29:26 +02:00
|
|
|
|
</button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
security: add Trivy container scan to Docker publish workflow (#399)
* feat(admin): add backup manager UI with 1-click restore
* feat(admin): collapse older backup entries by default
* fix(theme): isolate admin layout, instant DOM theme updates, and add Light/Dark mode toggle
* feat(admin): replace text controls for Photo Frame and Hero Style with visual preview cards
* fix(admin): define missing CSS rules for button system (.btn, .preset-btn)
* feat(admin): redesign theme settings with segmented mode toggle, accent color swatches, and custom iOS-style toggle cards
* feat(admin): upgrade all settings tabs with visual grid previews, Google SEO snippet preview, and toggle cards
* feat(admin): overhaul Pages Builder UX with slide-over drawer, card hover badges, real-time search, and floating save bar
* fix(admin): remove leftover dangling syntax snippet in PageBuilder.tsx
* fix(admin): add position: relative to subpage-tile so hover overlay is contained inside the tile
* style(admin): replace all emojis with clean Leica-style vector SVG icons
* fix(types): extend IconProps with SVGProps to allow style prop on Leica SVG icons
* feat(admin): add subpage live-preview, privacy analytics, and asset protection settings
* fix(typecheck): fix AlbumEntry property access in subpage drawer preview mode
* fix(types): fix ImmichAlbumInfo property names thumbnailAssetId and albumName in PageBuilder.tsx
* fix(types): add protection and watermark optional properties to AppConfig interface
* feat(settings): add toggle for disabling analytics tracking (defaults to enabled)
* feat(ux): show status banner when analytics tracking is disabled + add live sync indicator
* feat(watermark): render customizable watermark overlay on Lightbox images + rename section to Portfolio Features & Modules
* fix(types): import type LightboxWatermark in PhotoGrid.tsx
* fix(types): destructure watermark prop in PhotoGrid function signature
* feat(seo): add configurable subpage title template and metadata descriptions
* feat(seo): add generateMetadata to about page for title template support
* feat(proofing): add client proofing, favorites selection, compact URL bitmasking, and export modal
* feat(essay): add Photo Essay storytelling mode with RSC markdown parser and fullbleed/pair layouts
* feat(subpages): add page enabled toggle to deactivate subpages without deleting them
* feat(admin): add visual essay block builder for photo essay storytelling pages
* security: add Trivy container scan to Docker publish workflow
Scans the built image for CRITICAL/HIGH CVEs on every release and
uploads results to the GitHub Security tab. Non-blocking for now
(exit-code 0) so releases aren't gated on findings. Also documents
the scan and existing Dependabot coverage in the README.
---------
Co-authored-by: ralksta <ralksta@users.noreply.github.com>
2026-08-07 11:08:04 +02:00
|
|
|
|
|
|
|
|
|
|
<div className="subpage-drawer-body">
|
|
|
|
|
|
{drawerMode === 'preview' ? (
|
|
|
|
|
|
<div className="drawer-live-preview-panel">
|
|
|
|
|
|
<div className="preview-hero-banner">
|
|
|
|
|
|
<h2 className="preview-hero-title">{sp.title || sp.name || 'Untitled Page'}</h2>
|
|
|
|
|
|
{sp.subtitle && <p className="preview-hero-sub">{sp.subtitle}</p>}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
{sp.sections && sp.sections.length > 0 ? (
|
|
|
|
|
|
sp.sections.map((sec, sIdx) => (
|
|
|
|
|
|
<div key={sIdx} className="preview-section-group">
|
|
|
|
|
|
<h3 className="preview-section-title">{sec.title || 'Untitled Section'}</h3>
|
|
|
|
|
|
{sec.description && <p className="preview-section-desc">{sec.description}</p>}
|
|
|
|
|
|
<div className="preview-albums-grid">
|
|
|
|
|
|
{sec.albums.map((alb, aIdx) => {
|
|
|
|
|
|
const immichAlb = immichAlbums.find((a) => a.id === alb.id);
|
|
|
|
|
|
const thumb = alb.heroImage || immichAlb?.thumbnailAssetId;
|
|
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
|
<div key={aIdx} className="preview-album-tile">
|
|
|
|
|
|
<div className="preview-album-cover">
|
|
|
|
|
|
{thumb ? (
|
|
|
|
|
|
<img src={`/api/admin/thumbnail/${thumb}`} alt="" />
|
|
|
|
|
|
) : (
|
|
|
|
|
|
<div className="subpage-tile-placeholder"><Icons.Folder /></div>
|
|
|
|
|
|
)}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<span className="preview-album-title">
|
|
|
|
|
|
{alb.title || immichAlb?.albumName || alb.id}
|
|
|
|
|
|
</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
);
|
|
|
|
|
|
})}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
))
|
|
|
|
|
|
) : (
|
|
|
|
|
|
<div className="preview-albums-grid">
|
|
|
|
|
|
{sp.albums.map((alb, aIdx) => {
|
|
|
|
|
|
const immichAlb = immichAlbums.find((a) => a.id === alb.id);
|
|
|
|
|
|
const thumb = alb.heroImage || immichAlb?.thumbnailAssetId;
|
|
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
|
<div key={aIdx} className="preview-album-tile">
|
|
|
|
|
|
<div className="preview-album-cover">
|
|
|
|
|
|
{thumb ? (
|
|
|
|
|
|
<img src={`/api/admin/thumbnail/${thumb}`} alt="" />
|
|
|
|
|
|
) : (
|
|
|
|
|
|
<div className="subpage-tile-placeholder"><Icons.Folder /></div>
|
|
|
|
|
|
)}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<span className="preview-album-title">
|
|
|
|
|
|
{alb.title || immichAlb?.albumName || alb.id}
|
|
|
|
|
|
</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
);
|
|
|
|
|
|
})}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
)}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
) : (
|
|
|
|
|
|
<>
|
|
|
|
|
|
<div className="subpage-drawer-section">
|
|
|
|
|
|
<div className="admin-field">
|
|
|
|
|
|
<label>Page Name (URL Identifier)</label>
|
|
|
|
|
|
<div className="input-slug-wrapper">
|
2026-05-29 17:29:26 +02:00
|
|
|
|
<input
|
security: add Trivy container scan to Docker publish workflow (#399)
* feat(admin): add backup manager UI with 1-click restore
* feat(admin): collapse older backup entries by default
* fix(theme): isolate admin layout, instant DOM theme updates, and add Light/Dark mode toggle
* feat(admin): replace text controls for Photo Frame and Hero Style with visual preview cards
* fix(admin): define missing CSS rules for button system (.btn, .preset-btn)
* feat(admin): redesign theme settings with segmented mode toggle, accent color swatches, and custom iOS-style toggle cards
* feat(admin): upgrade all settings tabs with visual grid previews, Google SEO snippet preview, and toggle cards
* feat(admin): overhaul Pages Builder UX with slide-over drawer, card hover badges, real-time search, and floating save bar
* fix(admin): remove leftover dangling syntax snippet in PageBuilder.tsx
* fix(admin): add position: relative to subpage-tile so hover overlay is contained inside the tile
* style(admin): replace all emojis with clean Leica-style vector SVG icons
* fix(types): extend IconProps with SVGProps to allow style prop on Leica SVG icons
* feat(admin): add subpage live-preview, privacy analytics, and asset protection settings
* fix(typecheck): fix AlbumEntry property access in subpage drawer preview mode
* fix(types): fix ImmichAlbumInfo property names thumbnailAssetId and albumName in PageBuilder.tsx
* fix(types): add protection and watermark optional properties to AppConfig interface
* feat(settings): add toggle for disabling analytics tracking (defaults to enabled)
* feat(ux): show status banner when analytics tracking is disabled + add live sync indicator
* feat(watermark): render customizable watermark overlay on Lightbox images + rename section to Portfolio Features & Modules
* fix(types): import type LightboxWatermark in PhotoGrid.tsx
* fix(types): destructure watermark prop in PhotoGrid function signature
* feat(seo): add configurable subpage title template and metadata descriptions
* feat(seo): add generateMetadata to about page for title template support
* feat(proofing): add client proofing, favorites selection, compact URL bitmasking, and export modal
* feat(essay): add Photo Essay storytelling mode with RSC markdown parser and fullbleed/pair layouts
* feat(subpages): add page enabled toggle to deactivate subpages without deleting them
* feat(admin): add visual essay block builder for photo essay storytelling pages
* security: add Trivy container scan to Docker publish workflow
Scans the built image for CRITICAL/HIGH CVEs on every release and
uploads results to the GitHub Security tab. Non-blocking for now
(exit-code 0) so releases aren't gated on findings. Also documents
the scan and existing Dependabot coverage in the README.
---------
Co-authored-by: ralksta <ralksta@users.noreply.github.com>
2026-08-07 11:08:04 +02:00
|
|
|
|
className="subpage-name-input"
|
|
|
|
|
|
value={sp.name}
|
|
|
|
|
|
onChange={(e) => updateSubpage(spIndex, { name: e.target.value })}
|
|
|
|
|
|
placeholder="e.g. dubai, travel, weddings"
|
|
|
|
|
|
/>
|
|
|
|
|
|
<span className="input-slug-preview">/{slugify(sp.name)}</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
{/* Subpage metadata */}
|
|
|
|
|
|
<div className="subpage-drawer-section">
|
|
|
|
|
|
<div className="admin-field-row">
|
|
|
|
|
|
<div className="admin-field">
|
|
|
|
|
|
<label>Display Title (optional)</label>
|
|
|
|
|
|
<input
|
|
|
|
|
|
value={sp.title || ''}
|
2026-05-29 17:29:26 +02:00
|
|
|
|
onChange={(e) =>
|
security: add Trivy container scan to Docker publish workflow (#399)
* feat(admin): add backup manager UI with 1-click restore
* feat(admin): collapse older backup entries by default
* fix(theme): isolate admin layout, instant DOM theme updates, and add Light/Dark mode toggle
* feat(admin): replace text controls for Photo Frame and Hero Style with visual preview cards
* fix(admin): define missing CSS rules for button system (.btn, .preset-btn)
* feat(admin): redesign theme settings with segmented mode toggle, accent color swatches, and custom iOS-style toggle cards
* feat(admin): upgrade all settings tabs with visual grid previews, Google SEO snippet preview, and toggle cards
* feat(admin): overhaul Pages Builder UX with slide-over drawer, card hover badges, real-time search, and floating save bar
* fix(admin): remove leftover dangling syntax snippet in PageBuilder.tsx
* fix(admin): add position: relative to subpage-tile so hover overlay is contained inside the tile
* style(admin): replace all emojis with clean Leica-style vector SVG icons
* fix(types): extend IconProps with SVGProps to allow style prop on Leica SVG icons
* feat(admin): add subpage live-preview, privacy analytics, and asset protection settings
* fix(typecheck): fix AlbumEntry property access in subpage drawer preview mode
* fix(types): fix ImmichAlbumInfo property names thumbnailAssetId and albumName in PageBuilder.tsx
* fix(types): add protection and watermark optional properties to AppConfig interface
* feat(settings): add toggle for disabling analytics tracking (defaults to enabled)
* feat(ux): show status banner when analytics tracking is disabled + add live sync indicator
* feat(watermark): render customizable watermark overlay on Lightbox images + rename section to Portfolio Features & Modules
* fix(types): import type LightboxWatermark in PhotoGrid.tsx
* fix(types): destructure watermark prop in PhotoGrid function signature
* feat(seo): add configurable subpage title template and metadata descriptions
* feat(seo): add generateMetadata to about page for title template support
* feat(proofing): add client proofing, favorites selection, compact URL bitmasking, and export modal
* feat(essay): add Photo Essay storytelling mode with RSC markdown parser and fullbleed/pair layouts
* feat(subpages): add page enabled toggle to deactivate subpages without deleting them
* feat(admin): add visual essay block builder for photo essay storytelling pages
* security: add Trivy container scan to Docker publish workflow
Scans the built image for CRITICAL/HIGH CVEs on every release and
uploads results to the GitHub Security tab. Non-blocking for now
(exit-code 0) so releases aren't gated on findings. Also documents
the scan and existing Dependabot coverage in the README.
---------
Co-authored-by: ralksta <ralksta@users.noreply.github.com>
2026-08-07 11:08:04 +02:00
|
|
|
|
updateSubpage(spIndex, { title: e.target.value || undefined })
|
2026-05-29 17:29:26 +02:00
|
|
|
|
}
|
security: add Trivy container scan to Docker publish workflow (#399)
* feat(admin): add backup manager UI with 1-click restore
* feat(admin): collapse older backup entries by default
* fix(theme): isolate admin layout, instant DOM theme updates, and add Light/Dark mode toggle
* feat(admin): replace text controls for Photo Frame and Hero Style with visual preview cards
* fix(admin): define missing CSS rules for button system (.btn, .preset-btn)
* feat(admin): redesign theme settings with segmented mode toggle, accent color swatches, and custom iOS-style toggle cards
* feat(admin): upgrade all settings tabs with visual grid previews, Google SEO snippet preview, and toggle cards
* feat(admin): overhaul Pages Builder UX with slide-over drawer, card hover badges, real-time search, and floating save bar
* fix(admin): remove leftover dangling syntax snippet in PageBuilder.tsx
* fix(admin): add position: relative to subpage-tile so hover overlay is contained inside the tile
* style(admin): replace all emojis with clean Leica-style vector SVG icons
* fix(types): extend IconProps with SVGProps to allow style prop on Leica SVG icons
* feat(admin): add subpage live-preview, privacy analytics, and asset protection settings
* fix(typecheck): fix AlbumEntry property access in subpage drawer preview mode
* fix(types): fix ImmichAlbumInfo property names thumbnailAssetId and albumName in PageBuilder.tsx
* fix(types): add protection and watermark optional properties to AppConfig interface
* feat(settings): add toggle for disabling analytics tracking (defaults to enabled)
* feat(ux): show status banner when analytics tracking is disabled + add live sync indicator
* feat(watermark): render customizable watermark overlay on Lightbox images + rename section to Portfolio Features & Modules
* fix(types): import type LightboxWatermark in PhotoGrid.tsx
* fix(types): destructure watermark prop in PhotoGrid function signature
* feat(seo): add configurable subpage title template and metadata descriptions
* feat(seo): add generateMetadata to about page for title template support
* feat(proofing): add client proofing, favorites selection, compact URL bitmasking, and export modal
* feat(essay): add Photo Essay storytelling mode with RSC markdown parser and fullbleed/pair layouts
* feat(subpages): add page enabled toggle to deactivate subpages without deleting them
* feat(admin): add visual essay block builder for photo essay storytelling pages
* security: add Trivy container scan to Docker publish workflow
Scans the built image for CRITICAL/HIGH CVEs on every release and
uploads results to the GitHub Security tab. Non-blocking for now
(exit-code 0) so releases aren't gated on findings. Also documents
the scan and existing Dependabot coverage in the README.
---------
Co-authored-by: ralksta <ralksta@users.noreply.github.com>
2026-08-07 11:08:04 +02:00
|
|
|
|
placeholder="Display title (defaults to name)"
|
2026-05-29 17:29:26 +02:00
|
|
|
|
/>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div className="admin-field">
|
security: add Trivy container scan to Docker publish workflow (#399)
* feat(admin): add backup manager UI with 1-click restore
* feat(admin): collapse older backup entries by default
* fix(theme): isolate admin layout, instant DOM theme updates, and add Light/Dark mode toggle
* feat(admin): replace text controls for Photo Frame and Hero Style with visual preview cards
* fix(admin): define missing CSS rules for button system (.btn, .preset-btn)
* feat(admin): redesign theme settings with segmented mode toggle, accent color swatches, and custom iOS-style toggle cards
* feat(admin): upgrade all settings tabs with visual grid previews, Google SEO snippet preview, and toggle cards
* feat(admin): overhaul Pages Builder UX with slide-over drawer, card hover badges, real-time search, and floating save bar
* fix(admin): remove leftover dangling syntax snippet in PageBuilder.tsx
* fix(admin): add position: relative to subpage-tile so hover overlay is contained inside the tile
* style(admin): replace all emojis with clean Leica-style vector SVG icons
* fix(types): extend IconProps with SVGProps to allow style prop on Leica SVG icons
* feat(admin): add subpage live-preview, privacy analytics, and asset protection settings
* fix(typecheck): fix AlbumEntry property access in subpage drawer preview mode
* fix(types): fix ImmichAlbumInfo property names thumbnailAssetId and albumName in PageBuilder.tsx
* fix(types): add protection and watermark optional properties to AppConfig interface
* feat(settings): add toggle for disabling analytics tracking (defaults to enabled)
* feat(ux): show status banner when analytics tracking is disabled + add live sync indicator
* feat(watermark): render customizable watermark overlay on Lightbox images + rename section to Portfolio Features & Modules
* fix(types): import type LightboxWatermark in PhotoGrid.tsx
* fix(types): destructure watermark prop in PhotoGrid function signature
* feat(seo): add configurable subpage title template and metadata descriptions
* feat(seo): add generateMetadata to about page for title template support
* feat(proofing): add client proofing, favorites selection, compact URL bitmasking, and export modal
* feat(essay): add Photo Essay storytelling mode with RSC markdown parser and fullbleed/pair layouts
* feat(subpages): add page enabled toggle to deactivate subpages without deleting them
* feat(admin): add visual essay block builder for photo essay storytelling pages
* security: add Trivy container scan to Docker publish workflow
Scans the built image for CRITICAL/HIGH CVEs on every release and
uploads results to the GitHub Security tab. Non-blocking for now
(exit-code 0) so releases aren't gated on findings. Also documents
the scan and existing Dependabot coverage in the README.
---------
Co-authored-by: ralksta <ralksta@users.noreply.github.com>
2026-08-07 11:08:04 +02:00
|
|
|
|
<label>Subtitle</label>
|
2026-05-29 17:29:26 +02:00
|
|
|
|
<input
|
security: add Trivy container scan to Docker publish workflow (#399)
* feat(admin): add backup manager UI with 1-click restore
* feat(admin): collapse older backup entries by default
* fix(theme): isolate admin layout, instant DOM theme updates, and add Light/Dark mode toggle
* feat(admin): replace text controls for Photo Frame and Hero Style with visual preview cards
* fix(admin): define missing CSS rules for button system (.btn, .preset-btn)
* feat(admin): redesign theme settings with segmented mode toggle, accent color swatches, and custom iOS-style toggle cards
* feat(admin): upgrade all settings tabs with visual grid previews, Google SEO snippet preview, and toggle cards
* feat(admin): overhaul Pages Builder UX with slide-over drawer, card hover badges, real-time search, and floating save bar
* fix(admin): remove leftover dangling syntax snippet in PageBuilder.tsx
* fix(admin): add position: relative to subpage-tile so hover overlay is contained inside the tile
* style(admin): replace all emojis with clean Leica-style vector SVG icons
* fix(types): extend IconProps with SVGProps to allow style prop on Leica SVG icons
* feat(admin): add subpage live-preview, privacy analytics, and asset protection settings
* fix(typecheck): fix AlbumEntry property access in subpage drawer preview mode
* fix(types): fix ImmichAlbumInfo property names thumbnailAssetId and albumName in PageBuilder.tsx
* fix(types): add protection and watermark optional properties to AppConfig interface
* feat(settings): add toggle for disabling analytics tracking (defaults to enabled)
* feat(ux): show status banner when analytics tracking is disabled + add live sync indicator
* feat(watermark): render customizable watermark overlay on Lightbox images + rename section to Portfolio Features & Modules
* fix(types): import type LightboxWatermark in PhotoGrid.tsx
* fix(types): destructure watermark prop in PhotoGrid function signature
* feat(seo): add configurable subpage title template and metadata descriptions
* feat(seo): add generateMetadata to about page for title template support
* feat(proofing): add client proofing, favorites selection, compact URL bitmasking, and export modal
* feat(essay): add Photo Essay storytelling mode with RSC markdown parser and fullbleed/pair layouts
* feat(subpages): add page enabled toggle to deactivate subpages without deleting them
* feat(admin): add visual essay block builder for photo essay storytelling pages
* security: add Trivy container scan to Docker publish workflow
Scans the built image for CRITICAL/HIGH CVEs on every release and
uploads results to the GitHub Security tab. Non-blocking for now
(exit-code 0) so releases aren't gated on findings. Also documents
the scan and existing Dependabot coverage in the README.
---------
Co-authored-by: ralksta <ralksta@users.noreply.github.com>
2026-08-07 11:08:04 +02:00
|
|
|
|
value={sp.subtitle || ''}
|
2026-05-29 17:29:26 +02:00
|
|
|
|
onChange={(e) =>
|
security: add Trivy container scan to Docker publish workflow (#399)
* feat(admin): add backup manager UI with 1-click restore
* feat(admin): collapse older backup entries by default
* fix(theme): isolate admin layout, instant DOM theme updates, and add Light/Dark mode toggle
* feat(admin): replace text controls for Photo Frame and Hero Style with visual preview cards
* fix(admin): define missing CSS rules for button system (.btn, .preset-btn)
* feat(admin): redesign theme settings with segmented mode toggle, accent color swatches, and custom iOS-style toggle cards
* feat(admin): upgrade all settings tabs with visual grid previews, Google SEO snippet preview, and toggle cards
* feat(admin): overhaul Pages Builder UX with slide-over drawer, card hover badges, real-time search, and floating save bar
* fix(admin): remove leftover dangling syntax snippet in PageBuilder.tsx
* fix(admin): add position: relative to subpage-tile so hover overlay is contained inside the tile
* style(admin): replace all emojis with clean Leica-style vector SVG icons
* fix(types): extend IconProps with SVGProps to allow style prop on Leica SVG icons
* feat(admin): add subpage live-preview, privacy analytics, and asset protection settings
* fix(typecheck): fix AlbumEntry property access in subpage drawer preview mode
* fix(types): fix ImmichAlbumInfo property names thumbnailAssetId and albumName in PageBuilder.tsx
* fix(types): add protection and watermark optional properties to AppConfig interface
* feat(settings): add toggle for disabling analytics tracking (defaults to enabled)
* feat(ux): show status banner when analytics tracking is disabled + add live sync indicator
* feat(watermark): render customizable watermark overlay on Lightbox images + rename section to Portfolio Features & Modules
* fix(types): import type LightboxWatermark in PhotoGrid.tsx
* fix(types): destructure watermark prop in PhotoGrid function signature
* feat(seo): add configurable subpage title template and metadata descriptions
* feat(seo): add generateMetadata to about page for title template support
* feat(proofing): add client proofing, favorites selection, compact URL bitmasking, and export modal
* feat(essay): add Photo Essay storytelling mode with RSC markdown parser and fullbleed/pair layouts
* feat(subpages): add page enabled toggle to deactivate subpages without deleting them
* feat(admin): add visual essay block builder for photo essay storytelling pages
* security: add Trivy container scan to Docker publish workflow
Scans the built image for CRITICAL/HIGH CVEs on every release and
uploads results to the GitHub Security tab. Non-blocking for now
(exit-code 0) so releases aren't gated on findings. Also documents
the scan and existing Dependabot coverage in the README.
---------
Co-authored-by: ralksta <ralksta@users.noreply.github.com>
2026-08-07 11:08:04 +02:00
|
|
|
|
updateSubpage(spIndex, { subtitle: e.target.value || undefined })
|
2026-05-29 17:29:26 +02:00
|
|
|
|
}
|
security: add Trivy container scan to Docker publish workflow (#399)
* feat(admin): add backup manager UI with 1-click restore
* feat(admin): collapse older backup entries by default
* fix(theme): isolate admin layout, instant DOM theme updates, and add Light/Dark mode toggle
* feat(admin): replace text controls for Photo Frame and Hero Style with visual preview cards
* fix(admin): define missing CSS rules for button system (.btn, .preset-btn)
* feat(admin): redesign theme settings with segmented mode toggle, accent color swatches, and custom iOS-style toggle cards
* feat(admin): upgrade all settings tabs with visual grid previews, Google SEO snippet preview, and toggle cards
* feat(admin): overhaul Pages Builder UX with slide-over drawer, card hover badges, real-time search, and floating save bar
* fix(admin): remove leftover dangling syntax snippet in PageBuilder.tsx
* fix(admin): add position: relative to subpage-tile so hover overlay is contained inside the tile
* style(admin): replace all emojis with clean Leica-style vector SVG icons
* fix(types): extend IconProps with SVGProps to allow style prop on Leica SVG icons
* feat(admin): add subpage live-preview, privacy analytics, and asset protection settings
* fix(typecheck): fix AlbumEntry property access in subpage drawer preview mode
* fix(types): fix ImmichAlbumInfo property names thumbnailAssetId and albumName in PageBuilder.tsx
* fix(types): add protection and watermark optional properties to AppConfig interface
* feat(settings): add toggle for disabling analytics tracking (defaults to enabled)
* feat(ux): show status banner when analytics tracking is disabled + add live sync indicator
* feat(watermark): render customizable watermark overlay on Lightbox images + rename section to Portfolio Features & Modules
* fix(types): import type LightboxWatermark in PhotoGrid.tsx
* fix(types): destructure watermark prop in PhotoGrid function signature
* feat(seo): add configurable subpage title template and metadata descriptions
* feat(seo): add generateMetadata to about page for title template support
* feat(proofing): add client proofing, favorites selection, compact URL bitmasking, and export modal
* feat(essay): add Photo Essay storytelling mode with RSC markdown parser and fullbleed/pair layouts
* feat(subpages): add page enabled toggle to deactivate subpages without deleting them
* feat(admin): add visual essay block builder for photo essay storytelling pages
* security: add Trivy container scan to Docker publish workflow
Scans the built image for CRITICAL/HIGH CVEs on every release and
uploads results to the GitHub Security tab. Non-blocking for now
(exit-code 0) so releases aren't gated on findings. Also documents
the scan and existing Dependabot coverage in the README.
---------
Co-authored-by: ralksta <ralksta@users.noreply.github.com>
2026-08-07 11:08:04 +02:00
|
|
|
|
placeholder="Subtitle text"
|
2026-05-29 17:29:26 +02:00
|
|
|
|
/>
|
|
|
|
|
|
</div>
|
security: add Trivy container scan to Docker publish workflow (#399)
* feat(admin): add backup manager UI with 1-click restore
* feat(admin): collapse older backup entries by default
* fix(theme): isolate admin layout, instant DOM theme updates, and add Light/Dark mode toggle
* feat(admin): replace text controls for Photo Frame and Hero Style with visual preview cards
* fix(admin): define missing CSS rules for button system (.btn, .preset-btn)
* feat(admin): redesign theme settings with segmented mode toggle, accent color swatches, and custom iOS-style toggle cards
* feat(admin): upgrade all settings tabs with visual grid previews, Google SEO snippet preview, and toggle cards
* feat(admin): overhaul Pages Builder UX with slide-over drawer, card hover badges, real-time search, and floating save bar
* fix(admin): remove leftover dangling syntax snippet in PageBuilder.tsx
* fix(admin): add position: relative to subpage-tile so hover overlay is contained inside the tile
* style(admin): replace all emojis with clean Leica-style vector SVG icons
* fix(types): extend IconProps with SVGProps to allow style prop on Leica SVG icons
* feat(admin): add subpage live-preview, privacy analytics, and asset protection settings
* fix(typecheck): fix AlbumEntry property access in subpage drawer preview mode
* fix(types): fix ImmichAlbumInfo property names thumbnailAssetId and albumName in PageBuilder.tsx
* fix(types): add protection and watermark optional properties to AppConfig interface
* feat(settings): add toggle for disabling analytics tracking (defaults to enabled)
* feat(ux): show status banner when analytics tracking is disabled + add live sync indicator
* feat(watermark): render customizable watermark overlay on Lightbox images + rename section to Portfolio Features & Modules
* fix(types): import type LightboxWatermark in PhotoGrid.tsx
* fix(types): destructure watermark prop in PhotoGrid function signature
* feat(seo): add configurable subpage title template and metadata descriptions
* feat(seo): add generateMetadata to about page for title template support
* feat(proofing): add client proofing, favorites selection, compact URL bitmasking, and export modal
* feat(essay): add Photo Essay storytelling mode with RSC markdown parser and fullbleed/pair layouts
* feat(subpages): add page enabled toggle to deactivate subpages without deleting them
* feat(admin): add visual essay block builder for photo essay storytelling pages
* security: add Trivy container scan to Docker publish workflow
Scans the built image for CRITICAL/HIGH CVEs on every release and
uploads results to the GitHub Security tab. Non-blocking for now
(exit-code 0) so releases aren't gated on findings. Also documents
the scan and existing Dependabot coverage in the README.
---------
Co-authored-by: ralksta <ralksta@users.noreply.github.com>
2026-08-07 11:08:04 +02:00
|
|
|
|
</div>
|
|
|
|
|
|
<div className="admin-field">
|
|
|
|
|
|
<label>Password Protection (optional)</label>
|
|
|
|
|
|
<div className="password-input-wrapper">
|
|
|
|
|
|
<span className="password-icon"><Icons.Lock /></span>
|
|
|
|
|
|
<input
|
|
|
|
|
|
type="password"
|
|
|
|
|
|
value={sp.password || ''}
|
|
|
|
|
|
onChange={(e) =>
|
|
|
|
|
|
updateSubpage(spIndex, { password: e.target.value || undefined })
|
|
|
|
|
|
}
|
|
|
|
|
|
placeholder="Leave empty for public access"
|
|
|
|
|
|
/>
|
2026-05-29 17:29:26 +02:00
|
|
|
|
</div>
|
security: add Trivy container scan to Docker publish workflow (#399)
* feat(admin): add backup manager UI with 1-click restore
* feat(admin): collapse older backup entries by default
* fix(theme): isolate admin layout, instant DOM theme updates, and add Light/Dark mode toggle
* feat(admin): replace text controls for Photo Frame and Hero Style with visual preview cards
* fix(admin): define missing CSS rules for button system (.btn, .preset-btn)
* feat(admin): redesign theme settings with segmented mode toggle, accent color swatches, and custom iOS-style toggle cards
* feat(admin): upgrade all settings tabs with visual grid previews, Google SEO snippet preview, and toggle cards
* feat(admin): overhaul Pages Builder UX with slide-over drawer, card hover badges, real-time search, and floating save bar
* fix(admin): remove leftover dangling syntax snippet in PageBuilder.tsx
* fix(admin): add position: relative to subpage-tile so hover overlay is contained inside the tile
* style(admin): replace all emojis with clean Leica-style vector SVG icons
* fix(types): extend IconProps with SVGProps to allow style prop on Leica SVG icons
* feat(admin): add subpage live-preview, privacy analytics, and asset protection settings
* fix(typecheck): fix AlbumEntry property access in subpage drawer preview mode
* fix(types): fix ImmichAlbumInfo property names thumbnailAssetId and albumName in PageBuilder.tsx
* fix(types): add protection and watermark optional properties to AppConfig interface
* feat(settings): add toggle for disabling analytics tracking (defaults to enabled)
* feat(ux): show status banner when analytics tracking is disabled + add live sync indicator
* feat(watermark): render customizable watermark overlay on Lightbox images + rename section to Portfolio Features & Modules
* fix(types): import type LightboxWatermark in PhotoGrid.tsx
* fix(types): destructure watermark prop in PhotoGrid function signature
* feat(seo): add configurable subpage title template and metadata descriptions
* feat(seo): add generateMetadata to about page for title template support
* feat(proofing): add client proofing, favorites selection, compact URL bitmasking, and export modal
* feat(essay): add Photo Essay storytelling mode with RSC markdown parser and fullbleed/pair layouts
* feat(subpages): add page enabled toggle to deactivate subpages without deleting them
* feat(admin): add visual essay block builder for photo essay storytelling pages
* security: add Trivy container scan to Docker publish workflow
Scans the built image for CRITICAL/HIGH CVEs on every release and
uploads results to the GitHub Security tab. Non-blocking for now
(exit-code 0) so releases aren't gated on findings. Also documents
the scan and existing Dependabot coverage in the README.
---------
Co-authored-by: ralksta <ralksta@users.noreply.github.com>
2026-08-07 11:08:04 +02:00
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div className="admin-field" style={{ marginTop: '1rem' }}>
|
|
|
|
|
|
<label>Page Visibility Status</label>
|
2026-05-29 17:29:26 +02:00
|
|
|
|
<button
|
security: add Trivy container scan to Docker publish workflow (#399)
* feat(admin): add backup manager UI with 1-click restore
* feat(admin): collapse older backup entries by default
* fix(theme): isolate admin layout, instant DOM theme updates, and add Light/Dark mode toggle
* feat(admin): replace text controls for Photo Frame and Hero Style with visual preview cards
* fix(admin): define missing CSS rules for button system (.btn, .preset-btn)
* feat(admin): redesign theme settings with segmented mode toggle, accent color swatches, and custom iOS-style toggle cards
* feat(admin): upgrade all settings tabs with visual grid previews, Google SEO snippet preview, and toggle cards
* feat(admin): overhaul Pages Builder UX with slide-over drawer, card hover badges, real-time search, and floating save bar
* fix(admin): remove leftover dangling syntax snippet in PageBuilder.tsx
* fix(admin): add position: relative to subpage-tile so hover overlay is contained inside the tile
* style(admin): replace all emojis with clean Leica-style vector SVG icons
* fix(types): extend IconProps with SVGProps to allow style prop on Leica SVG icons
* feat(admin): add subpage live-preview, privacy analytics, and asset protection settings
* fix(typecheck): fix AlbumEntry property access in subpage drawer preview mode
* fix(types): fix ImmichAlbumInfo property names thumbnailAssetId and albumName in PageBuilder.tsx
* fix(types): add protection and watermark optional properties to AppConfig interface
* feat(settings): add toggle for disabling analytics tracking (defaults to enabled)
* feat(ux): show status banner when analytics tracking is disabled + add live sync indicator
* feat(watermark): render customizable watermark overlay on Lightbox images + rename section to Portfolio Features & Modules
* fix(types): import type LightboxWatermark in PhotoGrid.tsx
* fix(types): destructure watermark prop in PhotoGrid function signature
* feat(seo): add configurable subpage title template and metadata descriptions
* feat(seo): add generateMetadata to about page for title template support
* feat(proofing): add client proofing, favorites selection, compact URL bitmasking, and export modal
* feat(essay): add Photo Essay storytelling mode with RSC markdown parser and fullbleed/pair layouts
* feat(subpages): add page enabled toggle to deactivate subpages without deleting them
* feat(admin): add visual essay block builder for photo essay storytelling pages
* security: add Trivy container scan to Docker publish workflow
Scans the built image for CRITICAL/HIGH CVEs on every release and
uploads results to the GitHub Security tab. Non-blocking for now
(exit-code 0) so releases aren't gated on findings. Also documents
the scan and existing Dependabot coverage in the README.
---------
Co-authored-by: ralksta <ralksta@users.noreply.github.com>
2026-08-07 11:08:04 +02:00
|
|
|
|
type="button"
|
|
|
|
|
|
className={`admin-toggle-card ${sp.enabled !== false ? 'active' : ''}`}
|
|
|
|
|
|
onClick={() => updateSubpage(spIndex, { enabled: sp.enabled === false })}
|
|
|
|
|
|
style={{ padding: '10px 14px', width: '100%', display: 'flex', alignItems: 'center', justifyContent: 'space-between', borderRadius: '8px', cursor: 'pointer' }}
|
2026-05-29 17:29:26 +02:00
|
|
|
|
>
|
security: add Trivy container scan to Docker publish workflow (#399)
* feat(admin): add backup manager UI with 1-click restore
* feat(admin): collapse older backup entries by default
* fix(theme): isolate admin layout, instant DOM theme updates, and add Light/Dark mode toggle
* feat(admin): replace text controls for Photo Frame and Hero Style with visual preview cards
* fix(admin): define missing CSS rules for button system (.btn, .preset-btn)
* feat(admin): redesign theme settings with segmented mode toggle, accent color swatches, and custom iOS-style toggle cards
* feat(admin): upgrade all settings tabs with visual grid previews, Google SEO snippet preview, and toggle cards
* feat(admin): overhaul Pages Builder UX with slide-over drawer, card hover badges, real-time search, and floating save bar
* fix(admin): remove leftover dangling syntax snippet in PageBuilder.tsx
* fix(admin): add position: relative to subpage-tile so hover overlay is contained inside the tile
* style(admin): replace all emojis with clean Leica-style vector SVG icons
* fix(types): extend IconProps with SVGProps to allow style prop on Leica SVG icons
* feat(admin): add subpage live-preview, privacy analytics, and asset protection settings
* fix(typecheck): fix AlbumEntry property access in subpage drawer preview mode
* fix(types): fix ImmichAlbumInfo property names thumbnailAssetId and albumName in PageBuilder.tsx
* fix(types): add protection and watermark optional properties to AppConfig interface
* feat(settings): add toggle for disabling analytics tracking (defaults to enabled)
* feat(ux): show status banner when analytics tracking is disabled + add live sync indicator
* feat(watermark): render customizable watermark overlay on Lightbox images + rename section to Portfolio Features & Modules
* fix(types): import type LightboxWatermark in PhotoGrid.tsx
* fix(types): destructure watermark prop in PhotoGrid function signature
* feat(seo): add configurable subpage title template and metadata descriptions
* feat(seo): add generateMetadata to about page for title template support
* feat(proofing): add client proofing, favorites selection, compact URL bitmasking, and export modal
* feat(essay): add Photo Essay storytelling mode with RSC markdown parser and fullbleed/pair layouts
* feat(subpages): add page enabled toggle to deactivate subpages without deleting them
* feat(admin): add visual essay block builder for photo essay storytelling pages
* security: add Trivy container scan to Docker publish workflow
Scans the built image for CRITICAL/HIGH CVEs on every release and
uploads results to the GitHub Security tab. Non-blocking for now
(exit-code 0) so releases aren't gated on findings. Also documents
the scan and existing Dependabot coverage in the README.
---------
Co-authored-by: ralksta <ralksta@users.noreply.github.com>
2026-08-07 11:08:04 +02:00
|
|
|
|
<div className="toggle-card-info" style={{ textAlign: 'left' }}>
|
|
|
|
|
|
<span className="toggle-card-title" style={{ fontWeight: 600 }}>
|
|
|
|
|
|
{sp.enabled !== false ? '🟢 Page Active (Published)' : '🔴 Page Disabled (Hidden)'}
|
|
|
|
|
|
</span>
|
|
|
|
|
|
<span className="toggle-card-desc" style={{ fontSize: '0.8rem', display: 'block', opacity: 0.75 }}>
|
|
|
|
|
|
{sp.enabled !== false
|
|
|
|
|
|
? 'Visible in header menu and reachable via URL'
|
|
|
|
|
|
: 'Hidden from navigation menu. Returns 404 if accessed directly.'}
|
|
|
|
|
|
</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div className={`switch-toggle ${sp.enabled !== false ? 'on' : ''}`}>
|
|
|
|
|
|
<span className="switch-slider" />
|
|
|
|
|
|
</div>
|
2026-05-29 17:29:26 +02:00
|
|
|
|
</button>
|
|
|
|
|
|
</div>
|
security: add Trivy container scan to Docker publish workflow (#399)
* feat(admin): add backup manager UI with 1-click restore
* feat(admin): collapse older backup entries by default
* fix(theme): isolate admin layout, instant DOM theme updates, and add Light/Dark mode toggle
* feat(admin): replace text controls for Photo Frame and Hero Style with visual preview cards
* fix(admin): define missing CSS rules for button system (.btn, .preset-btn)
* feat(admin): redesign theme settings with segmented mode toggle, accent color swatches, and custom iOS-style toggle cards
* feat(admin): upgrade all settings tabs with visual grid previews, Google SEO snippet preview, and toggle cards
* feat(admin): overhaul Pages Builder UX with slide-over drawer, card hover badges, real-time search, and floating save bar
* fix(admin): remove leftover dangling syntax snippet in PageBuilder.tsx
* fix(admin): add position: relative to subpage-tile so hover overlay is contained inside the tile
* style(admin): replace all emojis with clean Leica-style vector SVG icons
* fix(types): extend IconProps with SVGProps to allow style prop on Leica SVG icons
* feat(admin): add subpage live-preview, privacy analytics, and asset protection settings
* fix(typecheck): fix AlbumEntry property access in subpage drawer preview mode
* fix(types): fix ImmichAlbumInfo property names thumbnailAssetId and albumName in PageBuilder.tsx
* fix(types): add protection and watermark optional properties to AppConfig interface
* feat(settings): add toggle for disabling analytics tracking (defaults to enabled)
* feat(ux): show status banner when analytics tracking is disabled + add live sync indicator
* feat(watermark): render customizable watermark overlay on Lightbox images + rename section to Portfolio Features & Modules
* fix(types): import type LightboxWatermark in PhotoGrid.tsx
* fix(types): destructure watermark prop in PhotoGrid function signature
* feat(seo): add configurable subpage title template and metadata descriptions
* feat(seo): add generateMetadata to about page for title template support
* feat(proofing): add client proofing, favorites selection, compact URL bitmasking, and export modal
* feat(essay): add Photo Essay storytelling mode with RSC markdown parser and fullbleed/pair layouts
* feat(subpages): add page enabled toggle to deactivate subpages without deleting them
* feat(admin): add visual essay block builder for photo essay storytelling pages
* security: add Trivy container scan to Docker publish workflow
Scans the built image for CRITICAL/HIGH CVEs on every release and
uploads results to the GitHub Security tab. Non-blocking for now
(exit-code 0) so releases aren't gated on findings. Also documents
the scan and existing Dependabot coverage in the README.
---------
Co-authored-by: ralksta <ralksta@users.noreply.github.com>
2026-08-07 11:08:04 +02:00
|
|
|
|
<div className="admin-field" style={{ marginTop: '1rem' }}>
|
|
|
|
|
|
<label>Page Layout Style</label>
|
|
|
|
|
|
<select
|
|
|
|
|
|
value={sp.grid?.layout || 'masonry'}
|
|
|
|
|
|
onChange={(e) => {
|
|
|
|
|
|
const newLayout = e.target.value;
|
|
|
|
|
|
updateSubpage(spIndex, {
|
|
|
|
|
|
grid: { ...(sp.grid || {}), layout: newLayout },
|
|
|
|
|
|
});
|
|
|
|
|
|
}}
|
|
|
|
|
|
style={{ padding: '8px 12px', borderRadius: '6px', width: '100%', fontSize: '0.9rem' }}
|
|
|
|
|
|
>
|
|
|
|
|
|
<option value="masonry">📷 Masonry Grid (Dynamic Height)</option>
|
|
|
|
|
|
<option value="uniform">🔲 Uniform Grid (Square Tiles)</option>
|
|
|
|
|
|
<option value="showcase">⭐ Showcase (Featured First Asset)</option>
|
|
|
|
|
|
<option value="filmstrip">🎞️ Filmstrip (Horizontal Scroll)</option>
|
|
|
|
|
|
<option value="editorial-flow">📰 Editorial Flow</option>
|
|
|
|
|
|
<option value="essay">📖 Photo Essay Mode (Storytelling Editor)</option>
|
|
|
|
|
|
</select>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
{/* Visual Photo Essay Builder (if layout === 'essay') */}
|
|
|
|
|
|
{(sp.grid?.layout === 'essay' || sp.essayText != null) && (
|
|
|
|
|
|
<div className="subpage-drawer-section" style={{ background: 'var(--admin-surface, rgba(255,255,255,0.02))', padding: '1rem', borderRadius: '8px', border: '1px solid var(--admin-border, rgba(255,255,255,0.1))' }}>
|
|
|
|
|
|
<div style={{ marginBottom: '1rem' }}>
|
|
|
|
|
|
<h4 style={{ margin: 0, fontSize: '1rem', display: 'flex', alignItems: 'center', gap: '8px' }}>
|
|
|
|
|
|
<span>📖 Storytelling & Photo Essay Builder</span>
|
|
|
|
|
|
</h4>
|
|
|
|
|
|
<p style={{ margin: '4px 0 0 0', fontSize: '0.8rem', opacity: 0.75 }}>
|
|
|
|
|
|
Compose text, quotes, and fullbleed/wide photos visually.
|
|
|
|
|
|
</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<EssayBlockEditor
|
|
|
|
|
|
markdown={sp.essayText || ''}
|
|
|
|
|
|
onChange={(newMarkdown) => updateSubpage(spIndex, { essayText: newMarkdown })}
|
|
|
|
|
|
onSelectPhoto={(callback) => {
|
chore(release): v0.10.0 (#400)
Photo Essay mode, client proofing, studio-modern preset, admin panel overhaul, watermark, cookieless analytics, subpage toggle, SEO title templates. Analytics API now admin-only, Trivy scan on publish. Album order mirrors the Immich timeline; map theme, admin status panel, lightbox counter and Docker health check fixed.
See CHANGELOG.md for the full list and the health-check upgrade note.
2026-08-10 09:56:20 +02:00
|
|
|
|
const firstAlbumId = sp.albums[0]?.id;
|
|
|
|
|
|
setHeroPickerTarget({
|
|
|
|
|
|
albumId: firstAlbumId,
|
|
|
|
|
|
title: 'Select Photo for Photo Essay',
|
|
|
|
|
|
onSelect: (assetId) => {
|
|
|
|
|
|
callback(assetId);
|
|
|
|
|
|
setHeroPickerTarget(null);
|
|
|
|
|
|
},
|
|
|
|
|
|
});
|
security: add Trivy container scan to Docker publish workflow (#399)
* feat(admin): add backup manager UI with 1-click restore
* feat(admin): collapse older backup entries by default
* fix(theme): isolate admin layout, instant DOM theme updates, and add Light/Dark mode toggle
* feat(admin): replace text controls for Photo Frame and Hero Style with visual preview cards
* fix(admin): define missing CSS rules for button system (.btn, .preset-btn)
* feat(admin): redesign theme settings with segmented mode toggle, accent color swatches, and custom iOS-style toggle cards
* feat(admin): upgrade all settings tabs with visual grid previews, Google SEO snippet preview, and toggle cards
* feat(admin): overhaul Pages Builder UX with slide-over drawer, card hover badges, real-time search, and floating save bar
* fix(admin): remove leftover dangling syntax snippet in PageBuilder.tsx
* fix(admin): add position: relative to subpage-tile so hover overlay is contained inside the tile
* style(admin): replace all emojis with clean Leica-style vector SVG icons
* fix(types): extend IconProps with SVGProps to allow style prop on Leica SVG icons
* feat(admin): add subpage live-preview, privacy analytics, and asset protection settings
* fix(typecheck): fix AlbumEntry property access in subpage drawer preview mode
* fix(types): fix ImmichAlbumInfo property names thumbnailAssetId and albumName in PageBuilder.tsx
* fix(types): add protection and watermark optional properties to AppConfig interface
* feat(settings): add toggle for disabling analytics tracking (defaults to enabled)
* feat(ux): show status banner when analytics tracking is disabled + add live sync indicator
* feat(watermark): render customizable watermark overlay on Lightbox images + rename section to Portfolio Features & Modules
* fix(types): import type LightboxWatermark in PhotoGrid.tsx
* fix(types): destructure watermark prop in PhotoGrid function signature
* feat(seo): add configurable subpage title template and metadata descriptions
* feat(seo): add generateMetadata to about page for title template support
* feat(proofing): add client proofing, favorites selection, compact URL bitmasking, and export modal
* feat(essay): add Photo Essay storytelling mode with RSC markdown parser and fullbleed/pair layouts
* feat(subpages): add page enabled toggle to deactivate subpages without deleting them
* feat(admin): add visual essay block builder for photo essay storytelling pages
* security: add Trivy container scan to Docker publish workflow
Scans the built image for CRITICAL/HIGH CVEs on every release and
uploads results to the GitHub Security tab. Non-blocking for now
(exit-code 0) so releases aren't gated on findings. Also documents
the scan and existing Dependabot coverage in the README.
---------
Co-authored-by: ralksta <ralksta@users.noreply.github.com>
2026-08-07 11:08:04 +02:00
|
|
|
|
}}
|
|
|
|
|
|
/>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
|
|
<div className="settings-section-divider" />
|
|
|
|
|
|
|
|
|
|
|
|
{/* Albums (if no sections) with DnD */}
|
|
|
|
|
|
{(!sp.sections || sp.sections.length === 0) && (
|
|
|
|
|
|
<div className="subpage-albums">
|
|
|
|
|
|
<div className="subpage-albums-header">
|
|
|
|
|
|
<span>Albums in this Page</span>
|
|
|
|
|
|
<button
|
|
|
|
|
|
className="admin-btn admin-btn-xs admin-btn-primary"
|
|
|
|
|
|
onClick={() =>
|
|
|
|
|
|
setPickerTarget({ type: 'subpage', subpageIndex: spIndex })
|
|
|
|
|
|
}
|
|
|
|
|
|
>
|
|
|
|
|
|
+ Add Album
|
|
|
|
|
|
</button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<DndContext
|
|
|
|
|
|
sensors={sensors}
|
|
|
|
|
|
collisionDetection={closestCenter}
|
|
|
|
|
|
onDragEnd={handleSubpageAlbumDragEnd(spIndex)}
|
|
|
|
|
|
>
|
|
|
|
|
|
<SortableContext
|
|
|
|
|
|
items={sp.albums.map((a, i) => `album-${a.id}-${i}`)}
|
|
|
|
|
|
strategy={verticalListSortingStrategy}
|
|
|
|
|
|
>
|
|
|
|
|
|
<div className="album-list">
|
|
|
|
|
|
{sp.albums.length === 0 && (
|
|
|
|
|
|
<p className="empty-hint">No albums added yet. Click + Add Album to pick from Immich.</p>
|
|
|
|
|
|
)}
|
|
|
|
|
|
{sp.albums.map((album, aIndex) => (
|
|
|
|
|
|
<SortableAlbumCard
|
|
|
|
|
|
key={`${album.id}-${aIndex}`}
|
|
|
|
|
|
album={album}
|
|
|
|
|
|
index={aIndex}
|
|
|
|
|
|
name={getAlbumName(album.id)}
|
|
|
|
|
|
count={getAlbumCount(album.id)}
|
|
|
|
|
|
thumbnailId={getAlbumThumbnailId(album.id)}
|
|
|
|
|
|
onRemove={() => removeSubpageAlbum(spIndex, aIndex)}
|
|
|
|
|
|
onEdit={() => setEditingAlbumAddress({ type: 'subpage', subpageIndex: spIndex, albumIndex: aIndex })}
|
|
|
|
|
|
/>
|
|
|
|
|
|
))}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</SortableContext>
|
|
|
|
|
|
</DndContext>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
|
|
{/* Sections */}
|
|
|
|
|
|
{sp.sections && sp.sections.length > 0 && (
|
|
|
|
|
|
<div className="subpage-sections">
|
|
|
|
|
|
{sp.sections.map((sec, secIndex) => (
|
|
|
|
|
|
<div key={secIndex} className="section-card">
|
|
|
|
|
|
<div className="section-header">
|
|
|
|
|
|
<input
|
|
|
|
|
|
className="section-title-input"
|
|
|
|
|
|
value={sec.title}
|
|
|
|
|
|
onChange={(e) =>
|
|
|
|
|
|
updateSection(spIndex, secIndex, { title: e.target.value })
|
|
|
|
|
|
}
|
|
|
|
|
|
placeholder="Section title"
|
|
|
|
|
|
/>
|
|
|
|
|
|
<button
|
|
|
|
|
|
className="admin-btn-icon"
|
|
|
|
|
|
onClick={() => removeSection(spIndex, secIndex)}
|
|
|
|
|
|
title="Remove section"
|
|
|
|
|
|
>
|
|
|
|
|
|
<Icons.Close />
|
|
|
|
|
|
</button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div className="admin-field">
|
|
|
|
|
|
<input
|
|
|
|
|
|
value={sec.description || ''}
|
|
|
|
|
|
onChange={(e) =>
|
|
|
|
|
|
updateSection(spIndex, secIndex, {
|
|
|
|
|
|
description: e.target.value || undefined,
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
placeholder="Section description (optional)"
|
|
|
|
|
|
className="section-desc-input"
|
|
|
|
|
|
/>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div className="album-list">
|
|
|
|
|
|
{sec.albums.map((album, aIndex) => (
|
|
|
|
|
|
<AlbumCard
|
|
|
|
|
|
key={`${album.id}-${aIndex}`}
|
|
|
|
|
|
album={album}
|
|
|
|
|
|
name={getAlbumName(album.id)}
|
|
|
|
|
|
count={getAlbumCount(album.id)}
|
|
|
|
|
|
thumbnailId={getAlbumThumbnailId(album.id)}
|
|
|
|
|
|
onRemove={() => removeSectionAlbum(spIndex, secIndex, aIndex)}
|
|
|
|
|
|
onEdit={() => setEditingAlbumAddress({ type: 'section', subpageIndex: spIndex, sectionIndex: secIndex, albumIndex: aIndex })}
|
|
|
|
|
|
/>
|
|
|
|
|
|
))}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<button
|
|
|
|
|
|
className="admin-btn admin-btn-xs"
|
|
|
|
|
|
onClick={() =>
|
|
|
|
|
|
setPickerTarget({
|
|
|
|
|
|
type: 'section',
|
|
|
|
|
|
subpageIndex: spIndex,
|
|
|
|
|
|
sectionIndex: secIndex,
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
>
|
|
|
|
|
|
+ Add Album
|
|
|
|
|
|
</button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
))}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
|
|
<div className="subpage-actions" style={{ marginTop: '1rem' }}>
|
|
|
|
|
|
<button className="admin-btn admin-btn-xs admin-btn-secondary" onClick={() => addSection(spIndex)}>
|
|
|
|
|
|
+ Add Section
|
|
|
|
|
|
</button>
|
|
|
|
|
|
{sp.sections && sp.sections.length > 0 && (
|
|
|
|
|
|
<button
|
|
|
|
|
|
className="admin-btn admin-btn-xs admin-btn-secondary"
|
|
|
|
|
|
onClick={() => setPickerTarget({ type: 'subpage', subpageIndex: spIndex })}
|
|
|
|
|
|
>
|
|
|
|
|
|
+ Add Loose Album
|
|
|
|
|
|
</button>
|
|
|
|
|
|
)}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</>
|
|
|
|
|
|
)}
|
feat: add visual admin panel with page builder and settings editor
- Add /admin route with password-protected visual page builder
- Page Builder: manage hero images, standalone albums, subpages, and sections
- Album Picker: browse all shared Immich albums with search and filtering
- Settings Editor: theme, grid, footer, legal, SEO configuration
- YAML service with atomic writes and automatic backups (content/.backups/)
- HMAC-signed admin sessions (24h expiry, HttpOnly cookies)
- API endpoints: /api/admin/{auth,gallery,settings,albums,reload}
- Enable with ADMIN_PASSWORD env var; disabled by default
- invalidateConfigCache() export for hot-reload after saves
2026-05-29 17:04:37 +02:00
|
|
|
|
</div>
|
2026-05-29 17:29:26 +02:00
|
|
|
|
|
security: add Trivy container scan to Docker publish workflow (#399)
* feat(admin): add backup manager UI with 1-click restore
* feat(admin): collapse older backup entries by default
* fix(theme): isolate admin layout, instant DOM theme updates, and add Light/Dark mode toggle
* feat(admin): replace text controls for Photo Frame and Hero Style with visual preview cards
* fix(admin): define missing CSS rules for button system (.btn, .preset-btn)
* feat(admin): redesign theme settings with segmented mode toggle, accent color swatches, and custom iOS-style toggle cards
* feat(admin): upgrade all settings tabs with visual grid previews, Google SEO snippet preview, and toggle cards
* feat(admin): overhaul Pages Builder UX with slide-over drawer, card hover badges, real-time search, and floating save bar
* fix(admin): remove leftover dangling syntax snippet in PageBuilder.tsx
* fix(admin): add position: relative to subpage-tile so hover overlay is contained inside the tile
* style(admin): replace all emojis with clean Leica-style vector SVG icons
* fix(types): extend IconProps with SVGProps to allow style prop on Leica SVG icons
* feat(admin): add subpage live-preview, privacy analytics, and asset protection settings
* fix(typecheck): fix AlbumEntry property access in subpage drawer preview mode
* fix(types): fix ImmichAlbumInfo property names thumbnailAssetId and albumName in PageBuilder.tsx
* fix(types): add protection and watermark optional properties to AppConfig interface
* feat(settings): add toggle for disabling analytics tracking (defaults to enabled)
* feat(ux): show status banner when analytics tracking is disabled + add live sync indicator
* feat(watermark): render customizable watermark overlay on Lightbox images + rename section to Portfolio Features & Modules
* fix(types): import type LightboxWatermark in PhotoGrid.tsx
* fix(types): destructure watermark prop in PhotoGrid function signature
* feat(seo): add configurable subpage title template and metadata descriptions
* feat(seo): add generateMetadata to about page for title template support
* feat(proofing): add client proofing, favorites selection, compact URL bitmasking, and export modal
* feat(essay): add Photo Essay storytelling mode with RSC markdown parser and fullbleed/pair layouts
* feat(subpages): add page enabled toggle to deactivate subpages without deleting them
* feat(admin): add visual essay block builder for photo essay storytelling pages
* security: add Trivy container scan to Docker publish workflow
Scans the built image for CRITICAL/HIGH CVEs on every release and
uploads results to the GitHub Security tab. Non-blocking for now
(exit-code 0) so releases aren't gated on findings. Also documents
the scan and existing Dependabot coverage in the README.
---------
Co-authored-by: ralksta <ralksta@users.noreply.github.com>
2026-08-07 11:08:04 +02:00
|
|
|
|
<div className="subpage-drawer-footer">
|
2026-05-29 17:29:26 +02:00
|
|
|
|
<button
|
security: add Trivy container scan to Docker publish workflow (#399)
* feat(admin): add backup manager UI with 1-click restore
* feat(admin): collapse older backup entries by default
* fix(theme): isolate admin layout, instant DOM theme updates, and add Light/Dark mode toggle
* feat(admin): replace text controls for Photo Frame and Hero Style with visual preview cards
* fix(admin): define missing CSS rules for button system (.btn, .preset-btn)
* feat(admin): redesign theme settings with segmented mode toggle, accent color swatches, and custom iOS-style toggle cards
* feat(admin): upgrade all settings tabs with visual grid previews, Google SEO snippet preview, and toggle cards
* feat(admin): overhaul Pages Builder UX with slide-over drawer, card hover badges, real-time search, and floating save bar
* fix(admin): remove leftover dangling syntax snippet in PageBuilder.tsx
* fix(admin): add position: relative to subpage-tile so hover overlay is contained inside the tile
* style(admin): replace all emojis with clean Leica-style vector SVG icons
* fix(types): extend IconProps with SVGProps to allow style prop on Leica SVG icons
* feat(admin): add subpage live-preview, privacy analytics, and asset protection settings
* fix(typecheck): fix AlbumEntry property access in subpage drawer preview mode
* fix(types): fix ImmichAlbumInfo property names thumbnailAssetId and albumName in PageBuilder.tsx
* fix(types): add protection and watermark optional properties to AppConfig interface
* feat(settings): add toggle for disabling analytics tracking (defaults to enabled)
* feat(ux): show status banner when analytics tracking is disabled + add live sync indicator
* feat(watermark): render customizable watermark overlay on Lightbox images + rename section to Portfolio Features & Modules
* fix(types): import type LightboxWatermark in PhotoGrid.tsx
* fix(types): destructure watermark prop in PhotoGrid function signature
* feat(seo): add configurable subpage title template and metadata descriptions
* feat(seo): add generateMetadata to about page for title template support
* feat(proofing): add client proofing, favorites selection, compact URL bitmasking, and export modal
* feat(essay): add Photo Essay storytelling mode with RSC markdown parser and fullbleed/pair layouts
* feat(subpages): add page enabled toggle to deactivate subpages without deleting them
* feat(admin): add visual essay block builder for photo essay storytelling pages
* security: add Trivy container scan to Docker publish workflow
Scans the built image for CRITICAL/HIGH CVEs on every release and
uploads results to the GitHub Security tab. Non-blocking for now
(exit-code 0) so releases aren't gated on findings. Also documents
the scan and existing Dependabot coverage in the README.
---------
Co-authored-by: ralksta <ralksta@users.noreply.github.com>
2026-08-07 11:08:04 +02:00
|
|
|
|
className="admin-btn admin-btn-danger admin-btn-sm"
|
|
|
|
|
|
onClick={() => {
|
|
|
|
|
|
removeSubpage(spIndex);
|
|
|
|
|
|
setExpandedSubpage(null);
|
|
|
|
|
|
}}
|
2026-05-29 17:29:26 +02:00
|
|
|
|
>
|
security: add Trivy container scan to Docker publish workflow (#399)
* feat(admin): add backup manager UI with 1-click restore
* feat(admin): collapse older backup entries by default
* fix(theme): isolate admin layout, instant DOM theme updates, and add Light/Dark mode toggle
* feat(admin): replace text controls for Photo Frame and Hero Style with visual preview cards
* fix(admin): define missing CSS rules for button system (.btn, .preset-btn)
* feat(admin): redesign theme settings with segmented mode toggle, accent color swatches, and custom iOS-style toggle cards
* feat(admin): upgrade all settings tabs with visual grid previews, Google SEO snippet preview, and toggle cards
* feat(admin): overhaul Pages Builder UX with slide-over drawer, card hover badges, real-time search, and floating save bar
* fix(admin): remove leftover dangling syntax snippet in PageBuilder.tsx
* fix(admin): add position: relative to subpage-tile so hover overlay is contained inside the tile
* style(admin): replace all emojis with clean Leica-style vector SVG icons
* fix(types): extend IconProps with SVGProps to allow style prop on Leica SVG icons
* feat(admin): add subpage live-preview, privacy analytics, and asset protection settings
* fix(typecheck): fix AlbumEntry property access in subpage drawer preview mode
* fix(types): fix ImmichAlbumInfo property names thumbnailAssetId and albumName in PageBuilder.tsx
* fix(types): add protection and watermark optional properties to AppConfig interface
* feat(settings): add toggle for disabling analytics tracking (defaults to enabled)
* feat(ux): show status banner when analytics tracking is disabled + add live sync indicator
* feat(watermark): render customizable watermark overlay on Lightbox images + rename section to Portfolio Features & Modules
* fix(types): import type LightboxWatermark in PhotoGrid.tsx
* fix(types): destructure watermark prop in PhotoGrid function signature
* feat(seo): add configurable subpage title template and metadata descriptions
* feat(seo): add generateMetadata to about page for title template support
* feat(proofing): add client proofing, favorites selection, compact URL bitmasking, and export modal
* feat(essay): add Photo Essay storytelling mode with RSC markdown parser and fullbleed/pair layouts
* feat(subpages): add page enabled toggle to deactivate subpages without deleting them
* feat(admin): add visual essay block builder for photo essay storytelling pages
* security: add Trivy container scan to Docker publish workflow
Scans the built image for CRITICAL/HIGH CVEs on every release and
uploads results to the GitHub Security tab. Non-blocking for now
(exit-code 0) so releases aren't gated on findings. Also documents
the scan and existing Dependabot coverage in the README.
---------
Co-authored-by: ralksta <ralksta@users.noreply.github.com>
2026-08-07 11:08:04 +02:00
|
|
|
|
<Icons.Trash /> Delete Subpage
|
2026-05-29 17:29:26 +02:00
|
|
|
|
</button>
|
security: add Trivy container scan to Docker publish workflow (#399)
* feat(admin): add backup manager UI with 1-click restore
* feat(admin): collapse older backup entries by default
* fix(theme): isolate admin layout, instant DOM theme updates, and add Light/Dark mode toggle
* feat(admin): replace text controls for Photo Frame and Hero Style with visual preview cards
* fix(admin): define missing CSS rules for button system (.btn, .preset-btn)
* feat(admin): redesign theme settings with segmented mode toggle, accent color swatches, and custom iOS-style toggle cards
* feat(admin): upgrade all settings tabs with visual grid previews, Google SEO snippet preview, and toggle cards
* feat(admin): overhaul Pages Builder UX with slide-over drawer, card hover badges, real-time search, and floating save bar
* fix(admin): remove leftover dangling syntax snippet in PageBuilder.tsx
* fix(admin): add position: relative to subpage-tile so hover overlay is contained inside the tile
* style(admin): replace all emojis with clean Leica-style vector SVG icons
* fix(types): extend IconProps with SVGProps to allow style prop on Leica SVG icons
* feat(admin): add subpage live-preview, privacy analytics, and asset protection settings
* fix(typecheck): fix AlbumEntry property access in subpage drawer preview mode
* fix(types): fix ImmichAlbumInfo property names thumbnailAssetId and albumName in PageBuilder.tsx
* fix(types): add protection and watermark optional properties to AppConfig interface
* feat(settings): add toggle for disabling analytics tracking (defaults to enabled)
* feat(ux): show status banner when analytics tracking is disabled + add live sync indicator
* feat(watermark): render customizable watermark overlay on Lightbox images + rename section to Portfolio Features & Modules
* fix(types): import type LightboxWatermark in PhotoGrid.tsx
* fix(types): destructure watermark prop in PhotoGrid function signature
* feat(seo): add configurable subpage title template and metadata descriptions
* feat(seo): add generateMetadata to about page for title template support
* feat(proofing): add client proofing, favorites selection, compact URL bitmasking, and export modal
* feat(essay): add Photo Essay storytelling mode with RSC markdown parser and fullbleed/pair layouts
* feat(subpages): add page enabled toggle to deactivate subpages without deleting them
* feat(admin): add visual essay block builder for photo essay storytelling pages
* security: add Trivy container scan to Docker publish workflow
Scans the built image for CRITICAL/HIGH CVEs on every release and
uploads results to the GitHub Security tab. Non-blocking for now
(exit-code 0) so releases aren't gated on findings. Also documents
the scan and existing Dependabot coverage in the README.
---------
Co-authored-by: ralksta <ralksta@users.noreply.github.com>
2026-08-07 11:08:04 +02:00
|
|
|
|
<button
|
|
|
|
|
|
className="admin-btn admin-btn-primary admin-btn-sm"
|
|
|
|
|
|
onClick={() => setExpandedSubpage(null)}
|
|
|
|
|
|
>
|
|
|
|
|
|
Done
|
|
|
|
|
|
</button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</>
|
|
|
|
|
|
);
|
|
|
|
|
|
})()}
|
|
|
|
|
|
</div>
|
feat: add visual admin panel with page builder and settings editor
- Add /admin route with password-protected visual page builder
- Page Builder: manage hero images, standalone albums, subpages, and sections
- Album Picker: browse all shared Immich albums with search and filtering
- Settings Editor: theme, grid, footer, legal, SEO configuration
- YAML service with atomic writes and automatic backups (content/.backups/)
- HMAC-signed admin sessions (24h expiry, HttpOnly cookies)
- API endpoints: /api/admin/{auth,gallery,settings,albums,reload}
- Enable with ADMIN_PASSWORD env var; disabled by default
- invalidateConfigCache() export for hot-reload after saves
2026-05-29 17:04:37 +02:00
|
|
|
|
</div>
|
2026-05-29 17:29:26 +02:00
|
|
|
|
)}
|
feat: add visual admin panel with page builder and settings editor
- Add /admin route with password-protected visual page builder
- Page Builder: manage hero images, standalone albums, subpages, and sections
- Album Picker: browse all shared Immich albums with search and filtering
- Settings Editor: theme, grid, footer, legal, SEO configuration
- YAML service with atomic writes and automatic backups (content/.backups/)
- HMAC-signed admin sessions (24h expiry, HttpOnly cookies)
- API endpoints: /api/admin/{auth,gallery,settings,albums,reload}
- Enable with ADMIN_PASSWORD env var; disabled by default
- invalidateConfigCache() export for hot-reload after saves
2026-05-29 17:04:37 +02:00
|
|
|
|
</section>
|
|
|
|
|
|
|
|
|
|
|
|
{/* Album Picker Modal */}
|
|
|
|
|
|
{pickerTarget && (
|
|
|
|
|
|
<AlbumPicker
|
|
|
|
|
|
albums={immichAlbums}
|
|
|
|
|
|
onSelect={handlePickAlbum}
|
|
|
|
|
|
onClose={() => setPickerTarget(null)}
|
|
|
|
|
|
usedAlbumIds={getAllUsedAlbumIds()}
|
|
|
|
|
|
/>
|
|
|
|
|
|
)}
|
2026-05-29 17:59:25 +02:00
|
|
|
|
|
2026-05-31 21:30:00 +02:00
|
|
|
|
{/* Slide-over Drawer for Album Details (Centered 2-Column Modal) */}
|
2026-05-31 21:27:13 +02:00
|
|
|
|
{(() => {
|
|
|
|
|
|
if (!editingAlbumAddress) return null;
|
|
|
|
|
|
const info = getEditingAlbumInfo(editingAlbumAddress);
|
|
|
|
|
|
if (!info) return null;
|
|
|
|
|
|
const { album, name, count, thumbnailId, onUpdate, onRemove } = info;
|
|
|
|
|
|
const heroThumb = album.heroImage || thumbnailId;
|
|
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
|
<div className="album-drawer-overlay open" onClick={() => setEditingAlbumAddress(null)}>
|
|
|
|
|
|
<div className="album-drawer" onClick={(e) => e.stopPropagation()}>
|
|
|
|
|
|
<div className="album-drawer-header">
|
|
|
|
|
|
<h3>
|
|
|
|
|
|
<Icons.Camera /> Edit Album Details
|
|
|
|
|
|
</h3>
|
|
|
|
|
|
<button
|
|
|
|
|
|
className="admin-btn-icon"
|
|
|
|
|
|
onClick={() => setEditingAlbumAddress(null)}
|
|
|
|
|
|
title="Close details"
|
|
|
|
|
|
>
|
|
|
|
|
|
<Icons.Close />
|
|
|
|
|
|
</button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div className="album-drawer-body">
|
2026-05-31 21:30:00 +02:00
|
|
|
|
{/* Left Column - Visual Info & Stats */}
|
|
|
|
|
|
<div className="modal-left-column">
|
|
|
|
|
|
<div className="modal-cover-container">
|
2026-05-31 21:27:13 +02:00
|
|
|
|
{heroThumb ? (
|
|
|
|
|
|
<img src={`/api/admin/thumbnail/${heroThumb}`} alt="" loading="lazy" />
|
|
|
|
|
|
) : (
|
2026-05-31 21:30:00 +02:00
|
|
|
|
<div className="modal-cover-placeholder">
|
2026-05-31 21:27:13 +02:00
|
|
|
|
<Icons.Camera />
|
|
|
|
|
|
</div>
|
|
|
|
|
|
)}
|
|
|
|
|
|
</div>
|
2026-05-31 21:30:00 +02:00
|
|
|
|
<div className="modal-meta-box">
|
|
|
|
|
|
<span className="modal-album-title">{album.title || name}</span>
|
|
|
|
|
|
<span className="modal-album-subtitle">Original: {name}</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div className="modal-info-list">
|
|
|
|
|
|
<div className="modal-info-item">
|
|
|
|
|
|
<span className="modal-info-label">Photo Count:</span>
|
|
|
|
|
|
<span className="modal-info-value">{count} photos</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div className="modal-info-item">
|
|
|
|
|
|
<span className="modal-info-label">Status:</span>
|
|
|
|
|
|
<span className="modal-info-value">
|
|
|
|
|
|
{album.password ? 'Password Protected' : 'Public Access'}
|
|
|
|
|
|
</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div className="modal-info-item">
|
|
|
|
|
|
<span className="modal-info-label">Custom Hero:</span>
|
|
|
|
|
|
<span className="modal-info-value">
|
|
|
|
|
|
{album.heroImage ? 'Yes' : 'No'}
|
|
|
|
|
|
</span>
|
|
|
|
|
|
</div>
|
2026-05-31 21:27:13 +02:00
|
|
|
|
</div>
|
|
|
|
|
|
|
2026-05-31 21:30:00 +02:00
|
|
|
|
{/* Technical UUID */}
|
|
|
|
|
|
<div className="drawer-uuid-section">
|
|
|
|
|
|
<label className="admin-field-label">IMMICH ALBUM UUID</label>
|
|
|
|
|
|
<div className="uuid-copy-box">
|
|
|
|
|
|
<code>{album.id}</code>
|
|
|
|
|
|
<button
|
|
|
|
|
|
className="uuid-copy-btn"
|
|
|
|
|
|
onClick={() => {
|
|
|
|
|
|
navigator.clipboard.writeText(album.id);
|
|
|
|
|
|
}}
|
|
|
|
|
|
title="Copy UUID"
|
|
|
|
|
|
>
|
|
|
|
|
|
<Icons.Copy />
|
|
|
|
|
|
</button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
2026-05-31 21:27:13 +02:00
|
|
|
|
</div>
|
|
|
|
|
|
|
2026-05-31 21:30:00 +02:00
|
|
|
|
{/* Right Column - Form Fields */}
|
|
|
|
|
|
<div className="modal-right-column">
|
|
|
|
|
|
<div className="admin-field">
|
|
|
|
|
|
<label>Title override</label>
|
2026-05-31 21:27:13 +02:00
|
|
|
|
<input
|
2026-05-31 21:30:00 +02:00
|
|
|
|
value={album.title || ''}
|
|
|
|
|
|
onChange={(e) => onUpdate({ title: e.target.value || undefined })}
|
|
|
|
|
|
placeholder={name}
|
2026-05-31 21:27:13 +02:00
|
|
|
|
/>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
2026-05-31 21:30:00 +02:00
|
|
|
|
<div className="admin-field">
|
|
|
|
|
|
<label>Description</label>
|
|
|
|
|
|
<textarea
|
|
|
|
|
|
value={album.description || ''}
|
|
|
|
|
|
onChange={(e) => onUpdate({ description: e.target.value || undefined })}
|
|
|
|
|
|
placeholder="Optional description for visitors"
|
|
|
|
|
|
rows={4}
|
|
|
|
|
|
/>
|
2026-05-31 21:27:13 +02:00
|
|
|
|
</div>
|
|
|
|
|
|
|
2026-05-31 21:30:00 +02:00
|
|
|
|
<div className="admin-field">
|
|
|
|
|
|
<label>Password protection</label>
|
|
|
|
|
|
<div className="input-with-icon">
|
|
|
|
|
|
<input
|
|
|
|
|
|
type="password"
|
|
|
|
|
|
value={album.password || ''}
|
|
|
|
|
|
onChange={(e) => onUpdate({ password: e.target.value || undefined })}
|
|
|
|
|
|
placeholder="Leave empty for public access"
|
|
|
|
|
|
/>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
{/* Hero Image Selection */}
|
|
|
|
|
|
<div className="admin-field">
|
|
|
|
|
|
<label>Custom Hero Image</label>
|
|
|
|
|
|
<div className="album-hero-field">
|
|
|
|
|
|
{album.heroImage ? (
|
|
|
|
|
|
<div className="album-hero-preview">
|
|
|
|
|
|
<img src={`/api/admin/thumbnail/${album.heroImage}`} alt="Hero" />
|
|
|
|
|
|
<button
|
|
|
|
|
|
className="album-hero-remove"
|
|
|
|
|
|
onClick={() => onUpdate({ heroImage: undefined })}
|
|
|
|
|
|
title="Remove custom hero image"
|
|
|
|
|
|
>
|
|
|
|
|
|
<Icons.Close />
|
|
|
|
|
|
</button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
) : (
|
|
|
|
|
|
<span className="album-hero-empty">Using default album cover</span>
|
|
|
|
|
|
)}
|
|
|
|
|
|
<button
|
|
|
|
|
|
className="admin-btn admin-btn-sm"
|
|
|
|
|
|
onClick={() =>
|
|
|
|
|
|
setHeroPickerTarget({
|
|
|
|
|
|
albumId: album.id,
|
|
|
|
|
|
onSelect: (assetId) => {
|
|
|
|
|
|
onUpdate({ heroImage: assetId });
|
|
|
|
|
|
setHeroPickerTarget(null);
|
|
|
|
|
|
},
|
|
|
|
|
|
currentAssetIds: album.heroImage ? [album.heroImage] : [],
|
|
|
|
|
|
title: `Pick Hero Image for ${name}`,
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
>
|
|
|
|
|
|
<Icons.Image /> {album.heroImage ? 'Change Image' : 'Pick Hero Image'}
|
|
|
|
|
|
</button>
|
|
|
|
|
|
</div>
|
2026-05-31 21:27:13 +02:00
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div className="album-drawer-footer">
|
|
|
|
|
|
<button
|
|
|
|
|
|
className="admin-btn admin-btn-ghost admin-btn-danger"
|
|
|
|
|
|
onClick={onRemove}
|
2026-05-31 21:30:00 +02:00
|
|
|
|
style={{ maxWidth: '160px' }}
|
2026-05-31 21:27:13 +02:00
|
|
|
|
>
|
|
|
|
|
|
<Icons.Trash /> Remove Album
|
|
|
|
|
|
</button>
|
2026-05-31 21:30:00 +02:00
|
|
|
|
<button
|
|
|
|
|
|
className="admin-btn admin-btn-primary"
|
|
|
|
|
|
onClick={() => setEditingAlbumAddress(null)}
|
|
|
|
|
|
style={{ maxWidth: '140px' }}
|
|
|
|
|
|
>
|
|
|
|
|
|
Done
|
|
|
|
|
|
</button>
|
2026-05-31 21:27:13 +02:00
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
);
|
|
|
|
|
|
})()}
|
|
|
|
|
|
|
2026-05-29 17:59:25 +02:00
|
|
|
|
{/* Hero Asset Picker Modal */}
|
2026-05-31 21:27:13 +02:00
|
|
|
|
{heroPickerTarget && (
|
2026-05-29 17:59:25 +02:00
|
|
|
|
<AssetPicker
|
2026-05-31 21:27:13 +02:00
|
|
|
|
albumId={heroPickerTarget.albumId}
|
|
|
|
|
|
onSelect={heroPickerTarget.onSelect}
|
|
|
|
|
|
onClose={() => setHeroPickerTarget(null)}
|
|
|
|
|
|
currentAssetIds={heroPickerTarget.currentAssetIds}
|
|
|
|
|
|
title={heroPickerTarget.title}
|
2026-05-29 17:59:25 +02:00
|
|
|
|
/>
|
|
|
|
|
|
)}
|
feat: add visual admin panel with page builder and settings editor
- Add /admin route with password-protected visual page builder
- Page Builder: manage hero images, standalone albums, subpages, and sections
- Album Picker: browse all shared Immich albums with search and filtering
- Settings Editor: theme, grid, footer, legal, SEO configuration
- YAML service with atomic writes and automatic backups (content/.backups/)
- HMAC-signed admin sessions (24h expiry, HttpOnly cookies)
- API endpoints: /api/admin/{auth,gallery,settings,albums,reload}
- Enable with ADMIN_PASSWORD env var; disabled by default
- invalidateConfigCache() export for hot-reload after saves
2026-05-29 17:04:37 +02:00
|
|
|
|
</div>
|
|
|
|
|
|
);
|
|
|
|
|
|
|
2026-05-31 21:27:13 +02:00
|
|
|
|
function getEditingAlbumInfo(addr: ActiveEditAlbumAddress) {
|
|
|
|
|
|
let album: AlbumEntry;
|
|
|
|
|
|
let name: string;
|
|
|
|
|
|
let count: number;
|
|
|
|
|
|
let thumbnailId: string | null;
|
|
|
|
|
|
let onUpdate: (updates: Partial<AlbumEntry>) => void;
|
|
|
|
|
|
let onRemove: () => void;
|
|
|
|
|
|
|
|
|
|
|
|
if (addr.type === 'standalone') {
|
|
|
|
|
|
album = gallery.albums[addr.albumIndex];
|
|
|
|
|
|
if (!album) return null;
|
|
|
|
|
|
name = getAlbumName(album.id);
|
|
|
|
|
|
count = getAlbumCount(album.id);
|
|
|
|
|
|
thumbnailId = getAlbumThumbnailId(album.id);
|
|
|
|
|
|
onUpdate = (updates) => {
|
|
|
|
|
|
setGallery((g) => {
|
|
|
|
|
|
const albums = [...g.albums];
|
|
|
|
|
|
albums[addr.albumIndex] = { ...albums[addr.albumIndex], ...updates };
|
|
|
|
|
|
return { ...g, albums };
|
|
|
|
|
|
});
|
|
|
|
|
|
markDirty();
|
|
|
|
|
|
};
|
|
|
|
|
|
onRemove = () => {
|
|
|
|
|
|
removeStandaloneAlbum(addr.albumIndex);
|
|
|
|
|
|
};
|
|
|
|
|
|
} else if (addr.type === 'subpage') {
|
|
|
|
|
|
const sp = gallery.subpages[addr.subpageIndex!];
|
|
|
|
|
|
if (!sp) return null;
|
|
|
|
|
|
album = sp.albums[addr.albumIndex];
|
|
|
|
|
|
if (!album) return null;
|
|
|
|
|
|
name = getAlbumName(album.id);
|
|
|
|
|
|
count = getAlbumCount(album.id);
|
|
|
|
|
|
thumbnailId = getAlbumThumbnailId(album.id);
|
|
|
|
|
|
onUpdate = (updates) => {
|
|
|
|
|
|
setGallery((g) => {
|
|
|
|
|
|
const subpages = [...g.subpages];
|
|
|
|
|
|
const sp2 = { ...subpages[addr.subpageIndex!] };
|
|
|
|
|
|
const albums = [...sp2.albums];
|
|
|
|
|
|
albums[addr.albumIndex] = { ...albums[addr.albumIndex], ...updates };
|
|
|
|
|
|
sp2.albums = albums;
|
|
|
|
|
|
subpages[addr.subpageIndex!] = sp2;
|
|
|
|
|
|
return { ...g, subpages };
|
|
|
|
|
|
});
|
|
|
|
|
|
markDirty();
|
|
|
|
|
|
};
|
|
|
|
|
|
onRemove = () => {
|
|
|
|
|
|
removeSubpageAlbum(addr.subpageIndex!, addr.albumIndex);
|
|
|
|
|
|
};
|
|
|
|
|
|
} else {
|
|
|
|
|
|
const sp = gallery.subpages[addr.subpageIndex!];
|
|
|
|
|
|
if (!sp) return null;
|
|
|
|
|
|
const sec = sp.sections?.[addr.sectionIndex!];
|
|
|
|
|
|
if (!sec) return null;
|
|
|
|
|
|
album = sec.albums[addr.albumIndex];
|
|
|
|
|
|
if (!album) return null;
|
|
|
|
|
|
name = getAlbumName(album.id);
|
|
|
|
|
|
count = getAlbumCount(album.id);
|
|
|
|
|
|
thumbnailId = getAlbumThumbnailId(album.id);
|
|
|
|
|
|
onUpdate = (updates) => {
|
|
|
|
|
|
setGallery((g) => {
|
|
|
|
|
|
const subpages = [...g.subpages];
|
|
|
|
|
|
const sp2 = { ...subpages[addr.subpageIndex!] };
|
|
|
|
|
|
const sections = [...(sp2.sections || [])];
|
|
|
|
|
|
const sec2 = { ...sections[addr.sectionIndex!] };
|
|
|
|
|
|
const albums = [...sec2.albums];
|
|
|
|
|
|
albums[addr.albumIndex] = { ...albums[addr.albumIndex], ...updates };
|
|
|
|
|
|
sec2.albums = albums;
|
|
|
|
|
|
sections[addr.sectionIndex!] = sec2;
|
|
|
|
|
|
sp2.sections = sections;
|
|
|
|
|
|
subpages[addr.subpageIndex!] = sp2;
|
|
|
|
|
|
return { ...g, subpages };
|
|
|
|
|
|
});
|
|
|
|
|
|
markDirty();
|
|
|
|
|
|
};
|
|
|
|
|
|
onRemove = () => {
|
|
|
|
|
|
removeSectionAlbum(addr.subpageIndex!, addr.sectionIndex!, addr.albumIndex);
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return { album, name, count, thumbnailId, onUpdate, onRemove };
|
|
|
|
|
|
}
|
|
|
|
|
|
|
feat: add visual admin panel with page builder and settings editor
- Add /admin route with password-protected visual page builder
- Page Builder: manage hero images, standalone albums, subpages, and sections
- Album Picker: browse all shared Immich albums with search and filtering
- Settings Editor: theme, grid, footer, legal, SEO configuration
- YAML service with atomic writes and automatic backups (content/.backups/)
- HMAC-signed admin sessions (24h expiry, HttpOnly cookies)
- API endpoints: /api/admin/{auth,gallery,settings,albums,reload}
- Enable with ADMIN_PASSWORD env var; disabled by default
- invalidateConfigCache() export for hot-reload after saves
2026-05-29 17:04:37 +02:00
|
|
|
|
function getAllUsedAlbumIds(): Set<string> {
|
|
|
|
|
|
const ids = new Set<string>();
|
|
|
|
|
|
gallery.albums.forEach((a) => ids.add(a.id));
|
|
|
|
|
|
gallery.subpages.forEach((sp) => {
|
|
|
|
|
|
sp.albums.forEach((a) => ids.add(a.id));
|
|
|
|
|
|
sp.sections?.forEach((sec) => sec.albums.forEach((a) => ids.add(a.id)));
|
|
|
|
|
|
});
|
|
|
|
|
|
return ids;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ── Album Card Sub-component ───────────────────────────────────
|
|
|
|
|
|
|
|
|
|
|
|
interface AlbumCardProps {
|
|
|
|
|
|
album: AlbumEntry;
|
|
|
|
|
|
name: string;
|
|
|
|
|
|
count: number;
|
2026-05-29 17:14:58 +02:00
|
|
|
|
thumbnailId: string | null;
|
feat: add visual admin panel with page builder and settings editor
- Add /admin route with password-protected visual page builder
- Page Builder: manage hero images, standalone albums, subpages, and sections
- Album Picker: browse all shared Immich albums with search and filtering
- Settings Editor: theme, grid, footer, legal, SEO configuration
- YAML service with atomic writes and automatic backups (content/.backups/)
- HMAC-signed admin sessions (24h expiry, HttpOnly cookies)
- API endpoints: /api/admin/{auth,gallery,settings,albums,reload}
- Enable with ADMIN_PASSWORD env var; disabled by default
- invalidateConfigCache() export for hot-reload after saves
2026-05-29 17:04:37 +02:00
|
|
|
|
onRemove: () => void;
|
2026-05-31 21:27:13 +02:00
|
|
|
|
onEdit: () => void;
|
2026-05-29 17:59:25 +02:00
|
|
|
|
dragListeners?: Record<string, unknown>;
|
feat: add visual admin panel with page builder and settings editor
- Add /admin route with password-protected visual page builder
- Page Builder: manage hero images, standalone albums, subpages, and sections
- Album Picker: browse all shared Immich albums with search and filtering
- Settings Editor: theme, grid, footer, legal, SEO configuration
- YAML service with atomic writes and automatic backups (content/.backups/)
- HMAC-signed admin sessions (24h expiry, HttpOnly cookies)
- API endpoints: /api/admin/{auth,gallery,settings,albums,reload}
- Enable with ADMIN_PASSWORD env var; disabled by default
- invalidateConfigCache() export for hot-reload after saves
2026-05-29 17:04:37 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-29 17:59:25 +02:00
|
|
|
|
function AlbumCard({
|
|
|
|
|
|
album,
|
|
|
|
|
|
name,
|
|
|
|
|
|
count,
|
|
|
|
|
|
thumbnailId,
|
|
|
|
|
|
onRemove,
|
2026-05-31 21:27:13 +02:00
|
|
|
|
onEdit,
|
2026-05-29 17:59:25 +02:00
|
|
|
|
dragListeners,
|
|
|
|
|
|
}: AlbumCardProps) {
|
2026-05-31 21:27:13 +02:00
|
|
|
|
const heroThumb = album.heroImage || thumbnailId;
|
|
|
|
|
|
const hasPassword = !!album.password;
|
|
|
|
|
|
const hasTitleOverride = !!album.title;
|
|
|
|
|
|
const hasDescription = !!album.description;
|
feat: add visual admin panel with page builder and settings editor
- Add /admin route with password-protected visual page builder
- Page Builder: manage hero images, standalone albums, subpages, and sections
- Album Picker: browse all shared Immich albums with search and filtering
- Settings Editor: theme, grid, footer, legal, SEO configuration
- YAML service with atomic writes and automatic backups (content/.backups/)
- HMAC-signed admin sessions (24h expiry, HttpOnly cookies)
- API endpoints: /api/admin/{auth,gallery,settings,albums,reload}
- Enable with ADMIN_PASSWORD env var; disabled by default
- invalidateConfigCache() export for hot-reload after saves
2026-05-29 17:04:37 +02:00
|
|
|
|
|
|
|
|
|
|
return (
|
2026-05-31 21:27:13 +02:00
|
|
|
|
<div className={`album-tile ${hasPassword ? 'has-password' : ''}`}>
|
2026-05-29 17:24:53 +02:00
|
|
|
|
<div className="album-tile-cover">
|
2026-05-29 17:59:25 +02:00
|
|
|
|
{dragListeners && (
|
|
|
|
|
|
<div className="album-tile-drag" {...dragListeners} title="Drag to reorder">
|
2026-05-31 21:27:13 +02:00
|
|
|
|
<Icons.Drag />
|
2026-05-29 17:59:25 +02:00
|
|
|
|
</div>
|
|
|
|
|
|
)}
|
2026-05-31 21:27:13 +02:00
|
|
|
|
{heroThumb ? (
|
|
|
|
|
|
<img src={`/api/admin/thumbnail/${heroThumb}`} alt="" loading="lazy" />
|
2026-05-29 17:24:53 +02:00
|
|
|
|
) : (
|
2026-05-31 21:27:13 +02:00
|
|
|
|
<div className="album-tile-placeholder">
|
|
|
|
|
|
<Icons.Camera />
|
|
|
|
|
|
</div>
|
2026-05-29 17:14:58 +02:00
|
|
|
|
)}
|
2026-05-29 17:24:53 +02:00
|
|
|
|
<div className="album-tile-overlay">
|
feat: add visual admin panel with page builder and settings editor
- Add /admin route with password-protected visual page builder
- Page Builder: manage hero images, standalone albums, subpages, and sections
- Album Picker: browse all shared Immich albums with search and filtering
- Settings Editor: theme, grid, footer, legal, SEO configuration
- YAML service with atomic writes and automatic backups (content/.backups/)
- HMAC-signed admin sessions (24h expiry, HttpOnly cookies)
- API endpoints: /api/admin/{auth,gallery,settings,albums,reload}
- Enable with ADMIN_PASSWORD env var; disabled by default
- invalidateConfigCache() export for hot-reload after saves
2026-05-29 17:04:37 +02:00
|
|
|
|
<button
|
2026-05-29 17:24:53 +02:00
|
|
|
|
className="album-tile-btn"
|
2026-05-31 21:27:13 +02:00
|
|
|
|
onClick={onEdit}
|
feat: add visual admin panel with page builder and settings editor
- Add /admin route with password-protected visual page builder
- Page Builder: manage hero images, standalone albums, subpages, and sections
- Album Picker: browse all shared Immich albums with search and filtering
- Settings Editor: theme, grid, footer, legal, SEO configuration
- YAML service with atomic writes and automatic backups (content/.backups/)
- HMAC-signed admin sessions (24h expiry, HttpOnly cookies)
- API endpoints: /api/admin/{auth,gallery,settings,albums,reload}
- Enable with ADMIN_PASSWORD env var; disabled by default
- invalidateConfigCache() export for hot-reload after saves
2026-05-29 17:04:37 +02:00
|
|
|
|
title="Edit details"
|
|
|
|
|
|
>
|
2026-05-31 21:27:13 +02:00
|
|
|
|
<Icons.Edit />
|
feat: add visual admin panel with page builder and settings editor
- Add /admin route with password-protected visual page builder
- Page Builder: manage hero images, standalone albums, subpages, and sections
- Album Picker: browse all shared Immich albums with search and filtering
- Settings Editor: theme, grid, footer, legal, SEO configuration
- YAML service with atomic writes and automatic backups (content/.backups/)
- HMAC-signed admin sessions (24h expiry, HttpOnly cookies)
- API endpoints: /api/admin/{auth,gallery,settings,albums,reload}
- Enable with ADMIN_PASSWORD env var; disabled by default
- invalidateConfigCache() export for hot-reload after saves
2026-05-29 17:04:37 +02:00
|
|
|
|
</button>
|
2026-05-31 21:27:13 +02:00
|
|
|
|
<button
|
|
|
|
|
|
className="album-tile-btn album-tile-btn-danger"
|
|
|
|
|
|
onClick={onRemove}
|
|
|
|
|
|
title="Remove album"
|
|
|
|
|
|
>
|
|
|
|
|
|
<Icons.Trash />
|
feat: add visual admin panel with page builder and settings editor
- Add /admin route with password-protected visual page builder
- Page Builder: manage hero images, standalone albums, subpages, and sections
- Album Picker: browse all shared Immich albums with search and filtering
- Settings Editor: theme, grid, footer, legal, SEO configuration
- YAML service with atomic writes and automatic backups (content/.backups/)
- HMAC-signed admin sessions (24h expiry, HttpOnly cookies)
- API endpoints: /api/admin/{auth,gallery,settings,albums,reload}
- Enable with ADMIN_PASSWORD env var; disabled by default
- invalidateConfigCache() export for hot-reload after saves
2026-05-29 17:04:37 +02:00
|
|
|
|
</button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
2026-05-29 17:24:53 +02:00
|
|
|
|
<div className="album-tile-info">
|
2026-05-31 21:27:13 +02:00
|
|
|
|
<div className="album-tile-title-row">
|
|
|
|
|
|
<span className={`album-tile-name ${hasTitleOverride ? 'custom-title' : ''}`} title={album.title || name}>
|
|
|
|
|
|
{album.title || name}
|
|
|
|
|
|
</span>
|
|
|
|
|
|
<div className="album-tile-badges">
|
|
|
|
|
|
{hasPassword && (
|
|
|
|
|
|
<span className="badge badge-password" title="Password protected">
|
|
|
|
|
|
<Icons.Lock />
|
|
|
|
|
|
</span>
|
|
|
|
|
|
)}
|
|
|
|
|
|
{album.heroImage && (
|
|
|
|
|
|
<span className="badge badge-hero" title="Custom Hero Image set">
|
|
|
|
|
|
<Icons.Image />
|
|
|
|
|
|
</span>
|
|
|
|
|
|
)}
|
feat: add visual admin panel with page builder and settings editor
- Add /admin route with password-protected visual page builder
- Page Builder: manage hero images, standalone albums, subpages, and sections
- Album Picker: browse all shared Immich albums with search and filtering
- Settings Editor: theme, grid, footer, legal, SEO configuration
- YAML service with atomic writes and automatic backups (content/.backups/)
- HMAC-signed admin sessions (24h expiry, HttpOnly cookies)
- API endpoints: /api/admin/{auth,gallery,settings,albums,reload}
- Enable with ADMIN_PASSWORD env var; disabled by default
- invalidateConfigCache() export for hot-reload after saves
2026-05-29 17:04:37 +02:00
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
2026-05-31 21:27:13 +02:00
|
|
|
|
<span className="album-tile-count">{count} photos</span>
|
|
|
|
|
|
</div>
|
feat: add visual admin panel with page builder and settings editor
- Add /admin route with password-protected visual page builder
- Page Builder: manage hero images, standalone albums, subpages, and sections
- Album Picker: browse all shared Immich albums with search and filtering
- Settings Editor: theme, grid, footer, legal, SEO configuration
- YAML service with atomic writes and automatic backups (content/.backups/)
- HMAC-signed admin sessions (24h expiry, HttpOnly cookies)
- API endpoints: /api/admin/{auth,gallery,settings,albums,reload}
- Enable with ADMIN_PASSWORD env var; disabled by default
- invalidateConfigCache() export for hot-reload after saves
2026-05-29 17:04:37 +02:00
|
|
|
|
</div>
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|