feat(irc): improve list view (#466)

* feat(irc): add irc status examples

* feat(irc): add dropdown menu to list

* feat(irc): update heroicons and add expand button

* feat(irc): update heroicons and add expand button
This commit is contained in:
ze0s 2022-09-22 16:39:05 +02:00 committed by GitHub
parent f5faf066a9
commit 300418b9f1
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
34 changed files with 478 additions and 258 deletions

View file

@ -19,6 +19,7 @@ type ircService interface {
StoreNetwork(ctx context.Context, network *domain.IrcNetwork) error
UpdateNetwork(ctx context.Context, network *domain.IrcNetwork) error
StoreChannel(networkID int64, channel *domain.IrcChannel) error
RestartNetwork(ctx context.Context, id int64) error
}
type ircHandler struct {
@ -38,6 +39,7 @@ func (h ircHandler) Routes(r chi.Router) {
r.Post("/", h.storeNetwork)
r.Put("/network/{networkID}", h.updateNetwork)
r.Post("/network/{networkID}/channel", h.storeChannel)
r.Get("/network/{networkID}/restart", h.restartNetwork)
r.Get("/network/{networkID}", h.getNetworkByID)
r.Delete("/network/{networkID}", h.deleteNetwork)
}
@ -69,6 +71,21 @@ func (h ircHandler) getNetworkByID(w http.ResponseWriter, r *http.Request) {
h.encoder.StatusResponse(ctx, w, network, http.StatusOK)
}
func (h ircHandler) restartNetwork(w http.ResponseWriter, r *http.Request) {
var (
ctx = r.Context()
networkID = chi.URLParam(r, "networkID")
)
id, _ := strconv.Atoi(networkID)
if err := h.service.RestartNetwork(ctx, int64(id)); err != nil {
h.encoder.Error(w, err)
}
h.encoder.NoContent(w)
}
func (h ircHandler) storeNetwork(w http.ResponseWriter, r *http.Request) {
var data domain.IrcNetwork

View file

@ -20,6 +20,7 @@ type Service interface {
StartHandlers()
StopHandlers()
StopNetwork(key handlerKey) error
RestartNetwork(ctx context.Context, id int64) error
ListNetworks(ctx context.Context) ([]domain.IrcNetwork, error)
GetNetworksWithHealth(ctx context.Context) ([]domain.IrcNetworkWithHealth, error)
GetNetworkByID(ctx context.Context, id int64) (*domain.IrcNetwork, error)
@ -279,6 +280,15 @@ func (s *service) checkIfNetworkRestartNeeded(network *domain.IrcNetwork) error
return nil
}
func (s *service) RestartNetwork(ctx context.Context, id int64) error {
network, err := s.repo.GetNetworkByID(ctx, id)
if err != nil {
return err
}
return s.restartNetwork(*network)
}
func (s *service) restartNetwork(network domain.IrcNetwork) error {
// look if we have the network in handlers, if so restart it
if existingHandler, found := s.handlers[handlerKey{network.Server, network.NickServ.Account}]; found {

View file

@ -9,7 +9,7 @@
"dependencies": {
"@fontsource/inter": "^4.5.11",
"@headlessui/react": "^1.6.4",
"@heroicons/react": "^1.0.6",
"@heroicons/react": "^2.0.11",
"@hookform/error-message": "^2.0.0",
"date-fns": "^2.28.0",
"formik": "^2.2.9",

View file

@ -139,7 +139,8 @@ export const APIClient = {
getNetworks: () => appClient.Get<IrcNetworkWithHealth[]>("api/irc"),
createNetwork: (network: IrcNetworkCreate) => appClient.Post("api/irc", network),
updateNetwork: (network: IrcNetwork) => appClient.Put(`api/irc/network/${network.id}`, network),
deleteNetwork: (id: number) => appClient.Delete(`api/irc/network/${id}`)
deleteNetwork: (id: number) => appClient.Delete(`api/irc/network/${id}`),
restartNetwork: (id: number) => appClient.Get(`api/irc/network/${id}/restart`)
},
events: {
logs: () => new EventSource(`${sseBaseUrl()}api/events?stream=logs`, { withCredentials: true })

View file

@ -1,6 +1,6 @@
import StackTracey from "stacktracey";
import type { FallbackProps } from "react-error-boundary";
import { RefreshIcon } from "@heroicons/react/solid";
import { ArrowPathIcon } from "@heroicons/react/24/solid";
export const ErrorPage = ({ error, resetErrorBoundary }: FallbackProps) => {
const stack = new StackTracey(error);
@ -73,7 +73,7 @@ export const ErrorPage = ({ error, resetErrorBoundary }: FallbackProps) => {
resetErrorBoundary();
}}
>
<RefreshIcon className="-ml-0.5 mr-2 h-5 w-5"/>
<ArrowPathIcon className="-ml-0.5 mr-2 h-5 w-5"/>
Reset page state
</button>
</div>

View file

@ -1,5 +1,4 @@
import { ExclamationIcon } from "@heroicons/react/solid";
import { ExclamationTriangleIcon } from "@heroicons/react/24/outline";
interface props {
title?: string;
@ -11,7 +10,7 @@ export function AlertWarning({ title, text }: props) {
<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
<ExclamationTriangleIcon
className="h-5 w-5 text-yellow-400 dark:text-yellow-600"
aria-hidden="true"
/>

View file

@ -1,7 +1,7 @@
import * as React from "react";
import { formatDistanceToNowStrict } from "date-fns";
import { CheckIcon } from "@heroicons/react/solid";
import { ClockIcon, BanIcon, ExclamationCircleIcon } from "@heroicons/react/outline";
import { CheckIcon } from "@heroicons/react/24/solid";
import { ClockIcon, ExclamationCircleIcon, NoSymbolIcon } from "@heroicons/react/24/outline";
import { classNames, simplifyDate } from "../../utils";
import { Tooltip } from "../tooltips/Tooltip";
@ -41,7 +41,7 @@ const StatusCellMap: Record<string, StatusCellMapEntry> = {
},
"PUSH_REJECTED": {
colors: "bg-blue-200 dark:bg-blue-100 text-blue-400 dark:text-blue-800 hover:bg-blue-300 dark:hover:bg-blue-400",
icon: <BanIcon className="h-5 w-5" aria-hidden="true" />
icon: <NoSymbolIcon className="h-5 w-5" aria-hidden="true" />
},
"PUSH_APPROVED": {
colors: "bg-green-100 text-green-800 hover:bg-green-300",

View file

@ -1,4 +1,4 @@
import { PlusIcon } from "@heroicons/react/solid";
import { PlusIcon } from "@heroicons/react/24/solid";
interface EmptySimpleProps {
title: string;

View file

@ -1,5 +1,5 @@
import { useToggle } from "../../hooks/hooks";
import { ClipboardCopyIcon, EyeIcon, EyeOffIcon, CheckIcon } from "@heroicons/react/outline";
import { CheckIcon, DocumentDuplicateIcon, EyeIcon, EyeSlashIcon } from "@heroicons/react/24/outline";
import { useState } from "react";
interface KeyFieldProps {
@ -52,7 +52,7 @@ export const KeyField = ({ value }: KeyFieldProps) => {
onClick={toggleVisibility}
title="show"
>
{!isVisible ? <EyeIcon className="h-5 w-5 text-gray-400 hover:text-gray-500" aria-hidden="true" /> : <EyeOffIcon className="h-5 w-5 text-gray-400 hover:text-gray-500" aria-hidden="true" />}
{!isVisible ? <EyeIcon className="h-5 w-5 text-gray-400 hover:text-gray-500" aria-hidden="true" /> : <EyeSlashIcon className="h-5 w-5 text-gray-400 hover:text-gray-500" aria-hidden="true" />}
</button>
<button
type="button"
@ -65,7 +65,7 @@ export const KeyField = ({ value }: KeyFieldProps) => {
className="text-blue-500 w-5 h-5"
aria-hidden="true"
/>
: <ClipboardCopyIcon
: <DocumentDuplicateIcon
className="text-blue-500 w-5 h-5"
aria-hidden="true"
/>

View file

@ -1,6 +1,6 @@
import { Field, FieldProps } from "formik";
import { classNames } from "../../utils";
import { EyeIcon, EyeOffIcon } from "@heroicons/react/solid";
import { EyeIcon, EyeSlashIcon } from "@heroicons/react/24/solid";
import { useToggle } from "../../hooks/hooks";
type COL_WIDTHS = 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12;
@ -24,7 +24,7 @@ export const TextField = ({
columns,
autoComplete,
hidden,
disabled,
disabled
}: TextFieldProps) => (
<div
className={classNames(
@ -52,7 +52,7 @@ export const TextField = ({
className={classNames(
meta.touched && meta.error ? "focus:ring-red-500 focus:border-red-500 border-red-500" : "focus:ring-indigo-500 dark:focus:ring-blue-500 focus:border-indigo-500 dark:focus:border-blue-500 border-gray-300 dark:border-gray-700",
disabled ? "bg-gray-100 dark:bg-gray-700 cursor-not-allowed" : "dark:bg-gray-800",
"mt-2 block w-full dark:text-gray-100 rounded-md",
"mt-2 block w-full dark:text-gray-100 rounded-md"
)}
disabled={disabled}
placeholder={placeholder}
@ -88,7 +88,7 @@ export const TextArea = ({
rows,
autoComplete,
hidden,
disabled,
disabled
}: TextAreaProps) => (
<div
className={classNames(
@ -116,7 +116,7 @@ export const TextArea = ({
className={classNames(
meta.touched && meta.error ? "focus:ring-red-500 focus:border-red-500 border-red-500" : "focus:ring-indigo-500 dark:focus:ring-blue-500 focus:border-indigo-500 dark:focus:border-blue-500 border-gray-300 dark:border-gray-700",
disabled ? "bg-gray-100 dark:bg-gray-700 cursor-not-allowed" : "dark:bg-gray-800",
"mt-2 block w-full dark:text-gray-100 rounded-md",
"mt-2 block w-full dark:text-gray-100 rounded-md"
)}
placeholder={placeholder}
disabled={disabled}
@ -181,7 +181,7 @@ export const PasswordField = ({
/>
<div className="absolute inset-y-0 right-0 px-3 flex items-center" onClick={toggleVisibility}>
{!isVisible ? <EyeIcon className="h-5 w-5 text-gray-400 hover:text-gray-500" aria-hidden="true" /> : <EyeOffIcon className="h-5 w-5 text-gray-400 hover:text-gray-500" aria-hidden="true" />}
{!isVisible ? <EyeIcon className="h-5 w-5 text-gray-400 hover:text-gray-500" aria-hidden="true" /> : <EyeSlashIcon className="h-5 w-5 text-gray-400 hover:text-gray-500" aria-hidden="true" />}
</div>
{help && (
@ -211,7 +211,7 @@ export const NumberField = ({
label,
placeholder,
step,
disabled,
disabled
}: NumberFieldProps) => (
<div className="col-span-12 sm:col-span-6">
<label htmlFor={name} className="block text-xs font-bold text-gray-700 dark:text-gray-200 uppercase tracking-wide">
@ -233,7 +233,7 @@ export const NumberField = ({
? "focus:ring-red-500 focus:border-red-500 border-red-500"
: "focus:ring-indigo-500 dark:focus:ring-blue-500 focus:border-indigo-500 dark:focus:border-blue-500 border-gray-300",
"mt-2 block w-full border border-gray-300 dark:border-gray-700 dark:text-gray-100 rounded-md",
disabled ? "bg-gray-100 dark:bg-gray-700 cursor-not-allowed" : "dark:bg-gray-800",
disabled ? "bg-gray-100 dark:bg-gray-700 cursor-not-allowed" : "dark:bg-gray-800"
)}
placeholder={placeholder}
disabled={disabled}

View file

@ -1,8 +1,8 @@
import { Field } from "formik";
import type { FieldProps } from "formik";
import { Field } from "formik";
import { classNames } from "../../utils";
import { useToggle } from "../../hooks/hooks";
import { EyeIcon, EyeOffIcon } from "@heroicons/react/solid";
import { EyeIcon, EyeSlashIcon } from "@heroicons/react/24/solid";
import { Switch } from "@headlessui/react";
import { ErrorField } from "./common";
@ -103,7 +103,7 @@ export const PasswordFieldWide = ({
placeholder={placeholder}
/>
<div className="absolute inset-y-0 right-0 px-3 flex items-center" onClick={toggleVisibility}>
{!isVisible ? <EyeIcon className="h-5 w-5 text-gray-400 hover:text-gray-500" aria-hidden="true" /> : <EyeOffIcon className="h-5 w-5 text-gray-400 hover:text-gray-500" aria-hidden="true" />}
{!isVisible ? <EyeIcon className="h-5 w-5 text-gray-400 hover:text-gray-500" aria-hidden="true" /> : <EyeSlashIcon className="h-5 w-5 text-gray-400 hover:text-gray-500" aria-hidden="true" />}
</div>
</div>
)}

View file

@ -1,7 +1,7 @@
import { Fragment } from "react";
import { Field, FieldProps } from "formik";
import { Transition, Listbox } from "@headlessui/react";
import { CheckIcon, SelectorIcon } from "@heroicons/react/solid";
import { Listbox, Transition } from "@headlessui/react";
import { CheckIcon, ChevronUpDownIcon } from "@heroicons/react/24/solid";
import { MultiSelect as RMSC } from "react-multi-select-component";
import { classNames, COL_WIDTHS } from "../../utils";
@ -162,7 +162,7 @@ export function DownloadClientSelect({
: "Choose a client"}
</span>
<span className="absolute inset-y-0 right-0 flex items-center pr-2 pointer-events-none">
<SelectorIcon
<ChevronUpDownIcon
className="h-5 w-5 text-gray-400 dark:text-gray-300"
aria-hidden="true" />
</span>
@ -274,7 +274,7 @@ export const Select = ({
}
</span>
<span className="absolute inset-y-0 right-0 flex items-center pr-2 pointer-events-none">
<SelectorIcon
<ChevronUpDownIcon
className="h-5 w-5 text-gray-400 dark:text-gray-300"
aria-hidden="true"
/>
@ -379,7 +379,7 @@ export const SelectWide = ({
}
</span>
<span className="absolute inset-y-0 right-0 flex items-center pr-2 pointer-events-none">
<SelectorIcon
<ChevronUpDownIcon
className="h-5 w-5 text-gray-400 dark:text-gray-300"
aria-hidden="true"
/>

View file

@ -1,17 +1,8 @@
import React, {
FC,
forwardRef,
ReactNode
} from "react";
import {
FieldError,
UseFormRegister,
Path,
RegisterOptions, DeepMap
} from "react-hook-form";
import React, { FC, forwardRef, ReactNode } from "react";
import { DeepMap, FieldError, Path, RegisterOptions, UseFormRegister } from "react-hook-form";
import { classNames, get } from "../../utils";
import { useToggle } from "../../hooks/hooks";
import { EyeIcon, EyeOffIcon } from "@heroicons/react/solid";
import { EyeIcon, EyeSlashIcon } from "@heroicons/react/24/solid";
import { ErrorMessage } from "@hookform/error-message";
export type FormErrorMessageProps = {
@ -183,7 +174,7 @@ export const PasswordInput = <TFormValues extends Record<string, unknown>>({
{...(register && register(name, rules))}
/>
<div className="absolute inset-y-0 right-0 px-3 flex items-center" onClick={toggleVisibility}>
{!isVisible ? <EyeIcon className="h-5 w-5 text-gray-400 hover:text-gray-500" aria-hidden="true" /> : <EyeOffIcon className="h-5 w-5 text-gray-400 hover:text-gray-500" aria-hidden="true" />}
{!isVisible ? <EyeIcon className="h-5 w-5 text-gray-400 hover:text-gray-500" aria-hidden="true" /> : <EyeSlashIcon className="h-5 w-5 text-gray-400 hover:text-gray-500" aria-hidden="true" />}
</div>
</div>
<ErrorMessage

View file

@ -1,6 +1,6 @@
import React, { Fragment, FC } from "react";
import React, { FC, Fragment } from "react";
import { Dialog, Transition } from "@headlessui/react";
import { ExclamationIcon } from "@heroicons/react/solid";
import { ExclamationTriangleIcon } from "@heroicons/react/24/solid";
interface DeleteModalProps {
isOpen: boolean;
@ -49,7 +49,7 @@ export const DeleteModal: FC<DeleteModalProps> = ({ isOpen, buttonRef, toggle, d
<div className="inline-block align-bottom rounded-lg text-left overflow-hidden shadow-xl transform transition-all sm:my-8 sm:align-middle sm:max-w-lg sm:w-full">
<div className="bg-white dark:bg-gray-800 px-4 pt-5 pb-4 sm:p-6 sm:pb-4">
<div className="sm:flex sm:items-start">
<ExclamationIcon className="h-16 w-16 text-red-500 dark:text-red-500" aria-hidden="true" />
<ExclamationTriangleIcon className="h-16 w-16 text-red-500 dark:text-red-500" aria-hidden="true" />
<div className="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left">
<Dialog.Title as="h3" className="text-lg leading-6 font-medium text-gray-900 dark:text-white">
{title}

View file

@ -1,5 +1,5 @@
import { FC } from "react";
import { XIcon, CheckCircleIcon, ExclamationIcon, ExclamationCircleIcon } from "@heroicons/react/solid";
import { CheckCircleIcon, ExclamationCircleIcon, ExclamationTriangleIcon, XMarkIcon } from "@heroicons/react/24/solid";
import { toast, Toast as Tooast } from "react-hot-toast";
import { classNames } from "../../utils";
@ -18,7 +18,7 @@ const Toast: FC<Props> = ({ type, body, t }) => (
<div className="flex-shrink-0">
{type === "success" && <CheckCircleIcon className="h-6 w-6 text-green-400" aria-hidden="true" />}
{type === "error" && <ExclamationCircleIcon className="h-6 w-6 text-red-400" aria-hidden="true" />}
{type === "warning" && <ExclamationIcon className="h-6 w-6 text-yellow-400" aria-hidden="true" />}
{type === "warning" && <ExclamationTriangleIcon className="h-6 w-6 text-yellow-400" aria-hidden="true" />}
</div>
<div className="ml-3 w-0 flex-1 pt-0.5">
<p className="text-sm font-medium text-gray-900 dark:text-gray-200">
@ -36,7 +36,7 @@ const Toast: FC<Props> = ({ type, body, t }) => (
}}
>
<span className="sr-only">Close</span>
<XIcon className="h-5 w-5" aria-hidden="true" />
<XMarkIcon className="h-5 w-5" aria-hidden="true" />
</button>
</div>
</div>

View file

@ -1,11 +1,11 @@
import React, { Fragment, useRef } from "react";
import { XIcon } from "@heroicons/react/solid";
import { Dialog, Transition } from "@headlessui/react";
import { Form, Formik } from "formik";
import React, {Fragment, useRef} from "react";
import {XMarkIcon} from "@heroicons/react/24/solid";
import {Dialog, Transition} from "@headlessui/react";
import {Form, Formik} from "formik";
import DEBUG from "../debug";
import { useToggle } from "../../hooks/hooks";
import { DeleteModal } from "../modals";
import { classNames } from "../../utils";
import {useToggle} from "../../hooks/hooks";
import {DeleteModal} from "../modals";
import {classNames} from "../../utils";
interface SlideOverProps<DataType> {
title: string;
@ -101,7 +101,7 @@ function SlideOver<DataType>({
onClick={toggle}
>
<span className="sr-only">Close panel</span>
<XIcon className="h-6 w-6" aria-hidden="true" />
<XMarkIcon className="h-6 w-6" aria-hidden="true" />
</button>
</div>
</div>

View file

@ -1,10 +1,10 @@
import { Fragment } from "react";
import { useMutation } from "react-query";
import { toast } from "react-hot-toast";
import { XIcon } from "@heroicons/react/solid";
import { XMarkIcon } from "@heroicons/react/24/solid";
import { Dialog, Transition } from "@headlessui/react";
import { Field, Form, Formik, FormikErrors, FormikValues } from "formik";
import type { FieldProps } from "formik";
import { Field, Form, Formik, FormikErrors, FormikValues } from "formik";
import { queryClient } from "../../App";
import { APIClient } from "../../api/APIClient";
@ -87,7 +87,7 @@ function FilterAddForm({ isOpen, toggle }: filterAddFormProps) {
onClick={toggle}
>
<span className="sr-only">Close panel</span>
<XIcon className="h-6 w-6" aria-hidden="true" />
<XMarkIcon className="h-6 w-6" aria-hidden="true" />
</button>
</div>
</div>

View file

@ -1,7 +1,7 @@
import { Fragment } from "react";
import { useMutation } from "react-query";
import { toast } from "react-hot-toast";
import { XIcon } from "@heroicons/react/solid";
import { XMarkIcon } from "@heroicons/react/24/solid";
import { Dialog, Transition } from "@headlessui/react";
import type { FieldProps } from "formik";
import { Field, Form, Formik, FormikErrors, FormikValues } from "formik";
@ -83,7 +83,7 @@ function APIKeyAddForm({ isOpen, toggle }: apiKeyAddFormProps) {
onClick={toggle}
>
<span className="sr-only">Close panel</span>
<XIcon className="h-6 w-6" aria-hidden="true"/>
<XMarkIcon className="h-6 w-6" aria-hidden="true"/>
</button>
</div>
</div>

View file

@ -1,7 +1,7 @@
import React, { Fragment, useRef, useState } from "react";
import { useMutation } from "react-query";
import { Dialog, Transition } from "@headlessui/react";
import { XIcon } from "@heroicons/react/solid";
import { XMarkIcon } from "@heroicons/react/24/solid";
import { classNames, sleep } from "../../utils";
import { Form, Formik, useFormikContext } from "formik";
import DEBUG from "../../components/debug";
@ -13,7 +13,13 @@ import { toast } from "react-hot-toast";
import Toast from "../../components/notifications/Toast";
import { useToggle } from "../../hooks/hooks";
import { DeleteModal } from "../../components/modals";
import { NumberFieldWide, PasswordFieldWide, SwitchGroupWide, TextFieldWide, RadioFieldsetWide } from "../../components/inputs";
import {
NumberFieldWide,
PasswordFieldWide,
RadioFieldsetWide,
SwitchGroupWide,
TextFieldWide
} from "../../components/inputs";
import DownloadClient from "../../screens/settings/DownloadClient";
interface InitialValuesSettings {
@ -511,7 +517,7 @@ export function DownloadClientAddForm({ isOpen, toggle }: formProps) {
onClick={toggle}
>
<span className="sr-only">Close panel</span>
<XIcon
<XMarkIcon
className="h-6 w-6"
aria-hidden="true"
/>
@ -710,7 +716,7 @@ export function DownloadClientUpdateForm({ client, isOpen, toggle }: updateFormP
onClick={toggle}
>
<span className="sr-only">Close panel</span>
<XIcon
<XMarkIcon
className="h-6 w-6"
aria-hidden="true"
/>

View file

@ -5,7 +5,7 @@ import Select, { components, ControlProps, InputProps, MenuProps, OptionProps }
import type { FieldProps } from "formik";
import { Field, Form, Formik, FormikValues } from "formik";
import { XIcon } from "@heroicons/react/solid";
import { XMarkIcon } from "@heroicons/react/24/solid";
import { Dialog, Transition } from "@headlessui/react";
import { sleep, slugify } from "../../utils";
@ -381,7 +381,7 @@ export function IndexerAddForm({ isOpen, toggle }: AddProps) {
onClick={toggle}
>
<span className="sr-only">Close panel</span>
<XIcon className="h-6 w-6" aria-hidden="true" />
<XMarkIcon className="h-6 w-6" aria-hidden="true" />
</button>
</div>
</div>

View file

@ -1,20 +1,16 @@
import { useMutation } from "react-query";
import { toast } from "react-hot-toast";
import { XIcon } from "@heroicons/react/solid";
import { Field, FieldArray, FormikErrors, FormikValues } from "formik";
import type { FieldProps } from "formik";
import {useMutation} from "react-query";
import {toast} from "react-hot-toast";
import {XMarkIcon} from "@heroicons/react/24/solid";
import type {FieldProps} from "formik";
import {Field, FieldArray, FormikErrors, FormikValues} from "formik";
import { queryClient } from "../../App";
import { APIClient } from "../../api/APIClient";
import {queryClient} from "../../App";
import {APIClient} from "../../api/APIClient";
import {
TextFieldWide,
PasswordFieldWide,
SwitchGroupWide,
NumberFieldWide
} from "../../components/inputs";
import { SlideOver } from "../../components/panels";
import {NumberFieldWide, PasswordFieldWide, SwitchGroupWide, TextFieldWide} from "../../components/inputs";
import {SlideOver} from "../../components/panels";
import Toast from "../../components/notifications/Toast";
import {ExclamationTriangleIcon} from "@heroicons/react/24/outline";
interface ChannelsFieldArrayProps {
channels: IrcChannel[];
@ -62,7 +58,7 @@ const ChannelsFieldArray = ({ channels }: ChannelsFieldArrayProps) => (
onClick={() => remove(index)}
>
<span className="sr-only">Remove</span>
<XIcon className="h-6 w-6" aria-hidden="true" />
<XMarkIcon className="h-6 w-6" aria-hidden="true" />
</button>
</div>
))
@ -160,6 +156,8 @@ export function IrcNetworkAddForm({ isOpen, toggle }: AddFormProps) {
>
{(values) => (
<div className="flex flex-col space-y-4 px-1 py-6 sm:py-0 sm:space-y-0">
<div className="flex justify-center dark:bg-red-300 text-sm font-bold text-center p-4 py-8 dark:text-red-800"><span className="flex"><ExclamationTriangleIcon className="mr-2 h-6 w-6" /> ADD NETWORKS VIA INDEXERS! ONLY USE THIS IS YOU DELETED NETWORKS</span></div>
<TextFieldWide
name="name"
label="Name"

View file

@ -1,19 +1,19 @@
import { Dialog, Transition } from "@headlessui/react";
import { Fragment } from "react";
import type { FieldProps } from "formik";
import { Field, Form, Formik, FormikErrors, FormikValues } from "formik";
import { XIcon } from "@heroicons/react/solid";
import Select, { components, ControlProps, InputProps, MenuProps, OptionProps } from "react-select";
import { PasswordFieldWide, SwitchGroupWide, TextFieldWide } from "../../components/inputs";
import {Dialog, Transition} from "@headlessui/react";
import {Fragment} from "react";
import type {FieldProps} from "formik";
import {Field, Form, Formik, FormikErrors, FormikValues} from "formik";
import {XMarkIcon} from "@heroicons/react/24/solid";
import Select, {components, ControlProps, InputProps, MenuProps, OptionProps} from "react-select";
import {PasswordFieldWide, SwitchGroupWide, TextFieldWide} from "../../components/inputs";
import DEBUG from "../../components/debug";
import { EventOptions, NotificationTypeOptions, SelectOption } from "../../domain/constants";
import { useMutation } from "react-query";
import { APIClient } from "../../api/APIClient";
import { queryClient } from "../../App";
import { toast } from "react-hot-toast";
import {EventOptions, NotificationTypeOptions, SelectOption} from "../../domain/constants";
import {useMutation} from "react-query";
import {APIClient} from "../../api/APIClient";
import {queryClient} from "../../App";
import {toast} from "react-hot-toast";
import Toast from "../../components/notifications/Toast";
import { SlideOver } from "../../components/panels";
import { componentMapType } from "./DownloadClientForms";
import {SlideOver} from "../../components/panels";
import {componentMapType} from "./DownloadClientForms";
const Input = (props: InputProps) => {
return (
@ -230,7 +230,7 @@ export function NotificationAddForm({ isOpen, toggle }: AddProps) {
onClick={toggle}
>
<span className="sr-only">Close panel</span>
<XIcon className="h-6 w-6" aria-hidden="true" />
<XMarkIcon className="h-6 w-6" aria-hidden="true" />
</button>
</div>
</div>

View file

@ -1,8 +1,8 @@
import { Fragment } from "react";
import { Link, NavLink, Outlet } from "react-router-dom";
import { Disclosure, Menu, Transition } from "@headlessui/react";
import { ExternalLinkIcon } from "@heroicons/react/solid";
import { ChevronDownIcon, MenuIcon, XIcon } from "@heroicons/react/outline";
import { ArrowTopRightOnSquareIcon } from "@heroicons/react/24/solid";
import { Bars3Icon, ChevronDownIcon, XMarkIcon } from "@heroicons/react/24/outline";
import { AuthContext } from "../utils/Context";
@ -76,7 +76,7 @@ export default function Base() {
)}
>
Docs
<ExternalLinkIcon className="inline ml-1 h-5 w-5"
<ArrowTopRightOnSquareIcon className="inline ml-1 h-5 w-5"
aria-hidden="true"/>
</a>
</div>
@ -157,9 +157,9 @@ export default function Base() {
className="bg-gray-200 dark:bg-gray-800 inline-flex items-center justify-center p-2 rounded-md text-gray-600 dark:text-gray-400 hover:text-white hover:bg-gray-700">
<span className="sr-only">Open main menu</span>
{open ? (
<XIcon className="block h-6 w-6" aria-hidden="true"/>
<XMarkIcon className="block h-6 w-6" aria-hidden="true"/>
) : (
<MenuIcon className="block h-6 w-6" aria-hidden="true"/>
<Bars3Icon className="block h-6 w-6" aria-hidden="true"/>
)}
</Disclosure.Button>
</div>

View file

@ -1,12 +1,12 @@
import { useEffect, useRef, useState } from "react";
import { ExclamationIcon } from "@heroicons/react/solid";
import {useEffect, useRef, useState} from "react";
import {ExclamationTriangleIcon} from "@heroicons/react/24/solid";
import format from "date-fns/format";
import { DebounceInput } from "react-debounce-input";
import {DebounceInput} from "react-debounce-input";
import { APIClient } from "../api/APIClient";
import { Checkbox } from "../components/Checkbox";
import { classNames } from "../utils";
import { SettingsContext } from "../utils/Context";
import {APIClient} from "../api/APIClient";
import {Checkbox} from "../components/Checkbox";
import {classNames} from "../utils";
import {SettingsContext} from "../utils/Context";
type LogEvent = {
time: string;
@ -79,7 +79,7 @@ export const Logs = () => {
<div className="max-w-screen-xl mx-auto px-4 sm:px-6 lg:px-8">
<h1 className="text-3xl font-bold text-black dark:text-white">Logs</h1>
<div className="flex justify-center">
<ExclamationIcon
<ExclamationTriangleIcon
className="h-5 w-5 text-yellow-400"
aria-hidden="true"
/>

View file

@ -1,15 +1,15 @@
import { NavLink, Outlet, useLocation } from "react-router-dom";
import {NavLink, Outlet, useLocation} from "react-router-dom";
import {
BellIcon,
ChatAlt2Icon,
ChatBubbleLeftRightIcon,
CogIcon,
CollectionIcon,
DownloadIcon,
FolderArrowDownIcon,
KeyIcon,
RectangleStackIcon,
RssIcon
} from "@heroicons/react/outline";
} from "@heroicons/react/24/outline";
import { classNames } from "../utils";
import {classNames} from "../utils";
interface NavTabType {
name: string;
@ -20,12 +20,12 @@ interface NavTabType {
const subNavigation: NavTabType[] = [
{ name: "Application", href: "", icon: CogIcon },
{ name: "Indexers", href: "indexers", icon: KeyIcon },
{ name: "IRC", href: "irc", icon: ChatAlt2Icon },
{ name: "IRC", href: "irc", icon: ChatBubbleLeftRightIcon },
{ name: "Feeds", href: "feeds", icon: RssIcon },
{ name: "Clients", href: "clients", icon: DownloadIcon },
{ name: "Clients", href: "clients", icon: FolderArrowDownIcon },
{ name: "Notifications", href: "notifications", icon: BellIcon },
{ name: "API keys", href: "api-keys", icon: KeyIcon },
{ name: "Releases", href: "releases", icon: CollectionIcon }
{ name: "Releases", href: "releases", icon: RectangleStackIcon }
// {name: 'Regex Playground', href: 'regex-playground', icon: CogIcon, current: false}
// {name: 'Rules', href: 'rules', icon: ClipboardCheckIcon, current: false},
];

View file

@ -1,17 +1,17 @@
import { AlertWarning } from "../../components/alerts";
import { DownloadClientSelect, NumberField, Select, SwitchGroup, TextField } from "../../components/inputs";
import { ActionContentLayoutOptions, ActionTypeNameMap, ActionTypeOptions } from "../../domain/constants";
import React, { Fragment, useRef } from "react";
import { useQuery } from "react-query";
import { APIClient } from "../../api/APIClient";
import { Field, FieldArray, FieldProps, FormikValues } from "formik";
import { EmptyListState } from "../../components/emptystates";
import { useToggle } from "../../hooks/hooks";
import { classNames } from "../../utils";
import { Dialog, Switch as SwitchBasic, Transition } from "@headlessui/react";
import { ChevronRightIcon } from "@heroicons/react/solid";
import { DeleteModal } from "../../components/modals";
import { CollapsableSection } from "./details";
import {AlertWarning} from "../../components/alerts";
import {DownloadClientSelect, NumberField, Select, SwitchGroup, TextField} from "../../components/inputs";
import {ActionContentLayoutOptions, ActionTypeNameMap, ActionTypeOptions} from "../../domain/constants";
import React, {Fragment, useRef} from "react";
import {useQuery} from "react-query";
import {APIClient} from "../../api/APIClient";
import {Field, FieldArray, FieldProps, FormikValues} from "formik";
import {EmptyListState} from "../../components/emptystates";
import {useToggle} from "../../hooks/hooks";
import {classNames} from "../../utils";
import {Dialog, Switch as SwitchBasic, Transition} from "@headlessui/react";
import {ChevronRightIcon} from "@heroicons/react/24/solid";
import {DeleteModal} from "../../components/modals";
import {CollapsableSection} from "./details";
interface FilterActionsProps {
filter: Filter;

View file

@ -1,9 +1,9 @@
import React, { useRef } from "react";
import { useMutation, useQuery } from "react-query";
import { NavLink, Route, Routes, useLocation, useNavigate, useParams } from "react-router-dom";
import { toast } from "react-hot-toast";
import { Form, Formik, FormikValues, useFormikContext } from "formik";
import { ChevronDownIcon, ChevronRightIcon } from "@heroicons/react/solid";
import React, {useRef} from "react";
import {useMutation, useQuery} from "react-query";
import {NavLink, Route, Routes, useLocation, useNavigate, useParams} from "react-router-dom";
import {toast} from "react-hot-toast";
import {Form, Formik, FormikValues, useFormikContext} from "formik";
import {ChevronDownIcon, ChevronRightIcon} from "@heroicons/react/24/solid";
import {
CODECS_OPTIONS,
@ -19,10 +19,10 @@ import {
SOURCES_MUSIC_OPTIONS,
SOURCES_OPTIONS
} from "../../domain/constants";
import { queryClient } from "../../App";
import { APIClient } from "../../api/APIClient";
import { useToggle } from "../../hooks/hooks";
import { classNames } from "../../utils";
import {queryClient} from "../../App";
import {APIClient} from "../../api/APIClient";
import {useToggle} from "../../hooks/hooks";
import {classNames} from "../../utils";
import {
CheckboxField,
@ -35,10 +35,10 @@ import {
} from "../../components/inputs";
import DEBUG from "../../components/debug";
import Toast from "../../components/notifications/Toast";
import { DeleteModal } from "../../components/modals";
import { TitleSubtitle } from "../../components/headings";
import { TextArea } from "../../components/inputs/input";
import { FilterActions } from "./action";
import {DeleteModal} from "../../components/modals";
import {TitleSubtitle} from "../../components/headings";
import {TextArea} from "../../components/inputs/input";
import {FilterActions} from "./action";
interface tabType {
name: string;

View file

@ -1,26 +1,26 @@
import { Dispatch, FC, Fragment, MouseEventHandler, useReducer, useRef, useState } from "react";
import { Link } from "react-router-dom";
import { toast } from "react-hot-toast";
import { Listbox, Menu, Switch, Transition } from "@headlessui/react";
import { useMutation, useQuery, useQueryClient } from "react-query";
import {Dispatch, FC, Fragment, MouseEventHandler, useReducer, useRef, useState} from "react";
import {Link} from "react-router-dom";
import {toast} from "react-hot-toast";
import {Listbox, Menu, Switch, Transition} from "@headlessui/react";
import {useMutation, useQuery, useQueryClient} from "react-query";
import {
ArrowsRightLeftIcon,
CheckIcon,
ChevronDownIcon,
DotsHorizontalIcon,
DuplicateIcon,
PencilAltIcon,
SwitchHorizontalIcon,
DocumentDuplicateIcon,
EllipsisHorizontalIcon,
PencilSquareIcon,
TrashIcon
} from "@heroicons/react/outline";
} from "@heroicons/react/24/outline";
import { queryClient } from "../../App";
import { classNames } from "../../utils";
import { FilterAddForm } from "../../forms";
import { useToggle } from "../../hooks/hooks";
import { APIClient } from "../../api/APIClient";
import {queryClient} from "../../App";
import {classNames} from "../../utils";
import {FilterAddForm} from "../../forms";
import {useToggle} from "../../hooks/hooks";
import {APIClient} from "../../api/APIClient";
import Toast from "../../components/notifications/Toast";
import { EmptyListState } from "../../components/emptystates";
import { DeleteModal } from "../../components/modals";
import {EmptyListState} from "../../components/emptystates";
import {DeleteModal} from "../../components/modals";
type FilterListState = {
indexerFilter: string[],
@ -250,7 +250,7 @@ const FilterItemDropdown = ({
text="Are you sure you want to remove this filter? This action cannot be undone."
/>
<Menu.Button className="px-4 py-2">
<DotsHorizontalIcon
<EllipsisHorizontalIcon
className="w-5 h-5 text-gray-700 hover:text-gray-900 dark:text-gray-100 dark:hover:text-gray-400"
aria-hidden="true"
/>
@ -277,7 +277,7 @@ const FilterItemDropdown = ({
"font-medium group flex rounded-md items-center w-full px-2 py-2 text-sm"
)}
>
<PencilAltIcon
<PencilSquareIcon
className={classNames(
active ? "text-white" : "text-blue-500",
"w-5 h-5 mr-2"
@ -297,7 +297,7 @@ const FilterItemDropdown = ({
)}
onClick={() => onToggle(!filter.enabled)}
>
<SwitchHorizontalIcon
<ArrowsRightLeftIcon
className={classNames(
active ? "text-white" : "text-blue-500",
"w-5 h-5 mr-2"
@ -317,7 +317,7 @@ const FilterItemDropdown = ({
)}
onClick={() => duplicateMutation.mutate(filter.id)}
>
<DuplicateIcon
<DocumentDuplicateIcon
className={classNames(
active ? "text-white" : "text-blue-500",
"w-5 h-5 mr-2"

View file

@ -1,16 +1,13 @@
import * as React from "react";
import { useQuery } from "react-query";
import { Listbox, Transition } from "@headlessui/react";
import {
CheckIcon,
ChevronDownIcon
} from "@heroicons/react/solid";
import {useQuery} from "react-query";
import {Listbox, Transition} from "@headlessui/react";
import {CheckIcon, ChevronDownIcon} from "@heroicons/react/24/solid";
import { APIClient } from "../../api/APIClient";
import { classNames } from "../../utils";
import { PushStatusOptions } from "../../domain/constants";
import { FilterProps } from "react-table";
import { DebounceInput } from "react-debounce-input";
import {APIClient} from "../../api/APIClient";
import {classNames} from "../../utils";
import {PushStatusOptions} from "../../domain/constants";
import {FilterProps} from "react-table";
import {DebounceInput} from "react-debounce-input";
interface ListboxFilterProps {
id: string;

View file

@ -1,29 +1,20 @@
import * as React from "react";
import { useQuery } from "react-query";
import {
useTable,
useSortBy,
usePagination,
useFilters,
Column
} from "react-table";
import {useQuery} from "react-query";
import {Column, useFilters, usePagination, useSortBy, useTable} from "react-table";
import {
ChevronDoubleLeftIcon,
ChevronDoubleRightIcon,
ChevronLeftIcon,
ChevronRightIcon,
ChevronDoubleRightIcon
} from "@heroicons/react/solid";
ChevronRightIcon
} from "@heroicons/react/24/solid";
import { APIClient } from "../../api/APIClient";
import { EmptyListState } from "../../components/emptystates";
import {APIClient} from "../../api/APIClient";
import {EmptyListState} from "../../components/emptystates";
import * as Icons from "../../components/Icons";
import * as DataTable from "../../components/data-table";
import {
IndexerSelectColumnFilter,
PushStatusSelectColumnFilter, SearchColumnFilter
} from "./Filters";
import {IndexerSelectColumnFilter, PushStatusSelectColumnFilter, SearchColumnFilter} from "./Filters";
type TableState = {
queryPageIndex: number;

View file

@ -1,16 +1,16 @@
import { queryClient } from "../../App";
import { useRef } from "react";
import { useMutation, useQuery } from "react-query";
import { KeyField } from "../../components/fields/text";
import { DeleteModal } from "../../components/modals";
import {queryClient} from "../../App";
import {useRef} from "react";
import {useMutation, useQuery} from "react-query";
import {KeyField} from "../../components/fields/text";
import {DeleteModal} from "../../components/modals";
import APIKeyAddForm from "../../forms/settings/APIKeyAddForm";
import Toast from "../../components/notifications/Toast";
import { APIClient } from "../../api/APIClient";
import { useToggle } from "../../hooks/hooks";
import { toast } from "react-hot-toast";
import { classNames } from "../../utils";
import { TrashIcon } from "@heroicons/react/outline";
import { EmptySimple } from "../../components/emptystates";
import {APIClient} from "../../api/APIClient";
import {useToggle} from "../../hooks/hooks";
import {toast} from "react-hot-toast";
import {classNames} from "../../utils";
import {TrashIcon} from "@heroicons/react/24/outline";
import {EmptySimple} from "../../components/emptystates";
function APISettings() {
const [addFormIsOpen, toggleAddForm] = useToggle(false);

View file

@ -1,23 +1,18 @@
import { useToggle } from "../../hooks/hooks";
import { useMutation, useQuery, useQueryClient } from "react-query";
import { APIClient } from "../../api/APIClient";
import { Menu, Switch, Transition } from "@headlessui/react";
import {useToggle} from "../../hooks/hooks";
import {useMutation, useQuery, useQueryClient} from "react-query";
import {APIClient} from "../../api/APIClient";
import {Menu, Switch, Transition} from "@headlessui/react";
import { classNames } from "../../utils";
import { Fragment, useRef, useState } from "react";
import { toast } from "react-hot-toast";
import {classNames} from "../../utils";
import {Fragment, useRef, useState} from "react";
import {toast} from "react-hot-toast";
import Toast from "../../components/notifications/Toast";
import { queryClient } from "../../App";
import { DeleteModal } from "../../components/modals";
import {
DotsHorizontalIcon,
PencilAltIcon,
SwitchHorizontalIcon,
TrashIcon
} from "@heroicons/react/outline";
import { FeedUpdateForm } from "../../forms/settings/FeedForms";
import { EmptySimple } from "../../components/emptystates";
import { ImplementationBadges } from "./Indexer";
import {queryClient} from "../../App";
import {DeleteModal} from "../../components/modals";
import {ArrowsRightLeftIcon, EllipsisHorizontalIcon, PencilSquareIcon, TrashIcon} from "@heroicons/react/24/outline";
import {FeedUpdateForm} from "../../forms/settings/FeedForms";
import {EmptySimple} from "../../components/emptystates";
import {ImplementationBadges} from "./Indexer";
function FeedSettings() {
const { data } = useQuery(
@ -183,7 +178,7 @@ const FeedItemDropdown = ({
text="Are you sure you want to remove this feed? This action cannot be undone."
/>
<Menu.Button className="px-4 py-2">
<DotsHorizontalIcon
<EllipsisHorizontalIcon
className="w-5 h-5 text-gray-700 hover:text-gray-900 dark:text-gray-100 dark:hover:text-gray-400"
aria-hidden="true"
/>
@ -210,7 +205,7 @@ const FeedItemDropdown = ({
)}
onClick={() => toggleUpdate()}
>
<PencilAltIcon
<PencilSquareIcon
className={classNames(
active ? "text-white" : "text-blue-500",
"w-5 h-5 mr-2"
@ -230,7 +225,7 @@ const FeedItemDropdown = ({
)}
onClick={() => onToggle(!feed.enabled)}
>
<SwitchHorizontalIcon
<ArrowsRightLeftIcon
className={classNames(
active ? "text-white" : "text-blue-500",
"w-5 h-5 mr-2"

View file

@ -1,14 +1,28 @@
import { useQuery } from "react-query";
import { useMutation, useQuery, useQueryClient } from "react-query";
import { simplifyDate, IsEmptyDate, classNames } from "../../utils";
import { classNames, IsEmptyDate, simplifyDate } 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 { LockClosedIcon, LockOpenIcon } from "@heroicons/react/solid";
import { LockClosedIcon, LockOpenIcon } from "@heroicons/react/24/solid";
import { Menu, Transition } from "@headlessui/react";
import { Fragment, useRef } from "react";
import { DeleteModal } from "../../components/modals";
import { toast } from "react-hot-toast";
import Toast from "../../components/notifications/Toast";
import {
ArrowsPointingInIcon,
ArrowsPointingOutIcon,
EllipsisHorizontalIcon,
ExclamationCircleIcon,
PencilSquareIcon,
TrashIcon
} from "@heroicons/react/24/outline";
export const IrcSettings = () => {
const [expandNetworks, toggleExpand] = useToggle(false);
const [addNetworkIsOpen, toggleAddNetwork] = useToggle(false);
const { data } = useQuery("networks", () => APIClient.irc.getNetworks(), {
@ -42,23 +56,63 @@ export const IrcSettings = () => {
</button>
</div>
</div>
<div className="flex justify-between flex-col sm:flex-row mt-10 px-1">
<ol className="flex flex-col sm:flex-row sm:gap-2 pb-4 sm:pb-0 sm:divide-x sm:divide-gray-200 sm:dark:divide-gray-700">
<li className="flex items-center">
<span
className="mr-2 flex h-4 w-4 relative"
title="Network healthy"
>
<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-4 w-4 bg-green-500" />
</span>
<span className="text-sm text-gray-800 dark:text-gray-500">Network healthy</span>
</li>
<li className="flex items-center sm:pl-2">
<span
className="mr-2 flex h-4 w-4 rounded-full opacity-75 bg-yellow-400 over:text-yellow-600"
title="Network unhealthy"
/>
<span className="text-sm text-gray-800 dark:text-gray-500">Network unhealthy</span>
</li>
<li className="flex items-center sm:pl-2">
<span
className="mr-2 flex h-4 w-4 rounded-full opacity-75 bg-gray-500"
title="Network disabled"
>
</span>
<span className="text-sm text-gray-800 dark:text-gray-500">Network disabled</span>
</li>
</ol>
<div className="flex gap-x-2">
<button className="flex items-center text-sm text-gray-800 dark:text-gray-400 p-1 px-2 rounded shadow bg-gray-200 dark:bg-gray-700 hover:bg-gray-300 dark:hover:bg-gray-600" onClick={toggleExpand} title={expandNetworks ? "collapse" : "expand"}>
{expandNetworks
? <span className="flex items-center">Collapse <ArrowsPointingInIcon className="ml-1 w-4 h-4"/></span>
: <span className="flex items-center">Expand <ArrowsPointingOutIcon className="ml-1 w-4 h-4"/></span>
}</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">
<ol className="min-w-full relative">
<li className="grid grid-cols-12 gap-4 border-b border-gray-200 dark:border-gray-700">
<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">
<div className="hidden sm:flex 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">
<div className="hidden sm:flex 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} />
<ListItem key={idx} idx={idx} expanded={expandNetworks} network={network} />
))}
</ol>
</section>
@ -78,15 +132,16 @@ export const IrcSettings = () => {
interface ListItemProps {
idx: number;
network: IrcNetworkWithHealth;
expanded: boolean;
}
const ListItem = ({ idx, network }: ListItemProps) => {
const ListItem = ({ idx, network, expanded }: ListItemProps) => {
const [updateIsOpen, toggleUpdate] = useToggle(false);
const [edit, toggleEdit] = useToggle(false);
return (
<li key={idx}>
<div className={classNames("grid grid-cols-12 gap-2 lg:gap-4 items-center py-4", network.enabled && !network.healthy ? "bg-red-50 dark:bg-red-900 hover:bg-red-100 dark:hover:bg-red-800" : "hover:bg-gray-50 dark:hover:bg-gray-700 ")}>
<div className={classNames("grid grid-cols-12 gap-2 lg:gap-4 items-center py-2", network.enabled && !network.healthy ? "bg-red-50 dark:bg-red-900 hover:bg-red-100 dark:hover:bg-red-800" : "hover:bg-gray-50 dark:hover:bg-gray-700 ")}>
<IrcNetworkUpdateForm
isOpen={updateIsOpen}
toggle={toggleUpdate}
@ -94,7 +149,7 @@ const ListItem = ({ idx, network }: ListItemProps) => {
/>
<div
className="col-span-3 items-center sm:px-6 text-sm font-medium text-gray-900 dark:text-white cursor-pointer"
className="col-span-10 xs:col-span-3 sm: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">
@ -126,7 +181,7 @@ const ListItem = ({ idx, network }: ListItemProps) => {
</div>
</div>
<div
className="col-span-5 sm:px-6 text-sm text-gray-500 dark:text-gray-400 cursor-pointer"
className="hidden sm:flex col-span-5 sm:px-6 text-sm text-gray-500 dark:text-gray-400 cursor-pointer"
onClick={toggleEdit}
>
<div
@ -155,7 +210,7 @@ const ListItem = ({ idx, network }: ListItemProps) => {
</div>
{network.nickserv && network.nickserv.account ? (
<div
className="col-span-3 items-center sm:px-6 text-sm text-gray-500 dark:text-gray-400 cursor-pointer"
className="hidden sm:flex 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">
@ -166,15 +221,10 @@ const ListItem = ({ idx, network }: ListItemProps) => {
<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}
>
Edit
</span>
<ListItemDropdown network={network} toggleUpdate={toggleUpdate} />
</div>
</div>
{edit && (
{(edit || expanded) && (
<div className="px-4 py-4 flex border-b border-x-0 dark:border-gray-600 dark:bg-gray-700">
<div className="min-w-full">
{network.channels.length > 0 ? (
@ -238,3 +288,168 @@ const ListItem = ({ idx, network }: ListItemProps) => {
</li>
);
};
interface ListItemDropdownProps {
network: IrcNetwork;
toggleUpdate: () => void;
}
const ListItemDropdown = ({
network,
toggleUpdate
}: ListItemDropdownProps) => {
const cancelModalButtonRef = useRef(null);
const queryClient = useQueryClient();
const [deleteModalIsOpen, toggleDeleteModal] = useToggle(false);
const deleteMutation = useMutation(
(id: number) => APIClient.irc.deleteNetwork(id),
{
onSuccess: () => {
queryClient.invalidateQueries(["networks"]);
queryClient.invalidateQueries(["networks", network.id]);
toast.custom((t) => <Toast type="success" body={`Network ${network.name} was deleted`} t={t}/>);
}
}
);
const restartMutation = useMutation(
(id: number) => APIClient.irc.restartNetwork(id),
{
onSuccess: () => {
toast.custom((t) => <Toast type="success"
body={`${network.name} was successfully restarted`}
t={t}/>);
queryClient.invalidateQueries(["networks"]);
queryClient.invalidateQueries(["networks", network.id]);
}
}
);
const restart = (id: number) => {
restartMutation.mutate(id);
};
return (
<Menu as="div">
<DeleteModal
isOpen={deleteModalIsOpen}
toggle={toggleDeleteModal}
buttonRef={cancelModalButtonRef}
deleteAction={() => {
deleteMutation.mutate(network.id);
toggleDeleteModal();
}}
title={`Remove network: ${network.name}`}
text="Are you sure you want to remove this network? This action cannot be undone."
/>
<Menu.Button className="px-4 py-2">
<EllipsisHorizontalIcon
className="w-5 h-5 text-gray-700 hover:text-gray-900 dark:text-gray-100 dark:hover:text-gray-400"
aria-hidden="true"
/>
</Menu.Button>
<Transition
as={Fragment}
enter="transition ease-out duration-100"
enterFrom="transform opacity-0 scale-95"
enterTo="transform opacity-100 scale-100"
leave="transition ease-in duration-75"
leaveFrom="transform opacity-100 scale-100"
leaveTo="transform opacity-0 scale-95"
>
<Menu.Items
className="absolute right-0 w-32 sm:w-56 mt-2 origin-top-right bg-white dark:bg-gray-800 divide-y divide-gray-200 dark:divide-gray-700 rounded-md shadow-lg ring-1 ring-black ring-opacity-10 focus:outline-none"
>
<div className="px-1 py-1">
<Menu.Item>
{({ active }) => (
<button
className={classNames(
active ? "bg-blue-600 text-white" : "text-gray-900 dark:text-gray-300",
"font-medium group flex rounded-md items-center w-full px-2 py-2 text-sm"
)}
onClick={() => toggleUpdate()}
>
<PencilSquareIcon
className={classNames(
active ? "text-white" : "text-blue-500",
"w-5 h-5 mr-2"
)}
aria-hidden="true"
/>
Edit
</button>
)}
</Menu.Item>
{/*<Menu.Item>*/}
{/* {({ active }) => (*/}
{/* <button*/}
{/* className={classNames(*/}
{/* active ? "bg-blue-600 text-white" : "text-gray-900 dark:text-gray-300",*/}
{/* "font-medium group flex rounded-md items-center w-full px-2 py-2 text-sm"*/}
{/* )}*/}
{/* onClick={() => onToggle(!network.enabled)}*/}
{/* >*/}
{/* <SwitchHorizontalIcon*/}
{/* className={classNames(*/}
{/* active ? "text-white" : "text-blue-500",*/}
{/* "w-5 h-5 mr-2"*/}
{/* )}*/}
{/* aria-hidden="true"*/}
{/* />*/}
{/* {network.enabled ? "Disable" : "Enable"}*/}
{/* </button>*/}
{/* )}*/}
{/*</Menu.Item>*/}
<Menu.Item>
{({ active }) => (
<button
className={classNames(
active ? "bg-blue-600 text-white" : "text-gray-900 dark:text-gray-300",
"font-medium group flex rounded-md items-center w-full px-2 py-2 text-sm"
)}
onClick={() => restart(network.id)}
>
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" strokeWidth={1.5} stroke="currentColor" className={classNames(
active ? "text-white" : "text-blue-500",
"w-5 h-5 mr-2"
)}>
<path strokeLinecap="round" strokeLinejoin="round" d="M5.636 5.636a9 9 0 1012.728 0M12 3v9" />
</svg>
Restart
</button>
)}
</Menu.Item>
</div>
<div className="px-1 py-1">
<Menu.Item>
{({ active }) => (
<button
className={classNames(
active ? "bg-red-600 text-white" : "text-gray-900 dark:text-gray-300",
"font-medium group flex rounded-md items-center w-full px-2 py-2 text-sm"
)}
onClick={() => toggleDeleteModal()}
>
<TrashIcon
className={classNames(
active ? "text-white" : "text-red-500",
"w-5 h-5 mr-2"
)}
aria-hidden="true"
/>
Delete
</button>
)}
</Menu.Item>
</div>
</Menu.Items>
</Transition>
</Menu>
);
};

View file

@ -2012,12 +2012,12 @@ __metadata:
languageName: node
linkType: hard
"@heroicons/react@npm:^1.0.6":
version: 1.0.6
resolution: "@heroicons/react@npm:1.0.6"
"@heroicons/react@npm:^2.0.11":
version: 2.0.11
resolution: "@heroicons/react@npm:2.0.11"
peerDependencies:
react: ">= 16"
checksum: 372b1eda3ce735ef069777bc96304f70de585ebb71a6d1cedc121bb695f9bca235619112e3ee14e8779e95a03096813cbbe3b755927a54b7580d1ce084fa4096
checksum: 9049e12bdb9bb1cc2ebc555877f538055ae01828bda781f0a7f3b2fe17aff8b6337d8562c5984bfe09ee77e95befd4e62854a58c9900c2e2650798b15ab807c3
languageName: node
linkType: hard
@ -12886,7 +12886,7 @@ __metadata:
dependencies:
"@fontsource/inter": ^4.5.11
"@headlessui/react": ^1.6.4
"@heroicons/react": ^1.0.6
"@heroicons/react": ^2.0.11
"@hookform/error-message": ^2.0.0
"@tailwindcss/forms": ^0.5.2
"@types/node": ^18.0.0