diff --git a/apps/desktop/src/api/commands.rs b/apps/desktop/src/api/commands.rs index d2e9c0a6..fba2a9e9 100644 --- a/apps/desktop/src/api/commands.rs +++ b/apps/desktop/src/api/commands.rs @@ -31,6 +31,8 @@ macro_rules! collect_commands { get_cluster, get_clusters, run_cluster, + get_cluster_logs, + get_cluster_log, // Processor get_running_clusters, get_processes_by_path, @@ -175,6 +177,23 @@ pub async fn get_cluster(uuid: Uuid) -> Result { } } + +#[specta::specta] +#[tauri::command] +pub async fn get_cluster_logs(uuid: Uuid) -> Result, String> { + let cluster = cluster::get_by_uuid(uuid, None).await?.ok_or("cluster not found")?; + let logs = cluster::get_logs(&cluster.cluster_path()).await?; + Ok(logs) +} + +#[specta::specta] +#[tauri::command] +pub async fn get_cluster_log(uuid: Uuid, log_name: String) -> Result { + let cluster = cluster::get_by_uuid(uuid, None).await?.ok_or("cluster not found")?; + let log = cluster::get_log(&cluster.cluster_path(), log_name).await?; + Ok(log) +} + #[specta::specta] #[tauri::command] pub async fn get_clusters() -> Result, String> { diff --git a/apps/frontend/src/ui/components/Sidebar.tsx b/apps/frontend/src/ui/components/Sidebar.tsx index 49a34e93..e61b3956 100644 --- a/apps/frontend/src/ui/components/Sidebar.tsx +++ b/apps/frontend/src/ui/components/Sidebar.tsx @@ -8,7 +8,6 @@ type SidebarProps = ParentProps & { }; function Sidebar(props: SidebarProps) { - const searchParams = new URLSearchParams(); const navigate = useNavigate(); const location = useLocation(); @@ -18,7 +17,7 @@ function Sidebar(props: SidebarProps) { }); function goto(href: string) { - const url = `${props.base}${href}?${searchParams.toString()}`; + const url = `${props.base}${href}${location.search.toString()}`; navigate(url); } diff --git a/apps/frontend/src/ui/components/base/Dropdown.module.scss b/apps/frontend/src/ui/components/base/Dropdown.module.scss index 76b444d4..f5358e2e 100644 --- a/apps/frontend/src/ui/components/base/Dropdown.module.scss +++ b/apps/frontend/src/ui/components/base/Dropdown.module.scss @@ -1,6 +1,10 @@ .dropdown { @apply relative h-full; + &[data-disabled="true"] { + @apply pointer-events-none opacity-50; + } + .dropdown_elements_container { @apply flex flex-col gap-1; } @@ -20,7 +24,7 @@ } .row { - @apply w-full flex flex-row justify-start items-center gap-1.5 h-4 text-nowrap overflow-hidden; + @apply w-full flex flex-row justify-start items-center gap-1.5 h-5 text-nowrap overflow-hidden; > svg { @apply min-w-4 min-h-4 w-4 h-4; @@ -34,4 +38,4 @@ } } -} \ No newline at end of file +} diff --git a/apps/frontend/src/ui/components/base/Dropdown.tsx b/apps/frontend/src/ui/components/base/Dropdown.tsx index 4db9d168..bb0f3554 100644 --- a/apps/frontend/src/ui/components/base/Dropdown.tsx +++ b/apps/frontend/src/ui/components/base/Dropdown.tsx @@ -21,7 +21,7 @@ export type DropdownProps = Omit, 'onChange'> }; function Dropdown(props: DropdownProps) { - const [split, rest] = splitProps(props, ['children', 'class', 'text', 'onChange', 'selected', 'component', 'dropdownClass', 'listToolRow']); + const [split, rest] = splitProps(props, ['disabled', 'children', 'class', 'text', 'onChange', 'selected', 'component', 'dropdownClass', 'listToolRow']); const [visible, setVisible] = createSignal(false); // eslint-disable-next-line solid/reactivity -- todo @@ -60,7 +60,7 @@ function Dropdown(props: DropdownProps) { ); return ( -
+
{ - return [] as string[]; - }); + const [cluster] = useClusterContext(); + const [logs] = useCommand(bridge.commands.getClusterLogs, cluster()!.uuid); + const settings = useSettingsContext(); const [activeLogFile, setActiveLogFile] = createSignal(null); - const [log, _setLog] = createSignal(null); + const [log, setLog] = createSignal(null); - // createEffect(() => { - // const log = logs()?.[0]; + function getAndSetLog(log_name: string) { + tryResult(bridge.commands.getClusterLog, cluster()!.uuid, log_name).then(setLog); + } - // // Set default log to the first file - // if (log && activeLogFile() === null) - // setActiveLogFile(log); + function changeLog(index: number) { + const log = untrack(() => logs()); + if (log === undefined || log[index] === undefined) + return; - // // Fetch log content - // if (activeLogFile() !== null) { - // getClusterLog(params.id!, activeLogFile()!).then((log) => { - // setLog(log); - // }); - // } - // }); + setActiveLogFile(log[index]); + } + + async function openFolder() { + const root = settings.config_dir; + const path = cluster()!.path; + if (root === null || root === undefined || path === null || path === undefined) + return; + + const dir = joinPath(root, 'clusters', path); + + open(joinPath(dir, 'logs')); + } + + const missingLogs = () => logs() === undefined || logs()?.length === 0 || false; + + createEffect(() => { + const log = logs()?.[0]; + + // Set default log to the first file + if (log !== undefined && activeLogFile() === null) + setActiveLogFile(log); + + // Fetch log content + if (activeLogFile() !== null) + getAndSetLog(activeLogFile()!); + }); return (
-

