Skip to content

Commit

Permalink
feat: MVP for installing plugins from a remote chris server
Browse files Browse the repository at this point in the history
  • Loading branch information
PintoGideon committed Jun 18, 2024
1 parent 0f3911a commit 44071b0
Show file tree
Hide file tree
Showing 3 changed files with 131 additions and 50 deletions.
153 changes: 109 additions & 44 deletions src/components/PluginInstall/index.tsx
Original file line number Diff line number Diff line change
@@ -1,30 +1,37 @@
import { ListVariant, LoginForm, LoginPage } from "@patternfly/react-core";
import { useMutation } from "@tanstack/react-query";
import React from "react";
import {
FormGroup,
Form,
TextInput,
Button,
ActionGroup,
} from "@patternfly/react-core";
import WrapperConnect from "../Wrapper";
import { useSearchQueryParams } from "../Feeds/usePaginate";
import { ExclamationCircleIcon } from "../Icons";
import WrapperConnect from "../Wrapper";
import { Alert } from "antd";
import { SpinContainer } from "../Common";
import { useNavigate } from "react-router";

const PluginInstall = () => {
const navigate = useNavigate();
const [showHelperText, setShowHelperText] = React.useState(false);
const [username, setUsername] = React.useState("");
const [isValidUsername, setIsValidUsername] = React.useState(true);
const [password, setPassword] = React.useState("");
const [isValidPassword, setIsValidPassword] = React.useState(true);
const [isRememberMeChecked, setIsRememberMeChecked] = React.useState(false);

const query = useSearchQueryParams();
const url = query.get("uri");

let decodedURL = "";

if (url) {
decodedURL = decodeURIComponent(url);
}

async function handleSave() {
const adminURL = import.meta.env.VITE_CHRIS_UI_URL;
const modifiedURL = adminURL.replace("/api/v1/", "/chris-admin/api/v1/");

if (!adminURL) {
throw new Error("Failed to fetch the admin url");
}

const modifiedURL = adminURL.replace("/api/v1/", "/chris-admin/api/v1/");
const credentials = btoa(`${username.trim()}:${password.trim()}`); // Base64 encoding for Basic Auth
const pluginData = {
compute_names: "host",
Expand All @@ -43,48 +50,106 @@ const PluginInstall = () => {
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}

const data = await response.json();
console.log("Data", data);
return data;
} catch (error) {
// biome-ignore lint/complexity/noUselessCatch: <explanation>
throw error;
}
}

const { isPending, isSuccess, isError, error, data, mutate } = useMutation({
mutationFn: async () => await handleSave(),
});

React.useEffect(() => {
if (isSuccess) {
if (data) {
const id = data.collection.items[0].data[0].value;
if (typeof id === "number" && !Number.isNaN(id)) {
navigate(`/plugin/${id}`);
}
}
}
}, [isSuccess]);

const handleUsernameChange = (
_event: React.FormEvent<HTMLInputElement>,
value: string,
) => {
setUsername(value);
};

const handlePasswordChange = (
_event: React.FormEvent<HTMLInputElement>,
value: string,
) => {
setPassword(value);
};

const onRememberMeClick = () => {
setIsRememberMeChecked(!isRememberMeChecked);
};

const onLoginButtonClick = (
event: React.MouseEvent<HTMLButtonElement, MouseEvent>,
) => {
event.preventDefault();
setIsValidUsername(!!username);
setIsValidPassword(!!password);
setShowHelperText(!username || !password);

if (username && password) {
mutate();
}
};

const loginForm = (
<LoginForm
showHelperText={showHelperText}
helperText="Invalid login credentials."
helperTextIcon={<ExclamationCircleIcon />}
usernameLabel="Username"
usernameValue={username}
onChangeUsername={handleUsernameChange}
isValidUsername={isValidUsername}
passwordLabel="Password"
passwordValue={password}
onChangePassword={handlePasswordChange}
isValidPassword={isValidPassword}
rememberMeLabel="Keep me logged in."
isRememberMeChecked={isRememberMeChecked}
onChangeRememberMe={onRememberMeClick}
onLoginButtonClick={onLoginButtonClick}
loginButtonLabel="Log in"
/>
);

return (
<WrapperConnect>
<Form isWidthLimited>
<FormGroup label="Enter username" isRequired>
<TextInput
id="username"
name="username"
value={username}
type="text"
onChange={(_e, value) => setUsername(value)}
/>
</FormGroup>
<FormGroup label="Enter password" isRequired>
<TextInput
id="password"
name="password"
value={password}
type="password"
onChange={(_e, value) => setPassword(value)}
/>
</FormGroup>
<ActionGroup>
<Button
onClick={() => {
handleSave();
}}
isDisabled={!(username && password)}
variant="primary"
>
Submit
</Button>
</ActionGroup>
</Form>
<LoginPage
footerListVariants={ListVariant.inline}
backgroundImgSrc="/assets/images/pfbg-icon.svg"
textContent="You can now install plugins from our public servers with the click of a button. All you need are the credentials for your admin account"
loginTitle="Admin Login"
loginSubtitle="Enter your admin credentials."
>
{loginForm}
<div
style={{
marginTop: "1rem",
}}
>
{isPending && <SpinContainer title="Installing the plugin..." />}
{isError && <Alert type="error" description={error.message} />}
{isSuccess && (
<Alert
type="success"
description="Plugin installed successfully..."
/>
)}
</div>
</LoginPage>
</WrapperConnect>
);
};
Expand Down
20 changes: 14 additions & 6 deletions src/components/SinglePlugin/PluginCatalogComponents.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ import {
Form,
TextInput,
} from "@patternfly/react-core";
import { Alert } from "antd";
import { Alert, Spin } from "antd";
import { UserAltIcon, CheckCircleIcon } from "@patternfly/react-icons";
import { PluginMeta, Plugin, PluginInstance } from "@fnndsc/chrisapi";
import { redirect, useNavigate } from "react-router";
Expand Down Expand Up @@ -455,6 +455,7 @@ export const HeaderSidebar = ({
<div className="plugin-body-side-col">
{
<Modal
aria-label="Admin credentials"
variant="small"
isOpen={installModal}
onClose={() => setInstallModal(!installModal)}
Expand All @@ -478,8 +479,9 @@ export const HeaderSidebar = ({
}}
isDisabled={!value}
variant="primary"
icon={handleInstallMutation.isPending && <Spin />}
>
Submit
Take me to my website
</Button>
<Button
onClick={() => setInstallModal(!installModal)}
Expand All @@ -491,16 +493,23 @@ export const HeaderSidebar = ({
</Form>
</Modal>
}

<div className="plugin-body-detail-section">
<Button
onClick={() => {
setInstallModal(!installModal);
}}
icon={handleInstallMutation.isPending && <Spin />}
>
Install this plugin on your server
</Button>
{/*
<p>
</div>
<div className="plugin-body-detail-section">
<p
style={{
marginTop: "1rem",
}}
>
Copy and Paste the URL below into your ChRIS Admin Dashboard to
install this plugin.
</p>
Expand All @@ -509,8 +518,7 @@ export const HeaderSidebar = ({
value={
parameterPayload?.url ? parameterPayload.url : "Fetching the url..."
}
/>
*/}
/>
</div>
<div className="plugin-body-detail-section">
<h4>Repository</h4>
Expand Down
8 changes: 8 additions & 0 deletions src/components/Wrapper/Sidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,14 @@ const Sidebar: React.FC<AllProps> = ({
{renderLink("/dataset", "Volume View", "dataset")}
</NavItem>

{/*
!isEmpty(import.meta.env.VITE_CHRIS_STORE_URL) && (
<NavItem itemId="store" isActive={sidebarActiveItem === "store"}>
{renderLink("/store", "Explore", "store")}
</NavItem>
)}
*/}

{!isEmpty(import.meta.env.VITE_CHRIS_STORE_URL) && (
<NavItem itemId="store" isActive={sidebarActiveItem === "store"}>
{renderLink("/store", "Explore", "store")}
Expand Down

0 comments on commit 44071b0

Please sign in to comment.