feat(nacos): add Nacos management console (#1456)
This commit is contained in:
62 files changed
+7314
-55
No files matched your search
@@ -765,6 +765,20 @@ async function openConnectionQuery(connectionId: string) {
|
||||
queryStore.openMqAdmin(connectionId);
|
||||
return;
|
||||
}
|
||||
if (initialTarget.kind === "nacos-admin") {
|
||||
try {
|
||||
await connectionStore.ensureConnected(connectionId);
|
||||
await connectionStore.loadNacosNamespaces(connectionId);
|
||||
} catch (e: any) {
|
||||
toast(
|
||||
t("connection.connectFailed", {
|
||||
message: translateBackendError(t, e?.message || String(e)),
|
||||
}),
|
||||
5000,
|
||||
);
|
||||
}
|
||||
return;
|
||||
}
|
||||
const tabId = queryStore.createTab(connectionId, initialTarget.database);
|
||||
try {
|
||||
await connectionStore.ensureConnected(connectionId);
|
||||
|
||||
@@ -15,6 +15,7 @@ import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover
|
||||
import { Switch } from "@/components/ui/switch";
|
||||
import type { ConnectionConfig, DatabaseType, JdbcDriverInfo, JdbcMavenBundleInfo, ProxyTunnelConfig, SshTunnelConfig, TransportLayerConfig } from "@/types/database";
|
||||
import type { MqAdminConfig, MqAuth, MqSystemKind } from "@/types/mq";
|
||||
import type { NacosAdminConfig, NacosAuthConfig } from "@/types/nacos";
|
||||
import { useConnectionStore } from "@/stores/connectionStore";
|
||||
import { useSettingsStore } from "@/stores/settingsStore";
|
||||
import { useToast } from "@/composables/useToast";
|
||||
@@ -42,12 +43,17 @@ type DialogStep = "select" | "config";
|
||||
type DbPickerView = "icon" | "list";
|
||||
type ConfigTab = "connection" | "advanced" | "tls" | "transport";
|
||||
type MqTokenSigningMode = "none" | "hs256" | "rs256";
|
||||
type NacosAuthKind = NacosAuthConfig["kind"];
|
||||
type JdbcDriverSelectItem = {
|
||||
id: string;
|
||||
label: string;
|
||||
paths: string[];
|
||||
};
|
||||
|
||||
const NACOS_DEFAULT_CONSOLE_URL = "http://127.0.0.1:8085";
|
||||
const NACOS_LEGACY_SERVER_PORT = "8848";
|
||||
const NACOS_DOCKER_CONSOLE_PORT = "8085";
|
||||
|
||||
type LegacyTransportFields = {
|
||||
ssh_enabled?: boolean;
|
||||
ssh_host?: string;
|
||||
@@ -307,6 +313,14 @@ const mqTlsSkipVerify = ref(false);
|
||||
const mqPinnedVersion = ref(pinnedVersionToSelection(undefined));
|
||||
const mqTokenSigningMode = ref<MqTokenSigningMode>("none");
|
||||
const mqTokenSigningKey = ref("");
|
||||
const nacosServerAddr = ref(NACOS_DEFAULT_CONSOLE_URL);
|
||||
const nacosNamespace = ref("");
|
||||
const nacosContextPath = ref("");
|
||||
const nacosAuthKind = ref<NacosAuthKind>("none");
|
||||
const nacosUsername = ref("nacos");
|
||||
const nacosPassword = ref("");
|
||||
const nacosTlsSkipVerify = ref(false);
|
||||
const nacosPageSize = ref(20);
|
||||
|
||||
const colorOptions = [
|
||||
{ value: "", class: "bg-transparent border-dashed", labelKey: "connection.colorNone" },
|
||||
@@ -509,6 +523,7 @@ const driverProfiles: Record<
|
||||
iotdb: { type: "iotdb", port: 6667, user: "root", label: "Apache IoTDB", icon: "iotdb" },
|
||||
etcd: { type: "etcd", port: 2379, user: "", label: "etcd", icon: "etcd" },
|
||||
mq: { type: "mq", port: 8080, user: "", label: "Apache Pulsar", icon: "pulsar", host: "127.0.0.1" },
|
||||
nacos: { type: "nacos", port: 8848, user: "nacos", label: "Nacos", icon: "nacos", host: "127.0.0.1" },
|
||||
iris: { type: "iris", port: 1972, user: "_SYSTEM", label: "IRIS", icon: "iris" },
|
||||
influxdb: { type: "influxdb", port: 8086, user: "", label: "InfluxDB", icon: "InfluxDB" },
|
||||
custom_mysql: {
|
||||
@@ -574,6 +589,26 @@ function hydrateMqFields(value: unknown) {
|
||||
resetMqFields(value as Partial<MqAdminConfig>);
|
||||
}
|
||||
|
||||
function resetNacosFields(config?: Partial<NacosAdminConfig>) {
|
||||
nacosServerAddr.value = config?.serverAddr?.trim() || NACOS_DEFAULT_CONSOLE_URL;
|
||||
nacosNamespace.value = config?.namespace || "";
|
||||
nacosContextPath.value = config?.contextPath || "";
|
||||
nacosTlsSkipVerify.value = !!config?.tlsSkipVerify;
|
||||
nacosPageSize.value = Number(config?.pageSize) > 0 ? Number(config?.pageSize) : 20;
|
||||
const auth = (config?.auth || { kind: "none" }) as NacosAuthConfig;
|
||||
nacosAuthKind.value = auth.kind || "none";
|
||||
nacosUsername.value = auth.username || "nacos";
|
||||
nacosPassword.value = auth.password || "";
|
||||
}
|
||||
|
||||
function hydrateNacosFields(value: unknown) {
|
||||
if (!value || typeof value !== "object") {
|
||||
resetNacosFields();
|
||||
return;
|
||||
}
|
||||
resetNacosFields(value as Partial<NacosAdminConfig>);
|
||||
}
|
||||
|
||||
function requireMqField(value: string, message: string): string {
|
||||
const trimmed = value.trim();
|
||||
if (!trimmed) throw new Error(message);
|
||||
@@ -629,6 +664,65 @@ function buildMqAdminConfig(): MqAdminConfig {
|
||||
};
|
||||
}
|
||||
|
||||
function buildNacosAuth(): NacosAuthConfig {
|
||||
if (nacosAuthKind.value === "usernamePassword") {
|
||||
return {
|
||||
kind: "usernamePassword",
|
||||
username: requireMqField(nacosUsername.value, t("connection.nacosUsernameRequired")),
|
||||
password: nacosPassword.value,
|
||||
};
|
||||
}
|
||||
return { kind: "none" };
|
||||
}
|
||||
|
||||
function buildNacosAdminConfig(): NacosAdminConfig {
|
||||
return {
|
||||
serverAddr: requireMqField(nacosServerAddr.value, t("connection.nacosConsoleUrlRequired")),
|
||||
namespace: nacosNamespace.value.trim() || undefined,
|
||||
contextPath: nacosContextPath.value.trim(),
|
||||
auth: buildNacosAuth(),
|
||||
tlsSkipVerify: nacosTlsSkipVerify.value || undefined,
|
||||
pageSize: Number(nacosPageSize.value) > 0 ? Number(nacosPageSize.value) : 20,
|
||||
};
|
||||
}
|
||||
|
||||
function dockerNacosConsoleFallbackUrl(serverAddr: string): string | null {
|
||||
let parsed: URL;
|
||||
try {
|
||||
parsed = new URL(serverAddr);
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
const port = parsed.port || (parsed.protocol === "https:" ? "443" : "80");
|
||||
const host = parsed.hostname.toLowerCase();
|
||||
if (port !== NACOS_LEGACY_SERVER_PORT || !["127.0.0.1", "localhost", "::1"].includes(host)) {
|
||||
return null;
|
||||
}
|
||||
parsed.port = NACOS_DOCKER_CONSOLE_PORT;
|
||||
return parsed.toString().replace(/\/$/, "");
|
||||
}
|
||||
|
||||
function isNacosAdminEndpointNotFound(message: string): boolean {
|
||||
return /Nacos admin endpoint was not found/i.test(message);
|
||||
}
|
||||
|
||||
async function tryNacosDockerConsoleFallback(config: ConnectionConfig, originalError: string): Promise<string | null> {
|
||||
if (config.db_type !== "nacos" || !isNacosAdminEndpointNotFound(originalError)) return null;
|
||||
const fallbackUrl = dockerNacosConsoleFallbackUrl(nacosServerAddr.value);
|
||||
if (!fallbackUrl || fallbackUrl === nacosServerAddr.value.trim()) return null;
|
||||
|
||||
const previousUrl = nacosServerAddr.value;
|
||||
nacosServerAddr.value = fallbackUrl;
|
||||
try {
|
||||
const fallbackConfig = connectionConfigForSubmit(config.id);
|
||||
const message = await api.testConnection(fallbackConfig);
|
||||
return `${message} ${t("connection.nacosConsoleUrlAutoAdjusted", { from: previousUrl.trim(), to: fallbackUrl })}`;
|
||||
} catch {
|
||||
nacosServerAddr.value = previousUrl;
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function applyMqAdminUrl(config: LegacyConnectionConfig, adminUrl: string) {
|
||||
let parsed: URL;
|
||||
try {
|
||||
@@ -642,6 +736,19 @@ function applyMqAdminUrl(config: LegacyConnectionConfig, adminUrl: string) {
|
||||
config.ssl = parsed.protocol === "https:";
|
||||
}
|
||||
|
||||
function applyNacosServerAddr(config: LegacyConnectionConfig, serverAddr: string) {
|
||||
let parsed: URL;
|
||||
try {
|
||||
parsed = new URL(serverAddr);
|
||||
} catch {
|
||||
throw new Error("Nacos server address is invalid");
|
||||
}
|
||||
const port = Number(parsed.port) || (parsed.protocol === "https:" ? 443 : 8848);
|
||||
config.host = parsed.hostname;
|
||||
config.port = port;
|
||||
config.ssl = parsed.protocol === "https:";
|
||||
}
|
||||
|
||||
function isCustomCompatibleProfile() {
|
||||
return selectedType.value === "custom_mysql" || selectedType.value === "custom_postgres";
|
||||
}
|
||||
@@ -691,6 +798,12 @@ function applyProfile(val: string, preserveConnectionFields = false) {
|
||||
form.value.database = undefined;
|
||||
form.value.connection_string = undefined;
|
||||
}
|
||||
if (profile.type === "nacos") {
|
||||
resetNacosFields();
|
||||
form.value.database = undefined;
|
||||
form.value.connection_string = undefined;
|
||||
form.value.url_params = "";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -763,6 +876,11 @@ watch(
|
||||
} else {
|
||||
resetMqFields();
|
||||
}
|
||||
if (config.db_type === "nacos") {
|
||||
hydrateNacosFields(config.external_config);
|
||||
} else {
|
||||
resetNacosFields();
|
||||
}
|
||||
h2ConnectionMode.value = h2ConnectionModeForConfig(config);
|
||||
customColorInput.value = config.color || "";
|
||||
selectedTransportLayerId.value = form.value.transport_layers?.[0]?.id || null;
|
||||
@@ -786,6 +904,7 @@ watch(
|
||||
selectedType.value = "mysql";
|
||||
customDriverName.value = "";
|
||||
resetMqFields();
|
||||
resetNacosFields();
|
||||
oceanbaseSubMode.value = "mysql";
|
||||
h2ConnectionMode.value = "file";
|
||||
dialogStep.value = "select";
|
||||
@@ -922,6 +1041,7 @@ const iconTypeMap: Record<string, string> = {
|
||||
iotdb: "iotdb",
|
||||
etcd: "etcd",
|
||||
mq: "mq",
|
||||
nacos: "nacos",
|
||||
dm: "dm",
|
||||
h2: "h2",
|
||||
snowflake: "snowflake",
|
||||
@@ -1004,6 +1124,7 @@ const dbOptions: DbOption[] = [
|
||||
{ value: "iotdb", label: "Apache IoTDB" },
|
||||
{ value: "etcd", label: "etcd" },
|
||||
{ value: "mq", label: "Apache Pulsar" },
|
||||
{ value: "nacos", label: "Nacos" },
|
||||
{ value: "influxdb", label: "InfluxDB" },
|
||||
{ value: "iris", label: "IRIS" },
|
||||
{ value: "jdbc", label: "JDBC" },
|
||||
@@ -1160,6 +1281,7 @@ const testResultMessage = computed(() => {
|
||||
});
|
||||
const hasRequiredConnectionTarget = computed(() => {
|
||||
if (form.value.db_type === "mq") return !!mqAdminUrl.value.trim();
|
||||
if (form.value.db_type === "nacos") return !!nacosServerAddr.value.trim();
|
||||
if (isH2FileMode.value) return !!(form.value.host.trim() || h2FilePathFromJdbcUrl(form.value.connection_string));
|
||||
return !!(form.value.host || (mongoUseUrl.value && form.value.connection_string) || (form.value.db_type === "jdbc" && form.value.connection_string) || connectionUrlInput.value.trim());
|
||||
});
|
||||
@@ -1209,8 +1331,8 @@ async function testConnection() {
|
||||
const runId = ++testRunId;
|
||||
isTesting.value = true;
|
||||
testResult.value = null;
|
||||
const config = connectionConfigForSubmit(editingId.value || uuid());
|
||||
try {
|
||||
const config = connectionConfigForSubmit(editingId.value || uuid());
|
||||
const msg = await api.testConnection(config);
|
||||
if (runId !== testRunId) return;
|
||||
if (config.db_type === "mongodb" && /legacy driver/i.test(msg)) {
|
||||
@@ -1219,7 +1341,10 @@ async function testConnection() {
|
||||
testResult.value = { ok: true, message: msg };
|
||||
} catch (e: any) {
|
||||
if (runId !== testRunId) return;
|
||||
testResult.value = { ok: false, message: mongodbAuthFailureHint(String(e)) };
|
||||
const message = mongodbAuthFailureHint(String(e));
|
||||
const fallbackMessage = await tryNacosDockerConsoleFallback(config, message);
|
||||
if (runId !== testRunId) return;
|
||||
testResult.value = fallbackMessage ? { ok: true, message: fallbackMessage } : { ok: false, message };
|
||||
} finally {
|
||||
if (runId === testRunId) {
|
||||
isTesting.value = false;
|
||||
@@ -1301,6 +1426,15 @@ function connectionConfigForSubmit(id: string): ConnectionConfig {
|
||||
config.database = undefined;
|
||||
config.connection_string = undefined;
|
||||
config.url_params = "";
|
||||
} else if (config.db_type === "nacos") {
|
||||
const nacosConfig = buildNacosAdminConfig();
|
||||
config.external_config = nacosConfig;
|
||||
applyNacosServerAddr(config, nacosConfig.serverAddr);
|
||||
config.username = nacosAuthKind.value === "usernamePassword" ? nacosUsername.value.trim() : "";
|
||||
config.password = nacosAuthKind.value === "usernamePassword" ? nacosPassword.value : "";
|
||||
config.database = nacosConfig.namespace || undefined;
|
||||
config.connection_string = undefined;
|
||||
config.url_params = "";
|
||||
} else {
|
||||
config.external_config = undefined;
|
||||
}
|
||||
@@ -2791,6 +2925,54 @@ function openExternalUrl(url: string) {
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- Nacos: server address, namespace and auth -->
|
||||
<template v-else-if="form.db_type === 'nacos'">
|
||||
<div class="grid grid-cols-4 items-center gap-4">
|
||||
<Label class="text-right">{{ t("connection.nacosConsoleUrl") }}</Label>
|
||||
<Input v-model="nacosServerAddr" class="col-span-3" placeholder="http://127.0.0.1:8085" />
|
||||
</div>
|
||||
<div class="grid grid-cols-4 items-start gap-4">
|
||||
<span />
|
||||
<p class="col-span-3 m-0 text-xs leading-5 text-muted-foreground">{{ t("connection.nacosConsoleUrlHint") }}</p>
|
||||
</div>
|
||||
<div class="grid grid-cols-4 items-center gap-4">
|
||||
<Label class="text-right">{{ t("connection.nacosNamespace") }}</Label>
|
||||
<Input v-model="nacosNamespace" class="col-span-3" placeholder="public" />
|
||||
</div>
|
||||
<div class="grid grid-cols-4 items-center gap-4">
|
||||
<Label class="text-right">{{ t("connection.nacosContextPath") }}</Label>
|
||||
<Input v-model="nacosContextPath" class="col-span-3" :placeholder="t('connection.nacosContextPathPlaceholder')" />
|
||||
</div>
|
||||
<div class="grid grid-cols-4 items-center gap-4">
|
||||
<Label class="text-right">{{ t("connection.nacosAuth") }}</Label>
|
||||
<div class="col-span-3 flex flex-wrap gap-2">
|
||||
<Button size="sm" :variant="nacosAuthKind === 'none' ? 'default' : 'outline'" @click="nacosAuthKind = 'none'">{{ t("connection.nacosAuthNone") }}</Button>
|
||||
<Button size="sm" :variant="nacosAuthKind === 'usernamePassword' ? 'default' : 'outline'" @click="nacosAuthKind = 'usernamePassword'">{{ t("connection.nacosAuthUserPassword") }}</Button>
|
||||
</div>
|
||||
</div>
|
||||
<template v-if="nacosAuthKind === 'usernamePassword'">
|
||||
<div class="grid grid-cols-4 items-center gap-4">
|
||||
<Label class="text-right">{{ t("connection.user") }}</Label>
|
||||
<Input v-model="nacosUsername" class="col-span-3" placeholder="nacos" />
|
||||
</div>
|
||||
<div class="grid grid-cols-4 items-center gap-4">
|
||||
<Label class="text-right">{{ t("connection.password") }}</Label>
|
||||
<PasswordInput v-model="nacosPassword" class="col-span-3" />
|
||||
</div>
|
||||
</template>
|
||||
<div class="grid grid-cols-4 items-center gap-4">
|
||||
<Label class="text-right text-xs">{{ t("connection.nacosTls") }}</Label>
|
||||
<label class="col-span-3 inline-flex items-center gap-2">
|
||||
<input type="checkbox" v-model="nacosTlsSkipVerify" class="mr-0" />
|
||||
<span class="text-xs text-muted-foreground">{{ t("connection.nacosTlsSkipVerify") }}</span>
|
||||
</label>
|
||||
</div>
|
||||
<div class="grid grid-cols-4 items-center gap-4">
|
||||
<Label class="text-right">{{ t("connection.nacosPageSize") }}</Label>
|
||||
<Input v-model.number="nacosPageSize" type="number" min="1" max="500" class="col-span-3" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- Redis: host, port, user, password, ssl -->
|
||||
<template v-else-if="form.db_type === 'redis'">
|
||||
<div class="grid grid-cols-4 items-center gap-4">
|
||||
|
||||
@@ -114,7 +114,7 @@ const showModified = ref(true);
|
||||
|
||||
let syncPlanRequestId = 0;
|
||||
|
||||
const sqlConnections = computed(() => store.connections.filter((connection) => !["redis", "mongodb", "elasticsearch", "qdrant", "milvus", "etcd"].includes(connection.db_type)));
|
||||
const sqlConnections = computed(() => store.connections.filter((connection) => !["redis", "mongodb", "elasticsearch", "qdrant", "milvus", "etcd", "mq", "nacos"].includes(connection.db_type)));
|
||||
const selectedSourceTableNames = computed(() => sourceTables.value.filter((table) => selectedSourceTables.value.has(table)));
|
||||
const isBatchCompare = computed(() => selectedSourceTableNames.value.length > 1);
|
||||
const filteredSourceTables = computed(() => {
|
||||
|
||||
@@ -53,7 +53,7 @@ const targetSchemas = ref<string[]>([]);
|
||||
const sourceDbVersion = ref<string | null>(null);
|
||||
const targetDbVersion = ref<string | null>(null);
|
||||
|
||||
const sqlConnections = computed(() => store.connections.filter((c: any) => c.db_type !== "mongodb" && c.db_type !== "redis"));
|
||||
const sqlConnections = computed(() => store.connections.filter((c: any) => !["mongodb", "redis", "elasticsearch", "etcd", "mq", "nacos"].includes(c.db_type)));
|
||||
|
||||
const sourceConfig = computed(() => store.getConfig(props.sourceConnectionId));
|
||||
const targetConfig = computed(() => store.getConfig(props.targetConnectionId));
|
||||
|
||||
@@ -8,6 +8,7 @@ import { ChevronUp, ChevronDown, ChevronRight, X } from "@lucide/vue";
|
||||
|
||||
const props = defineProps<{
|
||||
view: EditorView | null;
|
||||
tone?: "app" | "editor";
|
||||
}>();
|
||||
|
||||
const { t } = useI18n();
|
||||
@@ -214,56 +215,224 @@ defineExpose({ openSearch, openReplace, closeSearch });
|
||||
|
||||
<template>
|
||||
<Transition enter-active-class="transition-[transform,opacity] duration-150" leave-active-class="transition-[transform,opacity] duration-100" enter-from-class="opacity-0 -translate-y-1" leave-to-class="opacity-0 -translate-y-1">
|
||||
<div v-if="searchVisible" class="absolute top-1 right-4 z-[9999] isolate flex flex-col gap-1 rounded-md border bg-popover p-1.5 text-popover-foreground shadow-lg">
|
||||
<div class="flex items-center gap-0.5">
|
||||
<button class="w-5 h-5 flex items-center justify-center rounded text-muted-foreground hover:bg-accent hover:text-foreground" :title="showReplace ? t('editor.search.collapseReplace') : t('editor.search.expandReplace')" @click="showReplace = !showReplace">
|
||||
<ChevronRight class="w-3 h-3 transition-transform" :class="showReplace && 'rotate-90'" />
|
||||
<div v-if="searchVisible" class="editor-search-panel absolute right-4 top-3 z-[9999] isolate flex flex-col gap-1 rounded-lg border border-border bg-popover p-1.5 text-popover-foreground shadow-xl ring-1 ring-border/60" :class="{ 'editor-search-panel--editor': tone === 'editor' }">
|
||||
<div class="flex items-center gap-1">
|
||||
<button class="flex h-7 w-7 items-center justify-center rounded-md text-muted-foreground transition-colors hover:bg-accent hover:text-foreground" :title="showReplace ? t('editor.search.collapseReplace') : t('editor.search.expandReplace')" @click="showReplace = !showReplace">
|
||||
<ChevronRight class="h-4 w-4 transition-transform" :class="showReplace && 'rotate-90'" />
|
||||
</button>
|
||||
<input
|
||||
ref="searchInputRef"
|
||||
v-model="searchText"
|
||||
autocapitalize="off"
|
||||
autocorrect="off"
|
||||
spellcheck="false"
|
||||
class="w-48 h-6 text-xs bg-input border rounded px-2 outline-none focus:ring-1 focus:ring-ring placeholder:text-muted-foreground"
|
||||
:placeholder="t('editor.search.find')"
|
||||
@keydown="onSearchKeydown"
|
||||
/>
|
||||
<button class="w-6 h-6 flex items-center justify-center rounded text-xs font-mono hover:bg-accent" :class="caseSensitive ? 'bg-accent text-accent-foreground' : 'text-muted-foreground'" :title="t('editor.search.caseSensitive')" @click="caseSensitive = !caseSensitive">Aa</button>
|
||||
<button class="w-6 h-6 flex items-center justify-center rounded text-xs font-mono hover:bg-accent" :class="useRegex ? 'bg-accent text-accent-foreground' : 'text-muted-foreground'" :title="t('editor.search.regex')" @click="useRegex = !useRegex">.*</button>
|
||||
<span class="text-xs text-muted-foreground min-w-[3rem] text-center shrink-0">
|
||||
<div class="flex h-8 w-64 items-center rounded-md border border-input bg-background focus-within:border-ring focus-within:ring-1 focus-within:ring-ring">
|
||||
<input
|
||||
ref="searchInputRef"
|
||||
v-model="searchText"
|
||||
autocapitalize="off"
|
||||
autocorrect="off"
|
||||
spellcheck="false"
|
||||
class="h-full min-w-0 flex-1 bg-transparent px-2 text-sm text-foreground outline-none placeholder:text-muted-foreground"
|
||||
:placeholder="t('editor.search.find')"
|
||||
@keydown="onSearchKeydown"
|
||||
/>
|
||||
<button
|
||||
class="flex h-6 min-w-7 items-center justify-center rounded px-1.5 text-xs font-medium transition-colors hover:bg-accent hover:text-foreground"
|
||||
:class="caseSensitive ? 'bg-accent text-foreground' : 'text-muted-foreground'"
|
||||
:title="t('editor.search.caseSensitive')"
|
||||
@click="caseSensitive = !caseSensitive"
|
||||
>
|
||||
Aa
|
||||
</button>
|
||||
<button
|
||||
class="mr-1 flex h-6 min-w-7 items-center justify-center rounded px-1.5 font-mono text-xs transition-colors hover:bg-accent hover:text-foreground"
|
||||
:class="useRegex ? 'bg-accent text-foreground' : 'text-muted-foreground'"
|
||||
:title="t('editor.search.regex')"
|
||||
@click="useRegex = !useRegex"
|
||||
>
|
||||
.*
|
||||
</button>
|
||||
</div>
|
||||
<span class="min-w-[3.4rem] shrink-0 text-center text-xs" :class="searchText && matchCount === 0 ? 'text-destructive' : 'text-muted-foreground'">
|
||||
{{ searchText && matchCount > 0 ? `${currentMatchIndex}/${matchCount}${matchCountLimited ? "+" : ""}` : t("editor.search.noResults") }}
|
||||
</span>
|
||||
<button class="w-5 h-5 flex items-center justify-center rounded text-muted-foreground hover:bg-accent hover:text-foreground" :title="t('editor.search.prevMatch')" @click="prevMatch">
|
||||
<ChevronUp class="w-3.5 h-3.5" />
|
||||
<button class="flex h-7 w-7 items-center justify-center rounded-md text-muted-foreground transition-colors hover:bg-accent hover:text-foreground" :title="t('editor.search.prevMatch')" @click="prevMatch">
|
||||
<ChevronUp class="h-4 w-4" />
|
||||
</button>
|
||||
<button class="w-5 h-5 flex items-center justify-center rounded text-muted-foreground hover:bg-accent hover:text-foreground" :title="t('editor.search.nextMatch')" @click="nextMatch">
|
||||
<ChevronDown class="w-3.5 h-3.5" />
|
||||
<button class="flex h-7 w-7 items-center justify-center rounded-md text-muted-foreground transition-colors hover:bg-accent hover:text-foreground" :title="t('editor.search.nextMatch')" @click="nextMatch">
|
||||
<ChevronDown class="h-4 w-4" />
|
||||
</button>
|
||||
<button class="w-5 h-5 flex items-center justify-center rounded text-muted-foreground hover:bg-accent hover:text-foreground" :title="t('editor.search.close')" @click="closeSearch">
|
||||
<X class="w-3.5 h-3.5" />
|
||||
<button class="flex h-7 w-7 items-center justify-center rounded-md text-muted-foreground transition-colors hover:bg-accent hover:text-foreground" :title="t('editor.search.close')" @click="closeSearch">
|
||||
<X class="h-4 w-4" />
|
||||
</button>
|
||||
</div>
|
||||
<div v-if="showReplace" class="flex items-center gap-0.5">
|
||||
<div class="w-5 h-5 shrink-0" />
|
||||
<input
|
||||
ref="replaceInputRef"
|
||||
v-model="replaceText"
|
||||
autocapitalize="off"
|
||||
autocorrect="off"
|
||||
spellcheck="false"
|
||||
class="w-48 h-6 text-xs bg-input border rounded px-2 outline-none focus:ring-1 focus:ring-ring placeholder:text-muted-foreground"
|
||||
:placeholder="t('editor.search.replace')"
|
||||
@keydown.enter.prevent="doReplace"
|
||||
@keydown.escape.prevent="closeSearch"
|
||||
/>
|
||||
<button class="h-6 px-1.5 flex items-center justify-center rounded text-xs text-muted-foreground hover:bg-accent hover:text-foreground border" :title="t('editor.search.replace')" @click="doReplace">
|
||||
<div v-if="showReplace" class="flex items-center gap-1">
|
||||
<div class="h-7 w-7 shrink-0" />
|
||||
<div class="flex h-8 w-64 items-center rounded-md border border-input bg-background focus-within:border-ring focus-within:ring-1 focus-within:ring-ring">
|
||||
<input
|
||||
ref="replaceInputRef"
|
||||
v-model="replaceText"
|
||||
autocapitalize="off"
|
||||
autocorrect="off"
|
||||
spellcheck="false"
|
||||
class="h-full min-w-0 flex-1 bg-transparent px-2 text-sm text-foreground outline-none placeholder:text-muted-foreground"
|
||||
:placeholder="t('editor.search.replace')"
|
||||
@keydown.enter.prevent="doReplace"
|
||||
@keydown.escape.prevent="closeSearch"
|
||||
/>
|
||||
</div>
|
||||
<button class="flex h-7 items-center justify-center rounded-md border border-border px-2 text-xs text-muted-foreground transition-colors hover:bg-accent hover:text-foreground" :title="t('editor.search.replace')" @click="doReplace">
|
||||
{{ t("editor.search.replace") }}
|
||||
</button>
|
||||
<button class="h-6 px-1.5 flex items-center justify-center rounded text-xs text-muted-foreground hover:bg-accent hover:text-foreground border" :title="t('editor.search.replaceAll')" @click="doReplaceAll">
|
||||
<button class="flex h-7 items-center justify-center rounded-md border border-border px-2 text-xs text-muted-foreground transition-colors hover:bg-accent hover:text-foreground" :title="t('editor.search.replaceAll')" @click="doReplaceAll">
|
||||
{{ t("editor.search.replaceAll") }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</Transition>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.editor-search-panel {
|
||||
max-width: min(calc(100vw - 2rem), 620px);
|
||||
}
|
||||
|
||||
.editor-search-panel--editor {
|
||||
background: #f6f7f9;
|
||||
border-color: #d8dce3;
|
||||
border-radius: 6px;
|
||||
box-shadow:
|
||||
0 8px 22px rgb(15 23 42 / 0.14),
|
||||
0 1px 0 rgb(255 255 255 / 0.78) inset;
|
||||
color: #20242a;
|
||||
gap: 3px;
|
||||
max-width: min(calc(100vw - 2rem), 500px);
|
||||
padding: 4px 6px;
|
||||
right: 0.75rem;
|
||||
top: 0.75rem;
|
||||
}
|
||||
|
||||
.editor-search-panel--editor :deep(.h-8) {
|
||||
height: 27px;
|
||||
}
|
||||
|
||||
.editor-search-panel--editor :deep(.h-7) {
|
||||
height: 27px;
|
||||
}
|
||||
|
||||
.editor-search-panel--editor :deep(.w-7) {
|
||||
width: 27px;
|
||||
}
|
||||
|
||||
.editor-search-panel--editor :deep(.w-64) {
|
||||
width: 230px;
|
||||
}
|
||||
|
||||
.editor-search-panel--editor :deep(button) {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.editor-search-panel--editor :deep(button.px-2) {
|
||||
padding-left: 7px;
|
||||
padding-right: 7px;
|
||||
}
|
||||
|
||||
.editor-search-panel--editor :deep(.min-w-\[3\.4rem\]) {
|
||||
min-width: 3.25rem;
|
||||
}
|
||||
|
||||
.editor-search-panel--editor :deep(.border-input) {
|
||||
background: #ffffff;
|
||||
border-color: #c7ccd5;
|
||||
border-radius: 5px;
|
||||
box-shadow: 0 1px 0 rgb(15 23 42 / 0.03) inset;
|
||||
}
|
||||
|
||||
.editor-search-panel--editor :deep(.focus-within\:border-ring:focus-within) {
|
||||
border-color: #3b82f6;
|
||||
box-shadow: 0 0 0 1px #3b82f6;
|
||||
}
|
||||
|
||||
.editor-search-panel--editor :deep(input) {
|
||||
color: #20242a;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.editor-search-panel--editor :deep(input::placeholder) {
|
||||
color: #7a828e;
|
||||
}
|
||||
|
||||
.editor-search-panel--editor :deep(.text-muted-foreground) {
|
||||
color: #6e7681;
|
||||
}
|
||||
|
||||
.editor-search-panel--editor :deep(.text-destructive) {
|
||||
color: #c2410c;
|
||||
}
|
||||
|
||||
.editor-search-panel--editor :deep(.hover\:bg-accent:hover),
|
||||
.editor-search-panel--editor :deep(.bg-accent) {
|
||||
background: #e6e9ef;
|
||||
}
|
||||
|
||||
.editor-search-panel--editor :deep(.hover\:text-foreground:hover),
|
||||
.editor-search-panel--editor :deep(.text-foreground) {
|
||||
color: #1f2329;
|
||||
}
|
||||
|
||||
.editor-search-panel--editor :deep(button.border-border) {
|
||||
border-color: #d0d5dd;
|
||||
}
|
||||
|
||||
:global(.dark) .editor-search-panel--editor {
|
||||
background: #20252d;
|
||||
border-color: #2c333d;
|
||||
box-shadow:
|
||||
0 8px 24px rgb(0 0 0 / 0.28),
|
||||
0 1px 0 rgb(255 255 255 / 0.04) inset;
|
||||
color: #d4d7dc;
|
||||
}
|
||||
|
||||
:global(.dark) .editor-search-panel--editor :deep(.border-input) {
|
||||
background: #191d25;
|
||||
border-color: #2f3742;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
:global(.dark) .editor-search-panel--editor :deep(.focus-within\:border-ring:focus-within) {
|
||||
border-color: #4d8dff;
|
||||
box-shadow: 0 0 0 1px #4d8dff;
|
||||
}
|
||||
|
||||
:global(.dark) .editor-search-panel--editor :deep(input) {
|
||||
color: #d7dae0;
|
||||
}
|
||||
|
||||
:global(.dark) .editor-search-panel--editor :deep(input::placeholder) {
|
||||
color: #858c97;
|
||||
}
|
||||
|
||||
:global(.dark) .editor-search-panel--editor :deep(.text-muted-foreground) {
|
||||
color: #9aa2ad;
|
||||
}
|
||||
|
||||
:global(.dark) .editor-search-panel--editor :deep(.text-destructive) {
|
||||
color: #f59e7a;
|
||||
}
|
||||
|
||||
:global(.dark) .editor-search-panel--editor :deep(.hover\:bg-accent:hover),
|
||||
:global(.dark) .editor-search-panel--editor :deep(.bg-accent) {
|
||||
background: #303844;
|
||||
}
|
||||
|
||||
:global(.dark) .editor-search-panel--editor :deep(.hover\:text-foreground:hover),
|
||||
:global(.dark) .editor-search-panel--editor :deep(.text-foreground) {
|
||||
color: #f2f4f8;
|
||||
}
|
||||
|
||||
:global(.dark) .editor-search-panel--editor :deep(button.border-border) {
|
||||
border-color: #38414d;
|
||||
}
|
||||
|
||||
@media (max-width: 720px) {
|
||||
.editor-search-panel {
|
||||
left: 0.75rem;
|
||||
right: 0.75rem;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -65,7 +65,7 @@ const exportCancelled = ref(false);
|
||||
const pendingPrefillTable = ref("");
|
||||
const pendingPrefillTables = ref<string[]>([]);
|
||||
|
||||
const sqlConnections = computed(() => store.connections.filter((c) => !["redis", "mongodb", "elasticsearch", "qdrant", "milvus", "etcd"].includes(c.db_type)));
|
||||
const sqlConnections = computed(() => store.connections.filter((c) => !["redis", "mongodb", "elasticsearch", "qdrant", "milvus", "etcd", "mq", "nacos"].includes(c.db_type)));
|
||||
|
||||
const canExport = computed(() => connectionId.value && database.value && schema.value && !loadingTables.value && !tableError.value && (tables.value.length === 0 || selectedTables.value.length > 0) && (includeStructure.value || includeData.value || includeObjects.value) && !isExporting.value);
|
||||
|
||||
|
||||
@@ -81,6 +81,7 @@ const assetIcons: Record<string, string> = {
|
||||
milvus: "milvus.png",
|
||||
mq: "pulsar",
|
||||
pulsar: "pulsar",
|
||||
nacos: "nacos.png",
|
||||
iris: "iris.png",
|
||||
influxdb: "influxdb",
|
||||
};
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
import { computed, ref, watch, nextTick } from "vue";
|
||||
import type { CSSProperties } from "vue";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import { X, Pin, ChevronDown, Table2, Code2, TableProperties, PencilRuler, KeyRound, Pencil, Package, Check, Lock, Copy, AlertTriangle } from "@lucide/vue";
|
||||
import { X, Pin, ChevronDown, Table2, Code2, TableProperties, PencilRuler, KeyRound, Pencil, Package, Check, Lock, Copy, AlertTriangle, Network } from "@lucide/vue";
|
||||
import CustomContextMenu, { type ContextMenuItem } from "@/components/ui/CustomContextMenu.vue";
|
||||
import LightDropdown from "@/components/ui/LightDropdown.vue";
|
||||
import { Tooltip, TooltipTrigger, TooltipContent } from "@/components/ui/tooltip";
|
||||
@@ -236,6 +236,7 @@ function tabMenuIcon(tab: QueryTab) {
|
||||
if (tab.mode === "data" || tab.mode === "mongo" || tab.mode === "redis") return Table2;
|
||||
if (tab.mode === "vector") return TableProperties;
|
||||
if (tab.mode === "etcd") return KeyRound;
|
||||
if (tab.mode === "nacos") return Network;
|
||||
if (tab.mode === "objects") return TableProperties;
|
||||
if (tab.mode === "structure") return PencilRuler;
|
||||
return Code2;
|
||||
@@ -329,6 +330,7 @@ function activateTab(tabId: string) {
|
||||
<Table2 v-if="tab.mode === 'data' || tab.mode === 'mongo' || tab.mode === 'redis'" class="h-3.5 w-3.5" />
|
||||
<TableProperties v-else-if="tab.mode === 'vector'" class="h-3.5 w-3.5" />
|
||||
<KeyRound v-else-if="tab.mode === 'etcd'" class="h-3.5 w-3.5" />
|
||||
<Network v-else-if="tab.mode === 'nacos'" class="h-3.5 w-3.5" />
|
||||
<TableProperties v-else-if="tab.mode === 'objects'" class="h-3.5 w-3.5" />
|
||||
<PencilRuler v-else-if="tab.mode === 'structure'" class="h-3.5 w-3.5" />
|
||||
<Code2 v-else class="h-3.5 w-3.5" />
|
||||
|
||||
@@ -38,6 +38,7 @@ const EtcdKeyBrowser = defineAsyncComponent(() => import("@/components/etcd/Etcd
|
||||
const DocumentBrowser = defineAsyncComponent(() => import("@/components/document/DocumentBrowser.vue"));
|
||||
const VectorBrowser = defineAsyncComponent(() => import("@/components/vector/VectorBrowser.vue"));
|
||||
const MqAdminConsole = defineAsyncComponent(() => import("@/components/mq/MqAdminConsole.vue"));
|
||||
const NacosAdminConsole = defineAsyncComponent(() => import("@/components/nacos/NacosAdminConsole.vue"));
|
||||
const ObjectBrowser = defineAsyncComponent(() => import("@/components/objects/ObjectBrowser.vue"));
|
||||
const TableStructureEditor = defineAsyncComponent(() => import("@/components/structure/TableStructureEditor.vue"));
|
||||
const DatabaseUserAdmin = defineAsyncComponent(() => import("@/components/admin/DatabaseUserAdmin.vue"));
|
||||
@@ -1029,6 +1030,12 @@ defineExpose({ focusSearch, refreshData, handleModRTarget, requestQueryEditorExe
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template v-else-if="activeTab.mode === 'nacos'">
|
||||
<div class="flex-1 min-h-0">
|
||||
<NacosAdminConsole :key="activeTab.id" :connection-id="activeTab.connectionId" :namespace="activeTab.nacosNamespace" :namespace-name="activeTab.nacosNamespaceName" :read-only="activeConnection?.read_only ?? false" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- Objects mode: virtualized database object browser -->
|
||||
<template v-else-if="activeTab.mode === 'objects' && activeConnection">
|
||||
<ObjectBrowser
|
||||
|
||||
@@ -61,7 +61,7 @@ const activeConnectionValue = computed(() => props.activeConnection?.id || "");
|
||||
const activeSchemaValue = computed(() => props.activeTab.schema || "");
|
||||
const supportsExplain = computed(() => {
|
||||
const dbType = props.activeConnection?.db_type;
|
||||
return dbType !== "redis" && dbType !== "mongodb" && dbType !== "elasticsearch" && dbType !== "qdrant" && dbType !== "milvus" && dbType !== "etcd";
|
||||
return dbType !== "redis" && dbType !== "mongodb" && dbType !== "elasticsearch" && dbType !== "qdrant" && dbType !== "milvus" && dbType !== "etcd" && dbType !== "mq" && dbType !== "nacos";
|
||||
});
|
||||
const isSingleDb = computed(() => isSingleDatabase(props.activeConnection?.db_type));
|
||||
const hasDefaultDatabaseOption = computed(() => activeDatabaseOptions.value.includes(""));
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,209 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, ref } from "vue";
|
||||
import { Loader2 } from "@lucide/vue";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle } from "@/components/ui/dialog";
|
||||
import { buildNacosInlineDiff, buildNacosSideBySideDiff, summarizeNacosConfigDiff, type NacosDiffLineType, type NacosInlineSegment } from "@/lib/nacosAdmin";
|
||||
|
||||
const open = defineModel<boolean>("open", { default: false });
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
before: string;
|
||||
after: string;
|
||||
title?: string;
|
||||
beforeLabel?: string;
|
||||
afterLabel?: string;
|
||||
confirmLabel?: string;
|
||||
confirmVariant?: "default" | "destructive";
|
||||
showConfirm?: boolean;
|
||||
loading?: boolean;
|
||||
}>(),
|
||||
{
|
||||
title: "",
|
||||
beforeLabel: "",
|
||||
afterLabel: "",
|
||||
confirmLabel: "",
|
||||
confirmVariant: "default",
|
||||
showConfirm: true,
|
||||
loading: false,
|
||||
},
|
||||
);
|
||||
|
||||
const emit = defineEmits<{
|
||||
confirm: [];
|
||||
}>();
|
||||
|
||||
const { t } = useI18n();
|
||||
const inlineCompare = ref(false);
|
||||
const rows = computed(() => buildNacosSideBySideDiff(props.before, props.after));
|
||||
const inlineRows = computed(() => buildNacosInlineDiff(props.before, props.after));
|
||||
const summary = computed(() => summarizeNacosConfigDiff(props.before, props.after));
|
||||
|
||||
const dialogOpen = computed({
|
||||
get: () => open.value,
|
||||
set: (value) => {
|
||||
if (props.loading && !value) return;
|
||||
open.value = value;
|
||||
},
|
||||
});
|
||||
|
||||
function lineClass(type: NacosDiffLineType, side: "left" | "right") {
|
||||
return {
|
||||
"bg-red-500/20 text-red-50": type === "delete" || (type === "modify" && side === "left"),
|
||||
"bg-emerald-500/18 text-emerald-50": type === "insert" || (type === "modify" && side === "right"),
|
||||
"text-zinc-200": type === "equal",
|
||||
"bg-zinc-900/80 text-zinc-600": type === "padding",
|
||||
};
|
||||
}
|
||||
|
||||
function gutterClass(type: NacosDiffLineType, side: "left" | "right") {
|
||||
return {
|
||||
"text-red-300": type === "delete" || (type === "modify" && side === "left"),
|
||||
"text-emerald-300": type === "insert" || (type === "modify" && side === "right"),
|
||||
"text-zinc-500": type === "equal" || type === "padding",
|
||||
};
|
||||
}
|
||||
|
||||
function prefix(type: NacosDiffLineType, side: "left" | "right") {
|
||||
if (type === "delete" || (type === "modify" && side === "left")) return "-";
|
||||
if (type === "insert" || (type === "modify" && side === "right")) return "+";
|
||||
return "";
|
||||
}
|
||||
|
||||
function inlineSegments(content: string, segments: NacosInlineSegment[]) {
|
||||
return segments.length ? segments : [{ value: content, changed: false }];
|
||||
}
|
||||
|
||||
function inlineClass(type: NacosDiffLineType, changed: boolean) {
|
||||
if (!changed) return "";
|
||||
if (type === "delete" || type === "modify") return "nacos-inline-change rounded-[2px] bg-red-500/80 text-red-50";
|
||||
if (type === "insert") return "nacos-inline-change rounded-[2px] bg-emerald-500/75 text-emerald-50";
|
||||
return "";
|
||||
}
|
||||
|
||||
function rightInlineClass(type: NacosDiffLineType, changed: boolean) {
|
||||
if (!changed) return "";
|
||||
if (type === "modify" || type === "insert") return "nacos-inline-change rounded-[2px] bg-emerald-500/75 text-emerald-50";
|
||||
return inlineClass(type, changed);
|
||||
}
|
||||
|
||||
function inlineRowClass(type: "equal" | "delete" | "insert") {
|
||||
return {
|
||||
"bg-red-500/20 text-red-50": type === "delete",
|
||||
"bg-emerald-500/18 text-emerald-50": type === "insert",
|
||||
"text-zinc-200": type === "equal",
|
||||
};
|
||||
}
|
||||
|
||||
function inlineGutterClass(type: "equal" | "delete" | "insert") {
|
||||
return {
|
||||
"text-red-300": type === "delete",
|
||||
"text-emerald-300": type === "insert",
|
||||
"text-zinc-500": type === "equal",
|
||||
};
|
||||
}
|
||||
|
||||
function inlinePrefix(type: "equal" | "delete" | "insert") {
|
||||
if (type === "delete") return "-";
|
||||
if (type === "insert") return "+";
|
||||
return "";
|
||||
}
|
||||
|
||||
function inlineRowSegmentClass(type: "equal" | "delete" | "insert", changed: boolean) {
|
||||
if (!changed) return "";
|
||||
if (type === "delete") return "nacos-inline-change rounded-[2px] bg-red-500/80 text-red-50";
|
||||
if (type === "insert") return "nacos-inline-change rounded-[2px] bg-emerald-500/75 text-emerald-50";
|
||||
return "";
|
||||
}
|
||||
|
||||
function onConfirm() {
|
||||
if (props.loading) return;
|
||||
emit("confirm");
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Dialog v-model:open="dialogOpen">
|
||||
<DialogContent :show-close-button="false" class="nacos-config-diff-dialog flex h-[min(88vh,900px)] flex-col gap-0 overflow-hidden rounded-lg p-0 shadow-2xl">
|
||||
<DialogHeader class="shrink-0 border-b px-5 py-4">
|
||||
<div class="flex items-center justify-between gap-4">
|
||||
<DialogTitle class="text-lg font-semibold">{{ title || t("nacos.configDiffTitle") }}</DialogTitle>
|
||||
<button type="button" class="text-2xl leading-none text-muted-foreground hover:text-foreground" :disabled="loading" @click="open = false">×</button>
|
||||
</div>
|
||||
<div class="mt-3 flex flex-wrap items-center justify-between gap-3">
|
||||
<label class="inline-flex items-center gap-2 text-sm text-muted-foreground">
|
||||
<input v-model="inlineCompare" type="checkbox" class="h-4 w-4 rounded border-border" />
|
||||
<span>{{ t("nacos.inlineCompare") }}</span>
|
||||
</label>
|
||||
<div class="text-xs text-muted-foreground">{{ t("nacos.confirmSaveMessage", { added: summary.addedLines, removed: summary.removedLines }) }}</div>
|
||||
</div>
|
||||
</DialogHeader>
|
||||
|
||||
<div v-if="!inlineCompare" class="grid min-h-0 flex-1 grid-cols-1 gap-4 bg-background px-5 py-4 lg:grid-cols-2">
|
||||
<section class="flex min-w-0 min-h-0 flex-col">
|
||||
<div class="mb-2 text-sm font-medium text-foreground">{{ beforeLabel || t("nacos.currentVersionContent") }}</div>
|
||||
<div class="min-h-0 flex-1 overflow-auto rounded-sm border border-zinc-700 bg-[#1f1f1f] font-mono text-[13px] leading-6 text-zinc-200">
|
||||
<div v-for="row in rows" :key="`${row.id}-left`" class="grid min-w-max grid-cols-[52px_22px_minmax(48rem,1fr)]" :class="lineClass(row.leftType, 'left')">
|
||||
<span class="select-none border-r border-white/8 pr-2 text-right" :class="gutterClass(row.leftType, 'left')">{{ row.leftLineNumber ?? "" }}</span>
|
||||
<span class="select-none pl-2" :class="gutterClass(row.leftType, 'left')">{{ prefix(row.leftType, "left") }}</span>
|
||||
<pre class="whitespace-pre px-2"><template v-for="(segment, index) in inlineSegments(row.leftContent, row.leftInline)" :key="index"><span :class="inlineClass(row.leftType, segment.changed)">{{ segment.value }}</span></template></pre>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="flex min-w-0 min-h-0 flex-col">
|
||||
<div class="mb-2 text-sm font-medium text-foreground">{{ afterLabel || t("nacos.publishVersionContent") }}</div>
|
||||
<div class="min-h-0 flex-1 overflow-auto rounded-sm border border-zinc-700 bg-[#1f1f1f] font-mono text-[13px] leading-6 text-zinc-200">
|
||||
<div v-for="row in rows" :key="`${row.id}-right`" class="grid min-w-max grid-cols-[52px_22px_minmax(48rem,1fr)]" :class="lineClass(row.rightType, 'right')">
|
||||
<span class="select-none border-r border-white/8 pr-2 text-right" :class="gutterClass(row.rightType, 'right')">{{ row.rightLineNumber ?? "" }}</span>
|
||||
<span class="select-none pl-2" :class="gutterClass(row.rightType, 'right')">{{ prefix(row.rightType, "right") }}</span>
|
||||
<pre class="whitespace-pre px-2"><template v-for="(segment, index) in inlineSegments(row.rightContent, row.rightInline)" :key="index"><span :class="rightInlineClass(row.rightType, segment.changed)">{{ segment.value }}</span></template></pre>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<div v-else class="min-h-0 flex-1 bg-background px-5 py-4">
|
||||
<section class="flex h-full min-h-0 flex-col">
|
||||
<div class="mb-2 text-sm font-medium text-foreground">{{ t("nacos.inlineCompare") }}</div>
|
||||
<div class="min-h-0 flex-1 overflow-auto rounded-sm border border-zinc-700 bg-[#1f1f1f] font-mono text-[13px] leading-6 text-zinc-200">
|
||||
<div v-for="row in inlineRows" :key="row.id" class="grid min-w-max grid-cols-[52px_22px_minmax(96rem,1fr)]" :class="inlineRowClass(row.type)">
|
||||
<span class="select-none border-r border-white/8 pr-2 text-right" :class="inlineGutterClass(row.type)">{{ row.lineNumber ?? "" }}</span>
|
||||
<span class="select-none pl-2" :class="inlineGutterClass(row.type)">{{ inlinePrefix(row.type) }}</span>
|
||||
<pre class="whitespace-pre px-2"><template v-for="(segment, index) in row.segments" :key="index"><span :class="inlineRowSegmentClass(row.type, segment.changed)">{{ segment.value }}</span></template></pre>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<DialogFooter class="shrink-0 gap-3 border-t bg-muted/20 px-5 pb-6 pt-4">
|
||||
<Button v-if="showConfirm" :variant="confirmVariant" class="min-w-24 gap-1.5 px-5" :disabled="loading" @click="onConfirm">
|
||||
<Loader2 v-if="loading" class="h-3.5 w-3.5 animate-spin" />
|
||||
{{ confirmLabel || t("nacos.publish") }}
|
||||
</Button>
|
||||
<Button variant="outline" class="min-w-24 px-5" :disabled="loading" @click="open = false">{{ t("dangerDialog.cancel") }}</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</template>
|
||||
|
||||
<style>
|
||||
.nacos-config-diff-dialog {
|
||||
width: min(96vw, 1440px) !important;
|
||||
max-width: min(96vw, 1440px) !important;
|
||||
}
|
||||
|
||||
.nacos-inline-change {
|
||||
box-decoration-break: clone;
|
||||
padding: 0 1px;
|
||||
}
|
||||
|
||||
@media (max-width: 1023px) {
|
||||
.nacos-config-diff-dialog {
|
||||
width: min(96vw, 760px) !important;
|
||||
max-width: min(96vw, 760px) !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,224 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, ref, watch } from "vue";
|
||||
import { Clock3, Eye, FileText, GitCompare, Loader2, RefreshCw, RotateCcw, X } from "@lucide/vue";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle } from "@/components/ui/dialog";
|
||||
import type { NacosConfigHistoryItem, NacosConfigItem } from "@/types/nacos";
|
||||
|
||||
const open = defineModel<boolean>("open", { default: false });
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
config: NacosConfigItem | null;
|
||||
items: NacosConfigHistoryItem[];
|
||||
loading?: boolean;
|
||||
error?: string;
|
||||
pageNo: number;
|
||||
pageSize: number;
|
||||
totalCount: number;
|
||||
readOnly?: boolean;
|
||||
viewingItem?: NacosConfigHistoryItem | null;
|
||||
viewingContent?: string;
|
||||
viewingLoading?: boolean;
|
||||
}>(),
|
||||
{
|
||||
items: () => [],
|
||||
loading: false,
|
||||
error: "",
|
||||
readOnly: false,
|
||||
viewingItem: null,
|
||||
viewingContent: "",
|
||||
viewingLoading: false,
|
||||
},
|
||||
);
|
||||
|
||||
const emit = defineEmits<{
|
||||
load: [pageNo: number];
|
||||
view: [item: NacosConfigHistoryItem];
|
||||
"close-detail": [];
|
||||
compare: [item: NacosConfigHistoryItem];
|
||||
rollback: [item: NacosConfigHistoryItem];
|
||||
}>();
|
||||
|
||||
const { t } = useI18n();
|
||||
const detailOpen = ref(false);
|
||||
const totalPages = computed(() => Math.max(1, Math.ceil(props.totalCount / Math.max(1, props.pageSize))));
|
||||
const historyTitle = computed(() => {
|
||||
if (!props.config) return t("nacos.configHistory");
|
||||
return `${props.config.dataId} / ${props.config.group || "DEFAULT_GROUP"}`;
|
||||
});
|
||||
const namespaceLabel = computed(() => props.config?.namespace || "public");
|
||||
const dataIdLabel = computed(() => props.config?.dataId || "-");
|
||||
const groupLabel = computed(() => props.config?.group || "DEFAULT_GROUP");
|
||||
|
||||
watch(
|
||||
() => props.viewingItem,
|
||||
(item) => {
|
||||
if (item) detailOpen.value = true;
|
||||
},
|
||||
);
|
||||
|
||||
watch(detailOpen, (value) => {
|
||||
if (!value) emit("close-detail");
|
||||
});
|
||||
|
||||
function display(value?: string | null) {
|
||||
const trimmed = value?.trim();
|
||||
return trimmed || "-";
|
||||
}
|
||||
|
||||
function operationLabel(value?: string) {
|
||||
const normalized = value?.trim().toLowerCase();
|
||||
if (!normalized) return "-";
|
||||
if (["i", "insert", "create", "add"].includes(normalized)) return t("nacos.historyCreate");
|
||||
if (["u", "update", "modify", "publish"].includes(normalized)) return t("nacos.historyUpdate");
|
||||
if (["d", "delete", "remove"].includes(normalized)) return t("nacos.historyDelete");
|
||||
if (["rollback", "recover"].includes(normalized)) return t("nacos.historyRollback");
|
||||
return value || "-";
|
||||
}
|
||||
|
||||
function loadPage(pageNo: number) {
|
||||
if (props.loading || pageNo < 1 || pageNo > totalPages.value) return;
|
||||
emit("load", pageNo);
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Dialog v-model:open="open">
|
||||
<DialogContent :show-close-button="false" class="nacos-config-history-dialog flex h-[min(82vh,760px)] flex-col gap-0 overflow-hidden rounded-lg p-0 shadow-2xl">
|
||||
<DialogHeader class="shrink-0 border-b bg-muted/20 px-5 py-4">
|
||||
<div class="flex items-center justify-between gap-4">
|
||||
<div class="flex min-w-0 items-start gap-3">
|
||||
<div class="mt-0.5 flex h-9 w-9 shrink-0 items-center justify-center rounded-md border bg-background text-primary">
|
||||
<Clock3 class="h-4 w-4" />
|
||||
</div>
|
||||
<div class="min-w-0">
|
||||
<DialogTitle class="truncate text-lg font-semibold">{{ t("nacos.configHistory") }}</DialogTitle>
|
||||
<div class="mt-2 flex min-w-0 flex-wrap items-center gap-1.5 text-xs">
|
||||
<Badge variant="outline" class="max-w-64 truncate font-mono">namespace={{ namespaceLabel }}</Badge>
|
||||
<Badge variant="secondary" class="max-w-72 truncate font-mono">dataId={{ dataIdLabel }}</Badge>
|
||||
<Badge variant="outline" class="max-w-48 truncate font-mono">group={{ groupLabel }}</Badge>
|
||||
</div>
|
||||
<div class="sr-only">{{ historyTitle }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<Button variant="ghost" size="icon" class="h-8 w-8 shrink-0" @click="open = false">
|
||||
<X class="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
</DialogHeader>
|
||||
|
||||
<div v-if="error" class="shrink-0 border-b px-5 py-2 text-xs text-destructive">{{ error }}</div>
|
||||
<div class="min-h-0 flex-1 overflow-auto">
|
||||
<table class="w-full text-left text-sm">
|
||||
<thead class="sticky top-0 z-10 bg-muted/85 text-xs text-muted-foreground">
|
||||
<tr>
|
||||
<th class="px-4 py-2 font-medium">Data ID</th>
|
||||
<th class="px-4 py-2 font-medium">Group</th>
|
||||
<th class="px-4 py-2 font-medium">{{ t("nacos.updatedAt") }}</th>
|
||||
<th class="px-4 py-2 font-medium">{{ t("nacos.application") }}</th>
|
||||
<th class="px-4 py-2 font-medium">{{ t("nacos.operationType") }}</th>
|
||||
<th class="px-4 py-2 font-medium">{{ t("nacos.operator") }}</th>
|
||||
<th class="px-4 py-2 text-right font-medium">{{ t("nacos.actions") }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="item in items" :key="`${item.historyId}:${item.nid ?? ''}`" class="border-b">
|
||||
<td class="max-w-72 truncate px-4 py-2 font-medium" :title="item.dataId">{{ item.dataId }}</td>
|
||||
<td class="max-w-44 truncate px-4 py-2 text-xs text-muted-foreground" :title="item.group">{{ item.group || "DEFAULT_GROUP" }}</td>
|
||||
<td class="whitespace-nowrap px-4 py-2 text-xs text-muted-foreground">{{ display(item.lastModifiedTime) }}</td>
|
||||
<td class="max-w-40 truncate px-4 py-2 text-xs text-muted-foreground" :title="item.appName || ''">{{ display(item.appName) }}</td>
|
||||
<td class="px-4 py-2">
|
||||
<Badge variant="outline">{{ operationLabel(item.operation) }}</Badge>
|
||||
</td>
|
||||
<td class="max-w-40 truncate px-4 py-2 text-xs text-muted-foreground" :title="item.operator || ''">{{ display(item.operator) }}</td>
|
||||
<td class="px-4 py-2 text-right">
|
||||
<div class="inline-flex items-center gap-1.5">
|
||||
<Button size="sm" variant="ghost" class="h-7 gap-1 px-2" :disabled="loading" @click="emit('view', item)">
|
||||
<Eye class="h-3.5 w-3.5" />
|
||||
{{ t("nacos.view") }}
|
||||
</Button>
|
||||
<Button size="sm" variant="ghost" class="h-7 gap-1 px-2" :disabled="loading" @click="emit('compare', item)">
|
||||
<GitCompare class="h-3.5 w-3.5" />
|
||||
{{ t("nacos.compare") }}
|
||||
</Button>
|
||||
<Button size="sm" variant="ghost" class="h-7 gap-1 px-2 text-destructive hover:text-destructive" :disabled="readOnly || loading" @click="emit('rollback', item)">
|
||||
<RotateCcw class="h-3.5 w-3.5" />
|
||||
{{ t("nacos.rollback") }}
|
||||
</Button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<div v-if="loading" class="flex h-44 items-center justify-center text-sm text-muted-foreground">
|
||||
<Loader2 class="mr-2 h-4 w-4 animate-spin" />
|
||||
{{ t("nacos.loadingHistory") }}
|
||||
</div>
|
||||
<div v-else-if="items.length === 0" class="flex h-52 flex-col items-center justify-center gap-2 text-sm text-muted-foreground">
|
||||
<div class="flex h-10 w-10 items-center justify-center rounded-md border bg-muted/30">
|
||||
<FileText class="h-4 w-4" />
|
||||
</div>
|
||||
<div class="font-medium text-foreground">{{ t("nacos.noHistory") }}</div>
|
||||
<div class="text-xs">{{ t("nacos.noHistoryHint") }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<DialogFooter class="shrink-0 items-center justify-between gap-3 border-t bg-muted/20 px-5 pb-5 pt-4">
|
||||
<Button variant="outline" size="sm" class="h-8 gap-1.5" :disabled="loading" @click="loadPage(pageNo)">
|
||||
<RefreshCw class="h-3.5 w-3.5" />
|
||||
{{ t("nacos.refresh") }}
|
||||
</Button>
|
||||
<div class="flex items-center gap-2 rounded-md border bg-background px-2 py-1 text-xs text-muted-foreground">
|
||||
<span>{{ t("nacos.total", { count: totalCount }) }}</span>
|
||||
<Button size="sm" variant="outline" class="h-7" :disabled="pageNo <= 1 || loading" @click="loadPage(pageNo - 1)">{{ t("nacos.prev") }}</Button>
|
||||
<span>{{ pageNo }} / {{ totalPages }}</span>
|
||||
<Button size="sm" variant="outline" class="h-7" :disabled="pageNo >= totalPages || loading" @click="loadPage(pageNo + 1)">{{ t("nacos.next") }}</Button>
|
||||
</div>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
||||
<Dialog v-model:open="detailOpen">
|
||||
<DialogContent class="nacos-config-history-detail-dialog flex h-[min(88vh,900px)] flex-col gap-0 overflow-hidden p-0">
|
||||
<DialogHeader class="shrink-0 border-b px-5 py-4">
|
||||
<DialogTitle class="truncate text-base font-semibold">{{ t("nacos.historyDetail") }}</DialogTitle>
|
||||
<div v-if="viewingItem" class="mt-2 grid gap-1.5 text-xs text-muted-foreground sm:grid-cols-2">
|
||||
<div class="min-w-0 truncate font-mono" :title="viewingItem.namespace || 'public'">namespace={{ viewingItem.namespace || "public" }}</div>
|
||||
<div class="min-w-0 truncate font-mono" :title="viewingItem.dataId">dataId={{ viewingItem.dataId }}</div>
|
||||
<div class="min-w-0 truncate font-mono" :title="viewingItem.group || 'DEFAULT_GROUP'">group={{ viewingItem.group || "DEFAULT_GROUP" }}</div>
|
||||
<div class="min-w-0 truncate" :title="display(viewingItem.lastModifiedTime)">{{ t("nacos.updatedAt") }}={{ display(viewingItem.lastModifiedTime) }}</div>
|
||||
</div>
|
||||
</DialogHeader>
|
||||
<div class="min-h-0 flex-1 overflow-auto bg-muted/20 p-4">
|
||||
<div v-if="viewingLoading" class="flex h-full items-center justify-center text-sm text-muted-foreground">
|
||||
<Loader2 class="mr-2 h-4 w-4 animate-spin" />
|
||||
{{ t("nacos.loadingHistory") }}
|
||||
</div>
|
||||
<pre v-else class="min-h-full rounded-md border bg-background p-3 font-mono text-xs leading-5">{{ viewingContent || "" }}</pre>
|
||||
</div>
|
||||
<DialogFooter class="m-0 shrink-0 rounded-none border-t bg-background px-5 py-5 sm:py-4">
|
||||
<Button variant="outline" @click="detailOpen = false">{{ t("dangerDialog.cancel") }}</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</template>
|
||||
|
||||
<style>
|
||||
.nacos-config-history-dialog {
|
||||
width: min(96vw, 1280px) !important;
|
||||
max-width: min(96vw, 1280px) !important;
|
||||
}
|
||||
|
||||
.nacos-config-history-detail-dialog {
|
||||
width: min(92vw, 1180px) !important;
|
||||
max-width: min(92vw, 1180px) !important;
|
||||
}
|
||||
|
||||
.nacos-config-history-detail-dialog pre {
|
||||
min-width: max-content;
|
||||
white-space: pre;
|
||||
}
|
||||
</style>
|
||||
@@ -81,7 +81,7 @@ const isFiltering = computed(() => !!searchQuery.value.trim() || hasSearchScopeF
|
||||
|
||||
const SEARCH_SCOPE_TO_NODE_TYPES: Record<SearchScope, TreeNodeType[]> = {
|
||||
connection: ["connection"],
|
||||
database: ["database", "redis-db", "mq-tenant", "mongo-db"],
|
||||
database: ["database", "redis-db", "mq-tenant", "nacos-namespace", "mongo-db"],
|
||||
schema: ["schema"],
|
||||
table: ["table", "mongo-collection", "vector-collection", "elasticsearch-index"],
|
||||
view: ["view"],
|
||||
@@ -340,6 +340,8 @@ async function ensureTreeLoadedForTarget(target: ActiveTabSidebarTarget, opts?:
|
||||
await store.loadVectorCollections(connId);
|
||||
} else if (config.db_type === "mq") {
|
||||
await store.loadMqTenants(connId, loadOptions);
|
||||
} else if (config.db_type === "nacos") {
|
||||
await store.loadNacosNamespaces(connId, loadOptions);
|
||||
} else {
|
||||
await store.loadDatabases(connId, loadOptions);
|
||||
}
|
||||
@@ -348,7 +350,7 @@ async function ensureTreeLoadedForTarget(target: ActiveTabSidebarTarget, opts?:
|
||||
}
|
||||
}
|
||||
|
||||
if (config.db_type === "mq") return;
|
||||
if (config.db_type === "mq" || config.db_type === "nacos") return;
|
||||
if (!("database" in target) || !target.database) return;
|
||||
|
||||
// Find the database node
|
||||
|
||||
@@ -246,6 +246,8 @@ function getIconInfo(node: TreeNode): { icon: any; colorClass: string } | null {
|
||||
return { icon: Database, colorClass: "text-red-400" };
|
||||
case "mq-tenant":
|
||||
return { icon: FolderOpen, colorClass: "text-sky-400" };
|
||||
case "nacos-namespace":
|
||||
return { icon: FolderOpen, colorClass: "text-sky-500" };
|
||||
case "etcd-root":
|
||||
return { icon: Database, colorClass: "text-sky-500" };
|
||||
case "mongo-db":
|
||||
@@ -468,6 +470,8 @@ async function toggle() {
|
||||
await connectionStore.loadVectorCollections(node.connectionId);
|
||||
} else if (config?.db_type === "mq") {
|
||||
await connectionStore.loadMqTenants(node.connectionId);
|
||||
} else if (config?.db_type === "nacos") {
|
||||
await connectionStore.loadNacosNamespaces(node.connectionId);
|
||||
} else {
|
||||
await connectionStore.loadDatabases(node.connectionId);
|
||||
}
|
||||
@@ -476,6 +480,8 @@ async function toggle() {
|
||||
queryStore.createTab(node.connectionId, node.database, tabTitle, "redis");
|
||||
} else if (node.type === "mq-tenant" && node.connectionId) {
|
||||
queryStore.openMqAdmin(node.connectionId, { tenant: node.mqTenant || node.label });
|
||||
} else if (node.type === "nacos-namespace" && node.connectionId) {
|
||||
queryStore.openNacosAdmin(node.connectionId, { namespace: node.nacosNamespace || "", namespaceName: node.nacosNamespaceName || node.label });
|
||||
} else if (node.type === "etcd-root" && node.connectionId) {
|
||||
const tabTitle = `${connectionStore.getConfig(node.connectionId)?.name || "etcd"}:keys`;
|
||||
queryStore.createTab(node.connectionId, "", tabTitle, "etcd");
|
||||
@@ -1371,6 +1377,15 @@ const showCreateDatabaseDialog = ref(false);
|
||||
const createDatabaseName = ref("");
|
||||
const createDatabaseCharset = ref("utf8mb4");
|
||||
const createDatabaseCollation = ref("utf8mb4_unicode_ci");
|
||||
const showCreateNacosNamespaceDialog = ref(false);
|
||||
const createNacosNamespaceId = ref("");
|
||||
const createNacosNamespaceName = ref("");
|
||||
const createNacosNamespaceDesc = ref("");
|
||||
const createNacosNamespaceLoading = ref(false);
|
||||
const showEditNacosNamespaceDialog = ref(false);
|
||||
const editNacosNamespaceName = ref("");
|
||||
const editNacosNamespaceDesc = ref("");
|
||||
const editNacosNamespaceLoading = ref(false);
|
||||
const fallbackCreateDatabaseCharset = fallbackCreateDatabaseCharsetMetadata();
|
||||
const createDatabaseCharsetOptions = ref<string[]>(fallbackCreateDatabaseCharset.charsets);
|
||||
const createDatabaseCollationsByCharset = ref<Record<string, string[]>>(fallbackCreateDatabaseCharset.collationsByCharset);
|
||||
@@ -1851,6 +1866,17 @@ const canCreateDatabase = computed(() => {
|
||||
return props.node.type === "connection" && (supportsDatabaseCreation(config?.db_type) || config?.db_type === "duckdb" || (config?.db_type === "mongodb" && config.driver_profile !== "mongodb-legacy"));
|
||||
});
|
||||
|
||||
const canCreateNacosNamespace = computed(() => {
|
||||
const config = props.node.connectionId ? connectionStore.getConfig(props.node.connectionId) : undefined;
|
||||
return props.node.type === "connection" && config?.db_type === "nacos" && !config.read_only;
|
||||
});
|
||||
|
||||
const canEditNacosNamespace = computed(() => {
|
||||
if (props.node.type !== "nacos-namespace" || !props.node.connectionId || !props.node.nacosNamespace) return false;
|
||||
const config = connectionStore.getConfig(props.node.connectionId);
|
||||
return config?.db_type === "nacos" && !config.read_only;
|
||||
});
|
||||
|
||||
const isDuckDbConnection = computed(() => {
|
||||
const config = props.node.connectionId ? connectionStore.getConfig(props.node.connectionId) : undefined;
|
||||
return props.node.type === "connection" && config?.db_type === "duckdb";
|
||||
@@ -2037,6 +2063,63 @@ async function loadCreateDatabaseCharsetMetadata() {
|
||||
}
|
||||
}
|
||||
|
||||
function openCreateNacosNamespaceDialog() {
|
||||
createNacosNamespaceId.value = "";
|
||||
createNacosNamespaceName.value = "";
|
||||
createNacosNamespaceDesc.value = "";
|
||||
showCreateNacosNamespaceDialog.value = true;
|
||||
}
|
||||
|
||||
async function confirmCreateNacosNamespace() {
|
||||
const node = props.node;
|
||||
const namespaceName = createNacosNamespaceName.value.trim();
|
||||
if (!node.connectionId || !namespaceName || createNacosNamespaceLoading.value) return;
|
||||
createNacosNamespaceLoading.value = true;
|
||||
try {
|
||||
await api.nacosCreateNamespace(node.connectionId, {
|
||||
namespaceId: createNacosNamespaceId.value.trim() || undefined,
|
||||
namespaceName,
|
||||
namespaceDesc: createNacosNamespaceDesc.value.trim() || namespaceName,
|
||||
});
|
||||
showCreateNacosNamespaceDialog.value = false;
|
||||
await connectionStore.loadNacosNamespaces(node.connectionId, { force: true });
|
||||
node.isExpanded = true;
|
||||
toast(t("nacos.namespaceCreated", { name: namespaceName }), 3000);
|
||||
} catch (e: any) {
|
||||
toast(t("contextMenu.tableOperationFailed", { message: translateBackendError(t, e?.message || String(e)) }), 5000);
|
||||
} finally {
|
||||
createNacosNamespaceLoading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
function openEditNacosNamespaceDialog() {
|
||||
editNacosNamespaceName.value = props.node.nacosNamespaceName || props.node.label;
|
||||
editNacosNamespaceDesc.value = props.node.comment || "";
|
||||
showEditNacosNamespaceDialog.value = true;
|
||||
}
|
||||
|
||||
async function confirmEditNacosNamespace() {
|
||||
const node = props.node;
|
||||
const namespaceId = node.nacosNamespace?.trim() || "";
|
||||
const namespaceName = editNacosNamespaceName.value.trim();
|
||||
if (!node.connectionId || !namespaceId || !namespaceName || editNacosNamespaceLoading.value) return;
|
||||
editNacosNamespaceLoading.value = true;
|
||||
try {
|
||||
await api.nacosUpdateNamespace(node.connectionId, {
|
||||
namespaceId,
|
||||
namespaceName,
|
||||
namespaceDesc: editNacosNamespaceDesc.value.trim() || namespaceName,
|
||||
});
|
||||
showEditNacosNamespaceDialog.value = false;
|
||||
await connectionStore.loadNacosNamespaces(node.connectionId, { force: true });
|
||||
toast(t("nacos.namespaceUpdated", { name: namespaceName }), 3000);
|
||||
} catch (e: any) {
|
||||
toast(t("contextMenu.tableOperationFailed", { message: translateBackendError(t, e?.message || String(e)) }), 5000);
|
||||
} finally {
|
||||
editNacosNamespaceLoading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
function ensureDuckDbFileExtension(path: string): string {
|
||||
return /\.(duckdb|db)$/i.test(path) ? path : `${path}.duckdb`;
|
||||
}
|
||||
@@ -2861,7 +2944,7 @@ const nodeIconClass = computed(() => {
|
||||
const canConfigureVisibleDatabases = computed(() => {
|
||||
if (props.node.type !== "connection" || !props.node.connectionId) return false;
|
||||
const dbType = connectionStore.getConfig(props.node.connectionId)?.db_type;
|
||||
return dbType !== "elasticsearch" && dbType !== "qdrant" && dbType !== "milvus" && dbType !== "etcd" && dbType !== "mq";
|
||||
return dbType !== "elasticsearch" && dbType !== "qdrant" && dbType !== "milvus" && dbType !== "etcd" && dbType !== "mq" && dbType !== "nacos";
|
||||
});
|
||||
const canCopyFinalProxyPort = computed(() => {
|
||||
if (props.node.type !== "connection" || !props.node.connectionId) return false;
|
||||
@@ -3273,6 +3356,13 @@ function treeItemMenuItems(): ContextMenuItem[] {
|
||||
icon: Plus,
|
||||
});
|
||||
}
|
||||
if (canCreateNacosNamespace.value) {
|
||||
items.push({
|
||||
label: t("nacos.createNamespace"),
|
||||
action: openCreateNacosNamespaceDialog,
|
||||
icon: FolderPlus,
|
||||
});
|
||||
}
|
||||
items.push({ label: "", separator: true });
|
||||
if (availableGroups.value.length > 0 || currentGroupId.value) {
|
||||
const groupChildren: ContextMenuItem[] = availableGroups.value.map((group: { id: string; name: string }) => ({
|
||||
@@ -3455,6 +3545,22 @@ function treeItemMenuItems(): ContextMenuItem[] {
|
||||
return items;
|
||||
}
|
||||
|
||||
if (node.type === "nacos-namespace") {
|
||||
items.push({ label: t("contextMenu.openConnection"), action: toggle, icon: FolderOpen });
|
||||
if (canEditNacosNamespace.value) {
|
||||
items.push({ label: t("nacos.editNamespace"), action: openEditNacosNamespaceDialog, icon: Pencil });
|
||||
}
|
||||
items.push({
|
||||
label: t("contextMenu.refreshChildren"),
|
||||
action: refresh,
|
||||
icon: RefreshCw,
|
||||
shortcut: shortcutRefresh,
|
||||
});
|
||||
items.push({ label: "", separator: true });
|
||||
items.push({ label: t("contextMenu.copyName"), action: copyName, icon: Copy, shortcut: shortcutCopyName.value });
|
||||
return items;
|
||||
}
|
||||
|
||||
if (node.type === "mongo-collection") {
|
||||
items.push({ label: t("contextMenu.copyName"), action: copyName, icon: Copy, shortcut: shortcutCopyName.value });
|
||||
items.push({ label: "", separator: true });
|
||||
@@ -4004,6 +4110,58 @@ function treeItemMenuItems(): ContextMenuItem[] {
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
||||
<Dialog v-model:open="showCreateNacosNamespaceDialog">
|
||||
<DialogContent class="sm:max-w-[420px]">
|
||||
<DialogHeader>
|
||||
<DialogTitle>{{ t("nacos.createNamespace") }}</DialogTitle>
|
||||
</DialogHeader>
|
||||
<div class="grid gap-3">
|
||||
<div class="grid gap-1.5">
|
||||
<label class="text-xs font-medium text-muted-foreground">{{ t("nacos.namespaceId") }}</label>
|
||||
<Input v-model="createNacosNamespaceId" :placeholder="t('nacos.namespaceIdPlaceholder')" @keydown.enter.prevent="confirmCreateNacosNamespace" />
|
||||
</div>
|
||||
<div class="grid gap-1.5">
|
||||
<label class="text-xs font-medium text-muted-foreground">{{ t("nacos.namespaceName") }}</label>
|
||||
<Input v-model="createNacosNamespaceName" :placeholder="t('nacos.namespaceNamePlaceholder')" @keydown.enter.prevent="confirmCreateNacosNamespace" />
|
||||
</div>
|
||||
<div class="grid gap-1.5">
|
||||
<label class="text-xs font-medium text-muted-foreground">{{ t("nacos.namespaceDesc") }}</label>
|
||||
<Input v-model="createNacosNamespaceDesc" :placeholder="t('nacos.namespaceDescPlaceholder')" @keydown.enter.prevent="confirmCreateNacosNamespace" />
|
||||
</div>
|
||||
</div>
|
||||
<DialogFooter>
|
||||
<Button variant="outline" :disabled="createNacosNamespaceLoading" @click="showCreateNacosNamespaceDialog = false">{{ t("dangerDialog.cancel") }}</Button>
|
||||
<Button :disabled="!createNacosNamespaceName.trim() || createNacosNamespaceLoading" @click="confirmCreateNacosNamespace">
|
||||
{{ createNacosNamespaceLoading ? t("nacos.creatingNamespace") : t("dangerDialog.confirm") }}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
||||
<Dialog v-model:open="showEditNacosNamespaceDialog">
|
||||
<DialogContent class="sm:max-w-[420px]">
|
||||
<DialogHeader>
|
||||
<DialogTitle>{{ t("nacos.editNamespace") }}</DialogTitle>
|
||||
</DialogHeader>
|
||||
<div class="grid gap-3">
|
||||
<div class="grid gap-1.5">
|
||||
<label class="text-xs font-medium text-muted-foreground">{{ t("nacos.namespaceName") }}</label>
|
||||
<Input v-model="editNacosNamespaceName" :placeholder="t('nacos.namespaceNamePlaceholder')" @keydown.enter.prevent="confirmEditNacosNamespace" />
|
||||
</div>
|
||||
<div class="grid gap-1.5">
|
||||
<label class="text-xs font-medium text-muted-foreground">{{ t("nacos.namespaceDesc") }}</label>
|
||||
<Input v-model="editNacosNamespaceDesc" :placeholder="t('nacos.namespaceDescPlaceholder')" @keydown.enter.prevent="confirmEditNacosNamespace" />
|
||||
</div>
|
||||
</div>
|
||||
<DialogFooter>
|
||||
<Button variant="outline" :disabled="editNacosNamespaceLoading" @click="showEditNacosNamespaceDialog = false">{{ t("dangerDialog.cancel") }}</Button>
|
||||
<Button :disabled="!editNacosNamespaceName.trim() || editNacosNamespaceLoading" @click="confirmEditNacosNamespace">
|
||||
{{ editNacosNamespaceLoading ? t("nacos.updatingNamespace") : t("dangerDialog.confirm") }}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
||||
<DangerConfirmDialog
|
||||
v-model:open="showDropDatabaseConfirm"
|
||||
:title="t('contextMenu.confirmDropDatabaseTitle')"
|
||||
|
||||
@@ -49,7 +49,7 @@ const terminalStatus = ref<SqlFileStatus | "idle">("idle");
|
||||
const terminalError = ref("");
|
||||
const refreshedTarget = ref(false);
|
||||
|
||||
const sqlConnections = computed(() => store.connections.filter((c) => !["redis", "mongodb", "elasticsearch", "qdrant", "milvus", "etcd"].includes(c.db_type)));
|
||||
const sqlConnections = computed(() => store.connections.filter((c) => !["redis", "mongodb", "elasticsearch", "qdrant", "milvus", "etcd", "mq", "nacos"].includes(c.db_type)));
|
||||
|
||||
const selectedConnection = computed(() => sqlConnections.value.find((c) => c.id === connectionId.value));
|
||||
|
||||
|
||||
@@ -250,6 +250,20 @@ export default {
|
||||
etcdClientCertBrowse: "Choose client certificate",
|
||||
etcdClientKeyBrowse: "Choose client private key",
|
||||
etcdClientCertPairRequired: "Client certificate and private key must be provided together.",
|
||||
nacosConsoleUrl: "Console URL",
|
||||
nacosConsoleUrlHint: "Use the Nacos console/admin API address. Nacos 3 Docker usually exposes the console on 8085; older deployments may share 8848 with the service port.",
|
||||
nacosConsoleUrlRequired: "Nacos Console URL is required",
|
||||
nacosConsoleUrlAutoAdjusted: "Adjusted Nacos Console URL from {from} to {to}.",
|
||||
nacosNamespace: "Namespace",
|
||||
nacosContextPath: "Context Path",
|
||||
nacosContextPathPlaceholder: "Leave empty or /nacos",
|
||||
nacosAuth: "Auth",
|
||||
nacosAuthNone: "None",
|
||||
nacosAuthUserPassword: "User / Password",
|
||||
nacosUsernameRequired: "Nacos username is required",
|
||||
nacosTls: "TLS",
|
||||
nacosTlsSkipVerify: "Skip certificate verification",
|
||||
nacosPageSize: "Page Size",
|
||||
searchDatabasePlaceholder: "Search database types",
|
||||
iconView: "Icon view",
|
||||
listView: "List view",
|
||||
@@ -2720,4 +2734,121 @@ export default {
|
||||
exportError: "Export failed: {error}",
|
||||
exportCancelled: "Export cancelled",
|
||||
},
|
||||
nacos: {
|
||||
configs: "Configs",
|
||||
services: "Services",
|
||||
raw: "Raw",
|
||||
namespace: "Namespace",
|
||||
createNamespace: "Create Namespace",
|
||||
editNamespace: "Edit Namespace",
|
||||
creatingNamespace: "Creating...",
|
||||
updatingNamespace: "Updating...",
|
||||
namespaceCreated: 'Namespace "{name}" created',
|
||||
namespaceUpdated: 'Namespace "{name}" updated',
|
||||
namespaceId: "Namespace ID",
|
||||
namespaceIdPlaceholder: "Leave empty for Nacos to generate",
|
||||
namespaceName: "Namespace Name",
|
||||
namespaceNamePlaceholder: "Enter a namespace name",
|
||||
namespaceDesc: "Description",
|
||||
namespaceDescPlaceholder: "Defaults to namespace name",
|
||||
dataId: "Data ID",
|
||||
group: "Group",
|
||||
format: "Format",
|
||||
content: "Content",
|
||||
tags: "Tags",
|
||||
application: "Application",
|
||||
description: "Description",
|
||||
advanced: "Advanced",
|
||||
collapse: "Collapse",
|
||||
load: "Load",
|
||||
save: "Save",
|
||||
saveAs: "Save as",
|
||||
saving: "Saving...",
|
||||
saved: "Config saved",
|
||||
createdAndLoaded: "Created and loaded {dataId}",
|
||||
savedAndLoaded: "Saved and refreshed {dataId}",
|
||||
deleted: "Config deleted",
|
||||
delete: "Delete",
|
||||
copy: "Copy",
|
||||
copied: "Copied",
|
||||
export: "Export",
|
||||
exported: "Export copied",
|
||||
history: "History",
|
||||
configHistory: "Config history",
|
||||
historyDetail: "History detail",
|
||||
historyCompareTitle: "History Version Compare",
|
||||
currentPublishedContent: "Current published content:",
|
||||
historyVersionContent: "History version content:",
|
||||
updatedAt: "Updated at",
|
||||
operationType: "Operation",
|
||||
operator: "Operator",
|
||||
view: "View",
|
||||
compare: "Compare",
|
||||
rollback: "Rollback",
|
||||
loadingHistory: "Loading history...",
|
||||
noHistory: "No history versions",
|
||||
noHistoryHint: "Refresh if this config was just published. Some Nacos versions may not expose history APIs.",
|
||||
historyCreate: "Create",
|
||||
historyUpdate: "Update",
|
||||
historyDelete: "Delete",
|
||||
historyRollback: "Rollback",
|
||||
confirmRollbackTitle: "Rollback Nacos config",
|
||||
confirmRollbackMessage: "This will roll the current config back to the selected history version.",
|
||||
rollbackSuccess: "Config rolled back",
|
||||
publish: "Publish",
|
||||
dataIdRequired: "Data ID is required",
|
||||
allGroups: "All groups",
|
||||
noConfigs: "No configs",
|
||||
selectConfig: "Select or create a config",
|
||||
noServices: "No services",
|
||||
selectService: "Select a service",
|
||||
service: "Service",
|
||||
instances: "Instances",
|
||||
instanceCount: "{count} instances",
|
||||
refresh: "Refresh",
|
||||
address: "Address",
|
||||
cluster: "Cluster",
|
||||
weight: "Weight",
|
||||
state: "State",
|
||||
actions: "Actions",
|
||||
query: "Query",
|
||||
body: "Body",
|
||||
response: "Response",
|
||||
template: "Template",
|
||||
configDiffTitle: "Config Content Compare",
|
||||
inlineCompare: "Inline compare",
|
||||
currentVersionContent: "Current official version:",
|
||||
publishVersionContent: "This release content:",
|
||||
confirmSaveTitle: "Review Nacos config changes",
|
||||
confirmSaveMessage: "Save this config change? +{added} / -{removed} changed lines.",
|
||||
confirmDeleteTitle: "Delete Nacos config",
|
||||
confirmDeleteMessage: "This removes the selected config from Nacos.",
|
||||
confirmInstanceTitle: "Update Nacos instance",
|
||||
confirmInstanceMessage: "Confirm the target service instance state.",
|
||||
confirmRawTitle: "Send mutating Raw API request",
|
||||
confirmRawMessage: "This Raw API request can modify Nacos state.",
|
||||
rawTemplate: {
|
||||
serverState: "Server state",
|
||||
namespaceList: "Namespace list",
|
||||
configDetail: "Config detail",
|
||||
serviceList: "Service list",
|
||||
instanceList: "Instance list",
|
||||
},
|
||||
healthy: "Healthy",
|
||||
unhealthy: "Unhealthy",
|
||||
enabled: "Enabled",
|
||||
offline: "Offline",
|
||||
ephemeral: "Ephemeral",
|
||||
persistent: "Persistent",
|
||||
enable: "Enable",
|
||||
disable: "Disable",
|
||||
markHealthy: "Healthy",
|
||||
markUnhealthy: "Unhealthy",
|
||||
send: "Send",
|
||||
total: "{count} total",
|
||||
prev: "Prev",
|
||||
next: "Next",
|
||||
test: "Test",
|
||||
readOnly: "Read only",
|
||||
},
|
||||
};
|
||||
@@ -230,6 +230,20 @@ export default {
|
||||
redisSentinelTls: "TLS Sentinel",
|
||||
redisSentinelTlsHint: "Usar TLS al conectar con nodos Sentinel",
|
||||
redisKeySeparator: "Separador de espacio de nombres de clave",
|
||||
nacosConsoleUrl: "URL de consola",
|
||||
nacosConsoleUrlHint: "Usa la dirección de consola/admin API de Nacos. Nacos 3 Docker normalmente expone la consola en 8085; despliegues antiguos pueden compartir 8848 con el puerto de servicio.",
|
||||
nacosConsoleUrlRequired: "La URL de consola de Nacos es obligatoria",
|
||||
nacosConsoleUrlAutoAdjusted: "URL de consola de Nacos ajustada de {from} a {to}.",
|
||||
nacosNamespace: "Namespace",
|
||||
nacosContextPath: "Context Path",
|
||||
nacosContextPathPlaceholder: "Dejar vacío o /nacos",
|
||||
nacosAuth: "Autenticación",
|
||||
nacosAuthNone: "Ninguna",
|
||||
nacosAuthUserPassword: "Usuario / Contraseña",
|
||||
nacosUsernameRequired: "El usuario de Nacos es obligatorio",
|
||||
nacosTls: "TLS",
|
||||
nacosTlsSkipVerify: "Omitir verificación de certificado",
|
||||
nacosPageSize: "Tamaño de página",
|
||||
searchDatabasePlaceholder: "Buscar tipos de bases de datos",
|
||||
iconView: "Vista de íconos",
|
||||
listView: "Vista de lista",
|
||||
@@ -2359,4 +2373,109 @@ export default {
|
||||
exportError: "Error al exportar: {error}",
|
||||
exportCancelled: "Exportación cancelada",
|
||||
},
|
||||
nacos: {
|
||||
configs: "Configuraciones",
|
||||
services: "Servicios",
|
||||
raw: "Raw",
|
||||
namespace: "Namespace",
|
||||
dataId: "Data ID",
|
||||
group: "Grupo",
|
||||
format: "Formato",
|
||||
content: "Contenido",
|
||||
tags: "Etiquetas",
|
||||
application: "Aplicación",
|
||||
description: "Descripción",
|
||||
advanced: "Avanzado",
|
||||
collapse: "Contraer",
|
||||
load: "Cargar",
|
||||
save: "Guardar",
|
||||
saveAs: "Guardar como",
|
||||
saving: "Guardando...",
|
||||
saved: "Configuración guardada",
|
||||
createdAndLoaded: "{dataId} creado y cargado",
|
||||
savedAndLoaded: "{dataId} guardado y actualizado",
|
||||
deleted: "Configuración eliminada",
|
||||
delete: "Eliminar",
|
||||
copy: "Copiar",
|
||||
copied: "Copiado",
|
||||
export: "Exportar",
|
||||
exported: "Exportación copiada",
|
||||
history: "History",
|
||||
configHistory: "Config history",
|
||||
historyDetail: "History detail",
|
||||
historyCompareTitle: "History Version Compare",
|
||||
currentPublishedContent: "Current published content:",
|
||||
historyVersionContent: "History version content:",
|
||||
updatedAt: "Updated at",
|
||||
operationType: "Operation",
|
||||
operator: "Operator",
|
||||
view: "View",
|
||||
compare: "Compare",
|
||||
rollback: "Rollback",
|
||||
loadingHistory: "Loading history...",
|
||||
noHistory: "No history versions",
|
||||
noHistoryHint: "Refresh if this config was just published. Some Nacos versions may not expose history APIs.",
|
||||
historyCreate: "Create",
|
||||
historyUpdate: "Update",
|
||||
historyDelete: "Delete",
|
||||
historyRollback: "Rollback",
|
||||
confirmRollbackTitle: "Rollback Nacos config",
|
||||
confirmRollbackMessage: "This will roll the current config back to the selected history version.",
|
||||
rollbackSuccess: "Config rolled back",
|
||||
publish: "Publicar",
|
||||
dataIdRequired: "Data ID es obligatorio",
|
||||
allGroups: "Todos los grupos",
|
||||
noConfigs: "Sin configuraciones",
|
||||
selectConfig: "Selecciona o crea una configuración",
|
||||
noServices: "Sin servicios",
|
||||
selectService: "Selecciona un servicio",
|
||||
service: "Servicio",
|
||||
instances: "Instancias",
|
||||
instanceCount: "{count} instancias",
|
||||
refresh: "Actualizar",
|
||||
address: "Dirección",
|
||||
cluster: "Clúster",
|
||||
weight: "Peso",
|
||||
state: "Estado",
|
||||
actions: "Acciones",
|
||||
query: "Consulta",
|
||||
body: "Cuerpo",
|
||||
response: "Respuesta",
|
||||
template: "Plantilla",
|
||||
configDiffTitle: "Comparar contenido de configuración",
|
||||
inlineCompare: "Comparación en línea",
|
||||
currentVersionContent: "Versión oficial actual:",
|
||||
publishVersionContent: "Contenido de esta publicación:",
|
||||
confirmSaveTitle: "Revisar cambios de configuración Nacos",
|
||||
confirmSaveMessage: "¿Guardar este cambio? +{added} / -{removed} líneas.",
|
||||
confirmDeleteTitle: "Eliminar configuración Nacos",
|
||||
confirmDeleteMessage: "Esto elimina la configuración seleccionada de Nacos.",
|
||||
confirmInstanceTitle: "Actualizar instancia Nacos",
|
||||
confirmInstanceMessage: "Confirma el estado objetivo de la instancia.",
|
||||
confirmRawTitle: "Enviar solicitud Raw API modificadora",
|
||||
confirmRawMessage: "Esta solicitud Raw API puede modificar el estado de Nacos.",
|
||||
rawTemplate: {
|
||||
serverState: "Estado del servidor",
|
||||
namespaceList: "Lista de namespaces",
|
||||
configDetail: "Detalle de configuración",
|
||||
serviceList: "Lista de servicios",
|
||||
instanceList: "Lista de instancias",
|
||||
},
|
||||
healthy: "Saludable",
|
||||
unhealthy: "No saludable",
|
||||
enabled: "Habilitado",
|
||||
offline: "Offline",
|
||||
ephemeral: "Efímera",
|
||||
persistent: "Persistente",
|
||||
enable: "Habilitar",
|
||||
disable: "Deshabilitar",
|
||||
markHealthy: "Marcar saludable",
|
||||
markUnhealthy: "Marcar no saludable",
|
||||
send: "Enviar",
|
||||
total: "{count} en total",
|
||||
prev: "Anterior",
|
||||
next: "Siguiente",
|
||||
test: "Probar",
|
||||
readOnly: "Solo lectura",
|
||||
},
|
||||
};
|
||||
@@ -235,6 +235,20 @@ export default {
|
||||
redisSentinelTls: "TLS Sentinel",
|
||||
redisSentinelTlsHint: "Usa TLS quando ti connetti ai nodi Sentinel",
|
||||
redisKeySeparator: "Separatore namespace chiavi",
|
||||
nacosConsoleUrl: "URL console",
|
||||
nacosConsoleUrlHint: "Usa l'indirizzo console/admin API di Nacos. Nacos 3 Docker di solito espone la console su 8085; distribuzioni più vecchie possono condividere 8848 con la porta di servizio.",
|
||||
nacosConsoleUrlRequired: "L'URL console Nacos è obbligatorio",
|
||||
nacosConsoleUrlAutoAdjusted: "URL console Nacos regolato da {from} a {to}.",
|
||||
nacosNamespace: "Namespace",
|
||||
nacosContextPath: "Context Path",
|
||||
nacosContextPathPlaceholder: "Lascia vuoto o /nacos",
|
||||
nacosAuth: "Autenticazione",
|
||||
nacosAuthNone: "Nessuna",
|
||||
nacosAuthUserPassword: "Utente / Password",
|
||||
nacosUsernameRequired: "Il nome utente Nacos è obbligatorio",
|
||||
nacosTls: "TLS",
|
||||
nacosTlsSkipVerify: "Salta verifica certificato",
|
||||
nacosPageSize: "Dimensione pagina",
|
||||
searchDatabasePlaceholder: "Cerca tipi di database",
|
||||
iconView: "Vista icone",
|
||||
listView: "Vista elenco",
|
||||
@@ -2428,4 +2442,109 @@ export default {
|
||||
exportError: "Esportazione non riuscita: {error}",
|
||||
exportCancelled: "Esportazione annullata",
|
||||
},
|
||||
nacos: {
|
||||
configs: "Configurazioni",
|
||||
services: "Servizi",
|
||||
raw: "Raw",
|
||||
namespace: "Namespace",
|
||||
dataId: "Data ID",
|
||||
group: "Gruppo",
|
||||
format: "Formato",
|
||||
content: "Contenuto",
|
||||
tags: "Tag",
|
||||
application: "Applicazione",
|
||||
description: "Descrizione",
|
||||
advanced: "Avanzate",
|
||||
collapse: "Comprimi",
|
||||
load: "Carica",
|
||||
save: "Salva",
|
||||
saveAs: "Salva come",
|
||||
saving: "Salvataggio...",
|
||||
saved: "Configurazione salvata",
|
||||
createdAndLoaded: "{dataId} creato e caricato",
|
||||
savedAndLoaded: "{dataId} salvato e aggiornato",
|
||||
deleted: "Configurazione eliminata",
|
||||
delete: "Elimina",
|
||||
copy: "Copia",
|
||||
copied: "Copiato",
|
||||
export: "Esporta",
|
||||
exported: "Esportazione copiata",
|
||||
history: "History",
|
||||
configHistory: "Config history",
|
||||
historyDetail: "History detail",
|
||||
historyCompareTitle: "History Version Compare",
|
||||
currentPublishedContent: "Current published content:",
|
||||
historyVersionContent: "History version content:",
|
||||
updatedAt: "Updated at",
|
||||
operationType: "Operation",
|
||||
operator: "Operator",
|
||||
view: "View",
|
||||
compare: "Compare",
|
||||
rollback: "Rollback",
|
||||
loadingHistory: "Loading history...",
|
||||
noHistory: "No history versions",
|
||||
noHistoryHint: "Refresh if this config was just published. Some Nacos versions may not expose history APIs.",
|
||||
historyCreate: "Create",
|
||||
historyUpdate: "Update",
|
||||
historyDelete: "Delete",
|
||||
historyRollback: "Rollback",
|
||||
confirmRollbackTitle: "Rollback Nacos config",
|
||||
confirmRollbackMessage: "This will roll the current config back to the selected history version.",
|
||||
rollbackSuccess: "Config rolled back",
|
||||
publish: "Pubblica",
|
||||
dataIdRequired: "Data ID è obbligatorio",
|
||||
allGroups: "Tutti i gruppi",
|
||||
noConfigs: "Nessuna configurazione",
|
||||
selectConfig: "Seleziona o crea una configurazione",
|
||||
noServices: "Nessun servizio",
|
||||
selectService: "Seleziona un servizio",
|
||||
service: "Servizio",
|
||||
instances: "Istanze",
|
||||
instanceCount: "{count} istanze",
|
||||
refresh: "Aggiorna",
|
||||
address: "Indirizzo",
|
||||
cluster: "Cluster",
|
||||
weight: "Peso",
|
||||
state: "Stato",
|
||||
actions: "Azioni",
|
||||
query: "Query",
|
||||
body: "Corpo",
|
||||
response: "Risposta",
|
||||
template: "Template",
|
||||
configDiffTitle: "Confronto contenuto configurazione",
|
||||
inlineCompare: "Confronto inline",
|
||||
currentVersionContent: "Versione ufficiale attuale:",
|
||||
publishVersionContent: "Contenuto di questa pubblicazione:",
|
||||
confirmSaveTitle: "Rivedi modifiche configurazione Nacos",
|
||||
confirmSaveMessage: "Salvare questa modifica? +{added} / -{removed} righe.",
|
||||
confirmDeleteTitle: "Elimina configurazione Nacos",
|
||||
confirmDeleteMessage: "Questo rimuove la configurazione selezionata da Nacos.",
|
||||
confirmInstanceTitle: "Aggiorna istanza Nacos",
|
||||
confirmInstanceMessage: "Conferma lo stato di destinazione dell'istanza.",
|
||||
confirmRawTitle: "Invia richiesta Raw API modificante",
|
||||
confirmRawMessage: "Questa richiesta Raw API può modificare lo stato di Nacos.",
|
||||
rawTemplate: {
|
||||
serverState: "Stato server",
|
||||
namespaceList: "Lista namespace",
|
||||
configDetail: "Dettaglio configurazione",
|
||||
serviceList: "Lista servizi",
|
||||
instanceList: "Lista istanze",
|
||||
},
|
||||
healthy: "Sana",
|
||||
unhealthy: "Non sana",
|
||||
enabled: "Abilitata",
|
||||
offline: "Offline",
|
||||
ephemeral: "Effimera",
|
||||
persistent: "Persistente",
|
||||
enable: "Abilita",
|
||||
disable: "Disabilita",
|
||||
markHealthy: "Segna sana",
|
||||
markUnhealthy: "Segna non sana",
|
||||
send: "Invia",
|
||||
total: "{count} totali",
|
||||
prev: "Prec.",
|
||||
next: "Succ.",
|
||||
test: "Test",
|
||||
readOnly: "Sola lettura",
|
||||
},
|
||||
};
|
||||
@@ -249,6 +249,20 @@ export default {
|
||||
etcdClientCertBrowse: "クライアント証明書を選択",
|
||||
etcdClientKeyBrowse: "クライアント秘密鍵を選択",
|
||||
etcdClientCertPairRequired: "クライアント証明書と秘密鍵の両方を指定する必要があります。",
|
||||
nacosConsoleUrl: "コンソールURL",
|
||||
nacosConsoleUrlHint: "Nacosのコンソール/admin APIアドレスを指定します。Nacos 3 Dockerでは通常8085、古い構成ではサービス用ポート8848と共用される場合があります。",
|
||||
nacosConsoleUrlRequired: "NacosコンソールURLは必須です",
|
||||
nacosConsoleUrlAutoAdjusted: "NacosコンソールURLを{from}から{to}に自動調整しました。",
|
||||
nacosNamespace: "名前空間",
|
||||
nacosContextPath: "Context Path",
|
||||
nacosContextPathPlaceholder: "空欄または /nacos",
|
||||
nacosAuth: "認証",
|
||||
nacosAuthNone: "なし",
|
||||
nacosAuthUserPassword: "ユーザー / パスワード",
|
||||
nacosUsernameRequired: "Nacosユーザー名は必須です",
|
||||
nacosTls: "TLS",
|
||||
nacosTlsSkipVerify: "証明書検証をスキップ",
|
||||
nacosPageSize: "ページサイズ",
|
||||
searchDatabasePlaceholder: "データベースタイプを検索",
|
||||
iconView: "アイコン表示",
|
||||
listView: "リスト表示",
|
||||
@@ -2674,4 +2688,109 @@ export default {
|
||||
exportError: "エクスポートに失敗しました: {error}",
|
||||
exportCancelled: "エクスポートがキャンセルされました",
|
||||
},
|
||||
nacos: {
|
||||
configs: "設定",
|
||||
services: "サービス",
|
||||
raw: "Raw",
|
||||
namespace: "名前空間",
|
||||
dataId: "Data ID",
|
||||
group: "Group",
|
||||
format: "設定形式",
|
||||
content: "設定内容",
|
||||
tags: "タグ",
|
||||
application: "アプリケーション",
|
||||
description: "説明",
|
||||
advanced: "詳細設定",
|
||||
collapse: "閉じる",
|
||||
load: "読み込み",
|
||||
save: "保存",
|
||||
saveAs: "別名で保存",
|
||||
saving: "保存中...",
|
||||
saved: "設定を保存しました",
|
||||
createdAndLoaded: "{dataId}を作成して読み込みました",
|
||||
savedAndLoaded: "{dataId}を保存して更新しました",
|
||||
deleted: "設定を削除しました",
|
||||
delete: "削除",
|
||||
copy: "コピー",
|
||||
copied: "コピーしました",
|
||||
export: "エクスポート",
|
||||
exported: "エクスポート内容をコピーしました",
|
||||
history: "History",
|
||||
configHistory: "Config history",
|
||||
historyDetail: "History detail",
|
||||
historyCompareTitle: "History Version Compare",
|
||||
currentPublishedContent: "Current published content:",
|
||||
historyVersionContent: "History version content:",
|
||||
updatedAt: "Updated at",
|
||||
operationType: "Operation",
|
||||
operator: "Operator",
|
||||
view: "View",
|
||||
compare: "Compare",
|
||||
rollback: "Rollback",
|
||||
loadingHistory: "Loading history...",
|
||||
noHistory: "No history versions",
|
||||
noHistoryHint: "Refresh if this config was just published. Some Nacos versions may not expose history APIs.",
|
||||
historyCreate: "Create",
|
||||
historyUpdate: "Update",
|
||||
historyDelete: "Delete",
|
||||
historyRollback: "Rollback",
|
||||
confirmRollbackTitle: "Rollback Nacos config",
|
||||
confirmRollbackMessage: "This will roll the current config back to the selected history version.",
|
||||
rollbackSuccess: "Config rolled back",
|
||||
publish: "公開",
|
||||
dataIdRequired: "Data ID は必須です",
|
||||
allGroups: "すべてのグループ",
|
||||
noConfigs: "設定がありません",
|
||||
selectConfig: "設定を選択または作成",
|
||||
noServices: "サービスがありません",
|
||||
selectService: "サービスを選択",
|
||||
service: "サービス",
|
||||
instances: "インスタンス",
|
||||
instanceCount: "{count}件のインスタンス",
|
||||
refresh: "更新",
|
||||
address: "アドレス",
|
||||
cluster: "クラスター",
|
||||
weight: "重み",
|
||||
state: "状態",
|
||||
actions: "操作",
|
||||
query: "クエリ",
|
||||
body: "本文",
|
||||
response: "レスポンス",
|
||||
template: "テンプレート",
|
||||
configDiffTitle: "設定内容の比較",
|
||||
inlineCompare: "行内比較",
|
||||
currentVersionContent: "現在の正式バージョン:",
|
||||
publishVersionContent: "今回の公開内容:",
|
||||
confirmSaveTitle: "Nacos設定の変更を確認",
|
||||
confirmSaveMessage: "この設定変更を保存しますか? +{added} / -{removed} 行。",
|
||||
confirmDeleteTitle: "Nacos設定を削除",
|
||||
confirmDeleteMessage: "選択した設定をNacosから削除します。",
|
||||
confirmInstanceTitle: "Nacosインスタンスを更新",
|
||||
confirmInstanceMessage: "対象サービスインスタンスの状態を確認してください。",
|
||||
confirmRawTitle: "変更系Raw APIリクエストを送信",
|
||||
confirmRawMessage: "このRaw APIリクエストはNacos状態を変更する可能性があります。",
|
||||
rawTemplate: {
|
||||
serverState: "サーバー状態",
|
||||
namespaceList: "名前空間一覧",
|
||||
configDetail: "設定詳細",
|
||||
serviceList: "サービス一覧",
|
||||
instanceList: "インスタンス一覧",
|
||||
},
|
||||
healthy: "正常",
|
||||
unhealthy: "異常",
|
||||
enabled: "有効",
|
||||
offline: "オフライン",
|
||||
ephemeral: "一時",
|
||||
persistent: "永続",
|
||||
enable: "有効化",
|
||||
disable: "無効化",
|
||||
markHealthy: "正常にする",
|
||||
markUnhealthy: "異常にする",
|
||||
send: "送信",
|
||||
total: "合計 {count} 件",
|
||||
prev: "前へ",
|
||||
next: "次へ",
|
||||
test: "テスト",
|
||||
readOnly: "読み取り専用",
|
||||
},
|
||||
};
|
||||
@@ -235,6 +235,20 @@ export default {
|
||||
redisSentinelTls: "TLS do Sentinel",
|
||||
redisSentinelTlsHint: "Usar TLS ao conectar aos nós Sentinel",
|
||||
redisKeySeparator: "Separador de namespace de chave",
|
||||
nacosConsoleUrl: "URL do console",
|
||||
nacosConsoleUrlHint: "Use o endereço do console/admin API do Nacos. O Nacos 3 Docker geralmente expõe o console em 8085; implantações antigas podem compartilhar 8848 com a porta de serviço.",
|
||||
nacosConsoleUrlRequired: "A URL do console Nacos é obrigatória",
|
||||
nacosConsoleUrlAutoAdjusted: "URL do console Nacos ajustada de {from} para {to}.",
|
||||
nacosNamespace: "Namespace",
|
||||
nacosContextPath: "Context Path",
|
||||
nacosContextPathPlaceholder: "Deixe vazio ou /nacos",
|
||||
nacosAuth: "Autenticação",
|
||||
nacosAuthNone: "Nenhuma",
|
||||
nacosAuthUserPassword: "Usuário / Senha",
|
||||
nacosUsernameRequired: "O usuário Nacos é obrigatório",
|
||||
nacosTls: "TLS",
|
||||
nacosTlsSkipVerify: "Ignorar verificação do certificado",
|
||||
nacosPageSize: "Tamanho da página",
|
||||
searchDatabasePlaceholder: "Pesquisar tipos de banco de dados",
|
||||
iconView: "Visualização em ícones",
|
||||
listView: "Visualização em lista",
|
||||
@@ -2439,4 +2453,109 @@ export default {
|
||||
exportError: "Falha na exportação: {error}",
|
||||
exportCancelled: "Exportação cancelada",
|
||||
},
|
||||
nacos: {
|
||||
configs: "Configurações",
|
||||
services: "Serviços",
|
||||
raw: "Raw",
|
||||
namespace: "Namespace",
|
||||
dataId: "Data ID",
|
||||
group: "Grupo",
|
||||
format: "Formato",
|
||||
content: "Conteúdo",
|
||||
tags: "Tags",
|
||||
application: "Aplicação",
|
||||
description: "Descrição",
|
||||
advanced: "Avançado",
|
||||
collapse: "Recolher",
|
||||
load: "Carregar",
|
||||
save: "Salvar",
|
||||
saveAs: "Salvar como",
|
||||
saving: "Salvando...",
|
||||
saved: "Configuração salva",
|
||||
createdAndLoaded: "{dataId} criado e carregado",
|
||||
savedAndLoaded: "{dataId} salvo e atualizado",
|
||||
deleted: "Configuração excluída",
|
||||
delete: "Excluir",
|
||||
copy: "Copiar",
|
||||
copied: "Copiado",
|
||||
export: "Exportar",
|
||||
exported: "Exportação copiada",
|
||||
history: "History",
|
||||
configHistory: "Config history",
|
||||
historyDetail: "History detail",
|
||||
historyCompareTitle: "History Version Compare",
|
||||
currentPublishedContent: "Current published content:",
|
||||
historyVersionContent: "History version content:",
|
||||
updatedAt: "Updated at",
|
||||
operationType: "Operation",
|
||||
operator: "Operator",
|
||||
view: "View",
|
||||
compare: "Compare",
|
||||
rollback: "Rollback",
|
||||
loadingHistory: "Loading history...",
|
||||
noHistory: "No history versions",
|
||||
noHistoryHint: "Refresh if this config was just published. Some Nacos versions may not expose history APIs.",
|
||||
historyCreate: "Create",
|
||||
historyUpdate: "Update",
|
||||
historyDelete: "Delete",
|
||||
historyRollback: "Rollback",
|
||||
confirmRollbackTitle: "Rollback Nacos config",
|
||||
confirmRollbackMessage: "This will roll the current config back to the selected history version.",
|
||||
rollbackSuccess: "Config rolled back",
|
||||
publish: "Publicar",
|
||||
dataIdRequired: "Data ID é obrigatório",
|
||||
allGroups: "Todos os grupos",
|
||||
noConfigs: "Nenhuma configuração",
|
||||
selectConfig: "Selecione ou crie uma configuração",
|
||||
noServices: "Nenhum serviço",
|
||||
selectService: "Selecione um serviço",
|
||||
service: "Serviço",
|
||||
instances: "Instâncias",
|
||||
instanceCount: "{count} instâncias",
|
||||
refresh: "Atualizar",
|
||||
address: "Endereço",
|
||||
cluster: "Cluster",
|
||||
weight: "Peso",
|
||||
state: "Estado",
|
||||
actions: "Ações",
|
||||
query: "Query",
|
||||
body: "Corpo",
|
||||
response: "Resposta",
|
||||
template: "Modelo",
|
||||
configDiffTitle: "Comparar conteúdo da configuração",
|
||||
inlineCompare: "Comparação inline",
|
||||
currentVersionContent: "Versão oficial atual:",
|
||||
publishVersionContent: "Conteúdo desta publicação:",
|
||||
confirmSaveTitle: "Revisar alterações da configuração Nacos",
|
||||
confirmSaveMessage: "Salvar esta alteração? +{added} / -{removed} linhas.",
|
||||
confirmDeleteTitle: "Excluir configuração Nacos",
|
||||
confirmDeleteMessage: "Isso remove a configuração selecionada do Nacos.",
|
||||
confirmInstanceTitle: "Atualizar instância Nacos",
|
||||
confirmInstanceMessage: "Confirme o estado de destino da instância.",
|
||||
confirmRawTitle: "Enviar solicitação Raw API mutável",
|
||||
confirmRawMessage: "Esta solicitação Raw API pode modificar o estado do Nacos.",
|
||||
rawTemplate: {
|
||||
serverState: "Estado do servidor",
|
||||
namespaceList: "Lista de namespaces",
|
||||
configDetail: "Detalhe da configuração",
|
||||
serviceList: "Lista de serviços",
|
||||
instanceList: "Lista de instâncias",
|
||||
},
|
||||
healthy: "Saudável",
|
||||
unhealthy: "Não saudável",
|
||||
enabled: "Ativado",
|
||||
offline: "Offline",
|
||||
ephemeral: "Efêmera",
|
||||
persistent: "Persistente",
|
||||
enable: "Ativar",
|
||||
disable: "Desativar",
|
||||
markHealthy: "Marcar saudável",
|
||||
markUnhealthy: "Marcar não saudável",
|
||||
send: "Enviar",
|
||||
total: "{count} no total",
|
||||
prev: "Anterior",
|
||||
next: "Próximo",
|
||||
test: "Testar",
|
||||
readOnly: "Somente leitura",
|
||||
},
|
||||
};
|
||||
@@ -251,6 +251,20 @@ export default {
|
||||
etcdClientCertBrowse: "选择客户端证书",
|
||||
etcdClientKeyBrowse: "选择客户端私钥",
|
||||
etcdClientCertPairRequired: "客户端证书和私钥必须一起填写。",
|
||||
nacosConsoleUrl: "控制台 URL",
|
||||
nacosConsoleUrlHint: "填写 Nacos 控制台/admin API 地址。Nacos 3 Docker 通常是 8085;如果旧版控制台和服务共用 8848,也可以填 8848。",
|
||||
nacosConsoleUrlRequired: "Nacos 控制台 URL 不能为空",
|
||||
nacosConsoleUrlAutoAdjusted: "已自动将 Nacos 控制台 URL 从 {from} 调整为 {to}。",
|
||||
nacosNamespace: "命名空间",
|
||||
nacosContextPath: "Context Path",
|
||||
nacosContextPathPlaceholder: "留空或填写 /nacos",
|
||||
nacosAuth: "认证",
|
||||
nacosAuthNone: "无",
|
||||
nacosAuthUserPassword: "用户名 / 密码",
|
||||
nacosUsernameRequired: "Nacos 用户名不能为空",
|
||||
nacosTls: "TLS",
|
||||
nacosTlsSkipVerify: "跳过证书验证",
|
||||
nacosPageSize: "分页大小",
|
||||
searchDatabasePlaceholder: "搜索数据库类型",
|
||||
iconView: "图标视图",
|
||||
listView: "列表视图",
|
||||
@@ -2744,4 +2758,121 @@ export default {
|
||||
exportError: "导出失败: {error}",
|
||||
exportCancelled: "导出已取消",
|
||||
},
|
||||
nacos: {
|
||||
configs: "配置",
|
||||
services: "服务",
|
||||
raw: "Raw",
|
||||
namespace: "命名空间",
|
||||
createNamespace: "新建命名空间",
|
||||
editNamespace: "编辑命名空间",
|
||||
creatingNamespace: "正在创建...",
|
||||
updatingNamespace: "正在更新...",
|
||||
namespaceCreated: "命名空间「{name}」已创建",
|
||||
namespaceUpdated: "命名空间「{name}」已更新",
|
||||
namespaceId: "命名空间 ID",
|
||||
namespaceIdPlaceholder: "留空则由 Nacos 自动生成",
|
||||
namespaceName: "命名空间名",
|
||||
namespaceNamePlaceholder: "请输入命名空间名",
|
||||
namespaceDesc: "描述",
|
||||
namespaceDescPlaceholder: "留空则使用命名空间名",
|
||||
dataId: "Data ID",
|
||||
group: "Group",
|
||||
format: "配置格式",
|
||||
content: "配置内容",
|
||||
tags: "标签",
|
||||
application: "归属应用",
|
||||
description: "描述",
|
||||
advanced: "高级配置",
|
||||
collapse: "收起",
|
||||
load: "加载",
|
||||
save: "保存",
|
||||
saveAs: "另存为",
|
||||
saving: "保存中...",
|
||||
saved: "配置已保存",
|
||||
createdAndLoaded: "已创建并加载 {dataId}",
|
||||
savedAndLoaded: "已保存并刷新 {dataId}",
|
||||
deleted: "配置已删除",
|
||||
delete: "删除",
|
||||
copy: "复制",
|
||||
copied: "已复制",
|
||||
export: "导出",
|
||||
exported: "导出内容已复制",
|
||||
history: "历史",
|
||||
configHistory: "配置历史",
|
||||
historyDetail: "历史版本详情",
|
||||
historyCompareTitle: "历史版本对比",
|
||||
currentPublishedContent: "当前已发布内容:",
|
||||
historyVersionContent: "历史版本内容:",
|
||||
updatedAt: "更新时间",
|
||||
operationType: "操作类型",
|
||||
operator: "操作人",
|
||||
view: "查看",
|
||||
compare: "对比",
|
||||
rollback: "回滚",
|
||||
loadingHistory: "正在加载历史版本...",
|
||||
noHistory: "暂无历史版本",
|
||||
noHistoryHint: "如果刚发布过配置,请刷新;部分 Nacos 版本可能未开启历史记录接口。",
|
||||
historyCreate: "新增",
|
||||
historyUpdate: "更新",
|
||||
historyDelete: "删除",
|
||||
historyRollback: "回滚",
|
||||
confirmRollbackTitle: "回滚 Nacos 配置",
|
||||
confirmRollbackMessage: "该操作会将当前配置回滚到所选历史版本。",
|
||||
rollbackSuccess: "配置已回滚",
|
||||
publish: "发布",
|
||||
dataIdRequired: "Data ID 不能为空",
|
||||
allGroups: "全部分组",
|
||||
noConfigs: "暂无配置",
|
||||
selectConfig: "选择或新建配置",
|
||||
noServices: "暂无服务",
|
||||
selectService: "选择服务",
|
||||
service: "服务",
|
||||
instances: "实例",
|
||||
instanceCount: "{count} 个实例",
|
||||
refresh: "刷新",
|
||||
address: "地址",
|
||||
cluster: "集群",
|
||||
weight: "权重",
|
||||
state: "状态",
|
||||
actions: "操作",
|
||||
query: "查询参数",
|
||||
body: "请求体",
|
||||
response: "响应",
|
||||
template: "模板",
|
||||
configDiffTitle: "配置内容对比",
|
||||
inlineCompare: "行内对比",
|
||||
currentVersionContent: "当前正式版本内容:",
|
||||
publishVersionContent: "本次发布内容:",
|
||||
confirmSaveTitle: "确认保存 Nacos 配置",
|
||||
confirmSaveMessage: "是否保存这次配置变更?新增 {added} 行,删除 {removed} 行。",
|
||||
confirmDeleteTitle: "删除 Nacos 配置",
|
||||
confirmDeleteMessage: "该操作会从 Nacos 删除所选配置。",
|
||||
confirmInstanceTitle: "更新 Nacos 实例",
|
||||
confirmInstanceMessage: "请确认目标服务实例状态。",
|
||||
confirmRawTitle: "发送变更型 Raw API 请求",
|
||||
confirmRawMessage: "该 Raw API 请求可能会修改 Nacos 状态。",
|
||||
rawTemplate: {
|
||||
serverState: "服务状态",
|
||||
namespaceList: "命名空间列表",
|
||||
configDetail: "配置详情",
|
||||
serviceList: "服务列表",
|
||||
instanceList: "实例列表",
|
||||
},
|
||||
healthy: "健康",
|
||||
unhealthy: "不健康",
|
||||
enabled: "已启用",
|
||||
offline: "已下线",
|
||||
ephemeral: "临时实例",
|
||||
persistent: "持久实例",
|
||||
enable: "启用",
|
||||
disable: "禁用",
|
||||
markHealthy: "标记健康",
|
||||
markUnhealthy: "标记不健康",
|
||||
send: "发送",
|
||||
total: "共 {count} 条",
|
||||
prev: "上一页",
|
||||
next: "下一页",
|
||||
test: "测试",
|
||||
readOnly: "只读",
|
||||
},
|
||||
};
|
||||
@@ -235,6 +235,20 @@ export default {
|
||||
redisSentinelTls: "Sentinel TLS",
|
||||
redisSentinelTlsHint: "連線 Sentinel 節點時使用 TLS",
|
||||
redisKeySeparator: "鍵名分隔符(可選)",
|
||||
nacosConsoleUrl: "控制台 URL",
|
||||
nacosConsoleUrlHint: "填寫 Nacos 控制台/admin API 位址。Nacos 3 Docker 通常是 8085;如果舊版控制台和服務共用 8848,也可以填 8848。",
|
||||
nacosConsoleUrlRequired: "Nacos 控制台 URL 不能為空",
|
||||
nacosConsoleUrlAutoAdjusted: "已自動將 Nacos 控制台 URL 從 {from} 調整為 {to}。",
|
||||
nacosNamespace: "命名空間",
|
||||
nacosContextPath: "Context Path",
|
||||
nacosContextPathPlaceholder: "留空或填寫 /nacos",
|
||||
nacosAuth: "認證",
|
||||
nacosAuthNone: "無",
|
||||
nacosAuthUserPassword: "使用者名稱 / 密碼",
|
||||
nacosUsernameRequired: "Nacos 使用者名稱不能為空",
|
||||
nacosTls: "TLS",
|
||||
nacosTlsSkipVerify: "跳過憑證驗證",
|
||||
nacosPageSize: "分頁大小",
|
||||
searchDatabasePlaceholder: "搜尋資料庫類型",
|
||||
iconView: "圖示檢視",
|
||||
listView: "清單檢視",
|
||||
@@ -2453,4 +2467,109 @@ export default {
|
||||
exportError: "匯出失敗: {error}",
|
||||
exportCancelled: "匯出已取消",
|
||||
},
|
||||
nacos: {
|
||||
configs: "配置",
|
||||
services: "服務",
|
||||
raw: "Raw",
|
||||
namespace: "命名空間",
|
||||
dataId: "Data ID",
|
||||
group: "Group",
|
||||
format: "配置格式",
|
||||
content: "配置內容",
|
||||
tags: "標籤",
|
||||
application: "歸屬應用",
|
||||
description: "描述",
|
||||
advanced: "進階配置",
|
||||
collapse: "收起",
|
||||
load: "載入",
|
||||
save: "儲存",
|
||||
saveAs: "另存為",
|
||||
saving: "儲存中...",
|
||||
saved: "配置已儲存",
|
||||
createdAndLoaded: "已建立並載入 {dataId}",
|
||||
savedAndLoaded: "已儲存並重新整理 {dataId}",
|
||||
deleted: "配置已刪除",
|
||||
delete: "刪除",
|
||||
copy: "複製",
|
||||
copied: "已複製",
|
||||
export: "匯出",
|
||||
exported: "匯出內容已複製",
|
||||
history: "歷史",
|
||||
configHistory: "配置歷史",
|
||||
historyDetail: "歷史版本詳情",
|
||||
historyCompareTitle: "歷史版本對比",
|
||||
currentPublishedContent: "目前已發布內容:",
|
||||
historyVersionContent: "歷史版本內容:",
|
||||
updatedAt: "更新時間",
|
||||
operationType: "操作類型",
|
||||
operator: "操作人",
|
||||
view: "查看",
|
||||
compare: "對比",
|
||||
rollback: "回滾",
|
||||
loadingHistory: "正在載入歷史版本...",
|
||||
noHistory: "暫無歷史版本",
|
||||
noHistoryHint: "如果剛發布過配置,請重新整理;部分 Nacos 版本可能未開啟歷史記錄介面。",
|
||||
historyCreate: "新增",
|
||||
historyUpdate: "更新",
|
||||
historyDelete: "刪除",
|
||||
historyRollback: "回滾",
|
||||
confirmRollbackTitle: "回滾 Nacos 配置",
|
||||
confirmRollbackMessage: "該操作會將目前配置回滾到所選歷史版本。",
|
||||
rollbackSuccess: "配置已回滾",
|
||||
publish: "發布",
|
||||
dataIdRequired: "Data ID 不能為空",
|
||||
allGroups: "全部分組",
|
||||
noConfigs: "暫無配置",
|
||||
selectConfig: "選擇或新增配置",
|
||||
noServices: "暫無服務",
|
||||
selectService: "選擇服務",
|
||||
service: "服務",
|
||||
instances: "實例",
|
||||
instanceCount: "{count} 個實例",
|
||||
refresh: "重新整理",
|
||||
address: "位址",
|
||||
cluster: "叢集",
|
||||
weight: "權重",
|
||||
state: "狀態",
|
||||
actions: "操作",
|
||||
query: "查詢參數",
|
||||
body: "請求體",
|
||||
response: "回應",
|
||||
template: "模板",
|
||||
configDiffTitle: "配置內容對比",
|
||||
inlineCompare: "行內對比",
|
||||
currentVersionContent: "目前正式版本內容:",
|
||||
publishVersionContent: "本次發布內容:",
|
||||
confirmSaveTitle: "確認儲存 Nacos 配置",
|
||||
confirmSaveMessage: "是否儲存這次配置變更?新增 {added} 行,刪除 {removed} 行。",
|
||||
confirmDeleteTitle: "刪除 Nacos 配置",
|
||||
confirmDeleteMessage: "此操作會從 Nacos 刪除所選配置。",
|
||||
confirmInstanceTitle: "更新 Nacos 實例",
|
||||
confirmInstanceMessage: "請確認目標服務實例狀態。",
|
||||
confirmRawTitle: "傳送變更型 Raw API 請求",
|
||||
confirmRawMessage: "此 Raw API 請求可能會修改 Nacos 狀態。",
|
||||
rawTemplate: {
|
||||
serverState: "服務狀態",
|
||||
namespaceList: "命名空間列表",
|
||||
configDetail: "配置詳情",
|
||||
serviceList: "服務列表",
|
||||
instanceList: "實例列表",
|
||||
},
|
||||
healthy: "健康",
|
||||
unhealthy: "不健康",
|
||||
enabled: "已啟用",
|
||||
offline: "已下線",
|
||||
ephemeral: "臨時實例",
|
||||
persistent: "持久實例",
|
||||
enable: "啟用",
|
||||
disable: "停用",
|
||||
markHealthy: "標記健康",
|
||||
markUnhealthy: "標記不健康",
|
||||
send: "傳送",
|
||||
total: "共 {count} 筆",
|
||||
prev: "上一頁",
|
||||
next: "下一頁",
|
||||
test: "測試",
|
||||
readOnly: "唯讀",
|
||||
},
|
||||
};
|
||||
@@ -24,6 +24,10 @@ describe("quickConnectionOpenTarget", () => {
|
||||
expect(quickConnectionOpenTarget(connection("mq"))).toEqual({ kind: "mq-admin" });
|
||||
});
|
||||
|
||||
it("opens Nacos connections in the Nacos admin console", () => {
|
||||
expect(quickConnectionOpenTarget(connection("nacos"))).toEqual({ kind: "nacos-admin" });
|
||||
});
|
||||
|
||||
it("opens regular connections in a query tab", () => {
|
||||
expect(quickConnectionOpenTarget({ ...connection("postgresql"), database: "app" })).toEqual({
|
||||
kind: "query",
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { buildNacosConfigDeleteConfirm, buildNacosInlineDiff, buildNacosInstanceConfirm, buildNacosRawRequest, buildNacosSideBySideDiff, isNacosRawMutation, parseNacosRawBody, parseNacosRawQuery, summarizeNacosConfigDiff } from "../nacosAdmin";
|
||||
|
||||
describe("nacosAdmin helpers", () => {
|
||||
it("parses raw query and body text", () => {
|
||||
expect(parseNacosRawQuery("?dataId=a&group=DEFAULT_GROUP")).toEqual({ dataId: "a", group: "DEFAULT_GROUP" });
|
||||
expect(parseNacosRawQuery("")).toBeUndefined();
|
||||
expect(parseNacosRawBody('{"enabled":false}')).toEqual({ enabled: false });
|
||||
expect(parseNacosRawBody("plain text")).toBe("plain text");
|
||||
});
|
||||
|
||||
it("builds raw requests and detects mutations", () => {
|
||||
const req = buildNacosRawRequest("post", " /v1/cs/configs ", "a=1", '{"b":2}');
|
||||
expect(req).toEqual({ method: "post", path: "/v1/cs/configs", query: { a: "1" }, body: { b: 2 } });
|
||||
expect(isNacosRawMutation("GET")).toBe(false);
|
||||
expect(isNacosRawMutation("DELETE")).toBe(true);
|
||||
});
|
||||
|
||||
it("summarizes config diffs", () => {
|
||||
const diff = summarizeNacosConfigDiff("a\nb", "a\nc\nd");
|
||||
expect(diff.changed).toBe(true);
|
||||
expect(diff.removedLines).toBe(1);
|
||||
expect(diff.addedLines).toBe(2);
|
||||
expect(diff.preview).toContain("- b");
|
||||
expect(diff.preview).toContain("+ c");
|
||||
});
|
||||
|
||||
it("builds side-by-side config diff rows with inline segments", () => {
|
||||
const rows = buildNacosSideBySideDiff('cloud:\n secret: "aaa"\n', 'cloud:\n secret: "aaa1"\n enabled: true\n');
|
||||
expect(rows[0]).toMatchObject({ leftLineNumber: 1, rightLineNumber: 1, leftType: "equal", rightType: "equal" });
|
||||
expect(rows[1]).toMatchObject({ leftLineNumber: 2, rightLineNumber: 2, leftType: "modify", rightType: "modify" });
|
||||
expect(rows[1].rightInline.some((segment) => segment.changed && segment.value === "1")).toBe(true);
|
||||
expect(rows[2]).toMatchObject({ leftLineNumber: null, rightLineNumber: 3, leftType: "padding", rightType: "insert" });
|
||||
});
|
||||
|
||||
it("builds inline config diff rows with character-level changed segments", () => {
|
||||
const rows = buildNacosInlineDiff('secretId: "aaa1"\n', 'secretId: "aaa2"\n');
|
||||
expect(rows).toHaveLength(2);
|
||||
expect(rows[0]).toMatchObject({ lineNumber: 1, type: "delete" });
|
||||
expect(rows[1]).toMatchObject({ lineNumber: 1, type: "insert" });
|
||||
expect(rows[0].segments.some((segment) => segment.changed && segment.value === "1")).toBe(true);
|
||||
expect(rows[1].segments.some((segment) => segment.changed && segment.value === "2")).toBe(true);
|
||||
});
|
||||
|
||||
it("includes identifying fields in confirmations", () => {
|
||||
expect(buildNacosConfigDeleteConfirm({ namespace: "", dataId: "app.yaml", group: "DEFAULT_GROUP" })).toContain("dataId=app.yaml");
|
||||
const details = buildNacosInstanceConfirm({ serviceName: "DEFAULT_GROUP@@svc", groupName: "DEFAULT_GROUP" }, { ip: "127.0.0.1", port: 8080, enabled: true, metadata: null }, { enabled: false }, "", "public");
|
||||
expect(details).toContain("serviceName=DEFAULT_GROUP@@svc");
|
||||
expect(details).toContain("targetEnabled=false");
|
||||
});
|
||||
});
|
||||
@@ -228,6 +228,23 @@ export const pendingOpenDbFiles = forward("pendingOpenDbFiles");
|
||||
export const pendingOpenConnectionLinks = forward("pendingOpenConnectionLinks");
|
||||
export const readExternalSqlFile = forward("readExternalSqlFile");
|
||||
|
||||
// Nacos
|
||||
export const nacosTestConnection = forward("nacosTestConnection");
|
||||
export const nacosListNamespaces = forward("nacosListNamespaces");
|
||||
export const nacosCreateNamespace = forward("nacosCreateNamespace");
|
||||
export const nacosUpdateNamespace = forward("nacosUpdateNamespace");
|
||||
export const nacosListConfigs = forward("nacosListConfigs");
|
||||
export const nacosGetConfig = forward("nacosGetConfig");
|
||||
export const nacosPublishConfig = forward("nacosPublishConfig");
|
||||
export const nacosDeleteConfig = forward("nacosDeleteConfig");
|
||||
export const nacosListConfigHistory = forward("nacosListConfigHistory");
|
||||
export const nacosGetConfigHistory = forward("nacosGetConfigHistory");
|
||||
export const nacosRollbackConfig = forward("nacosRollbackConfig");
|
||||
export const nacosListServices = forward("nacosListServices");
|
||||
export const nacosListInstances = forward("nacosListInstances");
|
||||
export const nacosUpdateInstance = forward("nacosUpdateInstance");
|
||||
export const nacosRawRequest = forward("nacosRawRequest");
|
||||
|
||||
// Data Transfer
|
||||
export const startTransfer = forward("startTransfer");
|
||||
export const cancelTransfer = forward("cancelTransfer");
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
import type { ConnectionConfig } from "@/types/database";
|
||||
import { resolveDefaultDatabase } from "@/lib/defaultDatabase";
|
||||
|
||||
export type QuickConnectionOpenTarget = { kind: "mq-admin" } | { kind: "query"; database: string };
|
||||
export type QuickConnectionOpenTarget = { kind: "mq-admin" } | { kind: "nacos-admin" } | { kind: "query"; database: string };
|
||||
|
||||
export function quickConnectionOpenTarget(connection: Pick<ConnectionConfig, "db_type" | "database">, databaseOptions: string[] = []): QuickConnectionOpenTarget {
|
||||
if (connection.db_type === "mq") {
|
||||
return { kind: "mq-admin" };
|
||||
}
|
||||
if (connection.db_type === "nacos") {
|
||||
return { kind: "nacos-admin" };
|
||||
}
|
||||
return { kind: "query", database: resolveDefaultDatabase(connection, databaseOptions) };
|
||||
}
|
||||
@@ -96,6 +96,28 @@ import type { DatabaseNameSqlOptions, DropTableChildObjectSqlOptions, DropObject
|
||||
import type { BuildDatabaseSqlExportOptions, BuildExportInsertStatementsOptions } from "@/lib/databaseExport";
|
||||
import type { DataCompareFromTablesOptions, DataCompareFromTablesPreparation, DataCompareSyncPlan, DataCompareSyncPlanOptions, DataComparePreparation, DataComparePreparationOptions } from "@/lib/dataCompare";
|
||||
import type { DataGridSavePreparation } from "./tauri";
|
||||
import type {
|
||||
NacosConfigHistoryKey,
|
||||
NacosConfigHistoryList,
|
||||
NacosConfigHistoryQuery,
|
||||
NacosConfigItem,
|
||||
NacosConfigKey,
|
||||
NacosConfigList,
|
||||
NacosConfigQuery,
|
||||
NacosConfigRollbackRequest,
|
||||
NacosConfigUpsert,
|
||||
NacosConnectionInfo,
|
||||
NacosInstanceInfo,
|
||||
NacosInstanceQuery,
|
||||
NacosInstanceUpdate,
|
||||
NacosNamespaceCreate,
|
||||
NacosNamespaceInfo,
|
||||
NacosNamespaceUpdate,
|
||||
NacosRawRequest,
|
||||
NacosRawResponse,
|
||||
NacosServiceList,
|
||||
NacosServiceQuery,
|
||||
} from "@/types/nacos";
|
||||
import { safeLocalStorageGet, safeLocalStorageSet } from "@/lib/safeStorage";
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -1500,6 +1522,70 @@ export async function etcdDelete(connectionId: string, key: string): Promise<KvD
|
||||
return post("/api/etcd/delete", { connectionId, key });
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Nacos
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export async function nacosTestConnection(connectionId: string): Promise<NacosConnectionInfo> {
|
||||
return post("/api/nacos/test-connection", { connectionId });
|
||||
}
|
||||
|
||||
export async function nacosListNamespaces(connectionId: string): Promise<NacosNamespaceInfo[]> {
|
||||
return post("/api/nacos/namespaces/list", { connectionId });
|
||||
}
|
||||
|
||||
export async function nacosCreateNamespace(connectionId: string, req: NacosNamespaceCreate): Promise<void> {
|
||||
return post("/api/nacos/namespaces/create", { connectionId, req });
|
||||
}
|
||||
|
||||
export async function nacosUpdateNamespace(connectionId: string, req: NacosNamespaceUpdate): Promise<void> {
|
||||
return post("/api/nacos/namespaces/update", { connectionId, req });
|
||||
}
|
||||
|
||||
export async function nacosListConfigs(connectionId: string, query: NacosConfigQuery): Promise<NacosConfigList> {
|
||||
return post("/api/nacos/configs/list", { connectionId, query });
|
||||
}
|
||||
|
||||
export async function nacosGetConfig(connectionId: string, key: NacosConfigKey): Promise<NacosConfigItem> {
|
||||
return post("/api/nacos/configs/get", { connectionId, key });
|
||||
}
|
||||
|
||||
export async function nacosPublishConfig(connectionId: string, req: NacosConfigUpsert): Promise<void> {
|
||||
return post("/api/nacos/configs/publish", { connectionId, req });
|
||||
}
|
||||
|
||||
export async function nacosDeleteConfig(connectionId: string, key: NacosConfigKey): Promise<void> {
|
||||
return post("/api/nacos/configs/delete", { connectionId, key });
|
||||
}
|
||||
|
||||
export async function nacosListConfigHistory(connectionId: string, query: NacosConfigHistoryQuery): Promise<NacosConfigHistoryList> {
|
||||
return post("/api/nacos/configs/history/list", { connectionId, query });
|
||||
}
|
||||
|
||||
export async function nacosGetConfigHistory(connectionId: string, key: NacosConfigHistoryKey): Promise<NacosConfigItem> {
|
||||
return post("/api/nacos/configs/history/get", { connectionId, key });
|
||||
}
|
||||
|
||||
export async function nacosRollbackConfig(connectionId: string, req: NacosConfigRollbackRequest): Promise<void> {
|
||||
return post("/api/nacos/configs/history/rollback", { connectionId, req });
|
||||
}
|
||||
|
||||
export async function nacosListServices(connectionId: string, query: NacosServiceQuery): Promise<NacosServiceList> {
|
||||
return post("/api/nacos/services/list", { connectionId, query });
|
||||
}
|
||||
|
||||
export async function nacosListInstances(connectionId: string, query: NacosInstanceQuery): Promise<NacosInstanceInfo[]> {
|
||||
return post("/api/nacos/instances/list", { connectionId, query });
|
||||
}
|
||||
|
||||
export async function nacosUpdateInstance(connectionId: string, req: NacosInstanceUpdate): Promise<void> {
|
||||
return post("/api/nacos/instances/update", { connectionId, req });
|
||||
}
|
||||
|
||||
export async function nacosRawRequest(connectionId: string, req: NacosRawRequest): Promise<NacosRawResponse> {
|
||||
return post("/api/nacos/raw", { connectionId, req });
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// MongoDB
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
import { invoke } from "@tauri-apps/api/core";
|
||||
import type {
|
||||
NacosConfigHistoryKey,
|
||||
NacosConfigHistoryList,
|
||||
NacosConfigHistoryQuery,
|
||||
NacosConfigItem,
|
||||
NacosConfigKey,
|
||||
NacosConfigList,
|
||||
NacosConfigQuery,
|
||||
NacosConfigRollbackRequest,
|
||||
NacosConfigUpsert,
|
||||
NacosConnectionInfo,
|
||||
NacosInstanceInfo,
|
||||
NacosInstanceQuery,
|
||||
NacosInstanceUpdate,
|
||||
NacosNamespaceCreate,
|
||||
NacosNamespaceInfo,
|
||||
NacosNamespaceUpdate,
|
||||
NacosRawRequest,
|
||||
NacosRawResponse,
|
||||
NacosServiceList,
|
||||
NacosServiceQuery,
|
||||
} from "@/types/nacos";
|
||||
|
||||
export async function nacosTestConnection(connectionId: string): Promise<NacosConnectionInfo> {
|
||||
return invoke("nacos_test_connection", { connectionId });
|
||||
}
|
||||
|
||||
export async function nacosListNamespaces(connectionId: string): Promise<NacosNamespaceInfo[]> {
|
||||
return invoke("nacos_list_namespaces", { connectionId });
|
||||
}
|
||||
|
||||
export async function nacosCreateNamespace(connectionId: string, req: NacosNamespaceCreate): Promise<void> {
|
||||
return invoke("nacos_create_namespace", { connectionId, req });
|
||||
}
|
||||
|
||||
export async function nacosUpdateNamespace(connectionId: string, req: NacosNamespaceUpdate): Promise<void> {
|
||||
return invoke("nacos_update_namespace", { connectionId, req });
|
||||
}
|
||||
|
||||
export async function nacosListConfigs(connectionId: string, query: NacosConfigQuery): Promise<NacosConfigList> {
|
||||
return invoke("nacos_list_configs", { connectionId, query });
|
||||
}
|
||||
|
||||
export async function nacosGetConfig(connectionId: string, key: NacosConfigKey): Promise<NacosConfigItem> {
|
||||
return invoke("nacos_get_config", { connectionId, key });
|
||||
}
|
||||
|
||||
export async function nacosPublishConfig(connectionId: string, req: NacosConfigUpsert): Promise<void> {
|
||||
return invoke("nacos_publish_config", { connectionId, req });
|
||||
}
|
||||
|
||||
export async function nacosDeleteConfig(connectionId: string, key: NacosConfigKey): Promise<void> {
|
||||
return invoke("nacos_delete_config", { connectionId, key });
|
||||
}
|
||||
|
||||
export async function nacosListConfigHistory(connectionId: string, query: NacosConfigHistoryQuery): Promise<NacosConfigHistoryList> {
|
||||
return invoke("nacos_list_config_history", { connectionId, query });
|
||||
}
|
||||
|
||||
export async function nacosGetConfigHistory(connectionId: string, key: NacosConfigHistoryKey): Promise<NacosConfigItem> {
|
||||
return invoke("nacos_get_config_history", { connectionId, key });
|
||||
}
|
||||
|
||||
export async function nacosRollbackConfig(connectionId: string, req: NacosConfigRollbackRequest): Promise<void> {
|
||||
return invoke("nacos_rollback_config", { connectionId, req });
|
||||
}
|
||||
|
||||
export async function nacosListServices(connectionId: string, query: NacosServiceQuery): Promise<NacosServiceList> {
|
||||
return invoke("nacos_list_services", { connectionId, query });
|
||||
}
|
||||
|
||||
export async function nacosListInstances(connectionId: string, query: NacosInstanceQuery): Promise<NacosInstanceInfo[]> {
|
||||
return invoke("nacos_list_instances", { connectionId, query });
|
||||
}
|
||||
|
||||
export async function nacosUpdateInstance(connectionId: string, req: NacosInstanceUpdate): Promise<void> {
|
||||
return invoke("nacos_update_instance", { connectionId, req });
|
||||
}
|
||||
|
||||
export async function nacosRawRequest(connectionId: string, req: NacosRawRequest): Promise<NacosRawResponse> {
|
||||
return invoke("nacos_raw_request", { connectionId, req });
|
||||
}
|
||||
@@ -0,0 +1,323 @@
|
||||
import type { NacosConfigHistoryItem, NacosConfigItem, NacosInstanceInfo, NacosRawRequest, NacosServiceInfo } from "@/types/nacos";
|
||||
import { diffChars, diffLines } from "diff";
|
||||
|
||||
export type NacosRawTemplateKey = "serverState" | "namespaceList" | "configDetail" | "serviceList" | "instanceList";
|
||||
|
||||
export interface NacosRawTemplate {
|
||||
key: NacosRawTemplateKey;
|
||||
method: string;
|
||||
path: string;
|
||||
query: string;
|
||||
body: string;
|
||||
}
|
||||
|
||||
export const NACOS_RAW_TEMPLATES: NacosRawTemplate[] = [
|
||||
{ key: "serverState", method: "GET", path: "/v3/console/server/state", query: "", body: "" },
|
||||
{ key: "namespaceList", method: "GET", path: "/v3/console/core/namespace/list", query: "", body: "" },
|
||||
{
|
||||
key: "configDetail",
|
||||
method: "GET",
|
||||
path: "/v3/console/cs/config",
|
||||
query: "dataId=application.yaml&groupName=DEFAULT_GROUP&namespaceId=",
|
||||
body: "",
|
||||
},
|
||||
{
|
||||
key: "serviceList",
|
||||
method: "GET",
|
||||
path: "/v3/console/ns/service/list",
|
||||
query: "pageNo=1&pageSize=20&namespaceId=",
|
||||
body: "",
|
||||
},
|
||||
{
|
||||
key: "instanceList",
|
||||
method: "GET",
|
||||
path: "/v3/console/ns/instance/list",
|
||||
query: "serviceName=DEFAULT_GROUP@@example&namespaceId=",
|
||||
body: "",
|
||||
},
|
||||
];
|
||||
|
||||
export function parseNacosRawQuery(text: string): Record<string, string> | undefined {
|
||||
const trimmed = text.trim().replace(/^\?/, "");
|
||||
if (!trimmed) return undefined;
|
||||
return Object.fromEntries(new URLSearchParams(trimmed).entries());
|
||||
}
|
||||
|
||||
export function parseNacosRawBody(text: string): unknown {
|
||||
const trimmed = text.trim();
|
||||
if (!trimmed) return undefined;
|
||||
try {
|
||||
return JSON.parse(trimmed);
|
||||
} catch {
|
||||
return text;
|
||||
}
|
||||
}
|
||||
|
||||
export function buildNacosRawRequest(method: string, path: string, queryText: string, bodyText: string): NacosRawRequest {
|
||||
return {
|
||||
method,
|
||||
path: path.trim(),
|
||||
query: parseNacosRawQuery(queryText),
|
||||
body: parseNacosRawBody(bodyText),
|
||||
};
|
||||
}
|
||||
|
||||
export function isNacosRawMutation(method: string): boolean {
|
||||
return method.trim().toUpperCase() !== "GET";
|
||||
}
|
||||
|
||||
export function formatNacosConfigIdentity(item: Pick<NacosConfigItem, "namespace" | "dataId" | "group">, fallbackNamespace = ""): string {
|
||||
return [`namespace=${item.namespace || fallbackNamespace || "public"}`, `dataId=${item.dataId}`, `group=${item.group || "DEFAULT_GROUP"}`].join("\n");
|
||||
}
|
||||
|
||||
export function buildNacosConfigExport(item: NacosConfigItem, content: string): string {
|
||||
return [`# namespace: ${item.namespace || "public"}`, `# dataId: ${item.dataId}`, `# group: ${item.group || "DEFAULT_GROUP"}`, item.configType ? `# type: ${item.configType}` : "", "", content].filter((line, index) => index >= 4 || line).join("\n");
|
||||
}
|
||||
|
||||
export function buildNacosConfigCopy(item: NacosConfigItem, content: string): string {
|
||||
return `${formatNacosConfigIdentity(item)}\n\n${content}`;
|
||||
}
|
||||
|
||||
export function createNacosSaveAsCopy(item: NacosConfigItem): NacosConfigItem {
|
||||
return {
|
||||
...item,
|
||||
dataId: item.dataId ? `${item.dataId}.copy` : "",
|
||||
content: item.content ?? "",
|
||||
};
|
||||
}
|
||||
|
||||
export interface NacosDiffSummary {
|
||||
changed: boolean;
|
||||
addedLines: number;
|
||||
removedLines: number;
|
||||
preview: string;
|
||||
}
|
||||
|
||||
export type NacosDiffLineType = "equal" | "delete" | "insert" | "modify" | "padding";
|
||||
|
||||
export interface NacosInlineSegment {
|
||||
value: string;
|
||||
changed: boolean;
|
||||
}
|
||||
|
||||
export interface NacosSideBySideDiffRow {
|
||||
id: string;
|
||||
leftLineNumber: number | null;
|
||||
rightLineNumber: number | null;
|
||||
leftContent: string;
|
||||
rightContent: string;
|
||||
leftType: NacosDiffLineType;
|
||||
rightType: NacosDiffLineType;
|
||||
leftInline: NacosInlineSegment[];
|
||||
rightInline: NacosInlineSegment[];
|
||||
}
|
||||
|
||||
export interface NacosInlineDiffRow {
|
||||
id: string;
|
||||
lineNumber: number | null;
|
||||
content: string;
|
||||
type: Exclude<NacosDiffLineType, "modify" | "padding">;
|
||||
segments: NacosInlineSegment[];
|
||||
}
|
||||
|
||||
export function summarizeNacosConfigDiff(before: string, after: string, maxPreviewLines = 40): NacosDiffSummary {
|
||||
if (before === after) {
|
||||
return { changed: false, addedLines: 0, removedLines: 0, preview: "No content changes." };
|
||||
}
|
||||
const beforeLines = before.split(/\r?\n/);
|
||||
const afterLines = after.split(/\r?\n/);
|
||||
const max = Math.max(beforeLines.length, afterLines.length);
|
||||
const lines: string[] = [];
|
||||
let addedLines = 0;
|
||||
let removedLines = 0;
|
||||
for (let index = 0; index < max; index += 1) {
|
||||
const left = beforeLines[index];
|
||||
const right = afterLines[index];
|
||||
if (left === right) continue;
|
||||
if (left !== undefined) {
|
||||
removedLines += 1;
|
||||
lines.push(`- ${left}`);
|
||||
}
|
||||
if (right !== undefined) {
|
||||
addedLines += 1;
|
||||
lines.push(`+ ${right}`);
|
||||
}
|
||||
if (lines.length >= maxPreviewLines) {
|
||||
lines.push("...");
|
||||
break;
|
||||
}
|
||||
}
|
||||
return { changed: true, addedLines, removedLines, preview: lines.join("\n") };
|
||||
}
|
||||
|
||||
function normalizeNacosDiffText(value: string): string {
|
||||
return value.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
|
||||
}
|
||||
|
||||
function splitDiffLines(value: string): string[] {
|
||||
const lines = normalizeNacosDiffText(value).split("\n");
|
||||
if (lines.length > 0 && lines[lines.length - 1] === "") lines.pop();
|
||||
return lines;
|
||||
}
|
||||
|
||||
function inlineSegments(left: string, right: string): { left: NacosInlineSegment[]; right: NacosInlineSegment[] } {
|
||||
const changes = diffChars(left, right);
|
||||
const leftSegments: NacosInlineSegment[] = [];
|
||||
const rightSegments: NacosInlineSegment[] = [];
|
||||
for (const change of changes) {
|
||||
if (change.removed) {
|
||||
leftSegments.push({ value: change.value, changed: true });
|
||||
} else if (change.added) {
|
||||
rightSegments.push({ value: change.value, changed: true });
|
||||
} else {
|
||||
leftSegments.push({ value: change.value, changed: false });
|
||||
rightSegments.push({ value: change.value, changed: false });
|
||||
}
|
||||
}
|
||||
return { left: leftSegments, right: rightSegments };
|
||||
}
|
||||
|
||||
function pairChangedLines(leftLines: string[], rightLines: string[], leftStart: number, rightStart: number, rows: NacosSideBySideDiffRow[], nextId: () => string) {
|
||||
const max = Math.max(leftLines.length, rightLines.length);
|
||||
for (let index = 0; index < max; index += 1) {
|
||||
const left = leftLines[index];
|
||||
const right = rightLines[index];
|
||||
const hasLeft = left !== undefined;
|
||||
const hasRight = right !== undefined;
|
||||
const inline = hasLeft && hasRight ? inlineSegments(left, right) : { left: [], right: [] };
|
||||
rows.push({
|
||||
id: nextId(),
|
||||
leftLineNumber: hasLeft ? leftStart + index : null,
|
||||
rightLineNumber: hasRight ? rightStart + index : null,
|
||||
leftContent: left ?? "",
|
||||
rightContent: right ?? "",
|
||||
leftType: hasLeft ? (hasRight ? "modify" : "delete") : "padding",
|
||||
rightType: hasRight ? (hasLeft ? "modify" : "insert") : "padding",
|
||||
leftInline: inline.left,
|
||||
rightInline: inline.right,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export function buildNacosSideBySideDiff(before: string, after: string): NacosSideBySideDiffRow[] {
|
||||
const changes = diffLines(normalizeNacosDiffText(before), normalizeNacosDiffText(after), { newlineIsToken: false });
|
||||
const rows: NacosSideBySideDiffRow[] = [];
|
||||
let leftLineNumber = 1;
|
||||
let rightLineNumber = 1;
|
||||
let id = 0;
|
||||
const nextId = () => `nacos-diff-${id++}`;
|
||||
|
||||
for (let index = 0; index < changes.length; index += 1) {
|
||||
const change = changes[index];
|
||||
if (!change.added && !change.removed) {
|
||||
for (const line of splitDiffLines(change.value)) {
|
||||
rows.push({
|
||||
id: nextId(),
|
||||
leftLineNumber,
|
||||
rightLineNumber,
|
||||
leftContent: line,
|
||||
rightContent: line,
|
||||
leftType: "equal",
|
||||
rightType: "equal",
|
||||
leftInline: [{ value: line, changed: false }],
|
||||
rightInline: [{ value: line, changed: false }],
|
||||
});
|
||||
leftLineNumber += 1;
|
||||
rightLineNumber += 1;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (change.removed) {
|
||||
const leftLines = splitDiffLines(change.value);
|
||||
const next = changes[index + 1];
|
||||
if (next?.added) {
|
||||
const rightLines = splitDiffLines(next.value);
|
||||
pairChangedLines(leftLines, rightLines, leftLineNumber, rightLineNumber, rows, nextId);
|
||||
leftLineNumber += leftLines.length;
|
||||
rightLineNumber += rightLines.length;
|
||||
index += 1;
|
||||
} else {
|
||||
pairChangedLines(leftLines, [], leftLineNumber, rightLineNumber, rows, nextId);
|
||||
leftLineNumber += leftLines.length;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (change.added) {
|
||||
const rightLines = splitDiffLines(change.value);
|
||||
pairChangedLines([], rightLines, leftLineNumber, rightLineNumber, rows, nextId);
|
||||
rightLineNumber += rightLines.length;
|
||||
}
|
||||
}
|
||||
|
||||
return rows;
|
||||
}
|
||||
|
||||
function visibleInlineSegments(content: string, segments: NacosInlineSegment[]): NacosInlineSegment[] {
|
||||
return segments.length ? segments : [{ value: content, changed: false }];
|
||||
}
|
||||
|
||||
export function buildNacosInlineDiff(before: string, after: string): NacosInlineDiffRow[] {
|
||||
return buildNacosSideBySideDiff(before, after).flatMap((row) => {
|
||||
if (row.leftType === "equal" && row.rightType === "equal") {
|
||||
return [
|
||||
{
|
||||
id: `${row.id}-equal`,
|
||||
lineNumber: row.leftLineNumber,
|
||||
content: row.leftContent,
|
||||
type: "equal" as const,
|
||||
segments: visibleInlineSegments(row.leftContent, row.leftInline),
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
const rows: NacosInlineDiffRow[] = [];
|
||||
if (row.leftType === "delete" || row.leftType === "modify") {
|
||||
rows.push({
|
||||
id: `${row.id}-delete`,
|
||||
lineNumber: row.leftLineNumber,
|
||||
content: row.leftContent,
|
||||
type: "delete",
|
||||
segments: visibleInlineSegments(row.leftContent, row.leftInline),
|
||||
});
|
||||
}
|
||||
if (row.rightType === "insert" || row.rightType === "modify") {
|
||||
rows.push({
|
||||
id: `${row.id}-insert`,
|
||||
lineNumber: row.rightLineNumber,
|
||||
content: row.rightContent,
|
||||
type: "insert",
|
||||
segments: visibleInlineSegments(row.rightContent, row.rightInline),
|
||||
});
|
||||
}
|
||||
return rows;
|
||||
});
|
||||
}
|
||||
|
||||
export function buildNacosConfigDeleteConfirm(item: NacosConfigItem, fallbackNamespace = ""): string {
|
||||
return formatNacosConfigIdentity(item, fallbackNamespace);
|
||||
}
|
||||
|
||||
export function buildNacosConfigHistoryRollbackConfirm(item: NacosConfigHistoryItem, fallbackNamespace = ""): string {
|
||||
return [`namespace=${item.namespace || fallbackNamespace || "public"}`, `dataId=${item.dataId}`, `group=${item.group || "DEFAULT_GROUP"}`, item.lastModifiedTime ? `historyTime=${item.lastModifiedTime}` : "", item.operator ? `operator=${item.operator}` : ""].filter(Boolean).join("\n");
|
||||
}
|
||||
|
||||
export function buildNacosInstanceConfirm(service: NacosServiceInfo, instance: NacosInstanceInfo, patch: Partial<NacosInstanceInfo>, fallbackGroup = "", namespace = ""): string {
|
||||
const targetEnabled = patch.enabled ?? instance.enabled;
|
||||
const targetHealthy = patch.healthy ?? instance.healthy;
|
||||
return [
|
||||
`namespace=${namespace || "public"}`,
|
||||
`serviceName=${service.serviceName}`,
|
||||
`group=${instance.groupName || service.groupName || fallbackGroup || "DEFAULT_GROUP"}`,
|
||||
`instance=${instance.ip}:${instance.port}`,
|
||||
patch.enabled == null ? "" : `targetEnabled=${targetEnabled === false ? "false" : "true"}`,
|
||||
patch.healthy == null ? "" : `targetHealthy=${targetHealthy === false ? "false" : "true"}`,
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join("\n");
|
||||
}
|
||||
|
||||
export function buildNacosRawMutationConfirm(req: NacosRawRequest): string {
|
||||
return [`method=${req.method.toUpperCase()}`, `path=${req.path}`, req.query ? `query=${JSON.stringify(req.query)}` : ""].filter(Boolean).join("\n");
|
||||
}
|
||||
@@ -33,6 +33,8 @@ export interface SavedOpenTab {
|
||||
pinned?: boolean;
|
||||
mode?: QueryTab["mode"];
|
||||
mqTenant?: string;
|
||||
nacosNamespace?: string;
|
||||
nacosNamespaceName?: string;
|
||||
structureTableName?: string;
|
||||
objectBrowser?: QueryTab["objectBrowser"];
|
||||
objectSource?: QueryTab["objectSource"];
|
||||
@@ -72,6 +74,8 @@ export function serializeOpenTabs(tabs: QueryTab[]): SavedOpenTab[] {
|
||||
pinned: tab.pinned,
|
||||
mode: tab.mode,
|
||||
...(tab.mqTenant !== undefined ? { mqTenant: tab.mqTenant } : {}),
|
||||
...(tab.nacosNamespace !== undefined ? { nacosNamespace: tab.nacosNamespace } : {}),
|
||||
...(tab.nacosNamespaceName !== undefined ? { nacosNamespaceName: tab.nacosNamespaceName } : {}),
|
||||
...(tab.structureTableName !== undefined ? { structureTableName: tab.structureTableName } : {}),
|
||||
objectBrowser: tab.objectBrowser,
|
||||
objectSource: tab.objectSource,
|
||||
|
||||
@@ -29,6 +29,11 @@ export type ActiveTabSidebarTarget =
|
||||
connectionId: string;
|
||||
tenant: string;
|
||||
}
|
||||
| {
|
||||
type: "nacos-namespace";
|
||||
connectionId: string;
|
||||
namespace: string;
|
||||
}
|
||||
| {
|
||||
type: "query-context";
|
||||
connectionId: string;
|
||||
@@ -84,6 +89,10 @@ export function activeTabSidebarTarget(tab: QueryTab | undefined | null): Active
|
||||
return { type: "mq-tenant", connectionId: tab.connectionId, tenant: tab.mqTenant };
|
||||
}
|
||||
|
||||
if (tab.mode === "nacos") {
|
||||
return { type: "nacos-namespace", connectionId: tab.connectionId, namespace: tab.nacosNamespace || "" };
|
||||
}
|
||||
|
||||
if (tab.savedSqlId) {
|
||||
return { type: "saved-sql-file", savedSqlId: tab.savedSqlId };
|
||||
}
|
||||
@@ -133,6 +142,10 @@ export function matchesTarget(node: TreeNode, target: ActiveTabSidebarTarget): b
|
||||
return node.type === "mq-tenant" && node.connectionId === target.connectionId && (node.mqTenant || node.label) === target.tenant;
|
||||
}
|
||||
|
||||
if (target.type === "nacos-namespace") {
|
||||
return node.type === "nacos-namespace" && node.connectionId === target.connectionId && (node.nacosNamespace || "") === target.namespace;
|
||||
}
|
||||
|
||||
if (target.type === "saved-sql-file") {
|
||||
return node.type === "saved-sql-file" && node.savedSqlId === target.savedSqlId;
|
||||
}
|
||||
|
||||
@@ -181,6 +181,7 @@ export function tabModeLabel(tab: QueryTab, t: Translate): string {
|
||||
if (tab.mode === "vector") return t("tabs.vector");
|
||||
if (tab.mode === "redis") return t("tabs.redis");
|
||||
if (tab.mode === "etcd") return t("tabs.etcd");
|
||||
if (tab.mode === "nacos") return "Nacos";
|
||||
if (tab.mode === "objects") return t("tabs.objects");
|
||||
if (tab.mode === "users") return t("tabs.users");
|
||||
return tab.mode;
|
||||
|
||||
@@ -1843,3 +1843,4 @@ export async function exportQueryResultMarkdown(filePath: string, columns: strin
|
||||
}
|
||||
|
||||
export * from "./mq-tauri";
|
||||
export * from "./nacos-tauri";
|
||||
@@ -879,6 +879,8 @@ export const useConnectionStore = defineStore("connection", () => {
|
||||
await loadVectorCollections(connectionId);
|
||||
} else if (config.db_type === "mq") {
|
||||
await loadMqTenants(connectionId, { force: true });
|
||||
} else if (config.db_type === "nacos") {
|
||||
await loadNacosNamespaces(connectionId, { force: true });
|
||||
} else {
|
||||
await loadDatabases(connectionId, { force: true });
|
||||
}
|
||||
@@ -1228,6 +1230,47 @@ export const useConnectionStore = defineStore("connection", () => {
|
||||
}
|
||||
}
|
||||
|
||||
async function loadNacosNamespaces(connectionId: string, options?: LoadTreeOptions) {
|
||||
const node = findNode(treeNodes.value, connectionId);
|
||||
if (!node) return;
|
||||
|
||||
node.isLoading = true;
|
||||
try {
|
||||
await ensureConnected(connectionId);
|
||||
if (useCachedChildren(node, options)) return;
|
||||
|
||||
const namespaces = await api.nacosListNamespaces(connectionId);
|
||||
const sorted = [...namespaces].sort((left, right) => {
|
||||
const leftLabel = left.namespaceShowName || left.namespace || "public";
|
||||
const rightLabel = right.namespaceShowName || right.namespace || "public";
|
||||
return leftLabel.localeCompare(rightLabel);
|
||||
});
|
||||
setChildren(
|
||||
node,
|
||||
sorted.map((namespace) => {
|
||||
const value = namespace.namespace || "";
|
||||
const label = namespace.namespaceShowName || value || "public";
|
||||
return {
|
||||
id: schemaCacheKey(connectionId, "nacos-namespace", value || "public"),
|
||||
label,
|
||||
type: "nacos-namespace" as const,
|
||||
connectionId,
|
||||
nacosNamespace: value,
|
||||
nacosNamespaceName: label,
|
||||
comment: namespace.namespaceDesc || null,
|
||||
objectCount: namespace.configCount,
|
||||
};
|
||||
}),
|
||||
);
|
||||
node.isExpanded = true;
|
||||
} catch (e) {
|
||||
recordMetadataLoadError(connectionId, e);
|
||||
throw e;
|
||||
} finally {
|
||||
node.isLoading = false;
|
||||
}
|
||||
}
|
||||
|
||||
function updateRedisDbKeyStats(connectionId: string, db: number, stats: { loaded?: number; total?: number; totalDelta?: number }) {
|
||||
const node = findNode(treeNodes.value, `${connectionId}:db${db}`);
|
||||
if (!node || node.type !== "redis-db") return;
|
||||
@@ -1996,6 +2039,8 @@ export const useConnectionStore = defineStore("connection", () => {
|
||||
await loadVectorCollections(node.connectionId);
|
||||
} else if (config?.db_type === "mq") {
|
||||
await loadMqTenants(node.connectionId, options);
|
||||
} else if (config?.db_type === "nacos") {
|
||||
await loadNacosNamespaces(node.connectionId, options);
|
||||
} else {
|
||||
await loadDatabases(node.connectionId, options);
|
||||
}
|
||||
@@ -3266,6 +3311,7 @@ export const useConnectionStore = defineStore("connection", () => {
|
||||
refreshRedisDbKeyCounts,
|
||||
loadEtcdRoot,
|
||||
loadMqTenants,
|
||||
loadNacosNamespaces,
|
||||
updateRedisDbKeyStats,
|
||||
loadMongoDatabases,
|
||||
loadElasticsearchIndices,
|
||||
|
||||
@@ -603,6 +603,37 @@ export const useQueryStore = defineStore("query", () => {
|
||||
return id;
|
||||
}
|
||||
|
||||
function openNacosAdmin(connectionId: string, target?: { namespace?: string; namespaceName?: string }) {
|
||||
const namespace = target?.namespace ?? "";
|
||||
const namespaceName = target?.namespaceName || (namespace ? namespace : "public");
|
||||
const existing = tabs.value.find((tab) => tab.mode === "nacos" && tab.connectionId === connectionId && (tab.nacosNamespace || "") === namespace);
|
||||
if (existing) {
|
||||
existing.nacosNamespaceName = namespaceName;
|
||||
if (!existing.customTitle) existing.title = `${useConnectionStore().getConfig(connectionId)?.name || "Nacos"}:${namespaceName}`;
|
||||
activeTabId.value = existing.id;
|
||||
return existing.id;
|
||||
}
|
||||
|
||||
const conn = useConnectionStore().getConfig(connectionId);
|
||||
const id = uuid();
|
||||
const tab: QueryTab = {
|
||||
id,
|
||||
title: `${conn?.name || "Nacos"}:${namespaceName}`,
|
||||
connectionId,
|
||||
database: conn?.database || "",
|
||||
sql: "",
|
||||
isExecuting: false,
|
||||
isCancelling: false,
|
||||
isExplaining: false,
|
||||
mode: "nacos",
|
||||
nacosNamespace: namespace,
|
||||
nacosNamespaceName: namespaceName,
|
||||
};
|
||||
tabs.value.push(tab);
|
||||
activeTabId.value = id;
|
||||
return id;
|
||||
}
|
||||
|
||||
function openTableStructure(connectionId: string, database: string, schema?: string, tableName?: string) {
|
||||
const resolvedTableName = tableName || "";
|
||||
if (resolvedTableName) {
|
||||
@@ -769,6 +800,8 @@ export const useQueryStore = defineStore("query", () => {
|
||||
explainExecutionId: undefined,
|
||||
mode: original.mode,
|
||||
mqTenant: original.mqTenant,
|
||||
nacosNamespace: original.nacosNamespace,
|
||||
nacosNamespaceName: original.nacosNamespaceName,
|
||||
structureTableName: original.structureTableName,
|
||||
objectBrowser: original.objectBrowser ? { ...original.objectBrowser } : undefined,
|
||||
objectSource: original.objectSource ? { ...original.objectSource } : undefined,
|
||||
@@ -2189,6 +2222,7 @@ export const useQueryStore = defineStore("query", () => {
|
||||
openObjectBrowser,
|
||||
openUserAdmin,
|
||||
openMqAdmin,
|
||||
openNacosAdmin,
|
||||
openTableStructure,
|
||||
linkSavedSql,
|
||||
openSavedSql,
|
||||
|
||||
@@ -56,7 +56,8 @@ export type DatabaseType =
|
||||
| "iris"
|
||||
| "influxdb"
|
||||
| "jdbc"
|
||||
| "mq";
|
||||
| "mq"
|
||||
| "nacos";
|
||||
|
||||
export interface SqlSnippet {
|
||||
id: string;
|
||||
@@ -484,6 +485,7 @@ export type TreeNodeType =
|
||||
| "trigger"
|
||||
| "redis-db"
|
||||
| "mq-tenant"
|
||||
| "nacos-namespace"
|
||||
| "etcd-root"
|
||||
| "mongo-db"
|
||||
| "mongo-collection"
|
||||
@@ -517,6 +519,8 @@ export interface TreeNode {
|
||||
linkedCatalog?: string;
|
||||
linkedSchema?: string;
|
||||
mqTenant?: string;
|
||||
nacosNamespace?: string;
|
||||
nacosNamespaceName?: string;
|
||||
schema?: string;
|
||||
tableName?: string;
|
||||
tableType?: string;
|
||||
@@ -591,8 +595,10 @@ export interface QueryTab {
|
||||
executionId?: string;
|
||||
isExplaining?: boolean;
|
||||
explainExecutionId?: string;
|
||||
mode: "data" | "query" | "redis" | "mongo" | "vector" | "etcd" | "mq" | "objects" | "structure" | "users";
|
||||
mode: "data" | "query" | "redis" | "mongo" | "vector" | "etcd" | "mq" | "nacos" | "objects" | "structure" | "users";
|
||||
mqTenant?: string;
|
||||
nacosNamespace?: string;
|
||||
nacosNamespaceName?: string;
|
||||
structureTableName?: string;
|
||||
objectBrowser?: {
|
||||
schema?: string;
|
||||
|
||||
@@ -0,0 +1,200 @@
|
||||
export interface NacosCapabilities {
|
||||
supportsConfigManagement: boolean;
|
||||
supportsConfigHistory?: boolean;
|
||||
supportsServiceManagement: boolean;
|
||||
supportsInstanceUpdate: boolean;
|
||||
supportsRawApi: boolean;
|
||||
}
|
||||
|
||||
export interface NacosConnectionInfo {
|
||||
serverAddr: string;
|
||||
namespace: string;
|
||||
serverVersion?: string;
|
||||
auth: string;
|
||||
capabilities: NacosCapabilities;
|
||||
raw?: unknown;
|
||||
}
|
||||
|
||||
export interface NacosNamespaceInfo {
|
||||
namespace: string;
|
||||
namespaceShowName: string;
|
||||
namespaceDesc?: string;
|
||||
configCount?: number;
|
||||
quota?: number;
|
||||
namespaceType?: number;
|
||||
}
|
||||
|
||||
export interface NacosNamespaceCreate {
|
||||
namespaceId?: string;
|
||||
namespaceName: string;
|
||||
namespaceDesc?: string;
|
||||
}
|
||||
|
||||
export interface NacosNamespaceUpdate {
|
||||
namespaceId: string;
|
||||
namespaceName: string;
|
||||
namespaceDesc?: string;
|
||||
}
|
||||
|
||||
export interface NacosAuthConfig {
|
||||
kind: "none" | "usernamePassword";
|
||||
username?: string;
|
||||
password?: string;
|
||||
}
|
||||
|
||||
export interface NacosAdminConfig {
|
||||
serverAddr: string;
|
||||
namespace?: string;
|
||||
contextPath?: string;
|
||||
auth?: NacosAuthConfig;
|
||||
tlsSkipVerify?: boolean;
|
||||
pageSize?: number;
|
||||
}
|
||||
|
||||
export interface NacosConfigQuery {
|
||||
namespace?: string;
|
||||
group?: string;
|
||||
dataId?: string;
|
||||
search?: string;
|
||||
pageNo?: number;
|
||||
pageSize?: number;
|
||||
}
|
||||
|
||||
export interface NacosConfigItem {
|
||||
dataId: string;
|
||||
group: string;
|
||||
namespace: string;
|
||||
appName?: string;
|
||||
desc?: string;
|
||||
tags?: string;
|
||||
configType?: string;
|
||||
md5?: string;
|
||||
encryptedDataKey?: string;
|
||||
content?: string;
|
||||
}
|
||||
|
||||
export interface NacosConfigList {
|
||||
pageNo: number;
|
||||
pageSize: number;
|
||||
totalCount: number;
|
||||
items: NacosConfigItem[];
|
||||
}
|
||||
|
||||
export interface NacosConfigKey {
|
||||
namespace?: string;
|
||||
dataId: string;
|
||||
group: string;
|
||||
}
|
||||
|
||||
export interface NacosConfigUpsert extends NacosConfigKey {
|
||||
content: string;
|
||||
configType?: string;
|
||||
appName?: string;
|
||||
desc?: string;
|
||||
tags?: string;
|
||||
}
|
||||
|
||||
export interface NacosConfigHistoryQuery extends NacosConfigKey {
|
||||
pageNo?: number;
|
||||
pageSize?: number;
|
||||
}
|
||||
|
||||
export interface NacosConfigHistoryItem {
|
||||
historyId: string;
|
||||
nid?: number;
|
||||
dataId: string;
|
||||
group: string;
|
||||
namespace: string;
|
||||
appName?: string;
|
||||
operation?: string;
|
||||
operator?: string;
|
||||
lastModifiedTime?: string;
|
||||
configType?: string;
|
||||
tags?: string;
|
||||
md5?: string;
|
||||
}
|
||||
|
||||
export interface NacosConfigHistoryList {
|
||||
pageNo: number;
|
||||
pageSize: number;
|
||||
totalCount: number;
|
||||
items: NacosConfigHistoryItem[];
|
||||
}
|
||||
|
||||
export interface NacosConfigHistoryKey extends NacosConfigKey {
|
||||
historyId: string;
|
||||
nid?: number;
|
||||
}
|
||||
|
||||
export interface NacosConfigRollbackRequest extends NacosConfigHistoryKey {}
|
||||
|
||||
export interface NacosServiceQuery {
|
||||
namespace?: string;
|
||||
groupName?: string;
|
||||
serviceName?: string;
|
||||
pageNo?: number;
|
||||
pageSize?: number;
|
||||
}
|
||||
|
||||
export interface NacosServiceInfo {
|
||||
serviceName: string;
|
||||
groupName?: string;
|
||||
clusterCount?: number;
|
||||
ipCount?: number;
|
||||
healthyInstanceCount?: number;
|
||||
triggerFlag?: string;
|
||||
}
|
||||
|
||||
export interface NacosServiceList {
|
||||
pageNo: number;
|
||||
pageSize: number;
|
||||
totalCount: number;
|
||||
items: NacosServiceInfo[];
|
||||
}
|
||||
|
||||
export interface NacosInstanceQuery {
|
||||
namespace?: string;
|
||||
serviceName: string;
|
||||
groupName?: string;
|
||||
clusters?: string;
|
||||
}
|
||||
|
||||
export interface NacosInstanceInfo {
|
||||
ip: string;
|
||||
port: number;
|
||||
serviceName?: string;
|
||||
clusterName?: string;
|
||||
groupName?: string;
|
||||
healthy?: boolean;
|
||||
enabled?: boolean;
|
||||
ephemeral?: boolean;
|
||||
weight?: number;
|
||||
metadata?: unknown;
|
||||
}
|
||||
|
||||
export interface NacosInstanceUpdate {
|
||||
namespace?: string;
|
||||
serviceName: string;
|
||||
ip: string;
|
||||
port: number;
|
||||
groupName?: string;
|
||||
clusterName?: string;
|
||||
healthy?: boolean;
|
||||
enabled?: boolean;
|
||||
ephemeral?: boolean;
|
||||
weight?: number;
|
||||
metadata?: unknown;
|
||||
}
|
||||
|
||||
export interface NacosRawRequest {
|
||||
method: string;
|
||||
path: string;
|
||||
query?: Record<string, string>;
|
||||
body?: unknown;
|
||||
}
|
||||
|
||||
export interface NacosRawResponse {
|
||||
status: number;
|
||||
body: unknown;
|
||||
text?: string;
|
||||
}
|
||||
Reference in new issue
Block a user