Skip to content

Commit

Permalink
Added instance with instance checks.
Browse files Browse the repository at this point in the history
  • Loading branch information
SamTV12345 committed Jan 21, 2024
1 parent ce6114e commit f93e982
Show file tree
Hide file tree
Showing 6 changed files with 272 additions and 2 deletions.
3 changes: 3 additions & 0 deletions src/components/icons.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,15 @@ import {
AreaChart,
UserX2Icon,
Database,
Circle,
X,
} from "lucide-react";

export type Icon = LucideIcon;

export const Icons = {
alert: AlertTriangle,
circle: Circle,
instances: Database,
statistics: AreaChart,
dashboard: LayoutDashboardIcon,
Expand Down
120 changes: 120 additions & 0 deletions src/components/ui/dialog.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
import * as React from "react"
import * as DialogPrimitive from "@radix-ui/react-dialog"
import { Cross2Icon } from "@radix-ui/react-icons"

import { cn } from "@/lib/utils"

const Dialog = DialogPrimitive.Root

const DialogTrigger = DialogPrimitive.Trigger

const DialogPortal = DialogPrimitive.Portal

const DialogClose = DialogPrimitive.Close

const DialogOverlay = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Overlay>,
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Overlay>
>(({ className, ...props }, ref) => (
<DialogPrimitive.Overlay
ref={ref}
className={cn(
"fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
className
)}
{...props}
/>
))
DialogOverlay.displayName = DialogPrimitive.Overlay.displayName

const DialogContent = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content>
>(({ className, children, ...props }, ref) => (
<DialogPortal>
<DialogOverlay />
<DialogPrimitive.Content
ref={ref}
className={cn(
"fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg",
className
)}
{...props}
>
{children}
<DialogPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground">
<Cross2Icon className="h-4 w-4" />
<span className="sr-only">Close</span>
</DialogPrimitive.Close>
</DialogPrimitive.Content>
</DialogPortal>
))
DialogContent.displayName = DialogPrimitive.Content.displayName

const DialogHeader = ({
className,
...props
}: React.HTMLAttributes<HTMLDivElement>) => (
<div
className={cn(
"flex flex-col space-y-1.5 text-center sm:text-left",
className
)}
{...props}
/>
)
DialogHeader.displayName = "DialogHeader"

const DialogFooter = ({
className,
...props
}: React.HTMLAttributes<HTMLDivElement>) => (
<div
className={cn(
"flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2",
className
)}
{...props}
/>
)
DialogFooter.displayName = "DialogFooter"

const DialogTitle = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Title>,
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Title>
>(({ className, ...props }, ref) => (
<DialogPrimitive.Title
ref={ref}
className={cn(
"text-lg font-semibold leading-none tracking-tight",
className
)}
{...props}
/>
))
DialogTitle.displayName = DialogPrimitive.Title.displayName

const DialogDescription = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Description>,
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Description>
>(({ className, ...props }, ref) => (
<DialogPrimitive.Description
ref={ref}
className={cn("text-sm text-muted-foreground", className)}
{...props}
/>
))
DialogDescription.displayName = DialogPrimitive.Description.displayName

export {
Dialog,
DialogPortal,
DialogOverlay,
DialogTrigger,
DialogClose,
DialogContent,
DialogHeader,
DialogFooter,
DialogTitle,
DialogDescription,
}
2 changes: 2 additions & 0 deletions src/main.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,13 @@ import {createHashRouter, createRoutesFromElements, Navigate, Route} from "react
import {RouterProvider} from "react-router";
import {Statistics} from "@/pages/statistics.tsx";
import axios from "axios";
import {Instances} from "@/pages/instances.tsx";

const router = createHashRouter(createRoutesFromElements(
<Route element={<App/>}>
<Route index element={<Navigate to="/statistics"/>}/>
<Route path="/statistics" element={<Statistics/>}/>
<Route path={"/instances"} element={<Instances/>}/>
</Route>
))

Expand Down
130 changes: 130 additions & 0 deletions src/pages/instances.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
import {useEffect, useMemo, useState} from "react";
import axios, {AxiosResponse} from "axios";
import {Instance, InstancesResponse} from "@/types/InstancesResponse.ts";
import {AlertTriangle, Check, NotepadText} from 'lucide-react'

import {
Dialog,
DialogContent,
DialogDescription,
DialogHeader,
DialogPortal,
DialogTitle
} from "@/components/ui/dialog.tsx";
import {Card, CardContent, CardHeader, CardTitle} from "@/components/ui/card.tsx";


export const Instances = ()=>{
const [instances,setInstances] = useState<InstancesResponse>()
const [filter,setFilter] = useState<string>("")
const filteredInstances = useMemo(()=>{
if (filter.length === 0) return instances?.instances
return instances?.instances.filter((instance)=>{
return instance.name.includes(filter)
})
},[instances, filter])
const [dialogOpen, setDialogOpen] = useState(false)
const [instance, setInstance] = useState<Instance>()

useEffect(() => {
axios.get("/instances")
.then((res:AxiosResponse<InstancesResponse>)=>{
setInstances(res.data)

})
}, [])

const isOldVersion = (version:string)=>{
const replacedVersion = version.replaceAll(".","")
return replacedVersion < "190"
}

if(!filteredInstances) return <div>Loading...</div>

return (
<div className="flex flex-col m-5">
<h1 className="text-4xl font-bold mb-5">Scanned instances</h1>
<div className="flex flex-row mb-5">
<input type="text" placeholder="Enter your Etherpad url" className="w-full border border-gray-400 rounded-md px-2 py-1 focus:outline-none focus:border-blue-400" onChange={(e)=>{
setFilter(e.target.value)
}}/>
</div>
<table className=" w-full">
<thead>
<tr>
<td>Health</td>
<th className="px-4 py-2">Version</th>
<th className="px-4 py-2">Actions</th>
</tr>
</thead>
<tbody>
{filteredInstances.map((instance)=>{
return (
<tr key={instance.name}>
<td className="border px-4 py-2">{isOldVersion(instance.scan.version)?<AlertTriangle className="text-yellow-400"/>: <Check/>}</td>
<td className="border px-4 py-2" onClick={()=>{
if (!instance.name.startsWith("http")){
instance.name = "http://"+instance.name
}
window.open(instance.name)
}}>{instance.name}</td>
<td className="border px-4 py-2 text-etherpad" onClick={()=>{
setInstance(instance)
setDialogOpen(true)}
}><NotepadText className="mx-[40%]"/></td>
</tr>
)
})}
</tbody>
</table>
<Dialog open={dialogOpen} onOpenChange={()=>setDialogOpen(!dialogOpen)}>
<DialogPortal>
<DialogContent>
<DialogHeader>
<DialogTitle>Scan result from the {new Date(instance?.scan.scan_time!).toLocaleString()}</DialogTitle>
<DialogDescription>Instance {instance?.name}</DialogDescription>
</DialogHeader>
<div className="flex flex-col gap-5">
<div className="grid grid-cols-2 gap-5">
<Card>
<CardHeader>
<CardTitle>Version</CardTitle>
</CardHeader>
<CardContent>
<p>{instance?.scan.version}</p>
</CardContent>
</Card>

<Card>
<CardHeader>
<CardTitle>API level</CardTitle>
<CardContent>
<p>{instance?.scan.api_version}</p>
</CardContent>
</CardHeader>
</Card>
<div className="col-span-2">
<Card>
<CardHeader>
<CardTitle>Plugins</CardTitle>
</CardHeader>
<CardContent className="">
<ul className="list-disc ml-5">{instance?.scan.plugins.map(p=><li>{p}</li>)}</ul>
</CardContent>
</Card>
</div>
</div>
{
isOldVersion(instance?.scan.version!) &&
<span className="flex gap-5">
<AlertTriangle className="text-yellow-400 w-16 h-16"/>
<span className="text-red-700 font-bold mt-2">Your Etherpad version is really outdated. Consider upgrading to a newer version.</span>
</span>
}
</div>
</DialogContent>
</DialogPortal>
</Dialog>
</div>
)
}
15 changes: 15 additions & 0 deletions src/types/InstancesResponse.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
export type InstancesResponse = {
instances: Instance[]
}

export type Instance = {
name: string,
scan: ScanResult
}

type ScanResult = {
api_version: string,
version: string,
scan_time: string,
plugins: string[]
}
4 changes: 2 additions & 2 deletions tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@
"./src/*"
]
},
"target": "ES2020",
"target": "ES2023",

Check failure on line 9 in tsconfig.json

View workflow job for this annotation

GitHub Actions / deploy

Argument for '--target' option must be: 'es3', 'es5', 'es6', 'es2015', 'es2016', 'es2017', 'es2018', 'es2019', 'es2020', 'es2021', 'es2022', 'esnext'.
"useDefineForClassFields": true,
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"lib": ["ES2023", "DOM", "DOM.Iterable"],
"module": "ESNext",
"skipLibCheck": true,

Expand Down

0 comments on commit f93e982

Please sign in to comment.