mirror of
https://github.com/dromara/hertzbeat.git
synced 2026-10-09 18:09:06 +00:00
Refine entity, monitor, alert, settings, observability, and shared operator workflows; add scale guards, route compatibility, release validation, and Product Design coverage. AI assistance: used for draft implementation and test iteration.\nHuman validation: Node 22 production build; 426 frontend files and 3,398 tests; 59-route matrix; release budget 7.84 MiB / 8.00 MiB; focused backend tests and Browser QA.\nRisk notes: local scale proof covers 2,005 entities; sustained concurrent-ingestion SLA remains deployment-specific.
223 lines
6.2 KiB
TypeScript
223 lines
6.2 KiB
TypeScript
'use client';
|
|
|
|
import React, { useMemo } from 'react';
|
|
import CodeMirror from '@uiw/react-codemirror';
|
|
import { basicSetup } from 'codemirror';
|
|
import { html } from '@codemirror/lang-html';
|
|
import { javascript } from '@codemirror/lang-javascript';
|
|
import { json } from '@codemirror/lang-json';
|
|
import { yaml } from '@codemirror/lang-yaml';
|
|
import { EditorState, type Extension } from '@codemirror/state';
|
|
import { oneDark } from '@codemirror/theme-one-dark';
|
|
import { EditorView } from '@codemirror/view';
|
|
import { cn } from '../../lib/utils';
|
|
import { HiddenInput } from './hidden-input';
|
|
|
|
export type HzCodeEditorLanguage = 'yaml' | 'json' | 'html' | 'javascript' | 'shell' | 'text';
|
|
export type HzCodeEditorTheme = 'vs' | 'vs-dark';
|
|
|
|
export type HzCodeEditorProps = Omit<React.HTMLAttributes<HTMLDivElement>, 'onChange'> & {
|
|
value: string;
|
|
onChange?: (value: string) => void;
|
|
language?: HzCodeEditorLanguage;
|
|
theme?: HzCodeEditorTheme;
|
|
readOnly?: boolean;
|
|
loading?: boolean;
|
|
loadingLabel?: React.ReactNode;
|
|
folding?: boolean;
|
|
automaticLayout?: boolean;
|
|
height?: string;
|
|
minHeight?: string;
|
|
placeholder?: string;
|
|
name?: string;
|
|
ariaLabel?: string;
|
|
};
|
|
|
|
const hzCodeEditorTheme = EditorView.theme({
|
|
'&': {
|
|
backgroundColor: '#101217',
|
|
color: '#dbe4f0',
|
|
border: '1px solid #2b3039',
|
|
borderRadius: '3px',
|
|
fontSize: '12px',
|
|
minHeight: 'inherit'
|
|
},
|
|
'&.cm-focused': {
|
|
borderColor: '#4e74f8',
|
|
outline: '2px solid rgba(78,116,248,0.12)'
|
|
},
|
|
'.cm-scroller': {
|
|
fontFamily:
|
|
'ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace',
|
|
lineHeight: '1.6'
|
|
},
|
|
'.cm-content': {
|
|
padding: '8px 0',
|
|
caretColor: '#f8fafc'
|
|
},
|
|
'.cm-line': {
|
|
padding: '0 12px'
|
|
},
|
|
'.cm-gutters': {
|
|
backgroundColor: '#0b0c0e',
|
|
borderRight: '1px solid #252b34',
|
|
color: '#6f7787'
|
|
},
|
|
'.cm-activeLine': {
|
|
backgroundColor: '#151b28'
|
|
},
|
|
'.cm-activeLineGutter': {
|
|
backgroundColor: '#151b28',
|
|
color: '#dbe4f0'
|
|
},
|
|
'.cm-selectionBackground, &.cm-focused .cm-selectionBackground': {
|
|
backgroundColor: 'rgba(78,116,248,0.34)'
|
|
},
|
|
'.cm-placeholder': {
|
|
color: '#858d9a'
|
|
}
|
|
}, {
|
|
dark: true
|
|
});
|
|
|
|
const hzCodeEditorLightTheme = EditorView.theme({
|
|
'&': {
|
|
backgroundColor: '#f8fafc',
|
|
color: '#111827',
|
|
border: '1px solid #cbd5e1',
|
|
borderRadius: '3px',
|
|
fontSize: '12px',
|
|
minHeight: 'inherit'
|
|
},
|
|
'&.cm-focused': {
|
|
borderColor: '#4e74f8',
|
|
outline: '2px solid rgba(78,116,248,0.14)'
|
|
},
|
|
'.cm-scroller': {
|
|
fontFamily:
|
|
'ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace',
|
|
lineHeight: '1.6'
|
|
},
|
|
'.cm-content': {
|
|
padding: '8px 0',
|
|
caretColor: '#1f2937'
|
|
},
|
|
'.cm-line': {
|
|
padding: '0 12px'
|
|
},
|
|
'.cm-gutters': {
|
|
backgroundColor: '#eef2f7',
|
|
borderRight: '1px solid #cbd5e1',
|
|
color: '#64748b'
|
|
},
|
|
'.cm-activeLine': {
|
|
backgroundColor: '#e8f0ff'
|
|
},
|
|
'.cm-activeLineGutter': {
|
|
backgroundColor: '#e8f0ff',
|
|
color: '#1f2937'
|
|
},
|
|
'.cm-selectionBackground, &.cm-focused .cm-selectionBackground': {
|
|
backgroundColor: 'rgba(78,116,248,0.24)'
|
|
},
|
|
'.cm-placeholder': {
|
|
color: '#64748b'
|
|
}
|
|
}, {
|
|
dark: false
|
|
});
|
|
|
|
function getLanguageExtension(language: HzCodeEditorLanguage): Extension | null {
|
|
switch (language) {
|
|
case 'yaml':
|
|
return yaml();
|
|
case 'json':
|
|
return json();
|
|
case 'html':
|
|
return html();
|
|
case 'javascript':
|
|
return javascript();
|
|
case 'shell':
|
|
case 'text':
|
|
default:
|
|
return null;
|
|
}
|
|
}
|
|
|
|
export function HzCodeEditor({
|
|
value,
|
|
onChange,
|
|
language = 'text',
|
|
theme = 'vs-dark',
|
|
readOnly = false,
|
|
loading = false,
|
|
loadingLabel = 'Loading editor',
|
|
folding = true,
|
|
automaticLayout = true,
|
|
height,
|
|
minHeight = '220px',
|
|
placeholder,
|
|
name,
|
|
ariaLabel,
|
|
className,
|
|
style,
|
|
...props
|
|
}: HzCodeEditorProps) {
|
|
const editorHeight = height ?? minHeight;
|
|
const extensions = useMemo(() => {
|
|
const languageExtension = getLanguageExtension(language);
|
|
const themeExtension = theme === 'vs-dark' ? hzCodeEditorTheme : hzCodeEditorLightTheme;
|
|
return [
|
|
basicSetup,
|
|
themeExtension,
|
|
EditorView.lineWrapping,
|
|
...(ariaLabel ? [EditorView.contentAttributes.of({ 'aria-label': ariaLabel })] : []),
|
|
...(languageExtension ? [languageExtension] : []),
|
|
...(readOnly || loading ? [EditorState.readOnly.of(true)] : [])
|
|
];
|
|
}, [ariaLabel, language, loading, readOnly, theme]);
|
|
|
|
return (
|
|
<div
|
|
{...props}
|
|
data-hz-code-editor="codemirror"
|
|
data-hz-code-editor-language={language}
|
|
data-hz-code-editor-theme={theme}
|
|
data-hz-code-editor-loading={loading ? 'true' : 'false'}
|
|
data-hz-code-editor-loading-owner="hz-code-editor"
|
|
data-hz-code-editor-folding={folding ? 'true' : 'false'}
|
|
data-hz-code-editor-automatic-layout={automaticLayout ? 'true' : 'false'}
|
|
data-hz-code-editor-readonly={readOnly || loading ? 'true' : undefined}
|
|
data-hz-code-editor-layout-stability="fixed-editor-height"
|
|
data-hz-code-editor-license="codemirror-mit"
|
|
aria-busy={loading ? 'true' : undefined}
|
|
className={cn('relative min-w-0 overflow-hidden rounded-[3px]', className)}
|
|
style={{ minHeight, ...style }}
|
|
>
|
|
{name ? <HiddenInput name={name} value={value} data-hz-code-editor-value="hidden" /> : null}
|
|
<CodeMirror
|
|
value={value}
|
|
height={editorHeight}
|
|
minHeight={minHeight}
|
|
placeholder={placeholder}
|
|
basicSetup={false}
|
|
theme={theme === 'vs-dark' ? oneDark : 'light'}
|
|
extensions={extensions}
|
|
readOnly={readOnly || loading}
|
|
editable={!readOnly && !loading}
|
|
aria-label={ariaLabel}
|
|
onChange={nextValue => onChange?.(nextValue)}
|
|
/>
|
|
{loading ? (
|
|
<div
|
|
data-hz-code-editor-loading-state="angular-nz-code-editor-loading"
|
|
data-hz-code-editor-loading-state-owner="hz-code-editor"
|
|
className="absolute inset-0 flex items-center justify-center bg-[#0b0c0e]/72 text-[12px] font-semibold text-[#dbe4f0] backdrop-blur-[1px]"
|
|
>
|
|
{loadingLabel}
|
|
</div>
|
|
) : null}
|
|
</div>
|
|
);
|
|
}
|