Logs

- -
-
- - {(log) => { - const pretty = log.split('/').pop(); - return ( -
- {/* TODO: Try fix this when using OverlayScrollbarComponent https://github.com/KingSora/OverlayScrollbars/issues/627 */} - No logs found}> + No logs were found.}>
- {log()} + {log()}
diff --git a/apps/frontend/src/utils/helpers.ts b/apps/frontend/src/utils/helpers.ts index ee9ba4c7..7e49ab2b 100644 --- a/apps/frontend/src/utils/helpers.ts +++ b/apps/frontend/src/utils/helpers.ts @@ -10,3 +10,32 @@ export function formatVersionRelease(release: VersionType): string { return mapping[release]; } + +/** + * A simple analog of Node.js's `path.join(...)`. + * https://gist.github.com/creationix/7435851#gistcomment-3698888 + */ +export default function joinPath(...segments: string[]) { + const parts = segments.reduce((parts, segment) => { + // Remove leading slashes from non-first part. + if (parts.length > 0) + segment = segment.replace(/^\//, ''); + + // Remove trailing slashes. + segment = segment.replace(/\/$/, ''); + return parts.concat(segment.split('/')); + }, []); + + const resultParts: string[] = []; + for (const part of parts) { + if (part === '.') + continue; + + if (part === '..') { + resultParts.pop(); + continue; + } + resultParts.push(part); + } + return resultParts.join('/'); +} diff --git a/packages/core/src/api/cluster/mod.rs b/packages/core/src/api/cluster/mod.rs index 48ce4d35..01ed2986 100644 --- a/packages/core/src/api/cluster/mod.rs +++ b/packages/core/src/api/cluster/mod.rs @@ -239,6 +239,37 @@ pub async fn get_mod_path( .into()) } +/// Get log names for a specific cluster by it's [`ClusterPath`]. +#[tracing::instrument] +pub async fn get_logs(path: &ClusterPath) -> crate::Result> { + let logs_dir = path.full_path().await?.join("logs"); + let mut logs = io::read_dir(logs_dir).await?; + let mut log_names: Vec = vec![]; + + while let Some(log) = logs.next_entry().await? { + if log.file_type().await?.is_file() { + let file_name = log.file_name().to_string_lossy().to_string(); + + if file_name == "latest.log" { + log_names.insert(0, file_name); + continue; + } + + log_names.push(file_name); + } + } + + Ok(log_names) +} + +/// Get a log for a specific cluster by it's [`ClusterPath`] and log [`PathBuf`]. +#[tracing::instrument] +pub async fn get_log(path: &ClusterPath, log_name: String) -> crate::Result { + let log_path = path.full_path().await?.join("logs").join(log_name); + let log = io::read_to_string(&log_path).await?; + Ok(log) +} + /// edit a cluster with an async closure and it's [`ClusterPath`] pub async fn edit( path: &ClusterPath, diff --git a/packages/core/src/api/java.rs b/packages/core/src/api/java.rs index 0c14cdc5..94775c74 100644 --- a/packages/core/src/api/java.rs +++ b/packages/core/src/api/java.rs @@ -102,7 +102,7 @@ pub async fn install_java(java_version: u32) -> crate::Result { #[cfg(not(target_os = "macos"))] { - base_path = base_path.join("bin").join(constants::JAVA_BIN) + base_path = base_path.join("bin").join(crate::constants::JAVA_BIN) } Ok(base_path)