fix: harden widget review follow-ups
This commit is contained in:
1 parent
419c8434f9
commit
c7431ef1f5
6 files changed
+37
-13
No files matched your search
@@ -3,6 +3,8 @@
|
||||
import { ActionIcon, Anchor, Group, Indicator, Stack, Text } from "@mantine/core";
|
||||
import { IconFileText, IconLink } from "@tabler/icons-react";
|
||||
|
||||
import { useScopedI18n } from "@homarr/translation/client";
|
||||
|
||||
import actionTargetClasses from "../common/action-target.module.css";
|
||||
import { cleanFqdn, getResourceTimestamp, getStatusColor, parseStatus } from "./coolify-utils";
|
||||
|
||||
@@ -25,6 +27,7 @@ interface ResourceRowProps {
|
||||
}
|
||||
|
||||
export function ResourceRow({ item, baseUrl, isTiny, resourceType }: ResourceRowProps) {
|
||||
const t = useScopedI18n("widget.coolify");
|
||||
const status = parseStatus(item.status ?? "");
|
||||
const statusColor = getStatusColor(status);
|
||||
|
||||
@@ -64,7 +67,7 @@ export function ResourceRow({ item, baseUrl, isTiny, resourceType }: ResourceRow
|
||||
href={cleanFqdn(item.fqdn)}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
aria-label={`Open ${item.name}`}
|
||||
aria-label={t("action.openResource", { name: item.name })}
|
||||
size="xs"
|
||||
variant="subtle"
|
||||
c="dimmed"
|
||||
@@ -79,7 +82,7 @@ export function ResourceRow({ item, baseUrl, isTiny, resourceType }: ResourceRow
|
||||
href={logsUrl}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
aria-label={`Open ${item.name} logs`}
|
||||
aria-label={t("action.openLogs", { name: item.name })}
|
||||
size="xs"
|
||||
variant="subtle"
|
||||
c="dimmed"
|
||||
|
||||
@@ -42,7 +42,7 @@ export function ServersSection({
|
||||
</Group>
|
||||
<ActionIcon
|
||||
className={actionTargetClasses.root}
|
||||
aria-label={`${showIp ? tCommon("action.hide") : tCommon("action.show")} IP`}
|
||||
aria-label={t(showIp ? "action.hideIp" : "action.showIp")}
|
||||
size="xs"
|
||||
variant="subtle"
|
||||
c="dimmed"
|
||||
|
||||
@@ -8,7 +8,7 @@ import objectSupport from "dayjs/plugin/objectSupport";
|
||||
import relativeTime from "dayjs/plugin/relativeTime";
|
||||
|
||||
import { clientApi } from "@homarr/api/client";
|
||||
import { useI18n } from "@homarr/translation/client";
|
||||
import { useScopedI18n } from "@homarr/translation/client";
|
||||
|
||||
import type { WidgetComponentProps } from "../../definition";
|
||||
import { WifiVariant } from "./variants/wifi-variant";
|
||||
@@ -25,12 +25,17 @@ export default function NetworkControllerNetworkStatusWidget({
|
||||
width,
|
||||
height,
|
||||
}: WidgetComponentProps<"networkControllerStatus">) {
|
||||
const { data: summaries = [], isPending } = clientApi.widget.networkController.summary.useQuery({
|
||||
const {
|
||||
data: summaries,
|
||||
error,
|
||||
isPending,
|
||||
} = clientApi.widget.networkController.summary.useQuery({
|
||||
integrationIds,
|
||||
});
|
||||
const t = useI18n();
|
||||
const t = useScopedI18n("widget.networkControllerStatus");
|
||||
const tCommon = useScopedI18n("common");
|
||||
|
||||
const data = useMemo(() => summaries.flatMap(({ summary }) => summary), [summaries]);
|
||||
const data = useMemo(() => (summaries ?? []).flatMap(({ summary }) => summary), [summaries]);
|
||||
const isAdvanced = displayMode === "advanced";
|
||||
const countWifiGuests = data.reduce((sum, summary) => sum + summary.wifi.guests, 0);
|
||||
const countWifiUsers = data.reduce((sum, summary) => sum + summary.wifi.users, 0);
|
||||
@@ -38,11 +43,13 @@ export default function NetworkControllerNetworkStatusWidget({
|
||||
const countLanUsers = data.reduce((sum, summary) => sum + summary.lan.users, 0);
|
||||
const useHorizontalStats = !isAdvanced && height < 150 && width >= 200;
|
||||
|
||||
if (error && summaries === undefined) throw error;
|
||||
|
||||
if (isPending || data.length === 0) {
|
||||
return (
|
||||
<Center h="100%" p="sm">
|
||||
<Text c="dimmed" size="sm" ta="center">
|
||||
{isPending ? t("common.action.loading") : t("widget.networkControllerStatus.error.integrationsDisconnected")}
|
||||
{isPending ? tCommon("action.loading") : t("error.integrationsDisconnected")}
|
||||
</Text>
|
||||
</Center>
|
||||
);
|
||||
|
||||
@@ -79,7 +79,11 @@ export default function PaperlessNgxWidget({
|
||||
}: WidgetComponentProps<"paperlessNgx">) {
|
||||
const t = useScopedI18n("widget.paperlessNgx");
|
||||
const tCommon = useScopedI18n("common");
|
||||
const { data: stats, isPending } = clientApi.widget.paperlessNgx.getStats.useQuery({
|
||||
const {
|
||||
data: stats,
|
||||
error,
|
||||
isPending,
|
||||
} = clientApi.widget.paperlessNgx.getStats.useQuery({
|
||||
integrationId: integrationIds[0] ?? "",
|
||||
});
|
||||
|
||||
@@ -92,6 +96,7 @@ export default function PaperlessNgxWidget({
|
||||
</Center>
|
||||
);
|
||||
}
|
||||
if (error && stats === undefined) throw error;
|
||||
if (!stats) return <WidgetEmptyState />;
|
||||
|
||||
const canShowInboxHero = options.showDocumentsInbox && options.showDocumentsTotal;
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
import { Center, Flex, ScrollArea, SimpleGrid, Stack, Text } from "@mantine/core";
|
||||
|
||||
import { clientApi } from "@homarr/api/client";
|
||||
import { useScopedI18n } from "@homarr/translation/client";
|
||||
import { useCurrentLocale, useScopedI18n } from "@homarr/translation/client";
|
||||
|
||||
import type { WidgetComponentProps } from "../definition";
|
||||
import { VpnIntegrationCard } from "./vpn-card";
|
||||
@@ -21,6 +21,7 @@ export default function VpnWidget({
|
||||
});
|
||||
const t = useScopedI18n("widget.vpn");
|
||||
const tCommon = useScopedI18n("common");
|
||||
const locale = useCurrentLocale();
|
||||
const dense = displayMode === "compact" && (width < 240 || height < 120);
|
||||
|
||||
if (isPending || integrations.length === 0) {
|
||||
@@ -56,7 +57,7 @@ export default function VpnWidget({
|
||||
<VpnIntegrationCard vpn={result.summary} integrationName={result.integration.name} variant="list" />
|
||||
{!result.error && new Date(result.integration.updatedAt).getTime() > 0 && (
|
||||
<Text size="xs" c="dimmed" ta="right">
|
||||
{new Date(result.integration.updatedAt).toLocaleString()}
|
||||
{new Date(result.integration.updatedAt).toLocaleString(locale)}
|
||||
</Text>
|
||||
)}
|
||||
</Stack>
|
||||
|
||||
@@ -102,8 +102,12 @@ function VpnStatusColumn({
|
||||
compact: boolean;
|
||||
dense: boolean;
|
||||
}) {
|
||||
const t = useScopedI18n("widget.vpn");
|
||||
// Connected shows a shield with a check; anything else (stopped or unavailable) shows a shield with a cross.
|
||||
const ShieldIcon = vpnStatus === RUNNING_STATUS ? IconShieldCheck : IconShieldX;
|
||||
const statusLabel = t(
|
||||
vpnStatus === RUNNING_STATUS ? "status.running" : vpnStatus ? "status.notRunning" : "status.unavailable",
|
||||
);
|
||||
|
||||
return (
|
||||
<Flex
|
||||
@@ -114,7 +118,7 @@ function VpnStatusColumn({
|
||||
align="center"
|
||||
>
|
||||
<ShieldIcon
|
||||
aria-label={vpnStatus || "unavailable"}
|
||||
aria-label={t("status.vpn", { status: statusLabel })}
|
||||
stroke={2}
|
||||
color={getStatusColor(vpnStatus)}
|
||||
size={dense ? 32 : compact ? 44 : 52}
|
||||
@@ -181,7 +185,11 @@ function VpnProviderDetails({ provider, protocol }: { provider: string; protocol
|
||||
}
|
||||
|
||||
function DnsStatusBadge({ status }: { status: string }) {
|
||||
const t = useScopedI18n("widget.vpn");
|
||||
const StatusIcon = status === RUNNING_STATUS ? IconCircleCheckFilled : IconCircleXFilled;
|
||||
const statusLabel = t(
|
||||
status === RUNNING_STATUS ? "status.running" : status ? "status.notRunning" : "status.unavailable",
|
||||
);
|
||||
return (
|
||||
<Badge
|
||||
variant="outline"
|
||||
@@ -191,7 +199,7 @@ function DnsStatusBadge({ status }: { status: string }) {
|
||||
leftSection={<StatusIcon aria-hidden size={9} />}
|
||||
>
|
||||
DNS
|
||||
<VisuallyHidden> {status || "unavailable"}</VisuallyHidden>
|
||||
<VisuallyHidden> {statusLabel}</VisuallyHidden>
|
||||
</Badge>
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user