2024-11-16 23:50:34 +07:00

172 lines
4.2 KiB
TypeScript

import { View, Text, Spinner, ScrollView } from "tamagui";
import React, { useMemo, useState } from "react";
import { useNavigation } from "expo-router";
import SearchInput from "@/components/ui/search-input";
import { useTermSession } from "@/stores/terminal-sessions";
import { hostFormModal } from "./form";
import { GridLayout } from "@/components/ui/grid-view";
import HostItem from "./host-item";
import { useHosts } from "../hooks/query";
type HostsListProps = {
allowEdit?: boolean;
parentId?: string | null;
onParentIdChange?: (id: string | null) => void;
selected?: string[];
onSelectedChange?: (ids: string[]) => void;
};
const HostList = ({
allowEdit = true,
parentId,
onParentIdChange,
selected = [],
onSelectedChange,
}: HostsListProps) => {
const openSession = useTermSession((i) => i.push);
const navigation = useNavigation();
const [search, setSearch] = useState("");
const { data, isLoading } = useHosts({ parentId });
const hostsList = useMemo(() => {
let items = data || [];
if (search) {
items = items.filter((item: any) => {
const q = search.toLowerCase();
return (
item.label.toLowerCase().includes(q) ||
item.host.toLowerCase().includes(q)
);
});
}
return items.map((i: any) => ({ ...i, key: i.id }));
}, [data, search]);
const groups = useMemo(
() => hostsList.filter((i: any) => i.type === "group"),
[hostsList]
);
const hosts = useMemo(
() => hostsList.filter((i: any) => i.type !== "group"),
[hostsList]
);
const onSelect = (host: any) => {
if (selected.includes(host.id)) {
onSelectedChange?.(selected.filter((i) => i !== host.id));
} else {
onSelectedChange?.([...selected, host.id]);
}
};
const onEdit = (host: any) => {
if (!allowEdit) return;
hostFormModal.onOpen(host);
};
const onOpenTerminal = (host: any) => {
const session: any = {
id: host.id,
label: host.label,
type: host.type,
params: {
hostId: host.id,
},
};
if (host.type === "pve") {
session.params.client = host.metadata?.type === "lxc" ? "xtermjs" : "vnc";
}
openSession(session);
navigation.navigate("terminal" as never);
};
return (
<>
<View p="$4" pb="$3">
<SearchInput
placeholder="Search label, host, or IP..."
value={search}
onChangeText={setSearch}
/>
</View>
{isLoading ? (
<View alignItems="center" justifyContent="center" flex={1}>
<Spinner size="large" />
<Text mt="$4">Loading...</Text>
</View>
) : (
<ScrollView>
{groups.length > 0 && (
<>
<Text mx="$4">Groups</Text>
<ItemList
data={groups}
selected={selected}
onTap={onSelectedChange ? onSelect : undefined}
onMultiTap={(group) => onParentIdChange?.(group.id)}
onEdit={allowEdit ? onEdit : undefined}
/>
</>
)}
<Text mx="$4">Hosts</Text>
{!hosts.length && (
<Text mx="$4" fontSize="$3" mt="$2">
No hosts found
</Text>
)}
<ItemList
data={hosts}
selected={selected}
onTap={onSelectedChange ? onSelect : undefined}
onMultiTap={onOpenTerminal}
onEdit={allowEdit ? onEdit : undefined}
/>
</ScrollView>
)}
</>
);
};
type ItemListProps = {
data?: any[];
selected?: string[];
onTap?: (host: any) => void;
onMultiTap?: (host: any) => void;
onEdit?: (host: any) => void;
};
const ItemList = ({
data,
selected,
onTap,
onMultiTap,
onEdit,
}: ItemListProps) => (
<GridLayout
data={data}
columns={{ sm: 2, lg: 3, xl: 4 }}
padding="$2"
gap="$2.5"
renderItem={(host: any) => (
<HostItem
host={host}
selected={selected?.includes(host.id)}
onTap={onEdit ? () => onTap?.(host) : undefined}
onMultiTap={() => onMultiTap?.(host)}
onEdit={onEdit ? () => onEdit?.(host) : undefined}
/>
)}
/>
);
export default React.memo(HostList);