diff --git a/src/app/api/url-checker.jsx b/src/app/api/url-checker.jsx
new file mode 100644
index 0000000000..d36c7cfe6a
--- /dev/null
+++ b/src/app/api/url-checker.jsx
@@ -0,0 +1,10 @@
+import { useMutation } from 'react-query';
+
+import { useAxios } from './axios';
+
+const usePostUrlChecker = () => {
+ const axios = useAxios();
+ return useMutation(async (data) => axios.post('urls/validate', data));
+};
+
+export default usePostUrlChecker;
diff --git a/src/app/navigation/navigation-top.html b/src/app/navigation/navigation-top.html
index 9480391a80..b470a67f8c 100644
--- a/src/app/navigation/navigation-top.html
+++ b/src/app/navigation/navigation-top.html
@@ -90,6 +90,7 @@
Change Requests
System Maintenance
Dashboard
+ URL Checker
FF4j
diff --git a/src/app/pages/administration/administration.module.js b/src/app/pages/administration/administration.module.js
index f066c6d51b..95ef2ebf2e 100644
--- a/src/app/pages/administration/administration.module.js
+++ b/src/app/pages/administration/administration.module.js
@@ -5,6 +5,7 @@ import ChplLoginPage from './login/login-wrapper';
import ChplReportsWrapper from './reports/reports-wrapper';
import ChplSystemMaintenanceWrapper from './system-maintenance/system-maintenance-wrapper';
import ChplUploadPageWrapper from './upload/upload-page-wrapper';
+import ChplUrlCheckerWrapper from './url-checker/url-checker-wrapper';
import { reactToAngularComponent } from 'services/angular-react-helper';
@@ -39,4 +40,5 @@ angular
.component('chplLoginPageBridge', reactToAngularComponent(ChplLoginPage))
.component('chplReportsWrapperBridge', reactToAngularComponent(ChplReportsWrapper))
.component('chplSystemMaintenanceWrapperBridge', reactToAngularComponent(ChplSystemMaintenanceWrapper))
- .component('chplUploadPageWrapperBridge', reactToAngularComponent(ChplUploadPageWrapper));
+ .component('chplUploadPageWrapperBridge', reactToAngularComponent(ChplUploadPageWrapper))
+ .component('chplUrlCheckerWrapperBridge', reactToAngularComponent(ChplUrlCheckerWrapper));
diff --git a/src/app/pages/administration/administration.state.js b/src/app/pages/administration/administration.state.js
index 206eecf6fd..0d21c519ba 100644
--- a/src/app/pages/administration/administration.state.js
+++ b/src/app/pages/administration/administration.state.js
@@ -100,6 +100,14 @@ const states = [{
title: 'CHPL Administration - Upload',
roles: ['chpl-admin', 'chpl-onc', 'chpl-onc-acb'],
},
+}, {
+ name: 'administration.url-checker',
+ url: '/url-checker',
+ component: 'chplUrlCheckerWrapperBridge',
+ data: {
+ title: 'CHPL Administration - URL Checker',
+ roles: ['chpl-admin', 'chpl-onc', 'chpl-onc-acb'],
+ },
}, {
name: 'login',
url: '/login',
diff --git a/src/app/pages/administration/url-checker/url-checker-wrapper.jsx b/src/app/pages/administration/url-checker/url-checker-wrapper.jsx
new file mode 100644
index 0000000000..5f65283109
--- /dev/null
+++ b/src/app/pages/administration/url-checker/url-checker-wrapper.jsx
@@ -0,0 +1,18 @@
+import React from 'react';
+
+import ChplUrlChecker from './url-checker';
+
+import AppWrapper from 'app-wrapper';
+
+function ChplUrlCheckerWrapper() {
+ return (
+
+
+
+ );
+}
+
+export default ChplUrlCheckerWrapper;
+
+ChplUrlCheckerWrapper.propTypes = {
+};
diff --git a/src/app/pages/administration/url-checker/url-checker.jsx b/src/app/pages/administration/url-checker/url-checker.jsx
new file mode 100644
index 0000000000..935a1d6394
--- /dev/null
+++ b/src/app/pages/administration/url-checker/url-checker.jsx
@@ -0,0 +1,336 @@
+import React, { useContext, useEffect, useState } from 'react';
+import {
+ Box,
+ Button,
+ Card,
+ CardContent,
+ CircularProgress,
+ Container,
+ Typography,
+ makeStyles,
+} from '@material-ui/core';
+import { useSnackbar } from 'notistack';
+import { useFormik } from 'formik';
+import * as yup from 'yup';
+import CancelIcon from '@material-ui/icons/Cancel';
+import CheckCircleIcon from '@material-ui/icons/CheckCircle';
+import VerifiedUserIcon from '@material-ui/icons/VerifiedUser';
+
+import { ChplTextField } from 'components/util';
+import { utilStyles, palette } from 'themes';
+import usePostUrlChecker from 'api/url-checker';
+import { UserContext } from 'shared/contexts';
+
+const useStyles = makeStyles((theme) => ({
+ ...utilStyles,
+ titlePadding: {
+ paddingTop: '16px',
+ paddingBottom: '16px',
+ },
+ titleBackground: {
+ backgroundColor: palette.white,
+ paddingBottom: '16px',
+ marginTop: '-16px',
+ padding: '16px 32px',
+ boxShadow: 'rgb(149 157 165 / 10%) 0 4px 8px',
+ },
+ pageBackground: {
+ backgroundColor: palette.lightGray,
+ minHeight: 'calc(100vh - 64px)',
+ },
+ resultsCard: {
+ width: '32.3%',
+ overflowWrap: 'break-word',
+ [theme.breakpoints.down('sm')]: {
+ width: '100%',
+ },
+ },
+ resultsCardHalf: {
+ width: '49.2%',
+ overflowWrap: 'break-word',
+ [theme.breakpoints.down('sm')]: {
+ width: '100%',
+ },
+ },
+ statusText: {
+ display: 'flex',
+ alignItems: 'center',
+ },
+ resultsContainer: {
+ display: 'flex',
+ flexDirection: 'row',
+ gap: '16px',
+ paddingBottom: '16px',
+ [theme.breakpoints.down('sm')]: {
+ flexDirection: 'column',
+ },
+ },
+}));
+
+const validationSchema = yup.object({
+ url: yup.string()
+ .required('Field is required')
+ .url('Improper format (http://www.example.com)'),
+});
+
+function ChplUrlChecker() {
+ const { hasAnyRole } = useContext(UserContext);
+ const { enqueueSnackbar } = useSnackbar();
+ const { data, isLoading, isSuccess, mutate } = usePostUrlChecker();
+ const [urlCheckResponse, setUrlCheckResponse] = useState(undefined);
+ const classes = useStyles();
+
+ useEffect(() => {
+ if (isLoading || !isSuccess) { return; }
+ setUrlCheckResponse(data.data);
+ }, [data, isLoading, isSuccess]);
+
+ const validate = (urlToValidate) => {
+ setUrlCheckResponse(undefined);
+ mutate(urlToValidate, {
+ onError: () => {
+ enqueueSnackbar('There was an error attempting to check the URL.', {
+ variant: 'error',
+ });
+ },
+ });
+ }
+
+ const formik = useFormik({
+ initialValues: {
+ url: '',
+ },
+ onSubmit: () => {
+ const urlToValidate = {
+ url: formik.values.url,
+ };
+ validate(urlToValidate);
+ },
+ validationSchema,
+ });
+
+ return (
+ <>
+
+
+ URL Checker
+ Validate a URL
+
+
+ }
+ >
+ Validate
+
+
+
+
+
+ {(isLoading || isLoading) && (
+
+
+
+ )}
+ {urlCheckResponse
+ && (
+ <>
+ Results
+
+
+
+
+ Status:
+
+
+ {urlCheckResponse.passed ? 'Passed' : 'Failure'}
+ {urlCheckResponse.passed
+ ? (
+
+ ) : (
+
+ )}
+
+ {urlCheckResponse.errorMessage
+ && (
+ <>
+
+ Error Message:
+
+ {urlCheckResponse.errorMessage}
+ >
+ )}
+
+
+
+
+
+ URL:
+
+
+ {urlCheckResponse.url}
+
+
+
+
+ { (hasAnyRole(['chpl-admin', 'chpl-onc']) || !urlCheckResponse.passed)
+ && (
+ <>
+ Assertions
+
+
+
+ {urlCheckResponse.httpResponseAssertion?.actualValue ? (
+ <>
+
+ HTTP Status Code:
+
+
+
+ {urlCheckResponse.httpResponseAssertion.actualValue}
+
+ {urlCheckResponse.httpResponseAssertion.passed
+ ? (
+
+ ) : (
+
+ )}
+
+
+
+ Reference for HTTP Status Codes
+
+
+ >
+ ) : (
+ <>
+
+ No HTTP Status Code Available:
+
+
+
+ The HTTP response code could not be retrieved or is unavailable.
+
+ {urlCheckResponse.httpResponseAssertion.passed
+ ? (
+
+ ) : (
+
+ )}
+
+ >
+ )}
+
+
+
+
+
+ Response Time (in milliseconds):
+
+ {urlCheckResponse.responseTimeAssertion?.actualValue ?
+ (
+ <>
+
+
+ {urlCheckResponse.responseTimeAssertion.actualValue}
+
+ {urlCheckResponse.responseTimeAssertion.passed
+ ? (
+
+ ) : (
+
+ )}
+
+ >
+ ) : (
+ <>
+
+
+ The response time is empty or unavailable.
+
+ {urlCheckResponse.responseTimeAssertion.passed
+ ? (
+
+ ) : (
+
+ )}
+
+ >
+ )}
+
+
+
+
+ {urlCheckResponse.bodyNotEmptyAssertion?.actualValue ? (
+ <>
+
+ Body Content:
+
+
+
+ {urlCheckResponse.bodyNotEmptyAssertion.actualValue
+ ? urlCheckResponse.bodyNotEmptyAssertion.actualValue
+ : 'Empty body content'}
+
+ {urlCheckResponse.bodyNotEmptyAssertion.passed
+ ? (
+
+ ) : (
+
+ )}
+
+ >
+ ) : (
+ <>
+
+ No Content Available:
+
+
+
+ The body content is empty or unavailable.
+
+ {urlCheckResponse.bodyNotEmptyAssertion.passed
+ ? (
+
+ ) : (
+
+ )}
+
+ >
+ )}
+
+
+
+ >
+ )}
+ >
+ )}
+
+ >
+ );
+}
+
+export default ChplUrlChecker;
+
+ChplUrlChecker.propTypes = {
+};
\ No newline at end of file