add clear logs button to logs windows (#7429)

This commit is contained in:
Danglewood
2026-04-18 22:31:45 -07:00
committed by GitHub
parent 207c24a466
commit 203f62e7d2
8 changed files with 164 additions and 57 deletions

View File

@@ -56,8 +56,9 @@ use crate::tauri_handlers::helpers::{
use tauri_plugin_updater::UpdaterExt;
use crate::utils::process_monitor::{
GetProcessLogsRequest, LogEntry, LogStorage, RunningProcesses, get_log_storage,
get_process_logs, init_process_monitoring, register_process, unregister_process,
GetProcessLogsRequest, LogEntry, LogStorage, RunningProcesses, clear_process_logs,
get_log_storage, get_process_logs, init_process_monitoring, register_process,
unregister_process,
};
use crate::uninstall::uninstall_application;
@@ -91,6 +92,11 @@ fn get_process_logs_history(
get_process_logs(&state.0.clone(), request)
}
#[tauri::command]
fn clear_process_logs_history(state: State<ProcessLogState>, process_id: String) -> bool {
clear_process_logs(&state.0, &process_id)
}
async fn check_and_apply_update(app: AppHandle, always_prompt: bool) {
let show_error = |app: &AppHandle, title: &str, message: String| {
app.dialog()
@@ -526,6 +532,7 @@ fn main() {
register_process_monitoring,
unregister_process_monitoring,
get_process_logs_history,
clear_process_logs_history,
open_jupyter_logs_window,
update_jupyter_status,
open_backend_logs_window,

View File

@@ -391,14 +391,31 @@ pub async fn stop_jupyter_server_impl<R: tauri::Runtime, E: EnvSystem>(
if let Ok(pid) = pid_str.parse::<u32>() {
log::debug!("🎯 Found PID {pid} using port {port}, terminating...");
// Kill this specific PID
let kill_result = env_sys
let term_result = env_sys
.new_command("kill")
.args(["-15", &pid.to_string()])
.output();
match term_result {
Ok(_) => {
log::debug!(
"Sent SIGTERM to PID {pid}, waiting for graceful shutdown..."
);
std::thread::sleep(std::time::Duration::from_secs(2));
let check = env_sys
.new_command("kill")
.args(["-0", &pid.to_string()])
.output();
if check.is_ok() && check.unwrap().status.success() {
log::debug!("PID {pid} still running, sending SIGKILL...");
let _ = env_sys
.new_command("kill")
.args(["-9", &pid.to_string()])
.output();
}
match kill_result {
Ok(_) => {
log::debug!("✅ Successfully terminated Jupyter PID {pid}");
killed_any = true;
}

View File

@@ -77,6 +77,16 @@ pub fn unregister_process(logs: &LogStorage, process_id: &str) -> bool {
storage.remove(process_id).is_some()
}
pub fn clear_process_logs(logs: &LogStorage, process_id: &str) -> bool {
let mut storage = logs.lock().unwrap();
if let Some(buffer) = storage.get_mut(process_id) {
buffer.entries.clear();
true
} else {
false
}
}
#[derive(Deserialize)]
pub struct GetProcessLogsRequest {
pub process_id: String,
@@ -569,6 +579,38 @@ mod tests {
assert_eq!(logs[1].content, "Message 5");
}
#[test]
fn test_clear_process_logs_existing() {
let storage = create_log_storage();
register_process(&storage, "test_process");
{
let mut locked = storage.lock().unwrap();
let buffer = locked.get_mut("test_process").unwrap();
for i in 1..=3 {
buffer.add(LogEntry {
timestamp: i as i64,
content: format!("Message {i}"),
process_id: "test_process".to_string(),
});
}
}
let result = clear_process_logs(&storage, "test_process");
assert!(result);
let locked = storage.lock().unwrap();
let buffer = locked.get("test_process").unwrap();
assert_eq!(buffer.entries.len(), 0);
}
#[test]
fn test_clear_process_logs_nonexistent() {
let storage = create_log_storage();
let result = clear_process_logs(&storage, "nonexistent");
assert!(!result);
}
#[test]
fn test_running_processes_add_process_mock() {
let processes = TestRunningProcesses::new();

View File

@@ -1,7 +1,7 @@
{
"$schema": "https://schema.tauri.app/config/2",
"productName": "Open Data Platform by OpenBB",
"version": "1.0.1",
"version": "1.0.2",
"identifier": "co.openbb.platform",
"build": {
"frontendDist": "../dist",

View File

@@ -3,6 +3,7 @@ import { invoke } from '@tauri-apps/api/core';
import { listen } from '@tauri-apps/api/event';
import { useSearch } from '@tanstack/react-router';
import SearchBar from './SearchBar';
import CustomIcon from './Icon';
import '../styles/jupyter-logs.css';
interface LogEntry {
@@ -21,6 +22,7 @@ const BackendLogsPage: React.FC = () => {
const [searchVisible, setSearchVisible] = useState(false);
const [currentMatchIndex, setCurrentMatchIndex] = useState(0);
const [caseSensitive, setCaseSensitive] = useState(false);
const [logsCleared, setLogsCleared] = useState(false);
const logContainerRef = useRef<HTMLDivElement>(null);
const searchInputRef = useRef<HTMLInputElement>(null);
@@ -173,6 +175,7 @@ const BackendLogsPage: React.FC = () => {
const unsubscribe = listen<{ processId: string, output: string, timestamp: number }>('process-output', (event) => {
const { processId: eventProcessId, output, timestamp } = event.payload;
if (eventProcessId === processId) {
setLogsCleared(false);
setLogs(prev => ([...prev, { timestamp, content: cleanLogContent(output), process_id: eventProcessId }]));
}
});
@@ -195,10 +198,21 @@ const BackendLogsPage: React.FC = () => {
return (
<div className="h-full w-full flex flex-col bg-theme-secondary relative flex-grow">
{!searchVisible && (
<div className="search-help-container opacity-0 hover:opacity-100 transition-opacity hover:cursor-default">
<div className="body-xs-regular text-theme-muted items-center justify-center">
<div className="logs-toolbar-container opacity-0 hover:opacity-100 transition-opacity">
<div className="body-xs-regular text-theme-muted hover:cursor-default">
Press Ctrl+F (Cmd+F) to search
</div>
<div className="flex items-center gap-1 hover:cursor-pointer"
onClick={async () => {
const processId = `backend-${backendId}`;
await invoke('clear_process_logs_history', { processId });
setLogs([]);
setLogsCleared(true);
}}
>
<CustomIcon id="delete" className="h-3.5 w-3.5 text-theme-muted" />
<span className="body-xs-regular text-theme-muted">Clear logs</span>
</div>
</div>
)}
@@ -227,7 +241,7 @@ const BackendLogsPage: React.FC = () => {
</div>
) : error ? (
<div className="text-red-500 py-2 px-2">{error}</div>
) : logs.length === 0 ? (
) : logs.length === 0 && !logsCleared ? (
<div className="text-theme-secondary">No logs available for this backend. Try starting a backend service first.</div>
) : (
<div>

View File

@@ -3,6 +3,7 @@ import { invoke } from '@tauri-apps/api/core';
import { listen } from '@tauri-apps/api/event';
import { useSearch } from '@tanstack/react-router';
import SearchBar from './SearchBar';
import CustomIcon from './Icon';
import '../styles/jupyter-logs.css';
interface LogEntry {
@@ -23,6 +24,7 @@ const JupyterLogsPage: React.FC = () => {
const [searchVisible, setSearchVisible] = useState(false);
const [currentMatchIndex, setCurrentMatchIndex] = useState(0);
const [caseSensitive, setCaseSensitive] = useState(false);
const [logsCleared, setLogsCleared] = useState(false);
const logContainerRef = useRef<HTMLDivElement>(null);
const searchInputRef = useRef<HTMLInputElement>(null);
@@ -245,6 +247,7 @@ const JupyterLogsPage: React.FC = () => {
const { processId: eventProcessId, output, timestamp } = event.payload;
if (eventProcessId === processId) {
setLogsCleared(false);
// Add the new log entry
setLogs(prev => {
const newLogs = [...prev, {
@@ -287,10 +290,21 @@ const JupyterLogsPage: React.FC = () => {
return (
<div className="h-full w-full flex flex-col bg-theme-secondary relative flex-grow">
{!searchVisible && (
<div className="search-help-container opacity-0 hover:opacity-100 transition-opacity hover:cursor-default">
<div className="body-xs-regular text-theme-muted items-center justify-center">
<div className="logs-toolbar-container opacity-0 hover:opacity-100 transition-opacity">
<div className="body-xs-regular text-theme-muted hover:cursor-default">
Press Ctrl+F (Cmd+F) to search
</div>
<div className="flex items-center gap-1 hover:cursor-pointer"
onClick={async () => {
const processId = `jupyter-${environmentName}`;
await invoke('clear_process_logs_history', { processId });
setLogs([]);
setLogsCleared(true);
}}
>
<CustomIcon id="delete" className="h-3.5 w-3.5 text-theme-muted" />
<span className="body-xs-regular text-theme-muted">Clear logs</span>
</div>
</div>
)}
@@ -319,7 +333,7 @@ const JupyterLogsPage: React.FC = () => {
</div>
) : error ? (
<div className="text-red-500 py-2 px-2">{error}</div>
) : logs.length === 0 ? (
) : logs.length === 0 && !logsCleared ? (
<div className="text-theme-secondary">No logs available for this environment. Try starting a Jupyter server first.</div>
) : (
<div>

View File

@@ -89,7 +89,9 @@ export default function ApiKeysPage() {
});
}
} catch (e) {
throw new Error(`Invalid JSON file: ${e}`);
const err = new Error(`Invalid JSON file: ${e}`);
(err as unknown as Record<string, unknown>).cause = e;
throw err;
}
} else if (extension === "env") {
// Parse .env file

View File

@@ -27,7 +27,7 @@ body.jupyter-logs-view main > div {
}
/* Target any potential horizontal scrollbars */
body.jupyter-logs-view div,
body.jupyter-logs-view div:not(.logs-toolbar-container):not(.logs-toolbar-container *),
body.jupyter-logs-view pre,
body.jupyter-logs-view code {
overflow-x: hidden !important; /* No horizontal scrolling */
@@ -51,7 +51,7 @@ header.logs-page-header {
.search-help-container {
position: fixed;
top: 65px;
top: 75px;
left: 0;
right: 0;
z-index: 50;
@@ -60,6 +60,17 @@ header.logs-page-header {
align-items: flex-start;
}
.logs-toolbar-container {
position: fixed;
top: 75px;
left: 0;
right: 0;
z-index: 50;
display: flex;
justify-content: space-between;
align-items: center;
padding: 0 18px;
}
/* Search highlight styles */
.search-highlight {