Refactor irc client (#19)

* refactor: update http handlers

* feat: add trace log level

* refactir: irc handler

* refactor(definitions): add irc settings and invite cmd:

* feat: add dft values to inputs

* refactor: indexer irc forms

* refactor(definitions): fix nickserv.password var:

* feat: pre fill indexer name field

* refactor: handle stopping and updates
This commit is contained in:
Ludvig Lundgren 2021-08-29 23:23:02 +02:00 committed by GitHub
parent 5f69ae9380
commit 4d40d41628
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
48 changed files with 1380 additions and 943 deletions

View file

@ -7,10 +7,11 @@ interface Props {
name: string;
label: string;
description?: string;
defaultValue?: boolean;
className?: string;
}
const SwitchGroup: React.FC<Props> = ({name, label, description}) => (
const SwitchGroup: React.FC<Props> = ({name, label, description, defaultValue}) => (
<ul className="mt-2 divide-y divide-gray-200">
<Switch.Group as="li" className="py-4 flex items-center justify-between">
<div className="flex flex-col">
@ -27,6 +28,7 @@ const SwitchGroup: React.FC<Props> = ({name, label, description}) => (
<Field
name={name}
defaultValue={defaultValue as any}
render={({input: {onChange, checked, value}}) => (
<Switch
value={value}

View file

@ -1,18 +1,21 @@
import {Field} from "react-final-form";
import { Field } from "react-final-form";
import React from "react";
import Error from "./Error";
import {classNames} from "../../styles/utils";
import { classNames } from "../../styles/utils";
interface Props {
name: string;
label?: string;
help?: string;
placeholder?: string;
defaultValue?: string;
className?: string;
required?: boolean;
hidden?: boolean;
}
const TextFieldWide: React.FC<Props> = ({name, label, placeholder, required, className}) => (
<div
const TextFieldWide: React.FC<Props> = ({ name, label, help, placeholder, defaultValue, required, hidden, className}) => (
<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>
@ -23,17 +26,22 @@ const TextFieldWide: React.FC<Props> = ({name, label, placeholder, required, cla
<div className="sm:col-span-2">
<Field
name={name}
render={({input, meta}) => (
defaultValue={defaultValue}
render={({ input, meta }) => (
<input
{...input}
id={name}
type="text"
className={classNames(meta.touched && meta.error ? "focus:ring-red-500 focus:border-red-500 border-red-500" : "focus:ring-indigo-500 focus:border-indigo-500 border-gray-300", "block w-full shadow-sm sm:text-sm rounded-md")}
placeholder={placeholder}
hidden={hidden}
/>
)}
/>
<Error name={name} classNames="block text-red-500 mt-2"/>
{help && (
<p className="mt-2 text-sm text-gray-500" id="email-description">{help}</p>
)}
<Error name={name} classNames="block text-red-500 mt-2" />
</div>
</div>
)

View file

@ -7,15 +7,19 @@ interface Props {
name: string;
label?: string;
placeholder?: string;
defaultValue?: number;
className?: string;
required?: boolean;
hidden?: boolean;
}
const NumberFieldWide: React.FC<Props> = ({
name,
label,
placeholder,
defaultValue,
required,
hidden,
className,
}) => (
<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">
@ -30,6 +34,7 @@ const NumberFieldWide: React.FC<Props> = ({
<div className="sm:col-span-2">
<Field
name={name}
defaultValue={defaultValue}
parse={(v) => v & parseInt(v, 10)}
render={({ input, meta }) => (
<input

View file

@ -0,0 +1,56 @@
import { Field } from "react-final-form";
import Error from "../Error";
import { classNames } from "../../../styles/utils";
import { useToggle } from "../../../hooks/hooks";
import { EyeIcon, EyeOffIcon } from "@heroicons/react/solid";
interface Props {
name: string;
label?: string;
placeholder?: string;
defaultValue?: string;
help?: string;
required?: boolean;
}
function PasswordField({ name, label, placeholder, defaultValue, help, required }: Props) {
const [isVisible, toggleVisibility] = useToggle(false)
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>
<label htmlFor={name} className="block text-sm font-medium text-gray-900 sm:mt-px sm:pt-2">
{label} {required && <span className="text-gray-500">*</span>}
</label>
</div>
<div className="sm:col-span-2">
<Field
name={name}
defaultValue={defaultValue}
render={({ input, meta }) => (
<div className="relative">
<input
{...input}
id={name}
type={isVisible ? "text" : "password"}
className={classNames(meta.touched && meta.error ? "focus:ring-red-500 focus:border-red-500 border-red-500" : "focus:ring-indigo-500 focus:border-indigo-500 border-gray-300", "block w-full shadow-sm sm:text-sm rounded-md")}
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" />}
</div>
</div>
)}
/>
{help && (
<p className="mt-2 text-sm text-gray-500" id="email-description">{help}</p>
)}
<Error name={name} classNames="block text-red-500 mt-2" />
</div>
</div>
)
}
export default PasswordField;

View file

@ -1,3 +1,4 @@
export { default as NumberFieldWide } from "./NumberField";
export { default as PasswordFieldWide } from "./PasswordField";
export { default as RadioFieldsetWide } from "./RadioFieldsetWide";
export { default as SelectFieldWide } from "./SelectField";

View file

@ -31,6 +31,43 @@ export interface Indexer {
settings: object | any;
}
export interface IndexerSchema {
// id: number;
name: string;
identifier: string;
description: string;
language: string;
privacy: string;
protocol: string;
urls: string[];
settings: IndexerSchemaSettings[];
irc: IndexerSchemaIRC;
}
export interface IndexerSchemaSettings {
name: string;
type: string;
required: boolean;
label: string;
help: string;
description: string;
default: string;
}
export interface IndexerSchemaIRC {
network: string;
server: string;
port: number;
tls: boolean;
nickserv: boolean;
announcers: string[];
channels: string[];
invite: string[];
invite_command: string;
settings: IndexerSchemaSettings[];
}
export interface Filter {
id: number;
name: string;
@ -87,16 +124,29 @@ export interface DownloadClient {
settings: object;
}
export interface NickServ {
account: string;
password: string;
}
export interface Network {
id: number;
id?: number;
name: string;
enabled: boolean;
addr: string;
nick: string;
username: string;
realname: string;
pass: string;
sasl: SASL;
server: string;
port: number;
tls: boolean;
invite_command: string;
nickserv: {
account: string;
password: string;
}
channels: Channel[];
settings: object;
}
export interface Channel {
name: string;
}
export interface SASL {

View file

@ -1,23 +1,24 @@
import React, {Fragment} from "react";
import {useMutation, useQuery} from "react-query";
import {Indexer} from "../../domain/interfaces";
import {sleep} from "../../utils/utils";
import {XIcon} from "@heroicons/react/solid";
import {Dialog, Transition} from "@headlessui/react";
import {Field, Form} from "react-final-form";
import React, { Fragment } from "react";
import { useMutation, useQuery } from "react-query";
import { Channel, Indexer, IndexerSchema, IndexerSchemaSettings, Network } from "../../domain/interfaces";
import { sleep } from "../../utils/utils";
import { XIcon } from "@heroicons/react/solid";
import { Dialog, Transition } from "@headlessui/react";
import { Field, Form } from "react-final-form";
import DEBUG from "../../components/debug";
import Select from "react-select";
import {queryClient} from "../../App";
import { SwitchGroup } from "../../components/inputs";
import { queryClient } from "../../App";
import { SwitchGroup, TextFieldWide } from "../../components/inputs";
import APIClient from "../../api/APIClient";
import { NumberFieldWide, PasswordFieldWide } from "../../components/inputs/wide";
interface props {
isOpen: boolean;
toggle: any;
}
function IndexerAddForm({isOpen, toggle}: props) {
const {data} = useQuery<any[], Error>('indexerSchema', APIClient.indexers.getSchema,
function IndexerAddForm({ isOpen, toggle }: props) {
const { data } = useQuery<IndexerSchema[], Error>('indexerSchema', APIClient.indexers.getSchema,
{
enabled: isOpen,
refetchOnWindowFocus: false
@ -33,59 +34,122 @@ function IndexerAddForm({isOpen, toggle}: props) {
}
})
const onSubmit = (data: any) => {
mutation.mutate(data)
const ircMutation = useMutation((network: Network) => APIClient.irc.createNetwork(network), {
onSuccess: (data) => {
console.log("irc mutation: ", data);
// queryClient.invalidateQueries(['indexer']);
// sleep(1500)
// toggle()
}
})
const onSubmit = (formData: any) => {
let ind = data && data.find(i => i.identifier === formData.identifier)
if (!ind) {
return
}
let channels: Channel[] = []
if (ind.irc.channels.length) {
ind.irc.channels.forEach(element => {
channels.push({ name: element })
});
}
const network: Network = {
name: ind.name,
enabled: false,
server: formData.irc.server,
port: formData.irc.port,
tls: formData.irc.tls,
nickserv: formData.irc.nickserv,
invite_command: formData.irc.invite_command,
settings: formData.irc.settings,
channels: channels,
}
console.log("network: ", network);
mutation.mutate(formData, {
onSuccess: (data) => {
// create irc
ircMutation.mutate(network)
}
})
};
const renderSettingFields = (indexer: string) => {
if (indexer !== "") {
// let ind = data.find(i => i.implementation_name === indexer)
let ind = data && data.find(i => i.identifier === indexer)
return (
<div key="opt">
{ind && ind.settings && ind.settings.map((f: any, idx: number) => {
switch (f.type) {
case "text":
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" key={idx}>
<div>
<label
htmlFor={f.name}
className="block text-sm font-medium text-gray-900 sm:mt-px sm:pt-2"
>
{f.label}
</label>
</div>
<div className="sm:col-span-2">
<Field name={"settings."+f.name}>
{({input, meta}) => (
<div className="sm:col-span-2">
<input
type="text"
{...input}
className="block w-full shadow-sm sm:text-sm focus:ring-indigo-500 focus:border-indigo-500 border-gray-300 rounded-md"
/>
{meta.touched && meta.error &&
<span>{meta.error}</span>}
</div>
)}
</Field>
</div>
</div>
)
}
})}
{ind && ind.settings && ind.settings.map((f: any, idx: number) => {
switch (f.type) {
case "text":
return (
<TextFieldWide name={`settings.${f.name}`} label={f.label} key={idx} help={f.help} defaultValue=""/>
)
case "secret":
return (
<PasswordFieldWide name={`settings.${f.name}`} label={f.label} key={idx} help={f.help} defaultValue="" />
)
}
})}
<div hidden={true}>
<TextFieldWide name={`name`} label="Name" defaultValue={ind?.name} />
</div>
</div>
)
}
}
const renderIrcSettingFields = (indexer: string) => {
if (indexer !== "") {
let ind = data && data.find(i => i.identifier === indexer)
return (
<Fragment>
{ind && ind.irc && ind.irc.settings && (
<div className="border-t border-gray-200 py-5">
<div className="px-6 space-y-1">
<Dialog.Title className="text-lg font-medium text-gray-900">IRC</Dialog.Title>
<p className="text-sm text-gray-500">
Networks, channels and invite commands are configured automatically.
</p>
</div>
{ind.irc.settings.map((f: IndexerSchemaSettings, idx: number) => {
switch (f.type) {
case "text":
return <TextFieldWide name={`irc.${f.name}`} label={f.label} required={f.required} key={idx} help={f.help} />
case "secret":
return <PasswordFieldWide name={`irc.${f.name}`} label={f.label} required={f.required} key={idx} help={f.help} defaultValue={f.default} />
}
})}
<div hidden={true}>
<TextFieldWide name={`irc.server`} label="Server" defaultValue={ind.irc.server} />
<NumberFieldWide name={`irc.port`} label="Port" defaultValue={ind.irc.port} />
<SwitchGroup name="irc.tls" label="TLS" defaultValue={ind.irc.tls} />
</div>
</div>
)}
</Fragment>
)
}
}
return (
<Transition.Root show={isOpen} as={Fragment}>
<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
@ -100,18 +164,17 @@ function IndexerAddForm({isOpen, toggle}: props) {
<div className="w-screen max-w-2xl">
<Form
initialValues={{
name: "",
enabled: true,
identifier: "",
irc: {}
}}
onSubmit={onSubmit}
>
{({handleSubmit, values}) => {
{({ handleSubmit, values }) => {
return (
<form className="h-full flex flex-col bg-white shadow-xl overflow-y-scroll"
onSubmit={handleSubmit}>
onSubmit={handleSubmit}>
<div className="flex-1">
{/* Header */}
<div className="px-4 py-6 bg-gray-50 sm:px-6">
<div className="flex items-start justify-between space-x-3">
<div className="space-y-1">
@ -129,43 +192,14 @@ function IndexerAddForm({isOpen, toggle}: props) {
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>
{/* Divider container */}
<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">
<div>
<label
htmlFor="name"
className="block text-sm font-medium text-gray-900 sm:mt-px sm:pt-2"
>
Name
</label>
</div>
<Field name="name">
{({input, meta}) => (
<div className="sm:col-span-2">
<input
type="text"
{...input}
className="block w-full shadow-sm sm:text-sm focus:ring-indigo-500 focus:border-indigo-500 border-gray-300 rounded-md"
/>
{meta.touched && meta.error &&
<span>{meta.error}</span>}
</div>
)}
</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">
<SwitchGroup name="enabled" label="Enabled" />
</div>
<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">
@ -182,26 +216,32 @@ function IndexerAddForm({isOpen, toggle}: props) {
name="identifier"
parse={val => val && val.value}
format={val => data && data.find((o: any) => o.value === val)}
render={({input, meta}) => (
render={({ input, meta }) => (
<React.Fragment>
<Select {...input}
isClearable={true}
placeholder="Choose an indexer"
options={data && data.sort((a,b): any => a.name.localeCompare(b.name)).map(v => ({
label: v.name,
value: v.identifier
// value: v.implementation_name
}))}/>
{/*<Error name={input.name} classNames="text-red mt-2 block" />*/}
isClearable={true}
placeholder="Choose an indexer"
options={data && data.sort((a, b): any => a.name.localeCompare(b.name)).map(v => ({
label: v.name,
value: v.identifier
}))} />
</React.Fragment>
)}
/>
</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">
<SwitchGroup name="enabled" label="Enabled" />
</div>
{renderSettingFields(values.identifier)}
</div>
{renderIrcSettingFields(values.identifier)}
</div>
<div
@ -223,7 +263,7 @@ function IndexerAddForm({isOpen, toggle}: props) {
</div>
</div>
<DEBUG values={values}/>
<DEBUG values={values} />
</form>
)
}}

View file

@ -1,15 +1,16 @@
import {Fragment, useRef} from "react";
import {useMutation } from "react-query";
import {Indexer} from "../../domain/interfaces";
import {sleep} from "../../utils/utils";
import {ExclamationIcon, XIcon} from "@heroicons/react/solid";
import {Dialog, Transition} from "@headlessui/react";
import {Field, Form} from "react-final-form";
import { Fragment, useRef } from "react";
import { useMutation } from "react-query";
import { Indexer } from "../../domain/interfaces";
import { sleep } from "../../utils/utils";
import { ExclamationIcon, XIcon } from "@heroicons/react/solid";
import { Dialog, Transition } from "@headlessui/react";
import { Field, Form } from "react-final-form";
import DEBUG from "../../components/debug";
import { SwitchGroup } from "../../components/inputs";
import {useToggle} from "../../hooks/hooks";
import { SwitchGroup, TextFieldWide } from "../../components/inputs";
import { useToggle } from "../../hooks/hooks";
import APIClient from "../../api/APIClient";
import {queryClient} from "../../App";
import { queryClient } from "../../App";
import { PasswordFieldWide } from "../../components/inputs/wide";
interface props {
isOpen: boolean;
@ -17,7 +18,7 @@ interface props {
indexer: Indexer;
}
function IndexerUpdateForm({isOpen, toggle, indexer}: props) {
function IndexerUpdateForm({ isOpen, toggle, indexer }: props) {
const [deleteModalIsOpen, toggleDeleteModal] = useToggle(false)
const mutation = useMutation((indexer: Indexer) => APIClient.indexers.update(indexer), {
@ -55,31 +56,11 @@ function IndexerUpdateForm({isOpen, toggle, indexer}: props) {
switch (f.type) {
case "text":
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" key={idx}>
<div>
<label
htmlFor={f.name}
className="block text-sm font-medium text-gray-900 sm:mt-px sm:pt-2"
>
{f.label}
</label>
</div>
<div className="sm:col-span-2">
<Field name={"settings."+f.name}>
{({input, meta}) => (
<div className="sm:col-span-2">
<input
type="text"
{...input}
className="block w-full shadow-sm sm:text-sm focus:ring-indigo-500 focus:border-indigo-500 border-gray-300 rounded-md"
/>
{meta.touched && meta.error &&
<span>{meta.error}</span>}
</div>
)}
</Field>
</div>
</div>
<TextFieldWide name={`settings.${f.name}`} label={f.label} key={idx} help={f.help} />
)
case "secret":
return (
<PasswordFieldWide name={`settings.${f.name}`} label={f.label} key={idx} help={f.help} />
)
}
})}
@ -88,9 +69,6 @@ function IndexerUpdateForm({isOpen, toggle, indexer}: props) {
}
}
// const setss = indexer.settings.reduce((o: any, obj: any) => ({ ...o, [obj.name]: obj.value }), {})
// console.log("setts", setss)
return (
<Transition.Root show={isOpen} as={Fragment}>
<Dialog as="div" static className="fixed inset-0 overflow-hidden" open={isOpen} onClose={toggle}>
@ -119,8 +97,8 @@ function IndexerUpdateForm({isOpen, toggle, indexer}: props) {
{/* This element is to trick the browser into centering the modal contents. */}
<span className="hidden sm:inline-block sm:align-middle sm:h-screen" aria-hidden="true">
&#8203;
</span>
&#8203;
</span>
<Transition.Child
as={Fragment}
enter="ease-out duration-300"
@ -172,7 +150,7 @@ function IndexerUpdateForm({isOpen, toggle, indexer}: props) {
</Dialog>
</Transition.Root>
<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
@ -195,12 +173,11 @@ function IndexerUpdateForm({isOpen, toggle, indexer}: props) {
}}
onSubmit={onSubmit}
>
{({handleSubmit, values}) => {
{({ handleSubmit, values }) => {
return (
<form className="h-full flex flex-col bg-white shadow-xl overflow-y-scroll"
onSubmit={handleSubmit}>
onSubmit={handleSubmit}>
<div className="flex-1">
{/* Header */}
<div className="px-4 py-6 bg-gray-50 sm:px-6">
<div className="flex items-start justify-between space-x-3">
<div className="space-y-1">
@ -218,13 +195,12 @@ function IndexerUpdateForm({isOpen, toggle, indexer}: props) {
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>
{/* Divider container */}
<div
className="py-6 space-y-6 sm:py-0 sm:space-y-0 sm:divide-y sm:divide-gray-200">
<div
@ -238,7 +214,7 @@ function IndexerUpdateForm({isOpen, toggle, indexer}: props) {
</label>
</div>
<Field name="name">
{({input, meta}) => (
{({ input, meta }) => (
<div className="sm:col-span-2">
<input
type="text"
@ -246,7 +222,7 @@ function IndexerUpdateForm({isOpen, toggle, indexer}: props) {
className="block w-full shadow-sm sm:text-sm focus:ring-indigo-500 focus:border-indigo-500 border-gray-300 rounded-md"
/>
{meta.touched && meta.error &&
<span>{meta.error}</span>}
<span>{meta.error}</span>}
</div>
)}
</Field>
@ -289,7 +265,7 @@ function IndexerUpdateForm({isOpen, toggle, indexer}: props) {
</div>
</div>
<DEBUG values={values}/>
<DEBUG values={values} />
</form>
)
}}

View file

@ -5,26 +5,14 @@ import {Dialog, Transition} from "@headlessui/react";
import {XIcon} from "@heroicons/react/solid";
import {Field, Form} from "react-final-form";
import DEBUG from "../../components/debug";
import {SwitchGroup, TextAreaWide, TextFieldWide} from "../../components/inputs";
import {SwitchGroup, TextFieldWide} from "../../components/inputs";
import {queryClient} from "../../App";
import arrayMutators from "final-form-arrays";
import { FieldArray } from "react-final-form-arrays";
import {classNames} from "../../styles/utils";
import APIClient from "../../api/APIClient";
// interface radioFieldsetOption {
// label: string;
// description: string;
// value: string;
// }
// const saslTypeOptions: radioFieldsetOption[] = [
// {label: "None", description: "None", value: ""},
// {label: "Plain", description: "SASL plain", value: "PLAIN"},
// {label: "NickServ", description: "/NS identify", value: "NICKSERV"},
// ];
import { NumberFieldWide, PasswordFieldWide } from "../../components/inputs/wide";
function IrcNetworkAddForm({isOpen, toggle}: any) {
const mutation = useMutation((network: Network) => APIClient.irc.createNetwork(network), {
@ -53,12 +41,8 @@ function IrcNetworkAddForm({isOpen, toggle}: any) {
errors.name = "Required";
}
if (!values.addr) {
errors.addr = "Required";
}
if (!values.nick) {
errors.nick = "Required";
if (!values.server) {
errors.server = "Required";
}
return errors;
@ -86,18 +70,9 @@ function IrcNetworkAddForm({isOpen, toggle}: any) {
initialValues={{
name: "",
enabled: true,
addr: "",
server: "",
tls: false,
nick: "",
pass: "",
// connect_commands: "",
// sasl: {
// mechanism: "",
// plain: {
// username: "",
// password: "",
// }
// },
}}
mutators={{
...arrayMutators
@ -110,7 +85,6 @@ function IrcNetworkAddForm({isOpen, toggle}: any) {
<form className="h-full flex flex-col bg-white shadow-xl overflow-y-scroll"
onSubmit={handleSubmit}>
<div className="flex-1">
{/* Header */}
<div className="px-4 py-6 bg-gray-50 sm:px-6">
<div className="flex items-start justify-between space-x-3">
<div className="space-y-1">
@ -144,88 +118,19 @@ function IrcNetworkAddForm({isOpen, toggle}: any) {
</div>
<div>
<TextFieldWide name="addr" label="Address" placeholder="Address:port eg irc.server.net:6697" required={true} />
<TextFieldWide name="server" label="Server" placeholder="Address: Eg irc.server.net" required={true} />
<NumberFieldWide name="port" label="Port" 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">
<SwitchGroup name="tls" label="TLS"/>
</div>
<TextFieldWide name="nick" label="Nick" placeholder="Nick" required={true} />
<PasswordFieldWide name="pass" label="Password" help="Network password" />
<TextFieldWide name="password" label="Password" placeholder="Network password" />
<TextFieldWide name="nickserv.account" label="NickServ Account" required={true} />
<PasswordFieldWide name="nickserv.password" label="NickServ Password" />
<TextAreaWide name="connect_commands" label="Connect commands" placeholder="/msg test this" />
{/* <Field*/}
{/* name="sasl.mechanism"*/}
{/* type="select"*/}
{/* render={({input}) => (*/}
{/* <Listbox value={input.value} onChange={input.onChange}>*/}
{/* {({open}) => (*/}
{/* <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>*/}
{/* <Listbox.Label className="block text-sm font-medium text-gray-900 sm:mt-px sm:pt-2">SASL / auth</Listbox.Label>*/}
{/* </div>*/}
{/* <div className="sm:col-span-2 relative">*/}
{/* <Listbox.Button*/}
{/* className="bg-white relative w-full border border-gray-300 rounded-md shadow-sm pl-3 pr-10 py-2 text-left cursor-default focus:outline-none focus:ring-1 focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm">*/}
{/* <span className="block truncate">{input.value ? saslTypeOptions.find(c => c.value === input.value)!.label : "Choose auth method"}</span>*/}
{/* <span className="absolute inset-y-0 right-0 flex items-center pr-2 pointer-events-none">*/}
{/* <SelectorIcon className="h-5 w-5 text-gray-400" aria-hidden="true"/>*/}
{/*</span>*/}
{/* </Listbox.Button>*/}
{/* <Transition*/}
{/* show={open}*/}
{/* as={Fragment}*/}
{/* leave="transition ease-in duration-100"*/}
{/* leaveFrom="opacity-100"*/}
{/* leaveTo="opacity-0"*/}
{/* >*/}
{/* <Listbox.Options*/}
{/* static*/}
{/* className="absolute z-10 mt-1 w-full bg-white shadow-lg max-h-60 rounded-md py-1 text-base ring-1 ring-black ring-opacity-5 overflow-auto focus:outline-none sm:text-sm"*/}
{/* >*/}
{/* {saslTypeOptions.map((opt: any) => (*/}
{/* <Listbox.Option*/}
{/* key={opt.value}*/}
{/* className={({active}) =>*/}
{/* classNames(*/}
{/* active ? 'text-white bg-indigo-600' : 'text-gray-900',*/}
{/* 'cursor-default select-none relative py-2 pl-3 pr-9'*/}
{/* )*/}
{/* }*/}
{/* value={opt.value}*/}
{/* >*/}
{/* {({selected, active}) => (*/}
{/* <>*/}
{/* <span className={classNames(selected ? 'font-semibold' : 'font-normal', 'block truncate')}>*/}
{/* {opt.label}*/}
{/* </span>*/}
{/* {selected ? (*/}
{/* <span*/}
{/* className={classNames(*/}
{/* active ? 'text-white' : 'text-indigo-600',*/}
{/* 'absolute inset-y-0 right-0 flex items-center pr-4'*/}
{/* )}*/}
{/* >*/}
{/* <CheckIcon className="h-5 w-5" aria-hidden="true"/>*/}
{/* </span>*/}
{/* ) : null}*/}
{/* </>*/}
{/* )}*/}
{/* </Listbox.Option>*/}
{/* ))}*/}
{/* </Listbox.Options>*/}
{/* </Transition>*/}
{/* </div>*/}
{/* </div>*/}
{/* )}*/}
{/* </Listbox>*/}
{/* )} />*/}
<PasswordFieldWide name="invite_command" label="Invite command" />
</div>
</div>
@ -295,7 +200,6 @@ function IrcNetworkAddForm({isOpen, toggle}: any) {
<button
type="submit"
disabled={pristine || invalid}
// className="inline-flex justify-center py-2 px-4 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"
className={classNames(pristine || invalid ? "bg-indigo-300" : "bg-indigo-600 hover:bg-indigo-700","inline-flex justify-center py-2 px-4 border border-transparent shadow-sm text-sm font-medium rounded-md text-white focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500")}
>
Create

View file

@ -1,34 +1,22 @@
import {Fragment, useEffect, useRef} from "react";
import {useMutation} from "react-query";
import {Network} from "../../domain/interfaces";
import {Dialog, Transition} from "@headlessui/react";
import {XIcon} from "@heroicons/react/solid";
import {Field, Form} from "react-final-form";
import { Fragment, useEffect, useRef } from "react";
import { useMutation } from "react-query";
import { Network } from "../../domain/interfaces";
import { Dialog, Transition } from "@headlessui/react";
import { XIcon } from "@heroicons/react/solid";
import { Field, Form } from "react-final-form";
import DEBUG from "../../components/debug";
import {SwitchGroup, TextAreaWide, TextFieldWide} from "../../components/inputs";
import {queryClient} from "../../App";
import { SwitchGroup, TextFieldWide } from "../../components/inputs";
import { queryClient } from "../../App";
import arrayMutators from "final-form-arrays";
import { FieldArray } from "react-final-form-arrays";
import {classNames} from "../../styles/utils";
import {useToggle} from "../../hooks/hooks";
import {DeleteModal} from "../../components/modals";
import { classNames } from "../../styles/utils";
import { useToggle } from "../../hooks/hooks";
import { DeleteModal } from "../../components/modals";
import APIClient from "../../api/APIClient";
import { NumberFieldWide, PasswordFieldWide } from "../../components/inputs/wide";
// interface radioFieldsetOption {
// label: string;
// description: string;
// value: string;
// }
//
// const saslTypeOptions: radioFieldsetOption[] = [
// {label: "None", description: "None", value: ""},
// {label: "Plain", description: "SASL plain", value: "PLAIN"},
// {label: "NickServ", description: "/NS identify", value: "NICKSERV"},
// ];
function IrcNetworkUpdateForm({isOpen, toggle, network}: any) {
function IrcNetworkUpdateForm({ isOpen, toggle, network }: any) {
const [deleteModalIsOpen, toggleDeleteModal] = useToggle(false)
const mutation = useMutation((network: Network) => APIClient.irc.updateNetwork(network), {
@ -59,7 +47,7 @@ function IrcNetworkUpdateForm({isOpen, toggle, network}: any) {
// let cmds = data.connect_commands && data.connect_commands.length > 0 ? data.connect_commands.replace(/\r\n/g,"\n").split("\n") : [];
// data.connect_commands = cmds
// console.log("formatted", data)
mutation.mutate(data)
};
@ -70,12 +58,16 @@ function IrcNetworkUpdateForm({isOpen, toggle, network}: any) {
errors.name = "Required";
}
if (!values.addr) {
errors.addr = "Required";
if (!values.server) {
errors.server = "Required";
}
if (!values.nick) {
errors.nick = "Required";
if (!values.port) {
errors.port = "Required";
}
if (!values.nickserv.account) {
errors.nickserv.account = "Required";
}
return errors;
@ -98,7 +90,7 @@ function IrcNetworkUpdateForm({isOpen, toggle, network}: any) {
text="Are you sure you want to remove this network and channels? This action cannot be undone."
/>
<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
@ -117,19 +109,13 @@ function IrcNetworkUpdateForm({isOpen, toggle, network}: any) {
id: network.id,
name: network.name,
enabled: network.enabled,
addr: network.addr,
server: network.server,
port: network.port,
tls: network.tls,
nick: network.nick,
nickserv: network.nickserv,
pass: network.pass,
invite_command: network.invite_command,
connect_commands: network.connect_commands,
sasl: network.sasl,
// sasl: {
// mechanism: network.sasl.mechanism,
// plain: {
// username: network.sasl.plain.username,
// password: network.sasl.plain.password,
// }
// },
channels: network.channels
}}
mutators={{
@ -138,10 +124,10 @@ function IrcNetworkUpdateForm({isOpen, toggle, network}: any) {
validate={validate}
onSubmit={onSubmit}
>
{({handleSubmit, values, pristine, invalid}) => {
{({ handleSubmit, values, pristine, invalid }) => {
return (
<form className="h-full flex flex-col bg-white shadow-xl overflow-y-scroll"
onSubmit={handleSubmit}>
onSubmit={handleSubmit}>
<div className="flex-1">
{/* Header */}
<div className="px-4 py-6 bg-gray-50 sm:px-6">
@ -160,106 +146,48 @@ function IrcNetworkUpdateForm({isOpen, toggle, network}: any) {
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" placeholder="Name" required={true} />
<div className="py-6 space-y-6 sm:py-0 sm:space-y-0 sm:divide-y sm:divide-gray-200">
<div
className="py-6 px-6 space-y-6 sm:py-0 sm:space-y-0 sm:divide-y sm:divide-gray-200">
<SwitchGroup name="enabled" label="Enabled"/>
<SwitchGroup name="enabled" label="Enabled" />
</div>
<div>
<TextFieldWide name="addr" label="Address" placeholder="Address:port eg irc.server.net:6697" required={true} />
<div className="px-6 space-y-1 mt-6">
<Dialog.Title className="text-lg font-medium text-gray-900">Connection</Dialog.Title>
{/* <p className="text-sm text-gray-500">
Networks, channels and invite commands are configured automatically.
</p> */}
</div>
<TextFieldWide name="server" label="Server" placeholder="Address: Eg irc.server.net" required={true} />
<NumberFieldWide name="port" label="Port" />
<div className="py-6 px-6 space-y-6 sm:py-0 sm:space-y-0 sm:divide-y sm:divide-gray-200">
<SwitchGroup name="tls" label="TLS"/>
<SwitchGroup name="tls" label="TLS" />
</div>
<TextFieldWide name="nick" label="Nick" placeholder="Nick" required={true} />
<PasswordFieldWide name="pass" label="Password" help="Network password" />
<TextFieldWide name="password" label="Password" placeholder="Network password" />
<div className="px-6 space-y-1 border-t pt-6">
<Dialog.Title className="text-lg font-medium text-gray-900">Account</Dialog.Title>
{/* <p className="text-sm text-gray-500">
Networks, channels and invite commands are configured automatically.
</p> */}
</div>
<TextAreaWide name="connect_commands" label="Connect commands" placeholder="/msg test this" />
<TextFieldWide name="nickserv.account" label="NickServ Account" required={true} />
<PasswordFieldWide name="nickserv.password" label="NickServ Password" />
{/* <Field*/}
{/* name="sasl.mechanism"*/}
{/* type="select"*/}
{/* render={({input}) => (*/}
{/* <Listbox value={input.value} onChange={input.onChange}>*/}
{/* {({open}) => (*/}
{/* <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>*/}
{/* <Listbox.Label className="block text-sm font-medium text-gray-900 sm:mt-px sm:pt-2">SASL / auth</Listbox.Label>*/}
{/* </div>*/}
{/* <div className="sm:col-span-2 relative">*/}
{/* <Listbox.Button*/}
{/* className="bg-white relative w-full border border-gray-300 rounded-md shadow-sm pl-3 pr-10 py-2 text-left cursor-default focus:outline-none focus:ring-1 focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm">*/}
{/* <span className="block truncate">{input.value ? saslTypeOptions.find(c => c.value === input.value)!.label : "Choose a auth type"}</span>*/}
{/* /!*<span className="block truncate">Choose a auth type</span>*!/*/}
{/* <span*/}
{/* className="absolute inset-y-0 right-0 flex items-center pr-2 pointer-events-none">*/}
{/* <SelectorIcon className="h-5 w-5 text-gray-400" aria-hidden="true"/>*/}
{/*</span>*/}
{/* </Listbox.Button>*/}
{/* <Transition*/}
{/* show={open}*/}
{/* as={Fragment}*/}
{/* leave="transition ease-in duration-100"*/}
{/* leaveFrom="opacity-100"*/}
{/* leaveTo="opacity-0"*/}
{/* >*/}
{/* <Listbox.Options*/}
{/* static*/}
{/* className="absolute z-10 mt-1 w-full bg-white shadow-lg max-h-60 rounded-md py-1 text-base ring-1 ring-black ring-opacity-5 overflow-auto focus:outline-none sm:text-sm"*/}
{/* >*/}
{/* {saslTypeOptions.map((opt: any) => (*/}
{/* <Listbox.Option*/}
{/* key={opt.value}*/}
{/* className={({active}) =>*/}
{/* classNames(*/}
{/* active ? 'text-white bg-indigo-600' : 'text-gray-900',*/}
{/* 'cursor-default select-none relative py-2 pl-3 pr-9'*/}
{/* )*/}
{/* }*/}
{/* value={opt.value}*/}
{/* >*/}
{/* {({selected, active}) => (*/}
{/* <>*/}
{/* <span className={classNames(selected ? 'font-semibold' : 'font-normal', 'block truncate')}>*/}
{/* {opt.label}*/}
{/* </span>*/}
{/* {selected ? (*/}
{/* <span*/}
{/* className={classNames(*/}
{/* active ? 'text-white' : 'text-indigo-600',*/}
{/* 'absolute inset-y-0 right-0 flex items-center pr-4'*/}
{/* )}*/}
{/* >*/}
{/* <CheckIcon className="h-5 w-5" aria-hidden="true"/>*/}
{/* </span>*/}
{/* ) : null}*/}
{/* </>*/}
{/* )}*/}
{/* </Listbox.Option>*/}
{/* ))}*/}
{/* </Listbox.Options>*/}
{/* </Transition>*/}
{/* </div>*/}
{/* </div>*/}
{/* )}*/}
{/* </Listbox>*/}
{/* )} />*/}
<PasswordFieldWide name="invite_command" label="Invite command" />
</div>
</div>
@ -294,7 +222,7 @@ function IrcNetworkUpdateForm({isOpen, toggle, network}: any) {
onClick={() => fields.remove(index)}
>
<span className="sr-only">Remove</span>
<XIcon className="h-6 w-6" aria-hidden="true"/>
<XIcon className="h-6 w-6" aria-hidden="true" />
</button>
</div>
))
@ -337,7 +265,7 @@ function IrcNetworkUpdateForm({isOpen, toggle, network}: any) {
<button
type="submit"
disabled={pristine || invalid}
className={classNames(pristine || invalid ? "bg-indigo-300" : "bg-indigo-600 hover:bg-indigo-700","inline-flex justify-center py-2 px-4 border border-transparent shadow-sm text-sm font-medium rounded-md text-white focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500")}
className={classNames(pristine || invalid ? "bg-indigo-300" : "bg-indigo-600 hover:bg-indigo-700", "inline-flex justify-center py-2 px-4 border border-transparent shadow-sm text-sm font-medium rounded-md text-white focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500")}
>
Save
</button>
@ -345,27 +273,7 @@ function IrcNetworkUpdateForm({isOpen, toggle, network}: any) {
</div>
</div>
{/*<div*/}
{/* className="flex-shrink-0 px-4 border-t border-gray-200 py-5 sm:px-6">*/}
{/* <div className="space-x-3 flex justify-end">*/}
{/* <button*/}
{/* type="button"*/}
{/* className="bg-white py-2 px-4 border border-gray-300 rounded-md shadow-sm text-sm font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"*/}
{/* onClick={toggle}*/}
{/* >*/}
{/* Cancel*/}
{/* </button>*/}
{/* <button*/}
{/* type="submit"*/}
{/* disabled={pristine || invalid}*/}
{/* className={classNames(pristine || invalid ? "bg-indigo-300" : "bg-indigo-600 hover:bg-indigo-700","inline-flex justify-center py-2 px-4 border border-transparent shadow-sm text-sm font-medium rounded-md text-white focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500")}*/}
{/* >*/}
{/* Save*/}
{/* </button>*/}
{/* </div>*/}
{/*</div>*/}
<DEBUG values={values}/>
<DEBUG values={values} />
</form>
)
}}

View file

@ -82,7 +82,7 @@ function IrcSettings() {
scope="col"
className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"
>
Addr
Server
</th>
<th
scope="col"
@ -138,8 +138,8 @@ const ListItem = ({ idx, network }: any) => {
</Switch>
</td>
<td className="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-900">{network.name}</td>
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-500">{network.addr} {network.tls && <span className="ml-2 inline-flex items-center px-2 py-0.5 rounded text-xs font-medium bg-green-100 text-green-800">TLS</span>}</td>
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-500">{network.nick}</td>
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-500">{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 text-green-800">TLS</span>}</td>
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-500">{network.nickserv?.account}</td>
<td className="px-6 py-4 whitespace-nowrap text-right text-sm font-medium">
<span className="text-indigo-600 hover:text-indigo-900 cursor-pointer" onClick={toggleUpdate}>
Edit