Feature: Add "Online Notify" to log message context menu in main dialog

- Add "Online Notify" menu item to the log message list (m_CList_Message)
  right-click context menu, allowing users to extract IPv4 addresses from
  selected log entries and add them to notification keywords
- Implement ExtractFirstIPFromMessage() for dependency-free IPv4 parsing
  with per-segment 0-255 validation, handling diverse log message formats
- Extend NotifyManager::ShouldNotify with IP column (ONLINELIST_IP)
  fallback check, ensuring IP keywords match regardless of the user's
  configured ColumnIndex setting
- OnMsglogLoginNotify only appends keywords and enables the rule without
  overwriting the user's existing ColumnIndex or TriggerType settings
- Menu item is automatically grayed out when PowerShell is unavailable
  or no log entry is selected
- Sync en_US / zh_TW language files with new translation strings

Co-Authored-By: DeepSeek V4 Pro
This commit is contained in:
yuanyuanxiang
2026-08-06 18:09:26 +02:00
parent 7c09e9dd8c
commit eeacbe5d61
6 changed files with 219 additions and 24 deletions

View File

@@ -141,35 +141,52 @@ bool NotifyManager::ShouldNotify(context* ctx, std::string& outMatchedKeyword, c
}
}
// Get column text (for COMPUTER_NAME column, prefer remark if available)
// Split pattern by semicolon and get keywords
std::vector<std::string> keywords = SplitString(rule.matchPattern, ';');
// Helper: check if any keyword matches a given column text
auto matchColumn = [&keywords, this](const CString& colText) -> std::string {
if (colText.IsEmpty()) return "";
std::string colTextStr = CT2A(colText, CP_UTF8);
std::string colLower = colTextStr;
std::transform(colLower.begin(), colLower.end(), colLower.begin(), ::tolower);
for (const auto& kw : keywords) {
std::string trimmed = Trim(kw);
if (trimmed.empty()) continue;
std::string kwLower = trimmed;
std::transform(kwLower.begin(), kwLower.end(), kwLower.begin(), ::tolower);
if (colLower.find(kwLower) != std::string::npos) {
return trimmed;
}
}
return "";
};
// Check primary column (for COMPUTER_NAME column, prefer remark if available)
CString colText;
if (rule.columnIndex == ONLINELIST_COMPUTER_NAME && !remark.IsEmpty()) {
colText = remark;
} else {
colText = ctx->GetClientData(rule.columnIndex);
}
if (colText.IsEmpty()) return false;
std::string matched = matchColumn(colText);
if (!matched.empty()) {
outMatchedKeyword = matched;
m_config.lastNotifyTime[clientId] = now;
return true;
}
// Convert to std::string for matching
std::string colTextStr = CT2A(colText, CP_UTF8);
// Split pattern by semicolon and check each keyword
std::vector<std::string> keywords = SplitString(rule.matchPattern, ';');
for (const auto& kw : keywords) {
std::string trimmed = Trim(kw);
if (trimmed.empty()) continue;
// Case-insensitive substring search
std::string colLower = colTextStr;
std::string kwLower = trimmed;
std::transform(colLower.begin(), colLower.end(), colLower.begin(), ::tolower);
std::transform(kwLower.begin(), kwLower.end(), kwLower.begin(), ::tolower);
if (colLower.find(kwLower) != std::string::npos) {
outMatchedKeyword = trimmed;
m_config.lastNotifyTime[clientId] = now;
return true;
}
// Also check the IP column (ONLINELIST_IP) so IP keywords added from log
// messages can match against host IP addresses when they come online.
CString ipText = ctx->GetClientData(ONLINELIST_IP);
matched = matchColumn(ipText);
if (!matched.empty()) {
outMatchedKeyword = matched;
m_config.lastNotifyTime[clientId] = now;
return true;
}
return false;