linting and cleanup

This commit is contained in:
Ben Phelps 2022-09-07 16:53:24 +03:00
parent 7f041e8303
commit f74e8b9d32
51 changed files with 464 additions and 349 deletions

View file

@ -2,20 +2,22 @@ export default function Item({ bookmark }) {
const { hostname } = new URL(bookmark.href);
return (
<li
onClick={() => {
window.open(bookmark.href, "_blank").focus();
}}
key={bookmark.name}
className="mb-3 cursor-pointer flex rounded-md font-medium text-theme-700 hover:text-theme-800 dark:text-theme-200 dark:hover:text-theme-300 shadow-md shadow-theme-900/10 dark:shadow-theme-900/50 bg-white/50 hover:bg-theme-300/10 dark:bg-white/5 dark:hover:bg-white/10"
>
<div className="flex-shrink-0 flex items-center justify-center w-11 bg-theme-500/10 dark:bg-theme-900/50 text-theme-700 dark:text-theme-200 text-sm font-medium rounded-l-md">
{bookmark.abbr}
</div>
<div className="flex-1 flex items-center justify-between rounded-r-md ">
<div className="flex-1 grow pl-3 py-2 text-xs">{bookmark.name}</div>
<div className="px-2 py-2 truncate text-theme-500 dark:text-theme-400 opacity-50 text-xs">{hostname}</div>
</div>
<li key={bookmark.name}>
<button
type="button"
onClick={() => window.open(bookmark.href, "_blank").focus()}
className="w-full text-left mb-3 cursor-pointer rounded-md font-medium text-theme-700 hover:text-theme-800 dark:text-theme-200 dark:hover:text-theme-300 shadow-md shadow-theme-900/10 dark:shadow-theme-900/50 bg-white/50 hover:bg-theme-300/10 dark:bg-white/5 dark:hover:bg-white/10"
>
<div className="flex">
<div className="flex-shrink-0 flex items-center justify-center w-11 bg-theme-500/10 dark:bg-theme-900/50 text-theme-700 dark:text-theme-200 text-sm font-medium rounded-l-md">
{bookmark.abbr}
</div>
<div className="flex-1 flex items-center justify-between rounded-r-md ">
<div className="flex-1 grow pl-3 py-2 text-xs">{bookmark.name}</div>
<div className="px-2 py-2 truncate text-theme-500 dark:text-theme-400 opacity-50 text-xs">{hostname}</div>
</div>
</div>
</button>
</li>
);
}

View file

