feat(19-03): add credentials route and settings page link
- Create settings.credentials.tsx route using dot-notation pattern matching api-keys - RBAC guard restricts access to tenant_admin and above - Super admin org selector integration for multi-tenant support - Add Credential Profiles card to Settings page under Device Credentials section Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -5,7 +5,7 @@ import { useAuth, isSuperAdmin, isTenantAdmin } from '@/lib/auth'
|
|||||||
import { authApi } from '@/lib/api'
|
import { authApi } from '@/lib/api'
|
||||||
import { getSMTPSettings, updateSMTPSettings, testSMTPSettings, clearWinboxSessions } from '@/lib/settingsApi'
|
import { getSMTPSettings, updateSMTPSettings, testSMTPSettings, clearWinboxSessions } from '@/lib/settingsApi'
|
||||||
import { SMTP_PRESETS } from '@/lib/smtpPresets'
|
import { SMTP_PRESETS } from '@/lib/smtpPresets'
|
||||||
import { User, Shield, Info, Key, Lock, ChevronRight, Download, Trash2, AlertTriangle, Mail, Monitor } from 'lucide-react'
|
import { User, Shield, Info, Key, KeyRound, Lock, ChevronRight, Download, Trash2, AlertTriangle, Mail, Monitor } from 'lucide-react'
|
||||||
import { Button } from '@/components/ui/button'
|
import { Button } from '@/components/ui/button'
|
||||||
import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from '@/components/ui/dialog'
|
import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from '@/components/ui/dialog'
|
||||||
import { Input } from '@/components/ui/input'
|
import { Input } from '@/components/ui/input'
|
||||||
@@ -150,6 +150,23 @@ export function SettingsPage() {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{/* Credential Profiles */}
|
||||||
|
{isTenantAdmin(user) && (
|
||||||
|
<div className="rounded-lg border border-border bg-panel px-4 py-3 space-y-1">
|
||||||
|
<SectionHeader icon={KeyRound} title="Device Credentials" />
|
||||||
|
<Link
|
||||||
|
to="/settings/credentials"
|
||||||
|
className="flex items-center justify-between py-2 px-1 rounded hover:bg-elevated/30 transition-colors group"
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
<span className="text-sm text-text-primary">Credential Profiles</span>
|
||||||
|
<p className="text-xs text-text-muted">Manage shared credentials for RouterOS and SNMP devices</p>
|
||||||
|
</div>
|
||||||
|
<ChevronRight className="h-4 w-4 text-text-muted group-hover:text-text-primary transition-colors" />
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* Maintenance — super_admin only */}
|
{/* Maintenance — super_admin only */}
|
||||||
{isSuperAdmin(user) && (
|
{isSuperAdmin(user) && (
|
||||||
<div className="rounded-lg border border-border bg-panel px-4 py-3 space-y-1">
|
<div className="rounded-lg border border-border bg-panel px-4 py-3 space-y-1">
|
||||||
|
|||||||
46
frontend/src/routes/_authenticated/settings.credentials.tsx
Normal file
46
frontend/src/routes/_authenticated/settings.credentials.tsx
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
import { createFileRoute } from '@tanstack/react-router'
|
||||||
|
import { ShieldAlert, Building2 } from 'lucide-react'
|
||||||
|
import { useAuth, isSuperAdmin, isTenantAdmin } from '@/lib/auth'
|
||||||
|
import { useUIStore } from '@/lib/store'
|
||||||
|
import { CredentialProfilesPage } from '@/components/settings/CredentialProfilesPage'
|
||||||
|
|
||||||
|
export const Route = createFileRoute('/_authenticated/settings/credentials')({
|
||||||
|
component: CredentialProfilesRoute,
|
||||||
|
})
|
||||||
|
|
||||||
|
function CredentialProfilesRoute() {
|
||||||
|
const { user } = useAuth()
|
||||||
|
const { selectedTenantId } = useUIStore()
|
||||||
|
|
||||||
|
const tenantId = isSuperAdmin(user) ? (selectedTenantId ?? '') : (user?.tenant_id ?? '')
|
||||||
|
|
||||||
|
// RBAC: only tenant_admin+ can manage credential profiles
|
||||||
|
if (!isTenantAdmin(user)) {
|
||||||
|
return (
|
||||||
|
<div className="space-y-6 max-w-2xl">
|
||||||
|
<div className="rounded-lg border border-border bg-panel px-6 py-12 text-center">
|
||||||
|
<ShieldAlert className="h-10 w-10 text-text-muted mx-auto mb-3" />
|
||||||
|
<h2 className="text-sm font-medium mb-1">Access Denied</h2>
|
||||||
|
<p className="text-sm text-text-muted">
|
||||||
|
You need tenant admin or higher permissions to manage credential profiles.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="space-y-4 max-w-2xl">
|
||||||
|
{!tenantId ? (
|
||||||
|
<div className="rounded-lg border border-border bg-panel p-8 text-center space-y-2">
|
||||||
|
<Building2 className="h-6 w-6 mx-auto text-text-muted" />
|
||||||
|
<p className="text-sm text-text-muted">
|
||||||
|
Select an organization from the sidebar to manage credential profiles.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<CredentialProfilesPage tenantId={tenantId} />
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user