diff --git a/src/components/direct/hisp/HISPPortal.tsx b/src/components/direct/hisp/HISPPortal.tsx index 83f6c73e..9592003f 100644 --- a/src/components/direct/hisp/HISPPortal.tsx +++ b/src/components/direct/hisp/HISPPortal.tsx @@ -9,6 +9,7 @@ import IMAP from './IMAPTab' import POP3 from './POP3Tab' import XDR from './XDRTab' import ValidationResults from './ValidationResultsTab' +import ProfileProvider from './provider' const HISPPortal = () => { const hispTabs: TabInputs[] = [ @@ -41,7 +42,9 @@ const HISPPortal = () => { } /> {/* Main Content */} - + + + ) } diff --git a/src/components/direct/hisp/IMAPTab.tsx b/src/components/direct/hisp/IMAPTab.tsx index 1b91b10e..8bd12df6 100644 --- a/src/components/direct/hisp/IMAPTab.tsx +++ b/src/components/direct/hisp/IMAPTab.tsx @@ -17,12 +17,15 @@ import * as React from 'react' import testCases from '@/assets/SMTPTestCases' import _ from 'lodash' import TestFilter from './TestFilter' +import { useContext } from 'react' +import { ProfileContext } from './context' const IMAP = () => { const [option, setOption] = React.useState('') const imapTestCases = testCases.tests.filter((test) => test.protocol === 'imap') const imapTestCasesSender = imapTestCases.filter((test) => test.sutRole === 'sender' && test.sutHisp) const imapTestCasesReceiver = imapTestCases.filter((test) => test.sutRole === 'receiver' && test.sutHisp) + const { hostname, email, password, tls, username } = useContext(ProfileContext) const handleChange = (event: SelectChangeEvent) => { setOption(event.target.value as string) } @@ -86,7 +89,14 @@ const IMAP = () => { {imapTestCasesSender.map((test, i) => { return ( - + ) })} @@ -98,7 +108,15 @@ const IMAP = () => { {imapTestCasesReceiver.map((test, i) => { return ( - + ) })} diff --git a/src/components/direct/hisp/MessageTrackingTab.tsx b/src/components/direct/hisp/MessageTrackingTab.tsx index d57e1b6e..eefe46e5 100644 --- a/src/components/direct/hisp/MessageTrackingTab.tsx +++ b/src/components/direct/hisp/MessageTrackingTab.tsx @@ -20,12 +20,15 @@ import testCases from '@/assets/SMTPTestCases' import DragandDropFile from '@/components/shared/DragandDropFile' import HelpIcon from '@mui/icons-material/Help' import TestFilter from './TestFilter' +import { useContext } from 'react' +import { ProfileContext } from './context' const MessageTracking = () => { const [option, setOption] = React.useState('') const mu2TestCases = testCases.tests.filter((test) => test.protocol === 'mu2') const mu2TestCasesSender = mu2TestCases.filter((test) => test.sutRole === 'sender' && test.sutHisp) const mu2TestCasesReceiver = mu2TestCases.filter((test) => test.sutRole === 'receiver' && test.sutHisp) + const { hostname, email, password, tls, username } = useContext(ProfileContext) const handleChange = (event: SelectChangeEvent) => { setOption(event.target.value as string) } @@ -102,7 +105,14 @@ const MessageTracking = () => { {mu2TestCasesSender.map((test, i) => { return ( - + ) })} @@ -113,7 +123,15 @@ const MessageTracking = () => { {mu2TestCasesReceiver.map((test, i) => { return ( - + ) })} diff --git a/src/components/direct/hisp/POP3Tab.tsx b/src/components/direct/hisp/POP3Tab.tsx index 55ff0709..16319864 100644 --- a/src/components/direct/hisp/POP3Tab.tsx +++ b/src/components/direct/hisp/POP3Tab.tsx @@ -17,12 +17,16 @@ import * as React from 'react' import testCases from '@/assets/SMTPTestCases' import _ from 'lodash' import TestFilter from './TestFilter' +import { useContext } from 'react' +import { ProfileContext } from './context' const POP3 = () => { const [option, setOption] = React.useState('') const popTestCases = testCases.tests.filter((test) => test.protocol === 'pop') const popTestCasesSender = popTestCases.filter((test) => test.sutRole === 'sender' && test.sutHisp) const popTestCasesReceiver = popTestCases.filter((test) => test.sutRole === 'receiver' && test.sutHisp) + const { hostname, email, password, tls, username } = useContext(ProfileContext) + const handleChange = (event: SelectChangeEvent) => { setOption(event.target.value as string) } @@ -86,7 +90,14 @@ const POP3 = () => { {popTestCasesSender.map((test, i) => { return ( - + ) })} @@ -98,7 +109,15 @@ const POP3 = () => { {popTestCasesReceiver.map((test, i) => { return ( - + ) })} diff --git a/src/components/direct/hisp/SMTPTab.tsx b/src/components/direct/hisp/SMTPTab.tsx index 2c7c8126..1b217552 100644 --- a/src/components/direct/hisp/SMTPTab.tsx +++ b/src/components/direct/hisp/SMTPTab.tsx @@ -16,12 +16,15 @@ import palette from '@/styles/palette' import * as React from 'react' import testCases from '@/assets/SMTPTestCases' import TestFilter from './TestFilter' +import { useContext } from 'react' +import { ProfileContext } from './context' const SMTP = () => { const [option, setOption] = React.useState('') const smtpTestCases = testCases.tests.filter((test) => test.protocol === 'smtp') const smtpTestCasesSender = smtpTestCases.filter((test) => test.sutRole === 'sender' && test.sutHisp) const smtpTestCasesReceiver = smtpTestCases.filter((test) => test.sutRole === 'receiver' && test.sutHisp) + const { hostname, email, password, tls, username } = useContext(ProfileContext) const handleChange = (event: SelectChangeEvent) => { setOption(event.target.value as string) } @@ -85,7 +88,14 @@ const SMTP = () => { {smtpTestCasesSender.map((test, i) => { return ( - + ) })} @@ -96,7 +106,15 @@ const SMTP = () => { {smtpTestCasesReceiver.map((test, i) => { return ( - + ) })} diff --git a/src/components/direct/hisp/ValidationResultsTab.tsx b/src/components/direct/hisp/ValidationResultsTab.tsx index 59015da7..509c197c 100644 --- a/src/components/direct/hisp/ValidationResultsTab.tsx +++ b/src/components/direct/hisp/ValidationResultsTab.tsx @@ -1,41 +1,66 @@ import * as React from 'react' import { Box, Typography, Container } from '@mui/material' import ProfilesCard from './ProfilesCard' +import { useSession } from 'next-auth/react' +import PageAlertBox from '@/components/shared/PageAlertBox' +import { useEffect } from 'react' const ValidationResults = () => { + const { data: session, status } = useSession() + const [profiles, setProfiles] = React.useState([]) + + useEffect(() => { + if (status === 'authenticated') { + //const response = await fetchUserProfiles() + console.log(`DEBUG - Fetching profiles for user: ${session.user.email}`) + } + }, [status]) + return ( - - - - Below are the different profiles you are aligned to. Select one to see the validation results. - + <> + {status !== 'authenticated' ? ( + + + + ) : ( + + + + Below are the different profiles you are aligned to. Select one to see the validation results. + - - - - - - - - - + + + + + + + + + + )} + ) } diff --git a/src/components/direct/hisp/actions.ts b/src/components/direct/hisp/actions.ts new file mode 100644 index 00000000..8ede542b --- /dev/null +++ b/src/components/direct/hisp/actions.ts @@ -0,0 +1,112 @@ +'use server' + +import { authOptions } from '@/lib/auth' +import { StringIterator } from 'lodash' +import { getServerSession } from 'next-auth' + +const ETT_API_URL = process.env.ETT_API_URL +export interface Profile { + hostname: string + email: string + username: string + password: string + istls: boolean + profilename: string + profileid: string +} + +export async function saveProfile(data: Profile) { + console.log(`'Saving profile:', ${JSON.stringify(data)}`) + const { hostname, email, username, password, istls, profilename } = data + const session = await getServerSession(authOptions) + const jsessionid = session?.user?.jsessionid ?? '' + const ettAPIUrl = `${ETT_API_URL}/smtpProfile` + try { + const response = await fetch(ettAPIUrl, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + Cookie: `JSESSIONID=${jsessionid}`, + }, + body: JSON.stringify({ + sutSMTPAddress: hostname, + sutEmailAddress: email, + sutUsername: username, + sutPassword: password, + useTLS: istls, + profileName: profilename, + username: session?.user?.name, + }), + }) + const data = await response.json() + if (!response.ok) { + console.log(`Error: ${data}`) + } + return data + } catch (error: unknown) { + if (error instanceof Error) { + return JSON.stringify({ + status: 500, + success: false, + error: error.message || 'An error occurred', + }) + } + } +} + +export async function deleteProfile(profilename: string) { + console.log(`deleting profile: ${profilename}`) + const session = await getServerSession(authOptions) + const jsessionid = session?.user?.jsessionid ?? '' + const ettAPIUrl = `${ETT_API_URL}/smtpProfile/${profilename}` + try { + const response = await fetch(ettAPIUrl, { + method: 'DELETE', + headers: { + 'Content-Type': 'application/json', + Cookie: `JSESSIONID=${jsessionid}`, + }, + }) + const data = await response.json() + if (!response.ok) { + console.log(`Error: ${data}`) + } + return data + } catch (error: unknown) { + if (error instanceof Error) { + return JSON.stringify({ + status: 500, + success: false, + error: error.message || 'An error occurred', + }) + } + } +} + +export async function fetchProfiles() { + const session = await getServerSession(authOptions) + const jsessionid = session?.user?.jsessionid ?? '' + const ettAPIUrl = `${ETT_API_URL}/smtpProfile` + try { + const response = await fetch(ettAPIUrl, { + method: 'GET', + headers: { + 'Content-Type': 'application/json', + Cookie: `JSESSIONID=${jsessionid}`, + }, + }) + const data = await response.json() + if (!response.ok) { + console.log(`Error: ${data}`) + } + return data + } catch (error: unknown) { + if (error instanceof Error) { + return JSON.stringify({ + status: 500, + success: false, + error: error.message || 'An error occurred', + }) + } + } +} diff --git a/src/components/direct/hisp/context/index.tsx b/src/components/direct/hisp/context/index.tsx new file mode 100644 index 00000000..db49f4a8 --- /dev/null +++ b/src/components/direct/hisp/context/index.tsx @@ -0,0 +1,36 @@ +import { createContext } from 'react' + +interface ProfileContextType { + profileid?: string + profilename: string + hostname: string + email: string + username: string + password: string + tls: boolean + setProfileid: (profileid: string) => void + setHostname: (hostname: string) => void + setEmail: (email: string) => void + setUsername: (username: string) => void + setPassword: (password: string) => void + setTls: (tls: boolean) => void + setProfilename: (profilename: string) => void +} +const defaultValue: ProfileContextType = { + profileid: '', + hostname: '', + email: '', + username: '', + password: '', + tls: false, + profilename: 'Default Profile', + setProfileid: () => {}, + setHostname: () => {}, + setEmail: () => {}, + setUsername: () => {}, + setPassword: () => {}, + setTls: () => {}, + setProfilename: () => {}, +} + +export const ProfileContext = createContext(defaultValue) diff --git a/src/components/direct/hisp/provider/index.tsx b/src/components/direct/hisp/provider/index.tsx new file mode 100644 index 00000000..e1885519 --- /dev/null +++ b/src/components/direct/hisp/provider/index.tsx @@ -0,0 +1,42 @@ +'use client' +import { useState } from 'react' +import { ProfileContext } from '../context' + +import { ReactNode } from 'react' + +interface ProfileProviderProps { + children: ReactNode +} + +export default function ProfileProvider({ children }: ProfileProviderProps) { + const [profileid, setProfileid] = useState('') + const [hostname, setHostname] = useState('') + const [email, setEmail] = useState('') + const [username, setUsername] = useState('') + const [password, setPassword] = useState('') + const [tls, setTls] = useState(false) + const [profilename, setProfilename] = useState('') + + return ( + + {children} + + ) +} diff --git a/src/components/direct/shared/Profile.tsx b/src/components/direct/shared/Profile.tsx index 9b6cab7b..7a0b0a58 100644 --- a/src/components/direct/shared/Profile.tsx +++ b/src/components/direct/shared/Profile.tsx @@ -1,110 +1,284 @@ import palette from '@/styles/palette' -import { Box, TextField, Button, MenuItem, FormGroup, FormControlLabel, Switch } from '@mui/material' +import { + Box, + TextField, + Button, + FormGroup, + FormControlLabel, + Switch, + Select, + MenuItem, + SelectChangeEvent, + LinearProgress, +} from '@mui/material' +import { useContext, useEffect, useState } from 'react' +import { ProfileContext } from '../hisp/context' +import { deleteProfile, fetchProfiles, saveProfile } from '../hisp/actions' +import { useSession } from 'next-auth/react' +import AlertSnackbar from './AlertSnackbar' +import _ from 'lodash' -const dropdown = [ - { - value: 'Default Profile', - label: 'Default Profile', - }, -] +interface Profile { + profileName: string + sutSMTPAddress: string + sutEmailAddress: string + sutUsername: string + sutPassword: string + useTLS: boolean + smtpEdgeProfileID: string +} -interface ProfileProps { - setHostname?: (hostname: string) => void - setEmail?: (email: string) => void - setUsername?: (username: string) => void - setPassword?: (password: string) => void - setTls?: (tls: boolean) => void +const NEWPROFILENAME = '__new__' +const removeProfilesWithNullProfileName = (profiles: Profile[]) => { + return profiles.filter((profile) => profile.profileName !== null) } +const Profile = () => { + const { + setProfileid, + setProfilename, + setHostname, + setEmail, + setUsername, + setPassword, + setTls, + profileid, + profilename, + hostname, + email, + password, + tls, + username, + } = useContext(ProfileContext) + const { data: session, status } = useSession() + + const [profiles, setProfiles] = useState([]) + const [selectedProfileName, setSelectedProfileName] = useState(NEWPROFILENAME) + const [isLoading, setIsLoading] = useState(false) + interface Message { + text: string + severity: 'info' | 'error' | 'success' | 'warning' + } + + const [message, setMessage] = useState({ text: '', severity: 'info' }) + + useEffect(() => { + async function fetchLoggedInUsersProfiles() { + setIsLoading(true) + const loggedInUsersProfiles = await fetchProfiles() + const filteredProfiles = removeProfilesWithNullProfileName(loggedInUsersProfiles) + if (!_.isEmpty(filteredProfiles)) { + setProfiles(filteredProfiles) + setSelectedProfileName(filteredProfiles[0].profileName) + setProfilename(filteredProfiles[0].profileName) + setHostname(filteredProfiles[0].sutSMTPAddress) + setEmail(filteredProfiles[0].sutEmailAddress) + setUsername(filteredProfiles[0].sutUsername) + setPassword(filteredProfiles[0].sutPassword) + setTls(filteredProfiles[0].useTLS) + setProfileid(filteredProfiles[0].smtpEdgeProfileID) + } + setIsLoading(false) + } + if (status === 'authenticated') { + fetchLoggedInUsersProfiles() + } + }, [status, session]) + + const handleSaveProfile = () => { + setIsLoading(true) + saveProfile({ + profileid: profileid || '', + hostname: hostname, + email: email, + username: username, + password: password, + istls: tls, + profilename: profilename, + }).then(async (response) => { + if (response) { + setMessage({ text: `${profilename} saved`, severity: 'success' }) + const loggedInUsersProfiles = await fetchProfiles() + const filteredProfiles = removeProfilesWithNullProfileName(loggedInUsersProfiles) + setProfiles(filteredProfiles) + const savedProfile = filteredProfiles.filter((profile) => profile.profileName === profilename).pop() + if (savedProfile) { + setSelectedProfileName(savedProfile.profileName) + setProfilename(savedProfile.profileName) + setHostname(savedProfile.sutSMTPAddress) + setEmail(savedProfile.sutEmailAddress) + setUsername(savedProfile.sutUsername) + setPassword(savedProfile.sutPassword) + setTls(savedProfile.useTLS) + setProfileid(savedProfile.smtpEdgeProfileID) + } + } else { + setMessage({ text: `Failed to save ${profilename}`, severity: 'error' }) + } + setIsLoading(false) + }) + } -const noop = () => {} + const handleDeleteProfile = () => { + setIsLoading(true) + const profileNameToDelete = selectedProfileName + deleteProfile(profileNameToDelete).then(async (response) => { + if (response) { + setMessage({ text: `${profileNameToDelete} removed`, severity: 'success' }) + const loggedInUsersProfiles = await fetchProfiles() + const filteredProfiles = removeProfilesWithNullProfileName(loggedInUsersProfiles) + setProfiles(filteredProfiles || [{ smtpEdgeProfileID: NEWPROFILENAME } as Profile]) + const lastProfile = _.last(filteredProfiles) + setSelectedProfileName(lastProfile?.profileName || NEWPROFILENAME) + setProfilename(lastProfile?.profileName || '') + setHostname(lastProfile?.sutSMTPAddress || '') + setEmail(lastProfile?.sutEmailAddress || '') + setUsername(lastProfile?.sutUsername || '') + setPassword(lastProfile?.sutPassword || '') + setTls(lastProfile?.useTLS || false) + setProfileid(lastProfile?.smtpEdgeProfileID || NEWPROFILENAME) + } else { + setMessage({ text: `Failed to remove ${profileNameToDelete}`, severity: 'error' }) + } + setIsLoading(false) + }) + } + + const handleProfileChange = async (event: SelectChangeEvent) => { + const selectedProfileName = event.target.value + if (selectedProfileName === NEWPROFILENAME) { + console.log('New Profile') + setSelectedProfileName(selectedProfileName) + setProfilename('') + setHostname('') + setEmail('') + setUsername('') + setPassword('') + setTls(false) + setProfileid('') + return + } + const selectedProfile = profiles.find((profile) => profile.profileName === selectedProfileName) + if (selectedProfile) { + setSelectedProfileName(selectedProfile.profileName) + setProfilename(selectedProfile.profileName) + setHostname(selectedProfile.sutSMTPAddress) + setEmail(selectedProfile.sutEmailAddress) + setUsername(selectedProfile.sutUsername) + setPassword(selectedProfile.sutPassword) + setTls(selectedProfile.useTLS) + setProfileid(selectedProfile.smtpEdgeProfileID) + } + } -const Profile: React.FC = ({ - setHostname = noop, - setEmail = noop, - setUsername = noop, - setPassword = noop, - setTls = noop, -}) => { return ( - - - {dropdown.map((option) => ( - - {option.label} - - ))} - - - - setHostname(e.target.value)} - /> - setEmail(e.target.value)} - /> - - - setUsername(e.target.value)} - /> - setPassword(e.target.value)} - /> - - - setTls(e.target.checked)} />} - label="TLS REQUIRED" - name="tlsRequired" - /> - - - - - - + <> + {isLoading ? ( + + ) : ( + + {!_.isEmpty(profiles) && ( + + )} + + + setHostname(e.target.value)} + /> + setEmail(e.target.value)} + /> + + + setUsername(e.target.value)} + /> + setPassword(e.target.value)} + /> + + + setTls(e.target.checked)} />} + label="TLS REQUIRED" + name="tlsRequired" + /> + + + + + {status === 'authenticated' && ( + <> + setProfilename(e.target.value)} + /> + + + + + + )} + + {!_.isEmpty(message) && ( + setMessage({ text: '', severity: 'info' })} + /> + )} - - - + )} + ) } export default Profile diff --git a/src/components/direct/test-by-criteria/B1Tab.tsx b/src/components/direct/test-by-criteria/B1Tab.tsx index 729bcf98..f570016d 100644 --- a/src/components/direct/test-by-criteria/B1Tab.tsx +++ b/src/components/direct/test-by-criteria/B1Tab.tsx @@ -17,15 +17,13 @@ import palette from '@/styles/palette' import React, { useState } from 'react' import testCases from '../../../assets/SMTPTestCases' import xdrTestCases from '../../../assets/XDRTestCases' +import { useContext } from 'react' +import { ProfileContext } from '../hisp/context' const B1Component = () => { const [option, setOption] = useState('') const [showTestCard, setShowTestCard] = useState(false) - const [hostname, setHostname] = useState('') - const [email, setEmail] = useState('') - const [username, setUsername] = useState('') - const [password, setPassword] = useState('') - const [tlsRequired, setTlsRequired] = useState(false) + const { hostname, email, password, tls, username } = useContext(ProfileContext) const criteriaA = xdrTestCases.filter((testXdr) => testXdr.criteria?.includes('b1-1')) const criteriaB = testCases.tests.filter((test) => test.criteria?.includes('b1-8')) @@ -124,13 +122,7 @@ const B1Component = () => { - + @@ -144,7 +136,7 @@ const B1Component = () => { email={email} username={username} password={password} - tlsRequired={tlsRequired} + tlsRequired={tls} receive={isReceiveOption()} /> diff --git a/src/components/direct/test-by-criteria/H1Tab.tsx b/src/components/direct/test-by-criteria/H1Tab.tsx index efaf294d..75792521 100644 --- a/src/components/direct/test-by-criteria/H1Tab.tsx +++ b/src/components/direct/test-by-criteria/H1Tab.tsx @@ -15,14 +15,12 @@ import Profile from '../shared/Profile' import palette from '@/styles/palette' import TestCard from '../hisp/TestCard' import testCases from '../../../assets/SMTPTestCases' +import { useContext } from 'react' +import { ProfileContext } from '../hisp/context' const H1Component = () => { const [option, setOption] = useState('') - const [hostname, setHostname] = useState('') - const [email, setEmail] = useState('') - const [username, setUsername] = useState('') - const [password, setPassword] = useState('') - const [tlsRequired, setTlsRequired] = useState(false) + const { hostname, email, password, tls, username } = useContext(ProfileContext) const dropdownOptions = [ { @@ -97,13 +95,7 @@ const H1Component = () => { - + @@ -117,7 +109,7 @@ const H1Component = () => { email={email} username={username} password={password} - tlsRequired={tlsRequired} + tlsRequired={tls} receive={false} /> diff --git a/src/components/direct/test-by-criteria/TestByCriteria.tsx b/src/components/direct/test-by-criteria/TestByCriteria.tsx index 606b4d46..8eaa153e 100644 --- a/src/components/direct/test-by-criteria/TestByCriteria.tsx +++ b/src/components/direct/test-by-criteria/TestByCriteria.tsx @@ -7,6 +7,7 @@ import H2Component from './H2Tab' import Link from 'next/link' import styles from '@shared/styles.module.css' import TabsComponent, { TabInputs } from '@/components/shared/TabsComponent' +import ProfileProvider from '../hisp/provider' export interface criteriaProps { selectedTab: string } @@ -33,7 +34,9 @@ const TestByCriteria = ({ selectedTab }: criteriaProps) => { heading={'Test By Criteria'} description={<>New Helper Text} /> - + + + ) }