diff --git a/docs/content.en/docs/release-notes/_index.md b/docs/content.en/docs/release-notes/_index.md
index d5564ca5..537ceabe 100644
--- a/docs/content.en/docs/release-notes/_index.md
+++ b/docs/content.en/docs/release-notes/_index.md
@@ -19,6 +19,9 @@ Information about release notes of INFINI Console is provided here.
## 1.28.1 (2025-01-24)
+- add credential settings for agent in enrolling agent
+- add collection mode to cluster editing
+
### Features
- Support function-format parameters in Insight Data API
diff --git a/docs/content.zh/docs/release-notes/_index.md b/docs/content.zh/docs/release-notes/_index.md
index 001b43f5..8cf2b781 100644
--- a/docs/content.zh/docs/release-notes/_index.md
+++ b/docs/content.zh/docs/release-notes/_index.md
@@ -17,6 +17,9 @@ title: "版本历史"
### Improvements
+- 在注册 Agent 中新增 Agent 凭据设置
+- 在集群编辑中新增采集模式
+
## 1.28.1 (2025-01-24)
### Features
diff --git a/web/src/components/Overview/Monitor/index.jsx b/web/src/components/Overview/Monitor/index.jsx
index 6aa58222..c34b6af5 100644
--- a/web/src/components/Overview/Monitor/index.jsx
+++ b/web/src/components/Overview/Monitor/index.jsx
@@ -151,8 +151,11 @@ const Monitor = (props) => {
const breadcrumbList = getBreadcrumbList(state);
const isAgent = useMemo(() => {
- const { monitor_configs = {} } = selectedCluster || {}
- return monitor_configs?.node_stats?.enabled === false && monitor_configs?.index_stats?.enabled === false
+ const { metric_collection_mode, monitor_configs = {} } = selectedCluster || {}
+ if (typeof metric_collection_mode === 'undefined') {
+ return monitor_configs?.node_stats?.enabled === false && monitor_configs?.index_stats?.enabled === false
+ }
+ return metric_collection_mode === 'agent'
}, [JSON.stringify(selectedCluster?.monitor_configs)])
return (
diff --git a/web/src/locales/en-US/agent.js b/web/src/locales/en-US/agent.js
index a5d7da42..3f1f9472 100644
--- a/web/src/locales/en-US/agent.js
+++ b/web/src/locales/en-US/agent.js
@@ -6,7 +6,12 @@ export default {
"agent.instance.associate.labels.select_cluster": "Select Cluster",
"agent.instance.associate.tips.associate":
"Please select cluster(s) to enroll !",
+ "agent.instance.associate.set_credential": "Set credential for agent",
+ "agent.instance.associate.set_credential.tips":
+ "This permission will be used for metrics and log collection. It is recommended to use a user with a reasonable permission range.",
"agent.instance.associate.tips.connected": "Connection succeeded!",
+ "agent.instance.associate.tips.connected.check": "please set a credential for agent",
+ "agent.instance.associate.auth.error": "The following clusters need to set credentials for the agent:",
"agent.instance.associate.tips.metric":
"After enroll, the agent will collect metrics for the enrolled cluster",
"agent.instance.associate.tips.unregister":
@@ -35,4 +40,7 @@ export default {
"agent.install.setup.copy.success": "Copied to clipboard successfully!",
"agent.instance.auto_associate.title": "Auto Enroll",
"agent.instance.install.title": "Install Agent",
+
+ "agent.label.agent_credential": "Agent Credential",
+ "agent.credential.tip": "No credential required",
};
diff --git a/web/src/locales/en-US/alert.js b/web/src/locales/en-US/alert.js
index a5fba7c4..95d2e0cf 100644
--- a/web/src/locales/en-US/alert.js
+++ b/web/src/locales/en-US/alert.js
@@ -207,6 +207,8 @@ export default {
"alert.rule.table.columnns.schedule": "Schedule",
"alert.rule.table.columnns.expression": "Expression",
"alert.rule.table.columnns.status": "Status",
+ "alert.rule.table.columnns.status.failed": "Connect failed",
+ "alert.rule.table.columnns.status.succeeded": "Connect succeeded",
"alert.rule.table.columnns.enabled": "Enabled",
"alert.rule.table.columnns.updated": "Updated time",
"alert.rule.table.columnns.category": "Category",
diff --git a/web/src/locales/en-US/cluster.js b/web/src/locales/en-US/cluster.js
index 41173891..a115bb6e 100644
--- a/web/src/locales/en-US/cluster.js
+++ b/web/src/locales/en-US/cluster.js
@@ -35,6 +35,7 @@ export default {
"cluster.manage.table.column.location": "Location",
"cluster.manage.monitored.on": "ON",
"cluster.manage.monitored.off": "OFF",
+ "cluster.manage.metric_collection_mode": "Collect Mode",
"cluster.manage.monitor_configs.cluster_health": "Cluster health",
"cluster.manage.monitor_configs.cluster_stats": "Cluster stats",
"cluster.manage.monitor_configs.node_stats": "Node stats",
diff --git a/web/src/locales/zh-CN/agent.js b/web/src/locales/zh-CN/agent.js
index 7b66f5d4..a3693b5e 100644
--- a/web/src/locales/zh-CN/agent.js
+++ b/web/src/locales/zh-CN/agent.js
@@ -5,7 +5,11 @@ export default {
"agent.instance.associate.labels.cluster_version": "版本",
"agent.instance.associate.labels.select_cluster": "关联到集群",
"agent.instance.associate.tips.associate": "请选择要关联的集群!",
+ "agent.instance.associate.set_credential": "为代理设置凭据",
+ "agent.instance.associate.set_credential.tips": "此权限将用于度量和日志收集。建议使用具有合理权限范围的用户。",
"agent.instance.associate.tips.connected": "连接成功!",
+ "agent.instance.associate.tips.connected.check": "请设置凭据",
+ "agent.instance.associate.auth.error": "以下集群需要为 Agent 设置凭据:",
"agent.instance.associate.tips.metric":
"关联后 Agent 会对关联的集群进行指标采集操作",
"agent.instance.associate.tips.unregister":
@@ -33,4 +37,7 @@ export default {
"agent.install.setup.copy.success": "已成功复制到剪贴板!",
"agent.instance.auto_associate.title": "自动关联集群",
"agent.instance.install.title": "安装 Agent",
+
+ "agent.label.agent_credential": "代理凭据",
+ "agent.credential.tip": "不需要凭据",
};
diff --git a/web/src/locales/zh-CN/alert.js b/web/src/locales/zh-CN/alert.js
index 027c5a7d..dcffd4d5 100644
--- a/web/src/locales/zh-CN/alert.js
+++ b/web/src/locales/zh-CN/alert.js
@@ -195,6 +195,8 @@ export default {
"alert.rule.table.columnns.schedule": "计划周期",
"alert.rule.table.columnns.expression": "告警规则",
"alert.rule.table.columnns.status": "运行状态",
+ "alert.rule.table.columnns.status.failed": "连接失败",
+ "alert.rule.table.columnns.status.succeeded": "连接成功",
"alert.rule.table.columnns.enabled": "告警启停",
"alert.rule.table.columnns.updated": "更新时间",
"alert.rule.table.columnns.category": "分类",
diff --git a/web/src/locales/zh-CN/cluster.js b/web/src/locales/zh-CN/cluster.js
index 63865abb..a4f4b249 100644
--- a/web/src/locales/zh-CN/cluster.js
+++ b/web/src/locales/zh-CN/cluster.js
@@ -35,6 +35,7 @@ export default {
"cluster.manage.table.column.location": "位置",
"cluster.manage.monitored.on": "启用",
"cluster.manage.monitored.off": "关闭",
+ "cluster.manage.metric_collection_mode": "采集模式",
"cluster.manage.monitor_configs.cluster_health": "集群健康状态指标",
"cluster.manage.monitor_configs.cluster_stats": "集群指标",
"cluster.manage.monitor_configs.node_stats": "节点指标",
diff --git a/web/src/pages/Agent/Instance/components/AgentCredential.jsx b/web/src/pages/Agent/Instance/components/AgentCredential.jsx
new file mode 100644
index 00000000..71ab3b1a
--- /dev/null
+++ b/web/src/pages/Agent/Instance/components/AgentCredential.jsx
@@ -0,0 +1,117 @@
+import { Alert, Button, Form, message } from "antd";
+import { useState } from "react";
+import { formatMessage } from "umi/locale";
+
+import request from "@/utils/request";
+import AgentCredentialForm, { MANUAL_VALUE } from "./AgentCredentialForm";
+import { ESPrefix } from "@/services/common";
+
+const formItemLayout = {
+ labelCol: {
+ xs: { span: 24 },
+ sm: { span: 5 },
+ },
+ wrapperCol: {
+ xs: { span: 24 },
+ sm: { span: 18 },
+ },
+};
+
+export default Form.create()((props) => {
+ const { form, record, loading, tryConnect, onAgentCredentialSave } = props;
+
+ const [isManual, setIsManual] = useState(false);
+ const [saveLoading, setSaveLoading] = useState(false);
+
+ const needAuth = !!(record.credential_id || record.basic_auth?.username);
+
+ const onConfirm = async () => {
+ form.validateFields(async (errors, values) => {
+ if (errors) return;
+ setSaveLoading(true);
+ const { credential_id, basic_auth, metric_collection_mode } = record;
+ const res = await request(`${ESPrefix}/${record.id}`, {
+ method: "PUT",
+ body: {
+ credential_id,
+ basic_auth,
+ metric_collection_mode,
+ agent_credential_id:
+ values.agent_credential_id !== MANUAL_VALUE
+ ? values.agent_credential_id
+ : undefined,
+ agent_basic_auth: {
+ username: values.agent_username,
+ password: values.agent_password,
+ },
+ },
+ });
+ if (res?.result === "updated") {
+ message.success(
+ formatMessage({
+ id: "app.message.update.success",
+ })
+ );
+ const res = await request(`/elasticsearch/${record.id}`);
+ if (res?.found) {
+ onAgentCredentialSave(res._source);
+ if (res._source?.agent_credential_id) {
+ setIsManual(false);
+ }
+ form.setFieldsValue({
+ agent_credential_id: res._source?.agent_credential_id
+ ? res._source?.agent_credential_id
+ : res._source?.agent_basic_auth?.username
+ ? MANUAL_VALUE
+ : undefined,
+ agent_username: res._source.agent_basic_auth?.username,
+ agent_password: res._source.agent_basic_auth?.password,
+ });
+ }
+ } else {
+ message.error(
+ formatMessage({
+ id: "app.message.update.failed",
+ })
+ );
+ }
+ setSaveLoading(false);
+ });
+ };
+
+ if (!needAuth) {
+ return (
+
+ );
+ }
+
+ return (
+
+
+
+
+
+
+ );
+});
diff --git a/web/src/pages/Agent/Instance/components/AgentCredentialForm.jsx b/web/src/pages/Agent/Instance/components/AgentCredentialForm.jsx
new file mode 100644
index 00000000..5d5041b1
--- /dev/null
+++ b/web/src/pages/Agent/Instance/components/AgentCredentialForm.jsx
@@ -0,0 +1,143 @@
+import React, { useEffect, useMemo, useState } from "react";
+import { Button, Divider, Form, Input, Select, Row, Col } from "antd";
+import { formatMessage } from "umi/locale";
+
+import useFetch from "@/lib/hooks/use_fetch";
+import { formatESSearchResult } from "@/lib/elasticsearch/util";
+
+export const MANUAL_VALUE = "manual";
+
+export default (props) => {
+ const {
+ btnLoading = false,
+ needAuth,
+ form: { getFieldDecorator },
+ initialValue,
+ isEdit,
+ tryConnect,
+ credentialRequired = false,
+ isManual,
+ setIsManual,
+ } = props;
+
+ const { loading, error, value, run } = useFetch(
+ "/credential/_search",
+ {
+ queryParams: {
+ from: 0,
+ size: 1000,
+ },
+ },
+ []
+ );
+
+ const onCredentialChange = (value) => {
+ if (value === "manual") {
+ setIsManual(true);
+ } else {
+ setIsManual(false);
+ }
+ };
+
+ const { data, total } = useMemo(() => {
+ return formatESSearchResult(value);
+ }, [value]);
+
+ if (!needAuth) {
+ return null;
+ }
+
+ return (
+ <>
+
+ {getFieldDecorator("agent_credential_id", {
+ initialValue: initialValue?.agent_credential_id
+ ? initialValue?.agent_credential_id
+ : initialValue?.username
+ ? MANUAL_VALUE
+ : undefined,
+ rules: [
+ {
+ required: credentialRequired,
+ message: formatMessage({
+ id: "cluster.regist.form.verify.required.agent_credential",
+ }),
+ },
+ ],
+ })(
+
+ )}
+
+ {isManual && (
+ <>
+
+ {getFieldDecorator("agent_username", {
+ initialValue: initialValue?.username || "",
+ rules: [
+ {
+ required: credentialRequired,
+ message: formatMessage({
+ id: "cluster.regist.form.verify.required.auth_username",
+ }),
+ },
+ ],
+ })()}
+
+
+ {getFieldDecorator("agent_password", {
+ initialValue: initialValue?.password || "",
+ rules: [
+ {
+ required: credentialRequired,
+ message: formatMessage({
+ id: "cluster.regist.form.verify.required.auth_password",
+ }),
+ },
+ ],
+ })(
+
+ )}
+
+ {isEdit && (
+ <>
+
+
+ {formatMessage({
+ id: "cluster.regist.form.credential.manual.desc",
+ })}
+
+
+ >
+ )}
+ >
+ )}
+ >
+ );
+};
diff --git a/web/src/pages/Agent/Instance/components/AutoEnroll.jsx b/web/src/pages/Agent/Instance/components/AutoEnroll.jsx
index 448a8703..9ffcdf8c 100644
--- a/web/src/pages/Agent/Instance/components/AutoEnroll.jsx
+++ b/web/src/pages/Agent/Instance/components/AutoEnroll.jsx
@@ -1,22 +1,36 @@
import { useGlobal } from "@/layouts/GlobalContext";
import request from "@/utils/request";
-import { Form, Input, Switch, Icon, Button, Select } from "antd";
-import { useMemo, useRef, useState } from "react";
+import { Form, Input, Switch, Icon, Button, Select, Alert } from "antd";
+import { useEffect, useMemo, useRef, useState } from "react";
import { Link, router } from "umi";
import { formatMessage } from "umi/locale";
import ClusterSelect from "@/components/ClusterSelect";
+import SetAgentCredential from "./SetAgentCredential";
export default ({ onEnroll, loading }) => {
const { clusterList = [], clusterStatus } = useGlobal();
const [selectedCluster, setSelectedCluster] = useState([]);
+ const [auths, setAuths] = useState([]);
const onEnrollClick = () => {
- if (typeof onEnroll === "function") {
+ if (selectedCluster.length === 0) return;
+ const newAuths = [...auths]
+ selectedCluster.forEach((item) => {
+ if (item.credential_id && !item.agent_credential_id) {
+ newAuths.push(item)
+ }
+ })
+ setAuths(newAuths)
+ if (newAuths.length === 0 && typeof onEnroll === "function") {
onEnroll(selectedCluster.map((item) => item.id));
}
};
+ useEffect(() => {
+ setAuths([])
+ }, [JSON.stringify(selectedCluster)])
+
return (
{
}}
/>
+
+ {
+ auths.length > 0 && (
+
+
+ {formatMessage({
+ id: "agent.instance.associate.auth.error",
+ })}
+
+
+ { auths.map((item) => (
+
+ - {item.name}
+
+ )) }
+
+
+ )}/>
+ )
+ }
diff --git a/web/src/pages/Agent/Instance/components/RowDetail.jsx b/web/src/pages/Agent/Instance/components/RowDetail.jsx
index 4da977e7..1ea8347e 100644
--- a/web/src/pages/Agent/Instance/components/RowDetail.jsx
+++ b/web/src/pages/Agent/Instance/components/RowDetail.jsx
@@ -228,7 +228,7 @@ export const AgentRowDetail = ({ agentID, t }) => {
})
}
>
-