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
24 changes: 23 additions & 1 deletion apps/predbat/gecloud.py
Original file line number Diff line number Diff line change
Expand Up @@ -520,6 +520,26 @@ async def publish_meter(self, device, meter):
self.base.dashboard_item(entity_name + "_grid_import_total", state=meter[key][subkey].get("import", 0), attributes=attribute_table.get("grid_import_total", {}), app="gecloud")
self.base.dashboard_item(entity_name + "_grid_export_total", state=meter[key][subkey].get("export", 0), attributes=attribute_table.get("grid_export_total", {}), app="gecloud")

async def enable_real_time_control(self, device, registers):
for key in registers:
reg_name = registers[key].get("name", None)
value = registers[key].get("value", None)
if "real-time control" in reg_name.lower():
if value:
self.log("GECloud: Real-time control already enabled for {}".format(device))
return True
else:
self.log("GECloud: Enabling real-time control for {} as current value is {}".format(device, value))
result = await self.async_write_inverter_setting(device, key, True)
if result and ("value" in result):
registers[key]["value"] = result["value"]
await self.publish_registers(device, self.settings[device], select_key=key)
return True
else:
self.log("GECloud: Failed to enable real-time control for {}".format(device))
return False
return False

async def publish_registers(self, device, registers, select_key=None):
"""
Publish the registers
Expand All @@ -531,7 +551,7 @@ async def publish_registers(self, device, registers, select_key=None):
validation_rules = registers[key].get("validation_rules", None)
validation = registers[key].get("validation", None)
value = registers[key].get("value", None)
ha_name = reg_name.lower().replace(" ", "_").replace("%", "percent")
ha_name = reg_name.lower().replace(" ", "_").replace("%", "percent").replace("-", "_")
attributes = {}
attributes["friendly_name"] = reg_name

Expand Down Expand Up @@ -750,6 +770,8 @@ async def start(self):
if seconds == 0 or self.polling_mode or (device == ems_device) or (device == gateway_device):
self.settings[device] = await self.async_get_inverter_settings(device, first=False, previous=self.settings.get(device, {}))
await self.publish_registers(device, self.settings[device])
if seconds == 0:
await self.enable_real_time_control(device, self.settings[device])

except Exception as e:
self.log("Error: GECloud: Exception in main loop {}".format(e))
Expand Down
2 changes: 1 addition & 1 deletion apps/predbat/predbat.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@
import requests
import asyncio

THIS_VERSION = "v8.25.2"
THIS_VERSION = "v8.25.3"

# fmt: off
PREDBAT_FILES = ["predbat.py", "config.py", "prediction.py", "gecloud.py","utils.py", "inverter.py", "ha.py", "download.py", "unit_test.py", "web.py", "web_helper.py", "predheat.py", "futurerate.py", "octopus.py", "solcast.py","execute.py", "plan.py", "fetch.py", "output.py", "userinterface.py", "energydataservice.py", "alertfeed.py", "compare.py", "db_manager.py", "db_engine.py", "plugin_system.py", "ohme.py", "components.py", "fox.py"]
Expand Down
6 changes: 6 additions & 0 deletions apps/predbat/web.py
Original file line number Diff line number Diff line change
Expand Up @@ -1307,6 +1307,12 @@ async def html_log(self, request):
text += '<button id="pauseResumeBtn" onclick="toggleUpdates()" style="margin-left: 10px; padding: 4px 8px;">Pause</button>'
text += "</div>"

text += '<div class="log-search-container">'
text += '<input type="text" id="logSearchInput" class="log-search-input" placeholder="Search log entries..." oninput="filterLogEntries()" />'
text += '<button id="clearSearchBtn" class="clear-search-button" onclick="clearLogSearch()">Clear</button>'
text += '<div id="searchStatus" class="search-status"></div>'
text += "</div>"

text += '<div id="logStatus" class="log-status">Loading log data...</div>'
text += "<table width=100% id='logTable'>\n"
text += "<tbody id='logTableBody'>\n"
Expand Down
172 changes: 172 additions & 0 deletions apps/predbat/web_helper.py
Original file line number Diff line number Diff line change
Expand Up @@ -1804,6 +1804,102 @@ def get_log_css():
background-color: #4CAF50;
color: white;
}

/* Log search container styles */
.log-search-container {
display: flex;
align-items: center;
margin-bottom: 10px;
padding: 8px;
background-color: #f8f9fa;
border: 1px solid #ddd;
border-radius: 4px;
gap: 8px;
}

.log-search-input {
flex-grow: 1;
padding: 6px 10px;
border: 1px solid #ccc;
border-radius: 4px;
font-size: 14px;
min-width: 200px;
}

.log-search-input:focus {
outline: none;
border-color: #4CAF50;
box-shadow: 0 0 0 2px rgba(76, 175, 80, 0.2);
}

.clear-search-button {
padding: 6px 12px;
background-color: #6c757d;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 14px;
white-space: nowrap;
}

.clear-search-button:hover {
background-color: #5a6268;
}

.search-status {
font-size: 12px;
color: #6c757d;
white-space: nowrap;
}

/* Dark mode search styles */
body.dark-mode .log-search-container {
background-color: #2d3748;
border-color: #4a5568;
}

body.dark-mode .log-search-input {
background-color: #4a5568;
border-color: #718096;
color: #fff;
}

body.dark-mode .log-search-input:focus {
border-color: #4CAF50;
}

body.dark-mode .log-search-input::placeholder {
color: #a0aec0;
}

body.dark-mode .clear-search-button {
background-color: #4a5568;
}

body.dark-mode .clear-search-button:hover {
background-color: #2d3748;
}

body.dark-mode .search-status {
color: #a0aec0;
}

/* Hide filtered log entries */
.log-entry-hidden {
display: none !important;
}

/* Highlight matching text */
.search-highlight {
background-color: #ffeb3b;
font-weight: bold;
}

body.dark-mode .search-highlight {
background-color: #ffa000;
color: #000;
}
</style>
"""

