feat: show rsshub error message

This commit is contained in:
Stephen Zhou 2025-05-13 15:34:29 +08:00
parent 4213459ae0
commit ed95fb6b23
No known key found for this signature in database
6 changed files with 142 additions and 49 deletions

View File

@ -0,0 +1,45 @@
import {
Tooltip,
TooltipContent,
TooltipPortal,
TooltipTrigger,
} from "@follow/components/ui/tooltip/index.js"
import dayjs from "dayjs"
import { useTranslation } from "react-i18next"
export function ErrorTooltip({
errorAt,
errorMessage,
children,
showWhenError = false,
}: {
errorMessage?: string | null
errorAt?: string | null
children: React.ReactNode
showWhenError?: boolean
}) {
const { t } = useTranslation()
if (!errorAt || !errorMessage) {
return showWhenError ? children : null
}
return (
<Tooltip delayDuration={300}>
<TooltipTrigger asChild>{children}</TooltipTrigger>
<TooltipPortal>
<TooltipContent>
<div className="flex items-center gap-1">
<i className="i-mgc-time-cute-re" />
{t("feed_item.error_since")}{" "}
{dayjs.duration(dayjs(errorAt).diff(dayjs(), "minute"), "minute").humanize(true)}
</div>
{!!errorMessage && (
<div className="flex items-center gap-1">
<i className="i-mgc-bug-cute-re" />
{errorMessage}
</div>
)}
</TooltipContent>
</TooltipPortal>
</Tooltip>
)
}

View File

