Skip to content

Commit

Permalink
feat: create sidebar layout, apply tailwind styles (#8)
Browse files Browse the repository at this point in the history
  • Loading branch information
davidhu2000 authored Jan 13, 2023
1 parent 8a7022f commit ec8096c
Show file tree
Hide file tree
Showing 9 changed files with 235 additions and 32 deletions.
4 changes: 2 additions & 2 deletions index.html
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
<!DOCTYPE html>
<html lang="en">
<html lang="en" class="h-full bg-gray-100">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Kube Knots</title>
</head>

<body>
<body class="h-full">
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
Expand Down
3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,16 @@
"tauri": "tauri"
},
"dependencies": {
"@headlessui/react": "^1.7.7",
"@heroicons/react": "^2.0.13",
"@kubernetes/client-node": "^0.18.0",
"@tanstack/react-query": "^4.22.0",
"@tauri-apps/api": "^1.2.0",
"react": "^18.2.0",
"react-dom": "^18.2.0"
},
"devDependencies": {
"@tailwindcss/forms": "^0.5.3",
"@tauri-apps/cli": "^1.2.2",
"@trivago/prettier-plugin-sort-imports": "^4.0.0",
"@types/node": "^18.11.18",
Expand Down
28 changes: 22 additions & 6 deletions src/app.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,34 @@
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import type { PropsWithChildren } from "react";

import { Layout } from "./layout";
import { CurrentNamespaceProvider, NamespaceProvider } from "./namespaces/namespaces";
import { Pods } from "./pods/pods";

const queryClient = new QueryClient();

type Provider = ({ children }: PropsWithChildren) => JSX.Element;

const providers = [NamespaceProvider, CurrentNamespaceProvider];

const composeProviders = (providers: Provider[]) => {
return providers.reduce((Prev, Curr) => ({ children }) => (
<Prev>
<Curr>{children}</Curr>
</Prev>
));
};

const AppProviders = composeProviders(providers);

export function App() {
return (
<QueryClientProvider client={queryClient}>
<div className="container">
<h1>Kube Knots</h1>

<h2>Pods</h2>
<Pods />
</div>
<AppProviders>
<Layout>
<Pods />
</Layout>
</AppProviders>
</QueryClientProvider>
);
}
50 changes: 50 additions & 0 deletions src/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { DocumentMagnifyingGlassIcon } from "@heroicons/react/24/outline";
import type { PropsWithChildren } from "react";

import { NamespaceSelect } from "./namespaces/namespace-select";

const navigation = [
{ name: "Pods", href: "#" },
{ name: "Jobs", href: "#" },
];

export function Layout({ children }: PropsWithChildren) {
return (
<div>
<div className="fixed inset-y-0 flex w-40 flex-col bg-gray-200">
<div className="flex h-16 items-center px-2 font-medium">
{/* TODO: update logo */}
<DocumentMagnifyingGlassIcon className="mr-2 h-6 w-6" aria-hidden="true" />
Kube Knots
</div>

<div className="flex flex-1 flex-col overflow-y-auto">
<nav className="flex-1 space-y-1 px-2 py-4">
{navigation.map((item) => (
<a
key={item.name}
href={item.href}
className="group flex items-center rounded-md p-2 text-sm font-medium text-gray-800 hover:bg-gray-400"
>
{item.name}
</a>
))}
</nav>
</div>
</div>
<div className="flex flex-col pl-40">
<div className="sticky top-0 z-10 flex h-16 shrink-0 p-2 shadow">
<NamespaceSelect />
</div>

<main className="flex-1">
<div className="py-6">
<div className="mx-auto max-w-7xl px-4">
<div className="py-4">{children}</div>
</div>
</div>
</main>
</div>
</div>
);
}
69 changes: 69 additions & 0 deletions src/namespaces/namespace-select.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import { Combobox } from "@headlessui/react";
import { CheckIcon, ChevronUpDownIcon } from "@heroicons/react/20/solid";
import { useState } from "react";

import { useCurrentNamespace, useNamespace } from "./namespaces";

export function NamespaceSelect() {
const [query, setQuery] = useState("");
const { namespace, updateNamespace } = useCurrentNamespace();
const namespaces = useNamespace();

const filteredNamespaces = ["All namespaces"].concat(
namespaces.filter((namespace) => {
return namespace.toLowerCase().includes(query.toLowerCase());
})
);

return (
<Combobox as="div" value={namespace} onChange={updateNamespace}>
<div className="relative mt-1">
<Combobox.Input
className="w-full rounded-md border border-gray-300 bg-white py-2 pl-3 pr-10 shadow-sm focus:border-slate-500 focus:outline-none focus:ring-1 focus:ring-slate-500"
onChange={(event) => setQuery(event.target.value)}
/>
<Combobox.Button className="absolute inset-y-0 right-0 flex items-center rounded-r-md px-2 focus:outline-none">
<ChevronUpDownIcon className="h-5 w-5 text-gray-400" aria-hidden="true" />
</Combobox.Button>

{filteredNamespaces.length > 0 && (
<Combobox.Options className="absolute z-10 mt-1 max-h-60 w-full overflow-auto rounded-md bg-white py-1 text-base shadow-lg ring-1 ring-black/5 focus:outline-none">
{filteredNamespaces.map((namespace) => (
<Combobox.Option
key={namespace}
value={namespace}
className={({ active }) =>
`
relative cursor-default select-none py-2 pl-3 pr-9
${active ? "bg-slate-600 text-white" : "text-gray-900"}
`
}
>
{({ active, selected }) => (
<>
<span
className={`block cursor-pointer truncate ${selected ? "font-semibold" : ""}`}
>
{namespace}
</span>

{selected && (
<span
className={`
absolute inset-y-0 right-0 flex items-center pr-4
${active ? "text-white" : "text-slate-600"}
`}
>
<CheckIcon className="h-5 w-5" aria-hidden="true" />
</span>
)}
</>
)}
</Combobox.Option>
))}
</Combobox.Options>
)}
</div>
</Combobox>
);
}
44 changes: 32 additions & 12 deletions src/namespaces/namespaces.tsx
Original file line number Diff line number Diff line change
@@ -1,24 +1,44 @@
import type { V1Namespace } from "@kubernetes/client-node";
import { useQuery } from "@tanstack/react-query";
import { invoke } from "@tauri-apps/api";
import { createContext, PropsWithChildren, useContext, useState } from "react";

export function Namespaces({ onChange }: { onChange: (namespace: string) => void }) {
const NamespaceContext = createContext<string[]>([]);

export const useNamespace = () => useContext(NamespaceContext);

export function NamespaceProvider({ children }: PropsWithChildren) {
const result = useQuery(["namespaces"], () => {
return invoke<{ items: V1Namespace[] }>("get_namespaces");
});

if (!result.isSuccess) {
return <div>Error</div>;
}
const namespaces = result.data?.items.map((item) => item.metadata?.name ?? "");

return <NamespaceContext.Provider value={namespaces ?? []}>{children}</NamespaceContext.Provider>;
}

const CurrentNamespaceContext = createContext<{
namespace: string | undefined;
updateNamespace: (ns: string) => void;
}>({ namespace: undefined, updateNamespace: () => null });

export const useCurrentNamespace = () => useContext(CurrentNamespaceContext);

export function CurrentNamespaceProvider({ children }: PropsWithChildren) {
const [namespace, setNamespace] = useState<string | undefined>();

const updateNamespace = (ns: string) => {
// TODO: update to a const
if (ns === "All namespaces") {
setNamespace(undefined);
} else {
setNamespace(ns);
}
};

return (
<select name="namespaces" onChange={(e) => onChange(e.target.value)}>
<option>All</option>
{result.data.items.map((item) => (
<option key={item.metadata?.name} value={item.metadata?.name}>
{item.metadata?.name}
</option>
))}
</select>
<CurrentNamespaceContext.Provider value={{ namespace, updateNamespace }}>
{children}
</CurrentNamespaceContext.Provider>
);
}
16 changes: 5 additions & 11 deletions src/pods/pods.tsx
Original file line number Diff line number Diff line change
@@ -1,24 +1,18 @@
import { invoke } from "@tauri-apps/api";
import type { V1Pod } from "@kubernetes/client-node";
import { useQuery } from "@tanstack/react-query";
import { invoke } from "@tauri-apps/api";

import type { V1Pod } from "@kubernetes/client-node";
import { useState } from "react";
import { Namespaces } from "../namespaces/namespaces";
import { useCurrentNamespace } from "../namespaces/namespaces";

export function Pods() {
const [namespace, setNamespace] = useState<string | undefined>(undefined);
const { namespace } = useCurrentNamespace();

const result = useQuery(["pods", namespace], () => {
return invoke<{ items: V1Pod[] }>("get_pods", { namespace });
});

const handleNamespaceChange = (namespace: string) => {
setNamespace(namespace);
};

return (
<div>
<Namespaces onChange={handleNamespaceChange} />
<table>
<thead>
<tr>
Expand All @@ -28,7 +22,7 @@ export function Pods() {
</thead>
<tbody>
{result.data?.items.map((pod) => (
<tr>
<tr key={pod.metadata?.uid}>
<td>{pod.metadata?.name}</td>
<td>{pod.status?.phase}</td>
</tr>
Expand Down
2 changes: 1 addition & 1 deletion tailwind.config.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,5 @@ module.exports = {
theme: {
extend: {},
},
plugins: [],
plugins: [require('@tailwindcss/forms')],
};
51 changes: 51 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -519,6 +519,27 @@ __metadata:
languageName: node
linkType: hard

"@headlessui/react@npm:^1.7.7":
version: 1.7.7
resolution: "@headlessui/react@npm:1.7.7"
dependencies:
client-only: ^0.0.1
peerDependencies:
react: ^16 || ^17 || ^18
react-dom: ^16 || ^17 || ^18
checksum: 049d7ee46056fe96067f7b2f4f962672dfc824e68044ae38561a457278c8e38c0fd17592ab648ba5648e7e82bef9890eddbf329c9a00d11acf85700c7072a0bf
languageName: node
linkType: hard

"@heroicons/react@npm:^2.0.13":
version: 2.0.13
resolution: "@heroicons/react@npm:2.0.13"
peerDependencies:
react: ">= 16"
checksum: f03c393a129cb468d103e4334815df956d7afa9d0756ee09b33e9396214d39b786523a5e650709de73c68ba085facda3fe01984ff137bcc16c85d4ed2498e663
languageName: node
linkType: hard

"@humanwhocodes/config-array@npm:^0.11.8":
version: 0.11.8
resolution: "@humanwhocodes/config-array@npm:0.11.8"
Expand Down Expand Up @@ -673,6 +694,17 @@ __metadata:
languageName: node
linkType: hard

"@tailwindcss/forms@npm:^0.5.3":
version: 0.5.3
resolution: "@tailwindcss/forms@npm:0.5.3"
dependencies:
mini-svg-data-uri: ^1.2.3
peerDependencies:
tailwindcss: ">=3.0.0 || >= 3.0.0-alpha.1"
checksum: 9eddb4dbd06d01b1068138ff52a54ed0e35a28e7bfd3c72e226fc28658ecd92a9c078c4abe9c83db090984672040644d7ae2e035933fb619dd703df1d87aa275
languageName: node
linkType: hard

"@tanstack/query-core@npm:4.22.0":
version: 4.22.0
resolution: "@tanstack/query-core@npm:4.22.0"
Expand Down Expand Up @@ -1473,6 +1505,13 @@ __metadata:
languageName: node
linkType: hard

"client-only@npm:^0.0.1":
version: 0.0.1
resolution: "client-only@npm:0.0.1"
checksum: 0c16bf660dadb90610553c1d8946a7fdfb81d624adea073b8440b7d795d5b5b08beb3c950c6a2cf16279365a3265158a236876d92bce16423c485c322d7dfaf8
languageName: node
linkType: hard

"color-convert@npm:^1.9.0":
version: 1.9.3
resolution: "color-convert@npm:1.9.3"
Expand Down Expand Up @@ -2705,7 +2744,10 @@ __metadata:
version: 0.0.0-use.local
resolution: "kube-knots@workspace:."
dependencies:
"@headlessui/react": ^1.7.7
"@heroicons/react": ^2.0.13
"@kubernetes/client-node": ^0.18.0
"@tailwindcss/forms": ^0.5.3
"@tanstack/react-query": ^4.22.0
"@tauri-apps/api": ^1.2.0
"@tauri-apps/cli": ^1.2.2
Expand Down Expand Up @@ -2885,6 +2927,15 @@ __metadata:
languageName: node
linkType: hard

"mini-svg-data-uri@npm:^1.2.3":
version: 1.4.4
resolution: "mini-svg-data-uri@npm:1.4.4"
bin:
mini-svg-data-uri: cli.js
checksum: 997f1fbd8d59a70f03761e18626d335197a3479cb9d1ff75678e4b64b864d32a0b8fc18115eabde035e5299b8b4a354a78e57dd6ac10f9d604162a6170898d09
languageName: node
linkType: hard

"minimatch@npm:^3.0.5, minimatch@npm:^3.1.1, minimatch@npm:^3.1.2":
version: 3.1.2
resolution: "minimatch@npm:3.1.2"
Expand Down

0 comments on commit ec8096c

Please sign in to comment.