Skip to content

Commit

Permalink
feat(client): admin page to view all running dynamics
Browse files Browse the repository at this point in the history
  • Loading branch information
Ivo committed Apr 24, 2023
1 parent 62c9124 commit eeddab9
Show file tree
Hide file tree
Showing 22 changed files with 278 additions and 96 deletions.
8 changes: 8 additions & 0 deletions apps/client/public/locales/en-US/admin-running.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"title": "Running Dynamics",
"dynamic": {
"path": "Folder on storage",
"started-at": "Started at"
},
"worker-empty": "There are no tasks running on this worker"
}
3 changes: 2 additions & 1 deletion apps/client/public/locales/en-US/forms.json
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,8 @@
"box-distance": {
"title": "Box Distance (mm)",
"errors": {
"distance-doesnt-match": "Should be a number, decimal or not."
"distance-doesnt-match": "Should be a number, decimal or not.",
"out-of-bounds": "Must be between 0.1 and 1"
}
},
"neutralize": {
Expand Down
1 change: 1 addition & 0 deletions apps/client/public/locales/en-US/running.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
{
"title": "Your Dynamic is Running",
"taskId": "Task ID",
"abort": "Abort run",
"description": "Info",
"type": "Model",
"molecule": "Molecule",
Expand Down
63 changes: 63 additions & 0 deletions apps/client/src/components/AdminRunningDynamicsList/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import { Server } from "lucide-react";
import { useRouter } from "next/router";
import { useTranslation } from "next-i18next";

import { GetAdminRunningDynamicsListResult } from "@app/queries/useAdminRunningDynamicsList";

interface AdminRunningDynamicsListProps {
runningDynamics: GetAdminRunningDynamicsListResult;
}

export function AdminRunningDynamicsList({
runningDynamics
}: AdminRunningDynamicsListProps) {
const router = useRouter();
const { t } = useTranslation(["admin-running"]);

return (
<div>
{Object.keys(runningDynamics).map((worker) => {
const dynamics = runningDynamics[worker];

return (
<div
className="flex flex-col gap-2"
key={worker}
>
<div className="flex items-center gap-x-2">
<Server className="h-4 w-4 stroke-gray-500" />
<p className="text-gray-500">{worker}</p>
</div>
{dynamics && dynamics.length > 0 ? (
<div className="flex flex-col gap-y-1 rounded-lg p-4 odd:bg-zinc-500/20 even:bg-zinc-500/10">
{dynamics.map((dynamic) => (
<div key={`${worker}_${dynamic.id}`}>
<div className="flex gap-x-1">
<p>{t("admin-running:dynamic.path")}:</p>
<p className="font-semibold">{dynamic.args[0]}</p>
</div>
<div className="flex gap-x-1">
<p>{t("admin-running:dynamic.started-at")}:</p>
<p className="font-semibold">
{Intl.DateTimeFormat(router.locale, {
day: "2-digit",
month: "long",
year: "numeric",
hour: "2-digit",
minute: "2-digit",
second: "2-digit"
}).format(new Date(dynamic.time_start * 1000))}
</p>
</div>
</div>
))}
</div>
) : (
<p>{t("admin-running:worker-empty")}</p>
)}
</div>
);
})}
</div>
);
}
2 changes: 1 addition & 1 deletion apps/client/src/components/Container/Main.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ export function Main({ children }: IMain) {

return (
<main className="flex flex-1 flex-col bg-gray-100 text-gray-800 transition-all duration-150 dark:bg-gray-950 dark:text-gray-100 lg:overflow-y-auto lg:rounded-tl-3xl lg:border lg:border-l-gray-400 lg:border-t-gray-400 dark:lg:border-l-gray-600 dark:lg:border-t-gray-600">
<div className="sticky top-16 z-10 flex gap-x-2 bg-gray-100 px-6 shadow-sm shadow-gray-300 transition-all duration-150 dark:bg-gray-950 dark:shadow-gray-900 lg:top-0">
<div className="sticky top-[4.5rem] z-10 flex gap-x-2 bg-gray-100 px-6 shadow-sm shadow-gray-300 transition-all duration-150 dark:bg-gray-950 dark:shadow-gray-900 lg:top-0">
<Breadcrumb>
<BreadcrumbItem href="/">{t("common:app-name")}</BreadcrumbItem>
{breadcrumbs ? (
Expand Down
105 changes: 59 additions & 46 deletions apps/client/src/components/DynamicCard/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
Slash,
XCircle
} from "lucide-react";
import Link from "next/link";
import { useRouter } from "next/router";
import { useTranslation } from "next-i18next";

Expand All @@ -26,7 +27,7 @@ export function DynamicCard({ dynamic }: DynamicCardProps) {

return (
<div
className={clsx("flex w-full gap-2 rounded-md border p-2", {
className={clsx("flex w-full items-center gap-2 rounded-md border p-2", {
"border-cyan-600 bg-cyan-400/20": dynamic.status === "running",
"border-zinc-600 bg-zinc-400/20": dynamic.status === "canceled",
"border-yellow-600 bg-yellow-400/20": dynamic.status === "queued",
Expand All @@ -36,16 +37,16 @@ export function DynamicCard({ dynamic }: DynamicCardProps) {
key={dynamic.celeryId}
>
{dynamic.status === "finished" ? (
<CheckCircle className="mt-2 min-h-[2rem] min-w-[2rem] stroke-emerald-950 dark:stroke-emerald-300" />
<CheckCircle className="min-h-[2rem] min-w-[2rem] stroke-emerald-950 dark:stroke-emerald-300" />
) : null}
{dynamic.status === "canceled" ? (
<Slash className="mt-2 min-h-[2rem] min-w-[2rem] stroke-zinc-950 dark:stroke-zinc-300" />
<Slash className="min-h-[2rem] min-w-[2rem] stroke-zinc-950 dark:stroke-zinc-300" />
) : null}
{dynamic.status === "queued" ? (
<Clock className="mt-2 min-h-[2rem] min-w-[2rem] stroke-yellow-950 dark:stroke-yellow-300" />
<Clock className="min-h-[2rem] min-w-[2rem] stroke-yellow-950 dark:stroke-yellow-300" />
) : null}
{dynamic.status === "error" ? (
<XCircle className="mt-2 min-h-[2rem] min-w-[2rem] stroke-red-950 dark:stroke-red-300" />
<XCircle className="min-h-[2rem] min-w-[2rem] stroke-red-950 dark:stroke-red-300" />
) : null}
{dynamic.status === "running" ? (
<Spinner className="min-h-[2rem] min-w-[2rem] fill-blue-950 text-blue-100 dark:fill-blue-300" />
Expand Down Expand Up @@ -80,51 +81,63 @@ export function DynamicCard({ dynamic }: DynamicCardProps) {
{t("my-dynamics:downloads.title")}
</small>
<div className="flex flex-wrap gap-2">
<StatusButton
className="w-full md:w-fit"
LeftIcon={FileCode}
onClick={() =>
router.push(
`/api/downloads/commands?taskId=${dynamic.celeryId}`
)
}
status={dynamic.status}
<Link
href={`/api/downloads/commands?taskId=${dynamic.celeryId}`}
target="_blank"
>
{t("my-dynamics:downloads.commands")}
</StatusButton>
<StatusButton
className="w-full md:w-fit"
disabled={dynamic.status === "running"}
LeftIcon={Scroll}
onClick={() =>
router.push(`/api/downloads/log?taskId=${dynamic.celeryId}`)
}
status={dynamic.status}
<StatusButton
className="w-full md:w-fit"
LeftIcon={FileCode}
status={dynamic.status}
>
{t("my-dynamics:downloads.commands")}
</StatusButton>
</Link>
<Link
href={`/api/downloads/log?taskId=${dynamic.celeryId}`}
target="_blank"
>
{t("my-dynamics:downloads.log")}
</StatusButton>
<StatusButton
className="w-full md:w-fit"
disabled={dynamic.status === "running"}
LeftIcon={FileDigit}
onClick={() =>
router.push(`/api/downloads/results?taskId=${dynamic.celeryId}`)
}
status={dynamic.status}
<StatusButton
className="w-full md:w-fit"
disabled={
dynamic.status === "running" || dynamic.status === "queued"
}
LeftIcon={Scroll}
status={dynamic.status}
>
{t("my-dynamics:downloads.log")}
</StatusButton>
</Link>
<Link
href={`/api/downloads/results?taskId=${dynamic.celeryId}`}
target="_blank"
>
{t("my-dynamics:downloads.results")}
</StatusButton>
<StatusButton
className="w-full md:w-fit"
disabled={dynamic.status === "running"}
LeftIcon={Image}
onClick={() =>
router.push(`/api/downloads/figures?taskId=${dynamic.celeryId}`)
}
status={dynamic.status}
<StatusButton
className="w-full md:w-fit"
disabled={
dynamic.status === "running" || dynamic.status === "queued"
}
LeftIcon={FileDigit}
status={dynamic.status}
>
{t("my-dynamics:downloads.results")}
</StatusButton>
</Link>
<Link
href={`/api/downloads/figures?taskId=${dynamic.celeryId}`}
target="_blank"
>
{t("my-dynamics:downloads.figures")}
</StatusButton>
<StatusButton
className="w-full md:w-fit"
disabled={
dynamic.status === "running" || dynamic.status === "queued"
}
LeftIcon={Image}
status={dynamic.status}
>
{t("my-dynamics:downloads.figures")}
</StatusButton>
</Link>
</div>
</div>
</div>
Expand Down
9 changes: 9 additions & 0 deletions apps/client/src/components/FullPageLoader/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { Spinner } from "@app/components/Spinner";

export function FullPageLoader() {
return (
<div className="flex flex-1 items-center justify-center">
<Spinner />
</div>
);
}
1 change: 0 additions & 1 deletion apps/client/src/components/Input/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@ export const Input = forwardRef<HTMLInputElement, InputProps>(
"border-red-600/95 focus:ring-red-500": error
}
)}
min={type === "number" ? "0" : undefined}
step={type === "number" ? "0.1" : undefined}
placeholder={type === "number" ? "0" : placeholder}
autoComplete={type === "number" ? "off" : "auto"}
Expand Down
24 changes: 13 additions & 11 deletions apps/client/src/components/Sidebar/SidebarContent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ export function SidebarContent({ linkClicked }: ISidebarContent) {
},
{
label: "navigation:admin.dynamics.title",
href: "/admin/dynamics"
href: "/admin/running"
},
{
label: "navigation:admin.mdp.title",
Expand Down Expand Up @@ -127,15 +127,17 @@ export function SidebarContent({ linkClicked }: ISidebarContent) {

return (
<div className="text-gray-500 dark:text-gray-400">
<Link href="/">
<Image
alt="Visual Dynamics"
className="mx-auto my-3 h-full w-9/12"
height={0}
src="/images/logo.svg"
width={0}
/>
</Link>
<div className="sticky top-0 z-10 h-20 w-full bg-white py-3 dark:bg-gray-900">
<Link href="/">
<Image
alt="Visual Dynamics"
className="mx-auto h-full w-9/12"
height={0}
src="/images/logo.svg"
width={0}
/>
</Link>
</div>
<ul className="flex flex-col gap-y-4">
{navigationItems.map((section) => (
<div key={section.title}>
Expand All @@ -162,7 +164,7 @@ export function SidebarContent({ linkClicked }: ISidebarContent) {
<Link
href={link.href || "#"}
scroll={false}
className={`inline-flex h-full w-full items-center text-sm font-semibold transition-colors duration-150 hover:text-gray-800 dark:hover:text-gray-200 ${
className={`inline-flex h-full w-full items-center text-sm font-medium transition-colors duration-150 hover:text-gray-800 dark:hover:text-gray-200 ${
routeIsActive(pathname, link)
? "text-gray-800 dark:text-gray-100"
: ""
Expand Down
2 changes: 1 addition & 1 deletion apps/client/src/components/Transition/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -65,8 +65,8 @@ const CSSTransition: React.FC<TransitionProps> = function CSSTransition({
return (
<ReactCSSTransition
appear={appear}
unmountOnExit
in={show}
unmountOnExit
addEndListener={(node: HTMLElement, done) => {
node.addEventListener("transitionend", done, false);
}}
Expand Down
49 changes: 49 additions & 0 deletions apps/client/src/pages/admin/running.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import dynamic from "next/dynamic";
import { useTranslation } from "next-i18next";

import { FullPageLoader } from "@app/components/FullPageLoader";
import { SEO } from "@app/components/SEO";
import { Spinner } from "@app/components/Spinner";
import { withSSRAdmin } from "@app/hocs/withSSRAdmin";
import { withSSRTranslations } from "@app/hocs/withSSRTranslations";
import { useAdminRunningDynamicsList } from "@app/queries/useAdminRunningDynamicsList";

const AdminRunningDynamicsList = dynamic(
() =>
import("@app/components/AdminRunningDynamicsList").then(
(mod) => mod.AdminRunningDynamicsList
),
{
loading: () => <FullPageLoader />,
ssr: false
}
);

export const getServerSideProps = withSSRTranslations(withSSRAdmin(), {
namespaces: ["admin-running"]
});

export default function AdminSignup() {
const { data, isRefetching, isLoading } = useAdminRunningDynamicsList();
const { t } = useTranslation();

return (
<>
<SEO
title={t("admin-running:title")}
description={t("admin-running:description")}
/>
<h2 className="text-center text-2xl font-bold uppercase text-primary-700 dark:text-primary-400">
{t("admin-running:title")}
</h2>

{isLoading || isRefetching || !data ? (
<div className="flex flex-1 items-center justify-center">
<Spinner />
</div>
) : (
<AdminRunningDynamicsList runningDynamics={data} />
)}
</>
);
}
14 changes: 13 additions & 1 deletion apps/client/src/pages/admin/signup.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,25 @@
import axios from "axios";
import dynamic from "next/dynamic";
import { useTranslation } from "next-i18next";

import { AdminSignUpRequestList } from "@app/components/AdminSignUpRequestList";
import { FullPageLoader } from "@app/components/FullPageLoader";
import { SEO } from "@app/components/SEO";
import { Spinner } from "@app/components/Spinner";
import { withSSRAdmin } from "@app/hocs/withSSRAdmin";
import { withSSRTranslations } from "@app/hocs/withSSRTranslations";
import { useAdminSignUpRequestList } from "@app/queries/useAdminSignUpRequestList";

const AdminSignUpRequestList = dynamic(
() =>
import("@app/components/AdminSignUpRequestList").then(
(mod) => mod.AdminSignUpRequestList
),
{
loading: () => <FullPageLoader />,
ssr: false
}
);

export const getServerSideProps = withSSRTranslations(withSSRAdmin(), {
namespaces: ["admin-signup"]
});
Expand Down

0 comments on commit eeddab9

Please sign in to comment.