import React, { useEffect, useMemo, useRef, useState } from "react"; import Editor from "./MonacoEditor"; import { Alert, Button, Card, Descriptions, Empty, Input, Skeleton, Space, Tag, Typography, } from "antd"; import { FileSearchOutlined, ReloadOutlined, RobotOutlined, } from "@ant-design/icons"; import { useStore } from "../store"; import type { JVMActionDefinition, JVMApplyResult, JVMChangePreview, JVMChangeRequest, JVMAIPlanContext, JVMValueSnapshot, SavedConnection, TabData, } from "../types"; import { buildRpcConnectionConfig } from "../utils/connectionRpcConfig"; import { t as translate } from "../i18n"; import { useOptionalI18n } from "../i18n/provider"; import { buildJVMChangeDraftFromAIPlan, buildJVMAIPlanPrompt, matchesJVMAIPlanTargetTab, type JVMAIChangeDraft, type JVMAIChangePlan, } from "../utils/jvmAiPlan"; import { buildJVMActionPayloadTemplate, buildJVMPreviewApplyRequest, estimateJVMResourceEditorHeight, formatJVMActionDisplayText, formatJVMActionSummary, formatJVMMetadataForDisplay, formatJVMValueForDisplay, JVM_DEFAULT_PAYLOAD_TEMPLATE, resolveJVMActionDisplay, resolveJVMValueEditorLanguage, } from "../utils/jvmResourcePresentation"; import { buildJVMTabTitle } from "../utils/jvmRuntimePresentation"; import JVMModeBadge from "./jvm/JVMModeBadge"; import JVMChangePreviewModal from "./jvm/JVMChangePreviewModal"; import { getJVMWorkspaceCardStyle, JVMWorkspaceHero, JVMWorkspaceShell, } from "./jvm/JVMWorkspaceLayout"; const { Text } = Typography; const DESCRIPTION_STYLES = { label: { width: 120 } } as const; const { TextArea } = Input; const DEFAULT_PAYLOAD_TEXT = JVM_DEFAULT_PAYLOAD_TEMPLATE; type JVMResourceBrowserProps = { tab: TabData; }; type LocalizedError = Error & { userMessage?: string; }; const createLocalizedError = (message: string): LocalizedError => { const error = new Error(message) as LocalizedError; error.userMessage = message; return error; }; const resolveLocalizedErrorMessage = ( error: unknown, fallback: string, ): string => { const userMessage = (error as LocalizedError | undefined)?.userMessage; return typeof userMessage === "string" && userMessage.trim() ? userMessage : fallback; }; const buildJVMRuntimeConfig = ( connection: SavedConnection, providerMode: string, ) => { const sourceJVM = connection.config.jvm || {}; return buildRpcConnectionConfig(connection.config, { jvm: { ...sourceJVM, preferredMode: providerMode, allowedModes: [providerMode], }, }); }; const buildJVMPreviewConfigRevision = (value: unknown): string => { let text = ""; try { text = JSON.stringify(value ?? null); } catch { return "unserializable"; } let hash = 2166136261; for (let index = 0; index < text.length; index += 1) { hash ^= text.charCodeAt(index); hash = Math.imul(hash, 16777619); } return (hash >>> 0).toString(16); }; const buildJVMPreviewRuntimeFingerprint = ( connection: SavedConnection | undefined, providerMode: string, ): string => { const config = connection?.config; const jvm = config?.jvm || {}; return JSON.stringify({ configRevision: buildJVMPreviewConfigRevision(config), type: config?.type || "", host: config?.host || "", port: config?.port || 0, user: config?.user || "", providerMode, environment: jvm.environment || "", readOnly: jvm.readOnly !== false, allowedModes: jvm.allowedModes || [], preferredMode: jvm.preferredMode || "", jmx: { enabled: jvm.jmx?.enabled || false, host: jvm.jmx?.host || "", port: jvm.jmx?.port || 0, username: jvm.jmx?.username || "", domainAllowlist: jvm.jmx?.domainAllowlist || [], }, endpoint: { enabled: jvm.endpoint?.enabled || false, baseUrl: jvm.endpoint?.baseUrl || "", timeoutSeconds: jvm.endpoint?.timeoutSeconds || 0, }, agent: { enabled: jvm.agent?.enabled || false, baseUrl: jvm.agent?.baseUrl || "", timeoutSeconds: jvm.agent?.timeoutSeconds || 0, }, }); }; const buildJVMPreviewContextKey = ( connectionId: string, mode: string, path: string, runtimeFingerprint: string, ): string => `${connectionId}::${mode}::${path}::${runtimeFingerprint}`; const snapshotBlockStyle = (background: string): React.CSSProperties => ({ margin: 0, borderRadius: 8, background, overflow: "auto", }); const formatDraftPayload = (draft: JVMAIChangeDraft): string => { try { return JSON.stringify(draft.payload ?? {}, null, 2); } catch { return "{}"; } }; const resolveDefaultAction = ( actions: JVMActionDefinition[] | undefined, providerMode: "jmx" | "endpoint" | "agent", ): string => { if (actions && actions.length > 0) { return String(actions[0].action || "").trim() || "put"; } if (providerMode === "jmx") { return "set"; } return "put"; }; const normalizePreviewResult = (value: any): JVMChangePreview | null => { if ( value && typeof value === "object" && typeof value.allowed === "boolean" ) { return value as JVMChangePreview; } if (value?.data && typeof value.data.allowed === "boolean") { return value.data as JVMChangePreview; } return null; }; const normalizeApplyResult = (value: any): JVMApplyResult | null => { if (value && typeof value === "object" && typeof value.status === "string") { return value as JVMApplyResult; } if (value?.data && typeof value.data.status === "string") { return value.data as JVMApplyResult; } return null; }; const JVMResourceBrowser: React.FC = ({ tab }) => { const i18n = useOptionalI18n(); const i18nLanguage = i18n?.language; const tr = (key: string, params?: Parameters[1]) => translate(key, params, i18nLanguage); const connection = useStore((state) => state.connections.find((item) => item.id === tab.connectionId), ); const addTab = useStore((state) => state.addTab); const theme = useStore((state) => state.theme); const darkMode = theme === "dark"; const providerMode = (tab.providerMode || connection?.config.jvm?.preferredMode || "jmx") as "jmx" | "endpoint" | "agent"; const resourcePath = String(tab.resourcePath || "").trim(); const readOnly = connection?.config.jvm?.readOnly !== false; const runtimeFingerprint = useMemo( () => buildJVMPreviewRuntimeFingerprint(connection, providerMode), [connection, providerMode], ); const [loading, setLoading] = useState(true); const [snapshot, setSnapshot] = useState(null); const [error, setError] = useState(""); const [action, setAction] = useState(""); const [reason, setReason] = useState(""); const [payloadText, setPayloadText] = useState(DEFAULT_PAYLOAD_TEXT); const [draftSource, setDraftSource] = useState<"manual" | "ai-plan">( "manual", ); const [draftResourceId, setDraftResourceId] = useState(""); const [draftError, setDraftError] = useState(""); const [applyMessage, setApplyMessage] = useState(""); const [previewLoading, setPreviewLoading] = useState(false); const [previewOpen, setPreviewOpen] = useState(false); const [previewResult, setPreviewResult] = useState( null, ); const [previewRequest, setPreviewRequest] = useState( null, ); const [previewRuntimeConfig, setPreviewRuntimeConfig] = useState( null, ); const [previewContextKey, setPreviewContextKey] = useState(""); const [applyLoading, setApplyLoading] = useState(false); const snapshotLoadSequenceRef = useRef(0); const i18nLanguageRef = useRef(i18nLanguage); const previewSequenceRef = useRef(0); const currentPreviewContextKey = buildJVMPreviewContextKey( tab.connectionId, providerMode, resourcePath, runtimeFingerprint, ); const previewContextKeyRef = useRef(currentPreviewContextKey); i18nLanguageRef.current = i18nLanguage; previewContextKeyRef.current = currentPreviewContextKey; const clearPreviewState = () => { setPreviewOpen(false); setPreviewResult(null); setPreviewRequest(null); setPreviewRuntimeConfig(null); setPreviewContextKey(""); }; const displayValue = useMemo(() => formatJVMValueForDisplay(snapshot), [snapshot]); const displayLanguage = useMemo( () => snapshot?.sensitive ? "plaintext" : resolveJVMValueEditorLanguage(snapshot?.format || "", snapshot?.value), [snapshot?.format, snapshot?.sensitive, snapshot?.value], ); const metadataText = useMemo( () => formatJVMMetadataForDisplay(snapshot), [snapshot], ); const metadataLanguage = useMemo( () => snapshot?.sensitive ? "plaintext" : resolveJVMValueEditorLanguage("json", snapshot?.metadata), [snapshot?.metadata, snapshot?.sensitive], ); const supportedActions = useMemo(() => { if (!Array.isArray(snapshot?.supportedActions)) { return [] as JVMActionDefinition[]; } return snapshot.supportedActions.filter( (item) => !!String(item?.action || "").trim(), ); }, [snapshot]); const selectedActionDefinition = useMemo( () => supportedActions.find((item) => item.action === action) || null, [action, supportedActions], ); const selectedActionDisplay = useMemo( () => resolveJVMActionDisplay(selectedActionDefinition || action, i18nLanguage), [action, i18nLanguage, selectedActionDefinition], ); const loadSnapshot = async () => { const loadContextKey = currentPreviewContextKey; const loadLanguage = i18nLanguage; if (!connection) { setLoading(false); setSnapshot(null); setError(tr("jvm_resource.error.connection_missing")); return; } if (!resourcePath) { setLoading(false); setSnapshot(null); setError(tr("jvm_resource.error.resource_path_empty")); return; } const backendApp = (window as any).go?.app?.App; if (typeof backendApp?.JVMGetValue !== "function") { setLoading(false); setSnapshot(null); setError(tr("jvm_resource.error.get_value_unavailable")); return; } const loadSequence = ++snapshotLoadSequenceRef.current; setLoading(true); setError(""); try { const result = await backendApp.JVMGetValue( buildJVMRuntimeConfig(connection, providerMode), resourcePath, ); if ( loadSequence !== snapshotLoadSequenceRef.current || loadContextKey !== previewContextKeyRef.current || loadLanguage !== i18nLanguageRef.current ) { return; } if (!result?.success) { setSnapshot(null); setError(tr("jvm_resource.error.read_failed")); return; } setSnapshot((result.data || null) as JVMValueSnapshot | null); } catch (err: any) { if ( loadSequence !== snapshotLoadSequenceRef.current || loadContextKey !== previewContextKeyRef.current || loadLanguage !== i18nLanguageRef.current ) { return; } setSnapshot(null); setError(tr("jvm_resource.error.read_failed")); } finally { if ( loadSequence === snapshotLoadSequenceRef.current && loadContextKey === previewContextKeyRef.current && loadLanguage === i18nLanguageRef.current ) { setLoading(false); } } }; useEffect(() => { void loadSnapshot(); }, [connection, i18nLanguage, providerMode, resourcePath, runtimeFingerprint, tab.connectionId]); useEffect(() => { setSnapshot(null); setAction(""); setReason(""); setPayloadText(DEFAULT_PAYLOAD_TEXT); setDraftSource("manual"); setDraftResourceId(""); setDraftError(""); setApplyMessage(""); previewSequenceRef.current += 1; clearPreviewState(); }, [currentPreviewContextKey]); useEffect(() => { if (action.trim()) { return; } const nextAction = resolveDefaultAction(supportedActions, providerMode); setAction(nextAction); const nextDefinition = supportedActions.find( (item) => item.action === nextAction, ); if ( String(payloadText || "").trim() === "" || payloadText === DEFAULT_PAYLOAD_TEXT ) { setPayloadText(buildJVMActionPayloadTemplate(nextDefinition, snapshot?.sensitive)); } }, [action, payloadText, providerMode, supportedActions]); useEffect(() => { const handler = (event: Event) => { const detail = (event as CustomEvent).detail as | { plan?: JVMAIChangePlan; targetTabId?: string; connectionId?: string; providerMode?: JVMAIPlanContext["providerMode"]; resourcePath?: string; } | undefined; const plan = detail?.plan; if (!plan || (detail?.targetTabId && detail.targetTabId !== tab.id)) { return; } const planContext = detail?.targetTabId && detail?.connectionId && detail?.providerMode && detail?.resourcePath ? { tabId: detail.targetTabId, connectionId: detail.connectionId, providerMode: detail.providerMode, resourcePath: detail.resourcePath, } : undefined; if (!planContext) { setDraftError(tr("jvm_resource.error.ai_plan_missing_context")); setApplyMessage(""); clearPreviewState(); return; } if (!matchesJVMAIPlanTargetTab(tab, planContext)) { setDraftError(tr("jvm_resource.error.ai_plan_context_mismatch")); setApplyMessage(""); clearPreviewState(); return; } let draftFromPlan: JVMAIChangeDraft; try { draftFromPlan = buildJVMChangeDraftFromAIPlan(plan, tr); } catch { setDraftError(tr("jvm_resource.error.ai_plan_to_draft_failed")); setApplyMessage(""); clearPreviewState(); return; } setDraftResourceId(draftFromPlan.resourceId); setAction(draftFromPlan.action); setReason(draftFromPlan.reason); setPayloadText(formatDraftPayload(draftFromPlan)); setDraftSource(draftFromPlan.source || "ai-plan"); setDraftError(""); setApplyMessage( tr("jvm_resource.message.ai_plan_draft_filled", { resourceId: draftFromPlan.resourceId, }), ); clearPreviewState(); }; window.addEventListener( "gonavi:jvm-apply-ai-plan", handler as EventListener, ); return () => window.removeEventListener( "gonavi:jvm-apply-ai-plan", handler as EventListener, ); }, [ i18nLanguage, resourcePath, tab.connectionId, tab.id, tab.providerMode, tab.type, ]); const handleSelectAction = ( nextAction: string, definition?: JVMActionDefinition | null, ) => { const normalized = String(nextAction || "").trim(); setAction(normalized); if (!normalized) { return; } const currentPayload = String(payloadText || "").trim(); if ( !currentPayload || currentPayload === "{}" || payloadText === DEFAULT_PAYLOAD_TEXT ) { setPayloadText(buildJVMActionPayloadTemplate(definition, snapshot?.sensitive)); } }; const buildDraftPlan = (): JVMChangeRequest => { const trimmedAction = String(action || "").trim() || "put"; const trimmedReason = String(reason || "").trim(); if (!trimmedReason) { throw createLocalizedError(tr("jvm_resource.error.reason_required")); } const rawPayload = String(payloadText || "").trim(); let payload: Record = {}; if (rawPayload) { const parsed = JSON.parse(rawPayload); if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) { throw createLocalizedError( tr("jvm_resource.error.payload_object_required"), ); } payload = parsed as Record; } const resourceId = String(draftResourceId || resourcePath).trim(); if (!resourceId) { throw createLocalizedError(tr("jvm_resource.error.resource_id_empty")); } return { providerMode, resourceId, action: trimmedAction, reason: trimmedReason, source: draftSource, expectedVersion: snapshot?.version || undefined, payload, }; }; const handleOpenAudit = () => { if (!connection) { return; } addTab({ id: `jvm-audit-${connection.id}-${providerMode}`, title: buildJVMTabTitle(connection.name, "audit", providerMode), type: "jvm-audit", connectionId: connection.id, providerMode, }); }; const handleAskAIForPlan = () => { if (!connection) { setDraftError(tr("jvm_resource.error.connection_missing")); return; } const prompt = buildJVMAIPlanPrompt({ connectionName: connection.name, host: connection.config.host, providerMode, resourcePath, readOnly, environment: connection.config.jvm?.environment, snapshot, }, tr); const store = useStore.getState(); const wasClosed = !store.aiPanelVisible; if (wasClosed) { store.setAIPanelVisible(true); } setTimeout( () => { window.dispatchEvent( new CustomEvent("gonavi:ai:inject-prompt", { detail: { prompt } }), ); }, wasClosed ? 350 : 0, ); }; const handlePreview = async () => { if (!connection) { setDraftError(tr("jvm_resource.error.connection_missing")); return; } const backendApp = (window as any).go?.app?.App; if (typeof backendApp?.JVMPreviewChange !== "function") { setDraftError(tr("jvm_resource.error.preview_unavailable")); return; } let draftPlan: JVMChangeRequest; try { draftPlan = buildDraftPlan(); } catch (err) { setDraftError( resolveLocalizedErrorMessage( err, tr("jvm_resource.error.draft_invalid"), ), ); return; } const previewSequence = ++previewSequenceRef.current; const previewContextKey = currentPreviewContextKey; const runtimeConfig = buildJVMRuntimeConfig(connection, providerMode); setPreviewLoading(true); setDraftError(""); setApplyMessage(""); try { const result = await backendApp.JVMPreviewChange( runtimeConfig, draftPlan, ); if ( previewSequence !== previewSequenceRef.current || previewContextKey !== previewContextKeyRef.current ) { return; } if (result?.success === false) { clearPreviewState(); setDraftError( String(result?.message || tr("jvm_resource.error.preview_failed")), ); return; } const preview = normalizePreviewResult(result); if (!preview) { clearPreviewState(); setDraftError(tr("jvm_resource.error.preview_result_invalid")); return; } setPreviewResult(preview); setPreviewRequest(draftPlan); setPreviewRuntimeConfig(runtimeConfig); setPreviewContextKey(previewContextKey); setPreviewOpen(true); } catch (err: any) { clearPreviewState(); setDraftError( err?.message || (typeof err === "string" ? err : "") || tr("jvm_resource.error.preview_failed"), ); } finally { setPreviewLoading(false); } }; const handleApply = async () => { await Promise.resolve(); if (!connection) { setDraftError(tr("jvm_resource.error.connection_missing")); return; } const backendApp = (window as any).go?.app?.App; if (typeof backendApp?.JVMApplyChange !== "function") { setDraftError(tr("jvm_resource.error.apply_unavailable")); return; } if (!previewResult || !previewRequest || !previewRuntimeConfig) { setDraftError(tr("jvm_resource.error.preview_required")); return; } if (previewContextKey !== previewContextKeyRef.current) { clearPreviewState(); setDraftError(tr("jvm_resource.error.context_changed")); return; } let applyRequest: JVMChangeRequest; try { applyRequest = buildJVMPreviewApplyRequest(previewRequest, previewResult); } catch { setDraftError(tr("jvm_resource.error.confirmation_missing")); return; } setApplyLoading(true); setDraftError(""); setApplyMessage(""); try { const result = await backendApp.JVMApplyChange( previewRuntimeConfig, applyRequest, ); if (result?.success === false) { setDraftError( String(result?.message || tr("jvm_resource.error.apply_failed")), ); return; } const applyResult = normalizeApplyResult(result); if (applyResult?.updatedValue) { setSnapshot(applyResult.updatedValue); } clearPreviewState(); setApplyMessage( applyResult?.message || result?.message || tr("jvm_resource.message.apply_success"), ); await loadSnapshot(); } catch (err: any) { setDraftError( err?.message || (typeof err === "string" ? err : "") || tr("jvm_resource.error.apply_failed"), ); } finally { setApplyLoading(false); } }; if (!connection) { return ( ); } const cardStyle = getJVMWorkspaceCardStyle(darkMode); return ( <> {connection.name} ยท {resourcePath || "-"} } badges={ <> {readOnly ? tr("jvm_resource.badge.read_only") : tr("jvm_resource.badge.writable")} } actions={ <> } />
{loading ? ( ) : ( {error ? : null} {snapshot ? ( <> {snapshot.resourceId || "-"} {snapshot.kind || tab.resourceKind || "-"} {snapshot.format || "-"} {snapshot.version || "-"} {formatJVMActionSummary(supportedActions, i18nLanguage)} {snapshot.description ? ( {snapshot.description} ) : null}
{tr("jvm_resource.section.resource_value")}
{metadataText ? (
{tr("jvm_resource.section.metadata")}
) : null} ) : error ? null : ( )}
)}
{!readOnly ? ( {draftError ? ( ) : null} {applyMessage ? ( ) : null} {resourcePath || "-"} {draftResourceId || resourcePath || "-"} {snapshot?.version || "-"} {draftSource === "ai-plan" ? tr("jvm_resource.draft_source.ai_plan") : tr("jvm_resource.draft_source.manual")} {supportedActions.length > 0 ? ( {tr("jvm_resource.section.supported_actions")} {supportedActions.map((item) => ( ))} {selectedActionDisplay.description ? ( {selectedActionDisplay.description} ) : null} {selectedActionDefinition?.payloadFields?.length ? ( {tr("jvm_resource.field.payload_fields")} {selectedActionDefinition.payloadFields .map( (field) => `${field.name}${ field.required ? tr("jvm_resource.marker.required_suffix") : "" }`, ) .join(tr("jvm_resource.list_separator"))} ) : null} ) : null} {tr("jvm_resource.field.action")} handleSelectAction( event.target.value, selectedActionDefinition, ) } placeholder={ providerMode === "jmx" ? tr("jvm_resource.placeholder.action_jmx") : tr("jvm_resource.placeholder.action_default") } maxLength={64} /> {action ? ( {tr("jvm_resource.message.current_action")} {formatJVMActionDisplayText( selectedActionDisplay, i18nLanguage, )} ) : null} {tr("jvm_resource.field.reason")} setReason(event.target.value)} placeholder={tr("jvm_resource.placeholder.reason")} maxLength={200} /> {tr("jvm_resource.field.payload")} {tr("jvm_resource.message.payload_hint")} {selectedActionDefinition?.payloadExample && !snapshot?.sensitive ? ` ${tr("jvm_resource.message.payload_template_applied")}` : ""}