mirror of
https://github.com/idanoo/autobrr
synced 2025-07-23 00:39:13 +00:00
feat: overhaul webui (#135)
feat: Added Inter Variable as the default font feat: Added a pattern to the page background. fix(react-multi-select-component): Made the multiselect components theme-agnostic, so it works properly with the light theme now. fix(react-select): Made the components fix the default 30px height of the rest of the input components. fix(useToggle): Fixed a bug where the toggle didn't work due to useCallback memoizing a callback with the old value. refactor(Base): - Added small theme-primary gradient to the beginning of the header. - Made the splitter border theme-agnostic. - Increased logo size a bit. - Moved the links a bit closer to the logo. - Updated the default link style to look more stylish. - Added a link to the autobrr Docs section (currently defaults to the Indexers sections, but this should lead to an "Overview" page for configuring autobrr) - Adapted the user dropdown to match the other header links' stylesheets and removed the annoying ring focus. - Adapted the header for theme-agnostic mobile usage. - Removed the negative padding/margin hacks. refactor(StatsItem): Increased shadow size/strength and made the description text a bit lighter on the dark theme. refactor(Dashboard): Increased the heading text sizes. refactor(Logs, Releases, Settings, Login, filters/details, filters/list): Adapted to match the previous changes and improved theme compatibility with regards to text. refactor(RegexPlayground): Fixed match highlight for JS regex.
This commit is contained in:
parent
c3687b8fa5
commit
ac37bd4d9c
17 changed files with 371 additions and 342 deletions
|
@ -5,6 +5,7 @@
|
||||||
"proxy": "http://127.0.0.1:8989",
|
"proxy": "http://127.0.0.1:8989",
|
||||||
"homepage": ".",
|
"homepage": ".",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@fontsource/inter": "^4.5.4",
|
||||||
"@headlessui/react": "^1.2.0",
|
"@headlessui/react": "^1.2.0",
|
||||||
"@heroicons/react": "^1.0.1",
|
"@heroicons/react": "^1.0.1",
|
||||||
"date-fns": "^2.25.0",
|
"date-fns": "^2.25.0",
|
||||||
|
|
|
@ -28,6 +28,6 @@
|
||||||
</head>
|
</head>
|
||||||
<body class="bg-gray-100 dark:bg-gray-900">
|
<body class="bg-gray-100 dark:bg-gray-900">
|
||||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||||
<div id="root"></div>
|
<div id="root" class="pattern"></div>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
@ -38,5 +38,5 @@ export function App() {
|
||||||
<ReactQueryDevtools initialIsOpen={false} />
|
<ReactQueryDevtools initialIsOpen={false} />
|
||||||
) : null}
|
) : null}
|
||||||
</QueryClientProvider>
|
</QueryClientProvider>
|
||||||
)
|
);
|
||||||
}
|
}
|
|
@ -1,9 +1,11 @@
|
||||||
import { Fragment } from "react";
|
import { Fragment } from "react";
|
||||||
import { MultiSelect as RMSC} from "react-multi-select-component";
|
|
||||||
import { Transition, Listbox } from "@headlessui/react";
|
|
||||||
import { CheckIcon, SelectorIcon } from '@heroicons/react/solid';
|
|
||||||
import { classNames, COL_WIDTHS } from "../../utils";
|
|
||||||
import { Field } from "formik";
|
import { Field } from "formik";
|
||||||
|
import { Transition, Listbox } from "@headlessui/react";
|
||||||
|
import { CheckIcon, SelectorIcon } from "@heroicons/react/solid";
|
||||||
|
import { MultiSelect as RMSC } from "react-multi-select-component";
|
||||||
|
|
||||||
|
import { classNames, COL_WIDTHS } from "../../utils";
|
||||||
|
import { SettingsContext } from "../../utils/Context";
|
||||||
|
|
||||||
interface MultiSelectProps {
|
interface MultiSelectProps {
|
||||||
label?: string;
|
label?: string;
|
||||||
|
@ -17,80 +19,87 @@ export const MultiSelect = ({
|
||||||
label,
|
label,
|
||||||
options,
|
options,
|
||||||
columns,
|
columns,
|
||||||
}: MultiSelectProps) => (
|
}: MultiSelectProps) => {
|
||||||
<div
|
const settingsContext = SettingsContext.useValue();
|
||||||
className={classNames(
|
return (
|
||||||
columns ? `col-span-${columns}` : "col-span-12"
|
<div
|
||||||
)}
|
className={classNames(
|
||||||
>
|
columns ? `col-span-${columns}` : "col-span-12"
|
||||||
<label
|
|
||||||
className="block mb-2 text-xs font-bold tracking-wide text-gray-700 uppercase dark:text-gray-200"
|
|
||||||
htmlFor={label}
|
|
||||||
>
|
|
||||||
{label}
|
|
||||||
</label>
|
|
||||||
|
|
||||||
<Field name={name} type="select" multiple={true}>
|
|
||||||
{({
|
|
||||||
field,
|
|
||||||
form: { setFieldValue },
|
|
||||||
}: any) => (
|
|
||||||
<RMSC
|
|
||||||
{...field}
|
|
||||||
type="select"
|
|
||||||
options={options}
|
|
||||||
labelledBy={name}
|
|
||||||
value={field.value && field.value.map((item: any) => options.find((o: any) => o.value === item))}
|
|
||||||
onChange={(values: any) => {
|
|
||||||
const am = values && values.map((i: any) => i.value);
|
|
||||||
setFieldValue(field.name, am);
|
|
||||||
}}
|
|
||||||
className="dark:bg-gray-700 dark"
|
|
||||||
/>
|
|
||||||
)}
|
)}
|
||||||
</Field>
|
>
|
||||||
</div>
|
<label
|
||||||
);
|
className="block mb-2 text-xs font-bold tracking-wide text-gray-700 uppercase dark:text-gray-200"
|
||||||
|
htmlFor={label}
|
||||||
|
>
|
||||||
|
{label}
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<Field name={name} type="select" multiple={true}>
|
||||||
|
{({
|
||||||
|
field,
|
||||||
|
form: { setFieldValue },
|
||||||
|
}: any) => (
|
||||||
|
<RMSC
|
||||||
|
{...field}
|
||||||
|
type="select"
|
||||||
|
options={options}
|
||||||
|
labelledBy={name}
|
||||||
|
value={field.value && field.value.map((item: any) => options.find((o: any) => o.value === item))}
|
||||||
|
onChange={(values: any) => {
|
||||||
|
const am = values && values.map((i: any) => i.value);
|
||||||
|
setFieldValue(field.name, am);
|
||||||
|
}}
|
||||||
|
className={settingsContext.darkTheme ? "dark" : ""}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Field>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
export const IndexerMultiSelect = ({
|
export const IndexerMultiSelect = ({
|
||||||
name,
|
name,
|
||||||
label,
|
label,
|
||||||
options,
|
options,
|
||||||
columns,
|
columns,
|
||||||
}: MultiSelectProps) => (
|
}: MultiSelectProps) => {
|
||||||
<div
|
const settingsContext = SettingsContext.useValue();
|
||||||
className={classNames(
|
return (
|
||||||
columns ? `col-span-${columns}` : "col-span-12"
|
<div
|
||||||
)}
|
className={classNames(
|
||||||
>
|
columns ? `col-span-${columns}` : "col-span-12"
|
||||||
<label
|
)}
|
||||||
className="block mb-2 text-xs font-bold tracking-wide text-gray-700 uppercase dark:text-gray-200"
|
>
|
||||||
htmlFor={label}
|
<label
|
||||||
>
|
className="block mb-2 text-xs font-bold tracking-wide text-gray-700 uppercase dark:text-gray-200"
|
||||||
{label}
|
htmlFor={label}
|
||||||
</label>
|
>
|
||||||
|
{label}
|
||||||
|
</label>
|
||||||
|
|
||||||
<Field name={name} type="select" multiple={true}>
|
<Field name={name} type="select" multiple={true}>
|
||||||
{({
|
{({
|
||||||
field,
|
field,
|
||||||
form: { setFieldValue },
|
form: { setFieldValue },
|
||||||
}: any) => (
|
}: any) => (
|
||||||
<RMSC
|
<RMSC
|
||||||
{...field}
|
{...field}
|
||||||
type="select"
|
type="select"
|
||||||
options={options}
|
options={options}
|
||||||
labelledBy={name}
|
labelledBy={name}
|
||||||
value={field.value && field.value.map((item: any) => options.find((o: any) => o.value?.id === item.id))}
|
value={field.value && field.value.map((item: any) => options.find((o: any) => o.value?.id === item.id))}
|
||||||
onChange={(values: any) => {
|
onChange={(values: any) => {
|
||||||
const am = values && values.map((i: any) => i.value);
|
const am = values && values.map((i: any) => i.value);
|
||||||
setFieldValue(field.name, am);
|
setFieldValue(field.name, am);
|
||||||
}}
|
}}
|
||||||
className="dark:bg-gray-700 dark"
|
className={settingsContext.darkTheme ? "dark" : ""}
|
||||||
/>
|
itemHeight={50}
|
||||||
)}
|
/>
|
||||||
</Field>
|
)}
|
||||||
</div>
|
</Field>
|
||||||
);
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
interface DownloadClientSelectProps {
|
interface DownloadClientSelectProps {
|
||||||
name: string;
|
name: string;
|
||||||
|
|
|
@ -254,6 +254,14 @@ export function IndexerAddForm({ isOpen, toggle }: AddProps) {
|
||||||
color: "unset"
|
color: "unset"
|
||||||
})
|
})
|
||||||
}}
|
}}
|
||||||
|
theme={(theme) => ({
|
||||||
|
...theme,
|
||||||
|
spacing: {
|
||||||
|
...theme.spacing,
|
||||||
|
controlHeight: 30,
|
||||||
|
baseUnit: 2,
|
||||||
|
}
|
||||||
|
})}
|
||||||
value={field?.value && field.value.value}
|
value={field?.value && field.value.value}
|
||||||
onChange={(option: any) => {
|
onChange={(option: any) => {
|
||||||
setFieldValue("name", option?.label ?? "")
|
setFieldValue("name", option?.label ?? "")
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
import { useState, useCallback } from "react";
|
import { useState } from "react";
|
||||||
|
|
||||||
export function useToggle(initialValue = false): [boolean, () => void] {
|
export function useToggle(initialValue = false): [boolean, () => void] {
|
||||||
const [value, setValue] = useState(initialValue);
|
const [value, setValue] = useState(initialValue);
|
||||||
const toggle = useCallback(() => setValue(v => !v), []);
|
const toggle = () => setValue(v => !v);
|
||||||
|
|
||||||
return [value, toggle];
|
return [value, toggle];
|
||||||
}
|
}
|
||||||
|
|
File diff suppressed because one or more lines are too long
|
@ -1,5 +1,7 @@
|
||||||
import { StrictMode } from "react";
|
import { StrictMode } from "react";
|
||||||
import ReactDOM from "react-dom";
|
import ReactDOM from "react-dom";
|
||||||
|
|
||||||
|
import "@fontsource/inter/variable.css";
|
||||||
import "./index.css";
|
import "./index.css";
|
||||||
|
|
||||||
import { App } from "./App";
|
import { App } from "./App";
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
import { Fragment } from "react";
|
import { Fragment } from "react";
|
||||||
import { NavLink, Link, Route, Switch } from "react-router-dom";
|
import { NavLink, Link, Route, Switch } from "react-router-dom";
|
||||||
|
import type { match } from "react-router-dom";
|
||||||
import { Disclosure, Menu, Transition } from "@headlessui/react";
|
import { Disclosure, Menu, Transition } from "@headlessui/react";
|
||||||
|
import { ExternalLinkIcon } from "@heroicons/react/solid";
|
||||||
import { ChevronDownIcon, MenuIcon, XIcon } from "@heroicons/react/outline";
|
import { ChevronDownIcon, MenuIcon, XIcon } from "@heroicons/react/outline";
|
||||||
|
|
||||||
import Logs from "./Logs";
|
import Logs from "./Logs";
|
||||||
|
@ -13,13 +15,35 @@ import { AuthContext } from '../utils/Context';
|
||||||
|
|
||||||
import logo from '../logo.png';
|
import logo from '../logo.png';
|
||||||
|
|
||||||
|
interface NavItem {
|
||||||
|
name: string;
|
||||||
|
path: string;
|
||||||
|
}
|
||||||
|
|
||||||
function classNames(...classes: string[]) {
|
function classNames(...classes: string[]) {
|
||||||
return classes.filter(Boolean).join(' ')
|
return classes.filter(Boolean).join(' ')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const isActiveMatcher = (
|
||||||
|
match: match<any> | null,
|
||||||
|
location: { pathname: string },
|
||||||
|
item: NavItem
|
||||||
|
) => {
|
||||||
|
if (!match)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (match?.url === "/" && item.path === "/" && location.pathname === "/")
|
||||||
|
return true
|
||||||
|
|
||||||
|
if (match.url === "/")
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
export default function Base() {
|
export default function Base() {
|
||||||
const authContext = AuthContext.useValue();
|
const authContext = AuthContext.useValue();
|
||||||
const nav = [
|
const nav: Array<NavItem> = [
|
||||||
{ name: 'Dashboard', path: "/" },
|
{ name: 'Dashboard', path: "/" },
|
||||||
{ name: 'Filters', path: "/filters" },
|
{ name: 'Filters', path: "/filters" },
|
||||||
{ name: 'Releases', path: "/releases" },
|
{ name: 'Releases', path: "/releases" },
|
||||||
|
@ -28,56 +52,58 @@ export default function Base() {
|
||||||
];
|
];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div className="min-h-screen">
|
||||||
<Disclosure as="nav" className="bg-gray-900 pb-48">
|
<Disclosure
|
||||||
|
as="nav"
|
||||||
|
className="bg-gradient-to-b from-gray-100 dark:from-[#141414]"
|
||||||
|
>
|
||||||
{({ open }) => (
|
{({ open }) => (
|
||||||
<>
|
<>
|
||||||
<div className="max-w-7xl mx-auto sm:px-6 lg:px-8">
|
<div className="max-w-7xl mx-auto sm:px-6 lg:px-8">
|
||||||
<div className="border-b border-gray-700">
|
<div className="border-b border-gray-300 dark:border-gray-700">
|
||||||
<div className="flex items-center justify-between h-16 px-4 sm:px-0">
|
<div className="flex items-center justify-between h-16 px-4 sm:px-0">
|
||||||
<div className="flex items-center">
|
<div className="flex items-center">
|
||||||
<div className="flex-shrink-0 flex items-center">
|
<div className="flex-shrink-0 flex items-center">
|
||||||
<img
|
<img
|
||||||
className="block lg:hidden h-8 w-auto"
|
className="block lg:hidden h-10 w-auto"
|
||||||
src={logo}
|
src={logo}
|
||||||
alt="Logo"
|
alt="Logo"
|
||||||
/>
|
/>
|
||||||
<img
|
<img
|
||||||
className="hidden lg:block h-8 w-auto"
|
className="hidden lg:block h-10 w-auto"
|
||||||
src={logo}
|
src={logo}
|
||||||
alt="Logo"
|
alt="Logo"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="sm:ml-6 hidden sm:block">
|
<div className="sm:ml-3 hidden sm:block">
|
||||||
<div className="flex items-baseline space-x-4">
|
<div className="flex items-baseline space-x-4">
|
||||||
{nav.map((item, itemIdx) =>
|
{nav.map((item, itemIdx) =>
|
||||||
<NavLink
|
<NavLink
|
||||||
key={item.name + itemIdx}
|
key={item.name + itemIdx}
|
||||||
to={item.path}
|
to={item.path}
|
||||||
strict={true}
|
strict
|
||||||
isActive={(match, location) => {
|
|
||||||
if (match?.url === "/" && item.path === "/" && location.pathname === "/") {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!match) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (match.url === "/") {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}}
|
|
||||||
activeClassName="bg-gray-900 dark:bg-gray-700 text-white "
|
|
||||||
className={classNames(
|
className={classNames(
|
||||||
"text-gray-300 hover:bg-gray-700 hover:text-white px-3 py-2 rounded-md text-sm font-medium"
|
"text-gray-600 dark:text-gray-500 hover:bg-gray-200 dark:hover:bg-gray-800 hover:text-gray-900 dark:hover:text-white px-3 py-2 rounded-2xl text-sm font-medium",
|
||||||
|
"transition-colors duration-200"
|
||||||
)}
|
)}
|
||||||
|
activeClassName="text-black dark:text-gray-50 font-bold"
|
||||||
|
isActive={(match, location) => isActiveMatcher(match, location, item)}
|
||||||
>
|
>
|
||||||
{item.name}
|
{item.name}
|
||||||
</NavLink>
|
</NavLink>
|
||||||
)}
|
)}
|
||||||
|
<a
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
target="_blank"
|
||||||
|
href="https://autobrr.com/docs/configuration/indexers"
|
||||||
|
className={classNames(
|
||||||
|
"text-gray-600 dark:text-gray-500 hover:bg-gray-200 dark:hover:bg-gray-800 hover:text-gray-900 dark:hover:text-white px-3 py-2 rounded-2xl text-sm font-medium",
|
||||||
|
"transition-colors duration-200 flex items-center justify-center"
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
Docs
|
||||||
|
<ExternalLinkIcon className="inline ml-1 h-5 w-5" aria-hidden="true" />
|
||||||
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -86,19 +112,21 @@ export default function Base() {
|
||||||
<Menu as="div" className="ml-3 relative">
|
<Menu as="div" className="ml-3 relative">
|
||||||
{({ open }) => (
|
{({ open }) => (
|
||||||
<>
|
<>
|
||||||
<div>
|
<Menu.Button
|
||||||
<Menu.Button
|
className={classNames(
|
||||||
className="max-w-xs rounded-full flex items-center text-sm focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-offset-gray-800 focus:ring-white">
|
open ? "bg-gray-200 dark:bg-gray-800" : "",
|
||||||
<span
|
"text-gray-800 dark:text-gray-300 max-w-xs rounded-full flex items-center text-sm px-3 py-2 hover:bg-gray-200 dark:hover:bg-gray-800"
|
||||||
className="hidden text-gray-300 text-sm font-medium sm:block">
|
)}
|
||||||
<span className="sr-only">Open user menu for </span>{authContext.username}
|
>
|
||||||
</span>
|
<span className="hidden text-sm font-medium sm:block">
|
||||||
<ChevronDownIcon
|
<span className="sr-only">Open user menu for </span>
|
||||||
className="hidden flex-shrink-0 ml-1 h-5 w-5 text-gray-400 sm:block"
|
{authContext.username}
|
||||||
aria-hidden="true"
|
</span>
|
||||||
/>
|
<ChevronDownIcon
|
||||||
</Menu.Button>
|
className="hidden flex-shrink-0 ml-1 h-5 w-5 text-gray-800 dark:text-gray-300 sm:block"
|
||||||
</div>
|
aria-hidden="true"
|
||||||
|
/>
|
||||||
|
</Menu.Button>
|
||||||
<Transition
|
<Transition
|
||||||
show={open}
|
show={open}
|
||||||
as={Fragment}
|
as={Fragment}
|
||||||
|
@ -149,7 +177,7 @@ export default function Base() {
|
||||||
<div className="-mr-2 flex sm:hidden">
|
<div className="-mr-2 flex sm:hidden">
|
||||||
{/* Mobile menu button */}
|
{/* Mobile menu button */}
|
||||||
<Disclosure.Button
|
<Disclosure.Button
|
||||||
className="bg-gray-800 inline-flex items-center justify-center p-2 rounded-md text-gray-400 hover:text-white hover:bg-gray-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-offset-gray-800 focus:ring-white">
|
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>
|
<span className="sr-only">Open main menu</span>
|
||||||
{open ? (
|
{open ? (
|
||||||
<XIcon className="block h-6 w-6" aria-hidden="true" />
|
<XIcon className="block h-6 w-6" aria-hidden="true" />
|
||||||
|
@ -162,48 +190,28 @@ export default function Base() {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Disclosure.Panel className="border-b border-gray-700 md:hidden">
|
<Disclosure.Panel className="border-b border-gray-300 dark:border-gray-700 md:hidden">
|
||||||
<div className="px-2 py-3 space-y-1 sm:px-3">
|
<div className="px-2 py-3 space-y-1 sm:px-3">
|
||||||
{nav.map((item, itemIdx) =>
|
{nav.map((item) =>
|
||||||
itemIdx === 0 ? (
|
<NavLink
|
||||||
<Fragment key={item.path}>
|
key={item.path}
|
||||||
<Link to={item.path}
|
to={item.path}
|
||||||
className="bg-gray-900 text-white block px-3 py-2 rounded-md text-base font-medium">
|
strict
|
||||||
{item.name}
|
className="dark:bg-gray-900 dark:text-white block px-3 py-2 rounded-md text-base font-medium"
|
||||||
</Link>
|
activeClassName="font-bold bg-gray-300 text-black"
|
||||||
</Fragment>
|
isActive={(match, location) => isActiveMatcher(match, location, item)}
|
||||||
) : (
|
>
|
||||||
<Link
|
{item.name}
|
||||||
key={item.path}
|
</NavLink>
|
||||||
to={item.path}
|
|
||||||
className="text-gray-300 hover:bg-gray-700 hover:text-white block px-3 py-2 rounded-md text-base font-medium"
|
|
||||||
>
|
|
||||||
{item.name}
|
|
||||||
</Link>
|
|
||||||
)
|
|
||||||
)}
|
)}
|
||||||
|
<Link
|
||||||
|
to="/logout"
|
||||||
|
className="dark:bg-gray-900 dark:text-white block px-3 py-2 rounded-md text-base font-medium"
|
||||||
|
>
|
||||||
|
Logout
|
||||||
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
<div className="pt-4 pb-3 border-t border-gray-700">
|
|
||||||
<div className="flex items-center px-5">
|
|
||||||
<div>
|
|
||||||
<div className="text-base font-medium leading-none text-white">User</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="mt-3 px-2 space-y-1">
|
|
||||||
<Link
|
|
||||||
to="/settings"
|
|
||||||
className="block px-3 py-2 rounded-md text-base font-medium text-gray-400 hover:text-white hover:bg-gray-700"
|
|
||||||
>
|
|
||||||
Settings
|
|
||||||
</Link>
|
|
||||||
<Link
|
|
||||||
to="/logout"
|
|
||||||
className="block px-3 py-2 rounded-md text-base font-medium text-gray-400 hover:text-white hover:bg-gray-700"
|
|
||||||
>
|
|
||||||
Logout
|
|
||||||
</Link>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</Disclosure.Panel>
|
</Disclosure.Panel>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
@ -236,4 +244,4 @@ export default function Base() {
|
||||||
</Switch>
|
</Switch>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,7 +15,7 @@ import { ReleaseStatusCell } from "./Releases";
|
||||||
|
|
||||||
export function Dashboard() {
|
export function Dashboard() {
|
||||||
return (
|
return (
|
||||||
<main className="py-10 -mt-48">
|
<main className="py-10">
|
||||||
<div className="px-4 pb-8 mx-auto max-w-7xl sm:px-6 lg:px-8">
|
<div className="px-4 pb-8 mx-auto max-w-7xl sm:px-6 lg:px-8">
|
||||||
<Stats />
|
<Stats />
|
||||||
<DataTable />
|
<DataTable />
|
||||||
|
@ -26,15 +26,15 @@ export function Dashboard() {
|
||||||
|
|
||||||
const StatsItem = ({ name, stat }: any) => (
|
const StatsItem = ({ name, stat }: any) => (
|
||||||
<div
|
<div
|
||||||
className="relative px-4 pt-5 pb-2 overflow-hidden bg-white rounded-lg shadow dark:bg-gray-800 sm:pt-6 sm:px-6"
|
className="relative px-4 pt-5 pb-2 overflow-hidden bg-white rounded-lg shadow-lg dark:bg-gray-800 sm:pt-6 sm:px-6"
|
||||||
title="All time"
|
title="All time"
|
||||||
>
|
>
|
||||||
<dt>
|
<dt>
|
||||||
<p className="pb-1 text-sm font-medium text-gray-500 truncate dark:text-gray-600">{name}</p>
|
<p className="pb-1 text-sm font-medium text-gray-500 truncate">{name}</p>
|
||||||
</dt>
|
</dt>
|
||||||
|
|
||||||
<dd className="flex items-baseline pb-6 sm:pb-7">
|
<dd className="flex items-baseline pb-6 sm:pb-7">
|
||||||
<p className="text-2xl font-semibold text-gray-900 dark:text-gray-300">{stat}</p>
|
<p className="text-2xl font-semibold text-gray-900 dark:text-gray-200">{stat}</p>
|
||||||
</dd>
|
</dd>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
@ -51,7 +51,9 @@ function Stats() {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<h3 className="text-lg font-medium leading-6 text-gray-900 dark:text-gray-600">Stats</h3>
|
<h3 className="text-2xl font-medium leading-6 text-gray-900 dark:text-gray-200">
|
||||||
|
Stats
|
||||||
|
</h3>
|
||||||
|
|
||||||
<dl className="grid grid-cols-1 gap-5 mt-5 sm:grid-cols-2 lg:grid-cols-3">
|
<dl className="grid grid-cols-1 gap-5 mt-5 sm:grid-cols-2 lg:grid-cols-3">
|
||||||
<StatsItem name="Filtered Releases" stat={data?.filtered_count} />
|
<StatsItem name="Filtered Releases" stat={data?.filtered_count} />
|
||||||
|
@ -311,7 +313,7 @@ function DataTable() {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col mt-12">
|
<div className="flex flex-col mt-12">
|
||||||
<h3 className="text-lg font-medium leading-6 text-gray-900 dark:text-gray-600">
|
<h3 className="text-2xl font-medium leading-6 text-gray-900 dark:text-gray-200">
|
||||||
Recent activity
|
Recent activity
|
||||||
</h3>
|
</h3>
|
||||||
|
|
||||||
|
|
|
@ -30,15 +30,15 @@ export default function Logs() {
|
||||||
}, [setLogs]);
|
}, [setLogs]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<main className="-mt-48">
|
<main>
|
||||||
<header className="py-10">
|
<header className="py-10">
|
||||||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||||
<h1 className="text-3xl font-bold text-white capitalize">Logs</h1>
|
<h1 className="text-3xl font-bold text-black dark:text-white capitalize">Logs</h1>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
<div className="max-w-7xl mx-auto pb-12 px-2 sm:px-4 lg:px-8">
|
<div className="max-w-7xl mx-auto pb-12 px-2 sm:px-4 lg:px-8">
|
||||||
<div className="bg-white dark:bg-gray-800 rounded-lg shadow px-2 sm:px-4 py-3 sm:py-4">
|
<div className="bg-white dark:bg-gray-800 rounded-lg shadow-lg px-2 sm:px-4 py-3 sm:py-4">
|
||||||
<div className=" overflow-y-auto p-2 rounded-lg h-96 bg-gray-900">
|
<div className=" overflow-y-auto p-2 rounded-lg h-96 bg-gray-100 dark:bg-gray-900">
|
||||||
{logs.map((a, idx) => (
|
{logs.map((a, idx) => (
|
||||||
<p key={idx}>
|
<p key={idx}>
|
||||||
<span className="font-mono text-gray-600 mr-2">{a.time}</span>
|
<span className="font-mono text-gray-600 mr-2">{a.time}</span>
|
||||||
|
|
|
@ -21,11 +21,10 @@ import { classNames, simplifyDate } from "../utils";
|
||||||
|
|
||||||
export function Releases() {
|
export function Releases() {
|
||||||
return (
|
return (
|
||||||
<main className="-mt-48">
|
<main>
|
||||||
|
|
||||||
<header className="py-10">
|
<header className="py-10">
|
||||||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 flex justify-between">
|
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 flex justify-between">
|
||||||
<h1 className="text-3xl font-bold text-white capitalize">Releases</h1>
|
<h1 className="text-3xl font-bold text-black dark:text-white capitalize">Releases</h1>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
<div className="px-4 pb-8 mx-auto max-w-7xl sm:px-6 lg:px-8">
|
<div className="px-4 pb-8 mx-auto max-w-7xl sm:px-6 lg:px-8">
|
||||||
|
@ -305,156 +304,138 @@ function Table() {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{isSuccess && data ? (
|
{isSuccess && data ? (
|
||||||
<div className="flex flex-col mt-4">
|
<div className="flex flex-col">
|
||||||
{/* <GlobalFilter
|
<div className="overflow-hidden bg-white shadow-lg dark:bg-gray-800 sm:rounded-lg">
|
||||||
preGlobalFilteredRows={preGlobalFilteredRows}
|
<table {...getTableProps()} className="min-w-full divide-y divide-gray-200 dark:divide-gray-700">
|
||||||
globalFilter={state.globalFilter}
|
<thead className="bg-gray-50 dark:bg-gray-800">
|
||||||
setGlobalFilter={setGlobalFilter}
|
{headerGroups.map((headerGroup) => {
|
||||||
/> */}
|
const { key: rowKey, ...rowRest } = headerGroup.getHeaderGroupProps();
|
||||||
{/* {headerGroups.map((headerGroup: { headers: any[] }) =>
|
return (
|
||||||
headerGroup.headers.map((column) =>
|
<tr key={rowKey} {...rowRest}>
|
||||||
column.Filter ? (
|
{headerGroup.headers.map((column) => {
|
||||||
<div className="mt-2 sm:mt-0" key={column.id}>
|
const { key: columnKey, ...columnRest } = column.getHeaderProps(column.getSortByToggleProps());
|
||||||
{column.render("Filter")}
|
return (
|
||||||
</div>
|
// Add the sorting props to control sorting. For this example
|
||||||
) : null
|
// we can add them into the header props
|
||||||
)
|
<th
|
||||||
)} */}
|
key={`${rowKey}-${columnKey}`}
|
||||||
<div className="-mx-4 -my-2 overflow-x-auto sm:-mx-6 lg:-mx-8">
|
scope="col"
|
||||||
<div className="inline-block min-w-full py-2 align-middle sm:px-6 lg:px-8">
|
className="px-6 py-3 text-xs font-medium tracking-wider text-left text-gray-500 uppercase group"
|
||||||
<div className="overflow-hidden bg-white shadow dark:bg-gray-800 sm:rounded-lg">
|
{...columnRest}
|
||||||
<table {...getTableProps()} className="min-w-full divide-y divide-gray-200 dark:divide-gray-700">
|
>
|
||||||
<thead className="bg-gray-50 dark:bg-gray-800">
|
<div className="flex items-center justify-between">
|
||||||
{headerGroups.map((headerGroup) => {
|
{column.render('Header')}
|
||||||
const { key: rowKey, ...rowRest } = headerGroup.getHeaderGroupProps();
|
{/* Add a sort direction indicator */}
|
||||||
return (
|
<span>
|
||||||
<tr key={rowKey} {...rowRest}>
|
{column.isSorted ? (
|
||||||
{headerGroup.headers.map((column) => {
|
column.isSortedDesc ? (
|
||||||
const { key: columnKey, ...columnRest } = column.getHeaderProps(column.getSortByToggleProps());
|
<SortDownIcon className="w-4 h-4 text-gray-400" />
|
||||||
return (
|
) : (
|
||||||
// Add the sorting props to control sorting. For this example
|
<SortUpIcon className="w-4 h-4 text-gray-400" />
|
||||||
// we can add them into the header props
|
)
|
||||||
<th
|
) : (
|
||||||
key={`${rowKey}-${columnKey}`}
|
<SortIcon className="w-4 h-4 text-gray-400 opacity-0 group-hover:opacity-100" />
|
||||||
scope="col"
|
)}
|
||||||
className="px-6 py-3 text-xs font-medium tracking-wider text-left text-gray-500 uppercase group"
|
</span>
|
||||||
{...columnRest}
|
</div>
|
||||||
>
|
</th>
|
||||||
<div className="flex items-center justify-between">
|
);
|
||||||
{column.render('Header')}
|
})}
|
||||||
{/* Add a sort direction indicator */}
|
</tr>
|
||||||
<span>
|
);
|
||||||
{column.isSorted ? (
|
})}
|
||||||
column.isSortedDesc ? (
|
</thead>
|
||||||
<SortDownIcon className="w-4 h-4 text-gray-400" />
|
<tbody
|
||||||
) : (
|
{...getTableBodyProps()}
|
||||||
<SortUpIcon className="w-4 h-4 text-gray-400" />
|
className="divide-y divide-gray-200 dark:divide-gray-700"
|
||||||
)
|
>
|
||||||
) : (
|
{page.map((row: any) => { // new
|
||||||
<SortIcon className="w-4 h-4 text-gray-400 opacity-0 group-hover:opacity-100" />
|
prepareRow(row)
|
||||||
)}
|
const { key: bodyRowKey, ...bodyRowRest } = row.getRowProps();
|
||||||
</span>
|
return (
|
||||||
</div>
|
<tr key={bodyRowKey} {...bodyRowRest}>
|
||||||
</th>
|
{row.cells.map((cell: any) => {
|
||||||
);
|
const { key: cellRowKey, ...cellRowRest } = cell.getCellProps();
|
||||||
})}
|
return (
|
||||||
</tr>
|
<td
|
||||||
);
|
key={cellRowKey}
|
||||||
})}
|
className="px-6 py-4 whitespace-nowrap"
|
||||||
</thead>
|
role="cell"
|
||||||
<tbody
|
{...cellRowRest}
|
||||||
{...getTableBodyProps()}
|
>
|
||||||
className="divide-y divide-gray-200 dark:divide-gray-700"
|
{cell.column.Cell.name === "defaultRenderer"
|
||||||
>
|
? <div className="text-sm text-gray-500">{cell.render('Cell')}</div>
|
||||||
{page.map((row: any) => { // new
|
: cell.render('Cell')
|
||||||
prepareRow(row)
|
}
|
||||||
const { key: bodyRowKey, ...bodyRowRest } = row.getRowProps();
|
</td>
|
||||||
return (
|
);
|
||||||
<tr key={bodyRowKey} {...bodyRowRest}>
|
})}
|
||||||
{row.cells.map((cell: any) => {
|
</tr>
|
||||||
const { key: cellRowKey, ...cellRowRest } = cell.getCellProps();
|
);
|
||||||
return (
|
})}
|
||||||
<td
|
</tbody>
|
||||||
key={cellRowKey}
|
</table>
|
||||||
className="px-6 py-4 whitespace-nowrap"
|
|
||||||
role="cell"
|
|
||||||
{...cellRowRest}
|
|
||||||
>
|
|
||||||
{cell.column.Cell.name === "defaultRenderer"
|
|
||||||
? <div className="text-sm text-gray-500">{cell.render('Cell')}</div>
|
|
||||||
: cell.render('Cell')
|
|
||||||
}
|
|
||||||
</td>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</tr>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
|
|
||||||
{/* Pagination */}
|
{/* Pagination */}
|
||||||
<div className="flex items-center justify-between px-6 py-3 border-t border-gray-200 dark:border-gray-700">
|
<div className="flex items-center justify-between px-6 py-3 border-t border-gray-200 dark:border-gray-700">
|
||||||
<div className="flex justify-between flex-1 sm:hidden">
|
<div className="flex justify-between flex-1 sm:hidden">
|
||||||
<Button onClick={() => previousPage()} disabled={!canPreviousPage}>Previous</Button>
|
<Button onClick={() => previousPage()} disabled={!canPreviousPage}>Previous</Button>
|
||||||
<Button onClick={() => nextPage()} disabled={!canNextPage}>Next</Button>
|
<Button onClick={() => nextPage()} disabled={!canNextPage}>Next</Button>
|
||||||
</div>
|
</div>
|
||||||
<div className="hidden sm:flex-1 sm:flex sm:items-center sm:justify-between">
|
<div className="hidden sm:flex-1 sm:flex sm:items-center sm:justify-between">
|
||||||
<div className="flex items-baseline gap-x-2">
|
<div className="flex items-baseline gap-x-2">
|
||||||
<span className="text-sm text-gray-700">
|
<span className="text-sm text-gray-700">
|
||||||
Page <span className="font-medium">{pageIndex + 1}</span> of <span className="font-medium">{pageOptions.length}</span>
|
Page <span className="font-medium">{pageIndex + 1}</span> of <span className="font-medium">{pageOptions.length}</span>
|
||||||
</span>
|
</span>
|
||||||
<label>
|
<label>
|
||||||
<span className="sr-only">Items Per Page</span>
|
<span className="sr-only">Items Per Page</span>
|
||||||
<select
|
<select
|
||||||
className="block w-full border-gray-300 rounded-md shadow-sm cursor-pointer dark:bg-gray-800 dark:border-gray-800 dark:text-gray-600 dark:hover:text-gray-500 focus:border-blue-300 focus:ring focus:ring-blue-200 focus:ring-opacity-50"
|
className="block w-full border-gray-300 rounded-md shadow-sm cursor-pointer dark:bg-gray-800 dark:border-gray-800 dark:text-gray-600 dark:hover:text-gray-500 focus:border-blue-300 focus:ring focus:ring-blue-200 focus:ring-opacity-50"
|
||||||
value={pageSize}
|
value={pageSize}
|
||||||
onChange={e => {
|
onChange={e => {
|
||||||
setPageSize(Number(e.target.value))
|
setPageSize(Number(e.target.value))
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{[5, 10, 20, 50].map(pageSize => (
|
{[5, 10, 20, 50].map(pageSize => (
|
||||||
<option key={pageSize} value={pageSize}>
|
<option key={pageSize} value={pageSize}>
|
||||||
Show {pageSize}
|
Show {pageSize}
|
||||||
</option>
|
</option>
|
||||||
))}
|
))}
|
||||||
</select>
|
</select>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<nav className="relative z-0 inline-flex -space-x-px rounded-md shadow-sm" aria-label="Pagination">
|
<nav className="relative z-0 inline-flex -space-x-px rounded-md shadow-sm" aria-label="Pagination">
|
||||||
<PageButton
|
<PageButton
|
||||||
className="rounded-l-md"
|
className="rounded-l-md"
|
||||||
onClick={() => gotoPage(0)}
|
onClick={() => gotoPage(0)}
|
||||||
disabled={!canPreviousPage}
|
disabled={!canPreviousPage}
|
||||||
>
|
>
|
||||||
<span className="sr-only">First</span>
|
<span className="sr-only">First</span>
|
||||||
<ChevronDoubleLeftIcon className="w-5 h-5 text-gray-400" aria-hidden="true" />
|
<ChevronDoubleLeftIcon className="w-5 h-5 text-gray-400" aria-hidden="true" />
|
||||||
</PageButton>
|
</PageButton>
|
||||||
<PageButton
|
<PageButton
|
||||||
onClick={() => previousPage()}
|
onClick={() => previousPage()}
|
||||||
disabled={!canPreviousPage}
|
disabled={!canPreviousPage}
|
||||||
>
|
>
|
||||||
<span className="sr-only">Previous</span>
|
<span className="sr-only">Previous</span>
|
||||||
<ChevronLeftIcon className="w-5 h-5 text-gray-400" aria-hidden="true" />
|
<ChevronLeftIcon className="w-5 h-5 text-gray-400" aria-hidden="true" />
|
||||||
</PageButton>
|
</PageButton>
|
||||||
<PageButton
|
<PageButton
|
||||||
onClick={() => nextPage()}
|
onClick={() => nextPage()}
|
||||||
disabled={!canNextPage}>
|
disabled={!canNextPage}>
|
||||||
<span className="sr-only">Next</span>
|
<span className="sr-only">Next</span>
|
||||||
<ChevronRightIcon className="w-5 h-5 text-gray-400" aria-hidden="true" />
|
<ChevronRightIcon className="w-5 h-5 text-gray-400" aria-hidden="true" />
|
||||||
</PageButton>
|
</PageButton>
|
||||||
<PageButton
|
<PageButton
|
||||||
className="rounded-r-md"
|
className="rounded-r-md"
|
||||||
onClick={() => gotoPage(pageCount - 1)}
|
onClick={() => gotoPage(pageCount - 1)}
|
||||||
disabled={!canNextPage}
|
disabled={!canNextPage}
|
||||||
>
|
>
|
||||||
<span className="sr-only">Last</span>
|
<span className="sr-only">Last</span>
|
||||||
<ChevronDoubleRightIcon className="w-5 h-5 text-gray-400" aria-hidden="true" />
|
<ChevronDoubleRightIcon className="w-5 h-5 text-gray-400" aria-hidden="true" />
|
||||||
</PageButton>
|
</PageButton>
|
||||||
</nav>
|
</nav>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -62,15 +62,15 @@ function SidebarNav({subNavigation, url}: any) {
|
||||||
export default function Settings() {
|
export default function Settings() {
|
||||||
const { url } = useRouteMatch();
|
const { url } = useRouteMatch();
|
||||||
return (
|
return (
|
||||||
<main className="relative -mt-48">
|
<main>
|
||||||
<header className="py-10">
|
<header className="py-10">
|
||||||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||||
<h1 className="text-3xl font-bold text-white capitalize">Settings</h1>
|
<h1 className="text-3xl font-bold text-black dark:text-white capitalize">Settings</h1>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<div className="max-w-screen-xl mx-auto pb-6 px-4 sm:px-6 lg:pb-16 lg:px-8">
|
<div className="max-w-screen-xl mx-auto pb-6 px-4 sm:px-6 lg:pb-16 lg:px-8">
|
||||||
<div className="bg-white dark:bg-gray-800 rounded-lg shadow overflow-hidden">
|
<div className="bg-white dark:bg-gray-800 rounded-lg shadow-lg overflow-hidden">
|
||||||
<div className="divide-y divide-gray-200 dark:divide-gray-700 lg:grid lg:grid-cols-12 lg:divide-y-0 lg:divide-x">
|
<div className="divide-y divide-gray-200 dark:divide-gray-700 lg:grid lg:grid-cols-12 lg:divide-y-0 lg:divide-x">
|
||||||
<SidebarNav url={url} subNavigation={subNavigation}/>
|
<SidebarNav url={url} subNavigation={subNavigation}/>
|
||||||
|
|
||||||
|
|
|
@ -33,7 +33,7 @@ function Login() {
|
||||||
const handleSubmit = (data: any) => mutation.mutate(data);
|
const handleSubmit = (data: any) => mutation.mutate(data);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="min-h-screen bg-gray-50 dark:bg-gray-900 flex flex-col justify-center py-12 sm:px-6 lg:px-8">
|
<div className="min-h-screen flex flex-col justify-center py-12 sm:px-6 lg:px-8">
|
||||||
<div className="sm:mx-auto sm:w-full sm:max-w-md mb-6">
|
<div className="sm:mx-auto sm:w-full sm:max-w-md mb-6">
|
||||||
<img
|
<img
|
||||||
className="mx-auto h-12 w-auto"
|
className="mx-auto h-12 w-auto"
|
||||||
|
@ -41,8 +41,8 @@ function Login() {
|
||||||
alt="logo"
|
alt="logo"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="sm:mx-auto sm:w-full sm:max-w-md">
|
<div className="sm:mx-auto sm:w-full sm:max-w-md shadow-lg">
|
||||||
<div className="bg-white dark:bg-gray-800 py-8 px-4 shadow sm:rounded-lg sm:px-10">
|
<div className="bg-white dark:bg-gray-800 py-8 px-4 sm:rounded-lg sm:px-10">
|
||||||
|
|
||||||
<Formik
|
<Formik
|
||||||
initialValues={{ username: "", password: "" }}
|
initialValues={{ username: "", password: "" }}
|
||||||
|
@ -71,4 +71,4 @@ function Login() {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Login;
|
export default Login;
|
||||||
|
|
|
@ -186,16 +186,16 @@ export default function FilterDetails() {
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<main className="-mt-48 ">
|
<main>
|
||||||
<header className="py-10">
|
<header className="py-10">
|
||||||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 flex items-center">
|
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 flex items-center">
|
||||||
<h1 className="text-3xl font-bold text-white capitalize">
|
<h1 className="text-3xl font-bold text-black dark:text-white capitalize">
|
||||||
<NavLink to="/filters" exact={true}>
|
<NavLink to="/filters" exact={true}>
|
||||||
Filters
|
Filters
|
||||||
</NavLink>
|
</NavLink>
|
||||||
</h1>
|
</h1>
|
||||||
<ChevronRightIcon className="h-6 w-6 text-gray-500" aria-hidden="true" />
|
<ChevronRightIcon className="h-6 w-6 text-gray-500" aria-hidden="true" />
|
||||||
<h1 className="text-3xl font-bold text-white capitalize">{filter.name}</h1>
|
<h1 className="text-3xl font-bold text-black dark:text-white capitalize">{filter.name}</h1>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
<div className="max-w-7xl mx-auto pb-12 px-4 sm:px-6 lg:px-8">
|
<div className="max-w-7xl mx-auto pb-12 px-4 sm:px-6 lg:px-8">
|
||||||
|
|
|
@ -29,12 +29,12 @@ export default function Filters() {
|
||||||
return (<p>An error has occurred: </p>);
|
return (<p>An error has occurred: </p>);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<main className="-mt-48 ">
|
<main>
|
||||||
<FilterAddForm isOpen={createFilterIsOpen} toggle={toggleCreateFilter} />
|
<FilterAddForm isOpen={createFilterIsOpen} toggle={toggleCreateFilter} />
|
||||||
|
|
||||||
<header className="py-10">
|
<header className="py-10">
|
||||||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 flex justify-between">
|
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 flex justify-between">
|
||||||
<h1 className="text-3xl font-bold text-white capitalize">Filters</h1>
|
<h1 className="text-3xl font-bold text-black dark:text-white capitalize">Filters</h1>
|
||||||
|
|
||||||
<div className="flex-shrink-0">
|
<div className="flex-shrink-0">
|
||||||
<button
|
<button
|
||||||
|
@ -49,7 +49,7 @@ export default function Filters() {
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<div className="max-w-7xl mx-auto pb-12 px-4 sm:px-6 lg:px-8">
|
<div className="max-w-7xl mx-auto pb-12 px-4 sm:px-6 lg:px-8">
|
||||||
<div className="bg-white dark:bg-gray-800 light:rounded-lg light:shadow">
|
<div className="bg-white dark:bg-gray-800 light:rounded-lg shadow-lg">
|
||||||
<div className="relative inset-0 light:py-3 light:px-3 light:sm:px-3 light:lg:px-3 h-full">
|
<div className="relative inset-0 light:py-3 light:px-3 light:sm:px-3 light:lg:px-3 h-full">
|
||||||
{data && data.length > 0 ? <FilterList filters={data} /> :
|
{data && data.length > 0 ? <FilterList filters={data} /> :
|
||||||
<EmptyListState text="No filters here.." buttonText="Add new" buttonOnClick={toggleCreateFilter} />}
|
<EmptyListState text="No filters here.." buttonText="Add new" buttonOnClick={toggleCreateFilter} />}
|
||||||
|
|
|
@ -20,16 +20,23 @@ export const RegexPlayground = () => {
|
||||||
if (match.index === undefined)
|
if (match.index === undefined)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
|
if (!match.length)
|
||||||
|
continue;
|
||||||
|
|
||||||
const start = match.index;
|
const start = match.index;
|
||||||
|
|
||||||
|
let length = 0;
|
||||||
|
match.forEach((group) => length += group.length);
|
||||||
|
|
||||||
results.push(
|
results.push(
|
||||||
<span key={`match=${start}`}>
|
<span key={`match-${start}`}>
|
||||||
{line.substring(lastIndex, start)}
|
{line.substring(lastIndex, start)}
|
||||||
<span className="bg-green-200 text-black font-bold">
|
<span className="bg-blue-300 text-black font-bold">
|
||||||
{line.substring(start, start + match.length)}
|
{line.substring(start, start + length)}
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
lastIndex = start + match.length;
|
lastIndex = start + length;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (lastIndex < line.length) {
|
if (lastIndex < line.length) {
|
||||||
|
@ -60,7 +67,7 @@ export const RegexPlayground = () => {
|
||||||
<div className="px-6 py-4">
|
<div className="px-6 py-4">
|
||||||
<label
|
<label
|
||||||
htmlFor="input-regex"
|
htmlFor="input-regex"
|
||||||
className="block text-sm font-medium text-gray-300"
|
className="block text-sm font-medium text-gray-600 dark:text-gray-300"
|
||||||
>
|
>
|
||||||
RegExp filter
|
RegExp filter
|
||||||
</label>
|
</label>
|
||||||
|
@ -73,7 +80,7 @@ export const RegexPlayground = () => {
|
||||||
/>
|
/>
|
||||||
<label
|
<label
|
||||||
htmlFor="input-lines"
|
htmlFor="input-lines"
|
||||||
className="block text-sm font-medium text-gray-300"
|
className="block text-sm font-medium text-gray-600 dark:text-gray-300"
|
||||||
>
|
>
|
||||||
Lines to match
|
Lines to match
|
||||||
</label>
|
</label>
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue