feat(web): mobile UI improvements (#359)

* enhancement: improved alerts component contrast
enhancement: simplified and improved radio switch group look
fix: fixed inconsistent spacing in input components (there's still some work left to be done)
fix: made slideover panel display on full width on mobile devices
enhancement: made forms more accessible to mobile users, adapter changes in accordance with the previous input components fix
fix: fixed misspelling in NotificationForms filename
chore: cleaned up code
fix: made filter table top edges less round and improved look
fix: fixed a bug where when a modal/slideover component was opened, a 1px white bar would be shown in one of the modal parent elements (for the fix see L89 in screens/settings/DwonloadClient.tsx)
enhancement: improved responsiveness for irc network list

* Fixed 2 small comma warnings from ESLint

Co-authored-by: anonymous <anonymous>
This commit is contained in:
stacksmash76 2022-07-17 23:34:49 +02:00 committed by GitHub
parent f961115dac
commit 3da594ec75
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
26 changed files with 758 additions and 640 deletions

View file

@ -99,7 +99,7 @@ export const APIClient = {
toggleEnable: (id: number, enabled: boolean) => appClient.Patch(`api/feeds/${id}/enabled`, { enabled }),
update: (feed: Feed) => appClient.Put(`api/feeds/${feed.id}`, feed),
delete: (id: number) => appClient.Delete(`api/feeds/${id}`),
test: (feed: Feed) => appClient.Post("api/feeds/test", feed),
test: (feed: Feed) => appClient.Post("api/feeds/test", feed)
},
indexers: {
// returns indexer options for all currently present/enabled indexers

View file

@ -2,26 +2,26 @@
import { ExclamationIcon } from "@heroicons/react/solid";
interface props {
title: string;
title?: string;
text: string;
}
export function AlertWarning({ title, text }: props) {
return (
<div className="p-4">
<div className="rounded-md bg-yellow-50 p-4">
<div className="flex">
<div className="flex-shrink-0">
<ExclamationIcon
className="h-5 w-5 text-yellow-400"
aria-hidden="true"
/>
</div>
<div className="ml-3">
<h3 className="text-sm font-medium text-yellow-800">{title}</h3>
<div className="mt-2 text-sm text-yellow-700">
<p>{text}</p>
</div>
<div className="my-4 rounded-md bg-yellow-50 dark:bg-yellow-100 p-4 border border-yellow-300 dark:border-none">
<div className="flex">
<div className="flex-shrink-0">
<ExclamationIcon
className="h-5 w-5 text-yellow-400 dark:text-yellow-600"
aria-hidden="true"
/>
</div>
<div className="ml-3">
{title ? (
<h3 className="mb-1 text-md font-medium text-yellow-800">{title}</h3>
) : null}
<div className="text-sm text-yellow-800">
<p>{text}</p>
</div>
</div>
</div>

View file

@ -11,7 +11,7 @@ const DEBUG: FC<DebugProps> = ({ values }) => {
return (
<div className="w-full p-2 flex flex-col mt-6 bg-gray-100 dark:bg-gray-900">
<pre className="dark:text-gray-400">{JSON.stringify(values, null, 2)}</pre>
<pre className="dark:text-gray-400 break-all whitespace-pre-wrap">{JSON.stringify(values, null, 2)}</pre>
</div>
);
};

View file

@ -25,7 +25,7 @@ export const TextFieldWide = ({
required,
hidden
}: TextFieldWideProps) => (
<div hidden={hidden} className="space-y-1 px-4 sm:space-y-0 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6 sm:py-5">
<div hidden={hidden} className="space-y-1 p-4 sm:space-y-0 sm:grid sm:grid-cols-3 sm:gap-4">
<div>
<label htmlFor={name} className="block text-sm font-medium text-gray-900 dark:text-white sm:mt-px sm:pt-2">
{label} {required && <span className="text-gray-500">*</span>}
@ -80,7 +80,7 @@ export const PasswordFieldWide = ({
const [isVisible, toggleVisibility] = useToggle(defaultVisible);
return (
<div className="space-y-1 px-4 sm:space-y-0 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6 sm:py-5">
<div className="space-y-1 p-4 sm:space-y-0 sm:grid sm:grid-cols-3 sm:gap-4">
<div>
<label htmlFor={name} className="block text-sm font-medium text-gray-900 dark:text-white sm:mt-px sm:pt-2">
{label} {required && <span className="text-gray-500">*</span>}
@ -134,7 +134,7 @@ export const NumberFieldWide = ({
defaultValue,
required
}: NumberFieldWideProps) => (
<div className="px-4 space-y-1 sm:space-y-0 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6 sm:py-5">
<div className="px-4 space-y-1 sm:space-y-0 sm:grid sm:grid-cols-3 sm:gap-4 sm:py-4">
<div>
<label
htmlFor={name}
@ -187,7 +187,7 @@ export const SwitchGroupWide = ({
description,
defaultValue
}: SwitchGroupWideProps) => (
<ul className="mt-2 divide-y divide-gray-200 dark:divide-gray-700">
<ul className="mt-2 px-4 divide-y divide-gray-200 dark:divide-gray-700">
<Switch.Group as="li" className="py-4 flex items-center justify-between">
<div className="flex flex-col">
<Switch.Label as="p" className="text-sm font-medium text-gray-900 dark:text-white"
@ -212,7 +212,7 @@ export const SwitchGroupWide = ({
type="button"
value={field.value}
checked={field.checked ?? false}
onChange={value => {
onChange={(value: unknown) => {
form.setFieldValue(field?.name ?? "", value);
}}
className={classNames(

View file

@ -1,4 +1,3 @@
import { Fragment } from "react";
import { Field, useFormikContext } from "formik";
import { RadioGroup } from "@headlessui/react";
import { classNames } from "../../utils";
@ -25,14 +24,13 @@ function RadioFieldsetWide({ name, legend, options }: props) {
setFieldValue
} = useFormikContext<anyObj>();
const onChange = (value: string) => {
setFieldValue(name, value);
};
return (
<fieldset>
<div className="space-y-2 px-4 sm:space-y-0 sm:grid sm:grid-cols-3 sm:gap-4 sm:items-start sm:px-6 sm:py-5">
<div className="space-y-2 px-4 sm:space-y-0 sm:grid sm:grid-cols-3 sm:gap-4 sm:items-start sm:py-4">
<div>
<legend className="text-sm font-medium text-gray-900 dark:text-white">
{legend}
@ -60,49 +58,41 @@ function RadioFieldsetWide({ name, legend, options }: props) {
? "rounded-bl-md rounded-br-md"
: "",
checked
? "bg-indigo-50 dark:bg-gray-700 border-indigo-200 dark:border-blue-600 z-10"
? "border-1 bg-indigo-100 dark:bg-blue-900 border-indigo-400 dark:border-blue-600 z-10"
: "border-gray-200 dark:border-gray-700",
"relative border p-4 flex cursor-pointer focus:outline-none"
)
}
>
{({ active, checked }) => (
<Fragment>
{({ checked }) => (
<>
<span
className={classNames(
checked
? "bg-indigo-600 dark:bg-blue-600 border-transparent"
: "bg-white border-gray-300 dark:border-gray-300",
active
? "ring-2 ring-offset-2 ring-indigo-500 dark:ring-blue-500"
: "",
"h-4 w-4 mt-0.5 cursor-pointer rounded-full border flex items-center justify-center"
? "bg-indigo-600 dark:bg-blue-500 border-transparent"
: "bg-white dark:bg-gray-800 border-gray-300 dark:border-gray-300",
"h-6 w-6 mt-1 cursor-pointer rounded-full border flex items-center justify-center"
)}
aria-hidden="true"
>
<span className="rounded-full bg-white w-1.5 h-1.5" />
</span>
/>
<div className="ml-3 flex flex-col">
<RadioGroup.Label
as="span"
className={classNames(
checked ? "text-indigo-900 dark:text-blue-500" : "text-gray-900 dark:text-gray-300",
"block text-sm font-medium"
"block text-md text-gray-900 dark:text-gray-300",
checked ? "font-bold" : "font-medium"
)}
>
{setting.label}
</RadioGroup.Label>
<RadioGroup.Description
as="span"
className={classNames(
checked ? "text-indigo-700 dark:text-blue-500" : "text-gray-500",
"block text-sm"
)}
className="block text-sm text-gray-700 dark:text-gray-400"
>
{setting.description}
</RadioGroup.Description>
</div>
</Fragment>
</>
)}
</RadioGroup.Option>
))}

View file

@ -354,7 +354,7 @@ export const SelectWide = ({
return (
<div className="py-6 px-6 space-y-6 sm:py-0 sm:space-y-0 sm:divide-y sm:divide-gray-200">
<div className="space-y-1 px-4 sm:space-y-0 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6 sm:py-5">
<div className="space-y-1 px-4 sm:space-y-0 sm:grid sm:grid-cols-3 sm:gap-4 sm:py-4">
<Field name={name} type="select">
{({
field,

View file

@ -35,7 +35,7 @@ export const DeleteModal: FC<DeleteModalProps> = ({ isOpen, buttonRef, toggle, d
</Transition.Child>
<span className="hidden sm:inline-block sm:align-middle sm:h-screen" aria-hidden="true">
&#8203;
&#8203;
</span>
<Transition.Child
as={Fragment}

View file

@ -36,7 +36,7 @@ function SlideOver<DataType>({
testFn,
isTesting,
isTestSuccessful,
isTestError,
isTestError
}: SlideOverProps<DataType>): React.ReactElement {
const cancelModalButtonRef = useRef<HTMLInputElement | null>(null);
const [deleteModalIsOpen, toggleDeleteModal] = useToggle(false);
@ -64,7 +64,7 @@ function SlideOver<DataType>({
<div className="absolute inset-0 overflow-hidden">
<Dialog.Overlay className="absolute inset-0" />
<div className="fixed inset-y-0 right-0 pl-10 max-w-full flex sm:pl-16">
<div className="fixed inset-y-0 right-0 max-w-full flex">
<Transition.Child
as={Fragment}
enter="transform transition ease-in-out duration-500 sm:duration-700"

View file

@ -338,5 +338,5 @@ export const EventOptions: SelectOption[] = [
label: "New update",
value: "APP_UPDATE_AVAILABLE",
description: "Get notified on updates"
},
}
];

View file

@ -96,7 +96,7 @@ function FilterAddForm({ isOpen, toggle }: filterAddFormProps) {
<div
className="py-6 space-y-6 sm:py-0 sm:space-y-0 sm:divide-y sm:divide-gray-200">
<div
className="space-y-1 px-4 sm:space-y-0 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6 sm:py-5">
className="space-y-1 px-4 sm:space-y-0 sm:grid sm:grid-cols-3 sm:gap-4 sm:py-4">
<div>
<label
htmlFor="name"

View file

@ -50,24 +50,31 @@ function FormFieldsDefault() {
} = useFormikContext<InitialValues>();
return (
<Fragment>
<TextFieldWide name="host" label="Host" help="Eg. client.domain.ltd, domain.ltd/client, domain.ltd:port"/>
<div className="flex flex-col space-y-4 px-1 py-6 sm:py-0 sm:space-y-0">
<TextFieldWide
name="host"
label="Host"
help="Eg. client.domain.ltd, domain.ltd/client, domain.ltd:port"
/>
<NumberFieldWide name="port" label="Port" help="WebUI port for qBittorrent and daemon port for Deluge"/>
<NumberFieldWide
name="port"
label="Port"
help="WebUI port for qBittorrent and daemon port for Deluge"
/>
<div className="py-6 px-6 space-y-6 sm:py-0 sm:space-y-0 sm:divide-y sm:divide-gray-200 dark:divide-gray-700">
<SwitchGroupWide name="tls" label="TLS"/>
<SwitchGroupWide name="tls" label="TLS" />
{tls && (
<Fragment>
<SwitchGroupWide name="tls_skip_verify" label="Skip TLS verification (insecure)"/>
</Fragment>
)}
</div>
{tls && (
<SwitchGroupWide
name="tls_skip_verify"
label="Skip TLS verification (insecure)"
/>
)}
<TextFieldWide name="username" label="Username"/>
<PasswordFieldWide name="password" label="Password"/>
</Fragment>
<TextFieldWide name="username" label="Username" />
<PasswordFieldWide name="password" label="Password" />
</div>
);
}
@ -77,22 +84,24 @@ function FormFieldsArr() {
} = useFormikContext<InitialValues>();
return (
<Fragment>
<TextFieldWide name="host" label="Host" help="Full url http(s)://domain.ltd and/or subdomain/subfolder"/>
<div className="flex flex-col space-y-4 px-1 mb-4 sm:py-0 sm:space-y-0">
<TextFieldWide
name="host"
label="Host"
help="Full url http(s)://domain.ltd and/or subdomain/subfolder"
/>
<PasswordFieldWide name="settings.apikey" label="API key"/>
<PasswordFieldWide name="settings.apikey" label="API key" />
<div className="py-6 px-6 space-y-6 sm:py-0 sm:space-y-0 sm:divide-y sm:divide-gray-200">
<SwitchGroupWide name="settings.basic.auth" label="Basic auth"/>
</div>
<SwitchGroupWide name="settings.basic.auth" label="Basic auth" />
{settings.basic?.auth === true && (
<Fragment>
<TextFieldWide name="settings.basic.username" label="Username"/>
<PasswordFieldWide name="settings.basic.password" label="Password"/>
</Fragment>
<>
<TextFieldWide name="settings.basic.username" label="Username" />
<PasswordFieldWide name="settings.basic.password" label="Password" />
</>
)}
</Fragment>
</div>
);
}
@ -102,37 +111,42 @@ function FormFieldsQbit() {
} = useFormikContext<InitialValues>();
return (
<Fragment>
<TextFieldWide name="host" label="Host" help="Eg. client.domain.ltd, domain.ltd/client, domain.ltd:port" />
<div className="flex flex-col space-y-4 px-1 py-6 sm:py-0 sm:space-y-0">
<TextFieldWide
name="host"
label="Host"
help="Eg. client.domain.ltd, domain.ltd/client, domain.ltd:port"
/>
{port > 0 && (
<NumberFieldWide name="port" label="Port" help="WebUI port for qBittorrent" />
<NumberFieldWide
name="port"
label="Port"
help="WebUI port for qBittorrent"
/>
)}
<div className="py-6 px-6 space-y-6 sm:py-0 sm:space-y-0 sm:divide-y sm:divide-gray-200 dark:divide-gray-700">
<SwitchGroupWide name="tls" label="TLS" />
<SwitchGroupWide name="tls" label="TLS" />
{tls && (
<Fragment>
<SwitchGroupWide name="tls_skip_verify" label="Skip TLS verification (insecure)" />
</Fragment>
)}
</div>
{tls && (
<SwitchGroupWide
name="tls_skip_verify"
label="Skip TLS verification (insecure)"
/>
)}
<TextFieldWide name="username" label="Username" />
<PasswordFieldWide name="password" label="Password" />
<div className="py-6 px-6 space-y-6 sm:py-0 sm:space-y-0 sm:divide-y sm:divide-gray-200">
<SwitchGroupWide name="settings.basic.auth" label="Basic auth" />
</div>
<SwitchGroupWide name="settings.basic.auth" label="Basic auth" />
{settings.basic?.auth === true && (
<Fragment>
<>
<TextFieldWide name="settings.basic.username" label="Username" />
<PasswordFieldWide name="settings.basic.password" label="Password" />
</Fragment>
</>
)}
</Fragment>
</div>
);
}
@ -142,24 +156,27 @@ function FormFieldsTransmission() {
} = useFormikContext<InitialValues>();
return (
<Fragment>
<TextFieldWide name="host" label="Host" help="Eg. client.domain.ltd, domain.ltd/client, domain.ltd"/>
<div className="flex flex-col space-y-4 px-1 py-6 sm:py-0 sm:space-y-0">
<TextFieldWide
name="host"
label="Host"
help="Eg. client.domain.ltd, domain.ltd/client, domain.ltd"
/>
<NumberFieldWide name="port" label="Port" help="Port for Transmission"/>
<NumberFieldWide name="port" label="Port" help="Port for Transmission" />
<div className="py-6 px-6 space-y-6 sm:py-0 sm:space-y-0 sm:divide-y sm:divide-gray-200 dark:divide-gray-700">
<SwitchGroupWide name="tls" label="TLS"/>
<SwitchGroupWide name="tls" label="TLS" />
{tls && (
<Fragment>
<SwitchGroupWide name="tls_skip_verify" label="Skip TLS verification (insecure)"/>
</Fragment>
)}
</div>
{tls && (
<SwitchGroupWide
name="tls_skip_verify"
label="Skip TLS verification (insecure)"
/>
)}
<TextFieldWide name="username" label="Username"/>
<PasswordFieldWide name="password" label="Password"/>
</Fragment>
<TextFieldWide name="username" label="Username" />
<PasswordFieldWide name="password" label="Password" />
</div>
);
}
@ -186,21 +203,17 @@ function FormFieldsRulesBasic() {
return (
<div className="border-t border-gray-200 dark:border-gray-700 py-5">
<div className="px-6 space-y-1">
<div className="px-4 space-y-1">
<Dialog.Title className="text-lg font-medium text-gray-900 dark:text-white">Rules</Dialog.Title>
<p className="text-sm text-gray-500 dark:text-gray-400">
Manage max downloads.
</p>
</div>
<div className="py-6 px-6 space-y-6 sm:py-0 sm:space-y-0 sm:divide-y sm:divide-gray-200">
<SwitchGroupWide name="settings.rules.enabled" label="Enabled"/>
</div>
<SwitchGroupWide name="settings.rules.enabled" label="Enabled"/>
{settings && settings.rules?.enabled === true && (
<Fragment>
<NumberFieldWide name="settings.rules.max_active_downloads" label="Max active downloads"/>
</Fragment>
<NumberFieldWide name="settings.rules.max_active_downloads" label="Max active downloads"/>
)}
</div>
);
@ -212,34 +225,38 @@ function FormFieldsRules() {
} = useFormikContext<InitialValues>();
return (
<div className="border-t border-gray-200 dark:border-gray-700 py-5">
<div className="px-6 space-y-1">
<Dialog.Title className="text-lg font-medium text-gray-900 dark:text-white">Rules</Dialog.Title>
<div className="border-t border-gray-200 dark:border-gray-700 py-5 px-2">
<div className="px-4 space-y-1">
<Dialog.Title className="text-lg font-medium text-gray-900 dark:text-white">
Rules
</Dialog.Title>
<p className="text-sm text-gray-500 dark:text-gray-400">
Manage max downloads etc.
</p>
</div>
<div className="py-6 px-6 space-y-6 sm:py-0 sm:space-y-0 sm:divide-y sm:divide-gray-200">
<SwitchGroupWide name="settings.rules.enabled" label="Enabled"/>
</div>
<SwitchGroupWide name="settings.rules.enabled" label="Enabled" />
{settings.rules?.enabled === true && (
<Fragment>
<NumberFieldWide name="settings.rules.max_active_downloads" label="Max active downloads"/>
<div className="py-6 px-6 space-y-6 sm:py-0 sm:space-y-0 sm:divide-y sm:divide-gray-200">
<SwitchGroupWide name="settings.rules.ignore_slow_torrents" label="Ignore slow torrents"/>
</div>
<>
<NumberFieldWide
name="settings.rules.max_active_downloads"
label="Max active downloads"
/>
<SwitchGroupWide
name="settings.rules.ignore_slow_torrents"
label="Ignore slow torrents"
/>
{settings.rules?.ignore_slow_torrents === true && (
<Fragment>
<NumberFieldWide name="settings.rules.download_speed_threshold" label="Download speed threshold"
placeholder="in KB/s"
help="If download speed is below this when max active downloads is hit, download anyways. KB/s"/>
</Fragment>
<NumberFieldWide
name="settings.rules.download_speed_threshold"
label="Download speed threshold"
placeholder="in KB/s"
help="If download speed is below this when max active downloads is hit, download anyways. KB/s"
/>
)}
</Fragment>
</>
)}
</div>
);
@ -248,7 +265,7 @@ function FormFieldsRules() {
export const rulesComponentMap: componentMapType = {
DELUGE_V1: <FormFieldsRulesBasic/>,
DELUGE_V2: <FormFieldsRulesBasic/>,
QBITTORRENT: <FormFieldsRules/>,
QBITTORRENT: <FormFieldsRules/>
};
interface formButtonsProps {
@ -490,20 +507,14 @@ export function DownloadClientAddForm({ isOpen, toggle }: formProps) {
</div>
</div>
<div className="py-6 space-y-6 sm:py-0 sm:space-y-0 sm:divide-y dark:divide-gray-700">
<div className="flex flex-col space-y-4 px-1 py-6 sm:py-0 sm:space-y-0">
<TextFieldWide name="name" label="Name"/>
<div
className="py-6 px-6 space-y-6 sm:py-0 sm:space-y-0 sm:divide-y sm:divide-gray-200 dark:divide-gray-700">
<SwitchGroupWide name="enabled" label="Enabled"/>
</div>
<SwitchGroupWide name="enabled" label="Enabled"/>
<RadioFieldsetWide
name="type"
legend="Type"
options={DownloadClientTypeOptions}
/>
<div>{componentMap[values.type]}</div>
</div>
</div>
@ -697,17 +708,12 @@ export function DownloadClientUpdateForm({ client, isOpen, toggle }: updateFormP
<div className="py-6 space-y-6 sm:py-0 sm:space-y-0 sm:divide-y dark:divide-gray-700">
<TextFieldWide name="name" label="Name"/>
<div className="py-6 px-6 space-y-6 sm:py-0 sm:space-y-0 sm:divide-y sm:divide-gray-200">
<SwitchGroupWide name="enabled" label="Enabled"/>
</div>
<SwitchGroupWide name="enabled" label="Enabled"/>
<RadioFieldsetWide
name="type"
legend="Type"
options={DownloadClientTypeOptions}
/>
<div>{componentMap[values.type]}</div>
</div>
</div>

View file

@ -116,7 +116,7 @@ export function FeedUpdateForm({ isOpen, toggle, feed }: UpdateProps) {
<div className="space-y-4 divide-y divide-gray-200 dark:divide-gray-700">
<div
className="py-4 flex items-center justify-between space-y-1 px-4 sm:space-y-0 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6 sm:py-5">
className="py-4 flex items-center justify-between space-y-1 px-4 sm:space-y-0 sm:grid sm:grid-cols-3 sm:gap-4 sm:py-4">
<div>
<label
htmlFor="type"

View file

@ -63,7 +63,7 @@ const IrcSettingFields = (ind: IndexerDefinition, indexer: string) => {
<Fragment>
{ind && ind.irc && ind.irc.settings && (
<div className="border-t border-gray-200 dark:border-gray-700 py-5">
<div className="px-6 space-y-1">
<div className="px-4 space-y-1">
<Dialog.Title className="text-lg font-medium text-gray-900 dark:text-white">IRC</Dialog.Title>
<p className="text-sm text-gray-500 dark:text-gray-200">
Networks, channels and invite commands are configured automatically.
@ -94,7 +94,7 @@ const FeedSettingFields = (ind: IndexerDefinition, indexer: string) => {
<Fragment>
{ind && ind.torznab && ind.torznab.settings && (
<div className="">
<div className="px-6 space-y-1">
<div className="px-4 space-y-1">
<Dialog.Title className="text-lg font-medium text-gray-900 dark:text-white">Torznab</Dialog.Title>
<p className="text-sm text-gray-500 dark:text-gray-200">
Torznab feed
@ -329,13 +329,13 @@ export function IndexerAddForm({ isOpen, toggle }: AddProps) {
</div>
<div className="py-6 space-y-4 divide-y divide-gray-200 dark:divide-gray-700">
<div className="py-4 flex items-center justify-between space-y-1 px-4 sm:space-y-0 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6 sm:py-5">
<div className="py-4 flex items-center justify-between space-y-1 px-4 sm:space-y-0 sm:grid sm:grid-cols-3 sm:gap-4 sm:py-4">
<div>
<label
htmlFor="identifier"
className="block text-sm font-medium text-gray-900 dark:text-white"
>
Indexer
Indexer
</label>
</div>
<div className="sm:col-span-2">
@ -391,9 +391,7 @@ export function IndexerAddForm({ isOpen, toggle }: AddProps) {
</div>
</div>
<div className="py-6 px-6 space-y-6 sm:py-0 sm:space-y-0 sm:divide-y sm:divide-gray-200">
<SwitchGroupWide name="enabled" label="Enabled" />
</div>
<SwitchGroupWide name="enabled" label="Enabled" />
{SettingFields(indexer, values.identifier)}
@ -504,7 +502,7 @@ export function IndexerUpdateForm({ isOpen, toggle, indexer }: UpdateProps) {
...o,
[obj.name]: obj.value
} as Record<string, string>),
{} as Record<string, string>
{} as Record<string, string>
)
};
@ -519,16 +517,14 @@ export function IndexerUpdateForm({ isOpen, toggle, indexer }: UpdateProps) {
initialValues={initialValues}
>
{() => (
<div className="py-6 space-y-6 sm:py-0 sm:space-y-0 divide-y divide-gray-200 dark:divide-gray-700">
<div className="space-y-1 px-4 sm:space-y-0 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6 sm:py-5">
<div>
<label
htmlFor="name"
className="block text-sm font-medium text-gray-900 dark:text-white sm:mt-px sm:pt-2"
>
Name
</label>
</div>
<div className="py-2 space-y-6 sm:py-0 sm:space-y-0 divide-y divide-gray-200 dark:divide-gray-700">
<div className="space-y-1 p-4 sm:space-y-0 sm:grid sm:grid-cols-3 sm:gap-4">
<label
htmlFor="name"
className="block text-sm font-medium text-gray-900 dark:text-white sm:mt-px sm:pt-2"
>
Name
</label>
<Field name="name">
{({ field, meta }: FieldProps) => (
<div className="sm:col-span-2">
@ -542,11 +538,7 @@ export function IndexerUpdateForm({ isOpen, toggle, indexer }: UpdateProps) {
)}
</Field>
</div>
<div className="py-6 px-6 space-y-6 sm:py-0 sm:space-y-0 sm:divide-y sm:divide-gray-200 dark:sm:divide-gray-700">
<SwitchGroupWide name="enabled" label="Enabled" />
</div>
<SwitchGroupWide name="enabled" label="Enabled" />
{renderSettingFields(indexer.settings)}
</div>
)}

View file

@ -24,7 +24,7 @@ const ChannelsFieldArray = ({ channels }: ChannelsFieldArrayProps) => (
<div className="p-6">
<FieldArray name="channels">
{({ remove, push }) => (
<div className="flex flex-col border-2 border-dashed dark:border-gray-700 p-4">
<div className="flex flex-col space-y-2 border-2 border-dashed dark:border-gray-700 p-4">
{channels && channels.length > 0 ? (
channels.map((_channel: IrcChannel, index: number) => (
<div key={index} className="flex justify-between">
@ -68,7 +68,7 @@ const ChannelsFieldArray = ({ channels }: ChannelsFieldArrayProps) => (
))
) : (
<span className="text-center text-sm text-grey-darker dark:text-white">
No channels!
No channels!
</span>
)}
<button
@ -76,7 +76,7 @@ const ChannelsFieldArray = ({ channels }: ChannelsFieldArrayProps) => (
className="border dark:border-gray-600 dark:bg-gray-700 my-4 px-4 py-2 text-sm text-gray-700 dark:text-white hover:bg-gray-50 dark:hover:bg-gray-600 rounded self-center text-center"
onClick={() => push({ name: "", password: "" })}
>
Add Channel
Add Channel
</button>
</div>
)}
@ -159,34 +159,47 @@ export function IrcNetworkAddForm({ isOpen, toggle }: AddFormProps) {
validate={validate}
>
{(values) => (
<>
<TextFieldWide name="name" label="Name" placeholder="Name" required={true} />
<div className="flex flex-col space-y-4 px-1 py-6 sm:py-0 sm:space-y-0">
<TextFieldWide
name="name"
label="Name"
placeholder="Name"
required={true}
/>
<div className="py-6 space-y-6 sm:py-0 sm:space-y-0 sm:divide-y dark:divide-gray-700">
<div className="py-6 px-6 space-y-6 sm:py-0 sm:space-y-0 sm:divide-y sm:divide-gray-200 dark:sm:divide-gray-700">
<SwitchGroupWide name="enabled" label="Enabled" />
</div>
<div>
<TextFieldWide name="server" label="Server" placeholder="Address: Eg irc.server.net" required={true} />
<NumberFieldWide name="port" label="Port" placeholder="Eg 6667" required={true} />
<div className="py-6 px-6 space-y-6 sm:py-0 sm:space-y-0 sm:divide-y sm:divide-gray-200">
<SwitchGroupWide name="tls" label="TLS" />
</div>
<PasswordFieldWide name="pass" label="Password" help="Network password" />
<TextFieldWide name="nickserv.account" label="NickServ Account" placeholder="NickServ Account" required={true} />
<PasswordFieldWide name="nickserv.password" label="NickServ Password" />
<PasswordFieldWide name="invite_command" label="Invite command" />
</div>
</div>
<SwitchGroupWide name="enabled" label="Enabled" />
<TextFieldWide
name="server"
label="Server"
placeholder="Address: Eg irc.server.net"
required={true}
/>
<NumberFieldWide
name="port"
label="Port"
placeholder="Eg 6667"
required={true}
/>
<SwitchGroupWide name="tls" label="TLS" />
<PasswordFieldWide
name="pass"
label="Password"
help="Network password"
/>
<TextFieldWide
name="nickserv.account"
label="NickServ Account"
placeholder="NickServ Account"
required={true}
/>
<PasswordFieldWide
name="nickserv.password"
label="NickServ Password"
/>
<PasswordFieldWide name="invite_command" label="Invite command" />
<ChannelsFieldArray channels={values.channels} />
</>
</div>
)}
</SlideOver>
);
@ -290,34 +303,51 @@ export function IrcNetworkUpdateForm({
validate={validate}
>
{(values) => (
<>
<TextFieldWide name="name" label="Name" placeholder="Name" required={true} />
<div className="flex flex-col space-y-4 px-1 py-6 sm:py-0 sm:space-y-0">
<TextFieldWide
name="name"
label="Name"
placeholder="Name"
required={true}
/>
<div className="py-6 space-y-6 sm:py-0 sm:space-y-0 sm:divide-y dark:divide-gray-700">
<SwitchGroupWide name="enabled" label="Enabled" />
<TextFieldWide
name="server"
label="Server"
placeholder="Address: Eg irc.server.net"
required={true}
/>
<NumberFieldWide
name="port"
label="Port"
placeholder="Eg 6667"
required={true}
/>
<div className="py-6 px-6 space-y-6 sm:py-0 sm:space-y-0">
<SwitchGroupWide name="enabled" label="Enabled" />
</div>
<SwitchGroupWide name="tls" label="TLS" />
<div>
<TextFieldWide name="server" label="Server" placeholder="Address: Eg irc.server.net" required={true} />
<NumberFieldWide name="port" label="Port" placeholder="Eg 6667" required={true} />
<PasswordFieldWide
name="pass"
label="Password"
help="Network password"
/>
<div className="py-6 px-6 space-y-6 sm:py-0 sm:space-y-0 sm:divide-y sm:divide-gray-200">
<SwitchGroupWide name="tls" label="TLS" />
</div>
<TextFieldWide
name="nickserv.account"
label="NickServ Account"
placeholder="NickServ Account"
required={true}
/>
<PasswordFieldWide
name="nickserv.password"
label="NickServ Password"
/>
<PasswordFieldWide name="pass" label="Password" help="Network password" />
<TextFieldWide name="nickserv.account" label="NickServ Account" placeholder="NickServ Account" required={true} />
<PasswordFieldWide name="nickserv.password" label="NickServ Password" />
<PasswordFieldWide name="invite_command" label="Invite command" />
</div>
</div>
<PasswordFieldWide name="invite_command" label="Invite command" />
<ChannelsFieldArray channels={values.channels} />
</>
</div>
)}
</SlideOver>
);

View file

@ -62,11 +62,11 @@ const Option = (props: OptionProps) => {
function FormFieldsDiscord() {
return (
<div className="border-t border-gray-200 dark:border-gray-700 py-5">
<div className="px-6 space-y-1">
<div className="border-t border-gray-200 dark:border-gray-700 py-4">
<div className="px-4 space-y-1">
<Dialog.Title className="text-lg font-medium text-gray-900 dark:text-white">Settings</Dialog.Title>
<p className="text-sm text-gray-500 dark:text-gray-400">
Create a <a href="https://support.discord.com/hc/en-us/articles/228383668-Intro-to-Webhooks" rel="noopener noreferrer" target="_blank" className="font-medium text-blue-500">webhook integration</a> in your server.
Create a <a href="https://support.discord.com/hc/en-us/articles/228383668-Intro-to-Webhooks" rel="noopener noreferrer" target="_blank" className="font-medium text-blue-500 underline underline-offset-1 hover:text-blue-400">webhook integration</a> in your server.
</p>
</div>
@ -82,11 +82,11 @@ function FormFieldsDiscord() {
function FormFieldsTelegram() {
return (
<div className="border-t border-gray-200 dark:border-gray-700 py-5">
<div className="px-6 space-y-1">
<div className="border-t border-gray-200 dark:border-gray-700 py-4">
<div className="px-4 space-y-1">
<Dialog.Title className="text-lg font-medium text-gray-900 dark:text-white">Settings</Dialog.Title>
<p className="text-sm text-gray-500 dark:text-gray-400">
Read how to <a href="https://core.telegram.org/bots#3-how-do-i-create-a-bot" rel="noopener noreferrer" target="_blank" className="font-medium text-blue-500">create a bot</a>.
Read how to <a href="https://core.telegram.org/bots#3-how-do-i-create-a-bot" rel="noopener noreferrer" target="_blank" className="font-medium text-blue-500 underline underline-offset-1 hover:text-blue-400">create a bot</a>.
</p>
</div>
@ -161,9 +161,15 @@ export function NotificationAddForm({ isOpen, toggle }: AddProps) {
return (
<Transition.Root show={isOpen} as={Fragment}>
<Dialog as="div" static className="fixed inset-0 overflow-hidden" open={isOpen} onClose={toggle}>
<Dialog
as="div"
static
className="fixed inset-0 overflow-hidden"
open={isOpen}
onClose={toggle}
>
<div className="absolute inset-0 overflow-hidden">
<Dialog.Overlay className="absolute inset-0"/>
<Dialog.Overlay className="absolute inset-0" />
<div className="fixed inset-y-0 right-0 pl-10 max-w-full flex sm:pl-16">
<Transition.Child
@ -208,16 +214,20 @@ export function NotificationAddForm({ isOpen, toggle }: AddProps) {
onClick={toggle}
>
<span className="sr-only">Close panel</span>
<XIcon className="h-6 w-6" aria-hidden="true"/>
<XIcon className="h-6 w-6" aria-hidden="true" />
</button>
</div>
</div>
</div>
<TextFieldWide name="name" label="Name" required={true}/>
<div className="flex flex-col space-y-4 px-1 py-6 sm:py-0 sm:space-y-0">
<TextFieldWide
name="name"
label="Name"
required={true}
/>
<div className="space-y-4 divide-y divide-gray-200 dark:divide-gray-700">
<div className="py-4 flex items-center justify-between space-y-1 px-4 sm:space-y-0 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6 sm:py-5">
<div className="flex items-center justify-between space-y-1 px-4 sm:space-y-0 sm:grid sm:grid-cols-3 sm:gap-4">
<div>
<label
htmlFor="type"
@ -232,10 +242,16 @@ export function NotificationAddForm({ isOpen, toggle }: AddProps) {
field,
form: { setFieldValue, resetForm }
}: FieldProps) => (
<Select {...field}
<Select
{...field}
isClearable={true}
isSearchable={true}
components={{ Input, Control, Menu, Option }}
components={{
Input,
Control,
Menu,
Option
}}
placeholder="Choose a type"
styles={{
singleValue: (base) => ({
@ -257,39 +273,39 @@ export function NotificationAddForm({ isOpen, toggle }: AddProps) {
const opt = option as SelectOption;
// setFieldValue("name", option?.label ?? "")
setFieldValue(field.name, opt.value ?? "");
setFieldValue(
field.name,
opt.value ?? ""
);
}}
options={NotificationTypeOptions}
/>
)}
</Field>
</div>
</div>
<div className="py-6 px-6 space-y-6 sm:py-0 sm:space-y-0 sm:divide-y sm:divide-gray-200">
<SwitchGroupWide name="enabled" label="Enabled"/>
</div>
<SwitchGroupWide name="enabled" label="Enabled" />
<div className="border-t border-gray-200 dark:border-gray-700 py-5">
<div className="px-6 space-y-1">
<Dialog.Title
className="text-lg font-medium text-gray-900 dark:text-white">Events</Dialog.Title>
<div className="border-t mt-2 border-gray-200 dark:border-gray-700 py-4">
<div className="px-4 space-y-1">
<Dialog.Title className="text-lg font-medium text-gray-900 dark:text-white">
Events
</Dialog.Title>
<p className="text-sm text-gray-500 dark:text-gray-400">
Select what events to trigger on
</p>
</div>
<div className="space-y-1 px-4 sm:space-y-0 sm:grid sm:gap-4 sm:px-6 sm:py-5">
<div className="space-y-1 px-4 sm:space-y-0 sm:grid sm:gap-4 sm:py-4">
<EventCheckBoxes />
</div>
</div>
</div>
{componentMap[values.type]}
</div>
<div className="flex-shrink-0 px-4 border-t border-gray-200 dark:border-gray-700 py-5 sm:px-6">
<div className="flex-shrink-0 px-4 border-t border-gray-200 dark:border-gray-700 py-4 sm:px-6">
<div className="space-x-3 flex justify-end">
<button
type="button"
@ -314,12 +330,11 @@ export function NotificationAddForm({ isOpen, toggle }: AddProps) {
</div>
</div>
<DEBUG values={values}/>
<DEBUG values={values} />
</Form>
)}
</Formik>
</div>
</Transition.Child>
</div>
</div>
@ -432,8 +447,8 @@ export function NotificationUpdateForm({ isOpen, toggle, notification }: UpdateP
<div>
<TextFieldWide name="name" label="Name" required={true}/>
<div className="space-y-4 divide-y divide-gray-200 dark:divide-gray-700">
<div className="py-4 flex items-center justify-between space-y-1 px-4 sm:space-y-0 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6 sm:py-5">
<div className="space-y-2 divide-y divide-gray-200 dark:divide-gray-700">
<div className="py-4 flex items-center justify-between space-y-1 px-4 sm:space-y-0 sm:grid sm:grid-cols-3 sm:gap-4 sm:py-4">
<div>
<label
htmlFor="type"
@ -477,13 +492,9 @@ export function NotificationUpdateForm({ isOpen, toggle, notification }: UpdateP
</Field>
</div>
</div>
<div className="py-6 px-6 space-y-6 sm:py-0 sm:space-y-0 sm:divide-y sm:divide-gray-200">
<SwitchGroupWide name="enabled" label="Enabled"/>
</div>
<div className="border-t border-gray-200 dark:border-gray-700 py-5">
<div className="px-6 space-y-1">
<SwitchGroupWide name="enabled" label="Enabled"/>
<div className="border-t border-gray-200 dark:border-gray-700 py-4">
<div className="px-4 space-y-1">
<Dialog.Title
className="text-lg font-medium text-gray-900 dark:text-white">Events</Dialog.Title>
<p className="text-sm text-gray-500 dark:text-gray-400">
@ -491,7 +502,7 @@ export function NotificationUpdateForm({ isOpen, toggle, notification }: UpdateP
</p>
</div>
<div className="space-y-1 px-4 sm:space-y-0 sm:grid sm:gap-4 sm:px-6 sm:py-5">
<div className="space-y-1 px-4 sm:space-y-0 sm:grid sm:gap-4 sm:py-2">
<EventCheckBoxes />
</div>
</div>

View file

@ -46,7 +46,7 @@ function SubNavLink({ item }: NavLinkProps) {
className={({ isActive }) => classNames(
"border-transparent text-gray-900 dark:text-gray-200 hover:bg-gray-50 dark:hover:bg-gray-700 hover:text-gray-900 dark:hover:text-gray-300 group border-l-4 px-3 py-2 flex items-center text-sm font-medium",
isActive ?
"bg-teal-50 dark:bg-gray-700 border-teal-500 dark:border-blue-500 text-teal-700 dark:text-white hover:bg-teal-50 dark:hover:bg-gray-500 hover:text-teal-700 dark:hover:text-gray-200" : ""
"font-bold bg-teal-50 dark:bg-gray-700 border-teal-500 dark:border-blue-500 text-teal-700 dark:text-white hover:bg-teal-50 dark:hover:bg-gray-500 hover:text-teal-700 dark:hover:text-gray-200" : ""
)}
aria-current={splitLocation[2] === item.href ? "page" : undefined}
>

View file

@ -609,6 +609,266 @@ export function FilterActions({ filter, values }: FilterActionsProps) {
);
}
interface TypeFormProps {
action: Action;
idx: number;
clients: Array<DownloadClient>;
}
const TypeForm = ({ action, idx, clients }: TypeFormProps) => {
switch (action.type) {
case "TEST":
return (
<AlertWarning
text="The test action does nothing except to show if the filter works."
/>
);
case "EXEC":
return (
<div>
<div className="mt-6 grid grid-cols-12 gap-6">
<TextField
name={`actions.${idx}.exec_cmd`}
label="Command"
columns={6}
placeholder="Path to program eg. /bin/test"
/>
<TextField
name={`actions.${idx}.exec_args`}
label="Arguments"
columns={6}
placeholder="Arguments eg. --test"
/>
</div>
</div>
);
case "WATCH_FOLDER":
return (
<div className="mt-6 grid grid-cols-12 gap-6">
<TextField
name={`actions.${idx}.watch_folder`}
label="Watch folder"
columns={6}
placeholder="Watch directory eg. /home/user/rwatch"
/>
</div>
);
case "WEBHOOK":
return (
<div className="mt-6 grid grid-cols-12 gap-6">
<TextField
name={`actions.${idx}.webhook_host`}
label="Host"
columns={6}
placeholder="Host eg. http://localhost/webhook"
/>
<TextField
name={`actions.${idx}.webhook_data`}
label="Data (json)"
columns={6}
placeholder={"Request data: { \"key\": \"value\" }"}
/>
</div>
);
case "QBITTORRENT":
return (
<div className="w-full">
<div className="mt-6 grid grid-cols-12 gap-6">
<DownloadClientSelect
name={`actions.${idx}.client_id`}
action={action}
clients={clients}
/>
<div className="col-span-6 sm:col-span-6">
<TextField
name={`actions.${idx}.save_path`}
label="Save path"
columns={6}
placeholder="eg. /full/path/to/watch_folder"
/>
</div>
</div>
<div className="mt-6 grid grid-cols-12 gap-6">
<TextField
name={`actions.${idx}.category`}
label="Category"
columns={6}
placeholder="eg. category"
/>
<TextField
name={`actions.${idx}.tags`}
label="Tags"
columns={6}
placeholder="eg. tag1,tag2"
/>
</div>
<CollapsableSection title="Rules" subtitle="client options">
<div className="col-span-12">
<div className="mt-6 grid grid-cols-12 gap-6">
<NumberField
name={`actions.${idx}.limit_download_speed`}
label="Limit download speed (KB/s)"
/>
<NumberField
name={`actions.${idx}.limit_upload_speed`}
label="Limit upload speed (KB/s)"
/>
</div>
<div className="mt-6 grid grid-cols-12 gap-6">
<NumberField
name={`actions.${idx}.limit_ratio`}
label="Ratio limit"
step={0.5}
/>
<NumberField
name={`actions.${idx}.limit_seed_time`}
label="Seed time limit (seconds)"
/>
</div>
</div>
<div className="col-span-6">
<SwitchGroup
name={`actions.${idx}.paused`}
label="Add paused"
description="Add torrent as paused"
/>
<SwitchGroup
name={`actions.${idx}.ignore_rules`}
label="Ignore client rules"
description="Download if max active reached"
/>
</div>
</CollapsableSection>
<CollapsableSection title="Advanced" subtitle="Advanced options">
<div className="col-span-12">
<div className="mt-6 grid grid-cols-12 gap-6">
<NumberField
name={`actions.${idx}.reannounce_interval`}
label="Reannounce interval. Run every X seconds"
/>
<NumberField
name={`actions.${idx}.reannounce_max_attempts`}
label="Run reannounce Y times"
/>
</div>
</div>
<div className="col-span-6">
<SwitchGroup
name={`actions.${idx}.reannounce_skip`}
label="Skip reannounce"
description="If reannounce is not needed, skip"
/>
<SwitchGroup
name={`actions.${idx}.reannounce_delete`}
label="Delete stalled"
description="Delete stalled torrents after X attempts"
/>
</div>
</CollapsableSection>
</div>
);
case "DELUGE_V1":
case "DELUGE_V2":
return (
<div>
<div className="mt-6 grid grid-cols-12 gap-6">
<DownloadClientSelect
name={`actions.${idx}.client_id`}
action={action}
clients={clients}
/>
<div className="col-span-12 sm:col-span-6">
<TextField
name={`actions.${idx}.save_path`}
label="Save path"
columns={6}
/>
</div>
</div>
<div className="mt-6 col-span-12 sm:col-span-6">
<TextField
name={`actions.${idx}.label`}
label="Label"
columns={6}
/>
</div>
<div className="mt-6 grid grid-cols-12 gap-6">
<NumberField
name={`actions.${idx}.limit_download_speed`}
label="Limit download speed (KB/s)"
/>
<NumberField
name={`actions.${idx}.limit_upload_speed`}
label="Limit upload speed (KB/s)"
/>
</div>
<div className="mt-6 grid grid-cols-12 gap-6">
<div className="col-span-6">
<SwitchGroup
name={`actions.${idx}.paused`}
label="Add paused"
/>
</div>
</div>
</div>
);
case "TRANSMISSION":
return (
<div>
<div className="mt-6 grid grid-cols-12 gap-6">
<DownloadClientSelect
name={`actions.${idx}.client_id`}
action={action}
clients={clients}
/>
<div className="col-span-12 sm:col-span-6">
<TextField
name={`actions.${idx}.save_path`}
label="Save path"
columns={6}
/>
</div>
</div>
<div className="mt-6 grid grid-cols-12 gap-6">
<div className="col-span-6">
<SwitchGroup
name={`actions.${idx}.paused`}
label="Add paused"
/>
</div>
</div>
</div>
);
case "RADARR":
case "SONARR":
case "LIDARR":
case "WHISPARR":
return (
<div className="mt-6 grid grid-cols-12 gap-6">
<DownloadClientSelect
name={`actions.${idx}.client_id`}
action={action}
clients={clients}
/>
</div>
);
default:
return null;
}
};
interface FilterActionsItemProps {
action: Action;
clients: DownloadClient[];
@ -617,255 +877,10 @@ interface FilterActionsItemProps {
}
function FilterActionsItem({ action, clients, idx, remove }: FilterActionsItemProps) {
const [deleteModalIsOpen, toggleDeleteModal] = useToggle(false);
const [edit, toggleEdit] = useToggle(false);
const cancelButtonRef = useRef(null);
const TypeForm = (actionType: ActionType) => {
switch (actionType) {
case "TEST":
return (
<AlertWarning
title="Notice"
text="The test action does nothing except to show if the filter works."
/>
);
case "EXEC":
return (
<div>
<div className="mt-6 grid grid-cols-12 gap-6">
<TextField
name={`actions.${idx}.exec_cmd`}
label="Command"
columns={6}
placeholder="Path to program eg. /bin/test"
/>
<TextField
name={`actions.${idx}.exec_args`}
label="Arguments"
columns={6}
placeholder="Arguments eg. --test"
/>
</div>
</div>
);
case "WATCH_FOLDER":
return (
<div className="mt-6 grid grid-cols-12 gap-6">
<TextField
name={`actions.${idx}.watch_folder`}
label="Watch folder"
columns={6}
placeholder="Watch directory eg. /home/user/rwatch"
/>
</div>
);
case "WEBHOOK":
return (
<div className="mt-6 grid grid-cols-12 gap-6">
<TextField
name={`actions.${idx}.webhook_host`}
label="Host"
columns={6}
placeholder="Host eg. http://localhost/webhook"
/>
<TextField
name={`actions.${idx}.webhook_data`}
label="Data (json)"
columns={6}
placeholder={"Request data: { \"key\": \"value\" }"}
/>
</div>
);
case "QBITTORRENT":
return (
<div className="w-full">
<div className="mt-6 grid grid-cols-12 gap-6">
<DownloadClientSelect
name={`actions.${idx}.client_id`}
action={action}
clients={clients}
/>
<div className="col-span-6 sm:col-span-6">
<TextField
name={`actions.${idx}.save_path`}
label="Save path"
columns={6}
placeholder="eg. /full/path/to/watch_folder"
/>
</div>
</div>
<div className="mt-6 grid grid-cols-12 gap-6">
<TextField name={`actions.${idx}.category`} label="Category" columns={6} placeholder="eg. category" />
<TextField name={`actions.${idx}.tags`} label="Tags" columns={6} placeholder="eg. tag1,tag2" />
</div>
<CollapsableSection title="Rules" subtitle="client options">
<div className="col-span-12">
<div className="mt-6 grid grid-cols-12 gap-6">
<NumberField
name={`actions.${idx}.limit_download_speed`}
label="Limit download speed (KB/s)"
/>
<NumberField
name={`actions.${idx}.limit_upload_speed`}
label="Limit upload speed (KB/s)"
/>
</div>
<div className="mt-6 grid grid-cols-12 gap-6">
<NumberField
name={`actions.${idx}.limit_ratio`}
label="Ratio limit"
step={0.5}
/>
<NumberField
name={`actions.${idx}.limit_seed_time`}
label="Seed time limit (seconds)"
/>
</div>
</div>
<div className="col-span-6">
<SwitchGroup
name={`actions.${idx}.paused`}
label="Add paused"
description="Add torrent as paused"
/>
<SwitchGroup
name={`actions.${idx}.ignore_rules`}
label="Ignore client rules"
description="Download if max active reached"
/>
</div>
</CollapsableSection>
<CollapsableSection title="Advanced" subtitle="Advanced options">
<div className="col-span-12">
<div className="mt-6 grid grid-cols-12 gap-6">
<NumberField
name={`actions.${idx}.reannounce_interval`}
label="Reannounce interval. Run every X seconds"
/>
<NumberField
name={`actions.${idx}.reannounce_max_attempts`}
label="Run reannounce Y times"
/>
</div>
</div>
<div className="col-span-6">
<SwitchGroup
name={`actions.${idx}.reannounce_skip`}
label="Skip reannounce"
description="If reannounce is not needed, skip"
/>
<SwitchGroup
name={`actions.${idx}.reannounce_delete`}
label="Delete stalled"
description="Delete stalled torrents after X attempts"
/>
</div>
</CollapsableSection>
</div>
);
case "DELUGE_V1":
case "DELUGE_V2":
return (
<div>
<div className="mt-6 grid grid-cols-12 gap-6">
<DownloadClientSelect
name={`actions.${idx}.client_id`}
action={action}
clients={clients}
/>
<div className="col-span-12 sm:col-span-6">
<TextField
name={`actions.${idx}.save_path`}
label="Save path"
columns={6}
/>
</div>
</div>
<div className="mt-6 col-span-12 sm:col-span-6">
<TextField
name={`actions.${idx}.label`}
label="Label"
columns={6}
/>
</div>
<div className="mt-6 grid grid-cols-12 gap-6">
<NumberField
name={`actions.${idx}.limit_download_speed`}
label="Limit download speed (KB/s)"
/>
<NumberField
name={`actions.${idx}.limit_upload_speed`}
label="Limit upload speed (KB/s)"
/>
</div>
<div className="mt-6 grid grid-cols-12 gap-6">
<div className="col-span-6">
<SwitchGroup
name={`actions.${idx}.paused`}
label="Add paused"
/>
</div>
</div>
</div>
);
case "TRANSMISSION":
return (
<div>
<div className="mt-6 grid grid-cols-12 gap-6">
<DownloadClientSelect
name={`actions.${idx}.client_id`}
action={action}
clients={clients}
/>
<div className="col-span-12 sm:col-span-6">
<TextField
name={`actions.${idx}.save_path`}
label="Save path"
columns={6}
/>
</div>
</div>
<div className="mt-6 grid grid-cols-12 gap-6">
<div className="col-span-6">
<SwitchGroup
name={`actions.${idx}.paused`}
label="Add paused"
/>
</div>
</div>
</div>
);
case "RADARR":
case "SONARR":
case "LIDARR":
case "WHISPARR":
return (
<div className="mt-6 grid grid-cols-12 gap-6">
<DownloadClientSelect
name={`actions.${idx}.client_id`}
action={action}
clients={clients}
/>
</div>
);
default:
return null;
}
};
const [deleteModalIsOpen, toggleDeleteModal] = useToggle(false);
const [edit, toggleEdit] = useToggle(false);
return (
<li>
@ -966,7 +981,7 @@ function FilterActionsItem({ action, clients, idx, remove }: FilterActionsItemPr
<TextField name={`actions.${idx}.name`} label="Name" columns={6} />
</div>
{TypeForm(action.type)}
<TypeForm action={action} clients={clients} idx={idx} />
<div className="pt-6 divide-y divide-gray-200">
<div className="mt-4 pt-4 flex justify-between">

View file

@ -72,7 +72,7 @@ interface FilterListProps {
function FilterList({ filters }: FilterListProps) {
return (
<div className="overflow-x-auto align-middle min-w-full rounded-lg shadow-lg">
<div className="overflow-x-auto align-middle min-w-full rounded-t-md rounded-b-lg shadow-lg">
<table className="min-w-full">
<thead className="bg-gray-50 dark:bg-gray-800 text-gray-500 dark:text-gray-400 border-b border-gray-200 dark:border-gray-700">
<tr>
@ -80,7 +80,7 @@ function FilterList({ filters }: FilterListProps) {
<th
key={`th-${label}`}
scope="col"
className="px-6 py-2.5 text-left text-xs font-medium uppercase tracking-wider"
className="px-6 pt-4 pb-3 text-left text-xs font-medium uppercase tracking-wider"
>
{label}
</th>

View file

@ -1,26 +1,20 @@
function ActionSettings() {
return (
<div className="divide-y divide-gray-200 lg:col-span-9">
<div className="lg:col-span-9">
<div className="py-6 px-4 sm:p-6 lg:pb-8">
{/*{addClientIsOpen &&*/}
{/*<AddNewClientForm isOpen={addClientIsOpen} toggle={toggleAddClient}/>*/}
{/*}*/}
<div className="-ml-4 -mt-4 flex justify-between items-center flex-wrap sm:flex-nowrap">
<div className="ml-4 mt-4">
<h3 className="text-lg leading-6 font-medium text-gray-900">Actions</h3>
<p className="mt-1 text-sm text-gray-500">
Manage actions.
Manage actions.
</p>
</div>
<div className="ml-4 mt-4 flex-shrink-0">
<button
type="button"
className="relative inline-flex items-center px-4 py-2 border border-transparent shadow-sm text-sm font-medium rounded-md text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"
// onClick={toggleAddClient}
>
Add new
Add new
</button>
</div>
</div>
@ -36,25 +30,25 @@ function ActionSettings() {
scope="col"
className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"
>
Name
Name
</th>
<th
scope="col"
className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"
>
Type
Type
</th>
<th
scope="col"
className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"
>
Port
Port
</th>
<th
scope="col"
className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"
>
Enabled
Enabled
</th>
<th scope="col" className="relative px-6 py-3">
<span className="sr-only">Edit</span>

View file

@ -23,7 +23,7 @@ function ApplicationSettings() {
<div>
<h2 className="text-lg leading-6 font-medium text-gray-900 dark:text-white">Application</h2>
<p className="mt-1 text-sm text-gray-500 dark:text-gray-400">
Application settings. Change in config.toml and restart to take effect.
Application settings. Change in config.toml and restart to take effect.
</p>
</div>
@ -31,7 +31,7 @@ function ApplicationSettings() {
<div className="mt-6 grid grid-cols-12 gap-6">
<div className="col-span-6 sm:col-span-4">
<label htmlFor="host" className="block text-xs font-bold text-gray-700 dark:text-gray-200 uppercase tracking-wide">
Host
Host
</label>
<input
type="text"
@ -39,13 +39,13 @@ function ApplicationSettings() {
id="host"
value={data.host}
disabled={true}
className="mt-2 block w-full dark:bg-gray-800 border border-gray-300 dark:border-gray-700 border border-gray-300 rounded-md shadow-sm py-2 px-3 focus:outline-none focus:ring-blue-500 focus:border-blue-500 dark:text-gray-100 sm:text-sm"
className="mt-2 block w-full dark:bg-gray-800 border border-gray-300 dark:border-gray-700 rounded-md shadow-sm py-2 px-3 focus:outline-none focus:ring-blue-500 focus:border-blue-500 dark:text-gray-100 sm:text-sm"
/>
</div>
<div className="col-span-6 sm:col-span-4">
<label htmlFor="port" className="block text-xs font-bold text-gray-700 dark:text-gray-200 uppercase tracking-wide">
Port
Port
</label>
<input
type="text"
@ -53,13 +53,13 @@ function ApplicationSettings() {
id="port"
value={data.port}
disabled={true}
className="mt-2 block w-full dark:bg-gray-800 border border-gray-300 dark:border-gray-700 border border-gray-300 rounded-md shadow-sm py-2 px-3 focus:outline-none focus:ring-blue-500 focus:border-blue-500 dark:text-gray-100 sm:text-sm"
className="mt-2 block w-full dark:bg-gray-800 border border-gray-300 dark:border-gray-700 rounded-md shadow-sm py-2 px-3 focus:outline-none focus:ring-blue-500 focus:border-blue-500 dark:text-gray-100 sm:text-sm"
/>
</div>
<div className="col-span-6 sm:col-span-4">
<label htmlFor="base_url" className="block text-xs font-bold text-gray-700 dark:text-gray-200 uppercase tracking-wide">
Base url
Base url
</label>
<input
type="text"
@ -67,7 +67,7 @@ function ApplicationSettings() {
id="base_url"
value={data.base_url}
disabled={true}
className="mt-2 block w-full dark:bg-gray-800 border border-gray-300 dark:border-gray-700 border border-gray-300 rounded-md shadow-sm py-2 px-3 focus:outline-none focus:ring-blue-500 focus:border-blue-500 dark:text-gray-100 sm:text-sm"
className="mt-2 block w-full dark:bg-gray-800 border border-gray-300 dark:border-gray-700 rounded-md shadow-sm py-2 px-3 focus:outline-none focus:ring-blue-500 focus:border-blue-500 dark:text-gray-100 sm:text-sm"
/>
</div>
</div>
@ -80,19 +80,21 @@ function ApplicationSettings() {
{data?.version ? (
<div className="py-4 sm:py-5 sm:grid sm:grid-cols-4 sm:gap-4 sm:px-6">
<dt className="font-medium text-gray-500 dark:text-white">Version:</dt>
<dd className="mt-1 text-gray-900 dark:text-white sm:mt-0 sm:col-span-2">{data?.version}</dd>
<dd className="mt-1 text-gray-900 dark:text-white sm:mt-0 sm:col-span-2 break-all">
{data?.version}
</dd>
</div>
) : null}
{data?.commit ? (
<div className="py-4 sm:py-5 sm:grid sm:grid-cols-4 sm:gap-4 sm:px-6">
<dt className="font-medium text-gray-500 dark:text-white">Commit:</dt>
<dd className="mt-1 text-gray-900 dark:text-white sm:mt-0 sm:col-span-2">{data.commit}</dd>
<dd className="mt-1 text-gray-900 dark:text-white sm:mt-0 sm:col-span-2 break-all">{data.commit}</dd>
</div>
) : null}
{data?.date ? (
<div className="py-4 sm:py-5 sm:grid sm:grid-cols-4 sm:gap-4 sm:px-6">
<dt className="font-medium text-gray-500 dark:text-white">Date:</dt>
<dd className="mt-1 text-gray-900 dark:text-white sm:mt-0 sm:col-span-2">{data?.date}</dd>
<dd className="mt-1 text-gray-900 dark:text-white sm:mt-0 sm:col-span-2 break-all">{data?.date}</dd>
</div>
) : null}
</dl>
@ -123,7 +125,6 @@ function ApplicationSettings() {
</ul>
</div>
</form>
);
}

View file

@ -86,7 +86,7 @@ function DownloadClientSettings() {
return (<p>An error has occurred: </p>);
return (
<div className="divide-y divide-gray-200 lg:col-span-9">
<div className="lg:col-span-9">
<DownloadClientAddForm isOpen={addClientIsOpen} toggle={toggleAddClient} />
@ -121,25 +121,25 @@ function DownloadClientSettings() {
scope="col"
className="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider"
>
Enabled
Enabled
</th>
<th
scope="col"
className="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider"
>
Name
Name
</th>
<th
scope="col"
className="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider"
>
Host
Host
</th>
<th
scope="col"
className="px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider"
>
Type
Type
</th>
<th scope="col" className="relative px-6 py-3">
<span className="sr-only">Edit</span>

View file

@ -27,13 +27,13 @@ function FeedSettings() {
);
return (
<div className="divide-y divide-gray-200 lg:col-span-9">
<div className="lg:col-span-9">
<div className="py-6 px-4 sm:p-6 lg:pb-8">
<div className="-ml-4 -mt-4 flex justify-between items-center flex-wrap sm:flex-nowrap">
<div className="ml-4 mt-4">
<h3 className="text-lg leading-6 font-medium text-gray-900 dark:text-white">Feeds</h3>
<p className="mt-1 text-sm text-gray-500 dark:text-gray-400">
Manage Torznab feeds.
Manage Torznab feeds.
</p>
</div>
</div>

View file

@ -82,7 +82,7 @@ function IndexerSettings() {
return (<p>An error has occurred</p>);
return (
<div className="divide-y divide-gray-200 lg:col-span-9">
<div className="lg:col-span-9">
<IndexerAddForm isOpen={addIndexerIsOpen} toggle={toggleAddIndexer} />

View file

@ -1,41 +1,35 @@
import { useQuery } from "react-query";
import {
simplifyDate,
IsEmptyDate
} from "../../utils";
import {
IrcNetworkAddForm,
IrcNetworkUpdateForm
} from "../../forms";
import { simplifyDate, IsEmptyDate, classNames } from "../../utils";
import { IrcNetworkAddForm, IrcNetworkUpdateForm } from "../../forms";
import { useToggle } from "../../hooks/hooks";
import { APIClient } from "../../api/APIClient";
import { EmptySimple } from "../../components/emptystates";
import {ExclamationCircleIcon} from "@heroicons/react/outline";
import { ExclamationCircleIcon } from "@heroicons/react/outline";
import { LockClosedIcon, LockOpenIcon } from "@heroicons/react/solid";
export const IrcSettings = () => {
const [addNetworkIsOpen, toggleAddNetwork] = useToggle(false);
const { data } = useQuery(
"networks",
() => APIClient.irc.getNetworks(),
{
refetchOnWindowFocus: false,
// Refetch every 3 seconds
refetchInterval: 3000
}
);
const { data } = useQuery("networks", () => APIClient.irc.getNetworks(), {
refetchOnWindowFocus: false,
// Refetch every 3 seconds
refetchInterval: 3000
});
return (
<div className="divide-y divide-gray-200 lg:col-span-9">
<div className="lg:col-span-9">
<IrcNetworkAddForm isOpen={addNetworkIsOpen} toggle={toggleAddNetwork} />
<div className="py-6 px-4 sm:p-6 lg:pb-8">
<div className="-ml-4 -mt-4 flex justify-between items-center flex-wrap sm:flex-nowrap">
<div className="ml-4 mt-4">
<h3 className="text-lg leading-6 font-medium text-gray-900 dark:text-white">IRC</h3>
<h3 className="text-lg leading-6 font-medium text-gray-900 dark:text-white">
IRC
</h3>
<p className="mt-1 text-sm text-gray-500 dark:text-gray-400">
IRC networks and channels. Click on a network to view channel status.
IRC networks and channels. Click on a network to view channel
status.
</p>
</div>
<div className="ml-4 mt-4 flex-shrink-0">
@ -44,35 +38,46 @@ export const IrcSettings = () => {
onClick={toggleAddNetwork}
className="relative inline-flex items-center px-4 py-2 border border-transparent shadow-sm text-sm font-medium rounded-md text-white bg-indigo-600 dark:bg-blue-600 hover:bg-indigo-700 dark:hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"
>
Add new
Add new
</button>
</div>
</div>
{data && data.length > 0 ? (
<section className="mt-6 light:bg-white dark:bg-gray-800 light:shadow sm:rounded-md">
<ol className="min-w-full">
<li className="grid grid-cols-12 gap-4 border-b border-gray-200 dark:border-gray-700">
{/* <div className="col-span-1 px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">Enabled</div> */}
<div className="col-span-3 px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">Network</div>
<div className="col-span-4 px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">Server</div>
<div className="col-span-4 px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">Nick</div>
<div className="col-span-3 px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">
Network
</div>
<div className="col-span-5 px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">
Server
</div>
<div className="col-span-3 px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">
Nick
</div>
</li>
{data && data.map((network, idx) => (
<ListItem key={idx} idx={idx} network={network} />
))}
{data &&
data.map((network, idx) => (
<ListItem key={idx} idx={idx} network={network} />
))}
</ol>
</section>
) : <EmptySimple title="No networks" subtitle="Add a new network" buttonText="New network" buttonAction={toggleAddNetwork} />}
) : (
<EmptySimple
title="No networks"
subtitle="Add a new network"
buttonText="New network"
buttonAction={toggleAddNetwork}
/>
)}
</div>
</div>
);
};
interface ListItemProps {
idx: number;
network: IrcNetworkWithHealth;
idx: number;
network: IrcNetworkWithHealth;
}
const ListItem = ({ idx, network }: ListItemProps) => {
@ -80,33 +85,91 @@ const ListItem = ({ idx, network }: ListItemProps) => {
const [edit, toggleEdit] = useToggle(false);
return (
<li key={idx}>
<div className="grid grid-cols-12 gap-2 lg:gap-4 items-center hover:bg-gray-50 dark:hover:bg-gray-700 py-4">
<IrcNetworkUpdateForm
isOpen={updateIsOpen}
toggle={toggleUpdate}
network={network}
/>
<li key={idx} >
<div className="grid grid-cols-12 gap-4 items-center hover:bg-gray-50 dark:hover:bg-gray-700 py-4">
<IrcNetworkUpdateForm isOpen={updateIsOpen} toggle={toggleUpdate} network={network} />
<div className="col-span-3 items-center sm:px-6 text-sm font-medium text-gray-900 dark:text-white cursor-pointer" onClick={toggleEdit}>
<span className="relative inline-flex items-center">
{
network.enabled ? (
networkHealthy(network) ? (
<span className="mr-3 flex h-3 w-3 relative" title={`Connected since: ${simplifyDate(network.connected_since)}`}>
<span className="animate-ping inline-flex h-full w-full rounded-full bg-green-400 opacity-75"/>
<span className="inline-flex absolute rounded-full h-3 w-3 bg-green-500"/>
<div
className="col-span-3 items-center sm:px-6 text-sm font-medium text-gray-900 dark:text-white cursor-pointer"
onClick={toggleEdit}
>
<div className="flex">
<span className="relative inline-flex items-center ml-1">
{network.enabled ? (
IsNetworkHealthy(network) ? (
<span
className="mr-3 flex h-3 w-3 relative"
title={`Connected since: ${simplifyDate(network.connected_since)}`}
>
<span className="animate-ping inline-flex h-full w-full rounded-full bg-green-400 opacity-75" />
<span className="inline-flex absolute rounded-full h-3 w-3 bg-green-500" />
</span>
) : <span className="mr-3 flex items-center" title={network.connection_errors.toString()}><ExclamationCircleIcon className="h-4 w-4 text-red-400 hover:text-red-600" /></span>
) : <span className="mr-3 flex h-3 w-3 rounded-full opacity-75 bg-gray-500" />
}
{network.name}
</span>
) : (
<span
className="mr-3 flex items-center"
title={network.connection_errors.toString()}
>
<ExclamationCircleIcon className="h-4 w-4 text-red-400 hover:text-red-600" />
</span>
)
) : (
<span className="mr-3 flex h-3 w-3 rounded-full opacity-75 bg-gray-500" />
)}
</span>
<div className="overflow-x-auto flex">
{network.name}
</div>
</div>
</div>
<div
className="col-span-5 sm:px-6 text-sm text-gray-500 dark:text-gray-400 cursor-pointer"
onClick={toggleEdit}
>
<div
className="overflow-x-auto flex items-center"
title={network.tls ? "Secured using TLS" : "Insecure, not using TLS"}
>
<div className="min-h-2 min-w-2">
{network.tls ? (
<LockClosedIcon
className={classNames(
"mr-2 h-4 w-4",
network.enabled ? "text-green-600" : "text-gray-500"
)}
/>
) : (
<LockOpenIcon className={classNames(
"mr-2 h-4 w-4",
network.enabled ? "text-red-500" : "text-yellow-500"
)} />
)}
</div>
<p className="break-all">
{network.server}:{network.port}
</p>
</div>
</div>
<div className="col-span-4 flex justify-between items-center sm:px-6 text-sm text-gray-500 dark:text-gray-400 cursor-pointer" onClick={toggleEdit}>{network.server}:{network.port} {network.tls && <span className="ml-2 inline-flex items-center px-2 py-0.5 rounded text-xs font-medium bg-green-100 dark:bg-green-300 text-green-800 dark:text-green-900">TLS</span>}</div>
{network.nickserv && network.nickserv.account ? (
<div className="col-span-4 items-center sm:px-6 text-sm text-gray-500 dark:text-gray-400 cursor-pointer" onClick={toggleEdit}>{network.nickserv.account}</div>
) : <div className="col-span-4" />}
<div
className="col-span-3 items-center sm:px-6 text-sm text-gray-500 dark:text-gray-400 cursor-pointer"
onClick={toggleEdit}
>
<div className="overflow-x-auto flex">
{network.nickserv.account}
</div>
</div>
) : (
<div className="col-span-3" />
)}
<div className="col-span-1 text-sm text-gray-500 dark:text-gray-400">
<span className="text-indigo-600 dark:text-gray-300 hover:text-indigo-900 cursor-pointer" onClick={toggleUpdate}>
<span
className="text-indigo-600 dark:text-gray-300 hover:text-indigo-900 cursor-pointer"
onClick={toggleUpdate}
>
Edit
</span>
</div>
@ -117,39 +180,58 @@ const ListItem = ({ idx, network }: ListItemProps) => {
{network.channels.length > 0 ? (
<ol>
<li className="grid grid-cols-12 gap-4 border-b border-gray-200 dark:border-gray-700">
<div className="col-span-4 px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">Channel</div>
<div className="col-span-4 px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">Monitoring since</div>
<div className="col-span-4 px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">Last announce</div>
<div className="col-span-4 px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">
Channel
</div>
<div className="col-span-4 px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">
Monitoring since
</div>
<div className="col-span-4 px-6 py-3 text-left text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wider">
Last announce
</div>
</li>
{network.channels.map(c => (
{network.channels.map((c) => (
<li key={c.id} className="text-gray-500 dark:text-gray-400">
<div className="grid grid-cols-12 gap-4 items-center py-4">
<div className="col-span-4 flex items-center sm:px-6 ">
<span className="relative inline-flex items-center">
{
network.enabled ? (
c.monitoring ? (
<span className="mr-3 flex h-3 w-3 relative" title="monitoring">
<span className="animate-ping inline-flex h-full w-full rounded-full bg-green-400 opacity-75"/>
<span className="inline-flex absolute rounded-full h-3 w-3 bg-green-500"/>
</span>
) : <span className="mr-3 flex h-3 w-3 rounded-full opacity-75 bg-red-400" />
) : <span className="mr-3 flex h-3 w-3 rounded-full opacity-75 bg-gray-500" />
}
{network.enabled ? (
c.monitoring ? (
<span
className="mr-3 flex h-3 w-3 relative"
title="monitoring"
>
<span className="animate-ping inline-flex h-full w-full rounded-full bg-green-400 opacity-75" />
<span className="inline-flex absolute rounded-full h-3 w-3 bg-green-500" />
</span>
) : (
<span className="mr-3 flex h-3 w-3 rounded-full opacity-75 bg-red-400" />
)
) : (
<span className="mr-3 flex h-3 w-3 rounded-full opacity-75 bg-gray-500" />
)}
{c.name}
</span>
</div>
<div className="col-span-4 flex items-center sm:px-6 ">
<span className="" title={simplifyDate(c.monitoring_since)}>{IsEmptyDate(c.monitoring_since)}</span>
<span title={simplifyDate(c.monitoring_since)}>
{IsEmptyDate(c.monitoring_since)}
</span>
</div>
<div className="col-span-4 flex items-center sm:px-6 ">
<span className="" title={simplifyDate(c.last_announce)}>{IsEmptyDate(c.last_announce)}</span>
<span title={simplifyDate(c.last_announce)}>
{IsEmptyDate(c.last_announce)}
</span>
</div>
</div>
</li>
))}
</ol>
) : <div className="flex text-center justify-center py-4 dark:text-gray-500"><p>No channels!</p></div>}
) : (
<div className="flex text-center justify-center py-4 dark:text-gray-500">
<p>No channels!</p>
</div>
)}
</div>
</div>
)}
@ -157,10 +239,5 @@ const ListItem = ({ idx, network }: ListItemProps) => {
);
};
function networkHealthy(network: IrcNetworkWithHealth): boolean {
if (network.connection_errors.length > 0) {
return false
}
return true
}
const IsNetworkHealthy = (network: IrcNetworkWithHealth) =>
network.connection_errors.length <= 0;

View file

@ -2,7 +2,7 @@ import { useQuery } from "react-query";
import { APIClient } from "../../api/APIClient";
import { EmptySimple } from "../../components/emptystates";
import { useToggle } from "../../hooks/hooks";
import { NotificationAddForm, NotificationUpdateForm } from "../../forms/settings/NotifiactionForms";
import { NotificationAddForm, NotificationUpdateForm } from "../../forms/settings/NotificationForms";
import { Switch } from "@headlessui/react";
import { classNames } from "../../utils";
import { componentMapType } from "../../forms/settings/DownloadClientForms";

View file

@ -28,7 +28,11 @@ function ReleaseSettings() {
const cancelModalButtonRef = useRef(null);
return (
<form className="divide-y divide-gray-200 dark:divide-gray-700 lg:col-span-9" action="#" method="POST">
<form
className="lg:col-span-9"
action="#"
method="POST"
>
<DeleteModal
isOpen={deleteModalIsOpen}
toggle={toggleDeleteModal}
@ -40,9 +44,11 @@ function ReleaseSettings() {
<div className="py-6 px-4 sm:p-6 lg:pb-8">
<div>
<h2 className="text-lg leading-6 font-medium text-gray-900 dark:text-white">Releases</h2>
<h2 className="text-lg leading-6 font-medium text-gray-900 dark:text-white">
Releases
</h2>
<p className="mt-1 text-sm text-gray-500 dark:text-gray-400">
Release settings. Reset state.
Release settings. Reset state.
</p>
</div>
</div>
@ -50,25 +56,21 @@ function ReleaseSettings() {
<div className="pb-6 divide-y divide-gray-200 dark:divide-gray-700">
<div className="px-4 py-5 sm:p-0">
<div className="px-4 py-5 sm:p-6">
<div>
<h3 className="text-lg leading-6 font-medium text-gray-900 dark:text-white">Danger Zone</h3>
<h3 className="text-lg leading-6 font-medium text-gray-900 dark:text-white">
Danger Zone
</h3>
</div>
<ul className="p-4 mt-6 divide-y divide-gray-200 dark:divide-gray-700 border-red-500 border rounded-lg">
<div className="flex justify-between items-center py-2">
<p className="text-sm text-gray-500 dark:text-gray-400">
Delete all releases
</p>
<button
type="button"
onClick={toggleDeleteModal}
className="inline-flex items-center justify-center px-4 py-2 border border-transparent font-medium rounded-md text-red-700 dark:text-red-100 bg-red-100 dark:bg-red-500 hover:bg-red-200 dark:hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500 sm:text-sm"
>
Delete all releases
</button>
</div>
</ul>
<div className="flex justify-between items-center p-4 mt-6 max-w-sm m-auto">
<button
type="button"
onClick={toggleDeleteModal}
className="w-full inline-flex items-center justify-center px-4 py-2 border border-transparent font-medium rounded-md text-red-700 hover:text-red-900 dark:text-white bg-red-100 dark:bg-red-800 hover:bg-red-200 dark:hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500 sm:text-sm"
>
Delete all releases
</button>
</div>
</div>
</div>
</div>