Skip to content

Commit

Permalink
feat: all functionality to view logs and auto poll for new logs (#18)
Browse files Browse the repository at this point in the history
  • Loading branch information
davidhu2000 authored Jan 15, 2023
1 parent 2cb9eaf commit 57f9f01
Show file tree
Hide file tree
Showing 6 changed files with 185 additions and 34 deletions.
8 changes: 5 additions & 3 deletions src-tauri/src/resources/cron_jobs.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
use k8s_openapi::api::batch::v1::CronJob;
use kube::core::ObjectList;
use kube::{api::ListParams, core::ObjectList, Api};

use super::internal::get_resource_list;
use super::internal::get_api;

#[tauri::command]
pub async fn get_cron_jobs(namespace: Option<String>) -> ObjectList<CronJob> {
return get_resource_list(namespace).await;
let api: Api<CronJob> = get_api(namespace).await;
let lp = ListParams::default();
return api.list(&lp).await.unwrap();
}
8 changes: 5 additions & 3 deletions src-tauri/src/resources/deployments.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
use k8s_openapi::api::apps::v1::Deployment;
use kube::core::ObjectList;
use kube::{api::ListParams, core::ObjectList, Api};

use super::internal::get_resource_list;
use super::internal::get_api;

#[tauri::command]
pub async fn get_deployments(namespace: Option<String>) -> ObjectList<Deployment> {
return get_resource_list(namespace).await;
let api: Api<Deployment> = get_api(namespace).await;
let lp = ListParams::default();
return api.list(&lp).await.unwrap();
}
7 changes: 3 additions & 4 deletions src-tauri/src/resources/internal/mod.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use k8s_openapi::NamespaceResourceScope;
use kube::{api::ListParams, core::ObjectList, Api, Client};
use kube::{Api, Client};

pub async fn get_resource_list<T>(namespace: Option<String>) -> ObjectList<T>
pub async fn get_api<T>(namespace: Option<String>) -> Api<T>
where
T: serde::de::DeserializeOwned
+ std::fmt::Debug
Expand All @@ -15,6 +15,5 @@ where
Some(ns) => Api::namespaced(client, &ns),
None => Api::all(client),
};
let lp = ListParams::default();
return api.list(&lp).await.unwrap();
return api;
}
27 changes: 15 additions & 12 deletions src-tauri/src/resources/pods.rs
Original file line number Diff line number Diff line change
@@ -1,28 +1,31 @@
use k8s_openapi::api::core::v1::Pod;

use kube::{api::LogParams, core::ObjectList, Api, Client};
use kube::{
api::{ListParams, LogParams},
core::ObjectList,
Api,
};

use super::internal::get_resource_list;
use super::internal::get_api;

#[tauri::command]
pub async fn get_pods(namespace: Option<String>) -> ObjectList<Pod> {
return get_resource_list(namespace).await;
let api: Api<Pod> = get_api(namespace).await;
let lp = ListParams::default();
return api.list(&lp).await.unwrap();
}

