Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: create sidebar layout, apply tailwind styles #8

Merged
merged 1 commit into from
Jan 13, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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