feat(nacos): add browser console open, zen mode and metadata view

This commit is contained in:
二丫讲梵 authored and GitHub committed 2026-09-04 00:29:41 +08:00
1 parent 39de9499b4
commit 0279a0903c
18 files changed
+335 -14

No files matched your search

+9 -3
View File
@@ -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');
+12
View File
@@ -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.",
+12
View File
@@ -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.",
+12
View File
@@ -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.",
+12
View File
@@ -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 はまだインスタンスを返していません。更新して確認してください。",
+12
View File
@@ -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가 인스턴스를 아직 반환하지 않았습니다. 새로 고쳐 확인하세요.",
+12
View File
@@ -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.",
+12
View File
@@ -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 尚未返回该实例。请刷新后确认。",
+12
View File
@@ -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");
});
+23
View File
@@ -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();
+60 -1
View File
@@ -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
+2
View File
@@ -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;