feat(design-preview): упростить страницы — без технической строки источника, дублей и лишних KPI
Убрана строка «Получено… GET /api/…» из шапок страниц (свежесть в кнопке i), дублирующий баннер на «Пользователях», KPI на «Приложениях». Контент ограничен шириной 1480px. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
f486b72884
commit
5de88e18c8
9 files changed
+3
-42
No files matched your search
@@ -52,12 +52,10 @@ export function StatusBadge({ tone, children }: { tone: Tone; children: ReactNod
|
|||||||
export function PageHead({
|
export function PageHead({
|
||||||
title,
|
title,
|
||||||
sub,
|
sub,
|
||||||
source,
|
|
||||||
actions,
|
actions,
|
||||||
}: {
|
}: {
|
||||||
title: string
|
title: string
|
||||||
sub: string
|
sub: string
|
||||||
source: string
|
|
||||||
actions?: ReactNode
|
actions?: ReactNode
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
@@ -65,13 +63,6 @@ export function PageHead({
|
|||||||
<div className="flex min-w-0 flex-col gap-1">
|
<div className="flex min-w-0 flex-col gap-1">
|
||||||
<h1 className="text-2xl font-semibold tracking-tight text-balance">{title}</h1>
|
<h1 className="text-2xl font-semibold tracking-tight text-balance">{title}</h1>
|
||||||
<p className="text-muted-foreground max-w-prose text-sm text-pretty">{sub}</p>
|
<p className="text-muted-foreground max-w-prose text-sm text-pretty">{sub}</p>
|
||||||
<p
|
|
||||||
role="status"
|
|
||||||
className="text-muted-foreground flex flex-wrap items-center gap-1.5 text-xs"
|
|
||||||
>
|
|
||||||
<span className="bg-success size-1.5 rounded-full" aria-hidden />
|
|
||||||
<span>Получено 14:02:11 · {source}</span>
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
{actions ? <div className="flex flex-wrap items-center gap-2">{actions}</div> : null}
|
{actions ? <div className="flex flex-wrap items-center gap-2">{actions}</div> : null}
|
||||||
</header>
|
</header>
|
||||||
|
|||||||
@@ -110,7 +110,6 @@ export function AppLinksPage() {
|
|||||||
<PageHead
|
<PageHead
|
||||||
title="Ссылки приложений"
|
title="Ссылки приложений"
|
||||||
sub="Каталог приложений и переключатель в шапке · общий для всех сервисов семейства"
|
sub="Каталог приложений и переключатель в шапке · общий для всех сервисов семейства"
|
||||||
source="portal_settings.app_switcher_json · публичный GET /api/v1/app-switcher"
|
|
||||||
actions={
|
actions={
|
||||||
<Button variant="outline" onClick={() => toast('Добавление приложения недоступно в макете', { description: 'Каталог фиксирован контрактом: cfdm · vps · bgp · fw · dns · cdn · mm.' })}>
|
<Button variant="outline" onClick={() => toast('Добавление приложения недоступно в макете', { description: 'Каталог фиксирован контрактом: cfdm · vps · bgp · fw · dns · cdn · mm.' })}>
|
||||||
<PlusIcon aria-hidden />
|
<PlusIcon aria-hidden />
|
||||||
|
|||||||
@@ -1,13 +1,11 @@
|
|||||||
import { Link } from '@tanstack/react-router'
|
import { Link } from '@tanstack/react-router'
|
||||||
import { CloudIcon, GlobeIcon, LayoutDashboardIcon, ServerIcon, ShieldIcon, LayoutGridIcon, KeyRoundIcon, FingerprintIcon, ShieldCheckIcon } from 'lucide-react'
|
import { CloudIcon, GlobeIcon, LayoutDashboardIcon, ServerIcon, ShieldIcon } from 'lucide-react'
|
||||||
import { toast } from 'sonner'
|
import { toast } from 'sonner'
|
||||||
import { APPS, type AppId } from '@authportal/shared'
|
import { APPS, type AppId } from '@authportal/shared'
|
||||||
import { KpiStatGrid } from '@/components/reui-kit/kpi-stat-grid'
|
|
||||||
import { QuickActionGrid } from '@/components/reui-kit/quick-action-grid'
|
import { QuickActionGrid } from '@/components/reui-kit/quick-action-grid'
|
||||||
import { Button } from '@authportal/ui/components/button'
|
import { Button } from '@authportal/ui/components/button'
|
||||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@authportal/ui/components/tabs'
|
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@authportal/ui/components/tabs'
|
||||||
import { PageHead } from '../bits'
|
import { PageHead } from '../bits'
|
||||||
import { kpi } from '../kpi'
|
|
||||||
|
|
||||||
const ICONS: Record<AppId, typeof CloudIcon> = {
|
const ICONS: Record<AppId, typeof CloudIcon> = {
|
||||||
cfdm: CloudIcon,
|
cfdm: CloudIcon,
|
||||||
@@ -30,22 +28,12 @@ export function AppsPage() {
|
|||||||
<PageHead
|
<PageHead
|
||||||
title="Приложения"
|
title="Приложения"
|
||||||
sub="Открыть приложение с текущей сессией · SSO handoff без повторного ввода пароля"
|
sub="Открыть приложение с текущей сессией · SSO handoff без повторного ввода пароля"
|
||||||
source="сессия действует до 22:02 · права из базы портала, не из токена"
|
|
||||||
actions={
|
actions={
|
||||||
<Button variant="outline" size="sm" render={<Link to="/design-preview/app-links" />} nativeButton={false}>
|
<Button variant="outline" size="sm" render={<Link to="/design-preview/app-links" />} nativeButton={false}>
|
||||||
Ссылки приложений
|
Ссылки приложений
|
||||||
</Button>
|
</Button>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<KpiStatGrid
|
|
||||||
aria-label="Показатели"
|
|
||||||
items={[
|
|
||||||
kpi('avail', 'Доступно приложений', String(allowed.length), { icon: LayoutGridIcon, tone: 'ok', badge: 'доступ есть', sub: `из ${APPS.length}` }),
|
|
||||||
kpi('jwt', 'JWT SSO', String(allowed.filter((a) => a.authMode === 'jwt').length), { icon: KeyRoundIcon, tone: 'in', badge: 'fragment-токен', sub: 'вход без пароля' }),
|
|
||||||
kpi('oidc', 'OIDC', String(allowed.filter((a) => a.authMode === 'oidc').length), { icon: FingerprintIcon, tone: 'in', badge: 'Technitium DNS', sub: 'внешний RP' }),
|
|
||||||
kpi('role', 'Роль', 'Админ', { icon: ShieldCheckIcon, tone: 'wn', badge: 'is_admin', sub: 'полный доступ' }),
|
|
||||||
]}
|
|
||||||
/>
|
|
||||||
<Tabs defaultValue="all" className="min-w-0">
|
<Tabs defaultValue="all" className="min-w-0">
|
||||||
<TabsList>
|
<TabsList>
|
||||||
<TabsTrigger value="all">Доступные · {allowed.length}</TabsTrigger>
|
<TabsTrigger value="all">Доступные · {allowed.length}</TabsTrigger>
|
||||||
|
|||||||
@@ -72,7 +72,6 @@ export function AuditPage() {
|
|||||||
<PageHead
|
<PageHead
|
||||||
title="Изменения"
|
title="Изменения"
|
||||||
sub="Журнал действий администраторов и приложений · портал, EvoFirewall, EvoBGP, MikrotikManager и другие через ingest"
|
sub="Журнал действий администраторов и приложений · портал, EvoFirewall, EvoBGP, MikrotikManager и другие через ingest"
|
||||||
source="GET /api/v1/admin/audit?kind=changes · ingest от 5 приложений"
|
|
||||||
actions={
|
actions={
|
||||||
<>
|
<>
|
||||||
<Button variant="outline" onClick={() => toast.success('CSV сформирован (макет)')}>
|
<Button variant="outline" onClick={() => toast.success('CSV сформирован (макет)')}>
|
||||||
|
|||||||
@@ -65,7 +65,6 @@ export function LoginsPage() {
|
|||||||
<PageHead
|
<PageHead
|
||||||
title="Входы"
|
title="Входы"
|
||||||
sub="Журнал входов и активные сессии · портал и SSO в приложения"
|
sub="Журнал входов и активные сессии · портал и SSO в приложения"
|
||||||
source="GET /api/v1/admin/audit?kind=logins · хранение 90 дней"
|
|
||||||
actions={
|
actions={
|
||||||
<Button variant="outline" onClick={() => toast.success('CSV сформирован (макет)')}>
|
<Button variant="outline" onClick={() => toast.success('CSV сформирован (макет)')}>
|
||||||
<DownloadIcon aria-hidden />
|
<DownloadIcon aria-hidden />
|
||||||
|
|||||||
@@ -98,7 +98,6 @@ export function OidcPage() {
|
|||||||
<PageHead
|
<PageHead
|
||||||
title="OIDC-клиенты"
|
title="OIDC-клиенты"
|
||||||
sub="Портал как провайдер OpenID Connect · внешние приложения входят через auth.shnt.top"
|
sub="Портал как провайдер OpenID Connect · внешние приложения входят через auth.shnt.top"
|
||||||
source="discovery и JWKS доступны · подпись RS256"
|
|
||||||
actions={
|
actions={
|
||||||
<Button onClick={() => setCreateOpen(true)}>
|
<Button onClick={() => setCreateOpen(true)}>
|
||||||
<PlusIcon aria-hidden />
|
<PlusIcon aria-hidden />
|
||||||
|
|||||||
@@ -37,7 +37,6 @@ export function SecurityPage() {
|
|||||||
<PageHead
|
<PageHead
|
||||||
title="Безопасность"
|
title="Безопасность"
|
||||||
sub="Passkey как альтернатива паролю · пароль остаётся запасным входом"
|
sub="Passkey как альтернатива паролю · пароль остаётся запасным входом"
|
||||||
source="WebAuthn · домен auth.shnt.top"
|
|
||||||
actions={
|
actions={
|
||||||
<Button onClick={() => setAddOpen(true)}>
|
<Button onClick={() => setAddOpen(true)}>
|
||||||
<PlusIcon aria-hidden />
|
<PlusIcon aria-hidden />
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import { UsersIcon, ShieldCheckIcon, FingerprintIcon, UserXIcon } from 'lucide-react'
|
import { UsersIcon, ShieldCheckIcon, FingerprintIcon, UserXIcon } from 'lucide-react'
|
||||||
import { useCallback, useEffect, useRef, useState } from 'react'
|
import { useCallback, useEffect, useRef, useState } from 'react'
|
||||||
import { Link } from '@tanstack/react-router'
|
|
||||||
import { toast } from 'sonner'
|
import { toast } from 'sonner'
|
||||||
import type { AdminUser, CreateUserRequest } from '@authportal/shared'
|
import type { AdminUser, CreateUserRequest } from '@authportal/shared'
|
||||||
import { CreateUserSheet } from '@/components/reui-kit/create-user-sheet'
|
import { CreateUserSheet } from '@/components/reui-kit/create-user-sheet'
|
||||||
@@ -10,7 +9,7 @@ import { Tabs, TabsContent, TabsList, TabsTrigger } from '@authportal/ui/compone
|
|||||||
import { UserPlusIcon } from 'lucide-react'
|
import { UserPlusIcon } from 'lucide-react'
|
||||||
import { AccessDialog } from '../access-dialog'
|
import { AccessDialog } from '../access-dialog'
|
||||||
import { diffAccess, type AccessValue } from '../access-model'
|
import { diffAccess, type AccessValue } from '../access-model'
|
||||||
import { CodeBlock, KvList, PageHead, PreviewSheet, SignalAlert, TypedConfirm } from '../bits'
|
import { CodeBlock, KvList, PageHead, PreviewSheet, TypedConfirm } from '../bits'
|
||||||
import { kpi } from '../kpi'
|
import { kpi } from '../kpi'
|
||||||
import { ALL_APP_IDS, APP_SHORT, MOCK_USERS, USER_JOURNAL, type Tone } from '../mock'
|
import { ALL_APP_IDS, APP_SHORT, MOCK_USERS, USER_JOURNAL, type Tone } from '../mock'
|
||||||
import { PreviewSectionGrid, TwoLineCell, type GridColumn } from '../section-grid'
|
import { PreviewSectionGrid, TwoLineCell, type GridColumn } from '../section-grid'
|
||||||
@@ -121,7 +120,6 @@ export function UsersPage() {
|
|||||||
<PageHead
|
<PageHead
|
||||||
title="Пользователи"
|
title="Пользователи"
|
||||||
sub="Каталог учётных записей · роли, приложения, passkeys · изменения прав записываются в журнал"
|
sub="Каталог учётных записей · роли, приложения, passkeys · изменения прав записываются в журнал"
|
||||||
source={`GET /api/v1/admin/users · ${users.length} учётных записей`}
|
|
||||||
actions={
|
actions={
|
||||||
<Button onClick={() => setCreateOpen(true)}>
|
<Button onClick={() => setCreateOpen(true)}>
|
||||||
<UserPlusIcon aria-hidden />
|
<UserPlusIcon aria-hidden />
|
||||||
@@ -129,17 +127,6 @@ export function UsersPage() {
|
|||||||
</Button>
|
</Button>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<SignalAlert
|
|
||||||
tone="er"
|
|
||||||
title="Подозрительная активность: ops@shnt.top."
|
|
||||||
action={
|
|
||||||
<Button variant="outline" size="sm" render={<Link to="/design-preview/logins" />} nativeButton={false}>
|
|
||||||
Показать
|
|
||||||
</Button>
|
|
||||||
}
|
|
||||||
>
|
|
||||||
6 неудачных входов с 203.0.113.58 за 10 минут. Проверьте журнал входов и при необходимости отключите учётную запись.
|
|
||||||
</SignalAlert>
|
|
||||||
<KpiStatGrid
|
<KpiStatGrid
|
||||||
aria-label="Показатели"
|
aria-label="Показатели"
|
||||||
items={[
|
items={[
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ export function PreviewShell({ children }: { children: ReactNode }) {
|
|||||||
<PreviewSidebar />
|
<PreviewSidebar />
|
||||||
<SidebarInset id="main-content" className="min-w-0">
|
<SidebarInset id="main-content" className="min-w-0">
|
||||||
<PreviewHeader />
|
<PreviewHeader />
|
||||||
<div className="flex min-w-0 flex-1 flex-col gap-4 px-4 py-4 pb-24 md:gap-6 md:px-6 md:py-5 md:pb-24">
|
<div className="mx-auto flex w-full min-w-0 max-w-[1480px] flex-1 flex-col gap-4 px-4 py-4 pb-24 md:gap-6 md:px-6 md:py-5 md:pb-24">
|
||||||
{children}
|
{children}
|
||||||
</div>
|
</div>
|
||||||
</SidebarInset>
|
</SidebarInset>
|
||||||
|
|||||||
Reference in new issue
Block a user