mirror of
https://github.com/OpenBB-finance/OpenBB.git
synced 2026-10-27 04:31:13 +00:00
add clear logs button to logs windows (#7429)
This commit is contained in:
11
desktop/src-tauri/src/main.rs
vendored
11
desktop/src-tauri/src/main.rs
vendored
@@ -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,
|
||||
|
||||
25
desktop/src-tauri/src/tauri_handlers/jupyter.rs
vendored
25
desktop/src-tauri/src/tauri_handlers/jupyter.rs
vendored
@@ -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(["-9", &pid.to_string()])
|
||||
.args(["-15", &pid.to_string()])
|
||||
.output();
|
||||
|
||||
match kill_result {
|
||||
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();
|
||||
}
|
||||
|
||||
log::debug!("✅ Successfully terminated Jupyter PID {pid}");
|
||||
killed_any = true;
|
||||
}
|
||||
|
||||
42
desktop/src-tauri/src/utils/process_monitor.rs
vendored
42
desktop/src-tauri/src/utils/process_monitor.rs
vendored
@@ -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();
|
||||
|
||||
2
desktop/src-tauri/tauri.conf.json
vendored
2
desktop/src-tauri/tauri.conf.json
vendored
@@ -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",
|
||||
|
||||
20
desktop/src/components/BackendLogsPage.tsx
vendored
20
desktop/src/components/BackendLogsPage.tsx
vendored
@@ -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>
|
||||
|
||||
20
desktop/src/components/JupyterLogsPage.tsx
vendored
20
desktop/src/components/JupyterLogsPage.tsx
vendored
@@ -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>
|
||||
|
||||
4
desktop/src/routes/api-keys.tsx
vendored
4
desktop/src/routes/api-keys.tsx
vendored
@@ -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
|
||||
|
||||
15
desktop/src/styles/jupyter-logs.css
vendored
15
desktop/src/styles/jupyter-logs.css
vendored
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user