Added a new widget for tvheadend
This commit is contained in:
parent
91e529f87a
commit
2da7d42a34
19
docs/widgets/services/tvheadend.md
Normal file
19
docs/widgets/services/tvheadend.md
Normal file
@ -0,0 +1,19 @@
|
||||
---
|
||||
title: Tvheadend
|
||||
description: Tvheadend Widget Configuration
|
||||
---
|
||||
|
||||
Shows the status of the DVR feature (upcoming, finished, failed recordings) as well as a list of active subscriptions.
|
||||
|
||||
Learn more about [Tvheadend](https://tvheadend.org/).
|
||||
|
||||
Allowed fields: `["upcoming", "finished", "failed"]`.
|
||||
|
||||
```yaml
|
||||
widget:
|
||||
type: tvheadend
|
||||
url: http://tvheadend.host.or.ip
|
||||
username: user
|
||||
password: pass
|
||||
fields: ["upcoming", "finished", "failed"]
|
||||
```
|
||||
@ -1007,5 +1007,10 @@
|
||||
"issues": "Issues",
|
||||
"merges": "Merge Requests",
|
||||
"projects": "Projects"
|
||||
},
|
||||
"tvheadend": {
|
||||
"upcoming": "Upcoming",
|
||||
"finished": "Finished",
|
||||
"failed": "Failed"
|
||||
}
|
||||
}
|
||||
|
||||
@ -127,6 +127,7 @@ const components = {
|
||||
transmission: dynamic(() => import("./transmission/component")),
|
||||
tubearchivist: dynamic(() => import("./tubearchivist/component")),
|
||||
truenas: dynamic(() => import("./truenas/component")),
|
||||
tvheadend: dynamic(() => import("./tvheadend/component")),
|
||||
unifi: dynamic(() => import("./unifi/component")),
|
||||
unmanic: dynamic(() => import("./unmanic/component")),
|
||||
uptimekuma: dynamic(() => import("./uptimekuma/component")),
|
||||
|
||||
89
src/widgets/tvheadend/component.jsx
Normal file
89
src/widgets/tvheadend/component.jsx
Normal file
@ -0,0 +1,89 @@
|
||||
import { useTranslation } from "next-i18next";
|
||||
|
||||
import Container from "components/services/widget/container";
|
||||
import Block from "components/services/widget/block";
|
||||
import useWidgetAPI from "utils/proxy/use-widget-api";
|
||||
import Subscription from "widgets/tvheadend/subscription";
|
||||
|
||||
function timeAgo(ticks) {
|
||||
const now = Date.now(); // Current time in milliseconds
|
||||
const inputTime = ticks * 1000; // Convert the input ticks from seconds to milliseconds
|
||||
const difference = now - inputTime; // Difference in milliseconds
|
||||
|
||||
const seconds = Math.floor((difference / 1000) % 60);
|
||||
const minutes = Math.floor((difference / (1000 * 60)) % 60);
|
||||
const hours = Math.floor((difference / (1000 * 60 * 60)) % 24);
|
||||
return { hours, minutes, seconds };
|
||||
}
|
||||
|
||||
function timeAgoToString(ticks) {
|
||||
const { hours, minutes, seconds } = timeAgo(ticks);
|
||||
const parts = [];
|
||||
if (hours > 0) {
|
||||
parts.push(hours);
|
||||
}
|
||||
parts.push(minutes);
|
||||
parts.push(seconds);
|
||||
|
||||
return parts.map((part) => part.toString().padStart(2, "0")).join(":");
|
||||
}
|
||||
|
||||
export default function Component({ service }) {
|
||||
const { t } = useTranslation();
|
||||
const { widget } = service;
|
||||
|
||||
const { data: dvrData, error: dvrError } = useWidgetAPI(widget, "dvr", {
|
||||
refreshInterval: 60000,
|
||||
});
|
||||
const { data: subscriptionsData, error: subscriptionsError } = useWidgetAPI(widget, "subscriptions", {
|
||||
refreshInterval: 5000,
|
||||
});
|
||||
|
||||
if (dvrError || subscriptionsError) {
|
||||
const finalError = dvrError ?? subscriptionsError;
|
||||
return <Container service={service} error={finalError} />;
|
||||
}
|
||||
|
||||
if (!dvrData || !subscriptionsData) {
|
||||
return (
|
||||
<Container service={service}>
|
||||
<Block label="tvheadend.upcoming" />
|
||||
<Block label="tvheadend.finished" />
|
||||
<Block label="tvheadend.failed" />
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
|
||||
const upcomingCount = dvrData.entries.filter((entry) => entry.sched_status === "scheduled").length;
|
||||
const finishedCount = dvrData.entries.filter((entry) => entry.sched_status === "completed").length;
|
||||
const failedCount = dvrData.entries.filter((entry) => entry.sched_status === "failed").length;
|
||||
|
||||
const hasSubscriptions = Array.isArray(subscriptionsData.entries) && subscriptionsData.entries.length > 0;
|
||||
|
||||
return (
|
||||
<>
|
||||
<Container service={service}>
|
||||
<Block label="tvheadend.upcoming" value={t("common.number", { value: upcomingCount })} />
|
||||
<Block label="tvheadend.finished" value={t("common.number", { value: finishedCount })} />
|
||||
<Block label="tvheadend.failed" value={t("common.number", { value: failedCount })} />
|
||||
</Container>
|
||||
{hasSubscriptions &&
|
||||
subscriptionsData.entries
|
||||
.filter(
|
||||
(entry) =>
|
||||
entry.channel &&
|
||||
entry.id &&
|
||||
entry.start && // Only include valid entries
|
||||
entry.state === "Running", // and being watched (Idle=downloading)
|
||||
)
|
||||
.sort((a, b) => a.channel.localeCompare(b.channel))
|
||||
.map((subscription) => (
|
||||
<Subscription
|
||||
key={subscription.id}
|
||||
channel={subscription.channel}
|
||||
sinceStart={timeAgoToString(subscription.start)}
|
||||
/>
|
||||
))}
|
||||
</>
|
||||
);
|
||||
}
|
||||
11
src/widgets/tvheadend/subscription.jsx
Normal file
11
src/widgets/tvheadend/subscription.jsx
Normal file
@ -0,0 +1,11 @@
|
||||
export default function Subscription({ channel, sinceStart }) {
|
||||
return (
|
||||
<div className="flex flex-col pb-1 mx-1">
|
||||
<div className="text-theme-700 dark:text-theme-200 text-xs relative h-5 w-full rounded-md bg-theme-200/50 dark:bg-theme-900/20 mt-1">
|
||||
<div className="absolute left-2 text-xs mt-[2px]">{channel}</div>
|
||||
<div className="grow " />
|
||||
<div className="self-center text-xs flex justify-end mr-2 mt-[2px]">{sinceStart}</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
19
src/widgets/tvheadend/widget.js
Normal file
19
src/widgets/tvheadend/widget.js
Normal file
@ -0,0 +1,19 @@
|
||||
import genericProxyHandler from "utils/proxy/handlers/generic";
|
||||
|
||||
const widget = {
|
||||
api: "{url}/api/{endpoint}",
|
||||
proxyHandler: genericProxyHandler,
|
||||
|
||||
mappings: {
|
||||
dvr: {
|
||||
endpoint: "dvr/entry/grid",
|
||||
validate: ["entries"],
|
||||
},
|
||||
subscriptions: {
|
||||
endpoint: "status/subscriptions",
|
||||
validate: ["entries"],
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default widget;
|
||||
@ -118,6 +118,7 @@ import traefik from "./traefik/widget";
|
||||
import transmission from "./transmission/widget";
|
||||
import tubearchivist from "./tubearchivist/widget";
|
||||
import truenas from "./truenas/widget";
|
||||
import tvheadend from "./tvheadend/widget";
|
||||
import unifi from "./unifi/widget";
|
||||
import unmanic from "./unmanic/widget";
|
||||
import uptimekuma from "./uptimekuma/widget";
|
||||
@ -255,6 +256,7 @@ const widgets = {
|
||||
transmission,
|
||||
tubearchivist,
|
||||
truenas,
|
||||
tvheadend,
|
||||
unifi,
|
||||
unifi_console: unifi,
|
||||
unmanic,
|
||||
|
||||
Loading…
Reference in New Issue
Block a user