Split some code into seperate components

This commit is contained in:
niku 2023-03-21 20:09:37 +01:00
parent dddd9fd666
commit 42c2d069ef
6 changed files with 264 additions and 191 deletions

View File

@ -1,10 +1,13 @@
import { Link, Stack, useSearchParams } from "expo-router"; import { Link, useSearchParams } from "expo-router";
import { View, Text, SafeAreaView, ScrollView } from "react-native"; import { View, Text, SafeAreaView, ScrollView, Button } from "react-native";
import { NodeResource, useNode } from "../../../hooks/useNode"; import { NodeResource, useNode } from "../../../hooks/useNode";
import Icon from "@expo/vector-icons/Feather"; import Icon from "@expo/vector-icons/Feather";
import tw from "twrnc"; import tw from "twrnc";
import { formatBytes, formatPercentage } from "../../../lib/helper/format"; import { formatBytes, formatPercentage } from "../../../lib/helper/format";
import { useEffect, useMemo } from "react"; import { useEffect, useMemo } from "react";
import { Gauge } from "../../../components/Gauge";
import ProgressBar from "../../../components/ProgressBar";
import Card from "../../../components/Card";
interface ResourceListItemProps { interface ResourceListItemProps {
type: "LXC" | "QEMU"; 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() { export default function NodePage() {
const { node: nodeName } = useSearchParams<{ node: string }>(); const { node: nodeName } = useSearchParams<{ node: string }>();
const node = useNode(nodeName); const node = useNode(nodeName);
@ -105,14 +60,10 @@ export default function NodePage() {
}, [node.qemu.data]); }, [node.qemu.data]);
return ( return (
<View> <ScrollView>
<SafeAreaView> <Card>
<ScrollView> <View style={tw.style("p-1")}>
<View <View style={tw.style("px-1")}>
style={tw.style(
"bg-white m-2 p-3 rounded-lg border border-slate-200"
)}
>
<Text style={tw.style("text-2xl font-semibold")}>{nodeName}</Text> <Text style={tw.style("text-2xl font-semibold")}>{nodeName}</Text>
{node.status.isSuccess && ( {node.status.isSuccess && (
<View style={tw.style("mb-4")}> <View style={tw.style("mb-4")}>
@ -120,88 +71,78 @@ export default function NodePage() {
<Text>{node.status.data.kversion}</Text> <Text>{node.status.data.kversion}</Text>
</View> </View>
)} )}
{node.rdd.isSuccess && ( </View>
<View> {node.rdd.isSuccess && (
<View style={tw.style("mb-2")}> <View>
<ProgressBar label="CPU" value={node.rdd.data.cpu} max={1} /> <View style={tw.style("mb-2")}>
</View> <ProgressBar label="CPU" value={node.rdd.data.cpu} max={1} />
<View style={tw.style("mb-2")}> </View>
<ProgressBar <View style={tw.style("mb-2")}>
label="Memory" <ProgressBar
value={node.rdd.data.memused} label="Memory"
max={node.rdd.data.memtotal} value={node.rdd.data.memused}
formatFn={formatBytes} 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>
<View style={tw.style("mb-2")}> <View style={tw.style("flex-1")}>
<ProgressBar <Gauge
label="Storage" label="Down"
value={node.rdd.data.rootused} value={`${formatBytes(node.rdd.data.netin)}/s`}
max={node.rdd.data.roottotal}
formatFn={formatBytes}
/> />
</View> </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>
)} </View>
</View> )}
<Text style={tw.style("ml-6 mt-4")}>LXC Containers</Text> </View>
<View </Card>
style={tw.style( <Card label="LXC Containers">
"bg-white m-2 p-1 rounded-lg border border-slate-200" {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) => ( <ResourceListItem type="LXC" resource={lxc} />
<Link </Link>
key={lxc.vmid} ))}
href={{ </Card>
pathname: "/nodes/[name]/lxc/[vmid]", <Card label="Virtual Machine">
params: { name: nodeName, vmid: lxc.vmid }, {sortedVMs.map((vm) => (
}} <Link
style={tw.style("m-2")} key={vm.vmid}
> href={{
<ResourceListItem type="LXC" resource={lxc} /> pathname: "/nodes/[node]/lxc/[vmid]",
</Link> params: { node: nodeName, vmid: vm.vmid },
))} }}
</View> style={tw.style("m-2")}
<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"
)}
> >
{sortedVMs.map((vm) => ( <ResourceListItem type="QEMU" resource={vm} />
<Link </Link>
key={vm.vmid} ))}
href={{ </Card>
pathname: "/nodes/[node]/lxc/[vmid]", </ScrollView>
params: { node: nodeName, vmid: vm.vmid },
}}
style={tw.style("m-2")}
>
<ResourceListItem type="QEMU" resource={vm} />
</Link>
))}
</View>
</ScrollView>
</SafeAreaView>
</View>
); );
} }

