feat: OpsxDB 桌面数据库客户端首版入库

含工作台/连库多引擎字段与 SSH·SSL、结果编辑、Redis 键浏览、
Tauri 桌面与 docs/落地页;排除本地构建产物与 sidecar 二进制。
This commit is contained in:
logan committed 2026-09-19 22:43:45 +08:00
commit f8740d27f7
240 files changed
+21625

No files matched your search

+10
View File
@@ -0,0 +1,10 @@
import React from "react";
import ReactDOM from "react-dom/client";
import { OpsxWorkspace } from "@opsxdb/ui";
import "./styles.css";
ReactDOM.createRoot(document.getElementById("root")!).render(
<React.StrictMode>
<OpsxWorkspace />
</React.StrictMode>,
);
+427
View File
@@ -0,0 +1,427 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
@layer base {
:root {
/* Design tokens v1 — TablePlus/DataGrip density */
color-scheme: dark;
--ops-bg: #0e1116;
--ops-surface: #151a21;
--ops-surface-2: #1a212b;
--ops-border: #2a3340;
--ops-border-soft: #1f2733;
--ops-muted: #8b95a5;
--ops-text: #e8edf5;
--ops-accent: #3b82f6;
--ops-accent-strong: #2563eb;
--ops-row-alt: rgba(26, 33, 43, 0.65);
--ops-toolbar-h: 36px;
--ops-tab-h: 32px;
--ops-row-h: 28px;
--ops-statusbar-h: 24px;
--ops-sidebar-w: 260px;
--ops-font-xs: 11px;
--ops-font-sm: 12.5px;
--ops-font-md: 14px;
--ops-bg-active: color-mix(in srgb, var(--ops-accent) 16%, transparent);
--ops-danger: #ef4444;
--ops-warn: #f59e0b;
}
html.light {
color-scheme: light;
--ops-bg: #f1f5f9;
--ops-surface: #ffffff;
--ops-surface-2: #f8fafc;
--ops-border: #e2e8f0;
--ops-border-soft: #eef2f7;
--ops-muted: #64748b;
--ops-text: #1e293b;
--ops-accent: #0891b2;
--ops-accent-strong: #0e7490;
--ops-row-alt: #f8fafc;
--ops-danger: #dc2626;
--ops-warn: #d97706;
}
* { scrollbar-color: #334155 transparent; scrollbar-width: thin; }
html.light * { scrollbar-color: #cbd5e1 transparent; }
body {
@apply m-0 font-sans antialiased;
background: var(--ops-bg);
color: var(--ops-text);
}
button, input, select, textarea { font: inherit; }
}
@layer components {
.ops-shell {
background: var(--ops-bg);
color: var(--ops-text);
}
.ops-input {
@apply rounded border px-2 py-1 outline-none transition;
font-size: var(--ops-font-sm);
border-color: var(--ops-border);
background: var(--ops-surface-2);
color: var(--ops-text);
}
.ops-input::placeholder { color: var(--ops-muted); opacity: 0.7; }
.ops-input:focus {
border-color: var(--ops-accent);
box-shadow: 0 0 0 1px color-mix(in srgb, var(--ops-accent) 35%, transparent);
}
.ops-btn-primary {
@apply inline-flex items-center justify-center rounded px-2.5 font-semibold text-white shadow-sm transition disabled:cursor-not-allowed disabled:opacity-50;
height: var(--ops-row-h);
font-size: var(--ops-font-sm);
background: var(--ops-accent-strong);
}
.ops-btn-primary:hover:not(:disabled) { background: var(--ops-accent); }
.ops-btn-secondary {
@apply inline-flex items-center justify-center rounded border px-2 transition;
height: var(--ops-row-h);
font-size: var(--ops-font-sm);
border-color: var(--ops-border);
background: var(--ops-surface-2);
color: var(--ops-text);
}
.ops-btn-secondary:hover {
border-color: color-mix(in srgb, var(--ops-accent) 45%, var(--ops-border));
background: color-mix(in srgb, var(--ops-accent) 8%, var(--ops-surface-2));
}
.ops-btn-ghost {
@apply inline-flex items-center justify-center rounded px-1.5 transition;
height: var(--ops-row-h);
font-size: var(--ops-font-sm);
color: var(--ops-muted);
}
.ops-btn-ghost:hover {
color: var(--ops-text);
background: color-mix(in srgb, var(--ops-accent) 10%, transparent);
}
.ops-btn-ai {
@apply rounded border px-1.5 py-0.5 text-[11px] font-medium transition;
border-color: var(--ops-border);
background: var(--ops-surface-2);
color: var(--ops-text);
}
.ops-btn-ai:hover:not(:disabled) {
border-color: color-mix(in srgb, var(--ops-accent) 50%, var(--ops-border));
color: var(--ops-accent);
}
.ops-btn-ai:disabled { opacity: 0.5; cursor: not-allowed; }
.ops-tabbar {
@apply flex shrink-0 items-stretch gap-0.5 border-b px-1;
height: var(--ops-tab-h);
border-color: var(--ops-border);
background: var(--ops-surface-2);
}
.ops-empty {
@apply flex flex-1 flex-col items-center justify-center gap-3 px-6 text-center;
color: var(--ops-muted);
background: var(--ops-bg);
}
.ops-empty h2 {
@apply m-0 font-semibold;
font-size: var(--ops-font-md);
color: var(--ops-text);
}
.ops-empty p {
@apply m-0 max-w-sm;
font-size: var(--ops-font-sm);
}
.ops-conn-row {
@apply flex w-full items-center gap-3 border-b px-3 text-left transition;
min-height: 44px;
border-color: var(--ops-border-soft);
background: var(--ops-surface);
}
.ops-conn-row:hover {
background: color-mix(in srgb, var(--ops-accent) 8%, var(--ops-surface));
}
.ops-section-card {
@apply space-y-2 rounded border p-3;
border-color: var(--ops-border);
background: var(--ops-surface);
}
.ops-section-title {
@apply mb-1 font-semibold;
font-size: var(--ops-font-xs);
color: var(--ops-muted);
letter-spacing: 0.04em;
text-transform: uppercase;
}
.ops-tab {
@apply border-b-2 border-transparent px-2.5 py-1.5 text-[12px] font-medium transition;
color: var(--ops-muted);
}
.ops-tab:hover { color: var(--ops-text); }
.ops-tab-active {
border-color: var(--ops-accent);
color: var(--ops-accent);
}
.ops-code {
@apply font-mono text-[12.5px] leading-[1.45] outline-none;
background: #070b11;
color: #cbd5e1;
border-color: var(--ops-border);
}
html.light .ops-code {
background: #f8fafc;
color: #334155;
}
.ops-panel {
background: var(--ops-surface);
border-color: var(--ops-border);
}
.ops-chrome-header {
@apply flex shrink-0 items-center gap-1.5 border-b px-2.5;
height: var(--ops-toolbar-h);
border-color: var(--ops-border);
background: color-mix(in srgb, var(--ops-surface) 92%, #000);
}
html.light .ops-chrome-header {
background: var(--ops-surface);
}
.ops-section-label {
@apply flex h-7 shrink-0 items-center px-2.5 text-[10px] font-semibold uppercase tracking-[0.06em];
color: var(--ops-muted);
border-color: var(--ops-border);
background: var(--ops-surface-2);
}
.ops-statusbar {
@apply flex shrink-0 items-center gap-2.5 border-t px-2.5;
font-size: var(--ops-font-xs);
height: var(--ops-statusbar-h);
border-color: var(--ops-border);
background: var(--ops-surface-2);
color: var(--ops-muted);
}
.ops-tree-item {
@apply flex w-full items-center gap-1 truncate rounded px-1.5 text-left leading-tight transition;
min-height: var(--ops-row-h);
font-size: var(--ops-font-sm);
color: color-mix(in srgb, var(--ops-text) 78%, var(--ops-muted));
}
.ops-tree-item:hover {
background: color-mix(in srgb, var(--ops-accent) 10%, transparent);
color: var(--ops-accent);
}
.ops-tree-item-active {
background: color-mix(in srgb, var(--ops-accent) 14%, transparent);
color: var(--ops-accent);
}
.ops-grid {
@apply w-full border-collapse text-left text-[12px];
}
.ops-grid th {
@apply sticky top-0 border-b px-1.5 py-[3px] font-semibold whitespace-nowrap;
border-color: var(--ops-border);
background: var(--ops-surface-2);
color: var(--ops-muted);
}
.ops-grid td {
@apply border-b px-1.5 py-[2px] font-mono whitespace-nowrap;
border-color: var(--ops-border-soft);
color: var(--ops-text);
}
.ops-grid tbody tr:nth-child(even) td {
background: var(--ops-row-alt);
}
.ops-grid tbody tr:hover td {
background: color-mix(in srgb, var(--ops-accent) 8%, transparent);
}
.ops-grid tbody tr.ops-row-selected td {
background: var(--ops-bg-active) !important;
}
.ops-grid th {
position: relative;
user-select: none;
}
.ops-grid th .ops-col-resize {
position: absolute;
right: 0;
top: 0;
width: 5px;
height: 100%;
cursor: col-resize;
}
.ops-grid th.ops-sortable {
cursor: pointer;
}
.ops-driver-row {
display: flex;
align-items: center;
gap: 10px;
width: 100%;
min-height: var(--ops-row-h);
padding: 0 10px;
border-bottom: 1px solid var(--ops-border-soft);
text-align: left;
background: transparent;
color: var(--ops-text);
font-size: var(--ops-font-sm);
transition: background 0.1s;
}
.ops-driver-row:hover {
background: var(--ops-bg-active);
}
.ops-driver-row .ops-driver-meta {
margin-left: auto;
font-size: var(--ops-font-xs);
color: var(--ops-muted);
}
.ops-tab-dirty::before {
content: "●";
margin-right: 4px;
font-size: 8px;
color: var(--ops-accent);
}
.ops-grid .ops-null {
color: var(--ops-muted);
font-style: italic;
}
.ops-grid .ops-rownum {
@apply w-7 min-w-[1.75rem] text-right select-none;
color: var(--ops-muted);
background: var(--ops-surface-2) !important;
}
.ops-modal {
@apply overflow-hidden rounded-lg border;
border-color: var(--ops-border);
background: var(--ops-surface);
color: var(--ops-text);
}
.ops-modal-header {
@apply border-b px-3.5 py-2 text-[13px] font-semibold;
border-color: var(--ops-border);
background: var(--ops-surface-2);
}
.ops-modal-body {
@apply px-3.5 py-2.5 text-[13px] leading-relaxed;
color: color-mix(in srgb, var(--ops-text) 90%, var(--ops-muted));
}
.ops-modal-footer {
@apply flex justify-end gap-2 border-t px-3.5 py-2;
border-color: var(--ops-border);
}
.ops-card {
@apply flex flex-col items-center gap-1.5 rounded-md border p-2.5 transition;
border-color: var(--ops-border);
background: color-mix(in srgb, var(--ops-surface-2) 80%, transparent);
}
.ops-card:hover {
border-color: color-mix(in srgb, var(--ops-accent) 55%, var(--ops-border));
background: color-mix(in srgb, var(--ops-accent) 8%, var(--ops-surface-2));
}
details > summary .ops-tree-chevron {
display: inline-block;
transition: transform 0.12s ease;
}
details[open] > summary .ops-tree-chevron {
transform: rotate(90deg);
}
}
.ops-drop-line {
height: 2px;
margin: 1px 4px;
border-radius: 1px;
background: var(--ops-accent);
}
.ops-drop-slot {
border: 1px dashed var(--ops-border);
}
.ops-drop-slot-active {
border-color: var(--ops-accent);
background: color-mix(in srgb, var(--ops-accent) 12%, transparent);
}
.ops-split-x {
width: 5px;
cursor: col-resize;
flex-shrink: 0;
background: transparent;
}
.ops-split-x:hover,
.ops-split-x:active {
background: color-mix(in srgb, var(--ops-accent) 45%, transparent);
}
.ops-split-y {
height: 5px;
cursor: row-resize;
flex-shrink: 0;
background: transparent;
}
.ops-split-y:hover,
.ops-split-y:active {
background: color-mix(in srgb, var(--ops-accent) 45%, transparent);
}
.cm-editor {
height: 100%;
outline: none;
background: transparent;
}
.ops-close-faint {
display: inline-flex;
align-items: center;
justify-content: center;
width: 18px;
height: 18px;
border-radius: 3px;
font-size: 12px;
line-height: 1;
color: color-mix(in srgb, var(--ops-muted) 55%, transparent);
background: transparent;
}
.ops-close-faint:hover {
color: var(--ops-text);
background: color-mix(in srgb, var(--ops-muted) 18%, transparent);
}
.ops-ctx {
position: fixed;
z-index: 90;
min-width: 140px;
padding: 4px;
border-radius: 6px;
border: 1px solid var(--ops-border);
background: var(--ops-surface);
box-shadow: 0 8px 24px rgba(0,0,0,.35);
}
.ops-ctx button {
display: block;
width: 100%;
text-align: left;
padding: 6px 10px;
border-radius: 4px;
font-size: var(--ops-font-sm);
color: var(--ops-text);
background: transparent;
}
.ops-ctx button:hover {
background: var(--ops-bg-active);
}
.ops-tree-group {
font-size: 10px;
font-weight: 600;
letter-spacing: 0.04em;
color: var(--ops-muted);
}
+48
View File
@@ -0,0 +1,48 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
@layer base {
:root { color-scheme: dark; }
html.light { color-scheme: light; }
* { scrollbar-color: #334155 transparent; scrollbar-width: thin; }
body { @apply m-0 bg-[#080d14] font-sans antialiased text-slate-200; }
html.light body { @apply bg-slate-50 text-slate-800; }
button, input, select, textarea { font: inherit; }
}
@layer components {
.ops-input {
@apply rounded-md border border-slate-700 bg-slate-900 px-3 py-2 text-sm text-slate-200 outline-none transition placeholder:text-slate-600 focus:border-cyan-500 focus:ring-1 focus:ring-cyan-500/30;
}
html.light .ops-input {
@apply border-slate-300 bg-white text-slate-800 placeholder:text-slate-400;
}
.ops-btn-primary {
@apply rounded-md bg-cyan-600 px-4 py-2 text-sm font-semibold text-white shadow-sm transition hover:bg-cyan-500 disabled:cursor-not-allowed disabled:opacity-50;
}
.ops-btn-secondary {
@apply rounded-md border border-slate-700 bg-slate-900 px-3 py-1.5 text-sm text-slate-300 transition hover:border-slate-600 hover:bg-slate-800;
}
html.light .ops-btn-secondary {
@apply border-slate-300 bg-white text-slate-700 hover:bg-slate-100;
}
.ops-btn-ai {
@apply rounded-md border border-slate-700 bg-slate-900/80 px-2.5 py-1.5 text-xs font-medium text-slate-300 transition hover:border-cyan-600 hover:bg-cyan-950/40 hover:text-cyan-300;
}
.ops-tab {
@apply border-b-2 border-transparent px-4 py-3 text-xs font-medium text-slate-500 transition hover:text-slate-200;
}
.ops-tab-active {
@apply border-cyan-500 text-cyan-300;
}
.ops-code {
@apply rounded-md border border-slate-800 bg-[#070b11] p-3 font-mono text-xs leading-5 text-slate-300 outline-none focus:border-cyan-700;
}
.ops-panel {
@apply border-slate-800 bg-slate-950/80;
}
html.light .ops-panel {
@apply border-slate-200 bg-white;
}
}