Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions charts/veaiops/Chart.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -29,13 +29,13 @@ type: application
# This is the chart version. This version number should be incremented each time you make changes
# to the chart and its templates, including the app version.
# Versions are expected to follow Semantic Versioning (https://semver.org/)
version: 0.1.1
version: 0.1.2

# This is the version number of the application being deployed. This version number should be
# incremented each time you make changes to the application. Versions are not expected to
# follow Semantic Versioning. They should reflect the version the application is using.
# It is recommended to use it with quotes.
appVersion: "0.1.1"
appVersion: "0.1.2"

dependencies:
- name: common
Expand Down
10 changes: 5 additions & 5 deletions charts/veaiops/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -245,7 +245,7 @@ backend:
image:
registry: veaiops-registry-cn-beijing.cr.volces.com
repository: veaiops/backend
tag: v0.1.1
tag: v0.1.2
pullPolicy: Always
## Optionally specify an array of imagePullSecrets.
## Secrets must be manually created in the namespace.
Expand Down Expand Up @@ -312,7 +312,7 @@ chatops:
image:
registry: veaiops-registry-cn-beijing.cr.volces.com
repository: veaiops/chatops
tag: v0.1.1
tag: v0.1.2
pullPolicy: Always
## Optionally specify an array of imagePullSecrets.
## Secrets must be manually created in the namespace.
Expand Down Expand Up @@ -378,7 +378,7 @@ frontend:
image:
registry: veaiops-registry-cn-beijing.cr.volces.com
repository: veaiops/frontend
tag: v0.1.1
tag: v0.1.2
pullPolicy: Always
## Optionally specify an array of imagePullSecrets.
## Secrets must be manually created in the namespace.
Expand Down Expand Up @@ -445,7 +445,7 @@ intelligentThreshold:
image:
registry: veaiops-registry-cn-beijing.cr.volces.com
repository: veaiops/intelligent-threshold
tag: v0.1.1
tag: v0.1.2
pullPolicy: Always
## Optionally specify an array of imagePullSecrets.
## Secrets must be manually created in the namespace.
Expand Down Expand Up @@ -538,7 +538,7 @@ initial:
image:
registry: veaiops-registry-cn-beijing.cr.volces.com
repository: veaiops/initial
tag: v0.1.1
tag: v0.1.2
pullPolicy: Always
## Optionally specify an array of imagePullSecrets.
## Secrets must be manually created in the namespace.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,17 +13,18 @@
// limitations under the License.

/**
* 实例选择配置工厂
* @description 为不同数据源提供预定义的配置
* Instance selection config factory.
* @description Provides predefined instance selection configs for different data sources.
*/

import { IconCloud, IconDesktop } from '@arco-design/web-react/icon';
import type { AliyunInstance, VolcengineInstance } from '@wizard/types';
import { checkMatch } from '@wizard/utils/filter';
import type { ZabbixHost } from 'api-generate';
import type { AliyunInstance, VolcengineInstance } from '../../../../types';
import type { InstanceSelectionConfig } from './instance-selection-config';

/**
* 阿里云实例选择配置
* Aliyun instance selection config.
*/
export const createAliyunConfig = (
selectionAction: (instances: AliyunInstance[]) => void,
Expand All @@ -36,13 +37,13 @@ export const createAliyunConfig = (
itemType: '实例',
icon: <IconCloud />,
dataTransformer: (instance) => {
// 当只有 userId 而没有 instanceId 时,使用 userId 作为 id
// 这样可以确保标题和显示正确
// When only userId exists (no instanceId), use userId as the id
// This ensures the title and display are still meaningful
const id =
instance.instanceId ||
instance.dimensions?.instanceId ||
instance.dimensions?.userId ||
instance.userId ||
(instance as AliyunInstance & { userId?: string }).userId ||
'';
const name =
instance.instanceName ||
Expand All @@ -62,28 +63,28 @@ export const createAliyunConfig = (
},
selectionAction,
searchFilter: (instance, searchValue) => {
const searchLower = searchValue.toLowerCase();
return (
(instance.instanceId?.toLowerCase() || '').includes(searchLower) ||
(instance.instanceName?.toLowerCase() || '').includes(searchLower) ||
(instance.region?.toLowerCase() || '').includes(searchLower) ||
// 当只有 userId 时,也支持搜索 userId
(instance.dimensions?.userId?.toLowerCase() || '').includes(
searchLower,
) ||
(instance.userId?.toLowerCase() || '').includes(searchLower)
checkMatch(instance.instanceId, searchValue) ||
checkMatch(instance.instanceName, searchValue) ||
checkMatch(instance.region, searchValue) ||
// When only userId exists, also allow searching by userId
checkMatch(instance.dimensions?.userId, searchValue) ||
checkMatch(
(instance as AliyunInstance & { userId?: string }).userId,
searchValue,
)
);
},
getId: (instance) =>
instance.instanceId ||
instance.dimensions?.instanceId ||
instance.dimensions?.userId ||
instance.userId ||
(instance as AliyunInstance & { userId?: string }).userId ||
'',
});

/**
* 火山引擎实例选择配置
* Volcengine instance selection config.
*/
export const createVolcengineConfig = (
selectionAction: (instances: VolcengineInstance[]) => void,
Expand All @@ -104,35 +105,34 @@ export const createVolcengineConfig = (
}),
selectionAction,
searchFilter: (instance, searchValue) =>
instance.instanceId.toLowerCase().includes(searchValue) ||
(instance.instanceName?.toLowerCase() || '').includes(searchValue) ||
(instance.region?.toLowerCase() || '').includes(searchValue) ||
(instance.namespace?.toLowerCase() || '').includes(searchValue),
checkMatch(instance.instanceId, searchValue) ||
checkMatch(instance.instanceName, searchValue) ||
checkMatch(instance.region, searchValue) ||
checkMatch(instance.namespace, searchValue),
getId: (instance) => instance.instanceId,
});