View File

@ -5,6 +5,7 @@ import Icon from "@expo/vector-icons/Feather";
import tw from "twrnc"; import tw from "twrnc";
import { formatPercentage } from "../../lib/helper/format"; import { formatPercentage } from "../../lib/helper/format";
import { ScrollView } from "react-native-gesture-handler"; import { ScrollView } from "react-native-gesture-handler";
import Card from "../../components/Card";
export function NodeListItem({ node }: { node: ProxmoxNode }) { export function NodeListItem({ node }: { node: ProxmoxNode }) {
return ( return (
@ -38,43 +39,31 @@ export default function HomePage() {
const nodes = useNodes(); const nodes = useNodes();
return ( return (
<View> <ScrollView>
<ScrollView> <Card label="Nodes">
<SafeAreaView> {nodes.isSuccess &&
<Text style={tw.style("ml-6 mt-4")}>Nodes</Text> nodes.data.map((node) => (
<View <Link
style={tw.style( key={node.node}
"bg-white m-2 p-1 rounded-lg border border-slate-200" href={{
)} pathname: "/nodes/[node]",
> params: { node: node.node },
{nodes.isSuccess && }}
nodes.data.map((node) => ( disabled={node.status !== "online"}
<Link style={tw.style("p-2")}
key={node.node} >
href={{ <NodeListItem node={node} />
pathname: "/nodes/[node]", </Link>
params: { node: node.node }, ))}
}} </Card>
disabled={node.status !== "online"}
style={tw.style("p-2")}
>
<NodeListItem node={node} />
</Link>
))}
</View>
<Text style={tw.style("ml-6 mt-4")}>Storage</Text> <Card label="Storage">
<View <View
style={tw.style( style={tw.style("flex flex-row justify-center items-center h-16")}
"bg-white m-2 p-1 rounded-lg border border-slate-200" >
)} <Text>Currently not supported</Text>
> </View>
<Text style={tw.style("px-2 py-7 flex flex-row text-center")}> </Card>
Currently not supported </ScrollView>
</Text>
</View>
</SafeAreaView>
</ScrollView>
</View>
); );
} }

20
components/Card/index.tsx Normal file
View File

@ -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>
</>
);
}

View File

@ -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>
);
}

View File

@ -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 React, { useState } from "react";
import tw from "twrnc";
import useAuthStore from "../../stores/useAuthStore"; import useAuthStore from "../../stores/useAuthStore";
import { useTicketMut } from "../../hooks/useTicket"; 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() { export default function Login() {
const [domain, setDomain] = useState(""); const [domain, setDomain] = useState("https://pve.holowaif.us:8006");
const [username, setUsername] = useState("root@pam"); const [username, setUsername] = useState("root@pam");
const [password, setPassword] = useState(""); const [password, setPassword] = useState("1Absolut3.lyAd0r3Vtube.rs!");
const authStore = useAuthStore(); const authStore = useAuthStore();
const ticketMut = useTicketMut({ const ticketMut = useTicketMut({
@ -17,23 +54,51 @@ export default function Login() {
}); });
return ( return (
<View> <View style={tw.style("flex-1 justify-center")}>
<Text>Hello</Text> <SafeAreaView style={tw.style("mx-4")}>
<TextInput value={domain} onChangeText={setDomain} placeholder="Domain" /> <View style={tw.style("ml-2 mb-8")}>
<TextInput <Text style={tw.style("text-3xl font-semibold text-slate-700 mb-2")}>
value={username} PVERN
onChangeText={setUsername} </Text>
placeholder="Username" <Text style={tw.style("text-xl font-semibold text-slate-600")}>
/> Sign In
<TextInput </Text>
value={password} </View>
onChangeText={setPassword} <View style={tw.style("mb-5")}>
placeholder="Password" <FormField
/> inputMode="url"
<Button label="Server URL"
title="Login" placeholder="https://pve.example.com"
onPress={() => ticketMut.mutate({ domain, username, password })} 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> </View>
); );
} }

View File

@ -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>
);
}