Skip to content

Commit

Permalink
aside and form rework
Browse files Browse the repository at this point in the history
  • Loading branch information
kayra1 committed Jul 29, 2024
1 parent de4ca33 commit 09af802
Show file tree
Hide file tree
Showing 6 changed files with 208 additions and 93 deletions.
85 changes: 10 additions & 75 deletions ui/src/app/aside.tsx
Original file line number Diff line number Diff line change
@@ -1,85 +1,20 @@
import { SetStateAction, Dispatch, useState, createContext, ChangeEvent } from "react"
import { useMutation, useQueryClient } from "react-query";
import { postCSR } from "./queries";
import { extractCSR } from "./utils";
import { useCookies } from "react-cookie";
import { SetStateAction, Dispatch, createContext, useContext, ComponentType } from "react"

type AsideContextType = {
isOpen: boolean,
setIsOpen: Dispatch<SetStateAction<boolean>>
}
export const AsideContext = createContext<AsideContextType>({ isOpen: false, setIsOpen: () => { } });

export function Aside({ isOpen, setIsOpen }: { isOpen: boolean, setIsOpen: Dispatch<SetStateAction<boolean>> }) {
const [cookies, setCookie, removeCookie] = useCookies(['user_token']);
const queryClient = useQueryClient()
const mutation = useMutation(postCSR, {
onSuccess: () => {
queryClient.invalidateQueries('csrs')
},
})
const [CSRPEMString, setCSRPEMString] = useState<string>("")
const handleTextChange = (event: ChangeEvent<HTMLTextAreaElement>) => {
setCSRPEMString(event.target.value);
}
const handleFileChange = (event: ChangeEvent<HTMLInputElement>) => {
const file = event.target.files?.[0]
if (file) {
const reader = new FileReader();
reader.onload = (e: ProgressEvent<FileReader>) => {
if (e.target) {
if (e.target.result) {
setCSRPEMString(e.target.result.toString());
}
}
};
reader.readAsText(file);
}
};
export const AsideContext = createContext<AsideContextType>({
isOpen: false,
setIsOpen: () => { },
});

export function Aside({ FormComponent, formProps }: { FormComponent: React.ComponentType<any>, formProps: any }) {
const asideContext = useContext(AsideContext)
return (
<aside className={"l-aside" + (isOpen ? "" : " is-collapsed")} id="aside-panel" aria-label="aside-panel" >
<div className="p-panel">
<div className="p-panel__header">
<h4 className="p-panel__title">Add a New Certificate Request</h4>
<div className="p-panel__controls">
<button onClick={() => setIsOpen(false)} className="p-button--base u-no-margin--bottom has-icon"><i className="p-icon--close"></i></button>
</div>
</div>
<div className="p-panel__content">
<form className="p-form p-form--stacked">
<div className="p-form__group row">
<label htmlFor="textarea">
Enter or upload the CSR in PEM format below
</label>
<textarea id="csr-textarea" name="textarea" rows={10} placeholder="-----BEGIN CERTIFICATE REQUEST-----" onChange={handleTextChange} value={CSRPEMString} />
</div>
<div className="p-form__group row">
<input type="file" name="upload" accept=".pem,.csr" onChange={handleFileChange}></input>
</div>
<div className="p-form__group row">
<SubmitCSR csrText={CSRPEMString} onClickFunc={() => mutation.mutate({ authToken: cookies.user_token, csr: CSRPEMString })} />
</div>
</form>
</div>
</div >
<aside className={"l-aside" + (asideContext.isOpen ? "" : " is-collapsed")} id="aside-panel" aria-label="aside-panel" >
<FormComponent {...formProps} />
</aside >
)
}

function SubmitCSR({ csrText, onClickFunc }: { csrText: string, onClickFunc: any }) {
let csrIsValid = false
try {
extractCSR(csrText.trim())
csrIsValid = true
}
catch { }

const validationComponent = csrText == "" ? <></> : csrIsValid ? <div><i className="p-icon--success"></i>Valid CSR</div> : <div><i className="p-icon--error"></i>Invalid CSR</div>
const buttonComponent = csrIsValid ? <button className="p-button--positive u-float-right" name="submit" onClick={onClickFunc} >Submit</button> : <button className="p-button--positive u-float-right" name="submit" disabled={true} onClick={onClickFunc} >Submit</button>
return (
<>
{validationComponent}
{buttonComponent}
</>
)
}
79 changes: 79 additions & 0 deletions ui/src/app/certificate_requests/asideForm.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import { useMutation, useQueryClient } from "react-query";
import { extractCSR } from "../utils";
import { useCookies } from "react-cookie";
import { postCSR } from "../queries";
import { ChangeEvent, useContext, useState } from "react";
import { AsideContext } from "../aside";

export default function UploadCSRAsidePanel(): JSX.Element {
const asideContext = useContext(AsideContext)
const [cookies, setCookie, removeCookie] = useCookies(['user_token']);
const queryClient = useQueryClient()
const mutation = useMutation(postCSR, {
onSuccess: () => {
queryClient.invalidateQueries('csrs')
},
})
const [CSRPEMString, setCSRPEMString] = useState<string>("")
const handleTextChange = (event: ChangeEvent<HTMLTextAreaElement>) => {
setCSRPEMString(event.target.value);
}
const handleFileChange = (event: ChangeEvent<HTMLInputElement>) => {
const file = event.target.files?.[0]
if (file) {
const reader = new FileReader();
reader.onload = (e: ProgressEvent<FileReader>) => {
if (e.target) {
if (e.target.result) {
setCSRPEMString(e.target.result.toString());
}
}
};
reader.readAsText(file);
}
};
return (
<div className="p-panel" >
<div className="p-panel__header">
<h4 className="p-panel__title">Add a New Certificate Request</h4>
<div className="p-panel__controls">
<button onClick={() => asideContext.setIsOpen(false)} className="p-button--base u-no-margin--bottom has-icon"><i className="p-icon--close"></i></button>
</div>
</div>
<div className="p-panel__content">
<form className="p-form p-form--stacked">
<div className="p-form__group row">
<label htmlFor="textarea">
Enter or upload the CSR in PEM format below
</label>
<textarea id="csr-textarea" name="textarea" rows={10} placeholder="-----BEGIN CERTIFICATE REQUEST-----" onChange={handleTextChange} value={CSRPEMString} />
</div>
<div className="p-form__group row">
<input type="file" name="upload" accept=".pem,.csr" onChange={handleFileChange}></input>
</div>
<div className="p-form__group row">
<SubmitCSR csrText={CSRPEMString} onClickFunc={() => mutation.mutate({ authToken: cookies.user_token, csr: CSRPEMString })} />
</div>
</form>
</div>
</div >
)
}

function SubmitCSR({ csrText, onClickFunc }: { csrText: string, onClickFunc: any }) {
let csrIsValid = false
try {
extractCSR(csrText.trim())
csrIsValid = true
}
catch { }

const validationComponent = csrText == "" ? <></> : csrIsValid ? <div><i className="p-icon--success"></i>Valid CSR</div> : <div><i className="p-icon--error"></i>Invalid CSR</div>
const buttonComponent = csrIsValid ? <button className="p-button--positive u-float-right" name="submit" onClick={onClickFunc} >Submit</button> : <button className="p-button--positive u-float-right" name="submit" disabled={true} onClick={onClickFunc} >Submit</button>
return (
<>
{validationComponent}
{buttonComponent}
</>
)
}
38 changes: 22 additions & 16 deletions ui/src/app/nav.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import { Aside, AsideContext } from "./aside";
import { AccountTab } from "./login"
import { usePathname } from "next/navigation";
import { useAuth } from "./auth/authContext";
import UploadCSRAsidePanel from "./certificate_requests/asideForm";
import UploadUserAsidePanel from "./users/asideForm";

export function SideBar({ activePath, sidebarVisible, setSidebarVisible }: { activePath: string, sidebarVisible: boolean, setSidebarVisible: Dispatch<SetStateAction<boolean>> }) {
const auth = useAuth()
Expand Down Expand Up @@ -103,25 +105,29 @@ export default function Navigation({
const shouldRenderNavigation = !noNavRoutes.includes(activePath);
const [sidebarVisible, setSidebarVisible] = useState<boolean>(true)
const [asideOpen, setAsideOpen] = useState<boolean>(false)
let asideForm = UploadCSRAsidePanel
if (activePath == "/users") {
asideForm = UploadUserAsidePanel
}
return (
<QueryClientProvider client={queryClient}>
<div className="l-application" role="presentation">
{
shouldRenderNavigation ? (
<>
<TopBar setSidebarVisible={setSidebarVisible} />
<SideBar activePath={activePath} sidebarVisible={sidebarVisible} setSidebarVisible={setSidebarVisible} />
</>
) : (
<></>
)
}
<main className="l-main">
<AsideContext.Provider value={{ isOpen: asideOpen, setIsOpen: setAsideOpen }}>
{children}
</AsideContext.Provider>
</main>
<Aside isOpen={asideOpen} setIsOpen={setAsideOpen} />
<AsideContext.Provider value={{ isOpen: asideOpen, setIsOpen: setAsideOpen }}>
{
shouldRenderNavigation ? (
<>
<TopBar setSidebarVisible={setSidebarVisible} />
<SideBar activePath={activePath} sidebarVisible={sidebarVisible} setSidebarVisible={setSidebarVisible} />
</>
) : (
<></>
)
}
<main className="l-main">
{children}
</main>
<Aside FormComponent={asideForm} formProps={null} />
</AsideContext.Provider>
</div >
</QueryClientProvider>
)
Expand Down
12 changes: 12 additions & 0 deletions ui/src/app/queries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -142,4 +142,16 @@ export async function deleteUser(params: { authToken: string, id: string }) {
throw new Error(`${response.status}: ${HTTPStatus(response.status)}`)
}
return response.json()
}

export async function postUser(userForm: { authToken: string, username: string, password: string }) {
const response = await fetch("/api/v1/accounts", {
method: "POST",
body: JSON.stringify({ "username": userForm.username, "password": userForm.password })
})
const responseText = await response.text()
if (!response.ok) {
throw new Error(`${response.status}: ${HTTPStatus(response.status)}. ${responseText}`)
}
return responseText
}
83 changes: 83 additions & 0 deletions ui/src/app/users/asideForm.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import { useMutation, useQueryClient } from "react-query";
import { extractCSR } from "../utils";
import { useCookies } from "react-cookie";
import { postUser } from "../queries";
import { ChangeEvent, useContext, useState } from "react";
import { AsideContext } from "../aside";
import { useAuth } from "../auth/authContext";

export default function UploadUserAsidePanel() {
const asideContext = useContext(AsideContext)
const auth = useAuth()
const queryClient = useQueryClient()
const mutation = useMutation(postUser, {
onSuccess: () => {
queryClient.invalidateQueries('users')
setErrorText("")
},
onError: (e: Error) => {
setErrorText(e.message)
}
})
const [username, setUsername] = useState<string>("")
const [password1, setPassword1] = useState<string>("")
const [password2, setPassword2] = useState<string>("")
const passwordsMatch = password1 === password2
const [errorText, setErrorText] = useState<string>("")
const handleUsernameChange = (event: ChangeEvent<HTMLInputElement>) => { setUsername(event.target.value) }
const handlePassword1Change = (event: ChangeEvent<HTMLInputElement>) => { setPassword1(event.target.value) }
const handlePassword2Change = (event: ChangeEvent<HTMLInputElement>) => { setPassword2(event.target.value) }
return (
<div className="p-panel" >
<div className="p-panel__header">
<h4 className="p-panel__title">Add a New User</h4>
<div className="p-panel__controls">
<button onClick={() => asideContext.setIsOpen(false)} className="p-button--base u-no-margin--bottom has-icon"><i className="p-icon--close"></i></button>
</div>
</div>
<div className="p-panel__content">
<form className={"p-form-validation " + (!passwordsMatch ? "is-error" : "")}>
<div className="p-form__group row">
<label className="p-form__label">User Name</label>
<input type="text" id="InputUsername" name="InputUsername" onChange={handleUsernameChange} />
<label className="p-form__label">Password</label>
<input className="p-form-validation__input" type="password" id="password1" name="password" placeholder="******" autoComplete="current-password" required={true} onChange={handlePassword1Change} />
<p className="p-form-help-text">
Password must have 8 or more characters, must include at least one capital letter, one lowercase letter, and either a number or a symbol.
</p>
<label htmlFor="p-form__label">Password Again</label>
<input className="p-form-validation__input" type="password" id="InputPassword" name="password2" placeholder="******" autoComplete="current-password" onChange={handlePassword2Change} />
{!passwordsMatch && <p className="p-form-validation__message">Passwords do not match</p>}
{errorText &&
<div className="p-notification--negative">
<div className="p-notification__content">
<h5 className="p-notification__title">Error</h5>
<p className="p-notification__message">{errorText.split("error: ")}</p>
</div>
</div>
}
{!passwordsMatch ? (
<>
<button type="submit" name="submit" disabled={true}>Submit</button>
</>
) : (
<button type="submit" name="submit" onClick={(event) => { event.preventDefault(); mutation.mutate({ authToken: (auth.user ? auth.user.authToken : ""), username: username, password: password1 }) }}>Submit</button>
)
}
</div>
</form>
</div>
</div >
)
}

// function SubmitUser({ csrText, onClickFunc }: { csrText: string, onClickFunc: any }) {
// const validationComponent = csrText == "" ? <></> : csrIsValid ? <div><i className="p-icon--success"></i>Valid CSR</div> : <div><i className="p-icon--error"></i>Invalid CSR</div>
// const buttonComponent = csrIsValid ? <button className="p-button--positive u-float-right" name="submit" onClick={onClickFunc} >Submit</button> : <button className="p-button--positive u-float-right" name="submit" disabled={true} onClick={onClickFunc} >Submit</button>
// return (
// <>
// {validationComponent}
// {buttonComponent}
// </>
// )
// }
4 changes: 2 additions & 2 deletions ui/src/app/users/row.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,9 @@ export default function Row({ id, username }: rowProps) {
<td className="" aria-label="username">{username}</td>
<td className="" aria-label="delete-button">
{id == 1 ?
<button className="p-button--negative has-icon" onClick={handleDelete} disabled={true}><i className="p-icon--error"></i></button>
<button className="p-button--negative has-icon is-dense" onClick={handleDelete} disabled={true}><i className="p-icon--error"></i></button>
:
<button className="p-button--negative has-icon" onClick={handleDelete}><i className="p-icon--error"></i></button>
<button className="p-button--negative has-icon is-dense" onClick={handleDelete}><i className="p-icon--error"></i></button>
}
</td>
{confirmationModalData != null && <ConfirmationModal modalData={confirmationModalData} setModalData={setConfirmationModalData} />}
Expand Down

0 comments on commit 09af802

Please sign in to comment.