@ -11,13 +11,13 @@ import { EllipsisHorizontalTextWithTooltip } from "@follow/components/ui/typogra
import type { FeedViewType } from "@follow/constants"
import { nextFrame } from "@follow/utils/dom"
import { cn, isKeyForMultiSelectPressed } from "@follow/utils/utils"
import dayjs from "dayjs"
import { createElement, memo, use, useCallback, useState } from "react"
import { useTranslation } from "react-i18next"
import { MenuItemSeparator, MenuItemText, useShowContextMenu } from "~/atoms/context-menu"
import { getMainContainerElement } from "~/atoms/dom"
import { useGeneralSettingKey } from "~/atoms/settings/general"
import { ErrorTooltip } from "~/components/common/ErrorTooltip"
import { useFeedActions, useInboxActions, useListActions } from "~/hooks/biz/useFeedActions"
import { useFollow } from "~/hooks/biz/useFollow"
import { useNavigateEntry } from "~/hooks/biz/useNavigateEntry"
@ -187,29 +187,10 @@ const FeedItemImpl = ({ view, feedId, className, isPreview }: FeedItemProps) =>
<div className={cn("flex min-w-0 items-center", isFeed && feed.errorAt && "text-red")}>
<FeedIcon fallback feed={feed} size={16} />
<FeedTitle feed={feed} />
{isFeed && feed.errorAt && (
<Tooltip delayDuration={300}>
<TooltipTrigger asChild>
<i className="i-mgc-wifi-off-cute-re ml-1 shrink-0 text-base" />
</TooltipTrigger>
<TooltipPortal>
<TooltipContent>
<div className="flex items-center gap-1">
<i className="i-mgc-time-cute-re" />
{t("feed_item.error_since")}{" "}
{dayjs
.duration(dayjs(feed.errorAt).diff(dayjs(), "minute"), "minute")
.humanize(true)}
</div>
{!!feed.errorMessage && (
<div className="flex items-center gap-1">
<i className="i-mgc-bug-cute-re" />
{feed.errorMessage}
</div>
)}
</TooltipContent>
</TooltipPortal>
</Tooltip>
{isFeed && (
<ErrorTooltip errorAt={feed.errorAt} errorMessage={feed.errorMessage}>
<i className="i-mgc-wifi-off-cute-re ml-1 shrink-0 text-base" />
</ErrorTooltip>
)}
{subscription?.isPrivate && (
<Tooltip delayDuration={300}>

View File

@ -14,6 +14,7 @@ import { useTranslation } from "react-i18next"
import RSSHubIconUrl from "~/assets/rsshub-icon.png?url"
import { whoami } from "~/atoms/user"
import { ErrorTooltip } from "~/components/common/ErrorTooltip"
import { useModalStack } from "~/components/ui/modal/stacked/hooks"
import { useAuthQuery } from "~/hooks/common"
import { useSubViewTitle } from "~/modules/app-layout/subview/hooks"
@ -129,6 +130,13 @@ function List({ data }: { data?: RSSHubModel[] }) {
return 1
}
if (a.errorMessage && !b.errorMessage) {
return 1
}
if (!a.errorMessage && b.errorMessage) {
return -1
}
const loadA = Math.min((a.userCount ?? 0) / (a.userLimit ?? Infinity), 1)
const loadB = Math.min((b.userCount ?? 0) / (b.userLimit ?? Infinity), 1)
@ -191,31 +199,7 @@ function List({ data }: { data?: RSSHubModel[] }) {
</TableCell>
<TableCell className="text-right">
<div className="flex w-max items-center gap-2">
<Button
buttonClassName="shrink-0"
disabled={
instance.userCount && instance.userLimit
? instance.userCount >= instance.userLimit
: false
}
variant={
status?.data?.usage?.rsshubId === instance.id ? "outline" : "primary"
}
onClick={() => {
present({
title: t("rsshub.useModal.title"),
content: ({ dismiss }) => (
<SetModalContent dismiss={dismiss} instance={instance} />
),
})
}}
>
{t(
status?.data?.usage?.rsshubId === instance.id
? "rsshub.table.inuse"
: "rsshub.table.use",
)}
</Button>
<SelectInstanceButton instance={instance} />
{me?.id === instance.ownerUserId && (
<Button
variant="outline"
@ -256,3 +240,39 @@ function List({ data }: { data?: RSSHubModel[] }) {
</Table>
)
}
function SelectInstanceButton({ instance }: { instance: RSSHubModel }) {
const { t } = useTranslation("settings")
const { present } = useModalStack()
const status = useAuthQuery(Queries.rsshub.status())
const isNotAvailable = !!instance.errorMessage
const limitReached =
instance.userCount && instance.userLimit ? instance.userCount >= instance.userLimit : false
return (
<ErrorTooltip errorAt={instance.errorAt} errorMessage={instance.errorMessage} showWhenError>
<Button
buttonClassName="shrink-0"
disabled={isNotAvailable || limitReached}
variant={status?.data?.usage?.rsshubId === instance.id ? "outline" : "primary"}
onClick={() => {
present({
title: t("rsshub.useModal.title"),
content: ({ dismiss }) => <SetModalContent dismiss={dismiss} instance={instance} />,
})
}}
>
{t(
status?.data?.usage?.rsshubId === instance.id
? "rsshub.table.inuse"
: isNotAvailable
? "rsshub.table.unavailable"
: limitReached
? "rsshub.table.limit_reached"
: "rsshub.table.use",
)}
</Button>
</ErrorTooltip>
)
}

View File

@ -64,6 +64,7 @@
"13004": "RSSHub user limit exceeded",
"13005": "RSSHub purchase not found",
"13006": "RSSHub config invalid",
"13007": "This RSSHub instance is unavailable currently",
"14000": "Invalid file",
"14001": "File too large",
"14002": "Upload failed"

View File

@ -393,10 +393,12 @@
"rsshub.table.description": "Description",
"rsshub.table.edit": "Edit",
"rsshub.table.inuse": "In Use",
"rsshub.table.limit_reached": "Limit Reached",
"rsshub.table.official": "Official",
"rsshub.table.owner": "Owner",
"rsshub.table.price": "Monthly Price",
"rsshub.table.private": "Private",
"rsshub.table.unavailable": "Unavailable",
"rsshub.table.unlimited": "Unlimited",
"rsshub.table.use": "Use",
"rsshub.table.userCount": "User Count",

View File

@ -5211,6 +5211,40 @@ declare const rsshub: drizzle_orm_pg_core.PgTableWithColumns<{
identity: undefined;
generated: undefined;
}, {}, {}>;
errorMessage: drizzle_orm_pg_core.PgColumn<{
name: "error_message";
tableName: "rsshub";
dataType: "string";
columnType: "PgText";
data: string;
driverParam: string;
notNull: false;
hasDefault: false;
isPrimaryKey: false;
isAutoincrement: false;
hasRuntimeDefault: false;
enumValues: [string, ...string[]];
baseColumn: never;
identity: undefined;
generated: undefined;
}, {}, {}>;
errorAt: drizzle_orm_pg_core.PgColumn<{
name: "error_at";
tableName: "rsshub";
dataType: "date";
columnType: "PgTimestamp";
data: Date;
driverParam: string;
notNull: false;
hasDefault: false;
isPrimaryKey: false;
isAutoincrement: false;
hasRuntimeDefault: false;
enumValues: undefined;
baseColumn: never;
identity: undefined;
generated: undefined;
}, {}, {}>;
};
dialect: "pg";
}>;
@ -5222,9 +5256,13 @@ declare const rsshubOpenAPISchema: zod.ZodObject<{
price: zod.ZodNumber;
description: zod.ZodNullable<zod.ZodString>;
userLimit: zod.ZodNullable<zod.ZodNumber>;
errorMessage: zod.ZodNullable<zod.ZodString>;
errorAt: zod.ZodNullable<zod.ZodString>;
}, zod.UnknownKeysParam, zod.ZodTypeAny, {
id: string;
description: string | null;
errorMessage: string | null;
errorAt: string | null;
ownerUserId: string;
baseUrl: string;
accessKey: string | null;
@ -5233,6 +5271,8 @@ declare const rsshubOpenAPISchema: zod.ZodObject<{
}, {
id: string;
description: string | null;
errorMessage: string | null;
errorAt: string | null;
ownerUserId: string;
baseUrl: string;
accessKey: string | null;
@ -16844,6 +16884,8 @@ declare const _routes: hono_hono_base.HonoBase<Env, ({
data: {
id: string;
description: string | null;
errorMessage: string | null;
errorAt: string | null;
ownerUserId: string;
owner: {
id: string;
@ -16910,6 +16952,8 @@ declare const _routes: hono_hono_base.HonoBase<Env, ({
instance: {
id: string;
description: string | null;
errorMessage: string | null;
errorAt: string | null;
ownerUserId: string;
price: number;
userLimit: number | null;