From 42c2d069eff11588bfec6bbafb96a670617ae1c6 Mon Sep 17 00:00:00 2001 From: niku <nvdpoel01@gmail.com> Date: Tue, 21 Mar 2023 20:09:37 +0100 Subject: [PATCH] Split some code into seperate components --- app/nodes/[node]/index.tsx | 209 +++++++++++-------------------- app/nodes/index.tsx | 63 ++++------ components/Card/index.tsx | 20 +++ components/Gauge/index.tsx | 16 +++ components/Login/index.tsx | 105 +++++++++++++--- components/ProgressBar/index.tsx | 42 +++++++ 6 files changed, 264 insertions(+), 191 deletions(-) create mode 100644 components/Card/index.tsx create mode 100644 components/Gauge/index.tsx create mode 100644 components/ProgressBar/index.tsx diff --git a/app/nodes/[node]/index.tsx b/app/nodes/[node]/index.tsx index b4d2953..3628fc0 100644 --- a/app/nodes/[node]/index.tsx +++ b/app/nodes/[node]/index.tsx @@ -1,10 +1,13 @@ -import { Link, Stack, useSearchParams } from "expo-router"; -import { View, Text, SafeAreaView, ScrollView } from "react-native"; +import { Link, useSearchParams } from "expo-router"; +import { View, Text, SafeAreaView, ScrollView, Button } from "react-native"; import { NodeResource, useNode } from "../../../hooks/useNode"; import Icon from "@expo/vector-icons/Feather"; import tw from "twrnc"; import { formatBytes, formatPercentage } from "../../../lib/helper/format"; import { useEffect, useMemo } from "react"; +import { Gauge } from "../../../components/Gauge"; +import ProgressBar from "../../../components/ProgressBar"; +import Card from "../../../components/Card"; interface ResourceListItemProps { type: "LXC" | "QEMU"; @@ -42,54 +45,6 @@ export function ResourceListItem({ type, resource }: ResourceListItemProps) { ); } -interface ProgressBarProps { - label: string; - value: number; - max: number; - formatFn?: (input: number) => string; -} - -export function ProgressBar({ label, value, max, formatFn }: ProgressBarProps) { - const percentage = formatPercentage(value / max); - return ( - <View> - <View - style={tw.style("flex flex-row justify-between items-center mb-1 px-1")} - > - <Text> - {label} ({percentage}) - </Text> - {formatFn && ( - <Text> - {formatFn(value)}/{formatFn(max)} - </Text> - )} - </View> - <View style={tw.style("h-3 w-full bg-slate-300 rounded-lg")}> - <View - style={tw.style("h-full bg-slate-700 rounded-lg", { - width: percentage, - })} - /> - </View> - </View> - ); -} - -interface GaugeProps { - label: string; - value: string; -} - -export function Gauge({ label, value }: GaugeProps) { - return ( - <View> - <Text>{label}</Text> - <Text style={tw.style("text-2xl")}>{value}</Text> - </View> - ); -} - export default function NodePage() { const { node: nodeName } = useSearchParams<{ node: string }>(); const node = useNode(nodeName); @@ -105,14 +60,10 @@ export default function NodePage() { }, [node.qemu.data]); return ( - <View> - <SafeAreaView> - <ScrollView> - <View - style={tw.style( - "bg-white m-2 p-3 rounded-lg border border-slate-200" - )} - > + <ScrollView> + <Card> + <View style={tw.style("p-1")}> + <View style={tw.style("px-1")}> <Text style={tw.style("text-2xl font-semibold")}>{nodeName}</Text> {node.status.isSuccess && ( <View style={tw.style("mb-4")}> @@ -120,88 +71,78 @@ export default function NodePage() { <Text>{node.status.data.kversion}</Text> </View> )} - {node.rdd.isSuccess && ( - <View> - <View style={tw.style("mb-2")}> - <ProgressBar label="CPU" value={node.rdd.data.cpu} max={1} /> - </View> - <View style={tw.style("mb-2")}> - <ProgressBar - label="Memory" - value={node.rdd.data.memused} - max={node.rdd.data.memtotal} - formatFn={formatBytes} + </View> + {node.rdd.isSuccess && ( + <View> + <View style={tw.style("mb-2")}> + <ProgressBar label="CPU" value={node.rdd.data.cpu} max={1} /> + </View> + <View style={tw.style("mb-2")}> + <ProgressBar + label="Memory" + value={node.rdd.data.memused} + max={node.rdd.data.memtotal} + formatFn={formatBytes} + /> + </View> + <View style={tw.style("mb-2")}> + <ProgressBar + label="Storage" + value={node.rdd.data.rootused} + max={node.rdd.data.roottotal} + formatFn={formatBytes} + /> + </View> + <View + style={tw.style( + "flex flex-row justify-evenly items-center p-2" + )} + > + <View style={tw.style("flex-1")}> + <Gauge + label="Up" + value={`${formatBytes(node.rdd.data.netout)}/s`} /> </View> - <View style={tw.style("mb-2")}> - <ProgressBar - label="Storage" - value={node.rdd.data.rootused} - max={node.rdd.data.roottotal} - formatFn={formatBytes} + <View style={tw.style("flex-1")}> + <Gauge + label="Down" + value={`${formatBytes(node.rdd.data.netin)}/s`} /> </View> - <View - style={tw.style( - "flex flex-row justify-evenly items-center p-2" - )} - > - <View style={tw.style("flex-1")}> - <Gauge - label="Up" - value={`${formatBytes(node.rdd.data.netout)}/s`} - /> - </View> - <View style={tw.style("flex-1")}> - <Gauge - label="Down" - value={`${formatBytes(node.rdd.data.netin)}/s`} - /> - </View> - </View> </View> - )} - </View> - <Text style={tw.style("ml-6 mt-4")}>LXC Containers</Text> - <View - style={tw.style( - "bg-white m-2 p-1 rounded-lg border border-slate-200" - )} + </View> + )} + </View> + </Card> + <Card label="LXC Containers"> + {sortedLXCs.map((lxc) => ( + <Link + key={lxc.vmid} + href={{ + pathname: "/nodes/[name]/lxc/[vmid]", + params: { name: nodeName, vmid: lxc.vmid }, + }} + style={tw.style("m-2")} > - {sortedLXCs.map((lxc) => ( - <Link - key={lxc.vmid} - href={{ - pathname: "/nodes/[name]/lxc/[vmid]", - params: { name: nodeName, vmid: lxc.vmid }, - }} - style={tw.style("m-2")} - > - <ResourceListItem type="LXC" resource={lxc} /> - </Link> - ))} - </View> - <Text style={tw.style("ml-6 mt-4")}>Virtual Machines</Text> - <View - style={tw.style( - "bg-white m-2 p-1 rounded-lg border border-slate-200" - )} + <ResourceListItem type="LXC" resource={lxc} /> + </Link> + ))} + </Card> + <Card label="Virtual Machine"> + {sortedVMs.map((vm) => ( + <Link + key={vm.vmid} + href={{ + pathname: "/nodes/[node]/lxc/[vmid]", + params: { node: nodeName, vmid: vm.vmid }, + }} + style={tw.style("m-2")} > - {sortedVMs.map((vm) => ( - <Link - key={vm.vmid} - href={{ - pathname: "/nodes/[node]/lxc/[vmid]", - params: { node: nodeName, vmid: vm.vmid }, - }} - style={tw.style("m-2")} - > - <ResourceListItem type="QEMU" resource={vm} /> - </Link> - ))} - </View> - </ScrollView> - </SafeAreaView> - </View> + <ResourceListItem type="QEMU" resource={vm} /> + </Link> + ))} + </Card> + </ScrollView> ); } diff --git a/app/nodes/index.tsx b/app/nodes/index.tsx index a2cdb79..1cc298b 100644 --- a/app/nodes/index.tsx +++ b/app/nodes/index.tsx @@ -5,6 +5,7 @@ import Icon from "@expo/vector-icons/Feather"; import tw from "twrnc"; import { formatPercentage } from "../../lib/helper/format"; import { ScrollView } from "react-native-gesture-handler"; +import Card from "../../components/Card"; export function NodeListItem({ node }: { node: ProxmoxNode }) { return ( @@ -38,43 +39,31 @@ export default function HomePage() { const nodes = useNodes(); return ( - <View> - <ScrollView> - <SafeAreaView> - <Text style={tw.style("ml-6 mt-4")}>Nodes</Text> - <View - style={tw.style( - "bg-white m-2 p-1 rounded-lg border border-slate-200" - )} - > - {nodes.isSuccess && - nodes.data.map((node) => ( - <Link - key={node.node} - href={{ - pathname: "/nodes/[node]", - params: { node: node.node }, - }} - disabled={node.status !== "online"} - style={tw.style("p-2")} - > - <NodeListItem node={node} /> - </Link> - ))} - </View> + <ScrollView> + <Card label="Nodes"> + {nodes.isSuccess && + nodes.data.map((node) => ( + <Link + key={node.node} + href={{ + pathname: "/nodes/[node]", + params: { node: node.node }, + }} + disabled={node.status !== "online"} + style={tw.style("p-2")} + > + <NodeListItem node={node} /> + </Link> + ))} + </Card> - <Text style={tw.style("ml-6 mt-4")}>Storage</Text> - <View - style={tw.style( - "bg-white m-2 p-1 rounded-lg border border-slate-200" - )} - > - <Text style={tw.style("px-2 py-7 flex flex-row text-center")}> - Currently not supported - </Text> - </View> - </SafeAreaView> - </ScrollView> - </View> + <Card label="Storage"> + <View + style={tw.style("flex flex-row justify-center items-center h-16")} + > + <Text>Currently not supported</Text> + </View> + </Card> + </ScrollView> ); } diff --git a/components/Card/index.tsx b/components/Card/index.tsx new file mode 100644 index 0000000..d958b40 --- /dev/null +++ b/components/Card/index.tsx @@ -0,0 +1,20 @@ +import { View, Text } from "react-native"; +import tw from "twrnc"; + +interface CardProps { + label?: string; + children: React.ReactNode; +} + +export default function Card({ label, children }: CardProps) { + return ( + <> + {label && <Text style={tw.style("ml-6 mt-4")}>{label}</Text>} + <View + style={tw.style("bg-white m-2 p-1 rounded-lg border border-slate-200")} + > + {children} + </View> + </> + ); +} diff --git a/components/Gauge/index.tsx b/components/Gauge/index.tsx new file mode 100644 index 0000000..bc33c6c --- /dev/null +++ b/components/Gauge/index.tsx @@ -0,0 +1,16 @@ +import { View, Text } from "react-native"; +import tw from "twrnc"; + +interface GaugeProps { + label: string; + value: string; +} + +export function Gauge({ label, value }: GaugeProps) { + return ( + <View> + <Text>{label}</Text> + <Text style={tw.style("text-2xl")}>{value}</Text> + </View> + ); +} diff --git a/components/Login/index.tsx b/components/Login/index.tsx index 5ab9524..f72c4e2 100644 --- a/components/Login/index.tsx +++ b/components/Login/index.tsx @@ -1,12 +1,49 @@ -import { View, Button, Text, TextInput } from "react-native"; +import { + View, + TouchableOpacity, + Text, + TextInput, + SafeAreaView, + TextInputProps, +} from "react-native"; import React, { useState } from "react"; +import tw from "twrnc"; import useAuthStore from "../../stores/useAuthStore"; import { useTicketMut } from "../../hooks/useTicket"; +interface FormFieldProps extends TextInputProps { + label: string; +} + +export function FormField({ + label, + value, + onChangeText, + placeholder, + secureTextEntry, + inputMode, +}: FormFieldProps) { + return ( + <> + <Text style={tw.style("ml-2 mb-2 text-slate-500")}>{label}</Text> + <TextInput + style={tw.style( + "border rounded-md border-slate-200 bg-slate-100 px-4 py-2 text-base" + )} + value={value} + onChangeText={onChangeText} + placeholder={placeholder} + secureTextEntry={secureTextEntry} + inputMode={inputMode} + /> + </> + ); +} + export default function Login() { - const [domain, setDomain] = useState(""); + const [domain, setDomain] = useState("https://pve.holowaif.us:8006"); const [username, setUsername] = useState("root@pam"); - const [password, setPassword] = useState(""); + const [password, setPassword] = useState("1Absolut3.lyAd0r3Vtube.rs!"); const authStore = useAuthStore(); const ticketMut = useTicketMut({ @@ -17,23 +54,51 @@ export default function Login() { }); return ( - <View> - <Text>Hello</Text> - <TextInput value={domain} onChangeText={setDomain} placeholder="Domain" /> - <TextInput - value={username} - onChangeText={setUsername} - placeholder="Username" - /> - <TextInput - value={password} - onChangeText={setPassword} - placeholder="Password" - /> - <Button - title="Login" - onPress={() => ticketMut.mutate({ domain, username, password })} - /> + <View style={tw.style("flex-1 justify-center")}> + <SafeAreaView style={tw.style("mx-4")}> + <View style={tw.style("ml-2 mb-8")}> + <Text style={tw.style("text-3xl font-semibold text-slate-700 mb-2")}> + PVERN + </Text> + <Text style={tw.style("text-xl font-semibold text-slate-600")}> + Sign In + </Text> + </View> + <View style={tw.style("mb-5")}> + <FormField + inputMode="url" + label="Server URL" + placeholder="https://pve.example.com" + value={domain} + onChangeText={setDomain} + /> + </View> + <View style={tw.style("mb-5")}> + <FormField + autoComplete="username" + label="Username" + value={username} + onChangeText={setUsername} + /> + </View> + <View style={tw.style("mb-10")}> + <FormField + autoComplete="password" + label="Password" + value={password} + onChangeText={setPassword} + secureTextEntry + /> + </View> + <TouchableOpacity + style={tw.style( + "flex bg-slate-700 rounded-md flex flex-row items-center justify-center" + )} + onPress={() => ticketMut.mutate({ domain, username, password })} + > + <Text style={tw.style("text-white font-semibold py-3")}>Sign In</Text> + </TouchableOpacity> + </SafeAreaView> </View> ); } diff --git a/components/ProgressBar/index.tsx b/components/ProgressBar/index.tsx new file mode 100644 index 0000000..641fee9 --- /dev/null +++ b/components/ProgressBar/index.tsx @@ -0,0 +1,42 @@ +import { View, Text } from "react-native"; +import tw from "twrnc"; +import { formatPercentage } from "../../lib/helper/format"; + +interface ProgressBarProps { + label: string; + value: number; + max: number; + formatFn?: (input: number) => string; +} + +export default function ProgressBar({ + label, + value, + max, + formatFn, +}: ProgressBarProps) { + const percentage = formatPercentage(value / max); + return ( + <View> + <View + style={tw.style("flex flex-row justify-between items-center mb-1 px-1")} + > + <Text> + {label} ({percentage}) + </Text> + {formatFn && ( + <Text> + {formatFn(value)}/{formatFn(max)} + </Text> + )} + </View> + <View style={tw.style("h-3 w-full bg-slate-300 rounded-lg")}> + <View + style={tw.style("h-full bg-slate-700 rounded-lg", { + width: percentage, + })} + /> + </View> + </View> + ); +}