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
140 changes: 101 additions & 39 deletions src/pages/Apps/AppDetailPage.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import React from "react";
import React, { useEffect, useState } from "react";
import { useGetApp, useSetContainerSize } from "@/utils/helpers";
import { Link, useParams } from "react-router-dom";
import { Link, useNavigate, useParams } from "react-router-dom";
import {
ActionIcon,
Button,
Card,
Code,
Expand All @@ -11,18 +12,30 @@ import {
Skeleton,
Stack,
Text,
Tooltip,
} from "@mantine/core";
import TitleText, { CustomText } from "@/components/TitleText";
import { TbCopy, TbWorld } from "react-icons/tb";
import { FiExternalLink } from "react-icons/fi";
import { FaDocker } from "react-icons/fa";
import { LuScreenShare } from "react-icons/lu";
import { SiJupyter } from "react-icons/si";
import { ModalConfirm } from "@/components/Elements/Modals";
import usePost from "@/utils/usePost";
import { RiRefreshLine } from "react-icons/ri";

const AppDetailPage = () => {
const { app_id } = useParams();
const { app, loading } = useGetApp(app_id || "");
useSetContainerSize("md");
const [refreshModalOpened, setRefreshModalOpened] = useState(false);
const navigate = useNavigate();

const {
uploadData: refreshApp,
submitting: refreshingApp,
success: refreshedAppSuccess,
} = usePost();

const getAppStatus = (status: string) => {
if (status === "running") {
Expand Down Expand Up @@ -87,6 +100,25 @@ const AppDetailPage = () => {
value: getAppStatus(app?.app_running_status),
},
];

const handleRefresh = () => {
refreshApp({
api: `apps/${app?.id}/restart`,
params: {
restart: true,
},
});
};

useEffect(() => {
if (refreshedAppSuccess) {
setRefreshModalOpened(false);
navigate(
`/projects/${app?.project_id}/apps/${app?.id}/settings?tab=deployments`,
);
}
}, [refreshedAppSuccess]);

return (
<Stack gap={20}>
<TitleText
Expand All @@ -110,47 +142,77 @@ const AppDetailPage = () => {
<AppDetailsSkeleton />
) : (
<Stack gap={20}>
<Flex gap={20} wrap="wrap">
{app?.image && !app?.is_notebook && (
<Stack gap={5}>
<Text className="subtitle">Image</Text>
<Code>
<CustomText
size="sm"
leftSection={<FaDocker color="gray.7" />}
>
{app?.image}
</CustomText>
</Code>
<Flex gap={20} wrap="wrap" justify="space-between">
<Flex gap={20} wrap="wrap">
{app?.image && !app?.is_notebook && (
<Stack gap={5}>
<Text className="subtitle">Image</Text>
<Code>
<CustomText
size="sm"
leftSection={<FaDocker color="gray.7" />}
>
{app?.image}
</CustomText>
</Code>
</Stack>
)}
<Stack gap={5} w="fit-content" flex={app?.is_notebook && 1}>
<CustomText className="subtitle" leftSection={<TbWorld />}>
Domain
</CustomText>
<Text
component={Link}
size="sm"
to={app?.url}
target="_blank"
className="link"
>
{app?.url}
<FiExternalLink />
</Text>
</Stack>
)}
<Stack gap={5} w="fit-content" flex={app?.is_notebook && 1}>
<CustomText className="subtitle" leftSection={<TbWorld />}>
Domain
</CustomText>
<Text
component={Link}
size="sm"
to={app?.url}
target="_blank"
className="link"
{app?.is_notebook && (
<Pill w="fit-content">
<Flex gap={5} wrap="nowrap" w="fit-content" align="center">
<SiJupyter size={13} color="#f57c00" />
<Text size="sm" truncate>
Notebook
</Text>
</Flex>
</Pill>
)}
</Flex>
<Tooltip label="Refresh" withArrow position="bottom">
<ActionIcon
variant="light"
color="dark"
size="md"
radius="xl"
onClick={() => setRefreshModalOpened(true)}
>
{app?.url}
<FiExternalLink />
</Text>
</Stack>
{app?.is_notebook && (
<Pill w="fit-content">
<Flex gap={5} wrap="nowrap" w="fit-content" align="center">
<SiJupyter size={13} color="#f57c00" />
<Text size="sm" truncate>
Notebook
</Text>
</Flex>
</Pill>
)}
<RiRefreshLine size={18} />
</ActionIcon>
</Tooltip>
</Flex>

<ModalConfirm
opened={refreshModalOpened}
onClose={() => setRefreshModalOpened(false)}
title="Refresh App"
buttonColor="gray.9"
buttonText="Refresh"
onConfirm={handleRefresh}
loading={refreshingApp}
leftSection={<RiRefreshLine size={18} />}
>
Are you sure you want to refresh the app?
<Text size="sm" mt="md">
This action will restart the app and pull the latest image. This
may take a few minutes.
</Text>
</ModalConfirm>

<Grid>
{appInfo.map((info) => (
<Grid.Col
Expand Down
23 changes: 17 additions & 6 deletions src/pages/Apps/AppSettingsPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,12 @@ import {
import { FiCalendar, FiExternalLink } from "react-icons/fi";
import { LiaDocker } from "react-icons/lia";

import { Link, useNavigate, useParams } from "react-router-dom";
import {
Link,
useNavigate,
useParams,
useSearchParams,
} from "react-router-dom";
import { TbCheck, TbCopy } from "react-icons/tb";
import { useClipboard } from "@mantine/hooks";
import { useAuth } from "@/utils/AuthContext";
Expand All @@ -66,19 +71,25 @@ import { FaPencil } from "react-icons/fa6";

const AppSettingsPage = () => {
const { app_id } = useParams();
const [refresh, setRefresh] = useState<number>(0);
const [refresh, setRefresh] = useState(0);
const { app } = useGetApp(app_id || "", refresh);
const { setContainerSize } = useContext(MenuContext);

const [searchParams, setSearchParams] = useSearchParams();
const activeTab = searchParams.get("tab") || "general";

const handleTabChange = (value: string | null) => {
setSearchParams({ tab: value || "" });
};
useEffect(() => {
setContainerSize("md");
return () => {
setContainerSize("xl");
};
return () => setContainerSize("xl");
}, [setContainerSize]);

useEffect(() => {}, [app]);

return (
<Tabs defaultValue="general">
<Tabs defaultValue="general" value={activeTab} onChange={handleTabChange}>
<Tabs.List>
<Tabs.Tab value="general">General</Tabs.Tab>
<Tabs.Tab value="ci/cd">CI / CD</Tabs.Tab>
Expand Down
Loading