feat(nacos): add browser console open, zen mode and metadata view
This commit is contained in:
18 files changed
+335
-14
No files matched your search
@@ -599,10 +599,14 @@ async function detachTab(tab: QueryTab, position?: { x: number; y: number }) {
|
||||
watch(
|
||||
() => activeTab.value?.mode,
|
||||
(mode) => {
|
||||
if (mode !== "data") isZenMode.value = false;
|
||||
if (!supportsZenMode(mode)) isZenMode.value = false;
|
||||
},
|
||||
);
|
||||
|
||||
function supportsZenMode(mode: QueryTab["mode"] | undefined) {
|
||||
return mode === "data" || mode === "nacos";
|
||||
}
|
||||
|
||||
watch(activeOutputView, (view) => {
|
||||
if (isDetachedWindowContext && detachedContextTabId) {
|
||||
void queryStore.flushDetachedTabPersistence(detachedContextTabId, { activeOutputView: view }).catch(() => {});
|
||||
@@ -610,7 +614,7 @@ watch(activeOutputView, (view) => {
|
||||
});
|
||||
|
||||
function toggleZenMode() {
|
||||
if (activeTab.value?.mode !== "data") return;
|
||||
if (!supportsZenMode(activeTab.value?.mode)) return;
|
||||
isZenMode.value = !isZenMode.value;
|
||||
}
|
||||
|
||||
@@ -2953,7 +2957,7 @@ async function handleKeydown(e: KeyboardEvent) {
|
||||
setSidebarOpen(!sidebarOpen.value);
|
||||
return;
|
||||
}
|
||||
if (isToggleZenModeShortcut(e, shortcuts) && activeTab.value?.mode === "data") {
|
||||
if (isToggleZenModeShortcut(e, shortcuts) && supportsZenMode(activeTab.value?.mode)) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
toggleZenMode();
|
||||
@@ -3487,6 +3491,7 @@ onUnmounted(() => {
|
||||
:selected-sql="selectedSql"
|
||||
:cursor-pos="cursorPos"
|
||||
:block-dangerous-redis-commands="blockDangerousRedisCommands"
|
||||
:zen-mode="isZenMode"
|
||||
@update:active-output-view="activeOutputView = $event"
|
||||
@fix-with-ai="fixWithAi"
|
||||
@send-selection-to-ai="sendSelectionToAi"
|
||||
@@ -3544,6 +3549,7 @@ onUnmounted(() => {
|
||||
@structure-editor-close="activeTab && queryStore.closeTab(activeTab.id)"
|
||||
@open-settings="openSettings"
|
||||
@open-connection-settings="openConnectionSettings"
|
||||
@toggle-zen-mode="toggleZenMode"
|
||||
/>
|
||||
</KeepAlive>
|
||||
</div>
|
||||
|
||||
@@ -72,7 +72,7 @@ import { normalizeRabbitmqAddresses, parseRabbitmqAddress } from "@/lib/connecti
|
||||
import { detectMqUiAuthKind, isMqAuthKindAllowedForSystem, type MqUiAuthKind } from "@/lib/connection/mqAuth";
|
||||
import { driverInstallProgressChannel, driverInstallProgressPercent, isDriverInstallProgressForOperation, requestAgentInstallCancellation, resolveAgentInstallOutcome, type DriverInstallProgress } from "@/lib/connection/driverInstallProgressUi";
|
||||
import { requiresSqlServerLegacyCompatibilityComponent, setSqlServerLegacyCompatibilityConfig, sqlServerUsesLegacyCompatibility, SQLSERVER_LEGACY_COMPATIBILITY_DRIVER_KEY } from "@/lib/connection/sqlServerLegacyCompatibility";
|
||||
import { normalizeNacosEndpoint, normalizeNacosMetricsUrl, parseNacosManagedNamespaces } from "@/lib/nacos/nacosAdmin";
|
||||
import { normalizeNacosConsoleUrl, normalizeNacosEndpoint, normalizeNacosMetricsUrl, parseNacosManagedNamespaces } from "@/lib/nacos/nacosAdmin";
|
||||
import { loadReadableNacosNamespaces, nacosNamespaceIdentity, normalizeNacosNamespaceSelection } from "@/lib/nacos/nacosNamespaceVisibility";
|
||||
import {
|
||||
ArrowLeft,
|
||||
@@ -912,6 +912,7 @@ const nacosVersionMode = ref<NacosVersionMode>("v2");
|
||||
const nacosApiPlane = ref<NacosApiPlane>("admin");
|
||||
const nacosServerAddr = ref("");
|
||||
const nacosContextPath = ref("");
|
||||
const nacosConsoleUrl = ref("");
|
||||
const nacosManagedNamespacesText = ref("");
|
||||
const nacosRNacosConsoleAddr = ref("");
|
||||
const nacosHistoryEnabled = ref(false);
|
||||
@@ -972,6 +973,10 @@ const nacosPrimaryAddressPlaceholder = computed(() => {
|
||||
}
|
||||
return "http://127.0.0.1:8848/nacos";
|
||||
});
|
||||
const nacosWebConsoleUrlPlaceholder = computed(() => {
|
||||
if (nacosImplementation.value === "nacos" && nacosVersionMode.value === "v3") return "http://127.0.0.1:8080";
|
||||
return "http://127.0.0.1:8848/nacos";
|
||||
});
|
||||
const nacosServiceAddressHint = computed(() => {
|
||||
if (nacosImplementation.value === "nacos" && nacosVersionMode.value === "v3" && nacosApiPlane.value === "console") {
|
||||
return t("nacos.nacosConsoleServiceAddressHint");
|
||||
@@ -1286,6 +1291,7 @@ function resetNacosFields(config?: Partial<NacosAdminConfig>) {
|
||||
const contextPath = config?.contextPath?.trim() || "";
|
||||
nacosServerAddr.value = serverAddr;
|
||||
nacosContextPath.value = contextPath;
|
||||
nacosConsoleUrl.value = config?.consoleUrl?.trim() || "";
|
||||
nacosManagedNamespacesText.value = (config?.managedNamespaces || []).join("\n");
|
||||
nacosDynamicAllNamespaces.value = !!config && !config.managedNamespaces?.length && !Array.isArray(form.value.visible_databases);
|
||||
nacosRNacosConsoleAddr.value = config?.rnacosConsoleAddr?.trim() || "";
|
||||
@@ -1656,6 +1662,15 @@ function buildNacosAdminConfig(): NacosAdminConfig {
|
||||
let rnacosConsoleAuth: NacosRNacosConsoleAuth | undefined;
|
||||
const managedNamespaces = nacosImplementation.value === "nacos" && nacosAuthKind.value === "usernamePassword" ? parseNacosManagedNamespaces(nacosManagedNamespacesText.value) : [];
|
||||
let metricsUrl: string | undefined;
|
||||
let consoleUrl: string | undefined;
|
||||
const usesIndependentConsoleUrl = nacosImplementation.value === "nacos" && nacosVersionMode.value === "v3" && nacosApiPlane.value === "admin";
|
||||
if (usesIndependentConsoleUrl && nacosConsoleUrl.value.trim()) {
|
||||
try {
|
||||
consoleUrl = normalizeNacosConsoleUrl(nacosConsoleUrl.value);
|
||||
} catch {
|
||||
throw new Error(t("connection.nacosWebConsoleUrlInvalid"));
|
||||
}
|
||||
}
|
||||
if (nacosMetricsMode.value === "custom") {
|
||||
try {
|
||||
metricsUrl = normalizeNacosMetricsUrl(nacosMetricsUrl.value);
|
||||
@@ -1681,6 +1696,7 @@ function buildNacosAdminConfig(): NacosAdminConfig {
|
||||
apiPlane: nacosImplementation.value === "nacos" && nacosVersionMode.value === "v3" ? nacosApiPlane.value : undefined,
|
||||
serverAddr: normalized.serverAddr,
|
||||
contextPath: normalized.contextPath || undefined,
|
||||
consoleUrl,
|
||||
managedNamespaces: managedNamespaces.length ? managedNamespaces : undefined,
|
||||
rnacosConsoleAddr: rnacosConsoleConfigured ? nacosRNacosConsoleAddr.value.trim() : undefined,
|
||||
rnacosHistoryEnabled: nacosImplementation.value === "rnacos" ? nacosHistoryEnabled.value : undefined,
|
||||
@@ -8216,6 +8232,14 @@ function openExternalUrl(url: string) {
|
||||
<p class="text-[11px] leading-4 text-muted-foreground">{{ t("nacos.nacosPageSizeHint") }}</p>
|
||||
</div>
|
||||
|
||||
<div v-if="isNacosV3AdminPlane" class="grid gap-1.5 border-t pt-4">
|
||||
<div>
|
||||
<Label>{{ t("connection.nacosWebConsoleUrl") }}</Label>
|
||||
<p class="mt-1 text-[11px] leading-4 text-muted-foreground">{{ t("connection.nacosWebConsoleUrlHint") }}</p>
|
||||
</div>
|
||||
<Input v-model="nacosConsoleUrl" :placeholder="nacosWebConsoleUrlPlaceholder" />
|
||||
</div>
|
||||
|
||||
<div class="grid gap-2 border-t pt-4">
|
||||
<div class="grid gap-1.5 sm:grid-cols-[minmax(0,1fr)_180px] sm:items-center">
|
||||
<div>
|
||||
|
||||
@@ -209,6 +209,7 @@ const props = defineProps<{
|
||||
selectedSql: string;
|
||||
cursorPos: number;
|
||||
blockDangerousRedisCommands: boolean;
|
||||
zenMode?: boolean;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
@@ -243,6 +244,7 @@ const emit = defineEmits<{
|
||||
structureEditorClose: [];
|
||||
openSettings: [initialTab?: string, initialSection?: string];
|
||||
openConnectionSettings: [connectionId: string, initialTab: "advanced"];
|
||||
toggleZenMode: [];
|
||||
}>();
|
||||
|
||||
const { t, locale } = useI18n();
|
||||
@@ -2364,6 +2366,8 @@ defineExpose({
|
||||
:target-keyword="activeTab.nacosTargetKeyword"
|
||||
:target-request-id="activeTab.nacosTargetRequestId"
|
||||
:read-only="connectionIsEffectivelyReadOnly(activeConnection)"
|
||||
:zen-mode="props.zenMode"
|
||||
@toggle-zen-mode="emit('toggleZenMode')"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -3,7 +3,7 @@ import { computed, nextTick, onActivated, onBeforeUnmount, onDeactivated, onMoun
|
||||
import { Compartment, StateEffect, StateField, type Extension } from "@codemirror/state";
|
||||
import { StreamLanguage, ensureSyntaxTree } from "@codemirror/language";
|
||||
import { Decoration, EditorView } from "@codemirror/view";
|
||||
import { Archive, ArrowLeftRight, CheckCircle2, ChevronDown, ChevronLeft, ChevronRight, Clipboard, Columns3, Download, FileClock, FileInput, FileText, Loader2, Network, Plus, RefreshCw, Save, Search, Send, Server, Trash2, X } from "@lucide/vue";
|
||||
import { Archive, ArrowLeftRight, CheckCircle2, ChevronDown, ChevronLeft, ChevronRight, Clipboard, Columns3, Download, ExternalLink, FileClock, FileInput, FileText, Loader2, Maximize2, Minimize2, Network, Plus, RefreshCw, Save, Search, Send, Server, Trash2, X } from "@lucide/vue";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import ProductionContextBadge from "@/components/common/ProductionContextBadge.vue";
|
||||
@@ -40,6 +40,8 @@ import {
|
||||
isNacosConfigDeleteSnapshotInScope,
|
||||
resolveNacosConfigCopyText,
|
||||
resolveNacosConfigSaveCompletion,
|
||||
resolveNacosConsoleUrl,
|
||||
nacosMetadataTableRows,
|
||||
type NacosConfigDeleteSnapshot,
|
||||
} from "@/lib/nacos/nacosAdmin";
|
||||
import { createNacosNamespaceRequestGuard, subscribeNacosNamespacesChanged, type NacosNamespacesChangedDetail } from "@/lib/nacos/nacosNamespaceCache";
|
||||
@@ -93,6 +95,11 @@ const props = defineProps<{
|
||||
targetKeyword?: string;
|
||||
targetRequestId?: number;
|
||||
readOnly?: boolean;
|
||||
zenMode?: boolean;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
toggleZenMode: [];
|
||||
}>();
|
||||
|
||||
type AdminTab = "configs" | "services";
|
||||
@@ -241,6 +248,8 @@ const instanceEditorOpen = ref(false);
|
||||
const instanceEditorError = ref("");
|
||||
const instanceEditorTarget = ref<NacosInstanceInfo | null>(null);
|
||||
const instanceEditor = ref({ weight: "1", metadata: "{}" });
|
||||
const instanceMetadataTableOpen = ref(false);
|
||||
const instanceMetadataTableTarget = ref<NacosInstanceInfo | null>(null);
|
||||
const serviceEditorOpen = ref(false);
|
||||
const serviceEditorLoading = ref(false);
|
||||
const serviceEditorError = ref("");
|
||||
@@ -289,6 +298,16 @@ function isSelectedConfigListItem(item: NacosConfigItem) {
|
||||
}
|
||||
|
||||
const namespace = computed(() => props.namespace ?? connectionInfo.value?.namespace ?? "");
|
||||
const instanceMetadataTableRows = computed(() => nacosMetadataTableRows(instanceMetadataTableTarget.value?.metadata));
|
||||
const nacosConsoleUrl = computed(() => {
|
||||
const config = connectionStore.getConfig(props.connectionId)?.external_config;
|
||||
if (!config || typeof config !== "object" || Array.isArray(config)) return undefined;
|
||||
try {
|
||||
return resolveNacosConsoleUrl(config as import("@/types/nacos").NacosAdminConfig);
|
||||
} catch {
|
||||
return undefined;
|
||||
}
|
||||
});
|
||||
const nacosProductionContext = computed(() => productionContextForDatabase(connectionStore.getConfig(props.connectionId), namespace.value));
|
||||
const batchTargetConnections = computed<NacosConfigTransferTarget[]>(() =>
|
||||
connectionStore.connections
|
||||
@@ -2152,6 +2171,11 @@ function openInstanceEditor(instance: NacosInstanceInfo) {
|
||||
instanceEditorOpen.value = true;
|
||||
}
|
||||
|
||||
function openInstanceMetadataTable(instance: NacosInstanceInfo) {
|
||||
instanceMetadataTableTarget.value = instance;
|
||||
instanceMetadataTableOpen.value = true;
|
||||
}
|
||||
|
||||
function submitInstanceEditor() {
|
||||
const instance = instanceEditorTarget.value;
|
||||
const weight = Number(instanceEditor.value.weight);
|
||||
@@ -2645,6 +2669,19 @@ onBeforeUnmount(() => {
|
||||
pauseConfigEditorViewportWork();
|
||||
destroyConfigEditor();
|
||||
});
|
||||
|
||||
function openNacosConsole() {
|
||||
const url = nacosConsoleUrl.value;
|
||||
if (!url) {
|
||||
toast(t("nacos.consoleUrlMissing"), 5000);
|
||||
return;
|
||||
}
|
||||
if (isTauriRuntime()) {
|
||||
void import("@tauri-apps/plugin-shell").then(({ open }) => open(url)).catch(() => toast(t("nacos.consoleOpenFailed"), 5000));
|
||||
return;
|
||||
}
|
||||
window.open(url, "_blank", "noopener,noreferrer");
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -2661,6 +2698,15 @@ onBeforeUnmount(() => {
|
||||
</div>
|
||||
<div class="flex min-w-0 flex-wrap items-center justify-end gap-2">
|
||||
<span v-if="connectionError" class="max-w-96 truncate text-xs text-destructive">{{ connectionError }}</span>
|
||||
<Button size="sm" variant="outline" class="h-8 gap-1.5" :title="nacosConsoleUrl ? t('nacos.openConsole') : t('nacos.consoleUrlMissing')" :aria-label="t('nacos.openConsole')" :disabled="!nacosConsoleUrl" @click="openNacosConsole">
|
||||
<ExternalLink class="h-3.5 w-3.5" />
|
||||
{{ t("nacos.openConsole") }}
|
||||
</Button>
|
||||
<Button size="sm" variant="outline" class="h-8 gap-1.5" :title="props.zenMode ? t('nacos.exitZenMode') : t('nacos.enterZenMode')" :aria-label="props.zenMode ? t('nacos.exitZenMode') : t('nacos.enterZenMode')" @click="emit('toggleZenMode')">
|
||||
<Minimize2 v-if="props.zenMode" class="h-3.5 w-3.5" />
|
||||
<Maximize2 v-else class="h-3.5 w-3.5" />
|
||||
{{ props.zenMode ? t("nacos.exitZenMode") : t("nacos.enterZenMode") }}
|
||||
</Button>
|
||||
<Button v-if="connectionError" size="sm" variant="outline" class="h-8 w-8 px-0" :title="t('nacos.retryConnectionInfo')" :aria-label="t('nacos.retryConnectionInfo')" :disabled="infoLoading" @click="loadInfo">
|
||||
<Loader2 v-if="infoLoading" class="h-3.5 w-3.5 animate-spin" />
|
||||
<RefreshCw v-else class="h-3.5 w-3.5" />
|
||||
@@ -3220,10 +3266,18 @@ onBeforeUnmount(() => {
|
||||
<Button size="sm" variant="ghost" class="h-7 px-2" :disabled="isInstanceUpdating(instance)" @click="resetInstanceWeightDraft(instance)">{{ t("nacos.restore") }}</Button>
|
||||
</div>
|
||||
</div>
|
||||
<details v-if="instance.metadata && Object.keys(instance.metadata).length" class="min-w-0 self-start text-muted-foreground">
|
||||
<summary class="cursor-pointer select-none hover:text-foreground">{{ t("nacos.metadataLabel") }}({{ t("nacos.itemCount", { count: Object.keys(instance.metadata).length }) }})</summary>
|
||||
<pre class="mt-1 max-h-32 max-w-full overflow-auto rounded bg-muted p-2 font-mono text-[11px] text-foreground">{{ JSON.stringify(instance.metadata, null, 2) }}</pre>
|
||||
</details>
|
||||
<div v-if="instance.metadata && typeof instance.metadata === 'object' && Object.keys(instance.metadata).length" class="min-w-0 self-start">
|
||||
<div class="nacos-instance-metadata-actions flex min-w-0 flex-wrap items-center gap-1">
|
||||
<details class="min-w-0 text-muted-foreground">
|
||||
<summary class="cursor-pointer select-none hover:text-foreground">{{ t("nacos.metadataLabel") }}({{ t("nacos.itemCount", { count: Object.keys(instance.metadata).length }) }})</summary>
|
||||
<pre class="mt-1 max-h-32 max-w-full overflow-auto rounded bg-muted p-2 font-mono text-[11px] text-foreground">{{ JSON.stringify(instance.metadata, null, 2) }}</pre>
|
||||
</details>
|
||||
<Button size="sm" variant="ghost" class="h-6 shrink-0 gap-1 px-1.5 text-xs text-muted-foreground hover:text-foreground" :aria-label="t('nacos.viewMetadataTable')" @click="openInstanceMetadataTable(instance)">
|
||||
<Columns3 class="h-3.5 w-3.5" />
|
||||
{{ t("nacos.viewMetadataTable") }}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<span v-else class="self-start text-muted-foreground">{{ t("nacos.noMetadata") }}</span>
|
||||
</div>
|
||||
</div>
|
||||
@@ -3479,6 +3533,34 @@ onBeforeUnmount(() => {
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
||||
<Dialog v-model:open="instanceMetadataTableOpen">
|
||||
<DialogContent class="flex max-h-[80vh] flex-col sm:max-w-2xl">
|
||||
<DialogHeader>
|
||||
<DialogTitle>{{ t("nacos.instanceMetadataTitle") }}</DialogTitle>
|
||||
<DialogDescription>{{ instanceMetadataTableTarget ? `${instanceMetadataTableTarget.ip}:${instanceMetadataTableTarget.port} · ${instanceMetadataTableTarget.clusterName || "DEFAULT"}` : "" }}</DialogDescription>
|
||||
</DialogHeader>
|
||||
<div class="min-h-0 overflow-auto rounded-md border">
|
||||
<table class="w-full min-w-[420px] text-left text-sm">
|
||||
<thead class="sticky top-0 z-10 bg-muted/85 text-xs text-muted-foreground">
|
||||
<tr>
|
||||
<th class="w-1/3 border-b px-3 py-2 font-medium">{{ t("nacos.metadataKey") }}</th>
|
||||
<th class="border-b px-3 py-2 font-medium">{{ t("nacos.metadataValue") }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="row in instanceMetadataTableRows" :key="row.key" class="border-b last:border-0">
|
||||
<td class="break-all px-3 py-2 align-top font-mono text-xs font-medium">{{ row.key }}</td>
|
||||
<td class="whitespace-pre-wrap break-all px-3 py-2 align-top font-mono text-xs">{{ row.value }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<DialogFooter>
|
||||
<Button variant="outline" @click="instanceMetadataTableOpen = false">{{ t("nacos.cancel") }}</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
||||
<DangerConfirmDialog
|
||||
:open="!!pendingDeleteConfig"
|
||||
:title="t('nacos.confirmDeleteTitle')"
|
||||
|
||||
@@ -9,6 +9,26 @@ describe("NacosAdminConsole config workbench layout", () => {
|
||||
expect(source).toContain('type AdminTab = "configs" | "services";');
|
||||
});
|
||||
|
||||
it("offers browser-console and Zen-mode actions in the workspace toolbar", () => {
|
||||
expect(source).toContain("resolveNacosConsoleUrl");
|
||||
expect(source).toContain("function openNacosConsole()");
|
||||
expect(source).toContain('t("nacos.openConsole")');
|
||||
expect(source).toContain("emit('toggleZenMode')");
|
||||
expect(source).toContain('props.zenMode ? t("nacos.exitZenMode") : t("nacos.enterZenMode")');
|
||||
});
|
||||
|
||||
it("keeps JSON metadata available while offering an on-demand table dialog", () => {
|
||||
expect(source).toContain("function openInstanceMetadataTable(instance: NacosInstanceInfo)");
|
||||
expect(source).toContain('t("nacos.viewMetadataTable")');
|
||||
expect(source).toContain('class="nacos-instance-metadata-actions flex min-w-0 flex-wrap items-center gap-1"');
|
||||
expect(source).toContain('<details class="min-w-0 text-muted-foreground">');
|
||||
expect(source).not.toContain('<details class="min-w-0 flex-1 text-muted-foreground">');
|
||||
expect(source).toContain('v-model:open="instanceMetadataTableOpen"');
|
||||
expect(source).toContain('t("nacos.metadataKey")');
|
||||
expect(source).toContain('t("nacos.metadataValue")');
|
||||
expect(source).toContain("JSON.stringify(instance.metadata, null, 2)");
|
||||
});
|
||||
|
||||
it("keeps the editor as the final and primary workbench surface", () => {
|
||||
const contextBar = source.indexOf('class="nacos-config-context-bar');
|
||||
const inspector = source.indexOf('class="nacos-config-inspector');
|
||||
|
||||
@@ -699,6 +699,9 @@ export default {
|
||||
nacosConsoleUrlHint: "Use the address that matches the selected Nacos profile. Full browser and API URLs are accepted.",
|
||||
nacosConsoleUrlRequired: "Nacos management address is required",
|
||||
nacosConsoleUrlAutoAdjusted: "Adjusted Nacos Console URL from {from} to {to}.",
|
||||
nacosWebConsoleUrl: "Nacos Web Console URL",
|
||||
nacosWebConsoleUrlHint: "Optional. Opens the Nacos console in your browser. Nacos 3 requires its separate web-console address; the browser needs direct access and does not reuse DBX tunnels or API authentication.",
|
||||
nacosWebConsoleUrlInvalid: "Nacos Web Console URL must be a valid HTTP or HTTPS URL without embedded credentials.",
|
||||
nacosRNacosOpenApiAutoAdjusted: "Switched to the r-nacos OpenAPI: {from} → {to} (Context Path: /nacos).",
|
||||
nacosImplementation: "Service implementation",
|
||||
nacosVersion: "Nacos version",
|
||||
@@ -8598,6 +8601,11 @@ export default {
|
||||
presetSchemaDesc: "Current topic schema definition",
|
||||
},
|
||||
nacos: {
|
||||
openConsole: "Open Console",
|
||||
consoleUrlMissing: "Configure a Nacos Web Console URL to open it in the browser.",
|
||||
consoleOpenFailed: "Failed to open the Nacos console.",
|
||||
enterZenMode: "Expand workspace",
|
||||
exitZenMode: "Exit expanded workspace",
|
||||
dashboard: "Dashboard",
|
||||
dashboardTitle: "Operations Dashboard",
|
||||
dashboardUpdatedAt: "Updated {time}",
|
||||
@@ -9165,6 +9173,10 @@ export default {
|
||||
invalidWeight: "Weight must be a finite number greater than or equal to 0.",
|
||||
invalidWeightInput: "Enter a finite weight greater than or equal to 0.",
|
||||
metadataLabel: "Metadata",
|
||||
viewMetadataTable: "View table",
|
||||
instanceMetadataTitle: "Instance metadata",
|
||||
metadataKey: "Key",
|
||||
metadataValue: "Value",
|
||||
selectorLabel: "Selector",
|
||||
instanceUpdateUnconfirmed: "The instance update was submitted, but Nacos has not returned the new state. Refresh to check the server state.",
|
||||
instanceRegisterUnconfirmed: "Instance registration was submitted, but Nacos has not returned the instance. Refresh to confirm.",
|
||||
|
||||
@@ -665,6 +665,9 @@ export default withEnglishFallback({
|
||||
nacosConsoleUrlHint: "Use la dirección correspondiente al perfil Nacos seleccionado. Se aceptan URL completas del navegador y de la API.",
|
||||
nacosConsoleUrlRequired: "La URL de OpenAPI / API de administración de Nacos es obligatoria",
|
||||
nacosConsoleUrlAutoAdjusted: "URL de consola de Nacos ajustada de {from} a {to}.",
|
||||
nacosWebConsoleUrl: "URL de la consola web de Nacos",
|
||||
nacosWebConsoleUrlHint: "Opcional. Abre la consola de Nacos en el navegador. Nacos 3 requiere su dirección de consola web independiente; el navegador necesita acceso directo y no reutiliza los túneles ni la autenticación de API de DBX.",
|
||||
nacosWebConsoleUrlInvalid: "La URL de la consola web de Nacos debe ser una URL HTTP o HTTPS válida sin credenciales incrustadas.",
|
||||
nacosRNacosOpenApiAutoAdjusted: "Se cambió automáticamente a la OpenAPI de r-nacos: {from} → {to} (Context Path: /nacos).",
|
||||
nacosImplementation: "Implementación del servicio",
|
||||
nacosVersion: "Versión de Nacos",
|
||||
@@ -7143,6 +7146,11 @@ export default withEnglishFallback({
|
||||
},
|
||||
nacos: {
|
||||
...nacosAccessControlMessages,
|
||||
openConsole: "Abrir consola",
|
||||
consoleUrlMissing: "Configure primero una URL de consola web de Nacos.",
|
||||
consoleOpenFailed: "No se pudo abrir la consola de Nacos.",
|
||||
enterZenMode: "Expandir espacio de trabajo",
|
||||
exitZenMode: "Salir del espacio de trabajo expandido",
|
||||
dashboard: "Panel",
|
||||
dashboardTitle: "Panel de operaciones",
|
||||
dashboardUpdatedAt: "Actualizado {time}",
|
||||
@@ -7538,6 +7546,10 @@ export default withEnglishFallback({
|
||||
invalidWeight: "El peso debe ser un número finito mayor o igual que 0.",
|
||||
invalidWeightInput: "Introduce un peso finito mayor o igual que 0.",
|
||||
metadataLabel: "Metadatos",
|
||||
viewMetadataTable: "Ver tabla",
|
||||
instanceMetadataTitle: "Metadatos de la instancia",
|
||||
metadataKey: "Clave",
|
||||
metadataValue: "Valor",
|
||||
selectorLabel: "Selector",
|
||||
instanceUpdateUnconfirmed: "La actualización de la instancia se envió, pero Nacos aún no ha devuelto el nuevo estado. Actualiza para comprobar el estado del servidor.",
|
||||
instanceRegisterUnconfirmed: "El registro de la instancia se envió, pero Nacos aún no la ha devuelto. Actualiza para confirmarlo.",
|
||||
|
||||
@@ -664,6 +664,9 @@ export default withEnglishFallback({
|
||||
nacosConsoleUrlHint: "Usa l'indirizzo corrispondente al profilo Nacos selezionato. Sono accettati URL completi del browser e dell'API.",
|
||||
nacosConsoleUrlRequired: "L'URL OpenAPI / API di amministrazione Nacos è obbligatorio",
|
||||
nacosConsoleUrlAutoAdjusted: "URL console Nacos regolato da {from} a {to}.",
|
||||
nacosWebConsoleUrl: "URL console Web Nacos",
|
||||
nacosWebConsoleUrlHint: "Facoltativo. Apre la console Nacos nel browser. Nacos 3 richiede il proprio indirizzo della console Web separata; il browser deve avere accesso diretto e non riutilizza tunnel o autenticazione API di DBX.",
|
||||
nacosWebConsoleUrlInvalid: "L'URL della console Web Nacos deve essere un URL HTTP o HTTPS valido senza credenziali incorporate.",
|
||||
nacosRNacosOpenApiAutoAdjusted: "Passaggio automatico alla OpenAPI r-nacos: {from} → {to} (Context Path: /nacos).",
|
||||
nacosImplementation: "Implementazione del servizio",
|
||||
nacosVersion: "Versione Nacos",
|
||||
@@ -7144,6 +7147,11 @@ export default withEnglishFallback({
|
||||
},
|
||||
nacos: {
|
||||
...nacosAccessControlMessages,
|
||||
openConsole: "Apri console",
|
||||
consoleUrlMissing: "Configura prima un URL della console Web Nacos.",
|
||||
consoleOpenFailed: "Impossibile aprire la console Nacos.",
|
||||
enterZenMode: "Espandi area di lavoro",
|
||||
exitZenMode: "Esci dall'area di lavoro espansa",
|
||||
dashboard: "Dashboard",
|
||||
dashboardTitle: "Dashboard operativa",
|
||||
dashboardUpdatedAt: "Aggiornato {time}",
|
||||
@@ -7539,6 +7547,10 @@ export default withEnglishFallback({
|
||||
invalidWeight: "Il peso deve essere un numero finito maggiore o uguale a 0.",
|
||||
invalidWeightInput: "Inserisci un peso finito maggiore o uguale a 0.",
|
||||
metadataLabel: "Metadati",
|
||||
viewMetadataTable: "Visualizza tabella",
|
||||
instanceMetadataTitle: "Metadati dell'istanza",
|
||||
metadataKey: "Chiave",
|
||||
metadataValue: "Valore",
|
||||
selectorLabel: "Selettore",
|
||||
instanceUpdateUnconfirmed: "L'aggiornamento dell'istanza è stato inviato, ma Nacos non ha ancora restituito il nuovo stato. Aggiorna per controllare lo stato del server.",
|
||||
instanceRegisterUnconfirmed: "La registrazione dell'istanza è stata inviata, ma Nacos non ha ancora restituito l'istanza. Aggiorna per confermare.",
|
||||
|
||||
@@ -630,6 +630,9 @@ export default withEnglishFallback({
|
||||
nacosConsoleUrlHint: "選択した Nacos プロファイルに対応するアドレスを指定します。完全なブラウザー URL と API URL を使用できます。",
|
||||
nacosConsoleUrlRequired: "Nacos OpenAPI / 管理 API URL は必須です",
|
||||
nacosConsoleUrlAutoAdjusted: "NacosコンソールURLを{from}から{to}に自動調整しました。",
|
||||
nacosWebConsoleUrl: "Nacos Web コンソール URL",
|
||||
nacosWebConsoleUrlHint: "任意。ブラウザーで Nacos コンソールを開きます。Nacos 3 では専用の Web コンソールアドレスが必要です。ブラウザーから直接アクセスできる必要があり、DBX のトンネルや API 認証は再利用されません。",
|
||||
nacosWebConsoleUrlInvalid: "Nacos Web コンソール URL は、埋め込み認証情報を含まない有効な HTTP または HTTPS URL である必要があります。",
|
||||
nacosRNacosOpenApiAutoAdjusted: "r-nacos OpenAPI に自動切り替えました: {from} → {to}(Context Path: /nacos)。",
|
||||
nacosImplementation: "サービス実装",
|
||||
nacosVersion: "Nacos バージョン",
|
||||
@@ -7195,6 +7198,11 @@ export default withEnglishFallback({
|
||||
},
|
||||
nacos: {
|
||||
...nacosAccessControlMessages,
|
||||
openConsole: "コンソールを開く",
|
||||
consoleUrlMissing: "先に Nacos Web コンソール URL を設定してください。",
|
||||
consoleOpenFailed: "Nacos コンソールを開けませんでした。",
|
||||
enterZenMode: "ワークスペースを展開",
|
||||
exitZenMode: "展開したワークスペースを終了",
|
||||
dashboard: "ダッシュボード",
|
||||
dashboardTitle: "運用ダッシュボード",
|
||||
dashboardUpdatedAt: "更新 {time}",
|
||||
@@ -7590,6 +7598,10 @@ export default withEnglishFallback({
|
||||
invalidWeight: "重みは 0 以上の有限数である必要があります。",
|
||||
invalidWeightInput: "0 以上の有限な重みを入力してください。",
|
||||
metadataLabel: "メタデータ",
|
||||
viewMetadataTable: "表で表示",
|
||||
instanceMetadataTitle: "インスタンスのメタデータ",
|
||||
metadataKey: "キー",
|
||||
metadataValue: "値",
|
||||
selectorLabel: "セレクター",
|
||||
instanceUpdateUnconfirmed: "インスタンス更新は送信されましたが、Nacos は新しい状態をまだ返していません。更新してサーバーの状態を確認してください。",
|
||||
instanceRegisterUnconfirmed: "インスタンス登録は送信されましたが、Nacos はまだインスタンスを返していません。更新して確認してください。",
|
||||
|
||||
@@ -689,6 +689,9 @@ export default withEnglishFallback({
|
||||
nacosConsoleUrlHint: "선택한 Nacos 프로필과 일치하는 주소를 사용하세요. 전체 브라우저와 API URL이 모두 허용됩니다.",
|
||||
nacosConsoleUrlRequired: "Nacos 관리 주소는 필수입니다",
|
||||
nacosConsoleUrlAutoAdjusted: "Nacos 콘솔 URL을 {from}에서 {to}(으)로 조정했습니다.",
|
||||
nacosWebConsoleUrl: "Nacos 웹 콘솔 URL",
|
||||
nacosWebConsoleUrlHint: "선택 사항입니다. 브라우저에서 Nacos 콘솔을 엽니다. Nacos 3에는 별도의 웹 콘솔 주소가 필요합니다. 브라우저에서 직접 접근할 수 있어야 하며 DBX 터널이나 API 인증은 재사용되지 않습니다.",
|
||||
nacosWebConsoleUrlInvalid: "Nacos 웹 콘솔 URL은 포함된 자격 증명이 없는 유효한 HTTP 또는 HTTPS URL이어야 합니다.",
|
||||
nacosRNacosOpenApiAutoAdjusted: "r-nacos OpenAPI로 전환했습니다: {from} → {to} (컨텍스트 경로: /nacos).",
|
||||
nacosImplementation: "서비스 구현",
|
||||
nacosVersion: "Nacos 버전",
|
||||
@@ -7940,6 +7943,11 @@ export default withEnglishFallback({
|
||||
presetSchemaDesc: "현재 토픽 스키마 정의",
|
||||
},
|
||||
nacos: {
|
||||
openConsole: "콘솔 열기",
|
||||
consoleUrlMissing: "먼저 Nacos 웹 콘솔 URL을 구성하세요.",
|
||||
consoleOpenFailed: "Nacos 콘솔을 열지 못했습니다.",
|
||||
enterZenMode: "작업 공간 확장",
|
||||
exitZenMode: "확장 작업 공간 종료",
|
||||
dashboard: "대시보드",
|
||||
dashboardTitle: "운영 대시보드",
|
||||
dashboardUpdatedAt: "{time}에 업데이트됨",
|
||||
@@ -8340,6 +8348,10 @@ export default withEnglishFallback({
|
||||
invalidWeight: "가중치는 0 이상인 유한한 숫자여야 합니다.",
|
||||
invalidWeightInput: "0 이상인 유한한 가중치를 입력하세요.",
|
||||
metadataLabel: "메타데이터",
|
||||
viewMetadataTable: "표로 보기",
|
||||
instanceMetadataTitle: "인스턴스 메타데이터",
|
||||
metadataKey: "키",
|
||||
metadataValue: "값",
|
||||
selectorLabel: "셀렉터",
|
||||
instanceUpdateUnconfirmed: "인스턴스 업데이트를 제출했지만 Nacos가 새 상태를 아직 반환하지 않았습니다. 새로 고쳐 서버 상태를 확인하세요.",
|
||||
instanceRegisterUnconfirmed: "인스턴스 등록을 제출했지만 Nacos가 인스턴스를 아직 반환하지 않았습니다. 새로 고쳐 확인하세요.",
|
||||
|
||||
@@ -665,6 +665,9 @@ export default withEnglishFallback({
|
||||
nacosConsoleUrlHint: "Use o endereço correspondente ao perfil Nacos selecionado. URLs completas do navegador e da API são aceitas.",
|
||||
nacosConsoleUrlRequired: "A URL da OpenAPI / API administrativa do Nacos é obrigatória",
|
||||
nacosConsoleUrlAutoAdjusted: "URL do console Nacos ajustada de {from} para {to}.",
|
||||
nacosWebConsoleUrl: "URL do console web do Nacos",
|
||||
nacosWebConsoleUrlHint: "Opcional. Abre o console Nacos no navegador. O Nacos 3 requer seu endereço de console web separado; o navegador precisa de acesso direto e não reutiliza túneis ou autenticação de API do DBX.",
|
||||
nacosWebConsoleUrlInvalid: "A URL do console web do Nacos deve ser uma URL HTTP ou HTTPS válida sem credenciais incorporadas.",
|
||||
nacosRNacosOpenApiAutoAdjusted: "Alterado automaticamente para a OpenAPI do r-nacos: {from} → {to} (Context Path: /nacos).",
|
||||
nacosImplementation: "Implementação do serviço",
|
||||
nacosVersion: "Versão do Nacos",
|
||||
@@ -7145,6 +7148,11 @@ export default withEnglishFallback({
|
||||
},
|
||||
nacos: {
|
||||
...nacosAccessControlMessages,
|
||||
openConsole: "Abrir console",
|
||||
consoleUrlMissing: "Configure primeiro uma URL do console web do Nacos.",
|
||||
consoleOpenFailed: "Não foi possível abrir o console do Nacos.",
|
||||
enterZenMode: "Expandir espaço de trabalho",
|
||||
exitZenMode: "Sair do espaço de trabalho expandido",
|
||||
dashboard: "Painel",
|
||||
dashboardTitle: "Painel de operações",
|
||||
dashboardUpdatedAt: "Atualizado {time}",
|
||||
@@ -7540,6 +7548,10 @@ export default withEnglishFallback({
|
||||
invalidWeight: "O peso deve ser um número finito maior ou igual a 0.",
|
||||
invalidWeightInput: "Informe um peso finito maior ou igual a 0.",
|
||||
metadataLabel: "Metadados",
|
||||
viewMetadataTable: "Ver tabela",
|
||||
instanceMetadataTitle: "Metadados da instância",
|
||||
metadataKey: "Chave",
|
||||
metadataValue: "Valor",
|
||||
selectorLabel: "Seletor",
|
||||
instanceUpdateUnconfirmed: "A atualização da instância foi enviada, mas o Nacos ainda não retornou o novo estado. Atualize para verificar o estado do servidor.",
|
||||
instanceRegisterUnconfirmed: "O registro da instância foi enviado, mas o Nacos ainda não retornou a instância. Atualize para confirmar.",
|
||||
|
||||
@@ -624,6 +624,9 @@ export default withEnglishFallback({
|
||||
nacosConsoleUrlHint: "填写与所选 Nacos 配置匹配的地址,支持完整的浏览器或 API URL。",
|
||||
nacosConsoleUrlRequired: "Nacos OpenAPI / 管理 API URL 不能为空",
|
||||
nacosConsoleUrlAutoAdjusted: "已自动将 Nacos 控制台 URL 从 {from} 调整为 {to}。",
|
||||
nacosWebConsoleUrl: "Nacos Web 控制台 URL",
|
||||
nacosWebConsoleUrlHint: "可选,用于在浏览器打开 Nacos 控制台;Nacos 3 需填写独立的 Web 控制台地址。浏览器需可直接访问,且不会复用 DBX 的隧道或 API 认证。",
|
||||
nacosWebConsoleUrlInvalid: "Nacos Web 控制台 URL 必须是未包含凭据的有效 HTTP 或 HTTPS 地址。",
|
||||
nacosRNacosOpenApiAutoAdjusted: "已自动改用 r-nacos OpenAPI:{from} → {to}(Context Path: /nacos)。",
|
||||
nacosImplementation: "服务实现",
|
||||
nacosVersion: "Nacos 版本",
|
||||
@@ -8577,6 +8580,11 @@ export default withEnglishFallback({
|
||||
presetSchemaDesc: "查看主题当前 schema 定义",
|
||||
},
|
||||
nacos: {
|
||||
openConsole: "打开控制台",
|
||||
consoleUrlMissing: "请先配置 Nacos Web 控制台 URL。",
|
||||
consoleOpenFailed: "打开 Nacos 控制台失败。",
|
||||
enterZenMode: "展开工作台",
|
||||
exitZenMode: "退出展开工作台",
|
||||
dashboard: "大盘",
|
||||
dashboardTitle: "运行大盘",
|
||||
dashboardUpdatedAt: "更新于 {time}",
|
||||
@@ -9144,6 +9152,10 @@ export default withEnglishFallback({
|
||||
invalidWeight: "实例权重必须是大于或等于 0 的有限数字。",
|
||||
invalidWeightInput: "请输入大于或等于 0 的有限权重。",
|
||||
metadataLabel: "元数据",
|
||||
viewMetadataTable: "表格查看",
|
||||
instanceMetadataTitle: "实例元数据",
|
||||
metadataKey: "字段",
|
||||
metadataValue: "值",
|
||||
selectorLabel: "选择器",
|
||||
instanceUpdateUnconfirmed: "实例更新已提交,但 Nacos 尚未返回新状态。请刷新后查看服务端状态。",
|
||||
instanceRegisterUnconfirmed: "实例注册已提交,但 Nacos 尚未返回该实例。请刷新后确认。",
|
||||
|
||||
@@ -665,6 +665,9 @@ export default withEnglishFallback({
|
||||
nacosConsoleUrlHint: "填寫與所選 Nacos 設定相符的位址,支援完整的瀏覽器或 API URL。",
|
||||
nacosConsoleUrlRequired: "Nacos OpenAPI / 管理 API URL 不能為空",
|
||||
nacosConsoleUrlAutoAdjusted: "已自動將 Nacos 控制台 URL 從 {from} 調整為 {to}。",
|
||||
nacosWebConsoleUrl: "Nacos Web 控制台 URL",
|
||||
nacosWebConsoleUrlHint: "選填,用於在瀏覽器開啟 Nacos 控制台;Nacos 3 需填寫獨立的 Web 控制台位址。瀏覽器需可直接存取,且不會沿用 DBX 的通道或 API 驗證。",
|
||||
nacosWebConsoleUrlInvalid: "Nacos Web 控制台 URL 必須是未內嵌憑據的有效 HTTP 或 HTTPS 位址。",
|
||||
nacosRNacosOpenApiAutoAdjusted: "已自動改用 r-nacos OpenAPI:{from} → {to}(Context Path: /nacos)。",
|
||||
nacosImplementation: "服務實作",
|
||||
nacosVersion: "Nacos 版本",
|
||||
@@ -6462,6 +6465,11 @@ export default withEnglishFallback({
|
||||
},
|
||||
nacos: {
|
||||
...nacosAccessControlMessages,
|
||||
openConsole: "開啟控制台",
|
||||
consoleUrlMissing: "請先設定 Nacos Web 控制台 URL。",
|
||||
consoleOpenFailed: "開啟 Nacos 控制台失敗。",
|
||||
enterZenMode: "展開工作台",
|
||||
exitZenMode: "結束展開工作台",
|
||||
accessControlSidebarLabel: "使用者和角色",
|
||||
dashboard: "儀表板",
|
||||
dashboardTitle: "運行儀表板",
|
||||
@@ -6858,6 +6866,10 @@ export default withEnglishFallback({
|
||||
invalidWeight: "權重必須是大於或等於 0 的有限數字。",
|
||||
invalidWeightInput: "請輸入大於或等於 0 的有限權重。",
|
||||
metadataLabel: "中繼資料",
|
||||
viewMetadataTable: "表格檢視",
|
||||
instanceMetadataTitle: "實例中繼資料",
|
||||
metadataKey: "欄位",
|
||||
metadataValue: "值",
|
||||
selectorLabel: "選取器",
|
||||
instanceUpdateUnconfirmed: "實例更新已送出,但 Nacos 尚未回傳新狀態。請重新整理以查看伺服器狀態。",
|
||||
instanceRegisterUnconfirmed: "實例註冊已送出,但 Nacos 尚未回傳該實例。請重新整理確認。",
|
||||
|
||||
@@ -59,12 +59,12 @@ describe("right sidebar panel entry points", () => {
|
||||
expect(functionSource("setRightSidebarPanelOpen", "toggleRightSidebarPanel")).toContain("isAiPanelMaximized.value = false;");
|
||||
});
|
||||
|
||||
it("keeps Zen mode as a temporary data-tab layout", () => {
|
||||
it("keeps Zen mode as a temporary data or Nacos-tab layout", () => {
|
||||
expect(appSource).toContain("const isZenMode = ref(false);");
|
||||
expect(appSource).toContain('if (mode !== "data") isZenMode.value = false;');
|
||||
expect(appSource).toContain('return mode === "data" || mode === "nacos";');
|
||||
expect(appSource).toContain("function toggleZenMode() {");
|
||||
expect(appSource).toContain('if (activeTab.value?.mode !== "data") return;');
|
||||
expect(appSource).toContain('isToggleZenModeShortcut(e, shortcuts) && activeTab.value?.mode === "data"');
|
||||
expect(appSource).toContain("if (!supportsZenMode(activeTab.value?.mode)) return;");
|
||||
expect(appSource).toContain("isToggleZenModeShortcut(e, shortcuts) && supportsZenMode(activeTab.value?.mode)");
|
||||
expect(appSource).toContain('@toggle-zen-mode="toggleZenMode"');
|
||||
expect(appSource).toContain('v-show="sidebarOpen && !isZenMode"');
|
||||
expect(appSource).toContain('v-show="!sidebarOpen && !isZenMode"');
|
||||
|
||||
@@ -31,6 +31,7 @@ describe("Nacos connection dialog layout", () => {
|
||||
expect(main).toContain('t("nacos.nacosAdvancedHint")');
|
||||
expect(main).toContain("@click=\"configTab = 'advanced'\"");
|
||||
expect(main).toContain('v-model="nacosServerAddr"');
|
||||
expect(main).not.toContain('v-model="nacosConsoleUrl"');
|
||||
expect(main).not.toContain('v-model="nacosManagedNamespacesText"');
|
||||
expect(main).not.toContain("data-nacos-namespace-access-scope");
|
||||
expect(main).not.toContain("data-nacos-ordinary-user-toggle");
|
||||
@@ -52,6 +53,9 @@ describe("Nacos connection dialog layout", () => {
|
||||
expect(advanced).toContain('v-model="nacosContextPath"');
|
||||
expect(advanced).toContain('t("nacos.nacosContextPathHint")');
|
||||
expect(advanced).toContain('v-model="nacosMetricsMode"');
|
||||
expect(advanced).toContain('v-model="nacosConsoleUrl"');
|
||||
expect(advanced).toContain('t("connection.nacosWebConsoleUrlHint")');
|
||||
expect(advanced).toContain('v-if="isNacosV3AdminPlane"');
|
||||
expect(advanced).toContain('v-model="nacosRNacosConsoleAddr"');
|
||||
expect(advanced).toContain('v-if="nacosRNacosConsoleAddr.trim()"');
|
||||
expect(advanced).toContain('v-model="nacosHistoryEnabled"');
|
||||
@@ -67,6 +71,7 @@ describe("Nacos connection dialog layout", () => {
|
||||
const mainEnd = source.indexOf("<!-- Redis: host, port, user, password, ssl -->", mainStart);
|
||||
expect(source.slice(mainStart, mainEnd)).not.toContain("data-nacos-managed-namespaces");
|
||||
expect(source.slice(mainStart, mainEnd)).not.toContain('placeholder="http://127.0.0.1:8080"');
|
||||
expect(source).toContain('return "http://127.0.0.1:8080";');
|
||||
expect(source).not.toContain("http://127.0.0.1:8010");
|
||||
expect(source).not.toContain("http://127.0.0.1:8818");
|
||||
});
|
||||
|
||||
@@ -20,8 +20,10 @@ import {
|
||||
isNacosConfigDeleteSnapshotInScope,
|
||||
mergeNacosNamespacePermissionAssignments,
|
||||
nacosConfigFileExtension,
|
||||
nacosMetadataTableRows,
|
||||
nacosMetricsCandidates,
|
||||
normalizeNacosMetricsUrl,
|
||||
normalizeNacosConsoleUrl,
|
||||
parseNacosManagedNamespaces,
|
||||
parseNacosRawBody,
|
||||
parseNacosRawQuery,
|
||||
@@ -29,6 +31,7 @@ import {
|
||||
resolveRNacosOpenApiFallback,
|
||||
resolveNacosConfigCopyText,
|
||||
resolveNacosConfigSaveCompletion,
|
||||
resolveNacosConsoleUrl,
|
||||
sanitizeNacosConfigFileNameSegment,
|
||||
splitNacosContentLiteralMatches,
|
||||
summarizeNacosConfigDiff,
|
||||
@@ -92,6 +95,26 @@ describe("nacosAdmin helpers", () => {
|
||||
expect(() => normalizeNacosMetricsUrl("http://localhost/metrics#")).toThrow(/fragment/);
|
||||
});
|
||||
|
||||
it("formats instance metadata for a readable table without flattening nested values", () => {
|
||||
expect(nacosMetadataTableRows({ role: "manual-test", source: "dbx-ui", ports: [8080, 8848], nested: { enabled: true } })).toEqual([
|
||||
{ key: "role", value: "manual-test" },
|
||||
{ key: "source", value: "dbx-ui" },
|
||||
{ key: "ports", value: "[8080,8848]" },
|
||||
{ key: "nested", value: '{"enabled":true}' },
|
||||
]);
|
||||
expect(nacosMetadataTableRows(["legacy-value"])).toEqual([{ key: "0", value: "legacy-value" }]);
|
||||
});
|
||||
|
||||
it("resolves browser console URLs without inferring Nacos 3 ports", () => {
|
||||
expect(resolveNacosConsoleUrl({ implementation: "nacos", versionMode: "v2", serverAddr: "https://nacos.example:8848", contextPath: "/gateway/nacos", consoleUrl: "https://stale.example" })).toBe("https://nacos.example:8848/gateway/nacos");
|
||||
expect(resolveNacosConsoleUrl({ implementation: "nacos", versionMode: "v3", serverAddr: "https://nacos.example:11003", contextPath: "/nacos" })).toBeUndefined();
|
||||
expect(resolveNacosConsoleUrl({ implementation: "nacos", versionMode: "v3", serverAddr: "https://nacos.example:11003", consoleUrl: "https://nacos.example:11004/next/" })).toBe("https://nacos.example:11004/next");
|
||||
expect(resolveNacosConsoleUrl({ implementation: "nacos", versionMode: "v3", apiPlane: "console", serverAddr: "https://nacos.example:11004", contextPath: "/next", consoleUrl: "https://stale.example" })).toBe("https://nacos.example:11004/next");
|
||||
expect(resolveNacosConsoleUrl({ implementation: "rnacos", serverAddr: "https://rnacos.example:8848", rnacosConsoleAddr: "https://rnacos.example:10848/" })).toBe("https://rnacos.example:10848");
|
||||
expect(resolveNacosConsoleUrl({ serverAddr: "https://rnacos.example:8848", rnacosConsoleAddr: "https://rnacos.example:10848/" })).toBe("https://rnacos.example:10848");
|
||||
expect(() => normalizeNacosConsoleUrl("https://user:secret@nacos.example")).toThrow(/credentials/i);
|
||||
});
|
||||
|
||||
it("parses raw query and body text", () => {
|
||||
expect(parseNacosRawQuery("?dataId=a&group=DEFAULT_GROUP")).toEqual({ dataId: "a", group: "DEFAULT_GROUP" });
|
||||
expect(parseNacosRawQuery("")).toBeUndefined();
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { NacosApiPlane, NacosConfigHistoryItem, NacosConfigItem, NacosConfigKey, NacosContentMatch, NacosImplementation, NacosInstanceInfo, NacosPermissionInfo, NacosRawRequest, NacosServiceInfo, NacosVersionMode } from "@/types/nacos";
|
||||
import type { NacosAdminConfig, NacosApiPlane, NacosConfigHistoryItem, NacosConfigItem, NacosConfigKey, NacosContentMatch, NacosImplementation, NacosInstanceInfo, NacosPermissionInfo, NacosRawRequest, NacosServiceInfo, NacosVersionMode } from "@/types/nacos";
|
||||
import { diffArrays, diffChars } from "diff";
|
||||
|
||||
export type NacosRawTemplateKey = "serverState" | "namespaceList" | "configDetail" | "serviceList" | "instanceList";
|
||||
@@ -62,6 +62,65 @@ export interface NacosEndpointNormalizationOptions {
|
||||
contextPath?: string;
|
||||
}
|
||||
|
||||
export interface NacosMetadataTableRow {
|
||||
key: string;
|
||||
value: string;
|
||||
}
|
||||
|
||||
/** Makes a metadata object readable in a two-column view without losing nested values. */
|
||||
export function nacosMetadataTableRows(metadata: unknown): NacosMetadataTableRow[] {
|
||||
if (!metadata || typeof metadata !== "object") return [];
|
||||
return Object.entries(metadata as Record<string, unknown>).map(([key, value]) => ({
|
||||
key,
|
||||
value: typeof value === "string" ? value : (JSON.stringify(value) ?? String(value)),
|
||||
}));
|
||||
}
|
||||
|
||||
/** Validates a browser-only Nacos console URL without mixing credentials into a saved connection profile. */
|
||||
export function normalizeNacosConsoleUrl(input: string): string {
|
||||
let url: URL;
|
||||
try {
|
||||
url = new URL(input.trim());
|
||||
} catch {
|
||||
throw new Error("Nacos console URL must be a valid absolute URL");
|
||||
}
|
||||
if (!["http:", "https:"].includes(url.protocol)) throw new Error("Nacos console URL must use HTTP or HTTPS");
|
||||
if (url.username || url.password) throw new Error("Nacos console URL must not contain embedded credentials");
|
||||
return url.toString().replace(/\/$/, "");
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves the web-console address independently from DBX's API connection.
|
||||
* Nacos 3 must be configured explicitly because its Console port can differ
|
||||
* from its Admin API port. Nacos 2 keeps the historical same-endpoint fallback.
|
||||
*/
|
||||
function resolveNacosConsoleUrlFromServer(serverAddr: string, contextPath: string | undefined, fallbackPath: string): string {
|
||||
const endpoint = new URL(normalizeNacosConsoleUrl(serverAddr));
|
||||
const normalizedContextPath = contextPath?.trim().replace(/^\/+|\/+$/g, "");
|
||||
endpoint.pathname = normalizedContextPath ? `/${normalizedContextPath}` : fallbackPath || "/";
|
||||
endpoint.search = "";
|
||||
endpoint.hash = "";
|
||||
return endpoint.toString().replace(/\/$/, "");
|
||||
}
|
||||
|
||||
export function resolveNacosConsoleUrl(config: Pick<NacosAdminConfig, "implementation" | "versionMode" | "apiPlane" | "serverAddr" | "contextPath" | "consoleUrl" | "rnacosConsoleAddr">): string | undefined {
|
||||
// A Nacos 3 Console API connection already targets the browser console.
|
||||
// Prefer its primary endpoint even for legacy profiles that kept a stale
|
||||
// standalone console URL.
|
||||
if (config.versionMode === "v3" && config.apiPlane === "console") {
|
||||
const serverAddr = config.serverAddr?.trim();
|
||||
return serverAddr ? resolveNacosConsoleUrlFromServer(serverAddr, config.contextPath, "") : undefined;
|
||||
}
|
||||
if (config.implementation === "rnacos" || (!config.implementation && config.rnacosConsoleAddr?.trim())) {
|
||||
return config.rnacosConsoleAddr?.trim() ? normalizeNacosConsoleUrl(config.rnacosConsoleAddr) : undefined;
|
||||
}
|
||||
if (config.versionMode === "v3") return config.consoleUrl?.trim() ? normalizeNacosConsoleUrl(config.consoleUrl) : undefined;
|
||||
|
||||
const serverAddr = config.serverAddr?.trim();
|
||||
if (!serverAddr) return undefined;
|
||||
return resolveNacosConsoleUrlFromServer(serverAddr, config.contextPath, "/nacos");
|
||||
}
|
||||
|
||||
/**
|
||||
* Splits a pasted browser/API URL into the persisted origin and API context.
|
||||
* We only strip documented Nacos 3 UI routes; every other prefix remains an
|
||||
|
||||
@@ -252,6 +252,8 @@ export interface NacosAdminConfig {
|
||||
apiPlane?: NacosApiPlane;
|
||||
serverAddr: string;
|
||||
contextPath?: string;
|
||||
/** Browser URL for the Nacos web console. Nacos 3 uses a separate console endpoint. */
|
||||
consoleUrl?: string;
|
||||
/** Namespace IDs used when an official Nacos ordinary user cannot enumerate namespaces or authorization data. */
|
||||
managedNamespaces?: string[];
|
||||
rnacosConsoleAddr?: string;
|
||||
|
||||
Reference in new issue
Block a user