Skip to content

Commit

Permalink
feat: users table (#48)
Browse files Browse the repository at this point in the history
Co-authored-by: Guillaume Belanger <[email protected]>
  • Loading branch information
kayra1 and gruyaume committed Aug 1, 2024
1 parent 79891c5 commit 8c3383f
Show file tree
Hide file tree
Showing 15 changed files with 716 additions and 208 deletions.
93 changes: 18 additions & 75 deletions ui/src/app/aside.tsx
Original file line number Diff line number Diff line change
@@ -1,85 +1,28 @@
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"
import { useAuth } from "./auth/authContext"

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);
}
};
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 >
)
extraData: any
setExtraData: Dispatch<SetStateAction<any>>
}

function SubmitCSR({ csrText, onClickFunc }: { csrText: string, onClickFunc: any }) {
let csrIsValid = false
try {
extractCSR(csrText.trim())
csrIsValid = true
}
catch { }
export const AsideContext = createContext<AsideContextType>({
isOpen: false,
setIsOpen: () => { },

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>
extraData: null,
setExtraData: () => { },
})

export function Aside({ FormComponent }: { FormComponent: React.ComponentType<any> }) {
const auth = useAuth()
const asideContext = useContext(AsideContext)
return (
<>
{validationComponent}
{buttonComponent}
</>
<aside className={"l-aside" + (auth.user && asideContext.isOpen ? "" : " is-collapsed")} id="aside-panel" aria-label="aside-panel" >
<FormComponent />
</aside >
)
}
}
80 changes: 80 additions & 0 deletions ui/src/app/certificate_requests/asideForm.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
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 CertificateRequestsAsidePanel(): JSX.Element {
const asideContext = useContext(AsideContext)
const [cookies, setCookie, removeCookie] = useCookies(['user_token']);
const queryClient = useQueryClient()
const mutation = useMutation(postCSR, {
onSuccess: () => {
asideContext.setIsOpen(false)
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}
</>
)
}
8 changes: 4 additions & 4 deletions ui/src/app/certificate_requests/row.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { useState, Dispatch, SetStateAction, useEffect, useRef } from "react"
import { UseMutationResult, useMutation, useQueryClient } from "react-query"
import { extractCSR, extractCert } from "../utils"
import { RequiredParams, deleteCSR, rejectCSR, revokeCertificate } from "../queries"
import { RequiredCSRParams, deleteCSR, rejectCSR, revokeCertificate } from "../queries"
import { ConfirmationModal, SubmitCertificateModal, SuccessNotification } from "./components"
import "./../globals.scss"
import { useCookies } from "react-cookie"
Expand Down Expand Up @@ -43,7 +43,7 @@ export default function Row({ id, csr, certificate, ActionMenuExpanded, setActio
const revokeMutation = useMutation(revokeCertificate, {
onSuccess: () => queryClient.invalidateQueries('csrs')
})
const mutationFunc = (mutation: UseMutationResult<any, unknown, RequiredParams, unknown>, params: RequiredParams) => {
const mutationFunc = (mutation: UseMutationResult<any, unknown, RequiredCSRParams, unknown>, params: RequiredCSRParams) => {
mutation.mutate(params)
}

Expand Down Expand Up @@ -148,8 +148,8 @@ export default function Row({ id, csr, certificate, ActionMenuExpanded, setActio
aria-controls="action-menu"
aria-expanded={ActionMenuExpanded == id ? "true" : "false"}
aria-haspopup="true"
onClick={toggleActionMenu}
onBlur={toggleActionMenu}>
onClick={() => setActionMenuExpanded(id)}
onBlur={() => setActionMenuExpanded(0)}>
<i className="p-icon--menu p-contextual-menu__indicator"></i>
</button>
{successNotification && <SuccessNotification successMessage={successNotification} />}
Expand Down
100 changes: 0 additions & 100 deletions ui/src/app/change_password/page.tsx

This file was deleted.

9 changes: 6 additions & 3 deletions ui/src/app/login.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
import { useState } from "react";
import { useContext, useState } from "react";
import { useAuth } from "./auth/authContext";
import { useCookies } from "react-cookie";
import { useRouter } from "next/navigation";
import { ChangePasswordModal, ChangePasswordModalContext, ChangePasswordModalData } from "./users/components";



export function AccountTab() {
const router = useRouter()
const [cookies, setCookie, removeCookie] = useCookies(['user_token']);
const [menuOpen, setMenuOpen] = useState<boolean>(false)
const changePasswordModalContext = useContext(ChangePasswordModalContext)
const authDetails = useAuth()
return (
<>
Expand All @@ -21,7 +24,7 @@ export function AccountTab() {
<i className="p-icon--menu"></i>
<span className="p-contextual-menu__dropdown" id="menu-3" aria-hidden={!menuOpen} style={{ bottom: "40px" }}>
<span className="p-contextual-menu__group">
<button className="p-contextual-menu__link" onMouseDown={() => router.push("/change_password")}>Change Password</button>
<button className="p-contextual-menu__link" onMouseDown={() => changePasswordModalContext.setModalData({ "id": authDetails.user ? authDetails.user.id.toString() : "", "username": authDetails.user ? authDetails.user.username : "" })}>Change Password</button>
<button className="p-contextual-menu__link" onMouseDown={() => removeCookie("user_token")}>Log Out</button>
</span>
</span>
Expand Down
6 changes: 6 additions & 0 deletions ui/src/app/nav.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { expect, describe, it, vi } from "vitest";
import { render, fireEvent, screen } from '@testing-library/react'
import Navigation from "./nav";
import { CertificateRequestsTable } from "./certificate_requests/table";
import { User } from "./types";

vi.mock('next/navigation', () => ({
usePathname: () => {
Expand All @@ -13,6 +14,11 @@ vi.mock('next/navigation', () => ({
}
}
}));
vi.mock('./auth/authContext', () => ({
useAuth: () => {
return { "user": {"id": 0, "username": "adman" } as User}
}
}))

describe('Navigation', () => {
it('should open aside when clicking button', () => {
Expand Down
Loading

0 comments on commit 8c3383f

Please sign in to comment.