Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion spectre-frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,9 @@
"type": "module",
"scripts": {
"dev": "vite",
"build": "tsc -b && dotenvx run -f .env -- vite build",
"build": "eslint . && tsc -b && dotenvx run -f .env -- vite build",
"lint": "eslint . --fix",
"check": "eslint . && tsc -b",
"preview": "vite preview",
"generate": "dotenvx run -f .env.local -f .env -- gql-gen --config codegen.ts"
},
Expand Down
58 changes: 58 additions & 0 deletions spectre-frontend/src/components/CopyableValue.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import React, { useState } from 'react'
import type { TooltipProps } from '@heroui/react'
import { Tooltip } from '@heroui/react'
import SvgIcon from '@/components/icon/SvgIcon.tsx'
import Icon from '@/components/icon/icon.ts'

interface CopyableValueProps {
content: string
placement?: TooltipProps['placement']
}
const CopyableValue: React.FC<CopyableValueProps> = ({
content,
placement,
}) => {
const [isCopied, setCopied] = useState(false)
let tooltip: React.ReactNode
if (content.length > 256) {
tooltip = (
<span className="text-warning italic">{content.length} chars</span>
)
} else {
tooltip = content
}

const doCopy = () => {
if (isCopied) {
return
}
setCopied(true)
const clipboardItem = new ClipboardItem({
'text/plain': content,
})
navigator.clipboard.write([clipboardItem]).finally(() => {
setTimeout(() => {
setCopied(false)
}, 1000)
})
}

return (
<div className="flex items-center">
<Tooltip
content={tooltip}
classNames={{ content: 'max-w-96 break-all' }}
placement={placement}
>
<span className="max-w-64 truncate text-ellipsis">{content}</span>
</Tooltip>
<SvgIcon
onClick={doCopy}
icon={isCopied ? Icon.CHECK : Icon.COPY}
className="text-primary-200 hover:text-primary-300 ml-1 cursor-pointer"
/>
</div>
)
}

export default CopyableValue
10 changes: 7 additions & 3 deletions spectre-frontend/src/components/KVGird/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,22 +6,26 @@ interface KVGirdProps {
children: ReactElement<typeof KVGridItem>[] | ReactElement<typeof KVGridItem>
}

const MAX_ROW_COUNT = 4

