Skip to content

Commit 2de3485

Browse files
better msgpack and text detection and ui fixes
1 parent 5705830 commit 2de3485

File tree

4 files changed

+62
-40
lines changed

4 files changed

+62
-40
lines changed

app/app/page.tsx

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import { KeyList } from "@/components/key/list";
1010
import { KeyDetailsContent } from "@/components/key/content";
1111
import { KeyDetailsHeader } from "@/components/key/header";
1212
import Cluster from "@/components/cluster";
13+
import { useCluster } from "@/hooks/use-cluster";
1314

1415
export default function Home() {
1516
const {
@@ -27,6 +28,7 @@ export default function Home() {
2728
const [view, setView] = useState<"raw" | "parsed">("parsed");
2829
const [searchQuery, setSearchQuery] = useState("");
2930
const [deleteDialogOpen, setDeleteDialogOpen] = useState(false);
31+
const { clusters, listClusters, switchCluster } = useCluster();
3032

3133
const [pdAddrs, setPdAddrs] = useState("");
3234

@@ -39,8 +41,9 @@ export default function Home() {
3941

4042
// Initial load
4143
useEffect(() => {
44+
listClusters();
4245
loadKeys("", "", true);
43-
}, [loadKeys]);
46+
}, [loadKeys, listClusters]);
4447

4548
// Debounced search effect
4649
useEffect(() => {
@@ -77,6 +80,7 @@ export default function Home() {
7780
loadKeys={loadKeys}
7881
getNextKey={getNextKey}
7982
searchQuery={searchQuery}
83+
listClusters={listClusters}
8084
/>
8185
<KeyList
8286
keys={keys}
@@ -103,11 +107,15 @@ export default function Home() {
103107
<KeyDetailsContent selectedItem={selectedItem} view={view} />
104108
) : (
105109
<div className="flex flex-col items-center justify-center gap-2 h-screen opacity-70">
106-
<MousePointerClickIcon size={130} />
110+
<MousePointerClickIcon size={130} className="opacity-50" />
107111
<div>Select a key to view details</div>
108112
</div>
109113
)}
110-
<Cluster onChange={() => loadKeys("", "", true)} />
114+
<Cluster
115+
onChange={() => loadKeys("", "", true)}
116+
switchCluster={switchCluster}
117+
clusters={clusters}
118+
/>
111119
</div>
112120

113121
<DeleteKeyDialog

app/components/cluster.tsx

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import { CheckCircle2Icon, ServerIcon } from "lucide-react";
2-
import { useCluster } from "@/hooks/use-cluster";
32
import { useEffect, useState } from "react";
43
import {
54
Dialog,
@@ -8,21 +7,25 @@ import {
87
DialogTitle,
98
} from "@/components/ui/dialog";
109
import { cn } from "@/lib/utils";
10+
import { ClusterInfo } from "@/hooks/use-cluster";
1111

12-
export default function Cluster({ onChange }: { onChange?: () => void }) {
13-
const { clusters, listClusters, switchCluster } = useCluster();
12+
export default function Cluster({
13+
onChange,
14+
switchCluster,
15+
clusters,
16+
}: {
17+
onChange: () => void;
18+
switchCluster: (name: string) => Promise<void>;
19+
clusters: ClusterInfo[];
20+
}) {
1421
const [open, setOpen] = useState(false);
1522

16-
useEffect(() => {
17-
listClusters();
18-
}, [listClusters]);
19-
2023
const activeCluster = clusters.find((c) => c.active);
2124

2225
const handleSwitch = async (name: string) => {
2326
await switchCluster(name);
24-
setOpen(false);
2527
onChange?.();
28+
setOpen(false);
2629
};
2730

2831
if (!activeCluster && clusters.length === 0) return null;

app/components/sidebar.tsx

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,21 @@ import TiKV from "@/assets/img/tikv.webp";
66
import { SettingsDialog } from "./dialogs/settings";
77
import { AddKeyDialog } from "./dialogs/addkey";
88

9+
interface SidebarProps {
10+
addKey: (key: string, value: string) => Promise<void>;
11+
loadKeys: (key: string, endKey: string, reverse: boolean) => void;
12+
getNextKey: (key: string) => string;
13+
searchQuery: string;
14+
listClusters: () => void;
15+
}
16+
917
export default function Sidebar({
1018
addKey,
1119
loadKeys,
1220
getNextKey,
1321
searchQuery,
14-
}: any) {
22+
listClusters,
23+
}: SidebarProps) {
1524
const [settingsOpen, setSettingsOpen] = useState(false);
1625
const [addDialogOpen, setAddDialogOpen] = useState(false);
1726

@@ -53,7 +62,10 @@ export default function Sidebar({
5362
<SettingsDialog
5463
open={settingsOpen}
5564
onOpenChange={setSettingsOpen}
56-
onChange={() => loadKeys("", "", true)}
65+
onChange={() => {
66+
listClusters();
67+
loadKeys("", "", true);
68+
}}
5769
/>
5870

5971
<AddKeyDialog

pkg/utils/msgpack.go

Lines changed: 26 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -2,40 +2,36 @@ package utils
22

33
import (
44
"encoding/json"
5+
"unicode/utf8"
56

67
"github.com/vmihailenco/msgpack/v5"
78
)
89

9-
// ParseValue attempts to parse a byte slice as msgpack and return a structured value
10-
// If parsing fails, it returns the raw string
10+
// ParseValue attempts to parse a byte slice as msgpack / JSON and return a structured value.
1111
func ParseValue(data []byte) (parsed any, raw string) {
1212
raw = string(data)
1313

14-
// If the data is valid UTF-8 and doesn't contain msgpack markers, treat it as plain text
1514
if isPlainText(data) {
16-
// Try to decode as JSON even if it looks like plain text
1715
var decoded any
1816
if err := json.Unmarshal(data, &decoded); err == nil {
1917
return decoded, raw
2018
}
19+
2120
return raw, raw
2221
}
2322

24-
// Try to decode as msgpack
2523
var decoded any
2624
if err := msgpack.Unmarshal(data, &decoded); err != nil {
27-
// If msgpack fails, try JSON
2825
if err := json.Unmarshal(data, &decoded); err != nil {
29-
// If both fail, return the raw string as the parsed value
3026
return raw, raw
3127
}
3228
return decoded, raw
3329
}
3430

35-
// If msgpack decoded to a simple type (int, float, bool) but the raw data looks like text,
36-
// it's probably a false positive - return the raw string instead
3731
switch decoded.(type) {
38-
case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64, float32, float64:
32+
case int, int8, int16, int32, int64,
33+
uint, uint8, uint16, uint32, uint64,
34+
float32, float64:
3935
if isPlainText(data) {
4036
return raw, raw
4137
}
@@ -44,31 +40,34 @@ func ParseValue(data []byte) (parsed any, raw string) {
4440
return decoded, raw
4541
}
4642

47-
// isPlainText checks if the data appears to be plain text rather than binary/msgpack
43+
// isPlainText checks if the data appears to be plain text (UTF-8, mostly printable)
44+
// rather than binary/msgpack.
4845
func isPlainText(data []byte) bool {
49-
// Check if it's valid UTF-8
50-
if !json.Valid([]byte(`"` + string(data) + `"`)) {
51-
// If not valid as JSON string, check for msgpack markers
52-
if len(data) > 0 {
53-
first := data[0]
54-
// Check for common msgpack markers (maps, arrays, etc.)
55-
if first >= 0x80 && first <= 0x9f {
56-
return false
57-
}
58-
if first >= 0xde {
59-
return false
60-
}
46+
if len(data) == 0 {
47+
return false
48+
}
49+
50+
if !utf8.Valid(data) {
51+
return false
52+
}
53+
54+
controlCount := 0
55+
for _, b := range data {
56+
if b < 0x20 && b != '\n' && b != '\r' && b != '\t' {
57+
controlCount++
6158
}
6259
}
6360

64-
// If it contains mostly printable ASCII/UTF-8 characters, treat as plain text
61+
if controlCount > 0 {
62+
return false
63+
}
64+
6565
printable := 0
6666
for _, b := range data {
67-
if b >= 32 && b <= 126 || b >= 128 {
67+
if (b >= 0x20 && b <= 0x7E) || b >= 0x80 {
6868
printable++
6969
}
7070
}
7171

72-
// If more than 80% is printable, consider it plain text ( 80% is randomly choosen =) )
73-
return len(data) > 0 && float64(printable)/float64(len(data)) > 0.8
72+
return float64(printable)/float64(len(data)) > 0.8
7473
}

0 commit comments

Comments
 (0)