refactor(frontend): unify theme tokens to single --dg-* source
- remove shadcn HSL dual-track vars (523->416->~300 lines index.css); tailwind semantic colors (border/background/card/primary/destructive/ popover/muted/accent) now bridge directly to var(--dg-*) tokens - drop dead darkMode:['class'] config and inert dark: variants in kept ui components; theme switching is data-theme based (4 themes intact) - first-frame inline theme script in index.html (reads namespaced key with legacy fallback, resolves system, sets color-scheme) - no flash - consolidate duplicated db-type brand colors into lib/constants.ts single source (dbTypeColorMap + dbTypeBrandColorMap, with pg/mongodb aliases); instance health stroke now uses theme tokens - verified via browser: dark #0d1117 / light #f5f6f8 both resolve, tokens cascade correctly
This commit is contained in:
1 parent
a213d08e31
commit
ac563d1c5e
7 files changed
+98
-222
No files matched your search
@@ -5,6 +5,35 @@
|
|||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
||||||
<title>OpsxSQL</title>
|
<title>OpsxSQL</title>
|
||||||
|
<script>
|
||||||
|
// 首帧主题初始化(指南 06 章:读取要在 app 挂载前,防首屏闪烁)
|
||||||
|
// 与 useTheme.tsx 保持同一套取值与解析逻辑
|
||||||
|
(function () {
|
||||||
|
try {
|
||||||
|
var v = localStorage.getItem('opsxsql.theme.mode');
|
||||||
|
if (!v) {
|
||||||
|
var legacy = localStorage.getItem('dg-theme');
|
||||||
|
if (legacy) {
|
||||||
|
v = legacy === 'dark' || legacy === 'midnight-purple' || legacy === 'orange-vitality'
|
||||||
|
? 'ocean-deep'
|
||||||
|
: legacy === 'light' ? 'light-professional' : legacy;
|
||||||
|
localStorage.setItem('opsxsql.theme.mode', v);
|
||||||
|
localStorage.removeItem('dg-theme');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!/^(system|light-professional|ocean-deep|pine-green)$/.test(v || '')) v = 'system';
|
||||||
|
var resolved =
|
||||||
|
v === 'system'
|
||||||
|
? window.matchMedia('(prefers-color-scheme: dark)').matches
|
||||||
|
? 'ocean-deep'
|
||||||
|
: 'light-professional'
|
||||||
|
: v;
|
||||||
|
document.documentElement.setAttribute('data-theme', resolved);
|
||||||
|
document.documentElement.setAttribute('data-theme-choice', v);
|
||||||
|
document.documentElement.style.colorScheme = resolved === 'light-professional' ? 'light' : 'dark';
|
||||||
|
} catch (e) { /* noop */ }
|
||||||
|
})();
|
||||||
|
</script>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="root"></div>
|
<div id="root"></div>
|
||||||
|
|||||||
@@ -5,19 +5,19 @@ import { cva, type VariantProps } from "class-variance-authority"
|
|||||||
import { cn } from "@/lib/utils"
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
const buttonVariants = cva(
|
const buttonVariants = cva(
|
||||||
"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
|
"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20:ring-destructive/40 aria-invalid:border-destructive",
|
||||||
{
|
{
|
||||||
variants: {
|
variants: {
|
||||||
variant: {
|
variant: {
|
||||||
default: "bg-primary text-primary-foreground hover:bg-primary/90",
|
default: "bg-primary text-primary-foreground hover:bg-primary/90",
|
||||||
destructive:
|
destructive:
|
||||||
"bg-destructive text-white hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60",
|
"bg-destructive text-white hover:bg-destructive/90 focus-visible:ring-destructive/20:ring-destructive/40",
|
||||||
outline:
|
outline:
|
||||||
"border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50",
|
"border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground:bg-input/50",
|
||||||
secondary:
|
secondary:
|
||||||
"bg-secondary text-secondary-foreground hover:bg-secondary/80",
|
"bg-secondary text-secondary-foreground hover:bg-secondary/80",
|
||||||
ghost:
|
ghost:
|
||||||
"hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50",
|
"hover:bg-accent hover:text-accent-foreground:bg-accent/50",
|
||||||
link: "text-primary underline-offset-4 hover:underline",
|
link: "text-primary underline-offset-4 hover:underline",
|
||||||
},
|
},
|
||||||
size: {
|
size: {
|
||||||
|
|||||||
+3
-146
@@ -3,89 +3,10 @@
|
|||||||
@tailwind utilities;
|
@tailwind utilities;
|
||||||
|
|
||||||
@layer base {
|
@layer base {
|
||||||
/* ===== Theme 1: Ocean Deep (深蓝专业) — Default ===== */
|
/* ===== Design Tokens(--dg-* 单一事实源;多主题经 data-theme 整组覆盖) =====
|
||||||
|
tailwind 语义色(border/background/card/primary/...)由 tailwind.config.js 桥接到这些变量 */
|
||||||
:root {
|
:root {
|
||||||
--background: 220 13% 5%;
|
--radius: 0.5rem; /* 卡片/按钮/输入框圆角主值 */
|
||||||
--foreground: 210 40% 98%;
|
|
||||||
--card: 220 13% 7%;
|
|
||||||
--card-foreground: 210 40% 98%;
|
|
||||||
--popover: 220 13% 7%;
|
|
||||||
--popover-foreground: 210 40% 98%;
|
|
||||||
--primary: 217 91% 60%;
|
|
||||||
--primary-foreground: 0 0% 100%;
|
|
||||||
--secondary: 220 13% 14%;
|
|
||||||
--secondary-foreground: 210 40% 98%;
|
|
||||||
--muted: 220 13% 14%;
|
|
||||||
--muted-foreground: 215 20% 55%;
|
|
||||||
--accent: 220 13% 14%;
|
|
||||||
--accent-foreground: 210 40% 98%;
|
|
||||||
--destructive: 0 62% 50%;
|
|
||||||
--destructive-foreground: 0 0% 100%;
|
|
||||||
--border: 220 13% 18%;
|
|
||||||
--input: 220 13% 14%;
|
|
||||||
--ring: 217 91% 60%;
|
|
||||||
--radius: 0.625rem;
|
|
||||||
--sidebar-background: 220 13% 7%;
|
|
||||||
--sidebar-foreground: 210 40% 90%;
|
|
||||||
--sidebar-primary: 217 91% 60%;
|
|
||||||
--sidebar-primary-foreground: 0 0% 100%;
|
|
||||||
--sidebar-accent: 220 13% 14%;
|
|
||||||
--sidebar-accent-foreground: 210 40% 98%;
|
|
||||||
--sidebar-border: 220 13% 18%;
|
|
||||||
--sidebar-ring: 217 91% 60%;
|
|
||||||
--dg-bg-primary: #0d1117;
|
|
||||||
--dg-bg-secondary: #161b22;
|
|
||||||
--dg-bg-tertiary: #21262d;
|
|
||||||
--dg-bg-hover: #30363d;
|
|
||||||
--dg-border: #30363d;
|
|
||||||
--dg-border-light: #21262d;
|
|
||||||
--dg-text-primary: #f0f6fc;
|
|
||||||
--dg-text-secondary: #8b949e;
|
|
||||||
--dg-text-muted: #6e7681;
|
|
||||||
--dg-blue: #58a6ff;
|
|
||||||
--dg-green: #3fb950;
|
|
||||||
--dg-red: #f85149;
|
|
||||||
--dg-orange: #f0883e;
|
|
||||||
--dg-purple: #7c8aa5;
|
|
||||||
--dg-cyan: #39c5cf;
|
|
||||||
--dg-yellow: #d29922;
|
|
||||||
--dg-card-hover-border: rgba(88, 166, 255, 0.5);
|
|
||||||
--dg-gradient-start: #2563eb;
|
|
||||||
--dg-gradient-end: #0ea5e9;
|
|
||||||
--dg-gradient-text-start: #60a5fa;
|
|
||||||
--dg-gradient-text-end: #93c5fd;
|
|
||||||
--dg-input-focus-ring: rgba(96, 165, 250, 0.2);
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-theme="ocean-deep"] {
|
|
||||||
--background: 220 13% 5%;
|
|
||||||
--foreground: 210 40% 98%;
|
|
||||||
--card: 220 13% 7%;
|
|
||||||
--card-foreground: 210 40% 98%;
|
|
||||||
--popover: 220 13% 7%;
|
|
||||||
--popover-foreground: 210 40% 98%;
|
|
||||||
--primary: 217 91% 60%;
|
|
||||||
--primary-foreground: 0 0% 100%;
|
|
||||||
--secondary: 220 13% 14%;
|
|
||||||
--secondary-foreground: 210 40% 98%;
|
|
||||||
--muted: 220 13% 14%;
|
|
||||||
--muted-foreground: 215 20% 55%;
|
|
||||||
--accent: 220 13% 14%;
|
|
||||||
--accent-foreground: 210 40% 98%;
|
|
||||||
--destructive: 0 62% 50%;
|
|
||||||
--destructive-foreground: 0 0% 100%;
|
|
||||||
--border: 220 13% 18%;
|
|
||||||
--input: 220 13% 14%;
|
|
||||||
--ring: 217 91% 60%;
|
|
||||||
--radius: 0.625rem;
|
|
||||||
--sidebar-background: 220 13% 7%;
|
|
||||||
--sidebar-foreground: 210 40% 90%;
|
|
||||||
--sidebar-primary: 217 91% 60%;
|
|
||||||
--sidebar-primary-foreground: 0 0% 100%;
|
|
||||||
--sidebar-accent: 220 13% 14%;
|
|
||||||
--sidebar-accent-foreground: 210 40% 98%;
|
|
||||||
--sidebar-border: 220 13% 18%;
|
|
||||||
--sidebar-ring: 217 91% 60%;
|
|
||||||
--dg-bg-primary: #0d1117;
|
--dg-bg-primary: #0d1117;
|
||||||
--dg-bg-secondary: #161b22;
|
--dg-bg-secondary: #161b22;
|
||||||
--dg-bg-tertiary: #21262d;
|
--dg-bg-tertiary: #21262d;
|
||||||
@@ -113,34 +34,6 @@
|
|||||||
/* ===== Theme 2: Light Professional (浅色商务) ===== */
|
/* ===== Theme 2: Light Professional (浅色商务) ===== */
|
||||||
[data-theme="light-professional"],
|
[data-theme="light-professional"],
|
||||||
[data-theme="light"] {
|
[data-theme="light"] {
|
||||||
--background: 220 14% 97%;
|
|
||||||
--foreground: 220 14% 10%;
|
|
||||||
--card: 0 0% 100%;
|
|
||||||
--card-foreground: 220 14% 10%;
|
|
||||||
--popover: 0 0% 100%;
|
|
||||||
--popover-foreground: 220 14% 10%;
|
|
||||||
--primary: 217 91% 60%;
|
|
||||||
--primary-foreground: 0 0% 100%;
|
|
||||||
--secondary: 220 14% 96%;
|
|
||||||
--secondary-foreground: 220 14% 10%;
|
|
||||||
--muted: 220 14% 96%;
|
|
||||||
--muted-foreground: 220 10% 46%;
|
|
||||||
--accent: 220 14% 96%;
|
|
||||||
--accent-foreground: 220 14% 10%;
|
|
||||||
--destructive: 0 72% 51%;
|
|
||||||
--destructive-foreground: 0 0% 100%;
|
|
||||||
--border: 220 13% 91%;
|
|
||||||
--input: 220 13% 91%;
|
|
||||||
--ring: 217 91% 60%;
|
|
||||||
--radius: 0.625rem;
|
|
||||||
--sidebar-background: 0 0% 100%;
|
|
||||||
--sidebar-foreground: 220 14% 20%;
|
|
||||||
--sidebar-primary: 217 91% 60%;
|
|
||||||
--sidebar-primary-foreground: 0 0% 100%;
|
|
||||||
--sidebar-accent: 220 14% 94%;
|
|
||||||
--sidebar-accent-foreground: 220 14% 10%;
|
|
||||||
--sidebar-border: 220 13% 91%;
|
|
||||||
--sidebar-ring: 217 91% 60%;
|
|
||||||
--dg-bg-primary: #f5f6f8;
|
--dg-bg-primary: #f5f6f8;
|
||||||
--dg-bg-secondary: #ffffff;
|
--dg-bg-secondary: #ffffff;
|
||||||
--dg-bg-tertiary: #eef1f5;
|
--dg-bg-tertiary: #eef1f5;
|
||||||
@@ -167,34 +60,6 @@
|
|||||||
|
|
||||||
/* ===== Theme 4: Pine Green (松绿自然) ===== */
|
/* ===== Theme 4: Pine Green (松绿自然) ===== */
|
||||||
[data-theme="pine-green"] {
|
[data-theme="pine-green"] {
|
||||||
--background: 150 25% 5%;
|
|
||||||
--foreground: 140 60% 97%;
|
|
||||||
--card: 150 25% 9%;
|
|
||||||
--card-foreground: 140 60% 97%;
|
|
||||||
--popover: 150 25% 9%;
|
|
||||||
--popover-foreground: 140 60% 97%;
|
|
||||||
--primary: 142 71% 45%;
|
|
||||||
--primary-foreground: 150 25% 5%;
|
|
||||||
--secondary: 150 20% 18%;
|
|
||||||
--secondary-foreground: 140 60% 97%;
|
|
||||||
--muted: 150 20% 18%;
|
|
||||||
--muted-foreground: 150 20% 55%;
|
|
||||||
--accent: 150 20% 18%;
|
|
||||||
--accent-foreground: 140 60% 97%;
|
|
||||||
--destructive: 0 90% 72%;
|
|
||||||
--destructive-foreground: 150 25% 5%;
|
|
||||||
--border: 150 20% 18%;
|
|
||||||
--input: 150 20% 18%;
|
|
||||||
--ring: 142 71% 45%;
|
|
||||||
--radius: 0.625rem;
|
|
||||||
--sidebar-background: 150 25% 7%;
|
|
||||||
--sidebar-foreground: 140 40% 88%;
|
|
||||||
--sidebar-primary: 142 71% 45%;
|
|
||||||
--sidebar-primary-foreground: 150 25% 5%;
|
|
||||||
--sidebar-accent: 150 20% 18%;
|
|
||||||
--sidebar-accent-foreground: 140 60% 97%;
|
|
||||||
--sidebar-border: 150 20% 18%;
|
|
||||||
--sidebar-ring: 142 71% 45%;
|
|
||||||
--dg-bg-primary: #0a1210;
|
--dg-bg-primary: #0a1210;
|
||||||
--dg-bg-secondary: #111f1a;
|
--dg-bg-secondary: #111f1a;
|
||||||
--dg-bg-tertiary: #192c24;
|
--dg-bg-tertiary: #192c24;
|
||||||
@@ -289,14 +154,6 @@
|
|||||||
.scrollbar-thin::-webkit-scrollbar-thumb:hover {
|
.scrollbar-thin::-webkit-scrollbar-thumb:hover {
|
||||||
background: var(--dg-text-muted);
|
background: var(--dg-text-muted);
|
||||||
}
|
}
|
||||||
[data-theme="light-professional"] .scrollbar-thin::-webkit-scrollbar-thumb,
|
|
||||||
[data-theme="light"] .scrollbar-thin::-webkit-scrollbar-thumb {
|
|
||||||
background: #d1d5db;
|
|
||||||
}
|
|
||||||
[data-theme="light-professional"] .scrollbar-thin::-webkit-scrollbar-thumb:hover,
|
|
||||||
[data-theme="light"] .scrollbar-thin::-webkit-scrollbar-thumb:hover {
|
|
||||||
background: #9ca3af;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ===== Design System Utilities ===== */
|
/* ===== Design System Utilities ===== */
|
||||||
|
|
||||||
|
|||||||
@@ -65,9 +65,29 @@ export const statusColorMap: Record<string, string> = {
|
|||||||
rollback: 'bg-orange-500/15 text-orange-400',
|
rollback: 'bg-orange-500/15 text-orange-400',
|
||||||
};
|
};
|
||||||
|
|
||||||
export const dbTypeColorMap: Record<string, string> = {
|
/**
|
||||||
|
* 数据库品牌色(刻意不随主题切换,是产品语义而非主题语义)
|
||||||
|
* 全站唯一源:徽章淡色版 dbTypeColorMap / 实色版 dbTypeBrandColorMap
|
||||||
|
*/
|
||||||
|
const DB_TYPE_BADGE = {
|
||||||
mysql: 'bg-[rgba(0,117,143,0.15)] text-[#26a69a]',
|
mysql: 'bg-[rgba(0,117,143,0.15)] text-[#26a69a]',
|
||||||
postgres: 'bg-[rgba(51,103,145,0.15)] text-[#42a5f5]',
|
postgres: 'bg-[rgba(51,103,145,0.15)] text-[#42a5f5]',
|
||||||
redis: 'bg-[rgba(220,50,32,0.15)] text-[#ef5350]',
|
redis: 'bg-[rgba(220,50,32,0.15)] text-[#ef5350]',
|
||||||
mongo: 'bg-[rgba(71,162,72,0.15)] text-[#66bb6a]',
|
mongo: 'bg-[rgba(71,162,72,0.15)] text-[#66bb6a]',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const dbTypeColorMap: Record<string, string> = {
|
||||||
|
...DB_TYPE_BADGE,
|
||||||
|
postgresql: DB_TYPE_BADGE.postgres,
|
||||||
|
pg: DB_TYPE_BADGE.postgres,
|
||||||
|
mongodb: DB_TYPE_BADGE.mongo,
|
||||||
|
};
|
||||||
|
|
||||||
|
export const dbTypeBrandColorMap: Record<string, string> = {
|
||||||
|
mysql: 'bg-[#00758f]',
|
||||||
|
postgres: 'bg-[#336791]',
|
||||||
|
postgresql: 'bg-[#336791]',
|
||||||
|
redis: 'bg-[#dc382d]',
|
||||||
|
mongo: 'bg-[#47a248]',
|
||||||
|
mongodb: 'bg-[#47a248]',
|
||||||
|
};
|
||||||
@@ -7,7 +7,7 @@ import {
|
|||||||
FileText, ArrowUpRight, ArrowDownRight,
|
FileText, ArrowUpRight, ArrowDownRight,
|
||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
||||||
import { statusMap, statusColorMap, dbTypeColorMap } from '@/lib/constants';
|
import { statusMap, statusColorMap, dbTypeColorMap, dbTypeBrandColorMap } from '@/lib/constants';
|
||||||
import { useTheme } from '@/hooks/useTheme';
|
import { useTheme } from '@/hooks/useTheme';
|
||||||
import { useAuth } from '@/hooks/useAuth';
|
import { useAuth } from '@/hooks/useAuth';
|
||||||
import { dashboardApi } from '@/api/dashboard';
|
import { dashboardApi } from '@/api/dashboard';
|
||||||
@@ -378,10 +378,7 @@ export default function Dashboard() {
|
|||||||
return (
|
return (
|
||||||
<div key={inst.id} className="flex items-center px-5 py-3 border-b border-[var(--dg-border-light)] last:border-0 hover:bg-[var(--dg-bg-hover)] transition-colors cursor-pointer group">
|
<div key={inst.id} className="flex items-center px-5 py-3 border-b border-[var(--dg-border-light)] last:border-0 hover:bg-[var(--dg-bg-hover)] transition-colors cursor-pointer group">
|
||||||
<div className={`w-9 h-9 rounded-lg flex items-center justify-center mr-3.5 flex-shrink-0 ${
|
<div className={`w-9 h-9 rounded-lg flex items-center justify-center mr-3.5 flex-shrink-0 ${
|
||||||
inst.dbType === 'mysql' ? 'bg-[rgba(0,117,143,0.2)] text-[#26a69a]' :
|
dbTypeColorMap[inst.dbType] || dbTypeColorMap.mysql
|
||||||
inst.dbType === 'redis' ? 'bg-[rgba(220,50,32,0.2)] text-[#ef5350]' :
|
|
||||||
inst.dbType === 'postgres' ? 'bg-[rgba(51,103,145,0.2)] text-[#42a5f5]' :
|
|
||||||
'bg-[rgba(71,162,72,0.2)] text-[#66bb6a]'
|
|
||||||
}`}>
|
}`}>
|
||||||
<Database className="w-4.5 h-4.5" />
|
<Database className="w-4.5 h-4.5" />
|
||||||
</div>
|
</div>
|
||||||
@@ -529,10 +526,10 @@ export default function Dashboard() {
|
|||||||
<div className="space-y-2.5">
|
<div className="space-y-2.5">
|
||||||
<div className="text-xs text-[var(--dg-text-muted)] font-medium">查询类型分布</div>
|
<div className="text-xs text-[var(--dg-text-muted)] font-medium">查询类型分布</div>
|
||||||
{(queryStats?.type_distribution ?? [
|
{(queryStats?.type_distribution ?? [
|
||||||
{ label: 'MySQL', count: 0, color: 'bg-[#00758f]' },
|
{ label: 'MySQL', count: 0, color: dbTypeBrandColorMap.mysql },
|
||||||
{ label: 'PostgreSQL', count: 0, color: 'bg-[#336791]' },
|
{ label: 'PostgreSQL', count: 0, color: dbTypeBrandColorMap.postgres },
|
||||||
{ label: 'Redis', count: 0, color: 'bg-[#dc382d]' },
|
{ label: 'Redis', count: 0, color: dbTypeBrandColorMap.redis },
|
||||||
{ label: 'MongoDB', count: 0, color: 'bg-[#47a248]' },
|
{ label: 'MongoDB', count: 0, color: dbTypeBrandColorMap.mongodb },
|
||||||
]).map((item: any) => {
|
]).map((item: any) => {
|
||||||
const total = queryStats?.total || 1;
|
const total = queryStats?.total || 1;
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import {
|
|||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
||||||
import { useActionPermission } from '@/hooks/useAuth';
|
import { useActionPermission } from '@/hooks/useAuth';
|
||||||
|
import { dbTypeColorMap } from '@/lib/constants';
|
||||||
import PageHeader from '@/components/PageHeader';
|
import PageHeader from '@/components/PageHeader';
|
||||||
import EmptyState from '@/components/EmptyState';
|
import EmptyState from '@/components/EmptyState';
|
||||||
import { instanceApi } from '@/api/instance';
|
import { instanceApi } from '@/api/instance';
|
||||||
@@ -51,13 +52,13 @@ type ViewMode = 'grid' | 'list';
|
|||||||
/* ------------------------------------------------------------------ */
|
/* ------------------------------------------------------------------ */
|
||||||
/* DB Brand Icons */
|
/* DB Brand Icons */
|
||||||
|
|
||||||
const dbTypeConfig: Record<string, { icon: React.ElementType; color: string; bg: string; label: string }> = {
|
const dbTypeConfig: Record<string, { icon: React.ElementType; badge: string; label: string }> = {
|
||||||
mysql: { icon: MySQLIcon, color: 'text-[#26a69a]', bg: 'bg-[rgba(0,117,143,0.15)]', label: 'MySQL' },
|
mysql: { icon: MySQLIcon, badge: dbTypeColorMap.mysql, label: 'MySQL' },
|
||||||
postgres: { icon: PostgreSQLIcon, color: 'text-[#42a5f5]', bg: 'bg-[rgba(51,103,145,0.15)]', label: 'PostgreSQL' },
|
postgres: { icon: PostgreSQLIcon, badge: dbTypeColorMap.postgres, label: 'PostgreSQL' },
|
||||||
postgresql: { icon: PostgreSQLIcon, color: 'text-[#42a5f5]', bg: 'bg-[rgba(51,103,145,0.15)]', label: 'PostgreSQL' },
|
postgresql: { icon: PostgreSQLIcon, badge: dbTypeColorMap.postgresql, label: 'PostgreSQL' },
|
||||||
pg: { icon: PostgreSQLIcon, color: 'text-[#42a5f5]', bg: 'bg-[rgba(51,103,145,0.15)]', label: 'PostgreSQL' },
|
pg: { icon: PostgreSQLIcon, badge: dbTypeColorMap.pg, label: 'PostgreSQL' },
|
||||||
redis: { icon: RedisIcon, color: 'text-[#ef5350]', bg: 'bg-[rgba(220,50,32,0.15)]', label: 'Redis' },
|
redis: { icon: RedisIcon, badge: dbTypeColorMap.redis, label: 'Redis' },
|
||||||
mongo: { icon: MongoDBIcon, color: 'text-[#66bb6a]', bg: 'bg-[rgba(71,162,72,0.15)]', label: 'MongoDB' },
|
mongo: { icon: MongoDBIcon, badge: dbTypeColorMap.mongo, label: 'MongoDB' },
|
||||||
};
|
};
|
||||||
|
|
||||||
const envLabel: Record<string, string> = {
|
const envLabel: Record<string, string> = {
|
||||||
@@ -541,7 +542,7 @@ export default function InstancePage() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex items-start justify-between mb-4 pl-6">
|
<div className="flex items-start justify-between mb-4 pl-6">
|
||||||
<div className={`w-9 h-9 rounded-lg ${ic.bg} flex items-center justify-center`}>
|
<div className={`w-9 h-9 rounded-lg ${ic.badge} flex items-center justify-center`}>
|
||||||
<IconComp className="w-5 h-5 object-contain" />
|
<IconComp className="w-5 h-5 object-contain" />
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
@@ -689,7 +690,7 @@ export default function InstancePage() {
|
|||||||
</td>
|
</td>
|
||||||
<td className="px-4 py-3 font-medium">{instance.name}</td>
|
<td className="px-4 py-3 font-medium">{instance.name}</td>
|
||||||
<td className="px-4 py-3">
|
<td className="px-4 py-3">
|
||||||
<span className={`flex items-center gap-1.5 ${ic.color}`}>
|
<span className={`flex items-center gap-1.5 ${ic.badge} px-1.5 rounded-md`}>
|
||||||
<IconComp className="w-4 h-4 object-contain" />
|
<IconComp className="w-4 h-4 object-contain" />
|
||||||
<span className="text-xs">{ic.label}</span>
|
<span className="text-xs">{ic.label}</span>
|
||||||
</span>
|
</span>
|
||||||
@@ -923,7 +924,7 @@ function QuickImportDialog({ onClose, onImported }: { onClose: () => void; onImp
|
|||||||
/* ------------------------------------------------------------------ */
|
/* ------------------------------------------------------------------ */
|
||||||
function InstanceDetail({ instance, onBack, config, canEdit, canDelete, onDelete, onEdit, onClone }: {
|
function InstanceDetail({ instance, onBack, config, canEdit, canDelete, onDelete, onEdit, onClone }: {
|
||||||
instance: InstanceItem; onBack: () => void;
|
instance: InstanceItem; onBack: () => void;
|
||||||
config: { icon: React.ElementType; color: string; bg: string };
|
config: { icon: React.ElementType; badge: string };
|
||||||
canEdit: boolean; canDelete: boolean; onDelete: () => void; onEdit: () => void; onClone?: () => void;
|
canEdit: boolean; canDelete: boolean; onDelete: () => void; onEdit: () => void; onClone?: () => void;
|
||||||
}) {
|
}) {
|
||||||
const [testing, setTesting] = useState(false);
|
const [testing, setTesting] = useState(false);
|
||||||
@@ -965,8 +966,8 @@ function InstanceDetail({ instance, onBack, config, canEdit, canDelete, onDelete
|
|||||||
<ChevronLeft className="w-4 h-4" />
|
<ChevronLeft className="w-4 h-4" />
|
||||||
</button>
|
</button>
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
<div className={`w-10 h-10 rounded-xl ${config.bg} flex items-center justify-center`}>
|
<div className={`w-10 h-10 rounded-xl ${config.badge} flex items-center justify-center`}>
|
||||||
<DetailIcon className={`w-5 h-5 ${config.color}`} />
|
<DetailIcon className="w-5 h-5" />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<h1 className="text-xl font-semibold flex items-center gap-3">
|
<h1 className="text-xl font-semibold flex items-center gap-3">
|
||||||
@@ -1045,7 +1046,7 @@ function InstanceDetail({ instance, onBack, config, canEdit, canDelete, onDelete
|
|||||||
<circle cx="50" cy="50" r="42" fill="none" stroke="var(--dg-bg-tertiary)" strokeWidth="8" />
|
<circle cx="50" cy="50" r="42" fill="none" stroke="var(--dg-bg-tertiary)" strokeWidth="8" />
|
||||||
<circle
|
<circle
|
||||||
cx="50" cy="50" r="42" fill="none"
|
cx="50" cy="50" r="42" fill="none"
|
||||||
stroke={health >= 90 ? '#4ade80' : health >= 70 ? '#facc15' : health >= 50 ? '#fb923c' : '#f87171'}
|
stroke={health >= 90 ? 'var(--dg-green)' : health >= 70 ? 'var(--dg-yellow)' : health >= 50 ? 'var(--dg-orange)' : 'var(--dg-red)'}
|
||||||
strokeWidth="8"
|
strokeWidth="8"
|
||||||
strokeLinecap="round"
|
strokeLinecap="round"
|
||||||
strokeDasharray={`${(health / 100) * 264} 264`}
|
strokeDasharray={`${(health / 100) * 264} 264`}
|
||||||
|
|||||||
+21
-49
@@ -1,52 +1,43 @@
|
|||||||
/** @type {import('tailwindcss').Config} */
|
/** @type {import('tailwindcss').Config} */
|
||||||
module.exports = {
|
module.exports = {
|
||||||
darkMode: ["class"],
|
// 多主题经 data-theme 属性切换(--dg-* 单源),无 class 暗色模式
|
||||||
content: ['./index.html', './src/**/*.{js,ts,jsx,tsx}'],
|
content: ['./index.html', './src/**/*.{js,ts,jsx,tsx}'],
|
||||||
theme: {
|
theme: {
|
||||||
extend: {
|
extend: {
|
||||||
colors: {
|
colors: {
|
||||||
border: "hsl(var(--border))",
|
// 语义色全部派生自 --dg-* 设计 Token(唯一手写源)
|
||||||
input: "hsl(var(--input))",
|
border: "var(--dg-border)",
|
||||||
ring: "hsl(var(--ring))",
|
input: "var(--dg-border)",
|
||||||
background: "hsl(var(--background))",
|
ring: "var(--dg-blue)",
|
||||||
foreground: "hsl(var(--foreground))",
|
background: "var(--dg-bg-primary)",
|
||||||
|
foreground: "var(--dg-text-primary)",
|
||||||
primary: {
|
primary: {
|
||||||
DEFAULT: "hsl(var(--primary))",
|
DEFAULT: "var(--dg-blue)",
|
||||||
foreground: "hsl(var(--primary-foreground))",
|
foreground: "#ffffff",
|
||||||
},
|
},
|
||||||
secondary: {
|
secondary: {
|
||||||
DEFAULT: "hsl(var(--secondary))",
|
DEFAULT: "var(--dg-bg-tertiary)",
|
||||||
foreground: "hsl(var(--secondary-foreground))",
|
foreground: "var(--dg-text-primary)",
|
||||||
},
|
},
|
||||||
destructive: {
|
destructive: {
|
||||||
DEFAULT: "hsl(var(--destructive) / <alpha-value>)",
|
DEFAULT: "var(--dg-red)",
|
||||||
foreground: "hsl(var(--destructive-foreground) / <alpha-value>)",
|
foreground: "#ffffff",
|
||||||
},
|
},
|
||||||
muted: {
|
muted: {
|
||||||
DEFAULT: "hsl(var(--muted))",
|
DEFAULT: "var(--dg-bg-tertiary)",
|
||||||
foreground: "hsl(var(--muted-foreground))",
|
foreground: "var(--dg-text-muted)",
|
||||||
},
|
},
|
||||||
accent: {
|
accent: {
|
||||||
DEFAULT: "hsl(var(--accent))",
|
DEFAULT: "var(--dg-bg-hover)",
|
||||||
foreground: "hsl(var(--accent-foreground))",
|
foreground: "var(--dg-text-primary)",
|
||||||
},
|
},
|
||||||
popover: {
|
popover: {
|
||||||
DEFAULT: "hsl(var(--popover))",
|
DEFAULT: "var(--dg-bg-secondary)",
|
||||||
foreground: "hsl(var(--popover-foreground))",
|
foreground: "var(--dg-text-primary)",
|
||||||
},
|
},
|
||||||
card: {
|
card: {
|
||||||
DEFAULT: "hsl(var(--card))",
|
DEFAULT: "var(--dg-bg-secondary)",
|
||||||
foreground: "hsl(var(--card-foreground))",
|
foreground: "var(--dg-text-primary)",
|
||||||
},
|
|
||||||
sidebar: {
|
|
||||||
DEFAULT: "hsl(var(--sidebar-background))",
|
|
||||||
foreground: "hsl(var(--sidebar-foreground))",
|
|
||||||
primary: "hsl(var(--sidebar-primary))",
|
|
||||||
"primary-foreground": "hsl(var(--sidebar-primary-foreground))",
|
|
||||||
accent: "hsl(var(--sidebar-accent))",
|
|
||||||
"accent-foreground": "hsl(var(--sidebar-accent-foreground))",
|
|
||||||
border: "hsl(var(--sidebar-border))",
|
|
||||||
ring: "hsl(var(--sidebar-ring))",
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
borderRadius: {
|
borderRadius: {
|
||||||
@@ -59,25 +50,6 @@ module.exports = {
|
|||||||
boxShadow: {
|
boxShadow: {
|
||||||
xs: "0 1px 2px 0 rgb(0 0 0 / 0.05)",
|
xs: "0 1px 2px 0 rgb(0 0 0 / 0.05)",
|
||||||
},
|
},
|
||||||
keyframes: {
|
|
||||||
"accordion-down": {
|
|
||||||
from: { height: "0" },
|
|
||||||
to: { height: "var(--radix-accordion-content-height)" },
|
|
||||||
},
|
|
||||||
"accordion-up": {
|
|
||||||
from: { height: "var(--radix-accordion-content-height)" },
|
|
||||||
to: { height: "0" },
|
|
||||||
},
|
|
||||||
"caret-blink": {
|
|
||||||
"0%,70%,100%": { opacity: "1" },
|
|
||||||
"20%,50%": { opacity: "0" },
|
|
||||||
},
|
|
||||||
},
|
|
||||||
animation: {
|
|
||||||
"accordion-down": "accordion-down 0.2s ease-out",
|
|
||||||
"accordion-up": "accordion-up 0.2s ease-out",
|
|
||||||
"caret-blink": "caret-blink 1.25s ease-out infinite",
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
plugins: [require("tailwindcss-animate")],
|
plugins: [require("tailwindcss-animate")],
|
||||||
|
|||||||
Reference in new issue
Block a user