Wire up Profile/Appearance/Security in user menu (#21)

* Add editable display-name field to generic integrations

Lets users set a custom name for Proxmox, Docker, AWS, Remote Desktop,
Netbird, Cloudflare, Uptime Kuma, and Weather integrations, separate
from the host/IP field, mirroring the SSH host rename pattern.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016kF4hZWEkRCPPvCZTeXxn4

* Surface the new-integration name field as a labeled input

The name field for new generic integrations was a faint header input
with only placeholder text, easy to miss. Move it into the form grid
as a proper labeled "Name" field next to the other connection fields.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016kF4hZWEkRCPPvCZTeXxn4

* Add file upload for SSH private key and certificate fields

Lets users pick a key file from disk (e.g. ~/.ssh) instead of pasting
its contents into the Private Key / OPKSSH Certificate fields.

* Fix SSH private key paste corrupting multi-line PEM format

Private Key and Certificate fields were single-line <input> elements,
which strip newlines on paste and corrupt PEM-formatted keys (causing
'Unsupported key format' errors). Render them as multi-line textareas
instead so pasted keys keep their line breaks.

* Add JSON-converted bookmark import file for Archnest data import

Converts homarr-bookmarks.md into the format expected by /api/data/import.

* Auto-populate bookmark icons via favicon service in import JSON

Each bookmark now points to Google's favicon endpoint for its domain
instead of having no icon at all.

* Wire up Profile/Appearance/Security in the user menu

Profile, Appearance, and Security were dead "#" links. Added URL-based
tab deep-linking to the Settings page (?tab=profile|appearance|security|...)
and pointed the menu items at it. Added a Security tab placeholder ahead
of password/sessions/login-log/SSO work.

---------

Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
Samuel James 2026-06-20 09:44:43 -04:00 committed by GitHub
parent d9d9f3f610
commit ea14486f6e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 37 additions and 7 deletions

View file

@ -220,18 +220,27 @@ export default function TopBar() {
<p className="text-[10px] text-text-secondary">{user?.email || user?.username}</p>
</div>
<div className="py-1">
<a href="#" className="flex items-center gap-2.5 px-3 py-2 text-[12px] text-text-secondary hover:text-text-primary hover:bg-page transition-colors no-underline">
<button
onClick={() => { setUserMenuOpen(false); navigate('/settings?tab=profile') }}
className="flex w-full items-center gap-2.5 px-3 py-2 text-[12px] text-text-secondary hover:text-text-primary hover:bg-page transition-colors cursor-pointer border-none bg-transparent text-left"
>
<User size={14} />
<span>Profile</span>
</a>
<a href="#" className="flex items-center gap-2.5 px-3 py-2 text-[12px] text-text-secondary hover:text-text-primary hover:bg-page transition-colors no-underline">
</button>
<button
onClick={() => { setUserMenuOpen(false); navigate('/settings?tab=appearance') }}
className="flex w-full items-center gap-2.5 px-3 py-2 text-[12px] text-text-secondary hover:text-text-primary hover:bg-page transition-colors cursor-pointer border-none bg-transparent text-left"
>
<Palette size={14} />
<span>Appearance</span>
</a>
<a href="#" className="flex items-center gap-2.5 px-3 py-2 text-[12px] text-text-secondary hover:text-text-primary hover:bg-page transition-colors no-underline">
</button>
<button
onClick={() => { setUserMenuOpen(false); navigate('/settings?tab=security') }}
className="flex w-full items-center gap-2.5 px-3 py-2 text-[12px] text-text-secondary hover:text-text-primary hover:bg-page transition-colors cursor-pointer border-none bg-transparent text-left"
>
<Shield size={14} />
<span>Security</span>
</a>
</button>
<button
onClick={() => { setUserMenuOpen(false); navigate('/help') }}
className="flex w-full items-center gap-2.5 px-3 py-2 text-[12px] text-text-secondary hover:text-text-primary hover:bg-page transition-colors cursor-pointer border-none bg-transparent text-left"

View file

@ -1,4 +1,5 @@
import { useEffect, useRef, useState } from 'react'
import { useSearchParams } from 'react-router-dom'
import { api, ApiError, type Integration } from '../lib/api'
import { useAuth } from '../lib/AuthContext'
import {
@ -17,11 +18,13 @@ import {
Camera,
ChevronDown,
ChevronRight,
Shield,
} from 'lucide-react'
const navSections = [
{ id: 'profile', label: 'Profile', icon: User },
{ id: 'appearance', label: 'Appearance', icon: Palette },
{ id: 'security', label: 'Security', icon: Shield },
{ id: 'integrations', label: 'Integrations', icon: Plug },
{ id: 'notifications', label: 'Notifications', icon: Bell },
{ id: 'data', label: 'Data & Backup', icon: Database },
@ -1254,9 +1257,21 @@ function AboutSection() {
)
}
function SecuritySection() {
return (
<div style={cardBase}>
<h3 style={sectionTitle}>Security</h3>
<p style={{ fontSize: '13px', color: '#7A7D85' }}>
Password changes, active sessions, login activity, and SSO are coming soon.
</p>
</div>
)
}
const sectionComponents: Record<string, () => React.ReactElement> = {
profile: ProfileSection,
appearance: AppearanceSection,
security: SecuritySection,
integrations: IntegrationsSection,
notifications: NotificationsSection,
data: DataBackupSection,
@ -1264,9 +1279,15 @@ const sectionComponents: Record<string, () => React.ReactElement> = {
}
export default function Settings() {
const [active, setActive] = useState('profile')
const [searchParams, setSearchParams] = useSearchParams()
const requestedTab = searchParams.get('tab')
const active = requestedTab && sectionComponents[requestedTab] ? requestedTab : 'profile'
const ActiveSection = sectionComponents[active]
function setActive(id: string) {
setSearchParams({ tab: id })
}
return (
<div className="flex h-full w-full gap-5">
{/* Settings nav */}