#[tauri::command]
pub async fn get_pod_logs(pod_name: String, container_name: Option<String>) -> String {
let client = Client::try_default().await.unwrap();

let pods: Api<Pod> = Api::default_namespaced(client);
pub async fn get_pod_logs(
namespace: Option<String>,
pod_name: String,
container_name: Option<String>,
) -> String {
let pods: Api<Pod> = get_api(namespace).await;

let mut lp = LogParams::default();

lp.container = container_name;

let log_string = pods.logs(&pod_name, &lp).await.unwrap();

// print log_string
println!("{}", log_string);

return log_string;
}
95 changes: 95 additions & 0 deletions src/pods/pod-logs.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
/* This example requires Tailwind CSS v2.0+ */
import { Dialog, Transition } from "@headlessui/react";
import { XMarkIcon } from "@heroicons/react/24/outline";
import { type V1Pod } from "@kubernetes/client-node";
import { useQuery } from "@tanstack/react-query";
import { invoke } from "@tauri-apps/api";
import { Fragment, useEffect, useRef } from "react";

interface PodLogsProps {
isOpen: boolean;
selectedPod: V1Pod | null;
handleClose: () => void;
}

export default function PodLogs({ isOpen, selectedPod, handleClose }: PodLogsProps) {
const podName = selectedPod?.metadata?.name;
const namespace = selectedPod?.metadata?.namespace;
const container = selectedPod?.spec?.containers[0].name;

const result = useQuery(
["pod-logs", podName],
() => {
return invoke<string>("get_pod_logs", {
podName: podName,
namespace,
container,
});
},
{ enabled: !!podName, refetchInterval: 5000 }
);

const logBottomRef = useRef<HTMLDivElement>(null);

useEffect(() => {
logBottomRef.current?.scrollIntoView({ behavior: "smooth" });
}, [result.data]);

return (
<Transition.Root show={isOpen} as={Fragment}>
<Dialog as="div" className="relative z-10" onClose={handleClose}>
<div className="fixed inset-0 bg-black/30" aria-hidden="true" />
<div className="fixed inset-0 overflow-hidden">
<div className="absolute inset-0 overflow-hidden">
<div className="pointer-events-none fixed inset-y-0 right-0 flex max-w-full pl-10">
<Transition.Child
as={Fragment}
enter="transform transition ease-in-out duration-500"
enterFrom="translate-x-full"
enterTo="translate-x-0"
leave="transform transition ease-in-out duration-500"
leaveFrom="translate-x-0"
leaveTo="translate-x-full"
>
<Dialog.Panel className="pointer-events-auto w-screen max-w-4xl">
<div className="flex h-full flex-col overflow-y-scroll bg-white p-6 shadow-xl">
<div className="flex items-center justify-between">
<Dialog.Title className="text-lg font-medium text-gray-900">
{selectedPod?.metadata?.name}
</Dialog.Title>

<Dialog.Description className="text-sm text-gray-500">
{/* TODO: update this to be a selector to pick containers */}
Container: {selectedPod?.spec?.containers[0].name}
</Dialog.Description>

<button
type="button"
className="rounded-md p-2 text-gray-400 hover:bg-gray-200 hover:text-gray-600"
onClick={handleClose}
>
<span className="sr-only">Close</span>
<XMarkIcon className="h-6 w-6" aria-hidden="true" />
</button>
</div>

<div className="relative h-full flex-1">
<div className="absolute inset-0 h-full">
<div className="h-full shadow-xl" aria-hidden="true">
<pre className="h-full overflow-y-scroll rounded-md bg-gray-200 p-4 text-sm text-gray-500">
{result.data}
<div ref={logBottomRef} />
</pre>
</div>
</div>
</div>
</div>
</Dialog.Panel>
</Transition.Child>
</div>
</div>
</div>
</Dialog>
</Transition.Root>
);
}
74 changes: 62 additions & 12 deletions src/pods/pods.tsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,44 @@
import { BarsArrowDownIcon, PencilIcon } from "@heroicons/react/20/solid";
import type { V1Pod } from "@kubernetes/client-node";
import { useQuery } from "@tanstack/react-query";
import { invoke } from "@tauri-apps/api";
import { type PropsWithChildren, useState } from "react";

import { Table, TableHeader, TableBody, TableCell } from "../components/table";
import { useCurrentNamespace } from "../namespaces/namespaces";
import PodLogs from "./pod-logs";

const noPod = {
metadata: {
uid: "no-pods-found",
name: "No pod found",
},
status: {
phase: "---",
},
};
function ActionGroup({ children }: PropsWithChildren) {
return <span className="isolate inline-flex rounded-md shadow-sm">{children}</span>;
}

function ActionButton({
Icon,
label,
position,
onClick,
}: {
Icon: typeof PencilIcon;
label: string;
position: "left" | "right" | "middle";
onClick: () => void;
}) {
const roundedClass =
position === "left" ? "rounded-l-md" : position === "right" ? "rounded-r-md" : "";

return (
<button
type="button"
onClick={onClick}
className={`relative ${
position === "left" ? "" : "-ml-px"
} inline-flex items-center ${roundedClass} border border-gray-300 p-2 hover:bg-gray-50`}
>
<Icon className="mr-2 h-5 w-5 text-gray-400" aria-hidden="true" />
{label}
</button>
);
}

export function Pods() {
const { namespace } = useCurrentNamespace();
Expand All @@ -22,21 +47,46 @@ export function Pods() {
return invoke<{ items: V1Pod[] }>("get_pods", { namespace });
});

const pods = result.data?.items?.length ? result.data?.items : [noPod];
const pods = result.data?.items ?? [];

const [selectedPod, setSelectedPod] = useState<V1Pod | null>(null);

const handleSelectPod = (pod: V1Pod) => {
setSelectedPod(pod);
};

const handleLogPanelClose = () => setSelectedPod(null);

return (
<div>
<Table>
<TableHeader headers={["Name", "Status"]} />
<TableHeader headers={["Name", "Status", "Actions"]} />
<TableBody>
{pods.map((pod) => (
<tr key={pod.metadata?.uid}>
<TableCell>{pod.metadata?.name}</TableCell>
<TableCell> {pod.status?.phase}</TableCell>
<TableCell>{pod.status?.phase}</TableCell>
<TableCell>
<ActionGroup>
<ActionButton
Icon={BarsArrowDownIcon}
label="Logs"
position="left"
onClick={() => handleSelectPod(pod)}
/>
<ActionButton
Icon={PencilIcon}
label="Edit"
position="right"
onClick={() => alert("Edit")}
/>
</ActionGroup>
</TableCell>
</tr>
))}
</TableBody>
</Table>
<PodLogs isOpen={!!selectedPod} handleClose={handleLogPanelClose} selectedPod={selectedPod} />
</div>
);
}

0 comments on commit 57f9f01

Please sign in to comment.