@ -2,7 +2,7 @@ import Item from "components/bookmarks/item";
export default function List({ bookmarks }) {
return (
<ul role="list" className="mt-3 flex flex-col">
<ul className="mt-3 flex flex-col">
{bookmarks.map((bookmark) => (
<Item key={bookmark.name} bookmark={bookmark} />
))}

View file

@ -1,7 +1,7 @@
import { useContext } from "react";
import { useContext, Fragment } from "react";
import { IoColorPalette } from "react-icons/io5";
import { Popover, Transition } from "@headlessui/react";
import { Fragment } from "react";
import classNames from "classnames";
import { ColorContext } from "utils/color-context";
@ -39,42 +39,38 @@ export default function ColorToggle() {
return (
<div className="w-full self-center">
<Popover className="relative flex items-center">
{({ open }) => (
<>
<Popover.Button className="outline-none">
<IoColorPalette
className="h-5 w-5 text-theme-800 dark:text-theme-200 transition duration-150 ease-in-out"
aria-hidden="true"
/>
</Popover.Button>
<Transition
as={Fragment}
enter="transition ease-out duration-200"
enterFrom="opacity-0 translate-y-1"
enterTo="opacity-100 translate-y-0"
leave="transition ease-in duration-150"
leaveFrom="opacity-100 translate-y-0"
leaveTo="opacity-0 translate-y-1"
>
<Popover.Panel className="absolute -top-[75px] left-0">
<div className="rounded-md shadow-lg ring-1 ring-black ring-opacity-5">
<div className="relative grid gap-2 p-2 grid-cols-11 shadow-theme-900/10 dark:shadow-theme-900 rounded-md shadow-md">
{colors.map((color) => (
<button role="button" onClick={() => setColor(color)} key={color}>
<div
className={
(active == color ? "border-2" : "border-0") +
` rounded-md w-5 h-5 border-black/50 dark:border-white/50 theme-${color} bg-theme-500`
}
/>
</button>
))}
</div>
</div>
</Popover.Panel>
</Transition>
</>
)}
<Popover.Button className="outline-none">
<IoColorPalette
className="h-5 w-5 text-theme-800 dark:text-theme-200 transition duration-150 ease-in-out"
aria-hidden="true"
/>
</Popover.Button>
<Transition
as={Fragment}
enter="transition ease-out duration-200"
enterFrom="opacity-0 translate-y-1"
enterTo="opacity-100 translate-y-0"
leave="transition ease-in duration-150"
leaveFrom="opacity-100 translate-y-0"
leaveTo="opacity-0 translate-y-1"
>
<Popover.Panel className="absolute -top-[75px] left-0">
<div className="rounded-md shadow-lg ring-1 ring-black ring-opacity-5">
<div className="relative grid gap-2 p-2 grid-cols-11 shadow-theme-900/10 dark:shadow-theme-900 rounded-md shadow-md">
{colors.map((color) => (
<button type="button" onClick={() => setColor(color)} key={color}>
<div
className={classNames(
active === color ? "border-2" : "border-0",
`rounded-md w-5 h-5 border-black/50 dark:border-white/50 theme-${color} bg-theme-500`
)}
/>
</button>
))}
</div>
</div>
</Popover.Panel>
</Transition>
</Popover>
</div>
);

View file

@ -1,5 +1,4 @@
export default function Greeting() {
const name = process.env.NEXT_PUBLIC_DISPLAY_NAME;
const hour = new Date().getHours();
let day = "day";
@ -12,9 +11,5 @@ export default function Greeting() {
day = "evening";
}
return (
<div className="self-end grow text-2xl font-thin text-theme-800 dark:text-theme-200">
Good {day}
</div>
);
return <div className="self-end grow text-2xl font-thin text-theme-800 dark:text-theme-200">Good {day}</div>;
}

View file

@ -1,68 +0,0 @@
import { Fragment, useRef, useState, Children } from "react";
import { Dialog, Transition } from "@headlessui/react";
function classNames(...classes) {
return classes.filter(Boolean).join(" ");
}
const Modal = ({ Toggle, Content }) => {
const [open, setOpen] = useState(false);
const cancelButtonRef = useRef(null);
return (
<>
<Toggle open={open} setOpen={setOpen} />
<Transition.Root show={open} as={Fragment}>
<Dialog
as="div"
className="relative z-10"
initialFocus={cancelButtonRef}
onClose={setOpen}
>
<Transition.Child
as={Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0"
enterTo="opacity-100"
leave="ease-in duration-200"
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<div className="fixed inset-0 bg-theme-900/90 transition-opacity" />
</Transition.Child>
<div className="fixed z-10 inset-0 overflow-y-auto">
<div className="flex items-center justify-center min-h-full">
<Transition.Child
as={Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
enterTo="opacity-100 translate-y-0 sm:scale-100"
leave="ease-in duration-200"
leaveFrom="opacity-100 translate-y-0 sm:scale-100"
leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
>
<Dialog.Panel className="relative rounded-lg shadow-xl transform transition-all my-8 max-w-lg w-full">
<Content open={open} setOpen={setOpen} />
</Dialog.Panel>
</Transition.Child>
</div>
</div>
</Dialog>
</Transition.Root>
</>
);
};
const ModalToggle = ({ open, setOpen, children }) => (
<div onClick={() => setOpen(!open)}>{children}</div>
);
const ModalContent = ({ open, setOpen, children }) => (
<div className="body">{children}</div>
);
Modal.Toggle = ModalToggle;
Modal.Content = ModalContent;
export default Modal;

View file

@ -8,28 +8,32 @@ import Docker from "./widgets/service/docker";
function resolveIcon(icon) {
if (icon.startsWith("http")) {
return `/api/proxy?url=${encodeURIComponent(icon)}`;
} else if (icon.startsWith("/")) {
return icon;
} else {
if (icon.endsWith(".png")) {
return `https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons/png/${icon}`;
} else {
return `https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons/png/${icon}.png`;
}
}
if (icon.startsWith("/")) {
return icon;
}
if (icon.endsWith(".png")) {
return `https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons/png/${icon}`;
}
return `https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons/png/${icon}.png`;
}
export default function Item({ service }) {
return (
<li key={service.name}>
<Disclosure>
<div className={
(service.href && service.href !== "#" ? 'cursor-pointer ' : 'cursor-default ') +
'transition-all h-15 overflow-hidden mb-3 p-1 rounded-md font-medium text-theme-700 hover:text-theme-800 dark:text-theme-200 dark:hover:text-theme-300 shadow-md shadow-theme-900/10 dark:shadow-theme-900/40 bg-white/50 hover:bg-theme-300/10 dark:bg-white/5 dark:hover:bg-white/10'
}>
<div
className={`${
service.href && service.href !== "#" ? "cursor-pointer " : "cursor-default "
}transition-all h-15 overflow-hidden mb-3 p-1 rounded-md font-medium text-theme-700 hover:text-theme-800 dark:text-theme-200 dark:hover:text-theme-300 shadow-md shadow-theme-900/10 dark:shadow-theme-900/40 bg-white/50 hover:bg-theme-300/10 dark:bg-white/5 dark:hover:bg-white/10`}
>
<div className="flex">
{service.icon && (
<div
<button
type="button"
onClick={() => {
if (service.href && service.href !== "#") {
window.open(service.href, "_blank").focus();
@ -38,10 +42,11 @@ export default function Item({ service }) {
className="flex-shrink-0 flex items-center justify-center w-12 "
>
<Image src={resolveIcon(service.icon)} width={32} height={32} alt="logo" />
</div>
</button>
)}
<div
<button
type="button"
onClick={() => {
if (service.href && service.href !== "#") {
window.open(service.href, "_blank").focus();
@ -53,9 +58,12 @@ export default function Item({ service }) {
{service.name}
<p className="text-theme-500 dark:text-theme-400 text-xs font-extralight">{service.description}</p>
</div>
</div>
</button>
{service.container && (
<Disclosure.Button as="div" className="flex-shrink-0 flex items-center justify-center w-12 cursor-pointer">
<Disclosure.Button
as="div"
className="flex-shrink-0 flex items-center justify-center w-12 cursor-pointer"
>
<Status service={service} />
</Disclosure.Button>
)}

View file

@ -2,7 +2,7 @@ import Item from "components/services/item";
export default function List({ services }) {
return (
<ul role="list" className="mt-3 flex flex-col">
<ul className="mt-3 flex flex-col">
{services.map((service) => (
<Item key={service.name} service={service} />
))}

View file

@ -1,28 +1,18 @@
import useSWR from "swr";
export default function Status({ service }) {
const { data, error } = useSWR(
`/api/docker/status/${service.container}/${service.server || ""}`
);
const { data, error } = useSWR(`/api/docker/status/${service.container}/${service.server || ""}`);
if (error) {
return (
<div className="w-3 h-3 bg-rose-300 dark:bg-rose-500 rounded-full" />
);
return <div className="w-3 h-3 bg-rose-300 dark:bg-rose-500 rounded-full" />;
}
if (data && data.status === "running") {
return (
<div className="w-3 h-3 bg-emerald-300 dark:bg-emerald-500 rounded-full" />
);
return <div className="w-3 h-3 bg-emerald-300 dark:bg-emerald-500 rounded-full" />;
}
if (data && data.status === "not found") {
return (
<>
<div className="h-2.5 w-2.5 bg-orange-400/50 dark:bg-yellow-200/40 -rotate-45"></div>
</>
);
return <div className="h-2.5 w-2.5 bg-orange-400/50 dark:bg-yellow-200/40 -rotate-45" />;
}
return <div className="w-3 h-3 bg-black/20 dark:bg-white/40 rounded-full" />;

View file

@ -24,9 +24,9 @@ export default function Emby({ service, title = "Emby" }) {
);
}
const playing = sessionsData.filter((session) => session.hasOwnProperty("NowPlayingItem"));
const playing = sessionsData.filter((session) => session?.NowPlayingItem);
const transcoding = sessionsData.filter(
(session) => session.hasOwnProperty("PlayState") && session.PlayState.PlayMethod === "Transcode"
(session) => session?.PlayState && session.PlayState.PlayMethod === "Transcode"
);
const bitrate = playing.reduce((acc, session) => acc + session.NowPlayingItem.Bitrate, 0);

View file

@ -25,13 +25,9 @@ export default function Rutorrent({ service }) {
);
}
const upload = statusData.reduce((acc, torrent) => {
return acc + parseInt(torrent["d.get_up_rate"]);
}, 0);
const upload = statusData.reduce((acc, torrent) => acc + parseInt(torrent["d.get_up_rate"], 10), 0);
const download = statusData.reduce((acc, torrent) => {
return acc + parseInt(torrent["d.get_down_rate"]);
}, 0);
const download = statusData.reduce((acc, torrent) => acc + parseInt(torrent["d.get_down_rate"], 10), 0);
const active = statusData.filter((torrent) => torrent["d.get_state"] === "1");

View file

@ -24,7 +24,7 @@ export default function Tautulli({ service }) {
);
}
const data = statsData.response.data;
const { data } = statsData.response;
return (
<Widget>

View file

@ -1,7 +1,7 @@
import mapIcon from "utils/owm-condition-map";
export default function Icon({ condition, timeOfDay }) {
const Icon = mapIcon(condition, timeOfDay);
const IconComponent = mapIcon(condition, timeOfDay);
return <Icon className="w-10 h-10 text-theme-800 dark:text-theme-200"></Icon>;
return <IconComponent className="w-10 h-10 text-theme-800 dark:text-theme-200" />;
}

View file

@ -6,7 +6,7 @@ import Icon from "./icon";
export default function OpenWeatherMap({ options }) {
const { data, error } = useSWR(`/api/widgets/openweathermap?${new URLSearchParams(options).toString()}`);
if (error || data?.cod == 401) {
if (error || data?.cod === 401) {
return (
<div className="flex flex-col">
<div className="flex flex-row items-center justify-end">
@ -23,11 +23,11 @@ export default function OpenWeatherMap({ options }) {
}
if (!data) {
return <div className="flex flex-row items-center"></div>;
return <div className="flex flex-row items-center" />;
}
if (data.error) {
return <div className="flex flex-row items-center"></div>;
return <div className="flex flex-row items-center" />;
}
return (

View file

@ -1,6 +1,7 @@
import useSWR from "swr";
import { FiHardDrive } from "react-icons/fi";
import { BiError } from "react-icons/bi";
import { formatBytes } from "utils/stats-helpers";
export default function Disk({ options }) {

View file

@ -1,6 +1,7 @@
import useSWR from "swr";
import { FaMemory } from "react-icons/fa";
import { BiError } from "react-icons/bi";
import { formatBytes } from "utils/stats-helpers";
export default function Memory() {

View file

@ -4,19 +4,17 @@ import Memory from "./memory";
export default function Resources({ options }) {
return (
<>
<div className="flex flex-col max-w:full basis-1/2 sm:basis-auto self-center">
<div className="flex flex-row space-x-4 self-center">
{options.cpu && <Cpu />}
{options.memory && <Memory />}
{options.disk && <Disk options={options} />}
</div>
{options.label && (
<div className="border-t-2 border-theme-800 dark:border-theme-200 mt-1 pt-1 text-center text-theme-800 dark:text-theme-200 text-xs">
{options.label}
</div>
)}
<div className="flex flex-col max-w:full basis-1/2 sm:basis-auto self-center">
<div className="flex flex-row space-x-4 self-center">
{options.cpu && <Cpu />}
{options.memory && <Memory />}
{options.disk && <Disk options={options} />}
</div>
</>
{options.label && (
<div className="border-t-2 border-theme-800 dark:border-theme-200 mt-1 pt-1 text-center text-theme-800 dark:text-theme-200 text-xs">
{options.label}
</div>
)}
</div>
);
}

View file

@ -30,7 +30,7 @@ export default function Search({ options }) {
const [query, setQuery] = useState("");
if (!provider) {
return <></>;
return null;
}
function handleSubmit(event) {
@ -49,11 +49,10 @@ export default function Search({ options }) {
return (
<form className="flex-col relative h-8 my-4 min-w-full md:min-w-fit grow" onSubmit={handleSubmit}>
<div className="flex absolute inset-y-0 left-0 items-center pl-3 pointer-events-none w-full text-theme-800 dark:text-theme-200"></div>
<div className="flex absolute inset-y-0 left-0 items-center pl-3 pointer-events-none w-full text-theme-800 dark:text-theme-200" />
<input
type="search"
autoFocus
className={`overflow-hidden w-full placeholder-theme-900 text-xs text-theme-900 bg-theme-50 rounded-md border border-theme-300 focus:ring-theme-500 focus:border-theme-500 dark:bg-theme-800 dark:border-theme-600 dark:placeholder-theme-400 dark:text-white dark:focus:ring-theme-500 dark:focus:border-theme-500 h-full`}
className="overflow-hidden w-full placeholder-theme-900 text-xs text-theme-900 bg-theme-50 rounded-md border border-theme-300 focus:ring-theme-500 focus:border-theme-500 dark:bg-theme-800 dark:border-theme-600 dark:placeholder-theme-400 dark:text-white dark:focus:ring-theme-500 dark:focus:border-theme-500 h-full"
placeholder="Search..."
onChange={(s) => setQuery(s.currentTarget.value)}
required

View file

@ -1,7 +1,7 @@
import mapIcon from "utils/condition-map";
export default function Icon({ condition, timeOfDay }) {
const Icon = mapIcon(condition, timeOfDay);
const IconComponent = mapIcon(condition, timeOfDay);
return <Icon className="w-10 h-10 text-theme-800 dark:text-theme-200"></Icon>;
return <IconComponent className="w-10 h-10 text-theme-800 dark:text-theme-200" />;
}

View file

@ -4,7 +4,6 @@ import { BiError } from "react-icons/bi";
import Icon from "./icon";
export default function WeatherApi({ options }) {
console.log(options);
const { data, error } = useSWR(`/api/widgets/weather?${new URLSearchParams(options).toString()}`);
if (error) {
@ -24,11 +23,11 @@ export default function WeatherApi({ options }) {
}
if (!data) {
return <div className="flex flex-row items-center justify-end"></div>;
return <div className="flex flex-row items-center justify-end" />;
}
if (data.error) {
return <div className="flex flex-row items-center justify-end"></div>;
return <div className="flex flex-row items-center justify-end" />;
}
return (

View file

@ -1,3 +1,4 @@
/* eslint-disable react/jsx-props-no-spreading */
import { SWRConfig } from "swr";
import "styles/globals.css";
import "styles/weather-icons.css";

View file

@ -1,6 +1,8 @@
import { promises as fs } from "fs";
import path from "path";
import yaml from "js-yaml";
import checkAndCopyConfig from "utils/config";
export default async function handler(req, res) {
@ -11,17 +13,13 @@ export default async function handler(req, res) {
const bookmarks = yaml.load(fileContents);
// map easy to write YAML objects into easy to consume JS arrays
const bookmarksArray = bookmarks.map((group) => {
return {
name: Object.keys(group)[0],
bookmarks: group[Object.keys(group)[0]].map((entries) => {
return {
name: Object.keys(entries)[0],
...entries[Object.keys(entries)[0]][0],
};
}),
};
});
const bookmarksArray = bookmarks.map((group) => ({
name: Object.keys(group)[0],
bookmarks: group[Object.keys(group)[0]].map((entries) => ({
name: Object.keys(entries)[0],
...entries[Object.keys(entries)[0]][0],
})),
}));
res.send(bookmarksArray);
}

View file

@ -1,4 +1,5 @@
import Docker from "dockerode";
import getDockerArguments from "utils/docker";
export default async function handler(req, res) {
@ -21,30 +22,30 @@ export default async function handler(req, res) {
// bad docker connections can result in a <Buffer ...> object?
// in any case, this ensures the result is the expected array
if (!Array.isArray(containers)) {
return res.status(500).send({
res.status(500).send({
error: "query failed",
});
return;
}
const containerNames = containers.map((container) => {
return container.Names[0].replace(/^\//, "");
});
const containerNames = containers.map((container) => container.Names[0].replace(/^\//, ""));
const containerExists = containerNames.includes(containerName);
if (!containerExists) {
return res.status(200).send({
res.status(200).send({
error: "not found",
});
return;
}
const container = docker.getContainer(containerName);
const stats = await container.stats({ stream: false });
return res.status(200).json({
stats: stats,
res.status(200).json({
stats,
});
} catch {
return res.status(500).send({
res.status(500).send({
error: "unknown error",
});
}

View file

@ -1,4 +1,5 @@
import Docker from "dockerode";
import getDockerArguments from "utils/docker";
export default async function handler(req, res) {
@ -25,9 +26,7 @@ export default async function handler(req, res) {
});
}
const containerNames = containers.map((container) => {
return container.Names[0].replace(/^\//, "");
});
const containerNames = containers.map((container) => container.Names[0].replace(/^\//, ""));
const containerExists = containerNames.includes(containerName);
if (!containerExists) {

View file

@ -1,4 +1,5 @@
import https from "https";
import getRawBody from "raw-body";
import { httpRequest, httpsRequest } from "utils/http";
@ -11,7 +12,8 @@ export const config = {
export default async function handler(req, res) {
const headers = ["X-API-Key", "Authorization"].reduce((obj, key) => {
if (req.headers && req.headers.hasOwnProperty(key.toLowerCase())) {
if (req.headers && Object.prototype.hasOwnProperty.call(req.headers, key.toLowerCase())) {
// eslint-disable-next-line no-param-reassign
obj[key] = req.headers[key.toLowerCase()];
}
return obj;
@ -29,23 +31,9 @@ export default async function handler(req, res) {
const [status, contentType, data] = await httpsRequest(url, {
agent: httpsAgent,
method: req.method,
headers: headers,
headers,
body:
req.method == "GET" || req.method == "HEAD"
? null
: await getRawBody(req, {
encoding: "utf8",
}),
});
res.setHeader("Content-Type", contentType);
return res.status(status).send(data);
} else {
const [status, contentType, data] = await httpRequest(url, {
method: req.method,
headers: headers,
body:
req.method == "GET" || req.method == "HEAD"
req.method === "GET" || req.method === "HEAD"
? null
: await getRawBody(req, {
encoding: "utf8",
@ -55,4 +43,17 @@ export default async function handler(req, res) {
res.setHeader("Content-Type", contentType);
return res.status(status).send(data);
}
const [status, contentType, data] = await httpRequest(url, {
method: req.method,
headers,
body:
req.method === "GET" || req.method === "HEAD"
? null
: await getRawBody(req, {
encoding: "utf8",
}),
});
res.setHeader("Content-Type", contentType);
return res.status(status).send(data);
}

View file

@ -1,6 +1,8 @@
import { promises as fs } from "fs";
import path from "path";
import yaml from "js-yaml";
import checkAndCopyConfig from "utils/config";
export default async function handler(req, res) {
@ -11,30 +13,28 @@ export default async function handler(req, res) {
const services = yaml.load(fileContents);
// map easy to write YAML objects into easy to consume JS arrays
const servicesArray = services.map((group) => {
return {
name: Object.keys(group)[0],
services: group[Object.keys(group)[0]].map((entries) => {
const { widget, ...service } = entries[Object.keys(entries)[0]];
let res = {
name: Object.keys(entries)[0],
...service,
const servicesArray = services.map((group) => ({
name: Object.keys(group)[0],
services: group[Object.keys(group)[0]].map((entries) => {
const { widget, ...service } = entries[Object.keys(entries)[0]];
const result = {
name: Object.keys(entries)[0],
...service,
};
if (widget) {
const { type } = widget;
result.widget = {
type,
service_group: Object.keys(group)[0],
service_name: Object.keys(entries)[0],
};
}
if (widget) {
const { type } = widget;
res.widget = {
type: type,
service_group: Object.keys(group)[0],
service_name: Object.keys(entries)[0],
};
}
return res;
}),
};
});
return result;
}),
}));
res.send(servicesArray);
}

View file

@ -33,5 +33,5 @@ export default async function handler(req, res) {
return serviceProxyHandler(req, res);
}
res.status(403).json({ error: "Unkown proxy service type" });
return res.status(403).json({ error: "Unkown proxy service type" });
}

View file

@ -1,6 +1,8 @@
import { promises as fs } from "fs";
import path from "path";
import yaml from "js-yaml";
import checkAndCopyConfig from "utils/config";
export default async function handler(req, res) {
@ -11,12 +13,10 @@ export default async function handler(req, res) {
const widgets = yaml.load(fileContents);
// map easy to write YAML objects into easy to consume JS arrays
const widgetsArray = widgets.map((group) => {
return {
type: Object.keys(group)[0],
options: { ...group[Object.keys(group)[0]] },
};
});
const widgetsArray = widgets.map((group) => ({
type: Object.keys(group)[0],
options: { ...group[Object.keys(group)[0]] },
}));
res.send(widgetsArray);
}

View file

@ -22,7 +22,7 @@ export default async function handler(req, res) {
return res.status(400).json({ error: "Missing API key" });
}
const api_url = `https://api.openweathermap.org/data/2.5/weather?lat=${latitude}&lon=${longitude}&appid=${apiKey}&units=${units}`;
const apiUrl = `https://api.openweathermap.org/data/2.5/weather?lat=${latitude}&lon=${longitude}&appid=${apiKey}&units=${units}`;
res.send(await cachedFetch(api_url, cache));
return res.send(await cachedFetch(apiUrl, cache));
}

View file

@ -1,26 +1,30 @@
import { cpu, drive, mem, netstat } from "node-os-utils";
import { cpu, drive, mem } from "node-os-utils";
export default async function handler(req, res) {
const { type, target } = req.query;
if (type == "cpu") {
if (type === "cpu") {
return res.status(200).json({
cpu: {
usage: await cpu.usage(1000),
load: cpu.loadavgTime(5),
},
});
} else if (type == "disk") {
}
if (type === "disk") {
return res.status(200).json({
drive: await drive.info(target || "/"),
});
} else if (type == "memory") {
}
if (type === "memory") {
return res.status(200).json({
memory: await mem.info(),
});
} else {
return res.status(400).json({
error: "invalid type",
});
}
return res.status(400).json({
error: "invalid type",
});
}

View file

@ -22,7 +22,7 @@ export default async function handler(req, res) {
return res.status(400).json({ error: "Missing API key" });
}
const api_url = `http://api.weatherapi.com/v1/current.json?q=${latitude},${longitude}&key=${apiKey}`;
const apiUrl = `http://api.weatherapi.com/v1/current.json?q=${latitude},${longitude}&key=${apiKey}`;
res.send(await cachedFetch(api_url, cache));
return res.send(await cachedFetch(apiUrl, cache));
}

View file

@ -2,13 +2,11 @@ import useSWR from "swr";
import Head from "next/head";
import dynamic from "next/dynamic";
import { ThemeProvider } from "utils/theme-context";
import ServicesGroup from "components/services/group";
import BookmarksGroup from "components/bookmarks/group";
import Widget from "components/widget";
import { ColorProvider } from "utils/color-context";
import Search from "components/widgets/search/search";
import { ThemeProvider } from "utils/theme-context";
const ThemeToggle = dynamic(() => import("components/theme-toggle"), {
ssr: false,
@ -19,12 +17,11 @@ const ColorToggle = dynamic(() => import("components/color-toggle"), {
});
const rightAlignedWidgets = ["weatherapi", "openweathermap", "weather", "search"];
const expandedWidgets = ["search"];
export default function Home() {
const { data: services, error: servicesError } = useSWR("/api/services");
const { data: bookmarks, error: bookmarksError } = useSWR("/api/bookmarks");
const { data: widgets, error: widgetsError } = useSWR("/api/widgets");
const { data: services } = useSWR("/api/services");
const { data: bookmarks } = useSWR("/api/bookmarks");
const { data: widgets } = useSWR("/api/widgets");
return (
<ColorProvider>
@ -38,15 +35,15 @@ export default function Home() {
<>
{widgets
.filter((widget) => !rightAlignedWidgets.includes(widget.type))
.map((widget, i) => (
<Widget key={i} widget={widget} />
.map((widget) => (
<Widget key={widget} widget={widget} />
))}
<div className="flex flex-wrap basis-full space-x-0 sm:space-x-4 grow sm:basis-auto justify-between md:justify-end mt-2 md:mt-0">
{widgets
.filter((widget) => rightAlignedWidgets.includes(widget.type))
.map((widget, i) => (
<Widget key={i} widget={widget} />
.map((widget) => (
<Widget key={widget} widget={widget} />
))}
</div>
</>

View file

@ -15,13 +15,13 @@ const formats = {
};
export function formatApiCall(api, args) {
const match = /\{.*?\}/g;
const find = /\{.*?\}/g;
const replace = (match) => {
const key = match.replace(/\{|\}/g, "");
return args[key];
};
return formats[api].replace(match, replace);
return formats[api].replace(find, replace);
}
export function formatApiUrl(widget, endpoint) {

View file

@ -5,9 +5,9 @@ export default async function cachedFetch(url, duration) {
if (cached) {
return cached;
} else {
const data = await fetch(url).then((res) => res.json());
cache.put(url, data, duration * 1000 * 60);
return data;
}
const data = await fetch(url).then((res) => res.json());
cache.put(url, data, duration * 1000 * 60);
return data;
}

View file

@ -1,4 +1,4 @@
import { createContext, useState, useEffect } from "react";
import { createContext, useState, useEffect, useMemo } from "react";
let lastColor = false;
@ -16,7 +16,7 @@ const getInitialColor = () => {
export const ColorContext = createContext();
export const ColorProvider = ({ initialTheme, children }) => {
export function ColorProvider({ initialTheme, children }) {
const [color, setColor] = useState(getInitialColor);
const rawSetColor = (rawColor) => {
@ -38,5 +38,7 @@ export const ColorProvider = ({ initialTheme, children }) => {
rawSetColor(color);
}, [color]);
return <ColorContext.Provider value={{ color, setColor }}>{children}</ColorContext.Provider>;
};
const value = useMemo(() => ({ color, setColor }), [color]);
return <ColorContext.Provider value={value}>{children}</ColorContext.Provider>;
}

View file

@ -348,7 +348,9 @@ export default function mapIcon(weatherStatusCode, timeOfDay) {
if (mapping) {
if (timeOfDay === "day") {
return mapping.icon.day;
} else if (timeOfDay === "night") {
}
if (timeOfDay === "night") {
return mapping.icon.night;
}
}

View file

@ -1,5 +1,7 @@
/* eslint-disable no-console */
import { join } from "path";
import { existsSync, copyFile, promises as fs } from "fs";
import yaml from "js-yaml";
export default function checkAndCopyConfig(config) {
@ -8,7 +10,7 @@ export default function checkAndCopyConfig(config) {
const configSkeleton = join(process.cwd(), "src", "skeleton", config);
copyFile(configSkeleton, configYaml, (err) => {
if (err) {
console.log("error copying config", err);
console.error("error copying config", err);
throw err;
}
console.info("%s was copied to the config folder", config);

View file

@ -1,6 +1,8 @@
import yaml from "js-yaml";
import path from "path";
import { promises as fs } from "fs";
import yaml from "js-yaml";
import checkAndCopyConfig from "utils/config";
export default async function getDockerArguments(server) {
@ -13,18 +15,21 @@ export default async function getDockerArguments(server) {
if (!server) {
if (process.platform !== "win32" && process.platform !== "darwin") {
return { socketPath: "/var/run/docker.sock" };
} else {
return { host: "127.0.0.1" };
}
} else if (servers[server]) {
return { host: "127.0.0.1" };
}
if (servers[server]) {
if (servers[server].socket) {
return { socketPath: servers[server].socket };
} else if (servers[server].host) {
return { host: servers[server].host, port: servers[server].port || null };
} else {
return servers[server];
}
} else {
return null;
if (servers[server].host) {
return { host: servers[server].host, port: servers[server].port || null };
}
return servers[server];
}
return null;
}

View file

@ -1,10 +1,11 @@
/* eslint-disable prefer-promise-reject-errors */
import https from "https";
import http from "http";
export function httpsRequest(url, params) {
return new Promise(function (resolve, reject) {
var request = https.request(url, params, function (response) {
var data = [];
return new Promise((resolve, reject) => {
const request = https.request(url, params, (response) => {
const data = [];
response.on("data", (chunk) => {
data.push(chunk);
@ -24,9 +25,9 @@ export function httpsRequest(url, params) {
}
export function httpRequest(url, params) {
return new Promise(function (resolve, reject) {
var request = http.request(url, params, function (response) {
var data = [];
return new Promise((resolve, reject) => {
const request = http.request(url, params, (response) => {
const data = [];
response.on("data", (chunk) => {
data.push(chunk);
@ -57,7 +58,6 @@ export function httpProxy(url, params = {}) {
agent: httpsAgent,
...params,
});
} else {
return httpRequest(constructedUrl, params);
}
return httpRequest(constructedUrl, params);
}

View file

@ -135,7 +135,7 @@ const conditions = [
night: Icons.WiNightAltShowers,
},
},
{
code: 500,
icon: {
@ -393,18 +393,17 @@ const conditions = [
night: Icons.WiCloudy,
},
},
];
export default function mapIcon(weatherStatusCode, timeOfDay) {
const mapping = conditions.find(
(condition) => condition.code === weatherStatusCode
);
const mapping = conditions.find((condition) => condition.code === weatherStatusCode);
if (mapping) {
if (timeOfDay === "day") {
return mapping.icon.day;
} else if (timeOfDay === "night") {
}
if (timeOfDay === "night") {
return mapping.icon.night;
}
}

View file

@ -1,4 +1,4 @@
import { getServiceWidget } from "utils/service-helpers";
import getServiceWidget from "utils/service-helpers";
import { formatApiCall } from "utils/api-helpers";
import { httpProxy } from "utils/http";

View file

@ -1,4 +1,4 @@
import { getServiceWidget } from "utils/service-helpers";
import getServiceWidget from "utils/service-helpers";
import { formatApiCall } from "utils/api-helpers";
import { httpProxy } from "utils/http";

View file

@ -1,4 +1,4 @@
import { getServiceWidget } from "utils/service-helpers";
import getServiceWidget from "utils/service-helpers";
import { formatApiCall } from "utils/api-helpers";
export default async function npmProxyHandler(req, res) {
@ -25,7 +25,7 @@ export default async function npmProxyHandler(req, res) {
method: "GET",
headers: {
"Content-Type": "application/json",
Authorization: "Bearer " + authResponse.token,
Authorization: `Bearer ${authResponse.token}`,
},
}).then((response) => response.json());

View file

@ -1,5 +1,6 @@
import { JSONRPCClient } from "json-rpc-2.0";
import { getServiceWidget } from "utils/service-helpers";
import getServiceWidget from "utils/service-helpers";
export default async function nzbgetProxyHandler(req, res) {
const { group, service, endpoint } = req.query;
@ -25,9 +26,9 @@ export default async function nzbgetProxyHandler(req, res) {
if (response.status === 200) {
const jsonRPCResponse = await response.json();
return client.receive(jsonRPCResponse);
} else if (jsonRPCRequest.id !== undefined) {
return Promise.reject(new Error(response.statusText));
}
return Promise.reject(new Error(response.statusText));
})
);

View file

@ -1,6 +1,6 @@
import RuTorrent from "rutorrent-promise";
import { getServiceWidget } from "utils/service-helpers";
import getServiceWidget from "utils/service-helpers";
export default async function rutorrentProxyHandler(req, res) {
const { group, service } = req.query;

View file

@ -1,24 +1,21 @@
import { promises as fs } from "fs";
import path from "path";
import yaml from "js-yaml";
export async function getServiceWidget(group, service) {
export default async function getServiceWidget(group, service) {
const servicesYaml = path.join(process.cwd(), "config", "services.yaml");
const fileContents = await fs.readFile(servicesYaml, "utf8");
const services = yaml.load(fileContents);
// map easy to write YAML objects into easy to consume JS arrays
const servicesArray = services.map((group) => {
return {
name: Object.keys(group)[0],
services: group[Object.keys(group)[0]].map((entries) => {
return {
name: Object.keys(entries)[0],
...entries[Object.keys(entries)[0]],
};
}),
};
});
const servicesArray = services.map((servicesGroup) => ({
name: Object.keys(servicesGroup)[0],
services: servicesGroup[Object.keys(servicesGroup)[0]].map((entries) => ({
name: Object.keys(entries)[0],
...entries[Object.keys(entries)[0]],
})),
}));
const serviceGroup = servicesArray.find((g) => g.name === group);
if (serviceGroup) {

View file

@ -19,7 +19,7 @@ export function formatBytes(bytes, decimals = 2) {
const i = Math.floor(Math.log(bytes) / Math.log(k));
return parseFloat(bytes / Math.pow(k, i)).toFixed(dm) + " " + sizes[i];
return `${parseFloat(bytes / k ** i).toFixed(dm)} ${sizes[i]}`;
}
export function formatBits(bytes, decimals = 2) {
@ -31,5 +31,5 @@ export function formatBits(bytes, decimals = 2) {
const i = Math.floor(Math.log(bytes) / Math.log(k));
return parseFloat(bytes / Math.pow(k, i)).toFixed(dm) + " " + sizes[i];
return `${parseFloat(bytes / k ** i).toFixed(dm)} ${sizes[i]}`;
}

View file

@ -1,4 +1,4 @@
import { createContext, useState, useEffect } from "react";
import { createContext, useState, useEffect, useMemo } from "react";
const getInitialTheme = () => {
if (typeof window !== "undefined" && window.localStorage) {
@ -18,7 +18,7 @@ const getInitialTheme = () => {
export const ThemeContext = createContext();
export const ThemeProvider = ({ initialTheme, children }) => {
export function ThemeProvider({ initialTheme, children }) {
const [theme, setTheme] = useState(getInitialTheme);
const rawSetTheme = (rawTheme) => {
@ -39,5 +39,7 @@ export const ThemeProvider = ({ initialTheme, children }) => {
rawSetTheme(theme);
}, [theme]);
return <ThemeContext.Provider value={{ theme, setTheme }}>{children}</ThemeContext.Provider>;
};
const value = useMemo(() => ({ theme, setTheme }), [theme]);
return <ThemeContext.Provider value={value}>{children}</ThemeContext.Provider>;
}