Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 37 additions & 4 deletions tavern/internal/www/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions tavern/internal/www/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
"react-virtualized": "^9.22.4",
"recharts": "^2.11.0",
"sort-by": "^1.2.0",
"tailwind-variants": "^0.2.1",
"typescript": "^4.9.5",
"web-vitals": "^2.1.4",
"xterm-addon-attach": "^0.9.0",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
import { EmptyState, EmptyStateType } from "../tavern-base-ui/EmptyState";
import Button from "../tavern-base-ui/button/Button";

const EmptyStateNoBeacon = () => {
return (
<EmptyState type={EmptyStateType.noData} label="No beacons found" details="Get started by deploying an imix agent on your target system.">
<button
<Button
type="button"
className="inline-flex items-center rounded-md bg-purple-700 px-4 py-2 text-sm font-semibold text-white shadow-sm hover:bg-purple-600 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-purple-700"
onClick={() => window.open("https://docs.realm.pub/user-guide/getting-started#start-the-agent", '_blank')}
>
See imix docs
</button>
</Button>
</EmptyState>
);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
import { Link } from "react-router-dom";
import { EmptyState, EmptyStateType } from "../tavern-base-ui/EmptyState";
import Button from "../tavern-base-ui/button/Button";

const EmptyStateNoQuests = () => {
return (
<EmptyState label="No quests found" type={EmptyStateType.noData} details="Get started by creating a new quest." >
<Link to="/createQuest">
<button
<Button
type="button"
className="inline-flex items-center rounded-md bg-purple-700 px-4 py-2 text-sm font-semibold text-white shadow-sm hover:bg-purple-600 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-purple-700"
>
Create new quest
</button>
</Button>
</Link>
</EmptyState>
)
Expand Down
11 changes: 6 additions & 5 deletions tavern/internal/www/src/components/tavern-base-ui/Modal.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { XMarkIcon } from "@heroicons/react/24/outline";
import React, { FC, Fragment } from "react";
import { Dialog, Transition } from '@headlessui/react';
import Button from "./button/Button";

type ModalProps = {
isOpen: boolean,
Expand Down Expand Up @@ -31,15 +32,15 @@ const Modal: FC<ModalProps> = ({ isOpen, setOpen, children }) => {
<div className="flex w-full justify-end">

<div className="ml-3 flex h-7 items-center">
<button
<Button
type="button"
className="relative rounded-md bg-white text-gray-400 hover:text-gray-500 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2"
buttonStyle={{ color: "gray", size: "md" }}
buttonVariant="ghost"
onClick={() => setOpen(false)}
leftIcon={<XMarkIcon className="h-6 w-6" aria-hidden="true" />}
>
<span className="absolute -inset-2.5" />
<span className="sr-only">Close panel</span>
<XMarkIcon className="h-6 w-6" aria-hidden="true" />
</button>
</Button>
</div>
</div>
</div>
Expand Down
110 changes: 57 additions & 53 deletions tavern/internal/www/src/components/tavern-base-ui/TablePagination.tsx
Original file line number Diff line number Diff line change
@@ -1,62 +1,66 @@
import Button from "./button/Button";

type PageInfo = {
hasNextPage: boolean,
hasPreviousPage: boolean,
startCursor: string,
endCursor: string
hasNextPage: boolean,
hasPreviousPage: boolean,
startCursor: string,
endCursor: string
}
type Props = {
totalCount: number;
pageInfo: PageInfo;
refetchTable: (endCursor: string | undefined, startCursor: string | undefined) => void;
page: number;
setPage: any;
rowLimit: number;
totalCount: number;
pageInfo: PageInfo;
refetchTable: (endCursor: string | undefined, startCursor: string | undefined) => void;
page: number;
setPage: any;
rowLimit: number;
}
export default function TablePagination(props: Props) {
const {totalCount, pageInfo, refetchTable, page, setPage, rowLimit} = props;
const { totalCount, pageInfo, refetchTable, page, setPage, rowLimit } = props;

function handlePreviousClick(){
if(refetchTable && pageInfo.hasPreviousPage){
setPage((page:number)=> page-1);
refetchTable(undefined, pageInfo.startCursor);
}
}
function handleNextClick(){
if(refetchTable && pageInfo.hasNextPage){
setPage((page:number)=> page+1);
refetchTable( pageInfo.endCursor, undefined);
}
function handlePreviousClick() {
if (refetchTable && pageInfo.hasPreviousPage) {
setPage((page: number) => page - 1);
refetchTable(undefined, pageInfo.startCursor);
}
const getPageCount = () => {
return Math.ceil(totalCount / rowLimit);
}
function handleNextClick() {
if (refetchTable && pageInfo.hasNextPage) {
setPage((page: number) => page + 1);
refetchTable(pageInfo.endCursor, undefined);
}
}
const getPageCount = () => {
return Math.ceil(totalCount / rowLimit);
}

return (
<nav
className="flex items-center justify-between border-t border-gray-200 bg-white px-4 py-3 sm:px-6"
aria-label="Pagination"
>
<div className="hidden sm:block">
<p className="text-sm text-gray-800">
Page <span className="font-semibold">{page}</span> of <span className="font-semibold">{getPageCount()}</span> {`(${totalCount} results)`}
</p>
</div>
<div className="flex flex-1 justify-between sm:justify-end">
<button
disabled={!pageInfo.hasPreviousPage}
onClick={()=>handlePreviousClick()}
className="relative inline-flex items-center rounded-md bg-white px-3 py-2 text-sm font-semibold text-gray-900 ring-1 ring-inset ring-gray-300 hover:bg-gray-50 focus-visible:outline-offset-0 disabled:opacity-50 disabled:cursor-not-allowed"
>
Previous
</button>
<button
disabled={!pageInfo.hasNextPage}
onClick={()=> handleNextClick()}
className="relative ml-3 inline-flex items-center rounded-md bg-white px-3 py-2 text-sm font-semibold text-gray-900 ring-1 ring-inset ring-gray-300 hover:bg-gray-50 focus-visible:outline-offset-0 disabled:opacity-50 disabled:cursor-not-allowed"
>
Next
</button>
</div>
</nav>
)
};
return (
<nav
className="flex items-center justify-between border-t border-gray-200 bg-white px-4 py-3 sm:px-6"
aria-label="Pagination"
>
<div className="hidden sm:block">
<p className="text-sm text-gray-800">
Page <span className="font-semibold">{page}</span> of <span className="font-semibold">{getPageCount()}</span> {`(${totalCount} results)`}
</p>
</div>
<div className="flex flex-1 justify-between sm:justify-end gap-2">
<Button
buttonVariant="outline"
buttonStyle={{ color: 'gray', size: "md" }}
disabled={!pageInfo.hasPreviousPage}
onClick={() => handlePreviousClick()}
>
Previous
</Button>
<Button
buttonVariant="outline"
disabled={!pageInfo.hasNextPage}
buttonStyle={{ color: 'gray', size: "md" }}
onClick={() => handleNextClick()}
>
Next
</Button>
</div>
</nav>
)
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
/* eslint-disable react/jsx-props-no-spreading */
import { forwardRef, useMemo } from "react";
import { outlineButton, solidButton, ghostButton } from "./ButtonStyles";
import { VariantProps } from "tailwind-variants";
import { Ring } from "@uiball/loaders";

// define all the button attributes
type BaseButtonAttributes = React.ComponentPropsWithoutRef<"button">;

// define the ref type
type Ref = HTMLButtonElement;

// extend the base button attributes
interface ButtonProps extends BaseButtonAttributes {
isLoading?: boolean;
disabled?: boolean;
leftIcon?: React.ReactElement;
rightIcon?: React.ReactElement;
buttonStyle?: VariantProps<typeof solidButton | typeof outlineButton | typeof ghostButton>;
className?: string,
buttonVariant?: "solid" | "outline" | "ghost";
}

const Button = forwardRef<Ref, ButtonProps>((props, ref) => {
// destructure neccesary props
const { type, children, buttonStyle, buttonVariant, disabled, isLoading, leftIcon, rightIcon, className, ...rest } = props;

// determine icon placement
const { newIcon: icon, iconPlacement } = useMemo(() => {
let newIcon = rightIcon || leftIcon;

if (isLoading) {
newIcon = <Ring
size={14}
lineWeight={2}
speed={2}
color="white"
/>;
}

return {
newIcon,
iconPlacement: rightIcon ? ("right" as const) : ("left" as const),
};
}, [isLoading, leftIcon, rightIcon]);

const renderButtonVariant = () => {
if (buttonVariant === "solid") {
return solidButton({ ...buttonStyle, className })
}
if (buttonVariant === "outline") {
return outlineButton({ ...buttonStyle, className })
}
return ghostButton({ ...buttonStyle, className })
}

return (
<button
className={renderButtonVariant()}
{...rest}
type={type ? "submit" : "button"}
ref={ref}
disabled={disabled || isLoading}
>
{/** render icon before */}
{icon && iconPlacement === "left" ? (
<span className={`inline-flex shrink-0 self-center ${children && !isLoading && "mr-2"}`}>{icon}</span>
) : null}

{/** hide button text during loading state */}
{!isLoading && children}

{/** render icon after */}
{icon && iconPlacement === "right" ? (
<span className={`inline-flex shrink-0 self-center ${children && !isLoading && "ml-2"}`}>{icon}</span>
) : null}
</button>
);
});

// set default props
Button.defaultProps = {
buttonStyle: { color: "purple", size: "md" },
buttonVariant: "solid",
isLoading: false,
disabled: false,
leftIcon: undefined,
rightIcon: undefined,
};

export default Button;
Loading