diff --git a/src/app/(dashboard)/bookmarks/page.tsx b/src/app/(dashboard)/bookmarks/page.tsx index 7e08b3e..4d6ffb3 100644 --- a/src/app/(dashboard)/bookmarks/page.tsx +++ b/src/app/(dashboard)/bookmarks/page.tsx @@ -3,6 +3,7 @@ export default function BookmarksPage() { { label: "Productivity", emoji: "💼", + accent: "text-emerald-400", links: [ { name: "Pulse", url: "https://pulse.digital-home.site", desc: "Привычки и задачи", emoji: "💓" }, { name: "Gitea", url: "https://git.digital-home.site", desc: "Git репозитории", emoji: "🐙" }, @@ -11,6 +12,7 @@ export default function BookmarksPage() { { label: "Storage", emoji: "💾", + accent: "text-blue-400", links: [ { name: "Nextcloud", url: "https://cloud.digital-home.site", desc: "Облачное хранилище", emoji: "☁️" }, { name: "Immich", url: "https://photo.digital-home.site", desc: "Фото галерея", emoji: "📸" }, @@ -19,6 +21,7 @@ export default function BookmarksPage() { { label: "Tools", emoji: "🔧", + accent: "text-cyan-400", links: [ { name: "Vaultwarden", url: "https://vault.digital-home.site", desc: "Менеджер паролей", emoji: "🔐" }, { name: "IT-Tools", url: "https://tools.digital-home.site", desc: "Утилиты разработчика", emoji: "🛠️" }, @@ -30,6 +33,7 @@ export default function BookmarksPage() { { label: "AI Subscribe", emoji: "🤖", + accent: "text-violet-400", links: [ { name: "OpenAI Usage", url: "https://chatgpt.com/codex/cloud/settings/usage", desc: "Лимиты OpenAI", emoji: "🤖" }, { name: "Claude Usage", url: "https://claude.ai/settings/usage", desc: "Лимиты Claude", emoji: "✨" }, @@ -42,6 +46,7 @@ export default function BookmarksPage() { { label: "Dev & References", emoji: "💻", + accent: "text-amber-400", links: [ { name: "GitHub", url: "https://github.com/", desc: "Репозитории", emoji: "🐱" }, { name: "Go Playground", url: "https://go.dev/play/", desc: "Тест Go кода", emoji: "🐹" }, @@ -62,18 +67,20 @@ export default function BookmarksPage() { ]; return ( -
-
-

Bookmarks

-

Все ссылки в одном месте

+
+
+

Bookmarks

+

Все ссылки в одном месте

{categories.map((cat) => (
-

- {cat.emoji} - {cat.label} -

+
+

+ {cat.emoji} {cat.label} +

+
+
{cat.links.map((link) => ( -
- {link.emoji} -
+
+ {link.emoji} +
{link.name}
- {link.desc &&
{link.desc}
} -
+ {link.desc &&
{link.desc}
} +
{new URL(link.url).hostname}
diff --git a/src/app/(dashboard)/layout.tsx b/src/app/(dashboard)/layout.tsx index 1aee9f8..53ae786 100644 --- a/src/app/(dashboard)/layout.tsx +++ b/src/app/(dashboard)/layout.tsx @@ -13,9 +13,9 @@ export default async function DashboardLayout({ } return ( -
+
-
+
{children}
diff --git a/src/app/(dashboard)/page.tsx b/src/app/(dashboard)/page.tsx index 19d4483..6dd0013 100644 --- a/src/app/(dashboard)/page.tsx +++ b/src/app/(dashboard)/page.tsx @@ -4,26 +4,28 @@ import { TasksWidget } from "@/components/widgets/TasksWidget"; import { CalendarWidget } from "@/components/widgets/CalendarWidget"; import { ClaudeUsageWidget } from "@/components/widgets/ClaudeUsageWidget"; import { ClaudeApiWidget } from "@/components/widgets/ClaudeApiWidget"; +import { DashboardHeader } from "@/components/widgets/DashboardHeader"; export default function DashboardPage() { return ( -
-
-

Dashboard

-

Добро пожаловать домой

-
+
+ {/* Weather - full width */} {/* Calendar + Tasks */} -
- - +
+
+ +
+
+ +
{/* Claude row */} -
+
diff --git a/src/app/(dashboard)/system/page.tsx b/src/app/(dashboard)/system/page.tsx index f9861b3..74d3208 100644 --- a/src/app/(dashboard)/system/page.tsx +++ b/src/app/(dashboard)/system/page.tsx @@ -20,54 +20,44 @@ interface Metrics { network: { rxBytes: number | null; txBytes: number | null }; } -function GaugeChart({ value, label, color }: { value: number; label: string; color: string }) { - const r = 36; - const circ = 2 * Math.PI * r; - const half = circ / 2; - const offset = half - (value / 100) * half; - +function CircularGauge({ value, size = 96, color, label }: { value: number; size?: number; color: string; label: string }) { + const radius = (size - 14) / 2; + const circumference = 2 * Math.PI * radius; + const offset = circumference - (value / 100) * circumference; + return (
-
- - - + + + -
- {value}% +
+ {value}%
- {label} + {label}
); } function UsageBar({ label, value, color, detail }: { label: string; value: number; color: string; detail?: string }) { return ( -
+
- {label} - {detail || `${value}%`} + {label} + {detail || `${value}%`}
-
+
@@ -112,18 +102,19 @@ export default function SystemPage() { }; return ( -
-
+
+
-

System Monitor

-

+

System Monitor

+

{lastUpdated ? `Обновлено: ${lastUpdated.toLocaleTimeString("ru-RU")}` : "Загрузка..."}

{/* Tabs */} -
+
{TABS.map((tab) => ( @@ -148,30 +140,30 @@ export default function SystemPage() {
{error ? ( -
+
⚠️ Не удалось получить метрики
-
Убедитесь что node_exporter запущен на хосте
-
) : ( <> - {/* Gauges row */} -
-
- - Загрузка + {/* Circular gauges */} +
+
+ + Загрузка системы
{loading ? ( -
- {[1,2,3].map(i =>
)} +
+ {[1,2,3].map(i =>
)}
) : ( -
- - - +
+ + +
)}
@@ -179,90 +171,51 @@ export default function SystemPage() { {/* Stats grid */}
{[ - { - icon: Cpu, - label: "CPU", - value: loading ? "..." : `${metrics?.cpu ?? "—"}%`, - sub: loading ? "" : `Load: ${metrics?.load1?.toFixed(2) ?? "—"} · ${metrics?.cpuCount ?? "?"} cores`, - color: "text-indigo-400", - }, - { - icon: MemoryStick, - label: "RAM", - value: loading ? "..." : `${metrics?.ram?.used ?? "—"} GB`, - sub: loading ? "" : `из ${metrics?.ram?.total ?? "?"} GB`, - color: "text-violet-400", - }, - { - icon: HardDrive, - label: "Disk", - value: loading ? "..." : `${metrics?.disk?.used ?? "—"} GB`, - sub: loading ? "" : `из ${metrics?.disk?.total ?? "?"} GB`, - color: "text-emerald-400", - }, - { - icon: Clock, - label: "Uptime", - value: loading ? "..." : (metrics?.uptime ?? "—"), - sub: "Время работы", - color: "text-amber-400", - }, + { icon: Cpu, label: "CPU", value: loading ? "..." : `${metrics?.cpu ?? "—"}%`, sub: loading ? "" : `Load: ${metrics?.load1?.toFixed(2) ?? "—"} · ${metrics?.cpuCount ?? "?"} cores`, color: "text-indigo-400", accent: "card-accent-blue" }, + { icon: MemoryStick, label: "RAM", value: loading ? "..." : `${metrics?.ram?.used ?? "—"} GB`, sub: loading ? "" : `из ${metrics?.ram?.total ?? "?"} GB`, color: "text-violet-400", accent: "card-accent-violet" }, + { icon: HardDrive, label: "Disk", value: loading ? "..." : `${metrics?.disk?.used ?? "—"} GB`, sub: loading ? "" : `из ${metrics?.disk?.total ?? "?"} GB`, color: "text-emerald-400", accent: "card-accent-emerald" }, + { icon: Clock, label: "Uptime", value: loading ? "..." : (metrics?.uptime ?? "—"), sub: "Время работы", color: "text-amber-400", accent: "card-accent-amber" }, ].map((item) => ( -
+
- {item.label} + {item.label}
{item.value}
- {item.sub &&
{item.sub}
} + {item.sub &&
{item.sub}
}
))}
{/* Usage bars */} {!loading && metrics && ( -
-

Использование ресурсов

- +
+

Использование ресурсов

+ {metrics.ram && ( - + )} {metrics.disk && ( - + )}
)} {/* Network */} {!loading && metrics?.network && ( -
-
+
+
- Сеть (всего) + Сеть (всего)
-
-
↓ Получено
-
{formatBytes(metrics.network.rxBytes)}
+
+
↓ Получено
+
{formatBytes(metrics.network.rxBytes)}
-
-
↑ Отправлено
-
{formatBytes(metrics.network.txBytes)}
+
+
↑ Отправлено
+
{formatBytes(metrics.network.txBytes)}
diff --git a/src/app/globals.css b/src/app/globals.css index ddad068..1b9f054 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -1,27 +1,32 @@ +@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap'); + @tailwind base; @tailwind components; @tailwind utilities; @layer base { :root { - --background: 224 71% 4%; - --foreground: 213 31% 91%; - --card: 224 71% 4%; - --card-foreground: 213 31% 91%; - --border: 216 34% 17%; - --input: 216 34% 17%; + --background: 240 10% 4%; + --foreground: 210 40% 98%; + --card: 240 10% 4%; + --card-foreground: 210 40% 98%; + --border: 240 6% 12%; + --input: 240 6% 12%; --primary: 210 40% 98%; - --primary-foreground: 222.2 47.4% 1.2%; - --secondary: 222.2 47.4% 11.2%; + --primary-foreground: 240 10% 4%; + --secondary: 240 6% 10%; --secondary-foreground: 210 40% 98%; - --muted: 223 47% 11%; - --muted-foreground: 215.4 16.3% 56.9%; - --accent: 216 34% 17%; + --muted: 240 6% 9%; + --muted-foreground: 215 16% 47%; + --accent: 240 6% 12%; --accent-foreground: 210 40% 98%; --destructive: 0 63% 31%; --destructive-foreground: 210 40% 98%; - --ring: 216 34% 17%; - --radius: 0.5rem; + --ring: 240 6% 12%; + --radius: 0.75rem; + --bg-base: #0a0a0f; + --bg-surface: #111118; + --bg-elevated: #1a1a24; } } @@ -30,56 +35,87 @@ @apply border-border; } body { - @apply bg-background text-foreground; - background: radial-gradient(ellipse at top left, rgba(99,102,241,0.08) 0%, transparent 50%), - radial-gradient(ellipse at bottom right, rgba(139,92,246,0.05) 0%, transparent 50%), - hsl(224, 71%, 4%); + background-color: var(--bg-base); + font-family: 'Inter', system-ui, sans-serif; + color: #f8fafc; min-height: 100vh; } } +/* Scrollbar */ +::-webkit-scrollbar { width: 4px; height: 4px; } +::-webkit-scrollbar-track { background: transparent; } +::-webkit-scrollbar-thumb { background: rgba(255,255,255,0.1); border-radius: 2px; } +::-webkit-scrollbar-thumb:hover { background: rgba(255,255,255,0.2); } + +/* Card base style */ +.card { + background: rgba(255,255,255,0.03); + border: 1px solid rgba(255,255,255,0.06); + border-radius: 16px; + backdrop-filter: blur(10px); + transition: all 0.2s ease; +} + +.card:hover { + background: rgba(255,255,255,0.05); + border-color: rgba(255,255,255,0.1); + box-shadow: 0 8px 32px rgba(0,0,0,0.4); +} + +/* Legacy compat */ .glass-card { - background: rgba(15, 23, 42, 0.7); - backdrop-filter: blur(16px); - border: 1px solid rgba(99, 102, 241, 0.15); - border-radius: 12px; + background: rgba(255,255,255,0.03); + border: 1px solid rgba(255,255,255,0.06); + border-radius: 16px; + backdrop-filter: blur(10px); + transition: all 0.2s ease; } - .glass-card:hover { - border-color: rgba(99, 102, 241, 0.3); - transition: border-color 0.2s ease; + background: rgba(255,255,255,0.05); + border-color: rgba(255,255,255,0.1); + box-shadow: 0 8px 32px rgba(0,0,0,0.4); } +.card-accent-blue { border-top: 2px solid #3b82f6; } +.card-accent-violet { border-top: 2px solid #8b5cf6; } +.card-accent-emerald { border-top: 2px solid #10b981; } +.card-accent-amber { border-top: 2px solid #f59e0b; } +.card-accent-cyan { border-top: 2px solid #06b6d4; } +.card-accent-rose { border-top: 2px solid #f43f5e; } + +/* Gradient text */ +.gradient-text { + background: linear-gradient(135deg, #6366f1, #a855f7); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + background-clip: text; +} + +/* Glow effects */ +.glow-violet { box-shadow: 0 0 30px rgba(139,92,246,0.2); } +.glow-blue { box-shadow: 0 0 30px rgba(59,130,246,0.2); } + +/* Sidebar */ .sidebar { - background: rgba(10, 17, 32, 0.85); - backdrop-filter: blur(20px); - border-right: 1px solid rgba(99, 102, 241, 0.12); -} - -::-webkit-scrollbar { - width: 6px; -} -::-webkit-scrollbar-track { - background: rgba(15, 23, 42, 0.3); -} -::-webkit-scrollbar-thumb { - background: rgba(99, 102, 241, 0.3); - border-radius: 3px; -} -::-webkit-scrollbar-thumb:hover { - background: rgba(99, 102, 241, 0.5); + background: #0d0d14; + border-right: 1px solid rgba(255,255,255,0.05); } +/* Status indicators */ .status-online { - @apply bg-green-500; - box-shadow: 0 0 6px rgba(34, 197, 94, 0.5); + background: #10b981; + box-shadow: 0 0 6px rgba(16,185,129,0.5); } - .status-offline { - @apply bg-red-500; - box-shadow: 0 0 6px rgba(239, 68, 68, 0.5); + background: #ef4444; + box-shadow: 0 0 6px rgba(239,68,68,0.5); +} +.status-checking { + background: #6b7280; } -.status-checking { - @apply bg-yellow-500; +@keyframes status-ring { + 0%, 100% { opacity: 0.7; transform: scale(1); } + 50% { opacity: 0; transform: scale(2.5); } } diff --git a/src/components/layout/Sidebar.tsx b/src/components/layout/Sidebar.tsx index 7b1c02c..9168ec6 100644 --- a/src/components/layout/Sidebar.tsx +++ b/src/components/layout/Sidebar.tsx @@ -2,7 +2,7 @@ import Link from "next/link"; import { usePathname } from "next/navigation"; import { signOut } from "next-auth/react"; -import { Home, Server, Bookmark, LogOut, LayoutDashboard } from "lucide-react"; +import { LayoutDashboard, Server, Bookmark, LogOut } from "lucide-react"; import { cn } from "@/lib/utils"; const navItems = [ @@ -17,20 +17,26 @@ export function Sidebar({ userName }: { userName?: string | null }) { return (