mirror of
https://github.com/streamyfin/streamyfin.git
synced 2025-08-20 18:37:18 +02:00
158 lines
5.2 KiB
TypeScript
158 lines
5.2 KiB
TypeScript
import * as FileSystem from "expo-file-system";
|
|
import { useNavigation } from "expo-router";
|
|
import * as Sharing from "expo-sharing";
|
|
import { useCallback, useEffect, useMemo, useState } from "react";
|
|
import { useTranslation } from "react-i18next";
|
|
import { ScrollView, TouchableOpacity, View } from "react-native";
|
|
import Collapsible from "react-native-collapsible";
|
|
import { Text } from "@/components/common/Text";
|
|
import { FilterButton } from "@/components/filters/FilterButton";
|
|
import { Loader } from "@/components/Loader";
|
|
import { LogLevel, useLog, writeErrorLog } from "@/utils/log";
|
|
|
|
export default function page() {
|
|
const navigation = useNavigation();
|
|
const { logs } = useLog();
|
|
const { t } = useTranslation();
|
|
|
|
const defaultLevels: LogLevel[] = ["INFO", "ERROR", "DEBUG", "WARN"];
|
|
const codeBlockStyle = {
|
|
backgroundColor: "#000",
|
|
padding: 10,
|
|
fontFamily: "monospace",
|
|
maxHeight: 300,
|
|
};
|
|
|
|
const [loading, setLoading] = useState<boolean>(false);
|
|
const [state, setState] = useState<Record<string, boolean>>({});
|
|
|
|
const [order, setOrder] = useState<"asc" | "desc">("desc");
|
|
const [levels, setLevels] = useState<LogLevel[]>(defaultLevels);
|
|
|
|
const filteredLogs = useMemo(
|
|
() =>
|
|
logs
|
|
?.filter((log) => levels.includes(log.level))
|
|
?.[
|
|
// Already in asc order as they are recorded. just reverse for desc
|
|
order === "desc" ? "reverse" : "concat"
|
|
]?.(),
|
|
[logs, order, levels],
|
|
);
|
|
|
|
// Sharing it as txt while its formatted allows us to share it with many more applications
|
|
const share = useCallback(async () => {
|
|
const uri = `${FileSystem.documentDirectory}logs.txt`;
|
|
|
|
setLoading(true);
|
|
FileSystem.writeAsStringAsync(uri, JSON.stringify(filteredLogs))
|
|
.then(() => {
|
|
setLoading(false);
|
|
Sharing.shareAsync(uri, { mimeType: "txt", UTI: "txt" });
|
|
})
|
|
.catch((e) =>
|
|
writeErrorLog("Something went wrong attempting to export", e),
|
|
)
|
|
.finally(() => setLoading(false));
|
|
}, [filteredLogs]);
|
|
|
|
useEffect(() => {
|
|
navigation.setOptions({
|
|
headerRight: () =>
|
|
loading ? (
|
|
<Loader />
|
|
) : (
|
|
<TouchableOpacity onPress={share}>
|
|
<Text>{t("home.settings.logs.export_logs")}</Text>
|
|
</TouchableOpacity>
|
|
),
|
|
});
|
|
}, [share, loading]);
|
|
|
|
return (
|
|
<>
|
|
<View className='flex flex-row justify-end py-2 px-4 space-x-2'>
|
|
<FilterButton
|
|
id='order'
|
|
queryKey='log'
|
|
queryFn={async () => ["asc", "desc"]}
|
|
set={(values) => setOrder(values[0])}
|
|
values={[order]}
|
|
title={t("library.filters.sort_order")}
|
|
renderItemLabel={(order) => t(`library.filters.${order}`)}
|
|
showSearch={false}
|
|
/>
|
|
<FilterButton
|
|
id='levels'
|
|
queryKey='log'
|
|
queryFn={async () => defaultLevels}
|
|
set={setLevels}
|
|
values={levels}
|
|
title={t("home.settings.logs.level")}
|
|
renderItemLabel={(level) => level}
|
|
showSearch={false}
|
|
multiple={true}
|
|
/>
|
|
</View>
|
|
<ScrollView className='pb-4 px-4'>
|
|
<View className='flex flex-col space-y-2'>
|
|
{filteredLogs?.map((log, index) => (
|
|
<View className='bg-neutral-900 rounded-xl p-3' key={index}>
|
|
<TouchableOpacity
|
|
disabled={!log.data}
|
|
onPress={() =>
|
|
setState((v) => ({
|
|
...v,
|
|
[log.timestamp]: !v[log.timestamp],
|
|
}))
|
|
}
|
|
>
|
|
<View className='flex flex-row justify-between'>
|
|
<Text
|
|
className={`mb-1
|
|
${log.level === "INFO" && "text-blue-500"}
|
|
${log.level === "ERROR" && "text-red-500"}
|
|
${log.level === "DEBUG" && "text-purple-500"}
|
|
`}
|
|
>
|
|
{log.level}
|
|
</Text>
|
|
|
|
<Text className='text-xs'>
|
|
{new Date(log.timestamp).toLocaleString()}
|
|
</Text>
|
|
</View>
|
|
<Text uiTextView selectable className='text-xs'>
|
|
{log.message}
|
|
</Text>
|
|
</TouchableOpacity>
|
|
|
|
{log.data && (
|
|
<>
|
|
{!state[log.timestamp] && (
|
|
<Text className='text-xs mt-0.5'>
|
|
{t("home.settings.logs.click_for_more_info")}
|
|
</Text>
|
|
)}
|
|
<Collapsible collapsed={!state[log.timestamp]}>
|
|
<View className='mt-2 flex flex-col space-y-2'>
|
|
<ScrollView className='rounded-xl' style={codeBlockStyle}>
|
|
<Text>{JSON.stringify(log.data, null, 2)}</Text>
|
|
</ScrollView>
|
|
</View>
|
|
</Collapsible>
|
|
</>
|
|
)}
|
|
</View>
|
|
))}
|
|
{filteredLogs?.length === 0 && (
|
|
<Text className='opacity-50'>
|
|
{t("home.settings.logs.no_logs_available")}
|
|
</Text>
|
|
)}
|
|
</View>
|
|
</ScrollView>
|
|
</>
|
|
);
|
|
}
|