/**
* Zabbix主机选择配置
* Zabbix host selection config.
*/
export const createZabbixConfig = (
selectionAction: (hosts: ZabbixHost[]) => void,
): InstanceSelectionConfig<ZabbixHost> => ({
title: '选择主机',
description: '选择要监控的主机,可以选择多个主机',
emptyDescription: '暂无可用的主机',
searchPlaceholder: '搜索主机名称...',
searchPlaceholder: '搜索主机名称 (支持正则)...',
itemType: '主机',
icon: <IconDesktop />,
dataTransformer: (host) => ({
id: host.host, // 使用 host 作为唯一标识
id: host.host, // Use host as the unique identifier
name: host.name,
region: undefined, // Zabbix没有region概念
dimensions: undefined, // Zabbix没有dimensions概念
region: undefined, // Zabbix has no region concept
dimensions: undefined, // Zabbix has no dimensions concept
}),
selectionAction,
searchFilter: (host, searchValue) =>
host.host.toLowerCase().includes(searchValue) ||
host.name.toLowerCase().includes(searchValue),
getId: (host) => host.host, // 使用 host 作为唯一标识
useHostList: true, // 使用特殊的主机列表组件
checkMatch(host.host, searchValue) || checkMatch(host.name, searchValue),
getId: (host) => host.host, // Use host as the unique identifier
useHostList: true, // Use the specialized host list component
});
135 changes: 135 additions & 0 deletions frontend/apps/veaiops/src/components/wizard/utils/filter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
// Copyright 2025 Beijing Volcano Engine Technology Co., Ltd. and/or its affiliates
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

/**
* Check whether the given text matches the search value.
* Supports both fuzzy substring matching and regular expressions.
* @param text Text to check
* @param searchValue Search value (plain string or regex pattern string)
* @returns Whether the text matches the search condition
*/
export const isMatch = (
text: string | undefined | null,
searchValue: string,
): boolean => {
if (!text) {
return false;
}

const safeText = text.toLowerCase();
const safeSearch = searchValue.toLowerCase().trim();

if (!safeSearch) {
return true;
}

try {
// Try regex matching first
// Typical user inputs:
// - AA-\d+-BB style regex
// - AA-* style wildcard
// - Simple substring like AA

// Strategy:
// 1. Try to interpret the query as regex
// 2. If it fails and contains *, treat it as a wildcard
// 3. Otherwise fallback to plain substring match

// We directly build RegExp here to fully support regex syntax

const regex = new RegExp(safeSearch, 'i');
if (regex.test(text)) {
return true;
}
} catch (e) {
// If regex parsing fails (e.g. syntax error), try wildcard handling instead
}

// Wildcard handling (simple * to .*)
// Only used when query contains * and regex parsing failed
if (safeSearch.includes('*')) {
try {
// Escape all regex metacharacters except *
const pattern = safeSearch
.replace(/[.+?^${}()|[\]\\]/g, '\\$&')
.replace(/\*/g, '.*');
const wildcardRegex = new RegExp(`^${pattern}$`, 'i'); // Wildcard usually implies full match
if (wildcardRegex.test(text)) {
return true;
}
} catch (e) {
// Ignore and continue to substring fallback
}
}

// Final fallback: plain substring match
return safeText.includes(safeSearch);
};

/**
* Matching helper optimized for AA-number-BB style patterns
* while still supporting generic regex and wildcard queries.
*/
export const checkMatch = (
text: string | undefined | null,
searchValue: string,
): boolean => {
if (!text) {
return false;
}

const safeText = text.trim();
const query = searchValue.trim();

if (!query) {
return true;
}

// 1. Try regex match first
// Typical usage:
// - AA-\d+-BB style patterns with \d for digits
// - ^server.* for prefix matching
// Using RegExp directly gives maximum flexibility for power users.

try {
// Use 'i' flag to make the match case-insensitive
const regex = new RegExp(query, 'i');
if (regex.test(safeText)) {
return true;
}
} catch (e) {
// If regex parsing fails (e.g. unclosed parenthesis), fall through to wildcard or substring
}

// 2. Try wildcard * match
// Only used when query contains * and regex parsing did not succeed
if (query.includes('*')) {
try {
// Escape all regex metacharacters except *
const pattern = query
.replace(/[.+?^${}()|[\]\\]/g, '\\$&')
.replace(/\*/g, '.*');
const wildcardRegex = new RegExp(`^${pattern}$`, 'i'); // Wildcard usually implies full match
if (wildcardRegex.test(safeText)) {
return true;
}
} catch (e) {
// Ignore and fallback to plain substring match
}
}

// 3. Plain substring match (case-insensitive)
// Fallback for queries like "server(" which are invalid regex but valid text
return safeText.toLowerCase().includes(query.toLowerCase());
};
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.

import { Tooltip } from '@arco-design/web-react';
import { CellRender } from '@veaiops/components';
import type React from 'react';

Expand All @@ -29,9 +30,13 @@ export const LabelsColumn: React.FC<{
<div className="space-y-1">
{Object.entries(labels).map(([key, value]) => (
<div key={key} className="text-sm">
<CellRender.CustomOutlineTag>
{`${key}: ${value}`}
</CellRender.CustomOutlineTag>
<Tooltip content={`${key}: ${value}`}>
<span className="inline-block max-w-full">
<CellRender.CustomOutlineTag maxWidth={220} ellipsis>
{`${key}: ${value}`}
</CellRender.CustomOutlineTag>
</span>
</Tooltip>
</div>
))}
</div>
Expand Down
Loading