✨feat(nacos): 增加应用名称支持,优化配置查询功能

- 新增应用名称字段到配置查询接口
- 更新前端界面以支持应用名称输入
- 调整后端逻辑以处理应用名称参数
This commit is contained in:
二丫讲梵 authored and GitHub committed 2026-06-27 12:14:29 +08:00
1 parent c0bd9313d7
commit 4d435e2aab
4 files changed
+40 -14

No files matched your search

@@ -49,6 +49,7 @@ const configLoading = ref(false);
const configError = ref(""); const configError = ref("");
const configGroup = ref(""); const configGroup = ref("");
const configDataId = ref(""); const configDataId = ref("");
const configAppName = ref("");
const configPageNo = ref(1); const configPageNo = ref(1);
const configPageSize = ref(20); const configPageSize = ref(20);
const configs = ref<NacosConfigItem[]>([]); const configs = ref<NacosConfigItem[]>([]);
@@ -370,6 +371,7 @@ async function loadConfigs(page = configPageNo.value) {
namespace: namespace.value || undefined, namespace: namespace.value || undefined,
group: configGroup.value.trim() || undefined, group: configGroup.value.trim() || undefined,
dataId: configDataId.value.trim() || undefined, dataId: configDataId.value.trim() || undefined,
appName: configAppName.value.trim() || undefined,
pageNo: configPageNo.value, pageNo: configPageNo.value,
pageSize: configPageSize.value, pageSize: configPageSize.value,
}); });
@@ -881,9 +883,10 @@ onBeforeUnmount(() => {
<Splitpanes v-if="activeTab === 'configs'" class="nacos-admin-splitpanes min-h-0 flex-1" @resized="handleNacosSplitResized"> <Splitpanes v-if="activeTab === 'configs'" class="nacos-admin-splitpanes min-h-0 flex-1" @resized="handleNacosSplitResized">
<Pane :size="nacosSplitSize" min-size="24"> <Pane :size="nacosSplitSize" min-size="24">
<div class="flex h-full min-h-0 flex-col"> <div class="flex h-full min-h-0 flex-col">
<div class="grid shrink-0 grid-cols-[minmax(0,1fr)_minmax(0,1fr)_auto_auto] gap-2 border-b p-2"> <div class="grid shrink-0 grid-cols-[minmax(0,1fr)_minmax(0,1fr)_minmax(0,1fr)_auto_auto] gap-2 border-b p-2">
<Input v-model="configDataId" class="h-8 min-w-0" placeholder="dataId" @keyup.enter="loadConfigsWithRetry(1)" /> <Input v-model="configDataId" class="h-8 min-w-0" placeholder="dataId" @keyup.enter="loadConfigsWithRetry(1)" />
<Input v-model="configGroup" class="h-8 min-w-0" :placeholder="t('nacos.allGroups')" @keyup.enter="loadConfigsWithRetry(1)" /> <Input v-model="configGroup" class="h-8 min-w-0" :placeholder="t('nacos.allGroups')" @keyup.enter="loadConfigsWithRetry(1)" />
<Input v-model="configAppName" class="h-8 min-w-0" :placeholder="t('nacos.application')" @keyup.enter="loadConfigsWithRetry(1)" />
<Button size="sm" variant="outline" class="h-8 w-9 px-0" :title="t('nacos.load')" :disabled="configLoading" @click="loadConfigsWithRetry(1)"> <Button size="sm" variant="outline" class="h-8 w-9 px-0" :title="t('nacos.load')" :disabled="configLoading" @click="loadConfigsWithRetry(1)">
<Loader2 v-if="configLoading" class="h-3.5 w-3.5 animate-spin" /> <Loader2 v-if="configLoading" class="h-3.5 w-3.5 animate-spin" />
<RefreshCw v-else class="h-3.5 w-3.5" /> <RefreshCw v-else class="h-3.5 w-3.5" />
@@ -894,16 +897,17 @@ onBeforeUnmount(() => {
</div> </div>
<div v-if="configError" class="border-b px-3 py-2 text-xs text-destructive">{{ configError }}</div> <div v-if="configError" class="border-b px-3 py-2 text-xs text-destructive">{{ configError }}</div>
<div class="min-h-0 flex-1 overflow-auto"> <div class="min-h-0 flex-1 overflow-auto">
<div class="sticky top-0 z-10 grid grid-cols-[minmax(0,1fr)_128px_84px] border-b bg-muted/70 px-3 py-2 text-[11px] font-medium uppercase tracking-wide text-muted-foreground"> <div class="sticky top-0 z-20 grid grid-cols-4 border-b bg-muted px-3 py-2 text-[11px] font-medium uppercase tracking-wide text-muted-foreground shadow-sm">
<span>dataID</span> <span class="truncate pr-3">dataID</span>
<span>{{ t("nacos.group") }}</span> <span class="truncate pr-3">{{ t("nacos.group") }}</span>
<span>{{ t("nacos.format") }}</span> <span class="truncate pr-3">{{ t("nacos.application") }}</span>
<span class="truncate">{{ t("nacos.format") }}</span>
</div> </div>
<button <button
v-for="item in configs" v-for="item in configs"
:key="`${item.namespace}:${item.group}:${item.dataId}`" :key="`${item.namespace}:${item.group}:${item.dataId}`"
type="button" type="button"
class="grid w-full grid-cols-[minmax(0,1fr)_128px_84px] items-center border-b px-3 py-2.5 text-left text-sm transition-colors hover:bg-accent/50" class="grid w-full grid-cols-4 items-center border-b px-3 py-2.5 text-left text-sm transition-colors hover:bg-accent/50"
:class="{ 'bg-accent': selectedConfig?.dataId === item.dataId && selectedConfig?.group === item.group }" :class="{ 'bg-accent': selectedConfig?.dataId === item.dataId && selectedConfig?.group === item.group }"
@click="selectConfig(item)" @click="selectConfig(item)"
> >
@@ -912,6 +916,7 @@ onBeforeUnmount(() => {
<span class="truncate font-medium text-foreground">{{ item.dataId }}</span> <span class="truncate font-medium text-foreground">{{ item.dataId }}</span>
</span> </span>
<span class="truncate pr-3 text-xs text-muted-foreground" :title="item.group || 'DEFAULT_GROUP'">{{ item.group || "DEFAULT_GROUP" }}</span> <span class="truncate pr-3 text-xs text-muted-foreground" :title="item.group || 'DEFAULT_GROUP'">{{ item.group || "DEFAULT_GROUP" }}</span>
<span class="truncate pr-3 text-xs text-muted-foreground" :title="item.appName || '-'">{{ item.appName || "-" }}</span>
<span class="truncate text-xs text-muted-foreground" :title="configFormatLabel(item)">{{ configFormatLabel(item) }}</span> <span class="truncate text-xs text-muted-foreground" :title="configFormatLabel(item)">{{ configFormatLabel(item) }}</span>
</button> </button>
<div v-if="!configLoading && configs.length === 0" class="flex h-full items-center justify-center text-sm text-muted-foreground">{{ t("nacos.noConfigs") }}</div> <div v-if="!configLoading && configs.length === 0" class="flex h-full items-center justify-center text-sm text-muted-foreground">{{ t("nacos.noConfigs") }}</div>
+1
View File
@@ -56,6 +56,7 @@ export interface NacosConfigQuery {
namespace?: string; namespace?: string;
group?: string; group?: string;
dataId?: string; dataId?: string;
appName?: string;
search?: string; search?: string;
pageNo?: number; pageNo?: number;
pageSize?: number; pageSize?: number;
+26 -8
View File
@@ -198,10 +198,11 @@ impl NacosOpenApiAdmin {
namespace: &str, namespace: &str,
search: &str, search: &str,
group: &str, group: &str,
app_name: &str,
page_no: u32, page_no: u32,
page_size: u32, page_size: u32,
) -> Result<Value, String> { ) -> Result<Value, String> {
let v3_params = vec![ let mut v3_params = vec![
("search".to_string(), "blur".to_string()), ("search".to_string(), "blur".to_string()),
("dataId".to_string(), search.to_string()), ("dataId".to_string(), search.to_string()),
("groupName".to_string(), group.to_string()), ("groupName".to_string(), group.to_string()),
@@ -209,10 +210,11 @@ impl NacosOpenApiAdmin {
("pageNo".to_string(), page_no.to_string()), ("pageNo".to_string(), page_no.to_string()),
("pageSize".to_string(), page_size.to_string()), ("pageSize".to_string(), page_size.to_string()),
]; ];
push_optional(&mut v3_params, "appName", Some(app_name.to_string()));
match self.get_json("/v3/console/cs/config/list", v3_params).await { match self.get_json("/v3/console/cs/config/list", v3_params).await {
Ok(value) => Ok(value), Ok(value) => Ok(value),
Err(v3_err) => { Err(v3_err) => {
let v1_params = vec![ let mut v1_params = vec![
("search".to_string(), "blur".to_string()), ("search".to_string(), "blur".to_string()),
("dataId".to_string(), search.to_string()), ("dataId".to_string(), search.to_string()),
("group".to_string(), group.to_string()), ("group".to_string(), group.to_string()),
@@ -220,6 +222,7 @@ impl NacosOpenApiAdmin {
("pageNo".to_string(), page_no.to_string()), ("pageNo".to_string(), page_no.to_string()),
("pageSize".to_string(), page_size.to_string()), ("pageSize".to_string(), page_size.to_string()),
]; ];
push_optional(&mut v1_params, "appName", Some(app_name.to_string()));
self.get_json("/v1/cs/configs", v1_params).await.map_err(|v1_err| { self.get_json("/v1/cs/configs", v1_params).await.map_err(|v1_err| {
format!("Failed to list Nacos configs with v3 and v1 APIs. v3: {v3_err}; v1: {v1_err}") format!("Failed to list Nacos configs with v3 and v1 APIs. v3: {v3_err}; v1: {v1_err}")
}) })
@@ -285,6 +288,7 @@ impl NacosOpenApiAdmin {
namespace: String, namespace: String,
group: Option<String>, group: Option<String>,
data_id_filter: Option<String>, data_id_filter: Option<String>,
app_name_filter: Option<String>,
page_no: u32, page_no: u32,
page_size: u32, page_size: u32,
) -> Result<NacosConfigList, String> { ) -> Result<NacosConfigList, String> {
@@ -292,13 +296,15 @@ impl NacosOpenApiAdmin {
return Ok(NacosConfigList { page_no, page_size, total_count: 0, items: Vec::new() }); return Ok(NacosConfigList { page_no, page_size, total_count: 0, items: Vec::new() });
}; };
let group = group.unwrap_or_default(); let group = group.unwrap_or_default();
let app_name = app_name_filter.unwrap_or_default();
let scan_page_size = page_size.max(self.cfg.page_size).clamp(100, 500); let scan_page_size = page_size.max(self.cfg.page_size).clamp(100, 500);
let max_scan_pages = 10; let max_scan_pages = 10;
let mut matched = Vec::new(); let mut matched = Vec::new();
let mut current_page = 1; let mut current_page = 1;
while current_page <= max_scan_pages { while current_page <= max_scan_pages {
let value = self.get_config_list_value(&namespace, "", &group, current_page, scan_page_size).await?; let value =
self.get_config_list_value(&namespace, "", &group, &app_name, current_page, scan_page_size).await?;
let list = parse_config_list(value, namespace.clone(), current_page, scan_page_size); let list = parse_config_list(value, namespace.clone(), current_page, scan_page_size);
matched.extend(list.items.into_iter().filter(|item| item.data_id.to_lowercase().contains(&filter))); matched.extend(list.items.into_iter().filter(|item| item.data_id.to_lowercase().contains(&filter)));
@@ -459,12 +465,22 @@ impl NacosAdmin for NacosOpenApiAdmin {
let search = data_id_filter.clone().unwrap_or_default(); let search = data_id_filter.clone().unwrap_or_default();
let group_filter = query.group.clone(); let group_filter = query.group.clone();
let group = group_filter.clone().unwrap_or_default(); let group = group_filter.clone().unwrap_or_default();
let value = self.get_config_list_value(&namespace, &search, &group, page_no, page_size).await?; let app_name_filter = query.app_name.map(|value| value.trim().to_string()).filter(|value| !value.is_empty());
let app_name = app_name_filter.clone().unwrap_or_default();
let value = self.get_config_list_value(&namespace, &search, &group, &app_name, page_no, page_size).await?;
let parsed = let parsed =
self.enrich_missing_config_formats(parse_config_list(value, namespace.clone(), page_no, page_size)).await; self.enrich_missing_config_formats(parse_config_list(value, namespace.clone(), page_no, page_size)).await;
if data_id_filter.is_some() && parsed.items.is_empty() { if data_id_filter.is_some() && parsed.items.is_empty() {
let fallback = let fallback = self
self.list_configs_by_client_filter(namespace, group_filter, data_id_filter, page_no, page_size).await?; .list_configs_by_client_filter(
namespace,
group_filter,
data_id_filter,
app_name_filter,
page_no,
page_size,
)
.await?;
if !fallback.items.is_empty() { if !fallback.items.is_empty() {
return Ok(fallback); return Ok(fallback);
} }
@@ -1395,7 +1411,7 @@ mod tests {
let parsed = parse_config_list( let parsed = parse_config_list(
serde_json::json!({ serde_json::json!({
"totalCount": 1, "totalCount": 1,
"pageItems": [{ "dataId": "app.yaml", "group": "DEFAULT_GROUP", "type": "yaml" }] "pageItems": [{ "dataId": "app.yaml", "group": "DEFAULT_GROUP", "type": "yaml", "appName": "portal" }]
}), }),
"public".to_string(), "public".to_string(),
1, 1,
@@ -1404,6 +1420,7 @@ mod tests {
assert_eq!(parsed.total_count, 1); assert_eq!(parsed.total_count, 1);
assert_eq!(parsed.items[0].data_id, "app.yaml"); assert_eq!(parsed.items[0].data_id, "app.yaml");
assert_eq!(parsed.items[0].namespace, "public"); assert_eq!(parsed.items[0].namespace, "public");
assert_eq!(parsed.items[0].app_name.as_deref(), Some("portal"));
assert_eq!(parsed.items[0].config_type.as_deref(), Some("yaml")); assert_eq!(parsed.items[0].config_type.as_deref(), Some("yaml"));
} }
@@ -1451,7 +1468,7 @@ mod tests {
"data": { "data": {
"totalCount": 1, "totalCount": 1,
"pageItems": [ "pageItems": [
{ "dataId": "app.json", "groupName": "DEFAULT_GROUP", "namespaceId": "public" } { "dataId": "app.json", "groupName": "DEFAULT_GROUP", "namespaceId": "public", "appName": "console" }
] ]
} }
}), }),
@@ -1461,6 +1478,7 @@ mod tests {
); );
assert_eq!(parsed.total_count, 1); assert_eq!(parsed.total_count, 1);
assert_eq!(parsed.items[0].group, "DEFAULT_GROUP"); assert_eq!(parsed.items[0].group, "DEFAULT_GROUP");
assert_eq!(parsed.items[0].app_name.as_deref(), Some("console"));
assert_eq!(parsed.items[0].config_type.as_deref(), Some("json")); assert_eq!(parsed.items[0].config_type.as_deref(), Some("json"));
} }
+2
View File
@@ -79,6 +79,8 @@ pub struct NacosConfigQuery {
#[serde(default)] #[serde(default)]
pub data_id: Option<String>, pub data_id: Option<String>,
#[serde(default)] #[serde(default)]
pub app_name: Option<String>,
#[serde(default)]
pub search: Option<String>, pub search: Option<String>,
#[serde(default)] #[serde(default)]
pub page_no: Option<u32>, pub page_no: Option<u32>,