Expand Down Expand Up @@ -2262,6 +2358,15 @@ def get_logfile_js(filter_type):
}}
}});

// Apply current search filter to new entries
const searchTerm = document.getElementById('logSearchInput').value.toLowerCase();
if (searchTerm.trim() !== '') {{
// Re-apply filter to include new entries
setTimeout(() => {{
filterLogEntries();
}}, 10);
}}

// Auto-scroll to top for new entries (since newest are at top)
if (newEntriesAdded > 0 && shouldAutoScroll) {{
setTimeout(() => {{
Expand All @@ -2283,6 +2388,73 @@ def get_logfile_js(filter_type):
}});
}}

// Search functionality
function filterLogEntries() {{
const searchTerm = document.getElementById('logSearchInput').value.toLowerCase();
const rows = document.querySelectorAll('#logTableBody tr[data-line]');
const statusDiv = document.getElementById('searchStatus');

let visibleCount = 0;
let totalCount = rows.length;

// If search is empty, show all entries
if (searchTerm.trim() === '') {{
rows.forEach(row => {{
row.classList.remove('log-entry-hidden');
// Remove any existing highlights
const cells = row.querySelectorAll('td');
cells.forEach(cell => {{
cell.innerHTML = cell.innerHTML.replace(/<mark class="search-highlight">(.*?)<\\/mark>/gi, '$1');
}});
visibleCount++;
}});
statusDiv.textContent = '';
return;
}}

// Filter and highlight entries
rows.forEach(row => {{
const rowText = row.textContent.toLowerCase();

if (rowText.includes(searchTerm)) {{
row.classList.remove('log-entry-hidden');
visibleCount++;

// Highlight matching text
const cells = row.querySelectorAll('td');
cells.forEach(cell => {{
// First remove any existing highlights
cell.innerHTML = cell.innerHTML.replace(/<mark class="search-highlight">(.*?)<\\/mark>/gi, '$1');

// Then add new highlights
const regex = new RegExp(`(${{escapeRegExp(searchTerm)}})`, 'gi');
cell.innerHTML = cell.innerHTML.replace(regex, '<mark class="search-highlight">$1</mark>');
}});
}} else {{
row.classList.add('log-entry-hidden');
// Remove highlights from hidden rows
const cells = row.querySelectorAll('td');
cells.forEach(cell => {{
cell.innerHTML = cell.innerHTML.replace(/<mark class="search-highlight">(.*?)<\\/mark>/gi, '$1');
}});
}}
}});

// Update status
statusDiv.textContent = `Showing ${{visibleCount}} of ${{totalCount}} entries`;
}}

// Clear search function
function clearLogSearch() {{
document.getElementById('logSearchInput').value = '';
filterLogEntries();
}}

// Escape special regex characters
function escapeRegExp(string) {{
return string.replace(/[.*+?^${{}}()|[\\]\\\\]/g, '\\\\$&');
}}

// Fetch new log entries
async function updateLog() {{
if (isUpdating || isPaused) return;
Expand Down