const KVGird: React.FC<KVGirdProps> = (props) => {
const nodes = Array.isArray(props.children)
? props.children
: [props.children]
return (
<div
className="grid grid-cols-3 text-sm"
className="grid grid-cols-3 gap-y-3 text-sm"
style={{
gridTemplateColumns: `repeat(${nodes.length}, minmax(0, 1fr))`,
gridTemplateColumns: `repeat(${Math.min(nodes.length, MAX_ROW_COUNT)}, minmax(0, 1fr))`,
}}
>
{nodes.map((detail, index) => (
<div
key={index}
className={clsx(
index > 0 ? 'border-l-divider border-l-1 px-3' : undefined,
index > 0 || (index === 0 && nodes.length > MAX_ROW_COUNT)
? 'border-l-divider border-l-1 px-3'
: undefined,
)}
>
{detail}
Expand Down
27 changes: 27 additions & 0 deletions spectre-frontend/src/components/SimpleList.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { Code } from '@heroui/react'

interface SimpleListProps {
entities: string[]
name: string
color?: 'default' | 'primary' | 'secondary' | 'success' | 'warning' | 'danger'
}
const SimpleList: React.FC<SimpleListProps> = ({ entities, color, name }) => {
return (
<div>
<span className="text-sm font-bold">{name}:</span>
{entities.length > 0 ? (
<ul className="mt-3 ml-6 list-disc space-y-2">
{entities.map((entity) => (
<li key={entity}>
<Code color={color}>{entity}</Code>
</li>
))}
</ul>
) : (
<span className="ml-2">无</span>
)}
</div>
)
}

export default SimpleList
Empty file.
2 changes: 2 additions & 0 deletions spectre-frontend/src/components/icon/icon.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ const Icon = {
BOOKMARK: 'fc-bookmark',
LOCK: 'fc-lock',
EXTERNAL: 'fc-arrow-up-right-from-square',
COPY: 'fc-copy',
CHECK: 'fc-check',
} as const

export type Icons = (typeof Icon)[keyof typeof Icon]
Expand Down
22 changes: 22 additions & 0 deletions spectre-frontend/src/components/icon/svg-symbols.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -348,6 +348,28 @@ const SvgSymbols: React.FC = () => {
d="M384 64C366.3 64 352 78.3 352 96C352 113.7 366.3 128 384 128L466.7 128L265.3 329.4C252.8 341.9 252.8 362.2 265.3 374.7C277.8 387.2 298.1 387.2 310.6 374.7L512 173.3L512 256C512 273.7 526.3 288 544 288C561.7 288 576 273.7 576 256L576 96C576 78.3 561.7 64 544 64L384 64zM144 160C99.8 160 64 195.8 64 240L64 496C64 540.2 99.8 576 144 576L400 576C444.2 576 480 540.2 480 496L480 416C480 398.3 465.7 384 448 384C430.3 384 416 398.3 416 416L416 496C416 504.8 408.8 512 400 512L144 512C135.2 512 128 504.8 128 496L128 240C128 231.2 135.2 224 144 224L224 224C241.7 224 256 209.7 256 192C256 174.3 241.7 160 224 160L144 160z"
/>
</svg>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 640 640"
id={Icon.COPY}
>
{/*<!--!Font Awesome Free v7.1.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2026 Fonticons, Inc.-->*/}
<path
fill="currentColor"
d="M288 64C252.7 64 224 92.7 224 128L224 384C224 419.3 252.7 448 288 448L480 448C515.3 448 544 419.3 544 384L544 183.4C544 166 536.9 149.3 524.3 137.2L466.6 81.8C454.7 70.4 438.8 64 422.3 64L288 64zM160 192C124.7 192 96 220.7 96 256L96 512C96 547.3 124.7 576 160 576L352 576C387.3 576 416 547.3 416 512L416 496L352 496L352 512L160 512L160 256L176 256L176 192L160 192z"
/>
</svg>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 640 640"
id={Icon.CHECK}
>
{/*<!--!Font Awesome Free v7.1.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2026 Fonticons, Inc.-->*/}
<path
fill="currentColor"
d="M530.8 134.1C545.1 144.5 548.3 164.5 537.9 178.8L281.9 530.8C276.4 538.4 267.9 543.1 258.5 543.9C249.1 544.7 240 541.2 233.4 534.6L105.4 406.6C92.9 394.1 92.9 373.8 105.4 361.3C117.9 348.8 138.2 348.8 150.7 361.3L252.2 462.8L486.2 141.1C496.6 126.8 516.6 123.6 530.9 134z"
/>
</svg>
</defs>
</svg>,
document.body,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,21 +21,21 @@ const ChannelSvgSymbols: React.FC = () => {
viewBox="0 0 16 16"
>
{/*<!-- Copyright 2000-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. -->*/}
<g fill="none" fill-rule="evenodd">
<g fill="none" fillRule="evenodd">
<polygon
fill="#40B6E0"
fill-opacity=".7"
fillOpacity=".7"
points="1 16 16 16 16 9 1 9"
/>
<polygon fill="#9AA7B0" fill-opacity=".8" points="7 1 3 5 7 5" />
<polygon fill="#9AA7B0" fillOpacity=".8" points="7 1 3 5 7 5" />
<polygon
fill="#9AA7B0"
fill-opacity=".8"
fillOpacity=".8"
points="8 1 8 6 3 6 3 8 13 8 13 1"
/>
<path
fill="#231F20"
fill-opacity=".7"
fillOpacity=".7"
d="M1.39509277,3.58770752 C1.62440186,3.83789062 1.83782861,4 2.28682861,4 C2.81318359,4 3,3.58770752 3,3.29760742 L3,0 L4,0 L4,3.58770752 C4,4.31964111 3.32670898,5 2.45,5 C1.629,5 1.15,4.76264111 0.8,4.31964111 L1.39509277,3.58770752 Z"
transform="translate(2 10)"
/>
Expand Down Expand Up @@ -72,7 +72,7 @@ const ChannelSvgSymbols: React.FC = () => {
xmlns="http://www.w3.org/2000/svg"
>
{/*<!-- Copyright 2000-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. -->*/}
<g clip-path="url(#clip0_16788_11897)">
<g clipPath="url(#clip0_16788_11897)">
<path
d="M8.5 8C8.77614 8 9 8.22386 9 8.5V9.326L9.181 9.16238C9.90358 8.44447 10.9005 8 12.0002 8C13.1046 8 14.1053 8.44826 14.8286 9.17157C15.1887 9.5316 15.4806 9.96029 15.6831 10.4368C15.8159 10.7493 15.9101 11.0822 15.9597 11.4292C15.9988 11.7026 15.8089 11.9559 15.5356 11.995C15.2622 12.0341 15.0089 11.8442 14.9698 11.5708C14.9326 11.3108 14.8621 11.0617 14.7627 10.8279C14.6111 10.4711 14.3922 10.1493 14.1215 9.87868C13.5781 9.33524 12.8288 9 12.0002 9C11.1716 9 10.4223 9.33524 9.87888 9.87868L9.86997 9.88759L9.74562 10H10.5C10.7761 10 11 10.2239 11 10.5C11 10.7761 10.7761 11 10.5 11H8.5C8.22386 11 8 10.7761 8 10.5V8.5C8 8.22386 8.22386 8 8.5 8Z"
fill="#3574F0"
Expand All @@ -82,8 +82,8 @@ const ChannelSvgSymbols: React.FC = () => {
fill="#3574F0"
/>
<path
fill-rule="evenodd"
clip-rule="evenodd"
fillRule="evenodd"
clipRule="evenodd"
d="M5.30078 4.19998C5.30078 4.27878 5.30416 4.3568 5.31077 4.43389C4.839 4.69436 4.43376 5.06054 4.12699 5.5005L2.01731 4.3602C1.77438 4.2289 1.47101 4.31939 1.3397 4.56231C1.2084 4.80524 1.29889 5.10862 1.54181 5.23992L3.67814 6.39462C3.56257 6.74208 3.5 7.11373 3.5 7.5V8.5H1.09961C0.823467 8.5 0.599609 8.72386 0.599609 9C0.599609 9.27614 0.823467 9.5 1.09961 9.5H3.5V10C3.5 10.4891 3.57802 10.9599 3.72229 11.4008L1.52989 12.6665C1.29075 12.8046 1.20881 13.1104 1.34688 13.3496C1.48495 13.5887 1.79075 13.6706 2.02989 13.5326L4.13995 12.3143C4.87222 13.5331 6.16533 14.3765 7.66229 14.4875C7.46893 14.1509 7.31341 13.7893 7.20179 13.4086C5.65341 13.0474 4.5 11.6584 4.5 10V7.5C4.5 6.11929 5.61929 5 7 5H9C10.2198 5 11.2356 5.87365 11.4558 7.02938C11.6346 7.00997 11.8163 7 12.0002 7C12.1578 7 12.3137 7.00732 12.4676 7.02162C12.4381 6.80571 12.3889 6.59608 12.3219 6.39458L14.4551 5.23993C14.698 5.10848 14.7883 4.80506 14.6569 4.5622C14.5254 4.31935 14.222 4.22904 13.9791 4.36048L11.873 5.50046C11.5665 5.06098 11.1618 4.69512 10.6907 4.43472C10.6974 4.35736 10.7008 4.27906 10.7008 4.19998C10.7008 2.70882 9.49195 1.5 8.00078 1.5C6.50961 1.5 5.30078 2.70882 5.30078 4.19998ZM8.00078 2.49999C7.10602 2.49999 6.37271 3.19125 6.30576 4.06885C6.53015 4.02369 6.76231 4 7 4H9C9.23825 4 9.47094 4.02381 9.69582 4.06917C9.62903 3.19141 8.89565 2.49999 8.00078 2.49999Z"
fill="#6C707E"
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -130,11 +130,48 @@ const jad: FormHandle<JadValues> = {
isSync: true,
}

type TTValues = {
index: string
count: number
depth: number
expression: string
extraArgs?: string
}

const tt: FormHandle<TTValues> = {
name: 'Time Tunnel',
buildCommand(values) {
let base = `tt -w '${values.expression}' -x ${values.depth} -i ${values.index}`
if (values.count > 0) {
base += ' -n ' + values.count
}
if (values.extraArgs) {
base += ' ' + values.extraArgs
}
return base
},
defaultValues: {
count: -1,
depth: 3,
expression: DEFAULT_EXPRESSION,
},
items: [
{ name: 'index', isRequired: true, label: 'Index', type: 'number' },
[
{ name: 'count', label: '监听数量', type: 'number' },
{ name: 'depth', label: '递归深度', type: 'number' },
],
{ name: 'expression', label: '表达式' },
{ name: 'extraArgs', label: '额外参数' },
],
}

export const quickCommandHandles = {
watch,
stack,
trace,
jad,
tt,
}

export type QuickCommandKeys = keyof typeof quickCommandHandles
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@ interface QuickCommands {
jad: {
classname: string
}
tt: {
index: number
}
}

type OpenArgs<K extends keyof QuickCommands> =
Expand Down Expand Up @@ -77,6 +80,7 @@ const QuickCommand: React.FC<QuickCommandProps> = (props) => {
const onAction0 = (key: string | number, args: object = {}): boolean => {
const qck = key as QuickCommandKeys
const handle = quickCommandHandles[qck]
console.log('1')
if (!handle) {
return false
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,13 +68,15 @@ const ArthasResponseDetail: React.FC<ArthasResponseDetailProps> = (props) => {
const currentId = props.message.id
return (
<>
<div className="bg-primary-50 text-primary-700 mb-2 px-6 py-3 text-sm">
命令: {props.message.context.command}
</div>
{props.message.context.command ? (
<div className="bg-primary-50 text-primary-700 px-6 py-3 text-sm">
命令: {props.message.context.command}
</div>
) : null}
{/* 策略:对于脏组件,我们全部渲染但在 CSS 上隐藏;对于非脏组件,动态切换 */}
{Array.from(componentCache.current.entries()).map(([id, node]) => (
<div
className="px-2"
className="mt-2 px-2 pb-6"
key={id}
style={{ display: currentId === id ? 'block' : 'none' }}
>
Expand All @@ -84,7 +86,7 @@ const ArthasResponseDetail: React.FC<ArthasResponseDetailProps> = (props) => {

{/* 处理尚未变脏且未进入缓存的新组件 */}
{!componentCache.current.has(currentId) && (
<div className="px-2" key={currentId}>
<div className="mt-2 px-2 pb-6" key={currentId}>
{renderDetail(currentId, Component)}
</div>
)}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import type { DetailComponentProps } from '@/pages/channel/[channelId]/_message_view/factory.ts'

type RowAffectedMessage = {
type: 'row_affect'
jobId: number
rowCount: number
}

const RowAffectDetail: React.FC<DetailComponentProps<RowAffectedMessage>> = (
props,
) => {
return <div>影响了 {props.msg.rowCount} 个类</div>
}

export default RowAffectDetail
Loading