Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: users table #48

Merged
merged 12 commits into from
Aug 1, 2024
Merged
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
Loading